Android provides APIs for developers to create virtual private network (VPN) solutions. After reading this guide, you will know how to develop and test your own VPN client for Android devices.
VPNs allow devices that are not physically connected to a network to securely access the network.
Android includes a built-in VPN client (PPTP and L2TP / IPSec), which is sometimes called Old VPN. Android 4.0 (API level 14) introduced APIs so that app developers could provide their own VPN solutions. You bundle your VPN solution into an app that people install on the device. Developers usually create a VPN app for one of the following reasons:
- To provide VPN protocols that the built-in client does not support.
- To help people connect to a VPN service without complicated configuration.
The remainder of this guide explains how to develop VPN applications (including always-on and per-application VPNs) and does not cover the built-in VPN client.
Android provides a user interface (UI) to help someone configure, start, and stop your VPN solution. The system user interface also makes the person using the device aware of an active VPN connection. Android displays the following UI components for VPN connections:
- Before the VPN app becomes active for the first time, the system displays a connection request dialog. The dialog prompts the person using the device to confirm that they trust the VPN and accept the request.
- The VPN Settings screen (Settings > Network & Internet > VPN) shows the VPN apps where the person has accepted connection requests. There is a button to configure system options or forget VPN.
- The Quick Settings panel displays a dashboard when the connection is active. Clicking on the label displays a dialog with more information and a link to Settings.
- The status bar includes a VPN icon (key) to indicate an active connection.
Your application also needs to provide a user interface so that the person using the device can configure your service options. For example, your solution may need to record account authentication settings. Applications must show the following user interface:
- Controls the start and stop of the connection manually. An always-on VPN can connect when needed, but allows people to configure the connection the first time they use your VPN.
- Non-rejectionable notice when the service is active. The notification can show the status of the connection or provide more information – such as network statistics. Tapping on the notification brings your app to the fore. Remove the notification after the service becomes inactive.
Your app connects the user’s system network (or work profile) to the VPN gateway. Each user (or work profile) can run a different VPN app. It creates a VPN service that the system uses to start and stop the VPN, and keeps track of the connection status. Your VPN service inherits from VpnService.
The service also acts as a container for VPN gateway connections and its local hardware interfaces. Your service instance calls the VpnService.Builder methods to create a new local interface.
Figure 1. How VpnService Connects Android Networks to a VPN Gate
Your app transmits the following data to connect the device to the VPN gateway:
- It reads outgoing IP packets from the local interface file descriptor, encodes them, and sends them to the VPN gateway.
- Writes incoming packets (received and decrypted from the VPN gateway) to the local interface file descriptor.
warning: Your app must use strong encryption when transmitting data to and from the VPN gateway.
There is only one active service per user or profile. Starting a new service automatically stops an existing service.
To add a VPN service to your app, create an Android service inherited from VpnService. Declare the VPN service in your application manifest with the following extensions:
- Protect the service with the BIND_VPN_SERVICE permission so that only the system can associate with your service.
- Advertise the service with the target filter “android.net.VpnService” so that the system can find your service.
This example shows how you can declare a service in an application manifest file:
Now that your app declares the service, the system can start the app’s VPN service and stop it automatically when needed. For example, the system controls your service when you turn on an always-on VPN.
To set up the app to become the user’s current VPN service, call VpnService.prepare(). If the person using the device has not already given permission to your app, the method returns the activity target. This intent is used to initiate a system activity that requests permission. The system displays a dialog similar to other permission dialogs, such as Access to Camera or Contacts. If your app is already ready, the method will return null.
Only one application can be the VPN service currently set up. Always call VpnService.prepare() because someone may have set a different app as the VPN service since your app called the method. To learn more, see the Service Lifecycle section.
Once the service is up and running, you can create a new local interface that is connected to the VPN gateway. To request permission and connect your service to the VPN gateway, you need to complete the steps in the following order:
- Call VpnService.prepare() to request permission (if needed).
- Call VpnService.protect() to keep your application tunneling socket out of the system VPN and avoid circular connection.
- Call DatagramSocket.connect() to connect your application tunnel socket to the VPN gateway.
- Call VpnService.Builder methods to configure a new local TUN interface on the machine for VPN traffic.
- Call VpnService.Builder.establish() so that the system creates the local TUN interface and starts routing traffic through the interface.
The VPN gateway usually suggests the local TUN interface settings during a handshake. Your application calls the VpnService.Builder methods to configure a service as shown in the following sample:
// Create a new interface from our VpnService instance. This should be done // from within the VpnService. val builder = Builder() // Build a local TUN interface using predefined addresses. In your app, // you usually use the values returned from the VPN gateway during the handshake. val localTunnel = builder .adddress(“192.168.2.2”, 24) .addRoute(“0.0.0.0”, 0).addDnsServer(“192.168.1.1”) .establish()
// Create a new interface from our VpnService instance. This should be done // from within the VpnService. VpnService.Builder builder = new VpnService.Builder(); // Create a local TUN interface using predefined addresses. In your app, // you usually use the values returned from the VPN gateway during the handshake. ParcelFileDescriptor localTunnel = builder .addAddress(“192.168.2.2”, 24).addRoute(“0.0.0.0”,0).addDnsServer(“192.168.1.1”).establish();
The example in the VPN section for each application shows an IPv6 configuration including more options. You need to add the following VpnService.Builder values before you can create a new interface:
addAddress() Add at least one IPv4 or IPv6 address with a subnet mask that the system allocates as the address of the local TUN interface. Your app usually receives IP addresses and subnet masks from the VPN gateway during a handshake. addRoute() Add at least one route if you want the system to send traffic through the VPN interface. Filter paths by destination addresses. To accept all traffic, set an open path like 0.0.0.0/0 or ::/0.
The build() method returns an instance of ParcelFileDescriptor that your app uses to read and write packets to and from the interface buffer. The build() method returns null if your app is not ready or if someone has revoked the permission.
Service life cycle
Your app should keep track of the system specific VPN status and any active connections. Update the application user interface (UI) to keep the person using the device informed of any changes.
The VPN service can be started in the following ways:
- Your app starts the service — usually because the person clicked the connect button.
- The system starts the service because the VPN is always on.
Your app starts a VPN service by passing an intent to startService(). To learn more, read Getting Started.
The system starts your service in the background by calling onStartCommand(). However, Android places restrictions on background apps at version 8.0 (API level 26) or higher. If you support these API levels, you need to move your service to the foreground by calling Service.startForeground(). To learn more, read Turn on a service in the foreground.
Anyone using the device can stop your service using the application user interface. Stop the service instead of just closing the connection. The system also stops the active connection when the person using the device does the following in the VPN screen of the Settings app:
- Disconnect or forget the VPN app
- Turns off the always-on VPN to get an active connection
The system calls the service’s onRevoke() method but this call may not occur in the main thread. When the system calls this method, an alternate network interface is actually routing the traffic. You can safely dispose of the following resources:
Always on VPN
Android can start the VPN service when the device is turned on and keep it running while the device is on. This feature is called Always On VPN Available in Android 7.0 (API Level 24) or higher. While Android maintains the service lifecycle, your VPN service is responsible for the VPN gateway connection. An always-on VPN can block connections that are not using a VPN.
In Android 8.0 or higher, the system displays the following dialogs to make the person using the device aware of the always-on VPN:
- When always-on VPN connections drop or can’t connect, people see a non-rejectable notification. Clicking on the notification brings up a dialog that says more. The notification disappears when the VPN reconnects or someone turns off the always-on VPN option.
- An always-on VPN allows the person using a device to block any network connections that are not using a VPN. When this option is turned on, the Settings app warns people that they don’t have an internet connection before the VPN connects. The Settings app prompts the person using the device to continue or cancel.
Since the system (not the person) always initiates and stops a connection, you need to adapt the behavior of the application and the user interface:
- Disable any user interface that disconnects the connection because the System and Settings app controls the connection.
- Save any configuration between starting each application and configure a connection with the latest settings. Since the system starts your app on demand, the person using the device may not always want to configure a connection.
You can also use managed configurations to configure a connection. Managed configurations help your IT administrator configure your VPN remotely.
Android does not include APIs to confirm whether the system has started your VPN service. But, when your app determines which service states to start, you can assume that the system started unmarked VPN services that are always running. This is an example:
- Create an Intent instance to start the VPN service.
- Report the VPN service by putting an additional element in the intent.
- In the service’s onStartCommand() method, find the tag in the target argument’s extensions.
Anyone using the device (or an IT administrator) can force all traffic to use a VPN. The system blocks any network traffic that does not use a VPN. People using the device can find a file Block connections without a VPN Switch in VPN…