Android VPN Service Explained with Packet Bypass Example Program

by Terrence Sun on June 9, 2014

AndroidAndroid provides a user level interface for VPN services with which programmer only need to focus on the interaction with remote server.

Other actions, such as virtual interface creation, address and route configuration are done by OS.

From the APP side, there are two components for a VPN connection, the client and the service.

1. Make a VPN connection from Client

For the client, an intent for the VPN service must be required by the call of VpnService.prepare(). The intent can make sure that there is only one active VPN connection. If there is already a prepared VPN connection (e.g. stop and then start the same VPN connection manually), null will be returned. For which case, we should call the onActivityResult function manually.

Below is the code used to start a VPN service on Button’s onClick callback.

public void onClick(View v) {
  Intent intent = VpnService.prepare(getApplicationContext());
  if (intent != null) {
  	startActivityForResult(intent, 0);
  } else {
  	onActivityResult(0, RESULT_OK, null);
  }
}

After the intent finished or null returned, VPN service can be started by the calling of startService, with a intent of a VPN service as the parameter.

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (resultCode == RESULT_OK) {
  	Intent intent = new Intent(this, MyVpnService.class);
  	startService(intent);
  }
}

2. Implement a VPN Service

There are two function must be implemented when extending the Service class, the onStartCommand and the onDestroy. Usually a Thread can be used to provide the service in background, which can be start or interrupted/destroyed in above functions.

Android creates a TUN interface for VPN service, and provides API used by APP to interact with the TUN. We should do the following to implement a VPN service:

  1. Use a Builder to obtain an interface (FileDescription) for the TUN. The ip address, dns, and route table can be configured via the Builder. Please make sure the addRoute is properly called, as route table determinants which packet will be routed to the TUN.
  2. Get a input and output stream for the interface. Input stream and output stream are used to read and write packets in a manner of stream.
  3. Make a connection to the VPN server. Programmer can make any kinds of logic to handle the packet, usually use a connection to tunnel packet to the remote VPN server.
  4. Protect the tunnel socket from the VPN service. In order to avoid the deal loop of packet (tunnel packet be routed back to the TUN other than remote server), the connection must be protected (keep untouched) from the VPN service.
  5. Loop with the packet.

The inputstream (in), outputstream (out), tunnel connection (tunnel) and remote server work as below:

Network Activity -> TUN -> in -> tunnel -> Remote Server
Network Activity <- TUN <- out <- tunnel <- Remote Server

Below code shows these steps.

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();
  }

3. Permission

Service and Internet permission should be declared.

<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>

4. A Packet bypass Implementation

Using the service, we can build many kinds of interesting Apps, such as Proxy, Package Filter, Bandwidth Saver. There is an example (ToyVpn) in Android Samples for SDK (file path: sdk\samples\android-17\ToyVpn ), which can also be found in google source code here.

Here we are going to implement a packet bypass function, in which without the remote server, we bypass/filter packets that go through the VPN service.

The TUN is working on OSI layer 3, so what be read and write here is the IP packet.

From APP level, Android/Java do not support raw socket, so we can not directly bypass these IP packet into network interface.

But we can protect socket from VPN service, so its possible to use a protected socket to send/get the packet. TCP/UDP socket is working on OSI layer 4, so we need do a layer translate here:

  1. Get IP packet from TUN. Same as all VPN service does.
  2. Extract layer 4 information. Protocol type (e.g. TCP/UDP) and its payload is a must. As there is a handshake procedure in TCP, before getting actually payload data from it, we need to write back handshake packet first.
  3. Choose corresponding socket to send out the payload. As this step is working on layer 4, so we need to save the socket and try to get return data later. If there is any return data, we need to pass these packet to TUN.
  4. Get packet from socket, and build a layer 3 packet. First, we need to build a valid layer 4 packet. UDP is a bit easier as the 4 byte UDP header only contains source address, source port, destination address, destination port. TCP is more complex as it’s a state connection, the sequence number and acknowledge number should be properly set. Then, use the layer 4 packet as payload, we need to build a valid layer 3 packet.
  5. Write IP packet back to TUN. Same as all VPN service does.

5. Suggestions

Building these IP packet is not a easy thing, the suggestion is to use a packeting filter/capture tool like Wireshark to check it first.

ByteBuffer is a good choice for packet build and value retrieve, but keep in mind that Java treat short/long as signed values, do convert it to unsigned first.

Take UDP packet first. After the TUN is connected, usually OS will send out some DNS queries, which is on UDP port 53. The nslookup command is a good test for it, if something goes wrong, a timeout error will be reported.


Linux Sysadmin Course Linux provides several powerful administrative tools and utilities which will help you to manage your systems effectively. If you don’t know what these tools are and how to use them, you could be spending lot of time trying to perform even the basic administrative tasks. The focus of this course is to help you understand system administration tools, which will help you to become an effective Linux system administrator.
Get the Linux Sysadmin Course Now!

If you enjoyed this article, you might also like..

  1. 50 Linux Sysadmin Tutorials
  2. 50 Most Frequently Used Linux Commands (With Examples)
  3. Top 25 Best Linux Performance Monitoring and Debugging Tools
  4. Mommy, I found it! – 15 Practical Linux Find Command Examples
  5. Linux 101 Hacks 2nd Edition eBook Linux 101 Hacks Book

Bash 101 Hacks Book Sed and Awk 101 Hacks Book Nagios Core 3 Book Vim 101 Hacks Book

{ 10 comments… read them below or add one }

1 Sam June 27, 2014 at 4:55 am

Thanks a lot for this article.
Just one thing, do you have a working implementation of the packet bypass function? If yes, could you post it as example please.

Sam

2 cougar July 7, 2014 at 4:02 am

This tutorial is real helpful, thanks a lot !!
But can i set user/password to connect or use a .ovpn config file to connect ? when i finish step 3, my app shows that there’s a connection to VPN, but in fact, this connection doesn’t work, i can’t realize
the step 4, is there any help ? Thanks!!

3 Sauvik July 8, 2014 at 2:33 am

Hi,i’m having a little trouble with the following points. Could you please help me out?

– Does the TUN interface created by the VPN Service act like a tunnel, or like a capturing net that pushes packets into the actual tunnel?

– The address that is configured for the interface using the builder (builder.setSession.addAddress()) — shouldn’t it be the Local Loopback address if the VPN Service is implemented inside the app itself?

And thanks so much for posting this article!

4 TerrenceSun July 25, 2014 at 10:44 am

@cougar,
What about the server/remote side? Yes, If you have go to step 3, from APP side, there is a connection. But whether it works or not depending on if you properly send packet to the remote server. Maybe you can go through the ToyVpn example first.

5 TerrenceSun July 25, 2014 at 10:54 am

@Sauvik,
Tunnel is a must for VPN service. What we are implementing here is the tunnel function, the tunnel interface is done by OS. From App’s side, every packet goes to the TUN. From VPN service’s side, the packet will be send to remote server, which meet the definition of tunneling.
The address is assign to the TUN interface, it should be a local address, but not be a local loopback address. The server/remote also use the address to provide proper networking service. A local loopback address will mess up many things.

6 Mudra August 4, 2014 at 8:23 am

Terence

You say ‘TCP is more complex as it’s a state connection, the sequence number and acknowledge number should be properly set. Then, use the layer 4 packet as payload, we need to build a valid layer 3 packet.’

So if you are writing a bypass or a filter or the like, it seems like you have to keep track of the current sequence number and ack number expected by the client running on the client side that is talking to your bypass service, and build responses appropriately. Fortunately, you shouldn’t have losses on this link, but you still need to do a lot of state management. This looks like a very complex service to write. It woudl be much easier if one could use raw sockets !

If you want to do packet filtering or bypass, it seems like you have to basically do all TCP packet management yourself, i.e. keeping track of sequences

7 Mudra August 4, 2014 at 8:25 am

So I forgot to mention what I was asking — which is basically ” Is my assumption correct, or is there some way to avoid implementing a mini user-level TCP stack? if you want to do a bypass ?”

8 TerenceSun August 4, 2014 at 7:44 pm

@Mudra
You are right. Using a raw socket is the right way. But it’s not possible for non rooted device. Besides, there are already some user level tcp/ip stack projects, it is possible to port one here, especially for these that used in transparent proxy.

9 MSmith August 20, 2014 at 2:23 pm

Great article @TerenceSun

I have a similar problem as @Cougar though. My VPN Server requires me to pass a username and password to it in order to establish a connection. Any idea on how I would do that?

Thanks again

10 ashokkumar November 4, 2014 at 1:15 am

Do we have similar VPN service API in IOS? I need to implement VPN service in IOS app.

Leave a Comment

Previous post:

Next post: