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

Posted

技术标签:

【中文标题】在 iOS 8 中以编程方式连接到 ***【英文标题】:Connect to *** programmatically in iOS 8 【发布时间】:2014-09-18 04:00:36 【问题描述】:

ios 8 测试版发布以来,我在其捆绑包中发现了一个网络扩展框架,它可以让开发人员以编程方式配置和连接到 *** 服务器,而无需安装任何配置文件。

该框架包含一个名为 NE***Manager 的主要类。这个类还有 3 个主要的方法让我保存、加载或删除 *** 首选项。我在 viewDidLoad 方法中写了一段代码如下:

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

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

- (IBAction)buttonPressed:(id)sender 
   NSError *startError;
   [[NE***Manager sharedManager].connection start***TunnelAndReturnError:&startError];
   if(startError) 
      NSLog(@"Start error: %@", startError.localizedDescription);
   

这里有两个问题:

1) 当我尝试保存首选项时,会抛出以下错误: Save error: Error Domain=NE***ErrorDomain Code=4 "The operation could not be completed. (NE***ErrorDomain error 4.)”这是什么错误? 我该如何解决这个问题?

2) [[NE***Manager sharedManager].connection start***TunnelAndReturnError:&startError];方法在我调用它时不会返回任何错误,但是连接状态会从 Disconnected 更改为 Connecting 片刻,然后又回到 Disconnected 状态。

任何帮助将不胜感激:)

【问题讨论】:

奇怪,当我在带有 beta 4 的设备上运行您的代码副本时,我从 [NE***Manager sharedManager] 返回 nil; 啊,我没有在我的 App ID 或权利文件中添加“个人 ***”权利。为此,请转到项目下的“功能” 您如何从钥匙串中检索密码?我已将我帐户的密码存储在钥匙串中,并且我尝试将持久引用(文档暗示这是必要的)检索到 protocol.passwordReference 字段中,但是当我连接到时它仍然提示我输入密码*** 服务(一旦我进入它,我就会连接,所以其他一切看起来都很好)。 使用NE***Manager 类需要com.apple.developer.networking.***.api 权利。您可以通过在 Xcode 中为您的应用启用“个人 ***”功能来为您的应用获取此权利。 为什么不连接免费的 *** ip address 目标 c 【参考方案1】:

问题是您在保存时遇到的错误: Save error: Error Domain=NE***ErrorDomain Code=4

如果您查看 NE***Manager.h 头文件,您会看到错误代码 4 是“NE***ErrorConfigurationStale”。配置过时,需要加载。 您应该调用loadFromPreferencesWithCompletionHandler:在完成处理程序中 修改您要修改的值,然后然后 调用saveToPreferencesWithCompletionHandler:。您问题中的示例是在加载完成之前修改配置,这就是您收到此错误的原因。

更多这样的:

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

【讨论】:

它工作正常!另一件事是,此功能仅适用于真实设备,不适用于模拟器。 顺便说一句:今晚我会在博客上讨论这个,我会提到你;) 你在 beta5 上试过了吗?我有类似的代码可以工作到 beta 4,在 beta 5 上我得到一个错误: Domain=NEConfigurationErrorDomain Code=2 "Missing name" UserInfo=0x170078940 NSLocalizedDescription=Missing name 您对“缺少名称”错误有任何想法吗? xcode 6-beta 6 中出现相同的缺失名称错误!有什么解决办法吗?【参考方案2】:

这个答案对于那些正在寻找使用网络扩展框架的解决方案的人会有帮助。

我的要求是使用 IKEv2 协议连接/断开 *** 服务器(当然,您也可以通过更改 ***Manager 协议配置将此解决方案用于 IPSec)

注意:如果您正在寻找 L2TP 协议,则无法使用网络扩展连接 *** 服务器。参考:https://forums.developer.apple.com/thread/29909

这是我的工作代码 sn-p:

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

var ***Manager = NE***Manager.shared()
var isConnected = false

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

在 viewDidLoad 中添加观察者以获取 *** Staus 并将 ***Password 存储在 Keychain 中,您也可以存储 sharedSecret,这将需要 IPSec 协议。

override func viewDidLoad() 

    super.viewDidLoad()

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

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.***StatusDidChange(_:)), name: NSNotification.Name.NE***StatusDidChange, object: nil)

 

现在在我的应用程序中使用 UISwitch 连接/断开 *** 服务器。

func switchClicked() 

    switchConntectionStatus.isOn = false

    if !isConnected 
        init***TunnelProviderManager()
    
    else
        ***Manager.removeFromPreferences(completionHandler:  (error) in

            if((error) != nil) 
                print("*** Remove Preferences error: 1")
            
            else 
                self.***Manager.connection.stop***Tunnel()
                self.labelConntectionStatus.text = "Disconnected"
                self.switchConntectionStatus.isOn = false
                self.isConnected = false
            
        )
    

点击开关后,使用以下代码启动 *** 隧道。

func init***TunnelProviderManager()

    self.***Manager.loadFromPreferences  (error) -> Void in

        if((error) != nil) 
            print("*** Preferences error: 1")
        
        else 

            let p = NE***ProtocolIKEv2()
// 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("***Password")

            p.passwordReference = data
            p.authenticationMethod = NE***IKEAuthenticationMethod.none

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

            p.useExtendedAuthentication = true
            p.disconnectOnSleep = false

            self.***Manager.protocolConfiguration = p
            self.***Manager.isEnabled = true

            self.***Manager.saveToPreferences(completionHandler:  (error) -> Void in
                if((error) != nil) 
                    print("*** Preferences error: 2")
                
                else 


                    self.***Manager.loadFromPreferences(completionHandler:  (error) in

                        if((error) != nil) 

                            print("*** Preferences error: 2")
                        
                        else 

                            var startError: NSError?

                            do 
                                try self.***Manager.connection.start***Tunnel()
                            
                            catch let error as NSError 
                                startError = error
                                print(startError)
                            
                            catch 
                                print("Fatal Error")
                                fatalError()
                            
                            if((startError) != nil) 
                                print("*** Preferences error: 3")
                                let alertController = UIAlertController(title: "Oops..", message:
                                    "Something went wrong while connecting to the ***. 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.***StatusDidChange(nil)
                                print("*** started successfully..")
                            

                        

                    )

                
            )
        
    

一旦 *** 成功启动,您可以相应地更改状态,即通过调用 ***StatusDidChange

func ***StatusDidChange(_ notification: Notification?) 

    print("*** Status changed:")
    let status = self.***Manager.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://***.com/a/47569982/3931796

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

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

谢谢你:)

【讨论】:

我已按照您的步骤进行操作,但是在连接到 *** 时收到“意外错误”警报,我的 ikev2 服务器使用 .crt 并且我已经将它安装在我的设备上,手动添加的配置效果很好,只有具有相同配置的个人 *** 出现错误 安装的 .crt 可以同时使用添加的配置和个人 *** 吗?还是我们必须为 Personal *** 做其他事情才能使用证书? KeychainSwift 在这里不起作用,它只是将字符串作为数据返回,您需要一个钥匙串参考 blog.moatazthenervous.com/create-a-key-chain-for-apples-*** @KooroshGhorbani 我在使用此类时遇到了同样的问题,它在保存首选项时崩溃了。【参考方案3】:

我多次测试其他解决方案并注意到

saveToPreferences 放入loadFromPreferences 是不够的!!

考虑到我们已经加载了一个管理器实例(或在没有加载的情况下创建了新实例),在 load 的完成处理程序中调用 save 对我来说是随机工作(有时),并且具有我多年的调试经验,看起来 iOS 只是需要时间(处理一些东西?!)。

所以以后排队总是有效的,而不是随机的,比如:

guard let manager = self.manager else  return 

manager.loadFromPreferences(completionHandler:  _ in
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) 
        // Change some settings.
        manager.isEnabled = true
        // Save changes directly (without another load).
        manager.saveToPreferences(completionHandler: 
            [weak self] (error) in
            if let error = error 
                manager.isEnabled = false;
                print("Failed to enable - \(error)")
                return
            
            // Establishing tunnel really needs reload.
            manager.loadFromPreferences(completionHandler:  [weak self] (error) in
                if let error = error 
                    print("Failed to reload - \(error)")
                    return
                
                let session = (manager.connection as! NETunnelProviderSession);
                session.start***Tunnel()
            );
        );
    
);

注意,我在上面的主线程中进行了加载,但为了确定,只有在您还没有实例(或已更改)时才需要加载。

【讨论】:

以上是关于在 iOS 8 中以编程方式连接到 ***的主要内容,如果未能解决你的问题,请参考以下文章

在 Nodejs 中以编程方式连接到 Mongodb Amazon EC2 实例

获取连接到Mac Machine的设备的UDID

获取连接到 Mac 机器的设备的 UDID

如何在 Swift 中以编程方式创建新的 UIViewController

如何在 iOS 8 中以编程方式隐藏状态栏 [重复]

在 iOS 8 中以编程方式将视图中的按钮居中使用约束