Подключайтесь к VPN программно в iOS 8
После выпуска бета-версии iOS 8 я обнаружил в своем комплекте инфраструктуру Network Extension, которая позволит разработчикам настраивать и подключаться к VPN-серверам программно и без установки какого-либо профиля.
Каркас содержит основной класс, называемый 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);
}
}];
Я также поместил кнопку в моем представлении и установил для ее действия TouchUpInside следующий метод:
- (IBAction)buttonPressed:(id)sender {
NSError *startError;
[[NEVPNManager sharedManager].connection startVPNTunnelAndReturnError:&startError];
if(startError) {
NSLog(@"Start error: %@", startError.localizedDescription);
}
}
Здесь есть две проблемы:
1) Когда я пытаюсь сохранить настройки, выдается следующее сообщение об ошибке: Ошибка сохранения: Ошибка Домен = Код NEVPNErrorDomain =4 "Операция не может быть завершена. (Ошибка NEVPNErrorDomain 4.)" Что это за ошибка? Как можно Я решаю эту проблему?
2) [[NEVPNManager sharedManager].connection startVPNTunnelAndReturnError: & startError]; Метод не возвращает никакой ошибки, когда я его вызываю, но состояние соединения меняется на "Отключено" на "Соединение" на мгновение, а затем возвращается в состояние "Отключено".
Любая помощь будет оценена:)
4 ответа
Проблема заключается в ошибке, которую вы получаете при сохранении:Save error: Error Domain=NEVPNErrorDomain Code=4
Если вы загляните в файл заголовка NEVPNManager.h, вы увидите, что код ошибки 4 - "NEVPNErrorConfigurationStale". Конфигурация устарела и должна быть загружена. Вам следует позвонить loadFromPreferencesWithCompletionHandler:
и в обработчике завершения измените значения, которые вы хотите изменить, а затем вызовите saveToPreferencesWithCompletionHandler:
, Пример в вашем вопросе - изменение конфигурации до завершения загрузки, поэтому вы получаете эту ошибку.
Больше похоже на это:
[manager loadFromPreferencesWithCompletionHandler:^(NSError *error) {
// do config stuff
[manager saveToPreferencesWithCompletionHandler:^(NSError *error) {
}];
}];
Я опубликовал пост в блоге относительно этого поста. Это полное руководство по управлению VPN-подключением в iOS 8, которое можно найти здесь
Этот ответ будет полезен для тех, кто ищет решение с использованием инфраструктуры расширения сети.
Мое требование состояло в том, чтобы подключить / отключить VPN-сервер по протоколу IKEv2 (конечно, вы можете использовать это решение для IPSec, также изменив конфигурацию протокола vpnManager)
ПРИМЕЧАНИЕ. Если вы ищете протокол L2TP, при использовании расширения сети невозможно подключить VPN-сервер. См. https://forums.developer.apple.com/thread/29909
Вот мой рабочий код:
Объявите VPNManager Object и другие полезные вещи
var vpnManager = NEVPNManager.shared()
var isConnected = false
@IBOutlet weak var switchConntectionStatus: UISwitch!
@IBOutlet weak var labelConntectionStatus: UILabel!
Добавьте наблюдателя в viewDidLoad для получения VPN Staus и сохраните 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)
}
Теперь в моем приложении был 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
}
})
}
}
После нажатия на коммутатор инициируйте 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://forums.developer.apple.com/thread/25928
http://blog.moatazthenervous.com/create-a-vpn-connection-with-apple-swift/
Спасибо:)
Я несколько раз тестировал другие решения и заметил, что
Положив
saveToPreferences
в
loadFromPreferences
это не решение !!
Предположим, мы уже загрузили экземпляр менеджера (или создали новый без загрузки), вызов save внутри обработчика завершения загрузки был для меня случайным (иногда), и, имея мой возраст опыта отладки, казалось, что
iOS
просто нужно время (что-то обработать ?!).
Итак, ниже всегда работает, а не случайным образом:
guard let manager = self.manager else { return }
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.startVPNTunnel()
});
});
}
Обратите внимание, что я удалил загрузку из начала, указанного выше, и загрузка требуется только для запуска, поэтому при использовании вышеуказанной логики загрузка требуется ТОЛЬКО, если у вас еще нет экземпляра (или если он был изменен).