web-dev-qa-db-ja.com

iOS 8でプログラムでVPNに接続する

IOS 8ベータ版のリリース以来、バンドル内にNetwork Extensionフレームワークが見つかりました。これにより、開発者はプログラムを使用して、プロファイルをインストールせずにVPNサーバーを構成および接続できます。

フレームワークには、NEVPNManagerと呼ばれる主要なクラスが含まれています。このクラスには、VPN設定を保存、ロード、または削除できる3つの主要なメソッドもあります。次のように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);
   }
}];

また、ビューにボタンを配置し、TouchUpInsideアクションを以下のメソッドに設定しました。

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

ここには2つの問題があります。

1)プリファレンスを保存しようとすると、次のエラーがスローされます。この問題を解決しますか?

2)[[NEVPNManager sharedManager] .connection startVPNTunnelAndReturnError:&startError];メソッドを呼び出してもエラーは返されませんが、接続ステータスはしばらくの間、切断から接続に変わり、その後、切断状態に戻ります。

助けていただければ幸いです:)

問題は、保存時に取得するエラーです:Save error: Error Domain=NEVPNErrorDomain Code=4

NEVPNManager.hヘッダーファイルを見ると、エラーコード4が「NEVPNErrorConfigurationStale」であることがわかります。構成が古いため、ロードする必要があります。 loadFromPreferencesWithCompletionHandler:完了ハンドラー内を変更する値を変更し、thensaveToPreferencesWithCompletionHandler:を呼び出します。質問の例は、ロードが完了する前に構成を変更しているため、このエラーが発生します。

もっとこのように:

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

この回答は、Network Extensionフレームワークを使用したソリューションを探している人に役立ちます。

私の要件は、IKEv2プロトコルでVPNサーバーを接続/切断することでした(もちろん、vpnManager protocolConfigurationを変更することで、このソリューションをIPSecに使用することもできます)

注:ネットワーク拡張機能を使用してL2TPプロトコルを探している場合、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!

VPN Stausを取得するためのviewDidLoadにオブザーバーを追加し、キーチェーンにvpnPasswordを保存します。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)

 }

私のアプリでは、VPNサーバーを接続/切断するUISwitchがありました。

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

                        }

                    })

                }
            })
        }
    }
}

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/

ありがとうございました :)

8
Shrikant K