web-dev-qa-db-fra.com

Se connecter à VPN par programmation dans iOS 8

Depuis la sortie de la version bêta d'iOS 8, j'ai trouvé un framework d'extension réseau dans son bundle qui va permettre aux développeurs de configurer et de se connecter aux serveurs VPN par programmation et sans aucune installation de profil.

Le framework contient une classe majeure appelée NEVPNManager. Cette classe a également 3 méthodes principales qui me permettent d'enregistrer, de charger ou de supprimer les préférences VPN. J'ai écrit un morceau de code dans la méthode viewDidLoad comme suit:

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);
   }
}];

J'ai également placé un bouton dans ma vue et défini son action TouchUpInside sur la méthode ci-dessous:

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

Ici, nous avons deux problèmes:

1) Lorsque j'essaie d'enregistrer les préférences, l'erreur suivante s'affiche: Erreur d'enregistrement: domaine d'erreur = NEVPNErrorDomain Code = 4 "L'opération n'a pas pu être terminée. (Erreur NEVPNErrorDomain 4.)" Quelle est cette erreur? Comment Je résous ce problème?

2) [[NEVPNManager sharedManager] .connection startVPNTunnelAndReturnError: startError]; ne renvoie aucune erreur lorsque je l'appelle, mais l'état de la connexion passe de Déconnecté à Connexion pendant un instant, puis il revient à l'état Déconnecté.

Toute aide serait appréciée :)

23

Le problème est l'erreur que vous obtenez lors de l'enregistrement: Save error: Error Domain=NEVPNErrorDomain Code=4

Si vous regardez dans le fichier d'en-tête NEVPNManager.h, vous verrez que le code d'erreur 4 est "NEVPNErrorConfigurationStale". La configuration est périmée et doit être chargée. Vous devez appeler loadFromPreferencesWithCompletionHandler: et dans le gestionnaire de complétion modifiez les valeurs que vous souhaitez modifier, et alors appelez saveToPreferencesWithCompletionHandler:. L'exemple de votre question consiste à modifier la configuration avant la fin du chargement, c'est pourquoi vous obtenez cette erreur.

Plus comme ceci:

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

Cette réponse sera utile pour ceux qui recherchent une solution en utilisant le framework Network Extension.

Mon exigence était de connecter/déconnecter le serveur VPN avec le protocole IKEv2 (bien sûr, vous pouvez également utiliser cette solution pour IPSec en changeant la configuration du protocole vpnManager)

REMARQUE: Si vous recherchez le protocole L2TP, l'utilisation de l'extension réseau n'est pas possible de connecter un serveur VPN. Voir: https://forums.developer.Apple.com/thread/29909

Voici mon extrait de code de travail:

Déclarez l'objet VPNManager et d'autres choses utiles

var vpnManager = NEVPNManager.shared()
var isConnected = false

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

Ajouter un observateur dans viewDidLoad pour obtenir VPN Staus et stocker vpnPassword dans le trousseau, vous pouvez également stocker sharedSecret, dont vous aurez besoin du protocole 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)

 }

Maintenant, dans mon application, UISwitch devait connecter/déconnecter le serveur 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
            }
        })
    }
}

Après avoir cliqué sur le commutateur, lancez le tunnel VPN en utilisant le code ci-dessous.

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

                        }

                    })

                }
            })
        }
    }
}

Une fois le VPN démarré avec succès, vous pouvez modifier l'état en conséquence, c'est-à-dire en appelant 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
    }
}

Je me suis référé d'ici:

https://stackoverflow.com/a/47569982/3931796

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

http://blog.moatazthenervous.com/create-a-vpn-connection-with-Apple-Swift/

Merci :)

8
Shrikant K