VPNs with Packet Tunnel Network Extensions in Swift
The Network Extension framework is one of the most customizable frameworks that Apple provides. Allowing you to customize and extend the core networking features of iOS and macOS.
While this article by Alexander Grebenyuk covers this topic in depth I would like to add some things I have learnt.
I have recently worked on a project implementing a VPN using the OpenVPN protocol. This is not supported natively by the networking framework and requires a third party library, like OpenVPNAdapter.
The WWDC sessions from 2015, 2017 Part 1 and Part 2. Are specially useful to grasp essential points and know how this framework is used.
Using the Packet Tunnel Extension to Implement a VPN
Imagine you want to tunnel all your outgoing traffic using the VPN connection. All your traffic will be sent to your VPN server, that is also your DNS server. This solution can be used to block certain websites, like gambling or adult content and to access an internal company website.
This can be done in iOS using a Packet Tunnel
and for this to be published to the App Store it couldn’t be done with frameworks that require managed devices.
For unmanaged devices there are some things at our hand. Like playing around with the on demand rules. This allows the system to automatically start a VPN connection based on different rules. In this case forcing the system to establish a tunnel whenever it acquires internet connectivity, Cellular or WiFi.
OpenVPN has implemented their own solution for an always on VPN tunnel, called seamless tunnel. The implementation is not public but it seems to work for them.
Implementing a Packet Tunnel Network Extension will divide the app into two targets. Your main app where your app will reside and the target that subclasses NEPacketTunnelProvider
. Subclassing this class will grant us access to a virtual network interface. Creating a packet tunnel provider requires to configure the Info.plist
file.
The main app will only be in charge of doing tasks like configuring de VPN profile into the device Settings app. And the target will be doing all the networking operations: starting, stopping and managing all the states the tunnel could be in.
As there is not a lot of documentation or projects here are some of the things that explain how this framework works and how you can manage to build an always on VPN tunnel on iOS devices.
- As long as your device is charging the tunnel is kept alive. The
KEEPALIVE_TIMEOUT
messages are used to sense the other side of the tunnel and make sure it’s up/down. If your device is unplugged the tunnel could have died and your traffic will be going out using another interface. - Higher level APIs like
URLSession
do not redirect traffic through the interface in the extension. Lower level APIs likecreateTCPConnectionThroughTunnel
can force traffic to go out of the tunnel. - Some seconds after locking your screen the device goes to sleep. Override the
sleep
andwake
methods. In the first one you should quiesce the tunnel as appropiate and with the latter reactivate the tunnel. More info here. - Not 100% sure but if the device is charging the tunnel will never go to sleep.
- Network interface changes can be monitored by subscribing to
defaultPath
, it’s not a good idea to base your reconnecting logic on network changes. The standard approach is to monitor the path associated with your tunnel connection for: the path failing or a new tunnel connection and transition to that. Source. - Understand your underlying tunnel infraestructure. That is, know if an interface change will destroy your tunnel connection or if updating the tunnel settings will affect your connection. Source.
- Code inside the network extension is subject to different rules. A query using
URLSession
done from the network extension doesn’t leave the tunnel using thetun
interface but this done from the main target the petition leaves the device using thetun
interface. - Detecting if the tunnel is active is not an easy task. Using the
OpenVPNAdapter
library there are some properties that arenil
unless the tunnel is connected likesessionName
. I’ve checked if this value isnil
after waking up and in cases where the tunnel was not active the returning value wasn’tnil
so I am not completely sold on this working perfectly. - Read the logs that come from the VPN server. Some of them could indicate tha status your tunnel is in, like the
KEEPALIVE_TIMEOUT
messages. I’ve opened an issue in the OpenVPNAdapter library to treat this messages as errors instead. - There are some cases where a lot of rapid reconnections and changes will be fired and you will want to debounce your actions in this transient event until the connection is stable.