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) {
}];
}];
この回答は、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/
ありがとうございました :)