How to use the VPN service framework that comes with the Android system

Starting from 4.0 (API LEVEL 15), Android comes with a solution to help establish a VPN connection on the device without root permissions. This article will give a brief introduction to it.

1. Basic Principles

Before introducing how to use these new APIs, let's talk about the basic principles.

On an Android device, if the VpnService framework has been used to establish a VPN link from the device to the remote end, the data packets have roughly undergone the following four transformations on the device:

1) The application uses the socket to send the corresponding data packet to the real network device. Generally, the mobile device only has a wireless network card, so it is sent to the real WiFi device;

2) The Android system forwards all data packets to the TUN virtual network device through iptables and NAT, and the port is tun0;

3) The VPN program can obtain all IP packets forwarded to the TUN virtual network device by opening the /dev/tun device and reading the data on the device. Because all IP packets on the device will be NATed into the original address and sent by the tun0 port, so that means your VPN program can get almost all the data entering and leaving the device (there are exceptions, not all, such as loopback data cannot be get);

4) VPN data can do some processing, and then send the processed data packets through real network equipment. In order to prevent the sent data packets from being transferred to the TUN virtual network device, the socket used by the VPN program must first be explicitly bound to the real network device.

 Second, the code implementation

To implement a VPN program on an Android device, it is generally necessary to implement a client program with UI inherited from the Activity class and a service program inherited from the VpnService class.

declare authority

To make your VPN program work properly, you must first explicitly declare the "android.permission.BIND_VPN_SERVICE" permission in AndroidManifest.xml.

Client program implementation

The client program generally calls the VpnService.prepare function first:

Intent intent = VpnService.prepare(this);  
if (intent != null) {  
    startActivityForResult(intent, 0);  
} else {  
    onActivityResult(0, RESULT_OK, null);  
}
 Currently, Android only supports one VPN connection. If a new program wants to establish a VPN connection, it must first disconnect the VPN connection currently existing in the system.

 

At the same time, because the rights of the VPN program are too great, before the official establishment, a dialog box will pop up to let the user nod to confirm.

The purpose of the VpnService.prepare function is mainly to check whether a VPN connection already exists in the current system, and if so, is it created by this program.

If there is no VPN connection in the current system, or the existing VPN connection is not established by this program, the VpnService.prepare function will return an intent. This intent is used to trigger the confirmation dialog, and the program will then call startActivityForResult to pop up the dialog and wait for the user to confirm. If the user confirms, the previously established VPN connection will be closed and the virtual port will be reset. When the dialog box returns, the onActivityResult function will be called and the user's choice will be informed.

If there is a VPN connection in the current system, and this connection is established by this program, the function will return null, and the user does not need to confirm again. Because the user has already confirmed the VPN connection for the first time in this program, it is not necessary to repeat the confirmation, and just call the onActivityResult function manually.

In the onActivityResult function, the processing is relatively simple:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (resultCode == RESULT_OK) {
  	Intent intent = new Intent(this, MyVpnService.class);
  	startService(intent);
  }
}
 If the returned result is OK, that is, the user agrees to establish a VPN connection, then start the service you wrote and inherit from the VpnService class.

 

Of course, you can also pass some other parameters through intent.

Service program implementation

The service program must inherit from the android .NET .VpnService class: public class MyVpnService extends VpnService ...

The VpnService class encapsulates all the functions necessary to establish a VPN connection, which will be used step by step later.

 

The first step in establishing a link is to create and initialize the tun0 virtual network port with the appropriate parameters. This can be done through an inner class Builder in the VpnService class:

Builder builder = new Builder();  
builder.setMtu(...);  
builder.addAddress(...);  
builder.addRoute(...);  
builder.addDnsServer(...);  
builder.addSearchDomain(...);  
builder.setSession(...);  
builder.setConfigureIntent(...);  
  
ParcelFileDescriptor interface = builder.establish();  
 As you can see, the standard Builder design pattern is used here. Before formally establishing a virtual network interface, several parameters need to be set, namely:

 

1) MTU (Maximun Transmission Unit), which means the maximum transmission unit of the virtual network port. If the length of the sent packet exceeds this number, it will be sub-packaged;

2) Address, the IP address of this virtual network port;

3) Route, only matching IP packets will be routed to the virtual port. If it is 0.0.0.0/0, all IP packets will be routed to the virtual port;

4) DNS Server, which is the DNS server address of the port;

5) Search Domain is to add automatic completion of DNS domain name. The DNS server must search through the full domain name, but it is too troublesome to enter the full domain name for each search, which can be simplified by configuring the automatic completion rule of the domain name;

6) Session, which is the name of the VPN connection you want to establish, it will be displayed in the notification bar and dialog box related to the VPN connection managed by the system;

7) Configure Intent, this intent points to a configuration page to configure the VPN link. It is not necessary. If it is not set, the configuration button will not appear in the VPN-related dialog box that the system pops up.

Finally, call the Builder.establish function. If everything is normal, the tun0 virtual network interface is established. In addition, the NAT table will be modified through the iptables command, and all data will be forwarded to the tun0 interface.

After that, you can get all the IP data packets sent out from the device and return the processed IP data packets to the TCP/IP protocol stack by reading and writing the ParcelFileDescriptor instance returned by VpnService.Builder:

// Packets to be sent are queued in this input stream.  
FileInputStream in = new FileInputStream(interface.getFileDescriptor());  
  
// Packets received need to be written to this output stream.  
FileOutputStream out = new FileOutputStream(interface.getFileDescriptor());  
  
// Allocate the buffer for a single packet.  
ByteBuffer packet = ByteBuffer.allocate(32767);  
...  
// Read packets sending to this interface  
int length = in.read(packet.array());  
...  
// Write response packets back  
out.write(packet.array(), 0, length);  
 The ParcelFileDescriptor class has a getFileDescriptor function, which returns a file descriptor, so that the read and write operations on the interface can be converted into read and write operations on the file.

 

Each time the FileInputStream.read function is called, an IP data packet is read, and the FileOutputStream.write function is called to write an IP data packet to the TCP/IP protocol stack.

In fact, this is basically the whole of this so-called VpnService. Do you think it is a bit strange that it does not involve the establishment of a VPN connection at all. This framework actually just allows an application to easily intercept all the data packets sent and received on the device, nothing more. To obtain these data packets, of course, it is very convenient to encapsulate them and establish a VPN connection with the remote VPN server, but this VpnService framework is not involved, and it is left to your application to solve it by itself.

There is another point to explain in particular, the general application, after obtaining these IP data packets, will send them out through the socket. However, there will be problems in doing this. The socket created by your program is actually no different from the socket created by other programs. After sending out, it will still be forwarded to the tun0 interface and then returned to your program, which is an infinite loop. . In order to solve this problem, the VpnService class provides a function called protect. After the VPN program establishes its own socket, it must be protected: protect(my_socket);

The principle behind it is to bind the socket to the real network interface to ensure that the data packets sent through the socket must be sent through the real network interface and will not be forwarded to the virtual tun0 interface.

Well, the VPN framework provided by the Android system by default has only so much.

Finally, a brief summary:

1) The VPN connection is completely transparent to the application, the application is completely unaware of the existence of the VPN, and does not need to make any changes to support the VPN;

2) You don't need to get root permissions of your Android device to establish a VPN connection. All you need is to declare that you need a special permission called "android.permission.BIND_VPN_SERVICE" in the AndroidManifest.xml file in your application;

3) Before the VPN link is formally established, the Android system will pop up a dialog box that requires the user's explicit consent;

4) Once the VPN connection is established, all the IP packets sent from the Android device will be forwarded to the network interface of the virtual network card (mainly achieved by marking different sockets with fwmark labels and iproute2 policy routing) ;

5) The VPN program can obtain the IP packets sent from all devices by reading the data on this interface; at the same time, any IP data packets can be inserted into the TCP/IP protocol stack of the system by writing data to this interface , and finally sent to the receiving application;

6) Only one VPN connection is allowed to be established in the Android system at the same time. If there is a program that wants to establish a new VPN connection, the existing VPN connection will be interrupted after obtaining the user's consent;

7) Although this framework is called VpnService, it actually only allows the program to obtain all IP packets on the device. Through the simple analysis above, you should have felt that this so-called VPN service can indeed be conveniently used to establish a VPN connection with a remote server on an Android device, but in fact it can also be used to do a lot of fun For example, it can be used as a firewall, or it can be used to capture all IP packets on the device.

Tidy up the code:

MainActivity.java:

 

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btnVpn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
        setContentView(R.layout.activity_main);
        btnVpn = (Button) findViewById(R.id.btn_vpn);
    }

    @Override
    public void onClick(View v) {
        Intent intent = VpnService.prepare(getApplicationContext());
        if (intent != null) {
            startActivityForResult(intent, 0);
        } else {
            onActivityResult(0, RESULT_OK, null);
        }
    }
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            Intent intent = new Intent(this, MyVpnService.class);
            startService(intent);
        }
    }
}
MyVpnService.java
public class MyVpnService extends VpnService {

  private Thread mThread;
  private ParcelFileDescriptor mInterface;
  //a. Configure a builder for the interface.
  Builder builder = new Builder();

  // Services interface
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
  	// Start a new session by creating a new thread.
  	mThread = new Thread(new Runnable() {
  	  @Override
  	  public void run() {
  	  	try {
  	  	  //a. Configure the TUN and get the interface.
  	  	  mInterface = builder.setSession("MyVPNService")
  	  	  	.addAddress("192.168.0.1", 24)
  	  	  	.addDnsServer("8.8.8.8")
  	  	  	.addRoute("0.0.0.0", 0).establish();
  	  	  //b. Packets to be sent are queued in this input stream.
  	  	  FileInputStream in = new FileInputStream(
  	  	  		mInterface.getFileDescriptor());
  	  	  //b. Packets received need to be written to this output stream.
  	  	  FileOutputStream out = new FileOutputStream(
  	  	  		mInterface.getFileDescriptor());
                  //c. The UDP channel can be used to pass/get ip package to/from server
  	  	  DatagramChannel tunnel = DatagramChannel.open();
  	  	  // Connect to the server, localhost is used for demonstration only.
  	  	  tunnel.connect(new InetSocketAddress("127.0.0.1", 8087));
  	  	  //d. Protect this socket, so package send by it will not be feedback to the vpn service.
  	  	  protect(tunnel.socket());
  	  	  //e. Use a loop to pass packets.
  	  	  while (true) {
  	  	  	//get packet with in
  	  	  	//put packet to tunnel
  	  	  	//get packet form tunnel
  	  	  	//return packet with out
  	  	  	//sleep is a must
  	  	  	Thread.sleep(100);
  	  	  }

  	  	} catch (Exception e) {
  	  	  // Catch any exception
  	  	  e.printStackTrace ();
  	  	} finally {
  	  	  try {
  	  	  	if (mInterface != null) {
  	  	  		mInterface.close();
  	  	  		mInterface = null;
  	  	  	}
  	  	  } catch (Exception e) {

  	  	  }
  	  	}
  	  }

  	}, "MyVpnRunnable");

  	//start the service
  	mThread.start();
  	return START_STICKY;
  }

  @Override
  public void onDestroy() {
  	// TODO Auto-generated method stub
  	if (mThread != null) {
  		mThread.interrupt();
  	}
  	super.onDestroy ();
  }
}
 AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
<application>
  <service
	  android:name="com.example.vpnsrv.MyVpnService"
	  android:permission="android.permission.BIND_VPN_SERVICE" >
	  <intent-filter>
		  <action android:name="android.net.VpnService" />
	  </intent-filter>
  </service>
</application>
   

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326350700&siteId=291194637