Issue connecting to VPN - NETunnelProviderManager

//
//  VpnManager.swift
//  code
//
//  Created by me on 6/5/18.
//  Copyright © 2018 me. All rights reserved.
//

import Foundation
import NetworkExtension

class VpnManager {
    let serverAddress = "myvpndomain"
    let serverPort = "80"
    let mtu = "1500"
    let ip = "10.0.0.2"
    let subnet = "255.255.255.0"
    let dns = ["8.8.8.8", "8.4.4.4"]
    let providerBundleIdentifier = "com.example.project.NetworkTunnelClass"
    let localizedDescription = "Description"
    
    let username="username"
    let password="password"
    
    var manager:NETunnelProviderManager?
    

    /**
     Create an initial tunnel provider manager.
     - returns: The default provider manager.
     */
    func initializeTunnelProviderManager () {
        NETunnelProviderManager.loadAllFromPreferences { (savedManagers: [NETunnelProviderManager]?, error: Error?) in
            if let error = error {
                print(error)
            }
            
            if let savedManagers = savedManagers {
                if savedManagers.count > 0 {
                    //self.manager = savedManagers[0]
                   // print(self.manager?.localizedDescription)
                }
            }
            
            if (self.manager == nil) {
                self.manager = self.createTunnelProviderManager()
            }
            
            self.manager!.saveToPreferences(completionHandler: {(error) -> Void in
                if(error != nil) {
                    print("Error saving to preferences")
                } else {
                    self.manager!.loadFromPreferences(completionHandler: { (error) in
                        if (error != nil) {
                            print("Error loading from preferences")
                        } else {
                            do {
                                print("Trying to start")
                                self.initializeConnectionObserver()
                                try self.manager!.connection.startVPNTunnel()
                            } catch let error as NSError {
                                print(error)
                            } catch {
                                print("There was a fatal error")
                            }
                        }
                    })
                }
            })
        }
    }
    
    /*
     Observe the NEVPN status
    */
    func initializeConnectionObserver () {
        NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: self.manager!.connection, queue: OperationQueue.main) { (notification) -> Void in
            
            if self.manager!.connection.status == .invalid {
                print("VPN configuration is invalid")
            } else if self.manager!.connection.status == .disconnected {
                print("VPN is disconnected.")
            } else if self.manager!.connection.status == .connecting {
                print("VPN is connecting...")
            } else if self.manager!.connection.status == .reasserting {
                print("VPN is reasserting...")
            } else if self.manager!.connection.status == .disconnecting {
                print("VPN is disconnecting...")
            }
        }
    }
    
    
    
    /**
     Create an initial tunnel provider manager.
     - returns: The default provider manager.
     */
    func createTunnelProviderManager () -> NETunnelProviderManager {
        let manager = NETunnelProviderManager()
        
        manager.protocolConfiguration = createTunnelProviderProtocol()
        manager.localizedDescription = localizedDescription
        manager.isEnabled = true
        
        return manager
    }
    
    /**
     Create an initial tunnel provider protocol for the Tunnel Manager.
     - returns: The default provider proticil.
     */
    func createTunnelProviderProtocol () -> NETunnelProviderProtocol  {

        //TODO: Move
        let passwordData = password.data(using: String.Encoding.utf8, allowLossyConversion: false)!
        
        let attributes: [NSObject: AnyObject] = [
            kSecAttrService : UUID().uuidString as AnyObject,
            kSecValueData : passwordData as AnyObject,
            kSecAttrAccessible : kSecAttrAccessibleAlways,
            kSecClass : kSecClassGenericPassword,
            kSecReturnPersistentRef : kCFBooleanTrue
        ]
        
        var result: AnyObject?
        let status = SecItemAdd(attributes as CFDictionary, &result)
        
        if let newPersistentReference = result as? Data , status == errSecSuccess {
            print(newPersistentReference)
            
        }
        
        let providerProtocol = NETunnelProviderProtocol()
        providerProtocol.providerBundleIdentifier = providerBundleIdentifier
        providerProtocol.serverAddress = serverAddress
        providerProtocol.username = username
        providerProtocol.passwordReference = result as? Data
        
        providerProtocol.providerConfiguration = [
            "dns": dns,
            "ip": ip,
            "mtu": mtu,
            "port": serverPort,
            "server": serverAddress,
            "subnet": subnet
        ]
        return providerProtocol
    }
    
}


When trying to start the vpn, I keep recieving the error code: NEVPNErrorDomain Code=2, which translates to


/*! @const NEVPNErrorConfigurationDisabled The VPN configuration is not enabled. */
    NEVPNErrorConfigurationDisabled = 2,


However, as you can see in the method: createTunnelProviderManager, I enable the VPN. What else is causing this error message to be thrown?


Thanks.

Replies

It would help if you maintained a single thread on this topic. I’ve just posted a reply to your earlier thread, not knowing that you’ve made some progress on your own.

When trying to start the vpn, I keep recieving the error code: NEVPNErrorDomain Code=2, which translates to [

NEVPNErrorConfigurationDisabled
]

The most obvious thing to check for here is that you’re setting

isEnabled
to true on the manager. Your code is rather complex, so it’s possible you’ve managed to run some code path that doesn’t set that. To make sure you are I recommend that you add code like this between lines 48 and 49.
NSLog("saving")
assert(self.manager!.isEnabled)

Make sure you see the log statement and that the assert doesn’t fire.

ps I’m still curious as to what platform you’re working on.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for the response.


I added in the assert statement you mentioned above, to verify that I am enabling the manager, and it appears isEnabled is being set on the NETunnelProviderManager object. However, from your response to me in this thread, I noticied in my subclassed NEPacketTunnelProvider, I am not seeing init firing, where I added:



override init() {
        print("NEPacketTunnelProvider init")
        super.init()
    }



I assumed this is due to the fact that the VPN never connects (?), i.e. NEPacketTunnelProvider is instantiated by the system once the VPN successfully connects. Is this accurate?


I had been observing the connection in NEPacketTunnelProvider for its connection change. Once I added an observer to the NETunnelProviderManager as well, as documented in the code above, I receive the following print statements:



Error Domain=NEVPNErrorDomain Code=2 "(null)"

VPN is connecting...

VPN is disconnected.


The error log , where the "NEVPNErrorDomain Code=2" is the log output of the try catch:



do {
     print("Trying to start")
     self.initializeConnectionObserver()
      try self.manager!.connection.startVPNTunnel()
 } catch let error as NSError {
      print(error)
 } catch {
      print("There was a fatal error")
 }


And the connection logs are from the obersver I attached to NEVPNStatusDidChange on the class containing NETunnelProviderManager.


I think it is interesting to note, I do not recieve the error log everytime I run the code, but rather sometimes only recieve the following logs:


VPN is connecting...

VPN is disconnected.


As you can see, I never actually connect to the VPN, as it disconnects immediatley after trying to connect. And the NEVPNErrorDomain Code=2 is the only error response I recieve.


It seems, from what I have been able to scour, that issue is typically due to the NEVPNManager not being enabled. I assumed that by setting isEnabled on the NETunnelProviderManager, it also set enabled on the NEVPNManager, as NETunnelProviderManager extends NEVPNManager. (e.g.


let tunnelManager = NETunnelProviderManager()
tunnelManager.isEnabled = true // manager.connection.manager, where manager is of type NEVPNManager.


).


What other events may cause this error code, as it seems isEnabled has been set?


P.S. I am working on the iPhone SE.

I assumed this is due to the fact that the VPN never connects (?)

That’s not quite right. The

NEPacketTunnelProvider
subclass is actually responsible for running the VPN tunnel, so the sequence runs like this:
  1. Something causes the system to start the VPN tunnel. This can be your app calling

    -NETunnelProviderSession startTunnelWithOptions:andReturnError:]
    , the user starting VPN in Settings, VPN On Demand, or whatever.
  2. The system starts an app extension to host your packet tunnel provider.

  3. The system instantiates your

    NEPacketTunnelProvider
    subclass within that process.
  4. The system calls

    -startTunnelWithOptions:completionHandler:
    on that instance. This starts the process of setting up the tunnel on the wire.
  5. When that’s done your provider does two things:

    • It calls

      -setTunnelNetworkSettings:completionHandler:
      to tell the system about the network settings it’s determined for the tunnel.
    • It calls the completion handler that was passed to

      -startTunnelWithOptions:completionHandler:
      in step 4.

It’s this last step that tells the system that the tunnel is fully active and it can start sending packets through it.

Given that your packet tunnel providers initialiser was never called, it’s seems likely that you have some sort of packaging problem that’s preventing the system from creating the app extension or instantiating your

NEPacketTunnelProvider
subclass within that app extension. You should check that the app extension is in the right place within your app, that its
Info.plist
is set up correctly, specifically that it correctly references the provider subclass name, and that the entitlements are correctly. Beyond that, there may be some hints in the system log as to why this is failing.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Eskimo, thanks for the insight.


From your description, one should really essentially only need a few pieces of code to atleast get the subclassed NEPacketTunnelProvider initialized.


// Container class instantiating NETunnelProviderManager
// note, there will obviously need to be some loading and saving from preferences that needs to take place,
// but that has been removed for demostration purposes
class VPNManager {
     var targetManager = NETunnelProviderManager()
     
     start () {
          let protocolConfiguration = NETunnelProviderProtocol()
          protocolConfiguration.serverAddress = "***.***.xxxx"
          protocolConfiguration.providerBundleIdentifier = "com.myproject.NetworkTunnel"

          self.targetManager.protocolConfiguration = protocolConfiguration
          self.tagetManager.isEnabled = true


          let session = self.targetManager.connection as? NETunnelProviderSession

          do {
               try session?.startTunnel(options: nil)
          } catch {
               print("Error")
          }     
     }
}


// Subclassed NEPacketTunnelProvider
public class NetworkTunnel:NEPacketTunnelProvider {
  
    override init() {
        print("NEPacketTunnelProvider instantiated")
        super.init()
    }
  

     public override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
          print("I am here")
        //call  setTunnelNetworkSettings and completion handler
     }
}
//project layout
- myproject.xcodeproj
     | myproject
          -- NetworkTunnel.swift
          -- VpnManager.swift
// Info.plist
NSExtension
     NSEtensionPointIdentifier          com.apple.networkextension.packet-tunnel
     NSExtensionPrincipleClass          com.myproject.NetworkTunnel


// myproject.entitlements
EntitlementsFile
     Network Extensions
               Item 0 (Network Extensions)     String     Packet Tunnel


It seems to me, one only needs two classes (NETunnelProviderManager, NEPacketTunnelProvider) to start a tunnel, with minor configurations to entitlements and one's plist:


From the simplified code provided mapped to your steps listed respectively:


1. NETunnelProviderManager tries to start a tunnel by casting its connection property to a NETunnelProviderSession and calling startTunnel (could also be self.targetManager.connection.startVpnTunnel() ).


2. The system notes the packet tunnel extension in info.plist and starts the extension process.


3. The process notes the subclassed extension of NEPacketTunnelProvider is NetworkTunnel in info.plist and creates a new instance of it.


4. The system calls startTunnel on the instance of NetworkTunnel.


5. The overridden method startTunnel on NetworkTunnel executes setNetworkSettings and the completion handler, establishing an active connection.


However, I am unsure if it ever does anything past step 1. If I observe the VPNManager class for NEVPNStatusDidChange, I only see that it tries to connect and then disconnects. If startTunnel on the subclassed instance of NEPacketTunnelProvider is supposed to be responsible for actually making the connection, one should see a log from the constructor of the subclassed instance, regardless of if it connects or not, which I do not.


Regarding ensuring that my entitlements and plist is correct, I believe the example I provided should be sufficient to demonstrate that I believe it is, at least from what I have been able to gather from the docs.


Additionally, I checked the phones system logs, but was unable to find anything relating to the process at hand.


Any thoughts on atleast getting the init NEPacketTunnelProvider to fire?

Don’t use

print
for your logging; it’s hard to predict where that will end up, especially in the context of an app extension. Use
NSLog
or, better yet,
os_log
.

Regarding ensuring that my entitlements and plist is correct, I believe the example I provided should be sufficient to demonstrate that I believe it is, at least from what I have been able to gather from the docs.

It’s important that you check the entitlements of the built binary, not the

.entitlements
file. The latter is only one input to the code signing process that sets up the entitlements, and you want to check the output. You also want to make sure that you set the entitlements on both the app and the app extension.

Technote 2415 Entitlements Troubleshooting describes how to check the entitlements of a built binary.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"