Getting Started

Introduction

react-native-outline-vpn is Outline VPN library for React Native.

Installation

Installation has two part as Android & iOS. On Android part there is no additional requirement for vpn beside that on iOS part has required some additional configuration.

First of all, install react-native-outline-vpn

Terminal
yarn add react-native-outlive-vpn
Terminal
npm install react-native-outlive-vpn
import Outline from 'react-native-outline-vpn';
// THIS VPN INFORMATIONS INTENTIONALLY LEFT EXPOSED, HAS 0 MB LIMIT SO CANNOT ACCESS INTERNET BUT COULD BE USE FOR TEST VPN CONNECTION
// Start VPN
Outline.startVpn({
	host:'185.218.124.25',
	port: 42248,
	password: 'FQZV7mWkAB3l7pzg7tpv9p',
	method: 'chacha20-ietf-poly1305',
	prefix: '\u0005\u00DC\u005F\u00E0\u0001\u0020', //vpn prefix
	providerBundleIdentifier: 'org.reactjs.native.example.OutlineVpnExample.OutlineVpn', //apple bundle identifier declared step-2 on guide
	serverAddress: 'OutlineServer', //can be any string which user see MyPreciousVpn
	tunnelId: 'OutlineTunnel', //can be random string
	localizedDescription: 'OutlineVpn', //can be random string
});
 
// Stop VPN
Outline.stopVpn();

Android

Android need no additional configuration for VPN connection. Basically, you can start vpn connection with above code.

iOS

iOS need additional configuration for VPN connection. This configuration has 7 steps,

    1. Add Network Extension capability to your app
    1. Add App Group capability to your app
    1. Add OutlineVpn target to your app
    1. Add App Group capability to your OutlineVpn target
    1. Add Network Extension capability to your OutlineVpn target
    1. Add Tun2Socks framework to your OutlineVpn target
    1. Change PacketTunnelProvider content with our custom OutlineVpn content.

Step 1

Add Network Extension capability to your app with packet tunnel feature.

1.1 Add Network Extension Install

1.2 Check Packet Tunnel Install

Step 2

Add App Group capability to your app. App group is linked to your app and your extension. This is required for sharing data between your app and your extension.

2.1 Add App Group Install

2.2 Name App Group Install

Step 3

Add OutlineVpn target to your app. This target will be used for VPN connection.

3.1 Add Target Install

3.2 Give Name OutlineVpn Install

If its ask Activate OutlineVpn scheme, click Activate

Step 4

Add App Group capability to your OutlineVpn target. This is required for sharing data between your app and your extension.

4.1 Add App Group and select main app group which specified on step-2 Install

Step 5

Add Network Extension capability to your OutlineVpn target. This is required for VPN connection.

5.1 Add Network Extension and check Packet Tunnel Install

Step 6

Add Tun2Socks framework to your OutlineVpn target. This is required for VPN connection.

6.1 Drag Tun2Socks framework to OutlineVpn target Framework and Library section Install

Step 7

Change H and M PacketTunnelProvider content with our custom OutlineVpn content. When you add OutlineVpn target, it will create PacketTunnelProvider class. You need to change this class with our custom OutlineVpn content.

PacketTunnelProvider.m

NSString *appGroup = @"group.outlineexample.vpn";

is important for sharing data between your app and your extension. You need to change this value with your app group name which specified on step-2.

PacketTunnelProvider.h (opens in a new tab)

//
//  PacketTunnelProvider.h
//  OutlineVpn
//
//  Created by Zafer ATLI on 16.03.2025.
//
 
#ifndef PacketTunnelProvider_h
#define PacketTunnelProvider_h
 
@import NetworkExtension;
 
 
@interface PacketTunnelProvider : NEPacketTunnelProvider
@property (nonatomic, strong) NSDictionary *vpnConfig;
// This must be kept in sync with:
//  - cordova-plugin-outline/apple/src/OutlineVpn.swift#ErrorCode
//  - www/model/errors.ts
typedef NS_ENUM(NSInteger, ErrorCode) {
  noError = 0,
  undefinedError = 1,
  vpnPermissionNotGranted = 2,
  invalidServerCredentials = 3,
  udpRelayNotEnabled = 4,
  serverUnreachable = 5,
  vpnStartFailure = 6,
  illegalServerConfiguration = 7,
  shadowsocksStartFailure = 8,
  configureSystemProxyFailure = 9,
  noAdminPermissions = 10,
  unsupportedRoutingTable = 11,
  systemMisconfigured = 12
};
 
@end
 
#endif /* PacketTunnelProvider_h */

PacketTunnelProvider.m (opens in a new tab)

//
//  PacketTunnelProvider.m
//  OutlineVpn
//
//  Created by Zafer ATLI on 16.03.2025.
//
 
#import "PacketTunnelProvider.h"
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <netdb.h>
 
@import Tun2socks;
 
@interface PacketTunnelProvider ()<Tun2socksTunWriter>
@property id<Tun2socksOutlineTunnel> tunnel;
@property (nonatomic, copy) void (^startCompletion)(NSNumber *);
@property (nonatomic, copy) void (^stopCompletion)(NSNumber *);
@property (nonatomic, nullable) NSString *transportConfig;
@property (nonatomic) dispatch_queue_t packetQueue;
@property (nonatomic) BOOL isUdpSupported;
@end
 
@implementation PacketTunnelProvider
 
- (id)init {
  self = [super init];
  NSString *appGroup = @"group.outlineexample.vpn";
  NSURL *containerUrl = [[NSFileManager defaultManager]
                         containerURLForSecurityApplicationGroupIdentifier:appGroup];
  NSString *logsDirectory = [[containerUrl path] stringByAppendingPathComponent:@"Logs"];
  
  _packetQueue = dispatch_queue_create("org.outline.packetqueue", DISPATCH_QUEUE_SERIAL);
  [self showConfig];
  return self;
}
 
- (void)startTunnelWithOptions:(NSDictionary *)options completionHandler:(void (^)(NSError *))completionHandler {
  NSLog(@"Starting tunnel");
  NEPacketTunnelNetworkSettings *networkSettings = [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:@"10.0.0.3"];
  
  // IP address configuration
  networkSettings.IPv4Settings = [[NEIPv4Settings alloc] initWithAddresses:@[@"10.0.0.2"] subnetMasks:@[@"255.255.255.0"]];
  
  // Routing configuration
  networkSettings.IPv4Settings.includedRoutes = @[[NEIPv4Route defaultRoute]];
  
  // DNS settings
  // networkSettings.DNSSettings = [[NEDNSSettings alloc] initWithServers:@[@"1.1.1.1", @"8.8.8.8"]];
  
  // Applying the network settings
  [self setTunnelNetworkSettings:networkSettings completionHandler:^(NSError * _Nullable error) {
    if (error != nil) {
      NSLog(@"Failed to set network settings: %@", error);
      completionHandler(error);
      return;
    }
    
    // Start tun2socks after network settings are applied
    dispatch_async(dispatch_get_main_queue(), ^{
    });
    [self startTun2Socks];
    completionHandler(nil);
  }];
}
 
- (void)stopTunnelWithReason:(NEProviderStopReason)reason completionHandler:(void (^)(void))completionHandler {
  NSLog(@"VPN Config Stop tunnel with reason");
  // Add code here to start the process of stopping the tunnel.
  completionHandler();
}
 
- (void)handleAppMessage:(NSData *)messageData completionHandler:(void (^)(NSData *))completionHandler {
  NSLog(@"Starting tunnel");
  
  // Add code here to handle the message.
}
 
- (void)sleepWithCompletionHandler:(void (^)(void))completionHandler {
  NSLog(@"Starting tunnel");
  
  // Add code here to get ready to sleep.
  completionHandler();
}
 
- (void)wake {
  NSLog(@"Starting tunnel");
  
  // Add code here to wake up.
}
 
- (void)showConfig {
  
  NETunnelProviderProtocol *tunnelProtocol = (NETunnelProviderProtocol *)self.protocolConfiguration;
 
  NSDictionary *providerConfiguration = tunnelProtocol.providerConfiguration;
 
    if (providerConfiguration) {
      self.vpnConfig = @{
                 @"tunnelId": providerConfiguration[@"tunnelId"] ?: @"",
                 @"host": providerConfiguration[@"host"] ?: @"",
                 @"port": providerConfiguration[@"port"] ?: @(2222),
                 @"password": providerConfiguration[@"password"] ?: @"",
                 @"method": providerConfiguration[@"method"] ?:@"chacha20-ietf-poly1305",
                 @"prefix": providerConfiguration[@"prefix"] ?: @"",
             };
        NSLog(@"VPN Config: %@", self.vpnConfig);
    } else {
        NSLog(@"Provider configuration is empty!");
        NSError *error = [NSError errorWithDomain:@"CustomVPN" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Provider configuration not retrived"}];
        return;
    }
  
}
 
- (ShadowsocksClient*)ConnectSS {
    NETunnelProviderProtocol *tunnelProtocol = (NETunnelProviderProtocol *)self.protocolConfiguration;
    NSDictionary *providerConfiguration = tunnelProtocol.providerConfiguration;
  
    ShadowsocksConfig* config = [[ShadowsocksConfig alloc] init];
    config.host = providerConfiguration[@"host"];
    config.port = [providerConfiguration[@"port"] longValue];
    config.password = providerConfiguration[@"password"];
    config.cipherName = providerConfiguration[@"method"];
    
    NSError *error = nil;
    ShadowsocksClient *client = ShadowsocksNewClient(config, &error);
    long returnValue = -1;
    if (error != nil) {
        NSLog(@"Failed to construct client.");
    }
    return client;
}
 
- (void)showSSStatus:(long)status {
    switch (status) {
        case 0:
            NSLog(@"Shadows Stock Status -> NoError");
            break;
        case 1:
            NSLog(@"Shadows Stock Status -> Unexpected");
            break;
        case 2:
            NSLog(@"Shadows Stock Status -> NoVPNPermissions");
            break;
        case 3:
            NSLog(@"Shadows Stock Status -> AuthenticationFailure");
            break;
        case 4:
            NSLog(@"Shadows Stock Status -> UDPConnectivity");
            break;
        case 5:
            NSLog(@"Shadows Stock Status -> Unreachable");
            break;
        case 6:
            NSLog(@"Shadows Stock Status -> VpnStartFailure");
            break;
        case 7:
            NSLog(@"Shadows Stock Status -> IllegalConfiguration");
            break;
        case 8:
            NSLog(@"Shadows Stock Status -> ShadowsocksStartFailure");
            break;
        case 9:
            NSLog(@"Shadows Stock Status -> ConfigureSystemProxyFailure");
            break;
        case 10:
            NSLog(@"Shadows Stock Status -> NoAdminPermissions");
            break;
        case 11:
            NSLog(@"Shadows Stock Status -> UnsupportedRoutingTable");
            break;
        case 12:
            NSLog(@"Shadows Stock Status -> SystemMisconfigured");
            break;
        default:
            NSLog(@"Shadows Stock Status -> Error not found");
            break;
    }
    
}
 
- (void)startTun2Socks {
  NSLog(@"startTun2Socks has started!");
  __weak typeof(self) weakSelf = self;
  __block long bytesWritten = 0;
    NSError *err = nil;
    // Tun2socks interface initialization and start
  NSLog(@"startTun2Socks Connect SS Error");
    id client = [self ConnectSS];
    if (client == nil) {
      NSLog(@"startTun2Socks Client Error");
    }
    self.tunnel = Tun2socksConnectShadowsocksTunnel(weakSelf, client, false, &err);
    if (err != nil) {
        NSLog(@"startTun2Socks Connect error %@", err.localizedDescription);
      }
  if([self.tunnel isConnected]){
    NSLog(@"startTun2Socks connected");
  }else {
    NSLog(@"startTun2Socks error");
 
  }
 
  [weakSelf.packetFlow readPacketsWithCompletionHandler:^(NSArray<NSData *> *_Nonnull packets,
                                                          NSArray<NSNumber *> *_Nonnull protocols) {
      for (NSData *packet in packets) {
          // Her bir paketi tünele yaz
          [weakSelf.tunnel write:packet ret0_:&bytesWritten error:nil];
          
          if (bytesWritten < 0) {
              NSLog(@"Paket couldnt write.");
          } else {
              NSLog(@"%ld byte wrote.", bytesWritten);
          }
            dispatch_async(weakSelf.packetQueue, ^{
              [weakSelf processPackets];
            });
      }
  }];
}
 
- (void)startTun2Socks2 {
  NSLog(@"startTun2Socks started successfully");
  __weak typeof(self) weakSelf = self;
  __block long bytesWritten = 0;
  [weakSelf.packetFlow readPacketsWithCompletionHandler:^(NSArray<NSData *> *_Nonnull packets,
                                                          NSArray<NSNumber *> *_Nonnull protocols) {
    for (NSData *packet in packets) {
      [weakSelf.tunnel write:packet ret0_:&bytesWritten error:nil];
    }
    dispatch_async(self.packetQueue, ^{
      [weakSelf processPackets];
    });
  }];
}
 
- (BOOL)close:(NSError *_Nullable *)error {
  return YES;
}
 
- (BOOL)write:(NSData *_Nullable)packet n:(long *)n error:(NSError *_Nullable *)error {
  [self.packetFlow writePackets:@[ packet ] withProtocols:@[ @(AF_INET) ]];
  return YES;
}
 
- (void)processPackets {
  __weak typeof(self) weakSelf = self;
  __block long bytesWritten = 0;
  [weakSelf.packetFlow readPacketsWithCompletionHandler:^(NSArray<NSData *> *_Nonnull packets,
                                                          NSArray<NSNumber *> *_Nonnull protocols) {
    for (NSData *packet in packets) {
      [weakSelf.tunnel write:packet ret0_:&bytesWritten error:nil];
    }
    dispatch_async(weakSelf.packetQueue, ^{
      [weakSelf processPackets];
    });
  }];
}
@end

react-native-outline-vpn has example project with fully configured. You can check example project (opens in a new tab) for more information.