NESMVPNSession disconnected

Hi,
I have a problem with my OpenVPN connection on my app with iOS 14.4.
I perform my VPN configuration from an oven file, with a NETunnelProviderManager protocol, but when I perform the startVPNTunnel, it starts connecting and immediately disconnects. The error I see in the logs is the following:

NESMVPNSession[Primary Tunnel:OpenVPN Client: -----(null)]: status changed to disconnected, last stop reason Plugin was disabled

This happens to me when running my app on a physical iPad.

Regards


Code Block import NetworkExtension
import OpenVPNAdapter
class VPNConnection {
    
    var connectionStatus = "Disconnected"
        
    var myProviderManager: NETunnelProviderManager?
    
    func manageConnectionChanges( manager:NETunnelProviderManager ) -> String {
        NSLog("Waiting for changes");
        var status = "Disconnected"
        
        NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: manager.connection, queue: OperationQueue.main, using: { notification in
            
            let baseText = "VPN Status is "
            
            switch manager.connection.status {
            case .connected:
                status = "Connected"
            case .connecting:
                status = "Connecting"
            case .disconnected:
                status = "Disconnected"
            case .disconnecting:
                status = "Disconnecting"
            case .invalid:
                status = "Invalid"
            case .reasserting:
                status = "Reasserting"
            default:
                status = "Connected"
            }
            
            self.connectionStatus = status
            
            NSLog(baseText+status)
            
        });
        return status
    }
    
    func createProtocolConfiguration() -> NETunnelProviderProtocol {
        guard
            let configurationFileURL = Bundle.main.url(forResource: "app-vpn", withExtension: "ovpn"),
            let configurationFileContent = try? Data(contentsOf: configurationFileURL)
        else {
            fatalError()
        }
        
        let tunnelProtocol = NETunnelProviderProtocol()
        tunnelProtocol.serverAddress = ""
        tunnelProtocol.providerBundleIdentifier = "com.app.ios"
        
        tunnelProtocol.providerConfiguration = ["ovpn": String(data: configurationFileContent, encoding: .utf8)! as Any]
        tunnelProtocol.disconnectOnSleep = false
        
        return tunnelProtocol
    }
    
    func startConnection(completion:@escaping () -> Void){
        self.myProviderManager?.loadFromPreferences(completionHandler: { (error) in
            guard error == nil else {
                // Handle an occurred error
                return
            }
            
            do {
                try self.myProviderManager?.connection.startVPNTunnel()
                print("Tunnel started")
            } catch {
                fatalError()
            }
        })
    }
    
    func loadProviderManager(completion:@escaping () -> Void) {
        
        
        NETunnelProviderManager.loadAllFromPreferences { (managers, error) in
            guard error == nil else {
                fatalError()
                return
            }
            
            self.myProviderManager = managers?.first ?? NETunnelProviderManager()
            self.manageConnectionChanges(manager: self.myProviderManager!)
            
            self.myProviderManager?.loadFromPreferences(completionHandler: { (error) in
                guard error == nil else {
                    fatalError()
                    return
                }
                
                let tunnelProtocol = self.createProtocolConfiguration()
                
                self.myProviderManager?.protocolConfiguration = tunnelProtocol
                self.myProviderManager?.localizedDescription = "OpenVPN Client Ubic"
                
                self.myProviderManager?.isEnabled = true
                
                self.myProviderManager?.isOnDemandEnabled = false
                
                self.myProviderManager?.saveToPreferences(completionHandler: { (error) in
                    if error != nil  {
                        // Handle an occurred error
                        fatalError()
                    }
                    self.startConnection {
                        print("VPN loaded")
                    }
                })
            })
        }
    }
}


Replies

Hi, I have the same problem and haven't found a solution yet.
Did you manage to solve it?

I think the main problem is in certificates and identifiers.
Try updating your Info. plist by adding new values there, as shown here:
https://developer.apple.com/forums/thread/669690?answerId=653497022#653497022

Please, if you manage to solve this problem, let me know:)

Also, for a more in-depth study of this problem, I'm going to review these videos:
Watch them too if you haven't solved this problem yet.

[Creating and Debugging iOS Network Extensions, Jeroen Leenarts (English)](https://www.youtube.com/watch?v=f9A1nGqEgIw)


[Andrii Doroshko - Network Extension (Built-in VPN) @ Kharkiv iOS User Group #4 15.06.19 (Russian)]
https://www.youtube.com/watch?v=bPHgicqCfzQ

Hi,

I could see that when trying to connect I get the following error:

NESMVPNSession[Primary Tunnel:OpenVPN Client:---------------------:(null)] in state NESMVPNSessionStateStarting: plugin NEVPNTunnelPlugin(com.app.ios[inactive]) started with PID 0 error Error Domain=NEAgentErrorDomain Code=2 "(null)"

I have seen that it could be because of permissions, this is my .entitlement and my .plist

Code Block <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>allow-vpn</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.ID</string>
</array>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)ID.*</string>
</array>
</dict>
</plist>


Code Block <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>NameApp</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>


Can you help me?
I'm not sure how OpenVPN has their Network Extension configured, or if you are constructing this by yourself. However, it looks like this may be a Packet Tunnel Network Extension implementation and not a macOS Network System Extension. If that is correct then you can remove the Personal VPN entitlement for allow-vpn.

When I create a Packet Tunnel Network Extension from scratch, I usually start by doing the following:
  1. Start with a call to loadAllFromPreferencesWithCompletionHandler when you container app loads up.. Just to make sure you load any existing NETunnelProviderManagers.

  2. Call loadAllFromPreferencesWithCompletionHandler again to set any existing NETunnelProviderManager, or create a new one.

  3. Inside the callback for (2), load and configure, or create any NETunnelProviderProtocol or NETunnelProviderManager objects.

  4. Inside the callback for (3) with the newly created manager call saveToPreferencesWithCompletionHandler.

  5. Call loadFromPreferencesWithCompletionHandler on the new manager.

  6. Get the NEVPNConnection from the NETunnelProviderSession inside of the callback from (5) and call startTunnelWithOptions.

  7. Create your virtual interface settings with NEPacketTunnelNetworkSettings.

  8. Set those settings from (7) to the NEPacketTunnelProvider using setTunnelNetworkSettings. This is usually done inside startTunnelWithOptions method.

  9. Upon the success of setTunnelNetworkSettings call your packet managing class to start reading / writing packets. and then call the completion handler on startTunnelWithOptions.


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
@meaton

Hmm, I suspect that you have mixed up the sequence of actions.
Can you give a pseudo or real code?

Sorry, but step 6 can't be done.

Get the NEVPNConnection from the NETunnelProviderSession inside of the callback from (5) and call startTunnelWithOptions.

NETunnelProviderSession hasn't method, that can return 'NEVPNConnection'.

Code Block objective-c
@interface NETunnelProviderSession : NEVPNConnection
- (BOOL)startTunnelWithOptions:(nullable NSDictionary<NSString *,id> *)options andReturnError:(NSError **)error;
- (void)stopTunnel;
- (BOOL)sendProviderMessage:(NSData *)messageData returnError:(NSError **)error responseHandler:(nullable void (^)( NSData * __nullable responseData))responseHandler;
@end

@meaton, I followed your recommendation and removed PersonalVPN.
But it didn't help me.
All the methods I call in this sequence:


Code Block objective-c
-(void)initProvider
{
    __weak typeof(self) weak = self;
    // (1) load
    [NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager*>* _Nullable managers, NSError* _Nullable error) {
        if(error){
            NSLog(@"loadAllFromPreferencesWithCompletionHandler error: %@",error); return;
        }
        // (2) create providerProtocol
        NETunnelProviderProtocol *tunel = [[NETunnelProviderProtocol alloc] init];
        tunel.providerBundleIdentifier  = @"mySite.com.MyVPN.PacketTunnel";
        tunel.providerConfiguration     = @{ @"ovpn" : self.configData };
        tunel.serverAddress             = @"vpn.mySite.com";
        tunel.disconnectOnSleep         = NO;
        // (3) create providerManager
        weak.providerManager = managers.firstObject ? managers.firstObject : [NETunnelProviderManager new];
        weak.providerManager.protocolConfiguration = tunel;
        weak.providerManager.localizedDescription  = @"MYVPN";
        [weak.providerManager setEnabled:YES];
        // (4) save configuration
        [weak.providerManager saveToPreferencesWithCompletionHandler:^(NSError *error) {
            NSLog(@"%@", (error) ? @"Saved with error" : @"Save successfully");
            if(error) return;
            
            // (5) load again
            [weak.providerManager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
                if(error){
                     NSLog(@"loadFromPreferencesWithCompletionHandler error: %@",error); return;
                }
                // (6) connect
                [weak connection];
            }];
        }];
    }];
}

Selector for UIButton
Code Block objective-c
- (void)connection
{    
    __weak typeof(self) weak = self;
    [self.providerManager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
        if(!error){
            [weak.providerManager.connection startVPNTunnelAndReturnError:&error];
            if(error) {
                NSLog(@"Start error: %@", error.localizedDescription);
            }else{
                NSLog(@"Connection established!");
            }
        }else{
            NSLog(@"connection error:%@",error);
        }
    }];
}

My Info.plist and .entitlements configurations (link to images).
Code Block objective-c
https://ibb.co/k22ksfj
https://ibb.co/GpYbZCs
https://ibb.co/9sSkr5y

P.S.
Before building application, I press "Debug">"Attach to process by PID">(Select 'PacketTunnel').
Then I run application.
But my NSLog in PacketTunnelProviders don't print (!).

I don't know what I must do....


But my NSLog in PacketTunnelProviders don't print (!).

Is your execution getting to your Packet Tunnel Provider but just not being configured correctly in the provider? When I debug this I usually put a os_log statement in the constructor.

Code Block Objective-c
#import <os/log.h>
@property os_log_t log;
...
@implementation PacketTunnelProvider
- (id) init {
if( self = [super init] ) {
log = os_log_create("com.example.apple-samplecode.macOSTunnel.macOSPacketTunnel", "macos_provider");
os_log_debug(self.log, "[PacketTunnelProvider][init]");
}
return self;
}
...
@end


If this does not work then something is not up with your project or configuration. To figure out more I would recommend opening a TSI so I can take a closer look at everything.


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Dear @meaton,

I implemented os_log, but it didn't help me. This is an anomaly, logs are not output.
Show my code here:

Code Block objective-c
@interface PacketTunnelProvider ()
@property os_log_t log;
@end

In 'os_log_create' I pass my extension bundleID and Target name.
Code Block objective-c
- (id) init
{
    if( self = [super init] ) {
        self.log = os_log_create("site.com.MyVPN.PacketTunnel", "PacketTunnel");
        os_log_debug(self.log, "[PacketTunnelProvider][init]");
    }
    return self;
}


Code Block objective-c
-(void)handleAppMessage:(NSData *)messageData completionHandler:(void (^)(NSData * _Nullable))completionHandler{
    os_log_debug(self.log, "handleAppMessage");
}


Even i tried use 'OS_LOG_DEFAULT'

Code Block objective-c
    os_log_debug(OS_LOG_DEFAULT, "handleAppMessage");


In the Terminal, try logging the results to that subsystem just to make sure.
Code Block text
% log stream --level debug --predicate 'subsystem == "site.com.MyVPN.PacketTunnel"'

If you cannot see at least the constructor of your PacketTunnelProvider at least being hit, then execution is not even getting to your packet tunnel.

If that is the case, then I would recommend opening a TSI so I can take a closer look at everything.


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
You're right. I'm currently working on submitting my code for review in TSI.
Dear @meaton,

I just submitted my application.
I was assigned the code: Follow-up: 762793516

Regards,
@iosdev000

Follow-up: 762793516

Found it and added it to my queue. Thanks.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

Since this thread was created, I’ve created a new post, Debugging a Network Extension Provider, that has a bunch of suggestions for how to debug problems like this.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"