在 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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 01:34:31  来源:igfitidea点击:

Connect to VPN programmatically in iOS 8

iosobjective-cios8vpn

提问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 :)

谢谢 :)