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 like createTCPConnectionThroughTunnel can force traffic to go out of the tunnel.
  • Some seconds after locking your screen the device goes to sleep. Override the sleep and wake 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 the tun interface but this done from the main target the petition leaves the device using the tun interface.
  • Detecting if the tunnel is active is not an easy task. Using the OpenVPNAdapter library there are some properties that are nil unless the tunnel is connected like sessionName. I've checked if this value is nil after waking up and in cases where the tunnel was not active the returning value wasn't nil 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.