在iOS 8中以编程方式连接到VPN

自iOS 8 beta发布以来,我在其捆绑包中发现了一个networking扩展框架,它将允许开发人员以编程方式configuration和连接到VPN服务器,而无需任何configuration文件安装。

该框架包含一个名为NEVPNManager的主类。 这个类也有3个主要的方法,让我保存,加载或删除VPN偏好。 我用viewDidLoad方法写了一段代码,如下所示:

NEVPNManager *manager = [NEVPNManager sharedManager]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(vpnConnectionStatusChanged) name:NEVPNStatusDidChangeNotification object:nil]; [manager loadFromPreferencesWithCompletionHandler:^(NSError *error) { if(error) { NSLog(@"Load error: %@", error); }}]; NEVPNProtocolIPSec *p = [[NEVPNProtocolIPSec alloc] init]; p.username = @“[My username]”; p.passwordReference = [KeyChainAccess loadDataForServiceNamed:@"VIT"]; p.serverAddress = @“[My Server Address]“; p.authenticationMethod = NEVPNIKEAuthenticationMethodCertificate; p.localIdentifier = @“[My Local identifier]”; p.remoteIdentifier = @“[My Remote identifier]”; p.useExtendedAuthentication = NO; p.identityData = [My VPN certification private key]; p.disconnectOnSleep = NO; [manager setProtocol:p]; [manager setOnDemandEnabled:NO]; [manager setLocalizedDescription:@"VIT VPN"]; NSArray *array = [NSArray new]; [manager setOnDemandRules: array]; NSLog(@"Connection desciption: %@", manager.localizedDescription); NSLog(@"VPN status: %i", manager.connection.status); [manager saveToPreferencesWithCompletionHandler:^(NSError *error) { if(error) { NSLog(@"Save error: %@", error); } }]; 

我还在视图中放置了一个button,并将其TouchUpInside操作设置为以下方法:

 - (IBAction)buttonPressed:(id)sender { NSError *startError; [[NEVPNManager sharedManager].connection startVPNTunnelAndReturnError:&startError]; if(startError) { NSLog(@"Start error: %@", startError.localizedDescription); } } 

这里有两个问题:

1)当我尝试保存首选项时,会出现以下错误:保存错误:错误域= NEVPNErrorDomain代码= 4“操作无法完成(NEVPNErrorDomain错误4.)”这是什么错误?我解决这个问题?

2)[[NEVPNManager sharedManager] .connection startVPNTunnelAndReturnError:&startError]; 方法不会返回任何错误,当我打电话,但连接状态从断开连接变为连接一会儿,然后回到断开状态。

任何帮助将不胜感激 :)

问题是您Save error: Error Domain=NEVPNErrorDomain Code=4时得到的错误: Save error: Error Domain=NEVPNErrorDomain Code=4

如果您查看NEVPNManager.h头文件,您将看到错误代码4是“NEVPNErrorConfigurationStale”。 configuration是陈旧的,需要加载。 您应该调用loadFromPreferencesWithCompletionHandler:在完成处理程序中修改要修改的值, 然后调用saveToPreferencesWithCompletionHandler: 在你的问题中的例子是在加载完成之前修改configuration,这就是为什么你得到这个错误。

更像这样:

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

我已经发布了关于这篇文章的博客文章。 这是一个关于iOS 8中的VPN连接pipe理的完整教程,可以在这里find

这个答案将有助于那些正在寻找使用networking扩展框架的解决scheme。

我的要求是连接/断开VPN服务器与IKEv2协议(当然,您也可以通过更改vpnManager protocolConfiguration来使用IPSec的这个解决scheme)

注意:如果您正在寻找L2TP协议,使用networking扩展它不可能连接VPN服务器。 请参阅: https : //forums.developer.apple.com/thread/29909

这是我的工作代码片段:

声明VPNManager对象和其他有用的东西

 var vpnManager = NEVPNManager.shared() var isConnected = false @IBOutlet weak var switchConntectionStatus: UISwitch! @IBOutlet weak var labelConntectionStatus: UILabel! 

在viewDidLoad中添加观察者以获得VPN Staus并将vpnPassword存储在Keychain中,也可以存储sharedSecret,这将需要IPSec协议。

 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) } 

现在在我的应用程序是有UISwitch连接/断开VPN服务器。

 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 } }) } } 

点击开关后,使用下面的代码启动VPN隧道。

 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 ie 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..") } } }) } }) } } } 

一旦VPN启动成功,您可以相应地更改状态,即通过调用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 } } 

我从这里提到:

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

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

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

谢谢 :)