Create an IPSec VPN connection with swift

We needed to pro grammatically start an IPSec VPN connection from an App we are working on. Luckily Apple is providing a nice set API for doing that without external library.

To create a VPN connection in iOS you do not need to obtain a network extension entitlement from apple.

Create a VPN

import NetworkExtension

class VPN {

let vpnManager = NEVPNManager.shared();

private var vpnLoadHandler: (Error?) -> Void { return  
        { (error:Error?) in
            if ((error) != nil) {
                print("Could not load VPN Configurations")
                return;
            }
            let p = NEVPNProtocolIPSec()
            p.username = "SOME_USERNAME"
            p.serverAddress = "example.com"
            p.authenticationMethod = NEVPNIKEAuthenticationMethod.sharedSecret

            let kcs = KeychainService();
            kcs.save(key: "SHARED", value: "MY_SHARED_KEY")
            kcs.save(key: "VPN_PASSWORD", value: "MY_PASSWORD"
            p.sharedSecretReference = kcs.load(key: "SHARED")
            p.passwordReference = kcs.load(key: "VPN_PASSWORD)
            p.useExtendedAuthentication = true
            p.disconnectOnSleep = false
            self.vpnManager.protocolConfiguration = p
            self.vpnManager.localizedDescription = "Contensi"
            self.vpnManager.isEnabled = true
            self.vpnManager.saveToPreferences(completionHandler: self.vpnSaveHandler)
    } }

private var vpnSaveHandler: (Error?) -> Void { return  
    { (error:Error?) in
        if (error != nil) {
            print("Could not save VPN Configurations")
            return
        } else {
            do {
                try self.vpnManager.connection.startVPNTunnel()
            } catch let error {
                print("Error starting VPN Connection \(error.localizedDescription)");
                }
            }
        }
        self.vpnlock = false
    }}

public func connectVPN() {  
        //For no known reason the process of saving/loading the VPN configurations fails.On the 2nd time it works
        do {
            try self.vpnManager.loadFromPreferences(completionHandler: self.vpnLoadHandler)
        } catch let error {
            print("Could not start VPN Connection: \(error.localizedDescription)" )
        }
    }

public func disconnectVPN() ->Void {  
        vpnManager.connection.stopVPNTunnel()
}
}

The code does the following:
* Load the preferences
* Change the preferences to the desired values (username, password, URL etc..)
* Save the preferences
* Start the connection

I know that it is weird to load the preferences, and then save it, although we didn't have anything to load. But this is unfortunately how Apple decided that it should be done. If you apply your changes directly and saved to preferences before loading preferences, the save operation will fail. *sighs*

The password and the shared key have to be saved in a specific key chain. You can read more about saving/loading keychain entries for VPN in this other blog post.

Also note that the loadFromPreferences and the saveToPreferences callbacks are asynchronous

One more thing I learnt while figuring out this API is that the call vpnManager.connection.startVPNTunnel() succeeds it does not mean the VPN connection has been established successfully, but it means that the process of establishing a VPN tunnel has been started successfully. Apparently Apple has been writing those APIs for lawyers.

Finally if you want to be notified when the VPN connection has been established successfully, or otherwise has been disconnected, you need to use the NotificationCenter class. I'll describe this in a separate post.

comments powered by Disqus