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.

#import "PacketTunnelProvider.h"
#import "NEPacketTunnelFlow+NEPacketTunnelFlow_Extension.h"
@implementation PacketTunnelProvider

-(OpenVPNAdapter*)vpnAdapter{
    if(!_vpnAdapter){
        _vpnAdapter = [[OpenVPNAdapter alloc] init];
        _vpnAdapter.delegate = self;
    }
    return _vpnAdapter;
}

-(OpenVPNReachability*)openVpnReach{
    if(!_openVpnReach){
        _openVpnReach = [[OpenVPNReachability alloc] init];
    }
    return _openVpnReach;
}

-(void)handleAppMessage:(NSData *)messageData completionHandler:(void (^)(NSData * _Nullable))completionHandler{
    NSLog(@"handleAppMessage");
}

-(void)startTunnelWithOptions:(NSDictionary<NSString *,NSObject *> *)options completionHandler:(void (^)(NSError * _Nullable))completionHandler{

    NSLog(@"startTunnelWithOptions");

    NETunnelProviderProtocol *proto =  (NETunnelProviderProtocol*)self.protocolConfiguration;
    if(!proto){
        return;
    }
    NSDictionary<NSString *,id> *provider = proto.providerConfiguration;
    NSData * fileContent = provider[@"ovpn"];
    OpenVPNConfiguration *openVpnConfiguration = [[OpenVPNConfiguration alloc] init];
    openVpnConfiguration.fileContent = fileContent;
    NSError *error;
    OpenVPNProperties *properties = [self.vpnAdapter applyConfiguration:openVpnConfiguration error:&error];
    if(error){
        return;
    }
    
    if(!properties.autologin){
        OpenVPNCredentials *credentials = [[OpenVPNCredentials alloc] init];
        credentials.username = [NSString stringWithFormat:@"%@",[options objectForKey:@"username"]];
        credentials.password = [NSString stringWithFormat:@"%@",[options objectForKey:@"password"]];
        [self.vpnAdapter provideCredentials:credentials error:&error];
        if(error){
            return;
        }
    }
    
    [self.openVpnReach startTrackingWithCallback:^(OpenVPNReachabilityStatus status) {
        if(status==OpenVPNReachabilityStatusNotReachable){
            [self.vpnAdapter reconnectAfterTimeInterval:5];
        }
    }];
    
    [self.vpnAdapter connect];
    self.startHandler = completionHandler;
}


-(void)stopTunnelWithReason:(NEProviderStopReason)reason completionHandler:(void (^)(void))completionHandler{
  
    NSLog(@"stopTunnelWithReason");

    if ([self.openVpnReach isTracking]) {
        [self.openVpnReach stopTracking];
    }
    
    [self.vpnAdapter disconnect];
    self.stopHandler = completionHandler;
}


-(void)openVPNAdapter:(OpenVPNAdapter *)openVPNAdapter handleError:(NSError *)error{

    NSLog(@"openVPNAdapter:handleError:");

    BOOL isOpen = (BOOL)[error userInfo][OpenVPNAdapterErrorFatalKey];
    if(isOpen){
        if (self.openVpnReach.isTracking) {
            [self.openVpnReach stopTracking];
        }
        self.startHandler(error);
        self.startHandler = nil;
        
    }
}


-(void)openVPNAdapterDidReceiveClockTick:(OpenVPNAdapter *)openVPNAdapter{
    NSLog(@"openVPNAdapterDidReceiveClockTick");

}

-(void)openVPNAdapter:(OpenVPNAdapter *)openVPNAdapter handleEvent:(OpenVPNAdapterEvent)event message:(NSString *)message{
   
    NSLog(@"openVPNAdapter:handleEvent:");

    switch (event) {
        case OpenVPNAdapterEventConnected:
            if(self.reasserting){
                self.reasserting = false;
            }
            self.startHandler(nil);
            self.startHandler = nil;
            break;
        case OpenVPNAdapterEventDisconnected:
            if (self.openVpnReach.isTracking) {
                [self.openVpnReach stopTracking];
            }
            self.stopHandler();
            self.stopHandler = nil;
            break;
        case OpenVPNAdapterEventReconnecting:
            self.reasserting = true;
            break;
        default:
            break;
    }
}

-(void)openVPNAdapter:(OpenVPNAdapter *)openVPNAdapter configureTunnelWithNetworkSettings:(NEPacketTunnelNetworkSettings *)networkSettings completionHandler:(void (^)(id<OpenVPNAdapterPacketFlow> _Nullable))completionHandler{
    
    NSLog(@"openVPNAdapter:configureTunnelWithNetworkSettings:");

    __weak __typeof(self) weak_self = self;
    [self setTunnelNetworkSettings:networkSettings completionHandler:^(NSError * _Nullable error) {
        if(!error){
            completionHandler(weak_self.packetFlow);
        }
    }];
    
}


@end




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

//
//  ViewController.m
//  VPNOCDemo
//
//  Created by 程俊铭 on 2022/6/8.
//

#import "ViewController.h"
#import 
#import 

@interface ViewController ()
@property (nonatomic, strong)NETunnelProviderManager *providerManagers;
@property (nonatomic, strong) UIButton *connectBtn;
@property (nonatomic, strong) UILabel *stutusLb;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onVpnStateChange:) name:NEVPNStatusDidChangeNotification object:nil];
    
    [self.connectBtn addTarget:self action:@selector(connect) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.connectBtn];
    [self.view addSubview:self.stutusLb];
    
    [self.connectBtn mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self.view);
    }];
    [self.stutusLb mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.equalTo(self.view);
            make.top.equalTo(self.connectBtn.mas_bottom).offset(40);
    }];
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"FlyTestVPN" withExtension:@"ovpn"];
    NSData *data = [[NSData alloc] initWithContentsOfURL:url];
    [self saveVpn:data];
}


///  保存vpn相关的数据
/// @param data 数据
-(void)saveVpn:(NSData *)data
{
    //加载与调用应用程序关联的所有应用程序代理配置,这些配置以前已保存到网络扩展首选项中。
    [NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray * _Nullable managers, NSError * _Nullable error) {
        if (error) {
            NSLog(@"Load Error: %@", error.description);
        }

        NETunnelProviderManager *manager;
        if (managers.count > 0) {
            manager = managers[0];
        }else {
            manager = [[NETunnelProviderManager alloc] init];
            manager.protocolConfiguration = [[NETunnelProviderProtocol alloc] init];
        }
        
        NETunnelProviderProtocol *tunel = [[NETunnelProviderProtocol alloc]init];
        // 获取文件内容
        tunel.providerConfiguration = @{@"ovpn": data};
        // 项目的Identifier
        tunel.providerBundleIdentifier = @"com.chuancheng.zy.vpn.master.PacketTunnelProvider";
        // serverAddress:即在手机设置的vpn中显示的vpn地址(服务器显示)
        tunel.serverAddress = @"Fly VPN";
//        tunel.username = @"username";
//        tunel.identityDataPassword = @"password";
        // 设备进入睡眠,vpn断开连接
        tunel.disconnectOnSleep = NO;
        // 是否可以编辑
        [manager setEnabled:YES];
        // 协议配置
        [manager setProtocolConfiguration:tunel];
        // 包含vpn描述的字符串(类型显示)
        manager.localizedDescription = @"Fly-加速器";
        // 保存信息
//        SSLWeakSelf(self);
        [manager saveToPreferencesWithCompletionHandler:^(NSError *error) {
            
            if(error) {
                
                NSLog(@"Save error: %@", error);
                
            }else {
                
                self.providerManagers = manager;
                                
                NSLog(@"add success");
                //加载与调用应用程序关联的所有应用程序代理配置,这些配置以前已保存到网络扩展首选项中。
                [manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
                    NSLog(@"loadFromPreferences!");

                }];
            }
        }];
 
    }];
}


-(void)connect
{
    // 连接
    [self.providerManagers loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
        if(!error){
            
            [self.providerManagers saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
                if (error) {
                    NSLog(@"保存失败");
                }else {
                    NSError *error = nil;
                    [self.providerManagers.connection startVPNTunnelWithOptions:nil andReturnError:&error];
                    if(error) {
                        NSLog(@"Start error: %@", error.localizedDescription);
                    }else{
                        NSLog(@"Connection established!");
                    }
                }
                
               
            }];
            
           
        }
    }];
}

-(void)disconnectAction
{
    // 断开连接
    [self.providerManagers loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
       
        [self.providerManagers.connection stopVPNTunnel];
    }];

}

-(void)onVpnStateChange:(NSNotification *)Notification {
    NEVPNStatus status = self.providerManagers.connection.status;

    switch (status) {
        case NEVPNStatusInvalid:
        {
            NSLog(@"连接无效");
            self.stutusLb.text = @"连接无效";
        }
            break;
        case NEVPNStatusDisconnected:
        {
            NSLog(@"未连接");
            self.stutusLb.text = @"未连接";
        }
            break;
        case NEVPNStatusConnecting:
        {
            NSLog(@"正在连接");
            self.stutusLb.text = @"正在连接";
        }
            break;
        case NEVPNStatusConnected:
        {
            NSLog(@"已连接");
            self.stutusLb.text = @"已连接";
        }
           
            break;
        case NEVPNStatusDisconnecting:
        {
            NSLog(@"断开连接中...");
            self.stutusLb.text = @"断开连接中...";
        }
           
            break;
        case NEVPNStatusReasserting:
        {
            NSLog(@"重新连接...");
            self.stutusLb.text = @"重新连接...";
        }
            break;
        default:
            break;
    }
}

//@property (nonatomic, strong) UIButton *connectBtn;
//@property (nonatomic, strong) UILabel *stutusLb;
- (UIButton *)connectBtn {
    if (_connectBtn == nil) {
        _connectBtn = [[UIButton alloc]init];
        [_connectBtn setTitle:@"连接" forState:UIControlStateNormal];
        [_connectBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    }
    return _connectBtn;
}

- (UILabel *)stutusLb {
    if (_stutusLb == nil) {
        _stutusLb = [[UILabel alloc]init];
        _stutusLb.text = @"状态";
        _stutusLb.textColor = [UIColor redColor];
    }
    return _stutusLb;
}
@end

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.

NETunnelProviderManager disconnects during connection
 
 
Q