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
yarn add react-native-outlive-vpn
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,
-
- Add
Network Extension
capability to your app
- Add
-
- Add
App Group
capability to your app
- Add
-
- Add
OutlineVpn
target to your app
- Add
-
- Add
App Group
capability to yourOutlineVpn
target
- Add
-
- Add
Network Extension
capability to yourOutlineVpn
target
- Add
-
- Add
Tun2Socks
framework to yourOutlineVpn
target
- Add
-
- Change
PacketTunnelProvider
content with our customOutlineVpn
content.
- Change
Step 1
Add Network Extension
capability to your app with packet tunnel feature.
1.1 Add Network Extension
1.2 Check Packet Tunnel
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
2.2 Name App Group
Step 3
Add OutlineVpn
target to your app. This target will be used for VPN connection.
3.1 Add Target
3.2 Give Name OutlineVpn
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
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
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
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.