NETunnelProviderManager disconnects during connection

Greetings to all apple developers!

I have the problem with NETunnelProviderManager and PacketTunnelProvider.

When I try to make a connection (by OpenVPN), there is an instant disconnect.
I am using OpenVPNAdapter.framework for this.

I put nslog in all methods in PacketTunnelProvider and none were printed....

I have the following logs in the console:
Code Block objective-c
NEVPNStatusInvalid
Save successfully
Connection established!
NEVPNStatusConnecting
NEVPNStatusDisconnected


I checked all the .entitlements and other parameters.
As I think they are okay.
However, if you could check them again, I would be grateful.

Below I am pasting links to images.

[Main target settings](https://ibb.co/zmHRP2z)
[Extension target settings](https://ibb.co/tQdNNNY)
[Main target .entitlements](https://ibb.co/SwwY8L8)
[Extension target .entitlements](https://ibb.co/kJg7q6W)
[Extensions Info.plist](https://ibb.co/th2xSzm)

My code:
Code Block objective-c
- (IBAction) buttonAction:(id)sender
{
[self connection];
}


Code Block objective-c
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onVpnStateChange:) name:NEVPNStatusDidChangeNotification object:nil];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:@"...."]];
[request setHTTPMethod:@"GET"];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString* requestReply = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSData* responseData = [requestReply dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
weak.configData = [jsonDict[@"config"] dataUsingEncoding:NSUTF8StringEncoding]
[weak initProvider];
}] resume];
}


Code Block objective-c
-(void)initProvider
{
__weak typeof(self) weak = self;
[NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager*>* _Nullable managers, NSError* _Nullable error) {
if(error){
NSLog(@"error: %@",error); return;
}
weak.providerManager = managers.firstObject ? managers.firstObject : [NETunnelProviderManager new];
[weak.providerManager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
if(error){
NSLog(@"initProvider [weak.providerManager loadFromPreferencesWithCompletionHandler] error: %@",error);
return;
}
NETunnelProviderProtocol *tunel = [[NETunnelProviderProtocol alloc] init];
tunel.providerBundleIdentifier = @"....com.MyVPN.PacketTunnel";
tunel.providerConfiguration = @{ @"ovpn" : self.configData };
tunel.serverAddress = @"vpn.superVPN.com";
tunel.disconnectOnSleep = NO;
weak.providerManager.protocolConfiguration = tunel;
weak.providerManager.localizedDescription = @"superVPN";
[weak.providerManager setEnabled:YES];
[weak.providerManager saveToPreferencesWithCompletionHandler:^(NSError *error) {
if(error) {
NSLog(@"Save error: %@", error);
}else {
NSLog(@"Save successfully");
}
}];
}];
}];
}


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

I just don't know what the problem might be. Maybe in PacketTunnelProvider ?

The code for PacketTunnelProvider was taken from here.

All tests I run on a real device (iPhone iOS 12.4.4)
Xcode Version 12.4 (12D4e)

My .ovpn file is correct because it good works on android version of application.

Please help me, I don't know where to look for the answer.
There are similar questions on stackoverflow, but no one answered them.





Replies

I going to point you over to another post where I described creating a Packet Tunnel Network Extension from the ground up. Review my steps and if you still have problems, post your issue here.


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Hello everyone !

I have solved my problem and now I will tell you how.
First of all I wanted to thank @meaton for the help and assistance, thank you!

So let's get started.
The concept of a vpn application was arranged in the following way:
  1. Json comes to me from the server, one of the fields of which contains an .opvn file in string format.

  2. In my case, passwords and username are not required (! THIS IS IMPORTANT), I only use the certificate itself.

The main mistake was my non-fundamental knowledge of the principles of the protocols.
When I received .opvn that value for the 'serverAddress' property I took opposite the 'remote' field.

I took only the first part ! This was the main mistake.


Code Block objective-c
// Example of the file I received:
client
remote 32.185.104.19 53513
resolv-retry infinite
nobind
setenv opt block-outside-dns
script-security 2
dhcp-option DNS 1.1.1.1


I extracted only "32.185.104.19", and I need everything at once (along with a space) "32.185.104.19 53513".

Once again, I used the OpenVPNAdapter external library.
Below I will give an example of my connection code.

Code Block objective-c
#import <Foundation/Foundation.h>
@import NetworkExtension;
@import OpenVPNAdapter;
NS_ASSUME_NONNULL_BEGIN
@interface VPNManager : NSObject
@property(strong,nonatomic) NETunnelProviderManager *providerManager;
// Properties has data/string from .ovpn file
@property(strong,nonatomic) NSData* configData;
// Ip addres of server from json
@property(strong,nonatomic) NSString* remoteIp;
+ (instancetype)sharedInstance;
- (void) loadProviderManager;
- (void) uninstallVPNConfigurationFromManagers;
- (void) startConnection:(void(^)(void))completion;
- (void) stopConnection:(void(^)(void))completion;
@end
NS_ASSUME_NONNULL_END


Code Block objective-c
#import "VPNManager.h"
#import <os/log.h>
@interface VPNManager ()
@property os_log_t log;
@end
@implementation VPNManager
@synthesize log;
/*---------------------------------------------------------------------------------------------------------
Method initialize 'NETunnelProviderManager *providerManager'
---------------------------------------------------------------------------------------------------------*/
- (void) loadProviderManager
{
__weak typeof(self) weak = self;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
weak.log = os_log_create("secretVPN.com.VPNClient.iOSPacketTunnel", "ios_app");
});
[NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager*>* _Nullable managers, NSError* _Nullable error) {
if(error){
NSLog(@"loadAllFromPreferencesWithCompletionHandler error: %@",error); return;
}
if (managers.count > 1) {
[weak uninstallVPNConfigurationFromManagers];
}
weak.providerManager = managers.firstObject ? managers.firstObject : [NETunnelProviderManager new];
[weak.providerManager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
if(error){
NSLog(@"weak.providerManager loadAllFromPreferencesWithCompletionHandler error: %@",error); return;
}
NETunnelProviderProtocol *tunnelProtocol = [weak createProtocolConfiguration];
weak.providerManager.protocolConfiguration = tunnelProtocol;
weak.providerManager.localizedDescription = @"secretVPN";
weak.providerManager.enabled = YES;
weak.providerManager.onDemandEnabled = NO;
[weak.providerManager saveToPreferencesWithCompletionHandler:^(NSError *error) {
NSLog(@"%@", (error) ? @"Saved with error" : @"Save successfully");
if(error) return;
}];
}];
}];
}
/*---------------------------------------------------------------------------------------------------------
Unistall managers
---------------------------------------------------------------------------------------------------------*/
- (void) uninstallVPNConfigurationFromManagers
{
[NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager*>* _Nullable managers, NSError * _Nullable error) {
if (error != nil) {
os_log_debug(self.log, "ERROR Uninstall vpn config: %{public}@", error.localizedDescription);
return;
}
for (NETunnelProviderManager *manager in managers) {
[manager removeFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
os_log_debug(self.log, "ERROR Uninstall vpn config: %{public}@", error.localizedDescription);
return;
} else {
os_log_debug(self.log, "Successful uninstall %{public}@", manager.description);
}
}];
}
os_log_debug(self.log, "Uninstalled vpn config");
}];
}
/*---------------------------------------------------------------------------------------------------------
Start connection
---------------------------------------------------------------------------------------------------------*/
- (void) startConnection:(void(^)(void))completion
{
__weak typeof(self) weak = self;
[self.providerManager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
if(error){
NSLog(@"weak.providerManager loadAllFromPreferencesWithCompletionHandler error: %@",error); return;
}
[weak.providerManager.connection startVPNTunnelAndReturnError:&error];
NSLog(@"%@", (error) ? @"Saved with error" : @"Connection established!");
}];
}
/*---------------------------------------------------------------------------------------------------------
Stop connection
---------------------------------------------------------------------------------------------------------*/
- (void) stopConnection:(void(^)(void))completion
{
__weak typeof(self) weak = self;
[self.providerManager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
if(error){
NSLog(@"weak.providerManager loadAllFromPreferencesWithCompletionHandler error: %@",error); return;
}
[weak.providerManager.connection stopVPNTunnel];
NSLog(@"stopVPNTunnel");
}];
}
/*---------------------------------------------------------------------------------------------------------
Create providerProtocols
---------------------------------------------------------------------------------------------------------*/
- (NETunnelProviderProtocol*) createProtocolConfiguration
{
NETunnelProviderProtocol *tunel = [[NETunnelProviderProtocol alloc] init];
tunel.serverAddress = self.remoteIp;
tunel.providerBundleIdentifier = @"secretVPN.com.VPNClient.iOSPacketTunnel";
tunel.providerConfiguration = @{ @"ovpn" : self.configData };
tunel.disconnectOnSleep = NO;
return tunel;
}
#pragma mark - Helpers
/*---------------------------------------------------------------------------------------------------------
Convert Connection.Status to NSString
---------------------------------------------------------------------------------------------------------*/
-(void)onVpnStateChange:(NSNotification *)Notification {
switch (self.providerManager.connection.status) {
case NEVPNStatusInvalid:
NSLog(@"NEVPNStatusInvalid");
break;
case NEVPNStatusDisconnected:
NSLog(@"NEVPNStatusDisconnected");
break;
case NEVPNStatusConnecting:
NSLog(@"NEVPNStatusConnecting");
break;
case NEVPNStatusConnected:
NSLog(@"NEVPNStatusConnected");
break;
case NEVPNStatusDisconnecting:
NSLog(@"NEVPNStatusDisconnecting");
break;
case NEVPNStatusReasserting:
NSLog(@"******************ReConnecting****************");
break;
default:
break;
}
}

Swift example

Code Block swift
import Foundation
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: "clientVPN", withExtension: "ovpn"),
let configurationFileContent = try? Data(contentsOf: configurationFileURL)
else {
fatalError()
}
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.serverAddress = "21.182.222.81 53152" // There should be your individual address here (!)
tunnelProtocol.providerBundleIdentifier = "mysecretVPN.com.SwiftOpenVPNAdapter.iOSPacketTunnel"
tunnelProtocol.providerConfiguration = ["ovpn": configurationFileContent as Data]
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"
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")
}
})
})
}
}
}

My podfile

Code Block objective-c
platform :ios, '10.0'
use_frameworks!
target 'VPNClient' do
pod 'OpenVPNAdapter', :git => 'https://github.com/ss-abramchuk/OpenVPNAdapter.git', :tag => '0.7.0'
end
target 'iOSPacketTunnel' do
pod 'OpenVPNAdapter', :git => 'https://github.com/ss-abramchuk/OpenVPNAdapter.git', :tag => '0.7.0'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO'
end
end
end

I had the same problem as you

The configuration is successful, you can see it in the settings, when I click connect, it will print out add success and loadFromPreferences! The status changes to Disconnected, the ovpn file is correct, he can connect normally in another iOS project, can you help me check it out Any reason, thanks

@ConderCheng

It looks like based on the code provided that saveToPreferencesWithCompletionHandler may be getting run twice. To do this all in one shot, have you tried something like the following:


- (void)viewDidLoad {
	...
	[NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
		// The action taken here would be to decide if there is an existing manager
		// running already. If so, allow the user to uninstall or stop the provider etc..
	}];
}

- (void)createAndSaveNewVPNConfig {

    NETunnelProviderProtocol *provider = [[NETunnelProviderProtocol alloc] init];
    provider.providerBundleIdentifier = @"com.example.apple-samplecode.iOSTunnel.iOSPacketTunnel";
    provider.serverAddress = @"server_addr";

    self.manager = [[NETunnelProviderManager alloc] init];
    self.manager.protocolConfiguration = provider;
    self.manager.localizedDescription = @"iOS PacketTunnel";
    [self.manager setEnabled: YES];

    [self.manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
        if (error != nil) {
            // Log error here
            return;
        }
        [self.manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
            if (error != nil) {
                NSLog(@"ERROR: %@", error);
                // Log error here
                return;
            }
            NETunnelProviderSession *connection = (NETunnelProviderSession*)self.manager.connection;

            NSError *err = nil;
            NSDictionary<NSString *, NSString *> *options = @{@"hello": @"tunnel"};
            BOOL success = [connection startTunnelWithOptions:options andReturnError:&err];
            
            if (err != nil) {
                os_log_debug(self.log, "Error starting tunnel: %{public}@", err.localizedDescription);
                return;
            } else {
                os_log_debug(self.log, "Success starting tunnel");
            }
            
            [self observeTunnelStatus];
        }];

    }];
}

Note that the following approach is iOS specific and would be different when using macOS due to the possibility of a System Extension.

  • @ConderCheng Thank you for your reply, I found the problem. When creating the target, the version defaults to the latest version, which is higher than my iPhone system version, which makes it impossible to run. I am sorry for my carelessness.

Add a Comment