5 Replies
      Latest reply on Jun 15, 2018 10:51 AM by eskimo
      camdeng14 Level 1 Level 1 (0 points)
        //
        //  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.

        • Re: Issue connecting to VPN - NETunnelProviderManager
          eskimo Apple Staff Apple Staff (9,685 points)

          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"

            • Re: Issue connecting to VPN - NETunnelProviderManager
              camdeng14 Level 1 Level 1 (0 points)

              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.

                • Re: Issue connecting to VPN - NETunnelProviderManager
                  eskimo Apple Staff Apple Staff (9,685 points)

                  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"

                    • Re: Issue connecting to VPN - NETunnelProviderManager
                      camdeng14 Level 1 Level 1 (0 points)

                      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 = "xxx.xxx.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?

                        • Re: Issue connecting to VPN - NETunnelProviderManager
                          eskimo Apple Staff Apple Staff (9,685 points)

                          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"