Подключение к VPN программно в iOS 8
С момента выпуска бета-версии iOS 8 я нашел в своем комплекте инфраструктуру расширения сети, которая позволяет разработчикам настраивать и подключаться к VPN-серверам программно и без какой-либо установки профиля.
Структура содержит основной класс NEVPNManager. Этот класс также имеет 3 основных метода, которые позволяют мне сохранять, загружать или удалять настройки VPN. Ive написал кусок кода в методе 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 Code = 4 "Операция не может быть завершена (ошибка NEVPNErrorDomain 4.)" Что это за ошибка? Как я могу решить эту проблему?
2) [[NEVPNManager sharedManager]. connection startVPNTunnelAndReturnError: & startError]; метод не возвращает никаких ошибок, когда я его вызываю, но статус соединения изменяется с отключенного на соединение в течение всего лишь минуты, а затем возвращается в состояние Disconnected.
Любая помощь будет оценена:)
Ответы
Ответ 1
Проблема заключается в ошибке, которую вы получаете при сохранении:
Save error: Error Domain=NEVPNErrorDomain Code=4
Если вы посмотрите в заголовочном файле NEVPNManager.h, вы увидите, что код ошибки 4 "NEVPNErrorConfigurationStale". Конфигурация устарела и должна быть загружена.
Вы должны вызвать loadFromPreferencesWithCompletionHandler:
и в обработчике завершения изменить значения, которые вы хотите изменить, а затем вызвать saveToPreferencesWithCompletionHandler:
. Пример вашего вопроса заключается в изменении конфигурации до завершения загрузки, поэтому вы получаете эту ошибку.
Более того:
[manager loadFromPreferencesWithCompletionHandler:^(NSError *error) {
// do config stuff
[manager saveToPreferencesWithCompletionHandler:^(NSError *error) {
}];
}];
Ответ 2
Я опубликовал сообщение в блоге относительно этой публикации. Это полный учебник по управлению подключением VPN в iOS 8, который можно найти здесь
Ответ 3
Этот ответ будет полезен тем, кто ищет решение, используя инфраструктуру Network Extension.
Мое требование заключалось в подключении/отключении VPN-сервера с протоколом IKEv2 (конечно, вы можете использовать это решение для IPSec также путем изменения конфигурации протокола vpnManager)
ПРИМЕЧАНИЕ. Если вы ищете протокол 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!
Добавить наблюдателя в viewDidLoad для получения VPN Staus и сохранить 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)
}
Теперь в моем приложении было 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
}
}
Я сказал здесь:
fooobar.com/info/239208/...
https://forums.developer.apple.com/thread/25928
http://blog.moatazthenervous.com/create-a-vpn-connection-with-apple-swift/
Спасибо:)