HideMe VPN Blog

Download Free VPN apps

iOS 9 VPN API - The Definitive Guide to Network Extension API NEPacketTunnelProvider and NWUDPSession

HideMe VPN, 5 Aug 16 02:34 UTC

iOS 9 VPN API has a powerful new feature called enterprise VPN which allows third party developers to build custom VPN solution. This article serves as a guideline about how to program iOS 9 network extension.

Background

Developing custom VPN protocol on iOS devices had been an impossible task to do prior to iOS 9 because Apple did not provide official tun/tap driver and its related programming interface.

In session 717 WWDC 2015, Apple announced its official support of custom VPN for the first time.

session-717-wwdc-2015

session-717-wwdc-2015

A new set of API has been added to the Network Extension library, with which any third party developer can customize and extend the core iOS networking features.

The most significant change is, starting from iOS 9 and OSX 10.11, developers can use the NETunnelProvider family of APIs to connect iOS and OS X devices to a VPN server that uses a non-standard network tunneling protocol, such as an SSL-VPN server.

The NETunnelProvider family of APIs gives apps the ability to implement the client side of a custom network tunneling protocol, called a Tunnel Provider. A tunnel provider is a special kind of app extension, which is called network extension.

The two major classes in this set of API are:

Though the interfaces are clearly designed, writing a working VPN network extension is not as simple as “hello world”. The truth is, even writing a “hello world” network extension takes a lot work.

We will show you how to accomplish the task step by step.

Getting Network Extension Entitlement

Before doing anything with Network Extension, email to networkextension@apple.com and ask for the extra entitlement from Apple.

networkextension@apple.com

Without this entitlement, your dummy code will end up with endless NSError.

getting-network-extension-entitlement

Apple will send back a form asking a lot information about your company, your team, product, how you are going to use network API (ipv4 or ipv6, UDP or TCP, etc).

The screenshot shows a few of those questions.

form

Fill in the form carefully and send it back to Apple.

We got our entitlement after waiting for 2 weeks.

network-entitlement-acknowledge

Installing the Network Extension Xcode Target Template

After obtaining the entitlement, we can go ahead and install the network extension template for Xcode.

This module can be found at this location on any OS X 10.11 machine

/System/Library/Frameworks/NetworkExtension.framework/Resources/NEProviderTargetTemplate.pkg

Install this package.

Now we get a new set of application extension templates in Xcode.

new-target

Packet Tunnel Provider is what we need to develop VPN solution on iOS 9.

Creating new provisioning profiles

We created two new developer provisioning profiles for the VPN app, one for the app, the other for the app extension.

Since we have already got Network Extension entitlement in step 1, we can see it on the drop down list while creating a new provisioning profile.

Select this option.

create-provisioning-profile

Later, we will repeat the procedure and create two new profiles for production usage, both with network extension entitlement enabled.

Adding a new target into the project

An iOS VPN app is made up of two components.

The ordinary UIApplication component takes care of user interaction such as setting VPN server address, username, password. In addition, it controls the VPN extension by calling NETunnelProviderManager.

The second kind of component, NEPacketTunnelProvider, takes care of the real input output operations that make VPN work.

For those unfamiliar with the general concept behind App Extension, this document is a good starting point.

hideme-vpn-code

Controlling VPN Programmatically

Now we are talking about VPN code in depth.

NETunnelProviderManager is the core class - and the only class we need - to control VPN programmatically on iOS 9.

NETunnelProviderManager is used to configure and control enterprise VPN connections provided by a Tunnel Provider extension.

Each NETunnelProviderManager instance corresponds to a single VPN configuration stored in the Network Extension preferences. 

ios-vpn-settings

We use the core class NETunnelProviderManager to control VPN, there are four operations that we usually do:

  1. Create or load VPN profiles
  2. Start VPN
  3. Query VPN status
  4. Stop VPN

Let us go through these operations one by one.

Load VPN profiles

Call this method to load all existing VPN profiles from the system preferences.

(void)loadAllFromPreferencesWithCompletionHandler:(void (^) (NSArray<NETunnelProviderManager *> *managers, NSError *error))completionHandler

Apple docs NEVER tells us how to create a new enterprise VPN profile.

They only suggest us deploy enterprise VPN through over-the-air configuration deployment mechanism.

Okay, in fact, we can make a VPN profile in this way.

NETunnelProviderManager *manager = [[NETunnelProviderManager alloc] init];
NETunnelProviderProtocol *protocol = [[NETunnelProviderProtocol alloc] init];
protocol.providerBundleIdentifier = @"com.tigervpns.hideme.ios.HideMe.HideMeTunnelProvider";	// bundle ID of tunnel provider
protocol.providerConfiguration = @{@"key": @"value"};
protocol.serverAddress = @“server”;		// VPN server address
hideme.protocolConfiguration = protocol;
[manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) { … }

Get it?

No need to use over-the-air VPN configuration. A simple alloc-init statement can finish the job.

Setup the NETunnelProviderProtocol object with appropriate values. The most important field is providerBundleIdentifier, which MUST be the bundle ID of our custom network extension target.

Start VPN

Start VPN by calling the following code.

NETunnelProviderSession *session = (NETunnelProviderSession*) manager.connection;
NSDictionary *options = @{@“key” : @“value”};		// Send additional options to the tunnel provider
NSError *err;
[session startTunnelWithOptions:options andReturnError:&err];

Query VPN Status

To update user interface, we need know the current status of VPN.

Use this property to get VPN status.

manager.connection.status

Its values are defined as a enum.

Stop VPN

NETunnelProviderSession *session = (NETunnelProviderSession*) manager.connection;
[session stopTunnel];

NEPacketTunnelProvider,the I/O event loop of VPN

NEPacketTunnelProvider is the principal class for the Packet Tunnel Provider extension.

To control VPN we must subclass NEPacketTunnelProvider and override at least the following two methods.

- (void)startTunnelWithOptions:(NSDictionary *)options completionHandler:(void (^)(NSError *))completionHandler
- (void)stopTunnelWithReason:(NEProviderStopReason)reason completionHandler:(void (^)(void))completionHandler

When startTunnelWithOptions is invoked, iOS loads the network extension binary into memory.

startTunnelWithOptions takes two arguments.

options is a user defined key-value dictionary. Apps can pass any information through this argument to the VPN extension, like server address, user credentials, debug settings, etc.

completionHandler is a Objective-C block, generated by iOS. We MUST store this block to some variable, and later we will call this block when VPN connection phase is done.

stopTunnelWithReason also takes two arguments.

reason tells our app why VPN is shut down, its values are defined in NEProviderStopReason.

The purpose of completionHandler is same as the second argument of startTunnelWithOptions.

There are two threads of control in the VPN I/O loop.

  1. Read IP packets from TUN device, do some encryption, and send them out through the UDP endpoint.

  2. The reverse direction, read encrypted packets from UDP, decrypt, and write data to TUN device.

We will take advange of the asynchrous I/O model in the Network Extension API to build up the above I/O operations.

First, we need a socket to communicate with the VPN server.

NWHostEndpoint *peer = [NWHostEndpoint endpointWithHostname:_ip port:_port];

And, we will use UDP as our transport layer.

NWUDPSession *udpSession = [self createUDPSessionToEndpoint:peer fromEndpoint:nil]];

Here self is the instance of our NEPacketTunnelProvider subclass。

Now we can write some message to the udpSession.

[udpSession writeDatagram:packet completionHandler:^(NSError * _Nullable error) {….}];

Finally we tell udpSession how to handle inbound datagrams by setting up its read handler, a kind of callback function.

[udpSession
setReadHandler:(void (^)(NSArray<NSData *> *datagrams,
                                 NSError *error))handler
          maxDatagrams:(NSUInteger)maxDatagrams
]

iOS will call this handler when UDP receives datagrams from the server side.

Now let us turn to the TUN device side.

Suppose our VPN server returns the following configuration information.

The following code can bring up the virtual network interface vtun0.

NSArray *addresses = @[“10.0.1.100];
NSArray *subnetMasks = @[@"255.255.255.0"];
NSArray<NSString *> *dnsServers = @[“8.8.8.8”, “8.8.4.4”];

NEPacketTunnelNetworkSettings *settings = [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:[vpn_server_public_ip_address]];

settings.IPv4Settings = [[NEIPv4Settings alloc] initWithAddresses:addresses subnetMasks:subnetMasks];
NEIPv4Route *defaultRoute = [NEIPv4Route defaultRoute];
NEIPv4Route *localRoute = [[NEIPv4Route alloc] initWithDestinationAddress:@"10.0.0.0" subnetMask:@"255.255.255.0"];
settings.IPv4Settings.includedRoutes = @[defaultRoute, localRoute];
settings.IPv4Settings.excludedRoutes = @[];
settings.MTU = [NSNumber numberWithInt:1500];
[self setTunnelNetworkSettings:settings completionHandler:^(NSError * _Nullable error) {
            if (error) {
                NSLog(@"setTunnelNetworkSettings error: %@", error);
            } else {
                NSError *err;
                pendingCompletionHandler(err);
}];

Pay attention to the pendingCompletionHandler variable, it is the second argument passed into startTunnelWithOptions.

When everything goes well, iOS brings up its VPN module at this moment, we will see the VPN icon on the notification bar.

vpn-connected  And our NEPacketTunnelProvider instance starts its I/O loop immediately.

How could we get IP packets from vtun0?

Well, NEPacketTunnelProvider has a property called packetFlow, an instance of NEPacketTunnelFlow.

We can use the method readPacketsWithCompletionHandler to get IP packets from vtun0.

To write packets back to vtun0, simply call writePackets:withProtocols:.

Conclusion

Network Extension is an elegant abstraction of what we need to develop third party VPN solutions.

It makes complex concepts as simple as several method calls. Through these APIs, we are able to create UDP socket, read and write socket, bring up network interface, update system level routing table, and finally read and write virtual network interface.

VPN is only one of the applications of what we can make use of Network Extension. We will delve into more usage in future posts.

Do not forget to download our product and take a look how Network Extension works in practice.

Download HideMe VPN