在 iOS 8 中以编程方式连接到 VPN
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24970280/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Connect to VPN programmatically in iOS 8
提问by Mohammad M. Ramezanpour
Since the release of iOS 8 beta, I found a Network Extension framework in its bundle which is going to let developers configure and connect to VPN servers programmatically and without any profile installation.
自 iOS 8 测试版发布以来,我在其捆绑包中发现了一个网络扩展框架,该框架将使开发人员能够以编程方式配置和连接到 VPN 服务器,而无需安装任何配置文件。
The framework contains a major class called NEVPNManager. This class also has 3 main methods that let me save, load or remove VPN preferences. I've written a piece of code in viewDidLoad method as following:
该框架包含一个名为 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);
}
}];
I also placed a button in my view and set its TouchUpInside action to the method below:
我还在我的视图中放置了一个按钮,并将其 TouchUpInside 操作设置为以下方法:
- (IBAction)buttonPressed:(id)sender {
NSError *startError;
[[NEVPNManager sharedManager].connection startVPNTunnelAndReturnError:&startError];
if(startError) {
NSLog(@"Start error: %@", startError.localizedDescription);
}
}
There are two problems here:
这里有两个问题:
1) When I try to save the preferences, the following error will be thrown: ?Save error: Error Domain=NEVPNErrorDomain Code=4 "The operation couldn't be completed. (NEVPNErrorDomain error 4.)”?What is this error? How can I solve this issue?
1) 当我尝试保存首选项时,会抛出以下错误: ?Save error: Error Domain=NEVPNErrorDomain Code=4 “操作无法完成。(NEVPNErrorDomain error 4.)”?这是什么错误?我该如何解决这个问题?
2) [[NEVPNManager sharedManager].connection startVPNTunnelAndReturnError:&startError]; method doesn't return any error when I call it but the connection status changes from Disconnected to Connecting for just a moment and then it gets back to Disconnected state.
2) [[NEVPNManager sharedManager].connection startVPNTunnelAndReturnError:&startError]; 方法在我调用它时不会返回任何错误,但是连接状态从 Disconnected 变为 Connecting 片刻,然后又回到 Disconnected 状态。
Any help will be appreciated :)
任何帮助将不胜感激 :)
回答by quellish
The problem is the error you are getting when saving:
Save error: Error Domain=NEVPNErrorDomain Code=4
问题是您在保存时遇到的错误:
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 handlermodify the values you want to modify, and thencall saveToPreferencesWithCompletionHandler:
. The example in your question is modifying the configuration before the loading is completed, which is why you are getting this error.
如果您查看 NEVPNManager.h 头文件,您将看到错误代码 4 是“NEVPNErrorConfigurationStale”。配置已过时,需要加载。您应该调用loadFromPreferencesWithCompletionHandler:
并在完成处理程序中修改要修改的值,然后调用saveToPreferencesWithCompletionHandler:
. 您问题中的示例是在加载完成之前修改配置,这就是您收到此错误的原因。
More like this:
更像这样:
[manager loadFromPreferencesWithCompletionHandler:^(NSError *error) {
// do config stuff
[manager saveToPreferencesWithCompletionHandler:^(NSError *error) {
}];
}];
回答by Shrikant K
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)
我的要求是使用 IKEv2 协议连接/断开 VPN 服务器(当然,您也可以通过更改 vpnManager 协议配置来将此解决方案用于 IPSec)
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
注意:如果您正在寻找 L2TP 协议,则使用网络扩展无法连接 VPN 服务器。参考:https: //forums.developer.apple.com/thread/29909
Here is my working code snippet :
这是我的工作代码片段:
Declare VPNManager Object and other useful things
声明 VPNManager 对象和其他有用的东西
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.
在viewDidLoad中添加观察者以获取VPN状态并将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)
}
Now in my app was having UISwitch to connect/disconnect VPN server.
现在在我的应用程序中有 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
}
})
}
}
After clicking on switch initiate VPN tunnel using below code.
单击开关后,使用以下代码启动 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 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
一旦 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
}
}
I've referred from here :
我从这里提到:
https://stackoverflow.com/a/47569982/3931796
https://stackoverflow.com/a/47569982/3931796
https://forums.developer.apple.com/thread/25928
https://forums.developer.apple.com/thread/25928
http://blog.moatazthenervous.com/create-a-vpn-connection-with-apple-swift/
http://blog.moatazthenervous.com/create-a-vpn-connection-with-apple-swift/
Thank you :)
谢谢 :)