VPN over Shadowsocks, three providers added, still unstable (testing, not for production)

This commit is contained in:
Alex Kh 2021-12-31 10:57:58 +04:00
parent 7c46e42820
commit db527be97c
575 changed files with 991 additions and 40930 deletions

View file

@ -1,65 +0,0 @@
// Copyright 2018 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef Shadowsocks_h
#define Shadowsocks_h
#import <Foundation/Foundation.h>
/**
* Manages the lifecycle and configuration of ss-local, the Shadowsocks client library.
*/
@interface Shadowsocks : NSObject
extern const int kShadowsocksLocalPort;
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
};
@property (nonatomic) NSDictionary *config;
/**
* Initializes the object with a Shadowsocks server configuration, |config|.
*/
- (id)init:(NSDictionary *)config;
/**
* Starts ss-local on a separate thread with the configuration supplied in the constructor.
* If |checkConnectivity| is true, verifies that the server credentials are valid and that
* the remote supports UDP forwarding, calling |completion| with the result.
*/
- (void)startWithConnectivityChecks:(bool)checkConnectivity
completion:(void (^)(ErrorCode))completion;
/**
* Stops the thread running ss-local. Calls |completion| with the success of the operation.
*/
- (void)stop:(void (^)(ErrorCode))completion;
@end
#endif /* Shadowsocks_h */

View file

@ -1,202 +0,0 @@
// Copyright 2018 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Shadowsocks.h"
#include <limits.h>
#include <pthread.h>
#import "ShadowsocksConnectivity.h"
#import <Shadowsocks/shadowsocks.h>
const int kShadowsocksLocalPort = 9999;
static const int kShadowsocksTimeoutSecs = INT_MAX;
static const int kShadowsocksTcpAndUdpMode =
1; // See https://github.com/shadowsocks/shadowsocks-libev/blob/4ea517/src/jconf.h#L44
static char *const kShadowsocksLocalAddress = "127.0.0.1";
@interface Shadowsocks ()
@property (nonatomic) pthread_t ssLocalThreadId;
@property (nonatomic, copy) void (^startCompletion)(ErrorCode);
@property (nonatomic, copy) void (^stopCompletion)(ErrorCode);
@property (nonatomic) dispatch_queue_t dispatchQueue;
@property (nonatomic) dispatch_group_t dispatchGroup;
@property(nonatomic) bool checkConnectivity;
@property(nonatomic) ShadowsocksConnectivity *ssConnectivity;
@end
@implementation Shadowsocks
- (id) init:(NSDictionary *)config {
self = [super init];
if (self) {
_config = config;
_dispatchQueue = dispatch_queue_create("Shadowsocks", DISPATCH_QUEUE_SERIAL);
_dispatchGroup = dispatch_group_create();
_ssConnectivity = [[ShadowsocksConnectivity alloc] initWithPort:kShadowsocksLocalPort];
}
return self;
}
- (void)startWithConnectivityChecks:(bool)checkConnectivity
completion:(void (^)(ErrorCode))completion {
if (self.ssLocalThreadId != 0) {
return completion(shadowsocksStartFailure);
}
self.checkConnectivity = checkConnectivity;
dispatch_async(dispatch_get_main_queue(), ^{
// Start ss-local from the main application thread.
self.startCompletion = completion;
[self startShadowsocksThread];
});
}
-(void)stop:(void (^)(ErrorCode))completion {
if (self.ssLocalThreadId == 0) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
// The ev_loop in the ss-local thread will not break unless it is signaled to stop from the main
// application thread.
self.stopCompletion = completion;
pthread_kill(self.ssLocalThreadId, SIGUSR1);
self.ssLocalThreadId = 0;
});
}
#pragma mark - Lifecycle
void shadowsocksCallback(int socks_fd, int udp_fd, void *udata) {
if (socks_fd <= 0 || udp_fd <= 0) {
return;
}
Shadowsocks* ss = (__bridge Shadowsocks *)udata;
[ss checkServerConnectivity];
}
- (void)startShadowsocks {
if (self.config == nil) {
self.startCompletion(illegalServerConfiguration);
return;
}
int port = [self.config[@"port"] intValue];
char *host = (char *)[self.config[@"host"] UTF8String];
char *password = (char *)[self.config[@"password"] UTF8String];
char *method = (char *)[self.config[@"method"] UTF8String];
const profile_t profile = {.remote_host = host,
.local_addr = kShadowsocksLocalAddress,
.method = method,
.password = password,
.remote_port = port,
.local_port = kShadowsocksLocalPort,
.timeout = kShadowsocksTimeoutSecs,
.acl = NULL,
.log = NULL,
.fast_open = 0,
.mode = kShadowsocksTcpAndUdpMode,
.verbose = 0};
int success = start_ss_local_server_with_callback(profile, shadowsocksCallback,
(__bridge void *)self);
if (success < 0) {
self.startCompletion(shadowsocksStartFailure);
return;
}
if (self.stopCompletion) {
self.stopCompletion(noError);
self.stopCompletion = nil;
}
}
// Entry point for the Shadowsocks POSIX thread.
void *startShadowsocks(void *udata) {
Shadowsocks* ss = (__bridge Shadowsocks *)udata;
[ss startShadowsocks];
return NULL;
}
// Starts a POSIX thread that runs ss-local.
- (void)startShadowsocksThread {
pthread_attr_t attr;
int err = pthread_attr_init(&attr);
if (err) {
self.startCompletion(shadowsocksStartFailure);
return;
}
err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (err) {
self.startCompletion(shadowsocksStartFailure);
return;
}
err = pthread_create(&_ssLocalThreadId, &attr, &startShadowsocks, (__bridge void *)self);
if (err) {
self.startCompletion(shadowsocksStartFailure);
}
err = pthread_attr_destroy(&attr);
if (err) {
self.startCompletion(shadowsocksStartFailure);
return;
}
}
#pragma mark - Connectivity
/**
* Checks that the remote server is reachable, allows UDP forwarding, and the credentials are valid.
* Synchronizes and parallelizes the execution of the connectivity checks and calls
* |startCompletion| with the combined outcome.
* Only performs the tests if |checkConnectivity| is true; otherwise calls |startCompletion|
* with success.
*/
- (void) checkServerConnectivity {
if (!self.checkConnectivity) {
self.startCompletion(noError);
return;
}
__block BOOL isRemoteUdpForwardingEnabled = false;
__block BOOL serverCredentialsAreValid = false;
__block BOOL isServerReachable = false;
// Enter the group once for each check
dispatch_group_enter(self.dispatchGroup);
dispatch_group_enter(self.dispatchGroup);
dispatch_group_enter(self.dispatchGroup);
[self.ssConnectivity isUdpForwardingEnabled:^(BOOL enabled) {
isRemoteUdpForwardingEnabled = enabled;
dispatch_group_leave(self.dispatchGroup);
}];
[self.ssConnectivity isReachable:self.config[@"host"]
port:[self.config[@"port"] intValue]
completion:^(BOOL isReachable) {
isServerReachable = isReachable;
dispatch_group_leave(self.dispatchGroup);
}];
[self.ssConnectivity checkServerCredentials:^(BOOL valid) {
serverCredentialsAreValid = valid;
dispatch_group_leave(self.dispatchGroup);
}];
dispatch_group_notify(self.dispatchGroup, self.dispatchQueue, ^{
if (isRemoteUdpForwardingEnabled) {
self.startCompletion(noError);
} else if (serverCredentialsAreValid) {
self.startCompletion(udpRelayNotEnabled);
} else if (isServerReachable) {
self.startCompletion(invalidServerCredentials);
} else {
self.startCompletion(serverUnreachable);
}
});
}
@end

View file

@ -1,50 +0,0 @@
// Copyright 2018 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef ShadowsocksConnectivity_h
#define ShadowsocksConnectivity_h
#import <Foundation/Foundation.h>
/**
* Non-thread-safe class to perform Shadowsocks connectivity checks.
*/
@interface ShadowsocksConnectivity : NSObject
/**
* Initializes the object with a local Shadowsocks port, |shadowsocksPort|.
*/
- (id)initWithPort:(uint16_t)shadowsocksPort;
/**
* Verifies that the server has enabled UDP forwarding. Performs an end-to-end test by sending
* a DNS request through the proxy. This method is a superset of |checkServerCredentials|, as its
* success implies that the server credentials are valid.
*/
- (void)isUdpForwardingEnabled:(void (^)(BOOL))completion;
/**
* Verifies that the server credentials are valid. Performs an end-to-end authentication test
* by issuing an HTTP HEAD request to a target domain through the proxy.
*/
- (void)checkServerCredentials:(void (^)(BOOL))completion;
/**
* Checks that the server is reachable on |host| and |port|.
*/
- (void)isReachable:(NSString *)host port:(uint16_t)port completion:(void (^)(BOOL))completion;
@end
#endif /* ShadowsocksConnectivity_h */

View file

@ -1,333 +0,0 @@
// Copyright 2018 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "ShadowsocksConnectivity.h"
#include <arpa/inet.h>
#import <ShadowSocks/shadowsocks.h>
#import <CocoaAsyncSocket/CocoaAsyncSocket.h>
//@import CocoaAsyncSocket;
static char *const kShadowsocksLocalAddress = "127.0.0.1";
static char *const kDnsResolverAddress = "208.67.222.222"; // OpenDNS
static const uint16_t kDnsResolverPort = 53;
static const size_t kDnsRequestNumBytes = 28;
static const size_t kSocksHeaderNumBytes = 10;
static const uint8_t kSocksMethodsResponseNumBytes = 2;
static const size_t kSocksConnectResponseNumBytes = 10;
static const uint8_t kSocksVersion = 0x5;
static const uint8_t kSocksMethodNoAuth = 0x0;
static const uint8_t kSocksCmdConnect = 0x1;
static const uint8_t kSocksAtypIpv4 = 0x1;
static const uint8_t kSocksAtypDomainname = 0x3;
static const NSTimeInterval kTcpSocketTimeoutSecs = 10.0;
static const NSTimeInterval kUdpSocketTimeoutSecs = 1.0;
static const long kSocketTagHttpRequest = 100;
static const int kUdpForwardingMaxChecks = 5;
static const uint16_t kHttpPort = 80;
@interface ShadowsocksConnectivity ()<GCDAsyncSocketDelegate, GCDAsyncUdpSocketDelegate>
@property(nonatomic) uint16_t shadowsocksPort;
@property(nonatomic, copy) void (^udpForwardingCompletion)(BOOL);
@property(nonatomic, copy) void (^reachabilityCompletion)(BOOL);
@property(nonatomic, copy) void (^credentialsCompletion)(BOOL);
@property(nonatomic) dispatch_queue_t dispatchQueue;
@property(nonatomic) GCDAsyncUdpSocket *udpSocket;
@property(nonatomic) GCDAsyncSocket *credentialsSocket;
@property(nonatomic) GCDAsyncSocket *reachabilitySocket;
@property(nonatomic) bool isRemoteUdpForwardingEnabled;
@property(nonatomic) bool areServerCredentialsValid;
@property(nonatomic) bool isServerReachable;
@property(nonatomic) int udpForwardingNumChecks;
@end
@implementation ShadowsocksConnectivity
- (id)initWithPort:(uint16_t)shadowsocksPort {
self = [super init];
if (self) {
_shadowsocksPort = shadowsocksPort;
_dispatchQueue = dispatch_queue_create("ShadowsocksConnectivity", DISPATCH_QUEUE_SERIAL);
}
return self;
}
#pragma mark - UDP Forwarding
struct socks_udp_header {
uint16_t rsv;
uint8_t frag;
uint8_t atyp;
uint32_t addr;
uint16_t port;
};
- (void)isUdpForwardingEnabled:(void (^)(BOOL))completion {
self.isRemoteUdpForwardingEnabled = false;
self.udpForwardingNumChecks = 0;
self.udpForwardingCompletion = completion;
self.udpSocket =
[[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:self.dispatchQueue];
struct in_addr dnsResolverAddress;
if (!inet_aton(kDnsResolverAddress, &dnsResolverAddress)) {
[self udpForwardingCheckDone:false];
return;
}
struct socks_udp_header socksHeader = {
.atyp = kSocksAtypIpv4,
.addr = dnsResolverAddress.s_addr, // Already in network order
.port = htons(kDnsResolverPort)};
uint8_t *dnsRequest = [self getDnsRequest];
size_t packetNumBytes = kSocksHeaderNumBytes + kDnsRequestNumBytes;
uint8_t socksPacket[packetNumBytes];
memset(socksPacket, 0, packetNumBytes);
memcpy(socksPacket, &socksHeader, kSocksHeaderNumBytes);
memcpy(socksPacket + kSocksHeaderNumBytes, dnsRequest, kDnsRequestNumBytes);
NSData *packetData = [[NSData alloc] initWithBytes:socksPacket length:packetNumBytes];
dispatch_source_t timer =
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.dispatchQueue);
if (!timer) {
[self udpForwardingCheckDone:false];
return;
}
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0),
kUdpSocketTimeoutSecs * NSEC_PER_SEC, 0);
__weak ShadowsocksConnectivity *weakSelf = self;
dispatch_source_set_event_handler(timer, ^{
if (++weakSelf.udpForwardingNumChecks > kUdpForwardingMaxChecks ||
weakSelf.isRemoteUdpForwardingEnabled) {
dispatch_source_cancel(timer);
if (!weakSelf.isRemoteUdpForwardingEnabled) {
[weakSelf udpForwardingCheckDone:false];
}
[weakSelf.udpSocket close];
return;
}
[weakSelf.udpSocket sendData:packetData
toHost:[[NSString alloc] initWithUTF8String:kShadowsocksLocalAddress]
port:self.shadowsocksPort
withTimeout:kUdpSocketTimeoutSecs
tag:0];
if (![weakSelf.udpSocket receiveOnce:nil]) {
}
});
dispatch_resume(timer);
}
// Returns a byte representation of a DNS request for "google.com".
- (uint8_t *)getDnsRequest {
static uint8_t kDnsRequest[] = {
0, 0, // [0-1] query ID
1, 0, // [2-3] flags; byte[2] = 1 for recursion desired (RD).
0, 1, // [4-5] QDCOUNT (number of queries)
0, 0, // [6-7] ANCOUNT (number of answers)
0, 0, // [8-9] NSCOUNT (number of name server records)
0, 0, // [10-11] ARCOUNT (number of additional records)
6, 'g', 'o', 'o', 'g', 'l', 'e', 3, 'c', 'o', 'm',
0, // null terminator of FQDN (root TLD)
0, 1, // QTYPE, set to A
0, 1 // QCLASS, set to 1 = IN (Internet)
};
return kDnsRequest;
}
#pragma mark - GCDAsyncUdpSocketDelegate
- (void)udpSocket:(GCDAsyncUdpSocket *)sock
didNotSendDataWithTag:(long)tag
dueToError:(NSError *)error {
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock
didReceiveData:(NSData *)data
fromAddress:(NSData *)address
withFilterContext:(id)filterContext {
if (!self.isRemoteUdpForwardingEnabled) {
// Only report success if it hasn't been done so already.
[self udpForwardingCheckDone:true];
}
}
- (void)udpForwardingCheckDone:(BOOL)enabled {
self.isRemoteUdpForwardingEnabled = enabled;
if (self.udpForwardingCompletion != NULL) {
self.udpForwardingCompletion(self.isRemoteUdpForwardingEnabled);
self.udpForwardingCompletion = NULL;
}
}
#pragma mark - Credentials
struct socks_methods_request {
uint8_t ver;
uint8_t nmethods;
uint8_t method;
};
struct socks_request_header {
uint8_t ver;
uint8_t cmd;
uint8_t rsv;
uint8_t atyp;
};
- (void)checkServerCredentials:(void (^)(BOOL))completion {
self.areServerCredentialsValid = false;
self.credentialsCompletion = completion;
self.credentialsSocket =
[[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self.dispatchQueue];
NSError *error;
[self.credentialsSocket
connectToHost:[[NSString alloc] initWithUTF8String:kShadowsocksLocalAddress]
onPort:self.shadowsocksPort
withTimeout:kTcpSocketTimeoutSecs
error:&error];
if (error) {
[self serverCredentialsCheckDone];
return;
}
struct socks_methods_request methodsRequest = {
.ver = kSocksVersion, .nmethods = 0x1, .method = kSocksMethodNoAuth};
NSData *methodsRequestData =
[[NSData alloc] initWithBytes:&methodsRequest length:sizeof(struct socks_methods_request)];
[self.credentialsSocket writeData:methodsRequestData withTimeout:kTcpSocketTimeoutSecs tag:0];
[self.credentialsSocket readDataToLength:kSocksMethodsResponseNumBytes
withTimeout:kTcpSocketTimeoutSecs
tag:0];
size_t socksRequestHeaderNumBytes = sizeof(struct socks_request_header);
NSString *domain = [self chooseRandomDomain];
uint8_t domainNameNumBytes = domain.length;
size_t socksRequestNumBytes = socksRequestHeaderNumBytes + domainNameNumBytes +
sizeof(uint16_t) /* port */ +
sizeof(uint8_t) /* domain name length */;
struct socks_request_header socksRequestHeader = {
.ver = kSocksVersion, .cmd = kSocksCmdConnect, .atyp = kSocksAtypDomainname};
uint8_t socksRequest[socksRequestNumBytes];
memset(socksRequest, 0x0, socksRequestNumBytes);
memcpy(socksRequest, &socksRequestHeader, socksRequestHeaderNumBytes);
socksRequest[socksRequestHeaderNumBytes] = domainNameNumBytes;
memcpy(socksRequest + socksRequestHeaderNumBytes + sizeof(uint8_t), [domain UTF8String],
domainNameNumBytes);
uint16_t httpPort = htons(kHttpPort);
memcpy(socksRequest + socksRequestHeaderNumBytes + sizeof(uint8_t) + domainNameNumBytes,
&httpPort, sizeof(uint16_t));
NSData *socksRequestData =
[[NSData alloc] initWithBytes:socksRequest length:socksRequestNumBytes];
[self.credentialsSocket writeData:socksRequestData withTimeout:kTcpSocketTimeoutSecs tag:0];
[self.credentialsSocket readDataToLength:kSocksConnectResponseNumBytes
withTimeout:kTcpSocketTimeoutSecs
tag:0];
NSString *httpRequest =
[[NSString alloc] initWithFormat:@"HEAD / HTTP/1.1\r\nHost: %@\r\n\r\n", domain];
[self.credentialsSocket
writeData:[NSData dataWithBytes:[httpRequest UTF8String] length:httpRequest.length]
withTimeout:kTcpSocketTimeoutSecs
tag:kSocketTagHttpRequest];
[self.credentialsSocket readDataWithTimeout:kTcpSocketTimeoutSecs tag:kSocketTagHttpRequest];
[self.credentialsSocket disconnectAfterReading];
}
// Returns a statically defined array containing domain names for validating server credentials.
+ (const NSArray *)getCredentialsValidationDomains {
static const NSArray *kCredentialsValidationDomains;
static dispatch_once_t kDispatchOnceToken;
dispatch_once(&kDispatchOnceToken, ^{
// We have chosen these domains due to their neutrality.
kCredentialsValidationDomains =
@[ @"eff.org", @"ietf.org", @"w3.org", @"wikipedia.org", @"example.com" ];
});
return kCredentialsValidationDomains;
}
// Returns a random domain from |kCredentialsValidationDomains|.
- (NSString *)chooseRandomDomain {
const NSArray *domains = [ShadowsocksConnectivity getCredentialsValidationDomains];
int index = arc4random_uniform((uint32_t)domains.count);
return domains[index];
}
// Calls |credentialsCompletion| once with |areServerCredentialsValid|.
- (void)serverCredentialsCheckDone {
if (self.credentialsCompletion != NULL) {
self.credentialsCompletion(self.areServerCredentialsValid);
self.credentialsCompletion = NULL;
}
}
#pragma mark - Reachability
- (void)isReachable:(NSString *)host port:(uint16_t)port completion:(void (^)(BOOL))completion {
self.isServerReachable = false;
self.reachabilityCompletion = completion;
self.reachabilitySocket =
[[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self.dispatchQueue];
NSError *error;
[self.reachabilitySocket connectToHost:host
onPort:port
withTimeout:kTcpSocketTimeoutSecs
error:&error];
if (error) {
return;
}
}
// Calls |reachabilityCompletion| once with |isServerReachable|.
- (void)reachabilityCheckDone {
if (self.reachabilityCompletion != NULL) {
self.reachabilityCompletion(self.isServerReachable);
self.reachabilityCompletion = NULL;
}
}
#pragma mark - GCDAsyncSocketDelegate
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
// We don't need to inspect any of the data, as the SOCKS responses are hardcoded in ss-local and
// the fact that we have read the HTTP response indicates that the server credentials are valid.
if (tag == kSocketTagHttpRequest && data != nil) {
NSString *httpResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
self.areServerCredentialsValid = httpResponse != nil && [httpResponse hasPrefix:@"HTTP/1.1"];
}
}
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
if ([self.reachabilitySocket isEqual:sock]) {
self.isServerReachable = true;
[self.reachabilitySocket disconnect];
}
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error {
if ([self.reachabilitySocket isEqual:sock]) {
[self reachabilityCheckDone];
} else {
[self serverCredentialsCheckDone];
}
}
@end

View file

@ -1,86 +0,0 @@
// Copyright 2018 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
// Represents an IP subnetwork.
@objcMembers
class Subnet: NSObject {
static let kReservedSubnets = [
"10.0.0.0/8",
"100.64.0.0/10",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.2.0/24",
"192.31.196.0/24",
"192.52.193.0/24",
"192.88.99.0/24",
"192.168.0.0/16",
"192.175.48.0/24",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"240.0.0.0/4"
]
// Parses a CIDR subnet into a Subnet object. Returns nil on failure.
static func parse(_ cidrSubnet: String) -> Subnet? {
let components = cidrSubnet.components(separatedBy: "/")
guard components.count == 2 else {
NSLog("Malformed CIDR subnet")
return nil
}
guard let prefix = UInt16(components[1]) else {
NSLog("Invalid subnet prefix")
return nil
}
return Subnet(address: components[0], prefix: prefix)
}
// Returns a list of reserved Subnets.
static func getReservedSubnets() -> [Subnet] {
var subnets: [Subnet] = []
for cidrSubnet in kReservedSubnets {
if let subnet = self.parse(cidrSubnet) {
subnets.append(subnet)
}
}
return subnets
}
public var address: String
public var prefix: UInt16
public var mask: String
init(address: String, prefix: UInt16) {
self.address = address
self.prefix = prefix
let mask = (0xffffffff as UInt32) << (32 - prefix);
self.mask = mask.IPv4String()
}
}
extension UInt32 {
// Returns string representation of the integer as an IP address.
public func IPv4String() -> String {
let ip = self
let a = UInt8((ip>>24) & 0xff)
let b = UInt8((ip>>16) & 0xff)
let c = UInt8((ip>>8) & 0xff)
let d = UInt8(ip & 0xff)
return "\(a).\(b).\(c).\(d)"
}
}

View file

@ -0,0 +1,21 @@
#ifndef iosopenvpn2ssadapter_h
#define iosopenvpn2ssadapter_h
#import <Foundation/Foundation.h>
#import "ssadapterpacketflow.h"
#import <OpenVPNAdapter/OpenVPNAdapterPacketFlow.h>
@interface ShadowSocksAdapterFlowBridge: NSObject
@property (nonatomic, weak) id<ShadowSocksAdapterPacketFlow> ssPacketFlow;
@property (nonatomic, readonly) CFSocketRef ssSocket;
@property (nonatomic, readonly) CFSocketRef packetFlowSocket;
- (BOOL)configureSocketWithError:(NSError **)error;
- (void)invalidateSocketsIfNeeded;
- (void)processPackets;
@end
#endif /* iosopenvpn2ssadapter_h */

View file

@ -0,0 +1,138 @@
#import "iosopenvpn2ssadapter.h"
#include <sys/socket.h>
#include <arpa/inet.h>
#import "sspacket.h"
#import "ssadapterpacketflow.h"
@implementation ShadowSocksAdapterFlowBridge
# pragma mark - Sockets Configuration
static void SocketCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *obj) {
if (type != kCFSocketDataCallBack) {
return;
}
SSPacket *packet = [[SSPacket alloc] initWithSSData:data];
ShadowSocksAdapterFlowBridge *bridge = (__bridge ShadowSocksAdapterFlowBridge*)obj;
[bridge writePackets:@[packet] toPacketFlow:bridge.ssPacketFlow];
}
- (BOOL)configureSocketWithError:(NSError * __autoreleasing *)error {
int sockets[2];
if (socketpair(PF_LOCAL, SOCK_DGRAM, IPPROTO_IP, sockets) == -1) {
if (error) {
NSDictionary *userInfo = @{
// TODO: handle
};
*error = [NSError errorWithDomain:@"Some domain" code:100 userInfo:userInfo];
}
return NO;
}
CFSocketContext socketCtx = {0, (__bridge void *)self, NULL, NULL, NULL};
_packetFlowSocket = CFSocketCreateWithNative(kCFAllocatorDefault, sockets[0], kCFSocketDataCallBack, SocketCallback, &socketCtx);
_ssSocket = CFSocketCreateWithNative(kCFAllocatorDefault, sockets[1], kCFSocketNoCallBack, NULL, NULL);
if (!(_packetFlowSocket && _ssSocket)) {
if (error) {
NSDictionary *userInfo = @{
// TODO: handle
};
*error = [NSError errorWithDomain:@"Some domain" code:100 userInfo:userInfo];
}
return NO;
}
if (!([self configureOptionsForSocket:_packetFlowSocket error:error] && [self configureOptionsForSocket:_ssSocket error:error])) {
return NO;
}
CFRunLoopSourceRef packetFlowSocketSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _packetFlowSocket, 0);
CFRunLoopAddSource(CFRunLoopGetMain(), packetFlowSocketSource, kCFRunLoopDefaultMode);
CFRelease(packetFlowSocketSource);
return YES;
}
- (void)invalidateSocketsIfNeeded {
if (_ssSocket) {
CFSocketInvalidate(_ssSocket);
CFRelease(_ssSocket);
_ssSocket = NULL;
}
if (_packetFlowSocket) {
CFSocketInvalidate(_packetFlowSocket);
CFRelease(_packetFlowSocket);
_packetFlowSocket = NULL;
}
}
- (void)processPackets {
NSAssert(self.ssPacketFlow != nil, @"packetFlow property shouldn't be nil, set it before start reading packets.");
__weak typeof(self) weakSelf = self;
[self.ssPacketFlow readPacketsWithCompletionHandler:^(NSArray<NSData *> *packets, NSArray<NSNumber *> *protocols) {
__strong typeof(self) self = weakSelf;
[self writePackets:packets protocols:protocols toSocket:self.packetFlowSocket];
[self processPackets];
}];
}
- (void)dealloc {
[self invalidateSocketsIfNeeded];
}
# pragma mark - Socket configuration
- (BOOL)configureOptionsForSocket:(CFSocketRef)socket error:(NSError * __autoreleasing *)error {
CFSocketNativeHandle socketHandle = CFSocketGetNative(socket);
int buf_value = 65536;
socklen_t buf_len = sizeof(buf_value);
if (setsockopt(socketHandle, SOL_SOCKET, SO_RCVBUF, &buf_value, buf_len) == -1) {
if (error) {
NSDictionary *userInfo = @{
// TODO: handle
};
*error = [NSError errorWithDomain:@"Some domain" code:100 userInfo:userInfo];
}
return NO;
}
if (setsockopt(socketHandle, SOL_SOCKET, SO_SNDBUF, &buf_value, buf_len) == -1) {
if (error) {
NSDictionary *userInfo = @{
// TODO: handle
};
*error = [NSError errorWithDomain:@"Some domain" code:100 userInfo:userInfo];
}
return NO;
}
return YES;
}
# pragma mark - Protocol methods
- (void)writePackets:(NSArray<SSPacket *> *)packets toPacketFlow:(id<ShadowSocksAdapterPacketFlow>)packetFlow {
NSAssert(self.ssPacketFlow != nil, @"packetFlow shouldn't be nil, check provided parameter before start writing packets.");
NSMutableArray<NSData *> *flowPackets = [[NSMutableArray alloc] init];
NSMutableArray<NSNumber *> *protocols = [[NSMutableArray alloc] init];
[packets enumerateObjectsUsingBlock:^(SSPacket * _Nonnull packet, NSUInteger idx, BOOL * _Nonnull stop) {
[flowPackets addObject:packet.ssPacketFlowData];
[protocols addObject:packet.protocolFamily];
}];
[packetFlow writePackets:flowPackets withProtocols:protocols];
}
- (void)writePackets:(NSArray<NSData *> *)packets protocols:(NSArray<NSNumber *> *)protocols toSocket:(CFSocketRef)socket {
if (socket == NULL) { return; }
[packets enumerateObjectsUsingBlock:^(NSData *data, NSUInteger idx, BOOL *stop) {
NSNumber *protocolFamily = protocols[idx];
SSPacket *packet = [[SSPacket alloc] initWithPacketFlowData:data protocolFamily:protocolFamily];
CFSocketSendData(socket, NULL, (CFDataRef)packet.vpnData, 0.05);
}];
}
@end

View file

@ -1,17 +1,43 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation
import NetworkExtension
import os
import Darwin
import OpenVPNAdapter
//import ShadowSocks
//import Tun2socks
import Tun2socks
enum TunnelProtoType: String {
case wireguard, openvpn, shadowsocks, none
}
struct Constants {
static let kDefaultPathKey = "defaultPath"
static let processQueueName = "org.amnezia.process-packets"
static let ssQueueName = "org.amnezia.shadowsocks"
static let kActivationAttemptId = "activationAttemptId"
static let ovpnConfigKey = "ovpn"
static let ssConfigKey = "ss"
static let loggerTag = "NET"
static let ssRemoteHost = "server"
static let ssRemotePort = "server_port"
static let ssLocalAddressKey = "local_addr"
static let ssLocalPortKey = "local_port"
static let ssTimeoutKey = "timeout"
static let ssCipherKey = "method"
static let ssPasswordKey = "password"
static let kActionStart = "start"
static let kActionRestart = "restart"
static let kActionStop = "stop"
static let kActionGetTunnelId = "getTunnelId"
static let kActionIsServerReachable = "isServerReachable"
static let kMessageKeyAction = "action"
static let kMessageKeyTunnelid = "tunnelId"
static let kMessageKeyConfig = "config"
static let kMessageKeyErrorCode = "errorCode"
static let kMessageKeyHost = "host"
static let kMessageKeyPort = "port"
static let kMessageKeyOnDemand = "is-on-demand"
}
typealias ShadowsocksProxyCompletion = ((Int32, NSError?) -> Void)?
class PacketTunnelProvider: NEPacketTunnelProvider {
@ -29,15 +55,20 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}()
private var shadowSocksConfig: Data? = nil
private var openVPNConfig: Data? = nil
var ssCompletion: ShadowsocksProxyCompletion = nil
// private var shadowSocksPort: Int32 = 0
// private var isShadowsocksRunning: Bool = false
// private let ssQueue = DispatchQueue(label: "org.amnezia.shadowsocks")
// private var tun2socksWriter: AmneziaTun2SocksWriter? = nil
// private var tun2socksTunnel: Tun2socksOutlineTunnelProtocol? = nil
// private let processQueue = DispatchQueue(label: "org.amnezia.process-packets")
private var ssProvider: ShadowSocksTunnel? = nil
private var ssLocalPort: Int = 8585
private var ssRemoteHost = ""
private var leafProvider: TunProvider? = nil
private var tun2socksTunnel: Tun2socksOutlineTunnelProtocol? = nil
private var tun2socksWriter: Tun2socksTunWriter? = nil
private let processQueue = DispatchQueue(label: Constants.processQueueName)
private var connection: NWTCPConnection? = nil
private var session: NWUDPSession? = nil
private var observer: AnyObject?
let vpnReachability = OpenVPNReachability()
@ -46,15 +77,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
var protoType: TunnelProtoType = .wireguard
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
let activationAttemptId = options?["activationAttemptId"] as? String
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
Logger.configureGlobal(tagged: "NET", withFilePath: FileManager.logFileURL?.path)
Logger.configureGlobal(tagged: Constants.loggerTag, withFilePath: FileManager.logFileURL?.path)
if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let _: Data = providerConfiguration["ovpn"] as? Data {
let withoutShadowSocks = providerConfiguration["ss"] as? Data == nil
let _: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data {
let withoutShadowSocks = providerConfiguration[Constants.ssConfigKey] as? Data == nil
protoType = withoutShadowSocks ? .openvpn : .shadowsocks
} else {
protoType = .wireguard
@ -88,47 +119,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
if messageData.count == 1 && messageData[0] == 0 {
wgAdapter.getRuntimeConfiguration { settings in
var data: Data?
if let settings = settings {
data = settings.data(using: .utf8)!
}
completionHandler(data)
}
} else if messageData.count >= 1 {
// Updates the tunnel configuration and responds with the active configuration
wg_log(.info, message: "Switching tunnel configuration")
guard let configString = String(data: messageData, encoding: .utf8)
else {
completionHandler(nil)
return
}
do {
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
if let error = error {
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
completionHandler(nil)
return
}
self.wgAdapter.getRuntimeConfiguration { settings in
var data: Data?
if let settings = settings {
data = settings.data(using: .utf8)!
}
completionHandler(data)
}
}
} catch {
completionHandler(nil)
}
} else {
completionHandler(nil)
switch protoType {
case .wireguard:
handleWireguardAppMessage(messageData, completionHandler: completionHandler)
case .openvpn:
handleWireguardAppMessage(messageData, completionHandler: completionHandler)
case .shadowsocks:
handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
case .none:
break
}
}
@ -188,7 +187,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let ovpnConfiguration: Data = providerConfiguration["ovpn"] as? Data else {
let ovpnConfiguration: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
// TODO: handle errors properly
wg_log(.error, message: "Can't start startOpenVPN()")
return
@ -200,65 +199,18 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private func startShadowSocks(completionHandler: @escaping (Error?) -> Void) {
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let ssConfiguration: Data = providerConfiguration["ss"] as? Data,
let ovpnConfiguration: Data = providerConfiguration["ovpn"] as? Data else {
let ssConfiguration: Data = providerConfiguration[Constants.ssConfigKey] as? Data,
let ovpnConfiguration: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
// TODO: handle errors properly
wg_log(.error, message: "Cannot start startShadowSocks()")
return
}
self.shadowSocksConfig = ssConfiguration
// guard let config = self.shadowSocksConfig else { return }
// guard let ssConfig = try? JSONSerialization.jsonObject(with: config, options: []) as? [String: Any] else {
// self.ssCompletion?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown",
// code: 100,
// userInfo: [NSLocalizedDescriptionKey: "Cannot parse json for ss in tunnel"]))
// return
// }
//
// let sshost = ssConfig["local_addr"] as? String
// let ssport = ssConfig["local_port"] as? Int ?? 8585
// let method = ssConfig["method"] as? String
// let password = ssConfig["password"] as? String
//
// var errorCode: Int = 0
// ShadowsocksCheckConnectivity(sshost, ssport, password, method, &errorCode, nil)
// if (errorCode != 0) {
// self.ssCompletion?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown",
// code: 100,
// userInfo: [NSLocalizedDescriptionKey: "Error checking ss connectivity: \(errorCode)"]))
// return
// }
self.setupAndlaunchOpenVPN(withConfig: ovpnConfiguration) { error in
guard error == nil else {
wg_log(.error, message: "Start OpenVPN tunnel error : \(error?.localizedDescription ?? "none")")
completionHandler(error!)
return
}
wg_log(.error, message: "OpenVPN tunnel connected.")
// self.startTun2Socks(host: sshost, port: ssport, password: password, cipher: method, isUDPEnabled: false, error: nil)
}
//// Thread.detachNewThread { [weak self] in
//// setupAndLaunchShadowSocksProxy(withConfig: ssConfiguration, ssHandler: { [weak self] port, error in
//// wg_log(.info,
//// message: "Prepare to start openvpn, self is \(self == nil ? "null" : "not null")")
//// guard error == nil else {
//// wg_log(.error, message: "Stopping tunnel: \(error?.localizedDescription ?? "none")")
//// completionHandler(error!)
//// return
//// }
////
//// self?.setupAndlaunchOpenVPN(withConfig: ovpnConfiguration) { error in
//// guard error == nil else {
//// wg_log(.error, message: "Start OpenVPN tunnel error : \(error?.localizedDescription ?? "none")")
//// completionHandler(error!)
//// return
//// }
//// wg_log(.error, message: "OpenVPN tunnel connected.")
//// }
//// })
//// }
self.openVPNConfig = ovpnConfiguration
wg_log(.info, message: "Prepare to start shadowsocks/tun2socks/leaf")
// self.startSSProvider(completion: completionHandler)
// startTun2SocksTunnel(completion: completionHandler)
self.startLeafRedirector(completion: completionHandler)
}
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
@ -290,49 +242,465 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
private func stopShadowSocks(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
stopOpenVPN(with: reason) {
// if self.tun2socksTunnel != nil && self.tun2socksTunnel!.isConnected() {
// self.tun2socksTunnel?.disconnect()
// try? self.tun2socksWriter?.close()
stopOpenVPN(with: reason) { [weak self] in
guard let `self` = self else { return }
// self.stopSSProvider(completionHandler: completionHandler)
// self.stopTun2SocksTunnel(completionHandler: completionHandler)
self.stopLeafRedirector(completion: completionHandler)
}
}
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
if messageData.count == 1 && messageData[0] == 0 {
wgAdapter.getRuntimeConfiguration { settings in
var data: Data?
if let settings = settings {
data = settings.data(using: .utf8)!
}
completionHandler(data)
}
} else if messageData.count >= 1 {
// Updates the tunnel configuration and responds with the active configuration
wg_log(.info, message: "Switching tunnel configuration")
guard let configString = String(data: messageData, encoding: .utf8)
else {
completionHandler(nil)
return
}
do {
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
if let error = error {
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
completionHandler(nil)
return
}
self.wgAdapter.getRuntimeConfiguration { settings in
var data: Data?
if let settings = settings {
data = settings.data(using: .utf8)!
}
completionHandler(data)
}
}
} catch {
completionHandler(nil)
}
} else {
completionHandler(nil)
}
}
private func handleShadowSocksAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
if let configString = String(data: messageData, encoding: .utf8) {
wg_log(.debug, message: configString)
}
completionHandler(messageData)
}
// MARK: -- Tun2sock provider methods
private func startTun2SocksTunnel(completion: @escaping (Error?) -> Void) {
guard let ssConfiguration = self.shadowSocksConfig,
let ovpnConfiguration = self.openVPNConfig,
let ssConfig = try? JSONSerialization.jsonObject(with: ssConfiguration, options: []) as? [String: Any]
else {
wg_log(.info, message: "Cannot parse shadowsocks config")
let tun2socksError: NSError = .init(domain: "", code: 100, userInfo: nil)
completion(tun2socksError)
return
}
wg_log(.info, message: "SS Config: \(ssConfig)")
guard let remoteHost = ssConfig[Constants.ssRemoteHost] as? String,
let remotePort = ssConfig[Constants.ssRemotePort] as? Int,
let method = ssConfig[Constants.ssCipherKey] as? String,
let password = ssConfig[Constants.ssPasswordKey] as? String else {
wg_log(.error, message: "Cannot parse ss config")
let tun2socksError: NSError = .init(domain: "", code: 100, userInfo: nil)
completion(tun2socksError)
return
}
let connError: AutoreleasingUnsafeMutablePointer<NSError?>? = nil
ShadowsocksCheckConnectivity(remoteHost, remotePort, password, method, nil, connError)
if (connError?.pointee != nil) {
wg_log(.error, message: "Failed to start tun2socks tunnel with error: \(connError?.pointee?.localizedDescription ?? "oops")")
let tun2socksError: NSError = .init(domain: "", code: 100, userInfo: nil)
completion(tun2socksError)
return
}
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, withShadowSocks: true) { vpnError in
guard vpnError == nil else {
wg_log(.error, message: "Failed to start openvpn with tun2socks tunnel with error: \(vpnError?.localizedDescription ?? "oops")")
let tun2socksError: NSError = .init(domain: "", code: 100, userInfo: nil)
completion(tun2socksError)
return
}
// let ipv4settings: NEIPv4Settings = .init(addresses: ["192.0.2.1"], subnetMasks: ["255.255.255.0"])
// ipv4settings.includedRoutes = [.default()]
// ipv4settings.excludedRoutes = []
//
// let dnsSettings: NEDNSSettings = .init(servers: ["1.1.1.1", "9.9.9.9", "208.67.222.222", "208.67.220.220"])
// let settings: NEPacketTunnelNetworkSettings = .init(tunnelRemoteAddress: "192.0.2.2")
// settings.ipv4Settings = ipv4settings
// settings.dnsSettings = dnsSettings
// settings.mtu = 1600
//
// setTunnelNetworkSettings(settings) { tunError in
let ifaces = Interface.allInterfaces()
.filter { $0.family == .ipv4 }
.map { iface in iface.name }
wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
self.tun2socksWriter = Tun2socksTunWriter()
let tunError: AutoreleasingUnsafeMutablePointer<NSError?>? = nil
self.tun2socksTunnel = Tun2socksConnectShadowsocksTunnel(self.tun2socksWriter, remoteHost, remotePort, password, method, false, tunError)
if (tunError?.pointee != nil) {
wg_log(.error, message: "Failed to start tun2socks tunnel with error: \(tunError?.pointee?.localizedDescription ?? "oops")")
let tun2socksError: NSError = .init(domain: "", code: 100, userInfo: nil)
completion(tun2socksError)
return
}
self.processQueue.async { self.processPackets() }
completion(nil)
}
}
private func stopTun2SocksTunnel(completionHandler: @escaping () -> Void) {
if self.tun2socksTunnel != nil && self.tun2socksTunnel!.isConnected() {
self.tun2socksTunnel?.disconnect()
}
try? self.tun2socksWriter?.close()
completionHandler()
}
private func processPackets() {
packetFlow.readPacketObjects { [weak self] packets in
guard let `self` = self else { return }
do {
let _ = try packets.map {
var bytesWritten: Int = 0
try self.tun2socksTunnel?.write($0.data, ret0_: &bytesWritten)
self.processQueue.async {
self.processPackets()
}
}
} catch (let err) {
wg_log(.debug, message: "Error in tun2sock: \(err.localizedDescription)")
}
}
}
// MARK: -- Leaf provider methods
private func prepareConfig(onInterface iface: String, fromSSConfig ssConfig: Data, andOvpnConfig ovpnConfig: Data) -> UnsafePointer<CChar>? {
guard let ssConfig = try? JSONSerialization.jsonObject(with: ssConfig, options: []) as? [String: Any] else {
self.ssCompletion?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown",
code: 100,
userInfo: [NSLocalizedDescriptionKey: "Cannot parse json for ss in tunnel"]))
return nil
}
guard let remoteHost = ssConfig[Constants.ssRemoteHost] as? String,
let remotePort = ssConfig[Constants.ssRemotePort] as? Int,
let method = ssConfig[Constants.ssCipherKey] as? String,
let password = ssConfig[Constants.ssPasswordKey] as? String else {
self.ssCompletion?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown",
code: 100,
userInfo: [NSLocalizedDescriptionKey: "Cannot asign profile params for ss in tunnel"]))
return nil
}
var insettings: [String: Any] = .init()
insettings["name"] = iface
insettings["address"] = "10.8.0.4"
insettings["netmask"] = "255.255.255.0"
insettings["gateway"] = "10.8.0.1"
insettings["mtu"] = 1600
var inbounds: [String: Any] = .init()
inbounds["protocol"] = "tun"
inbounds["settings"] = insettings
inbounds["tag"] = "tun_in"
var outbounds: [String: Any] = .init()
var outsettings: [String: Any] = .init()
outsettings["address"] = remoteHost
outsettings["port"] = remotePort
outsettings["method"] = method
outsettings["password"] = password
outbounds["protocol"] = "shadowsocks"
outbounds["settings"] = outsettings
outbounds["tag"] = "shadowsocks_out"
var params: [String: Any] = .init()
params["inbounds"] = [inbounds]
params["outbounds"] = [outbounds]
wg_log(.error, message: "Config dictionary: \(params)")
guard let jsonData = try? JSONSerialization.data(withJSONObject: params, options: .prettyPrinted),
let jsonString = String(data: jsonData, encoding: .utf8) else { return nil }
wg_log(.error, message: "JSON String: \(jsonString)")
var path = ""
if let documentDirectory = FileManager.default.urls(for: .documentDirectory,
in: .userDomainMask).first {
let pathWithFilename = documentDirectory.appendingPathComponent("config.json")
do {
try jsonString.write(to: pathWithFilename,
atomically: true,
encoding: .utf8)
path = pathWithFilename.path
} catch {
// Handle error
}
}
return UnsafePointer(strdup(path))
}
private func startLeafRedirector(completion: @escaping (Error?) -> Void) {
let ipv4settings: NEIPv4Settings = .init(addresses: ["10.8.0.4"], subnetMasks: ["255.255.255.0"])
ipv4settings.includedRoutes = [.default()]
ipv4settings.excludedRoutes = []
let dnsSettings: NEDNSSettings = .init(servers: ["1.1.1.1", "9.9.9.9", "208.67.222.222", "208.67.220.220"])
dnsSettings.matchDomains = []
let proxySettings: NEProxySettings = .init()
proxySettings.httpEnabled = true
proxySettings.httpServer = .init(address: "localhost", port: 8585)
proxySettings.httpsEnabled = true
proxySettings.httpsServer = .init(address: "localhost", port: 8585)
proxySettings.excludeSimpleHostnames = true
let settings: NEPacketTunnelNetworkSettings = .init(tunnelRemoteAddress: "10.8.0.1")
settings.ipv4Settings = ipv4settings
settings.dnsSettings = dnsSettings
settings.proxySettings = proxySettings
settings.mtu = 1600
self.setTunnelNetworkSettings(settings) { tunError in
let ifaces = Interface.allInterfaces()
.filter { $0.name.contains("tun") && $0.family == .ipv4 }
.map { iface in iface.name }
wg_log(.error, message: "Try on interface: \(ifaces)")
guard let ssConf = self.shadowSocksConfig,
let ovpnConf = self.openVPNConfig,
let config = self.prepareConfig(onInterface: ifaces.first ?? "utun2",
fromSSConfig: ssConf,
andOvpnConfig: ovpnConf) else {
let ret: NSError = .init(domain: "", code: 100, userInfo: nil)
completion(ret)
return
}
self.leafProvider = TunProvider(withConfig: config)
self.leafProvider?.testConfig(onPath: config) { configError in
wg_log(.error, message: "Config check status: \(configError!.desc)")
guard configError! == .noError else {
wg_log(.error, message: "Config check status: \(configError!.desc)")
let ret: NSError = .init(domain: "", code: 100, userInfo: nil)
completion(ret)
return
}
wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
self.leafProvider?.startTunnel { tunError in
wg_log(.error, message: "Leaf tunnel start status: \(tunError!.desc)")
guard tunError! == .noError else {
wg_log(.error, message: "Leaf tunnel start error: \(tunError!.desc)")
let ret: NSError = .init(domain: "", code: 100, userInfo: nil)
completion(ret)
return
}
completion(nil)
}
}
}
}
private func stopLeafRedirector(completion: @escaping () -> Void) {
leafProvider?.stopTunnel { error in
// TODO: handle errors
completion()
}
}
// MARK: -- ShadowSocks Provider methods
private func startSSProvider(completion: @escaping (Error?) -> Void) {
guard let ssConfig = self.shadowSocksConfig, let ovpnConfig = self.openVPNConfig else { return }
if ssProvider == nil {
guard let config = try? JSONSerialization.jsonObject(with: ssConfig, options: []) as? [String: Any],
let remoteHost = config[Constants.ssRemoteHost] as? String,
let port = config[Constants.ssLocalPortKey] as? Int else {
self.ssCompletion?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown",
code: 100,
userInfo: [NSLocalizedDescriptionKey: "Cannot parse json for ss in tunnel"]))
return
}
ssProvider = SSProvider(config: ssConfig, localPort: port)
ssLocalPort = port
ssRemoteHost = remoteHost
}
ssProvider?.start(usingPacketFlow: packetFlow, withConnectivityCheck: false) { errorCode in
wg_log(.info, message: "After starting shadowsocks")
wg_log(.error, message: "Starting ShadowSocks State: \(String(describing: errorCode))")
if (errorCode != nil && errorCode! != .noError) {
wg_log(.error, message: "Error starting ShadowSocks: \(String(describing: errorCode))")
return
}
// self.setupAndHandleOpenVPNOverSSConnection(withConfig: ovpnConfig)
self.startAndHandleTunnelOverSS(completionHandler: completion)
}
}
private func startAndHandleTunnelOverSS(completionHandler: @escaping (Error?) -> Void) {
let ipv4settings: NEIPv4Settings = .init(addresses: ["192.0.2.2"], subnetMasks: ["255.255.255.0"])
ipv4settings.includedRoutes = [.default()]
ipv4settings.excludedRoutes = []
let dnsSettings: NEDNSSettings = .init(servers: ["1.1.1.1", "9.9.9.9", "208.67.222.222", "208.67.220.220"])
let settings: NEPacketTunnelNetworkSettings = .init(tunnelRemoteAddress: "192.0.2.1")
settings.ipv4Settings = ipv4settings
settings.dnsSettings = dnsSettings
settings.mtu = 1600
setTunnelNetworkSettings(settings) { tunError in
let ifaces = Interface.allInterfaces()
.filter { $0.family == .ipv4 }
.map { iface in iface.name }
wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
let endpoint = NWHostEndpoint(hostname: "127.0.0.1", port: "\(self.ssLocalPort)")
self.session = self.createUDPSession(to: endpoint, from: nil)
self.observer = self.session!.observe(\.state, options: [.new]) { conn, _ in
switch conn.state {
case .ready:
self.setupWriteToFlow()
self.readFromFlow()
completionHandler(nil)
case .cancelled, .failed, .invalid:
self.stopSSProvider {
self.cancelTunnelWithError(nil)
completionHandler(nil)
}
default:
break
}
}
}
}
private func setupAndHandleOpenVPNOverSSConnection(withConfig ovpnConfig: Data) {
let endpoint = NWHostEndpoint(hostname: "127.0.0.1", port: "\(self.ssLocalPort)")
self.session = self.createUDPSession(to: endpoint, from: nil)
// self.connection = self.createTCPConnection(to: endpoint, enableTLS: false, tlsParameters: nil, delegate: nil)
self.observer = self.session!.observe(\.state, options: [.new]) { conn, _ in
switch conn.state {
case .ready:
self.processQueue.async {
self.setupWriteToFlow()
}
self.processQueue.async {
self.readFromFlow()
}
self.setupAndlaunchOpenVPN(withConfig: ovpnConfig, withShadowSocks: true) { vpnError in
wg_log(.info, message: "After starting openVPN")
guard vpnError == nil else {
wg_log(.error, message: "Failed to start openvpn with error: \(vpnError?.localizedDescription ?? "oops")")
return
}
}
case .cancelled, .failed, .invalid:
self.stopSSProvider {
self.cancelTunnelWithError(nil)
}
default:
break
}
}
}
private func readFromFlow() {
wg_log(.error, message: "Start reading packets to connection")
wg_log(.error, message: "Connection is \(session != nil ? "not null" : "null")")
packetFlow.readPackets { [weak self] packets, protocols in
wg_log(.error, message: "\(packets.count) outcoming packets proccessed of \(protocols.first?.stringValue ?? "unknown") type")
guard let `self` = self else { return }
self.session?.writeMultipleDatagrams(packets, completionHandler: { _ in
self.processQueue.async {
self.readFromFlow()
}
})
// let _ = packets.map {
// wg_log(.error, message: "Packet: \($0.data) of \($0.protocolFamily)")
// self.connection?.write($0.data, completionHandler: { _ in })
// self.processQueue.async {
// self.readFromFlow()
// }
// }
}
}
// private func startTun2Socks(host: String?, port: Int, password: String?, cipher: String?, isUDPEnabled: Bool, error: NSErrorPointer) {
// let isOn = self.tun2socksTunnel != nil && self.tun2socksTunnel!.isConnected()
// if isOn { tun2socksTunnel?.disconnect() }
// self.tun2socksWriter = AmneziaTun2SocksWriter(tunnelFlow: self.packetFlow)
// self.tun2socksTunnel = Tun2socksConnectShadowsocksTunnel(self.tun2socksWriter, host, port, password, cipher, isUDPEnabled, error)
// if (!isOn) {
// self.processQueue.sync { self.processPackets() }
// }
// }
// private func processPackets() {
// wg_log(.info, message: "Inside startTun2SocksPacketForwarder")
// packetFlow.readPacketObjects { [weak self] packets in
// guard let `self` = self else { return }
// do {
// let _ = try packets.map {
// var bytesWritten: Int = 0
// try self.tun2socksTunnel?.write($0.data, ret0_: &bytesWritten)
// self.processQueue.sync {
// self.processPackets()
// }
// }
// } catch (let err) {
// wg_log(.debug, message: "Error in tun2sock: \(err.localizedDescription)")
private func setupWriteToFlow() {
wg_log(.error, message: "Start writing packets from connection")
wg_log(.error, message: "Connection is \(session != nil ? "not null" : "null")")
session?.setReadHandler({ ssdata, error in
wg_log(.error, message: "Packets are \(ssdata != nil ? "not null" : "null"), error: \(error?.localizedDescription ?? "none")")
guard error == nil, let packets = ssdata else { return }
wg_log(.error, message: "\(packets.count) incoming packets proccessed")
self.packetFlow.writePackets(packets, withProtocols: [NSNumber(value: AF_INET)])
}, maxDatagrams: Int.max)
// connection?.readLength(1450, completionHandler: { [weak self] ssdata, readError in
// wg_log(.error, message: "Packets are \(ssdata != nil ? "not null" : "null")")
// guard let `self` = self, let packets = ssdata else { return }
// wg_log(.error, message: "Packet: \(packets) or error: \(readError?.localizedDescription ?? "")")
// self.packetFlow.writePackets([packets], withProtocols: [NSNumber(value: AF_INET)])
// self.processQueue.async {
// self.writeToFlow()
// }
// }
// }
// })
}
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data, completionHandler: @escaping (Error?) -> Void) {
private func stopSSProvider(completionHandler: @escaping () -> Void) {
self.ssProvider?.stop { _ in
if let provider = self.ssProvider, let threadId = provider.ssLocalThreadId {
pthread_kill(threadId, SIGUSR1)
}
self.ssProvider = nil
completionHandler()
}
}
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data, withShadowSocks viaSS: Bool = false, completionHandler: @escaping (Error?) -> Void) {
wg_log(.info, message: "Inside setupAndlaunchOpenVPN()")
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
wg_log(.info, message: "OPENVPN config: \(str)")
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnConfiguration
if viaSS {
// configuration.settings = [
// "remote": "137.74.6.148 1194",
// "proto": "tcp",
// "link-mtu": "1480",
// "tun-mtu": "1460",
// ]
}
let evaluation: OpenVPNConfigurationEvaluation
do {
evaluation = try ovpnAdapter.apply(configuration: configuration)
@ -352,102 +720,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
startHandler = completionHandler
ovpnAdapter.connect(using: packetFlow)
let ifaces = Interface.allInterfaces()
.filter { $0.family == .ipv4 }
.map { iface in iface.name }
wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
}
// private func setupAndLaunchShadowSocksProxy(withConfig config: Data, ssHandler: ShadowsocksProxyCompletion) {
// let str = String(decoding: config, as: UTF8.self)
// wg_log(.info, message: "config: \(str)")
// ssCompletion = ssHandler
// guard let ssConfig = try? JSONSerialization.jsonObject(with: config, options: []) as? [String: Any] else {
// ssHandler?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown",
// code: 100,
// userInfo: [NSLocalizedDescriptionKey: "Cannot parse json for ss in tunnel"]))
// return
// }
//
// wg_log(.info, message: "SS Config: \(ssConfig)")
//
// guard let remoteHost = ssConfig["server"] as? String, // UnsafeMutablePointer<CChar>,
// let remotePort = ssConfig["server_port"] as? Int32,
// let localAddress = ssConfig["local_addr"] as? String, //UnsafeMutablePointer<CChar>,
// let localPort = ssConfig["local_port"] as? Int32,
// let method = ssConfig["method"] as? String, //UnsafeMutablePointer<CChar>,
// let password = ssConfig["password"] as? String,//UnsafeMutablePointer<CChar>,
// let timeout = ssConfig["timeout"] as? Int32
// else {
// ssHandler?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown",
// code: 100,
// userInfo: [NSLocalizedDescriptionKey: "Cannot assing profile params for ss in tunnel"]))
// return
// }
//
// /* An example profile
// *
// * const profile_t EXAMPLE_PROFILE = {
// * .remote_host = "example.com",
// * .local_addr = "127.0.0.1",
// * .method = "bf-cfb",
// * .password = "barfoo!",
// * .remote_port = 8338,
// * .local_port = 1080,
// * .timeout = 600;
// * .acl = NULL,
// * .log = NULL,
// * .fast_open = 0,
// * .mode = 0,
// * .verbose = 0
// * };
// */
//
// var profile: profile_t = .init()
// memset(&profile, 0, MemoryLayout<profile_t>.size)
// profile.remote_host = strdup(remoteHost)
// profile.remote_port = remotePort
// profile.local_addr = strdup(localAddress)
// profile.local_port = localPort
// profile.method = strdup(method)
// profile.password = strdup(password)
// profile.timeout = timeout
// profile.acl = nil
// profile.log = nil
// profile.mtu = 1600
// profile.fast_open = 1
// profile.mode = 0
// profile.verbose = 1
//
// wg_log(.debug, message: "Prepare to start shadowsocks proxy server...")
// let observer = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
// ssQueue.sync { [weak self] in
// let success = start_ss_local_server_with_callback(profile, { socks_fd, udp_fd, data in
// wg_log(.debug, message: "Inside cb callback")
// wg_log(.debug, message: "Params: socks_fd -> \(socks_fd), udp_fd -> \(udp_fd)")
// if let obs = data {
// wg_log(.debug, message: "Prepare to call onShadowsocksCallback() with socks port \(socks_fd) and udp port \(udp_fd)")
// let mySelf = Unmanaged<PacketTunnelProvider>.fromOpaque(obs).takeUnretainedValue()
// mySelf.onShadowsocksCallback(fd: socks_fd)
// }
// }, observer)
// if success != -1 {
// wg_log(.error, message: "ss proxy started on port \(localPort)")
// self?.shadowSocksPort = localPort
// self?.isShadowsocksRunning = true
// } else {
// wg_log(.error, message: "Failed to start ss proxy")
// }
// }
// }
//
// private func onShadowsocksCallback(fd: Int32) {
// wg_log(.debug, message: "Inside onShadowsocksCallback() with port \(fd)")
// var error: NSError? = nil
// if fd > 0 {
//// shadowSocksPort = getSockPort(for: fd)
// isShadowsocksRunning = true
// } else {
// error = NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", code: 100, userInfo: [NSLocalizedDescriptionKey : "Failed to start shadowsocks proxy"])
// }
// ssCompletion?(shadowSocksPort, error)
// }
// MARK: -- Network observing methods
private func getSockPort(for fd: Int32) -> Int32 {
var addr_in = sockaddr_in();
@ -468,6 +749,40 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return 0
}
}
private func startListeningForNetworkChanges() {
stopListeningForNetworkChanges()
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
}
private func stopListeningForNetworkChanges() {
removeObserver(self, forKeyPath: Constants.kDefaultPathKey)
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
guard Constants.kDefaultPathKey != keyPath else { return }
// Since iOS 11, we have observed that this KVO event fires repeatedly when connecting over Wifi,
// even though the underlying network has not changed (i.e. `isEqualToPath` returns false),
// leading to "wakeup crashes" due to excessive network activity. Guard against false positives by
// comparing the paths' string description, which includes properties not exposed by the class
guard let lastPath: NWPath = change?[.oldKey] as? NWPath,
let defPath = defaultPath,
lastPath != defPath || lastPath.description != defPath.description else {
return
}
DispatchQueue.main.async { [weak self] in
guard let `self` = self, self.defaultPath != nil else { return }
self.handle(networkChange: self.defaultPath!) { _ in }
}
}
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
wg_log(.info, message: "Tunnel restarted.")
startTunnel(options: nil, completionHandler: completion)
}
}
extension WireGuardLogLevel {
@ -483,6 +798,8 @@ extension WireGuardLogLevel {
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
extension NEPacketTunnelFlow: ShadowSocksAdapterPacketFlow {}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
@ -561,19 +878,12 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
}
}
//class AmneziaTun2SocksWriter: Tun2socksTunWriter {
// private var tunnelFlow: NEPacketTunnelFlow
//
// init(tunnelFlow: NEPacketTunnelFlow) {
// self.tunnelFlow = tunnelFlow
// super.init()
// }
//
// override func write(_ p0: Data?, n: UnsafeMutablePointer<Int>?) throws {
// if let packets = p0 {
// tunnelFlow.writePackets([packets], withProtocols: [NSNumber(value: AF_INET)])
// }
// }
//
// override func close() throws {}
//}
extension PacketTunnelProvider: Tun2socksTunWriterProtocol {
func write(_ p0: Data?, n: UnsafeMutablePointer<Int>?) throws {
if let packets = p0 {
self.packetFlow.writePackets([packets], withProtocols: [NSNumber(value: AF_INET)])
}
}
func close() throws {}
}

View file

@ -0,0 +1,12 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol ShadowSocksAdapterPacketFlow <NSObject>
- (void)readPacketsWithCompletionHandler:(void (^)(NSArray<NSData *> *packets, NSArray<NSNumber *> *protocols))completionHandler;
- (BOOL)writePackets:(NSArray<NSData *> *)packets withProtocols:(NSArray<NSNumber *> *)protocols;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,16 @@
#ifndef sspacket_h
#define sspacket_h
#import <Foundation/Foundation.h>
@interface SSPacket : NSObject
@property (readonly, nonatomic) NSData *vpnData;
@property (readonly, nonatomic) NSData *ssPacketFlowData;
@property (readonly,nonatomic) NSNumber *protocolFamily;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithSSData:(NSData *)data NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithPacketFlowData:(NSData *)data protocolFamily:(NSNumber *)protocolFamily NS_DESIGNATED_INITIALIZER;
@end
#endif /* sspacket_h */

View file

@ -0,0 +1,57 @@
#import <sspacket.h>
#include <arpa/inet.h>
@interface SSPacket () {
NSData *_data;
NSNumber *_protocolFamily;
}
@end
@implementation SSPacket
- (instancetype)initWithSSData:(NSData *)data {
if (self = [super init]) {
// NSUInteger prefix_size = sizeof(uint32_t);
// uint32_t protocol = PF_UNSPEC;
// [data getBytes:&protocol length:prefix_size];
// protocol = CFSwapInt32HostToBig(protocol);
//
// NSRange range = NSMakeRange(prefix_size, data.length - prefix_size);
// NSData *packetData = [data subdataWithRange:range];
// _data = packetData;
// _protocolFamily = @(protocol);
_data = data;
_protocolFamily = @(PF_INET);
}
return self;
}
- (instancetype)initWithPacketFlowData:(NSData *)data protocolFamily:(NSNumber *)protocolFamily {
if (self = [super init]) {
_data = data;
_protocolFamily = protocolFamily;
}
return self;
}
- (NSData *)vpnData {
// uint32_t prefix = CFSwapInt32HostToBig(_protocolFamily.unsignedIntegerValue);
// NSUInteger prefix_size = sizeof(uint32_t);
// NSMutableData *data = [NSMutableData dataWithCapacity:prefix_size + _data.length];
//
// [data appendBytes:&prefix length:prefix_size];
// [data appendData:_data];
return _data;
}
- (NSData *)ssPacketFlowData {
return _data;
}
- (NSNumber *)protocolFamily {
return _protocolFamily;
}
@end