≡ Menu

Android VPN Service Explained with Packet Bypass Example Program

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.

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

{ 16 comments… add one }

  • Sam June 27, 2014, 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

  • cougar July 7, 2014, 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!!

  • Sauvik July 8, 2014, 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!

  • TerrenceSun July 25, 2014, 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.

  • TerrenceSun July 25, 2014, 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.

  • Mudra August 4, 2014, 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

  • Mudra August 4, 2014, 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 ?”

  • TerenceSun August 4, 2014, 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.

  • MSmith August 20, 2014, 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

  • ashokkumar November 4, 2014, 1:15 am

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

  • omy January 2, 2015, 5:08 am

    Can we somehow bypass remote server? I mean, not sending data to the tunnel but the real destination directly?

  • omy January 10, 2015, 6:47 pm

    you write: 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);
    }

    but how to get packet with in? as we don’t know the size of the packet in advance, when we read some bytes from in (inputstream), these bytes might contain more than one IP packet or might just be part of an IP packet but not the whole IP packet.

    from the bytes read from in, how can we reconstruct the packets?

    thanks a lot in advance if you can shed some light on this issue….

  • TerrenceSun January 10, 2015, 10:45 pm

    @omy
    If with root access, we can write packet directly into any interface as we want.
    But, with the VPN api, we can only write into the tunnel.

    Please note that, the in as an instance of FileInputStream is constructed from a VPN interface. If we set buffer to the maximum size of IP packet, say 65535 byte, the integrity of packet is guaranteed. The return value of in.read records the actual size of the packet.

  • omy January 11, 2015, 12:14 am

    @Terence: thanks a lot for the reply.

    I’m ok with what you replied: ‘If with root access, we can write packet directly into any interface as we want.
    But, with the VPN api, we can only write into the tunnel.’

    But if we set buffer to the maximum size of IP packet, say 65535 bytes, i’m ok with the fact that the integrity of the packet is guaranteed but what if there are more than one packets in these 65535 packets?

    After reading from “in” stream, of course, we can write to the tunnel the bytes read. There is no problem…that’s done in ToyVpn example. But how to reconstruct/rebuild IP packets on the fly from these bytes received? I hope you got my question?

  • omy January 11, 2015, 12:49 am

    Sorry…a mistake in the previous comment: “these 65535 packets” —-> “these 65535 bytes”

    And just to elaborate a bit my previous comment: when we try to read 65535 bytes, let’s say that the length of the bytes actually read is 60000. Now the challenge is to know if it’s one IP packet or there are N ip packets in these 60000 bytes or there are N + a part of N+1th ip packet?

  • omy January 11, 2015, 5:53 am

    @Terence: I might still not be clear to you…so I explain you my problem once again…

    The thing is: Writing the bytes read from “in” to “tunnel” is ok. The problem is I want to play with the bytes read from “in” and convert these bytes to meaningful ip packets…and then, look at different layers (for example, TCP headers)…

    Is it clearer?

Leave a Comment