Connect to VPN programmatically in iOS 8

十年热恋 提交于 2019-11-27 17:37:57

The problem is the error you are getting when saving: Save error: Error Domain=NEVPNErrorDomain Code=4

If you look in the NEVPNManager.h header file, you will see that error code 4 is "NEVPNErrorConfigurationStale". The configuration is stale and needs to be loaded. You should call loadFromPreferencesWithCompletionHandler: and in the completion handler modify the values you want to modify, and then call saveToPreferencesWithCompletionHandler:. The example in your question is modifying the configuration before the loading is completed, which is why you are getting this error.

More like this:

[manager loadFromPreferencesWithCompletionHandler:^(NSError *error) {
     // do config stuff
     [manager saveToPreferencesWithCompletionHandler:^(NSError *error) {
     }];
}];

This answer will be helpful for those who are looking for solution using Network Extension framework.

My requirement was to connect/disconnect VPN server with IKEv2 Protocol (of course you can use this solution for IPSec also by changing vpnManager protocolConfiguration)

NOTE : If you are looking for L2TP Protocol, Using Network extension its not possible to connect VPN server. Refer : https://forums.developer.apple.com/thread/29909

Here is my working code snippet :

Declare VPNManager Object and other useful things

var vpnManager = NEVPNManager.shared()
var isConnected = false

@IBOutlet weak var switchConntectionStatus: UISwitch!    
@IBOutlet weak var labelConntectionStatus: UILabel!

Add observer in viewDidLoad for getting VPN Staus and store vpnPassword in Keychain, you can store sharedSecret too which will you need IPSec protocol.

override func viewDidLoad() {

    super.viewDidLoad()

    let keychain = KeychainSwift()
    keychain.set("*****", forKey: "vpnPassword")

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.VPNStatusDidChange(_:)), name: NSNotification.Name.NEVPNStatusDidChange, object: nil)

 }

Now in my app was having UISwitch to connect/disconnect VPN server.

func switchClicked() {

    switchConntectionStatus.isOn = false

    if !isConnected {
        initVPNTunnelProviderManager()
    }
    else{
        vpnManager.removeFromPreferences(completionHandler: { (error) in

            if((error) != nil) {
                print("VPN Remove Preferences error: 1")
            }
            else {
                self.vpnManager.connection.stopVPNTunnel()
                self.labelConntectionStatus.text = "Disconnected"
                self.switchConntectionStatus.isOn = false
                self.isConnected = false
            }
        })
    }
}

After clicking on switch initiate VPN tunnel using below code.

func initVPNTunnelProviderManager(){

    self.vpnManager.loadFromPreferences { (error) -> Void in

        if((error) != nil) {
            print("VPN Preferences error: 1")
        }
        else {

            let p = NEVPNProtocolIKEv2()
// You can change Protocol and credentials as per your protocol i.e IPSec or IKEv2

            p.username = "*****"
            p.remoteIdentifier = "*****"
            p.serverAddress = "*****"

            let keychain = KeychainSwift()
            let data = keychain.getData("vpnPassword")

            p.passwordReference = data
            p.authenticationMethod = NEVPNIKEAuthenticationMethod.none

//          p.sharedSecretReference = KeychainAccess.getData("sharedSecret")! 
// Useful for when you have IPSec Protocol

            p.useExtendedAuthentication = true
            p.disconnectOnSleep = false

            self.vpnManager.protocolConfiguration = p
            self.vpnManager.isEnabled = true

            self.vpnManager.saveToPreferences(completionHandler: { (error) -> Void in
                if((error) != nil) {
                    print("VPN Preferences error: 2")
                }
                else {


                    self.vpnManager.loadFromPreferences(completionHandler: { (error) in

                        if((error) != nil) {

                            print("VPN Preferences error: 2")
                        }
                        else {

                            var startError: NSError?

                            do {
                                try self.vpnManager.connection.startVPNTunnel()
                            }
                            catch let error as NSError {
                                startError = error
                                print(startError)
                            }
                            catch {
                                print("Fatal Error")
                                fatalError()
                            }
                            if((startError) != nil) {
                                print("VPN Preferences error: 3")
                                let alertController = UIAlertController(title: "Oops..", message:
                                    "Something went wrong while connecting to the VPN. Please try again.", preferredStyle: UIAlertControllerStyle.alert)
                                alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.default,handler: nil))

                                self.present(alertController, animated: true, completion: nil)
                                print(startError)
                            }
                            else {
                                self.VPNStatusDidChange(nil)
                                print("VPN started successfully..")
                            }

                        }

                    })

                }
            })
        }
    }
}

Once VPN started successfully you can change the status accordingly i.e. by calling VPNStatusDidChange

func VPNStatusDidChange(_ notification: Notification?) {

    print("VPN Status changed:")
    let status = self.vpnManager.connection.status
    switch status {
    case .connecting:
        print("Connecting...")
        self.labelConntectionStatus.text = "Connecting..."
        self.switchConntectionStatus.isOn = false
        self.isConnected = false

        break
    case .connected:
        print("Connected")
        self.labelConntectionStatus.text = "Connected"
        self.switchConntectionStatus.isOn = true
        self.isConnected = true
        break
    case .disconnecting:
        print("Disconnecting...")
        self.labelConntectionStatus.text = "Disconnecting..."
        self.switchConntectionStatus.isOn = false
        self.isConnected = false

        break
    case .disconnected:
        print("Disconnected")
        self.labelConntectionStatus.text = "Disconnected..."
        self.switchConntectionStatus.isOn = false
        self.isConnected = false

        break
    case .invalid:
        print("Invalid")
        self.labelConntectionStatus.text = "Invalid Connection"
        self.switchConntectionStatus.isOn = false
        self.isConnected = false

        break
    case .reasserting:
        print("Reasserting...")
        self.labelConntectionStatus.text = "Reasserting Connection"
        self.switchConntectionStatus.isOn = false
        self.isConnected = false

        break
    }
}

I've referred from here :

https://stackoverflow.com/a/47569982/3931796

https://forums.developer.apple.com/thread/25928

http://blog.moatazthenervous.com/create-a-vpn-connection-with-apple-swift/

Thank you :)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!