missing files added

This commit is contained in:
Alex Kh 2021-12-31 10:58:36 +04:00
parent db527be97c
commit 7131257354
227 changed files with 16475 additions and 0 deletions

View file

@ -0,0 +1,230 @@
import Foundation
import Darwin
typealias InetFamily = UInt8
typealias Flags = Int32
func destinationAddress(_ data: ifaddrs) -> UnsafeMutablePointer<sockaddr>! { return data.ifa_dstaddr }
func socketLength4(_ addr: sockaddr) -> UInt32 { return socklen_t(addr.sa_len) }
/**
* This class represents a network interface in your system. For example, `en0` with a certain IP address.
* It is a wrapper around the `getifaddrs` system call.
*
* Typical use of this class is to first call `Interface.allInterfaces()` and then use the properties of the interface(s) that you need.
*
* - See: `/usr/include/ifaddrs.h`
*/
open class Interface : CustomStringConvertible, CustomDebugStringConvertible {
public var id = UUID()
/// The network interface family (IPv4 or IPv6, otherwise error).
public enum Family : Int {
case ipv4
case ipv6
case other
public func toString() -> String {
switch (self) {
case .ipv4: return "IPv4"
case .ipv6: return "IPv6"
default: return "other"
}
}
}
/**
* Returns all network interfaces in your system. If you have an interface name (e.g. `en0`) that has
* multiple IP addresses (e.g. one IPv4 address and a few IPv6 addresses), then they will be returned
* as separate instances of Interface.
* - Returns: An array containing all network interfaces in your system.
*/
public static func allInterfaces() -> [Interface] {
var interfaces : [Interface] = []
var ifaddrsPtr : UnsafeMutablePointer<ifaddrs>? = nil
if getifaddrs(&ifaddrsPtr) == 0 {
var ifaddrPtr = ifaddrsPtr
while ifaddrPtr != nil {
let addr = ifaddrPtr?.pointee.ifa_addr.pointee
if addr?.sa_family == InetFamily(AF_INET) || addr?.sa_family == InetFamily(AF_INET6) {
interfaces.append(Interface(data: (ifaddrPtr?.pointee)!))
}
ifaddrPtr = ifaddrPtr?.pointee.ifa_next
}
freeifaddrs(ifaddrsPtr)
}
return interfaces
}
/**
* Returns a new Interface instance that does not represent a real network interface, but can be used for (unit) testing.
* - Returns: An instance of Interface that does *not* represent a real network interface.
*/
public static func createTestDummy(_ name:String, family:Family, address:String, multicastSupported:Bool, broadcastAddress:String?) -> Interface
{
return Interface(name: name, family: family, address: address, netmask: nil, running: true, up: true, loopback: false, multicastSupported: multicastSupported, broadcastAddress: broadcastAddress)
}
/**
* Initialize a new Interface with the given properties.
*/
public init(name:String, family:Family, address:String?, netmask:String?, running:Bool, up:Bool, loopback:Bool, multicastSupported:Bool, broadcastAddress:String?) {
self.name = name
self.family = family
self.address = address
self.netmask = netmask
self.running = running
self.up = up
self.loopback = loopback
self.multicastSupported = multicastSupported
self.broadcastAddress = broadcastAddress
}
convenience init(data:ifaddrs) {
let flags = Flags(data.ifa_flags)
let broadcastValid : Bool = ((flags & IFF_BROADCAST) == IFF_BROADCAST)
self.init(name: String(cString: data.ifa_name),
family: Interface.extractFamily(data),
address: Interface.extractAddress(data.ifa_addr),
netmask: Interface.extractAddress(data.ifa_netmask),
running: ((flags & IFF_RUNNING) == IFF_RUNNING),
up: ((flags & IFF_UP) == IFF_UP),
loopback: ((flags & IFF_LOOPBACK) == IFF_LOOPBACK),
multicastSupported: ((flags & IFF_MULTICAST) == IFF_MULTICAST),
broadcastAddress: ((broadcastValid && destinationAddress(data) != nil) ? Interface.extractAddress(destinationAddress(data)) : nil))
}
fileprivate static func extractFamily(_ data:ifaddrs) -> Family {
var family : Family = .other
let addr = data.ifa_addr.pointee
if addr.sa_family == InetFamily(AF_INET) {
family = .ipv4
}
else if addr.sa_family == InetFamily(AF_INET6) {
family = .ipv6
}
else {
family = .other
}
return family
}
fileprivate static func extractAddress(_ address: UnsafeMutablePointer<sockaddr>?) -> String? {
guard let address = address else { return nil }
return address.withMemoryRebound(to: sockaddr_storage.self, capacity: 1) {
if (address.pointee.sa_family == sa_family_t(AF_INET)) {
return extractAddress_ipv4($0)
}
else if (address.pointee.sa_family == sa_family_t(AF_INET6)) {
return extractAddress_ipv6($0)
}
else {
return nil
}
}
}
fileprivate static func extractAddress_ipv4(_ address:UnsafeMutablePointer<sockaddr_storage>) -> String? {
return address.withMemoryRebound(to: sockaddr.self, capacity: 1) { addr in
var address : String? = nil
var hostname = [CChar](repeating: 0, count: Int(2049))
if (getnameinfo(&addr.pointee, socklen_t(socketLength4(addr.pointee)), &hostname,
socklen_t(hostname.count), nil, socklen_t(0), NI_NUMERICHOST) == 0) {
address = String(cString: hostname)
}
else {
// var error = String.fromCString(gai_strerror(errno))!
// println("ERROR: \(error)")
}
return address
}
}
fileprivate static func extractAddress_ipv6(_ address:UnsafeMutablePointer<sockaddr_storage>) -> String? {
var addr = address.pointee
var ip : [Int8] = [Int8](repeating: Int8(0), count: Int(INET6_ADDRSTRLEN))
return inetNtoP(&addr, ip: &ip)
}
fileprivate static func inetNtoP(_ addr:UnsafeMutablePointer<sockaddr_storage>, ip:UnsafeMutablePointer<Int8>) -> String? {
return addr.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) { addr6 in
let conversion:UnsafePointer<CChar> = inet_ntop(AF_INET6, &addr6.pointee.sin6_addr, ip, socklen_t(INET6_ADDRSTRLEN))
return String(cString: conversion)
}
}
/**
* Creates the network format representation of the interface's IP address. Wraps `inet_pton`.
*/
open var addressBytes: [UInt8]? {
guard let addr = address else { return nil }
let af:Int32
let len:Int
switch family {
case .ipv4:
af = AF_INET
len = 4
case .ipv6:
af = AF_INET6
len = 16
default:
return nil
}
var bytes = [UInt8](repeating: 0, count: len)
let result = inet_pton(af, addr, &bytes)
return ( result == 1 ) ? bytes : nil
}
/// `IFF_RUNNING` flag of `ifaddrs->ifa_flags`.
open var isRunning: Bool { return running }
/// `IFF_UP` flag of `ifaddrs->ifa_flags`.
open var isUp: Bool { return up }
/// `IFF_LOOPBACK` flag of `ifaddrs->ifa_flags`.
open var isLoopback: Bool { return loopback }
/// `IFF_MULTICAST` flag of `ifaddrs->ifa_flags`.
open var supportsMulticast: Bool { return multicastSupported }
/// Field `ifaddrs->ifa_name`.
public let name : String
/// Field `ifaddrs->ifa_addr->sa_family`.
public let family : Family
/// Extracted from `ifaddrs->ifa_addr`, supports both IPv4 and IPv6.
public let address : String?
/// Extracted from `ifaddrs->ifa_netmask`, supports both IPv4 and IPv6.
public let netmask : String?
/// Extracted from `ifaddrs->ifa_dstaddr`. Not applicable for IPv6.
public let broadcastAddress : String?
fileprivate let running : Bool
fileprivate let up : Bool
fileprivate let loopback : Bool
fileprivate let multicastSupported : Bool
/// Returns the interface name.
open var description: String { return name }
/// Returns a string containing a few properties of the Interface.
open var debugDescription: String {
var s = "Interface name:\(name) family:\(family)"
if let ip = address {
s += " ip:\(ip)"
}
s += isUp ? " (up)" : " (down)"
s += isRunning ? " (running)" : "(not running)"
return s
}
}
#if swift(>=5)
extension Interface: Identifiable {}
#endif

View file

@ -0,0 +1,36 @@
#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

@ -0,0 +1,334 @@
#import "./ssconnectivity.h"
#include <arpa/inet.h>
//#import <ShadowSocks/shadowsocks.h>
@import CocoaAsyncSocket;
//@import CocoaLumberjack;
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 {
// DDLogInfo(@"Starting remote UDP forwarding check.");
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)) {
// DDLogError(@"Failed to convert DNS resolver IP.");
[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) {
// DDLogError(@"Failed to create 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;
}
// DDLogDebug(@"Checking remote server's UDP forwarding (%d of %d).",
// weakSelf.udpForwardingNumChecks, kUdpForwardingMaxChecks);
[weakSelf.udpSocket sendData:packetData
toHost:[[NSString alloc] initWithUTF8String:kShadowsocksLocalAddress]
port:self.shadowsocksPort
withTimeout:kUdpSocketTimeoutSecs
tag:0];
if (![weakSelf.udpSocket receiveOnce:nil]) {
// DDLogError(@"UDP socket failed to receive data");
}
});
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 {
// DDLogError(@"Failed to send data on UDP socket");
}
- (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 {
// DDLogInfo(@"Remote UDP forwarding %@", enabled ? @"enabled" : @"disabled");
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 {
// DDLogInfo(@"Starting server creds. validation");
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) {
// DDLogError(@"Unable to connect to local Shadowsocks server.");
[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 {
// DDLogInfo(@"Server creds. %@.", self.areServerCredentialsValid ? @"succeeded" : @"failed");
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 {
// DDLogInfo(@"Starting server reachability check.");
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) {
// DDLogError(@"Unable to connect to Shadowsocks server.");
return;
}
}
// Calls |reachabilityCompletion| once with |isServerReachable|.
- (void)reachabilityCheckDone {
// DDLogInfo(@"Server %@.", self.isServerReachable ? @"reachable" : @"unreachable");
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

@ -0,0 +1,292 @@
import Foundation
import ShadowSocks
import NetworkExtension
import Darwin
enum ErrorCode: Int {
case noError = 0
case undefinedError
case vpnPermissionNotGranted
case invalidServerCredentials
case udpRelayNotEnabled
case serverUnreachable
case vpnStartFailure
case illegalServerConfiguration
case shadowsocksStartFailure
case configureSystemProxyFailure
case noAdminPermissions
case unsupportedRoutingTable
case systemMisconfigured
}
protocol ShadowSocksTunnel {
var ssLocalThreadId: pthread_t? { get }
func start(usingPacketFlow packetFlow: ShadowSocksAdapterPacketFlow,
withConnectivityCheck connectivityCheck: Bool,
completion: @escaping (ErrorCode?) -> Void)
func stop(completion: @escaping (ErrorCode?) -> Void)
func isUp() -> Bool
}
final class SSProvider: NSObject, ShadowSocksTunnel {
private var startCompletion: ((ErrorCode?) -> Void)? = nil
private var stopCompletion: ((ErrorCode?) -> Void)? = nil
private var dispatchQueue: DispatchQueue
private var processQueue: DispatchQueue
private var dispatchGroup: DispatchGroup
private var connectivityCheck: Bool = false
private var ssConnectivity: ShadowsocksConnectivity? = nil
private var config: Data
private var ssPacketFlowBridge: ShadowSocksAdapterFlowBridge? = nil
var ssLocalThreadId: pthread_t? = nil
init(config: Data, localPort: Int = 8585) {
self.config = config
self.dispatchQueue = DispatchQueue(label: "org.amnezia.shadowsocks")
self.processQueue = DispatchQueue(label: "org.amnezia.packet-processor")
self.dispatchGroup = DispatchGroup()
self.ssPacketFlowBridge = .init()
self.ssConnectivity = ShadowsocksConnectivity.init(port: NSNumber(value: localPort).uint16Value)
super.init()
}
func isUp() -> Bool {
return ssLocalThreadId != nil
}
func start(usingPacketFlow packetFlow: ShadowSocksAdapterPacketFlow, withConnectivityCheck connectivityCheck: Bool = false, completion: @escaping (ErrorCode?) -> Void) {
guard ssLocalThreadId == nil else {
wg_log(.error, message: "SS detached thread has already been started with id \(ssLocalThreadId!)")
completion(.shadowsocksStartFailure)
return
}
wg_log(.info, message: "Starting SSProvider...")
self.connectivityCheck = connectivityCheck
wg_log(.info, message: "ssPacketFlowBridge is \(ssPacketFlowBridge != nil ? "not null" : "null")")
self.ssPacketFlowBridge?.ssPacketFlow = packetFlow
wg_log(.info, message: "ssPacketFlow is \(ssPacketFlowBridge?.ssPacketFlow != nil ? "not null" : "null")")
self.startCompletion = completion
dispatchQueue.async {
wg_log(.info, message: "Starting ss thread...")
self.startShadowSocksThread()
}
}
func stop(completion: @escaping (ErrorCode?) -> Void) {
guard ssLocalThreadId != nil else { return }
self.stopCompletion = completion
dispatchQueue.async {
pthread_kill(self.ssLocalThreadId!, SIGUSR1)
self.ssPacketFlowBridge?.invalidateSocketsIfNeeded()
self.ssPacketFlowBridge = nil
self.ssLocalThreadId = nil
}
}
private func onShadowsocksCallback(socks_fd: Int32, udp_fd: Int32, obs: UnsafeMutableRawPointer) {
NSLog("Inside onShadowsocksCallback() with port \(socks_fd)")
wg_log(.debug, message: "Inside onShadowsocksCallback() with port \(socks_fd)")
var error: NSError? = nil
if (socks_fd <= 0 && udp_fd <= 0) {
error = NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", code: 100, userInfo: [NSLocalizedDescriptionKey : "Failed to start shadowsocks proxy"])
NSLog("onShadowsocksCallback with error \(error!.localizedDescription)")
wg_log(.debug, message: "onShadowsocksCallback failed with error \(error!.localizedDescription)")
// return
}
let ss = Unmanaged<SSProvider>.fromOpaque(obs).takeUnretainedValue()
ss.establishTunnel()
ss.checkServerConnectivity()
}
private func startShadowSocksThread() {
var attr: pthread_attr_t = .init()
var err = pthread_attr_init(&attr)
wg_log(.debug, message: "pthread_attr_init returned \(err)")
if (err != 0) {
NSLog("pthread_attr_init failed with error \(err)")
wg_log(.debug, message: "pthread_attr_init failed with error \(err)")
startCompletion?(.shadowsocksStartFailure)
return
}
err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)
wg_log(.debug, message: "pthread_attr_setdetachstate returned \(err)")
if (err != 0) {
wg_log(.debug, message: "pthread_attr_setdetachstate failed with error \(err)")
startCompletion?(.shadowsocksStartFailure)
return
}
let observer = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
err = pthread_create(&ssLocalThreadId, &attr, { obs in
let ss = Unmanaged<SSProvider>.fromOpaque(obs).takeUnretainedValue()
ss.startShadowSocks()
return nil
}, observer)
wg_log(.debug, message: "pthread_create returned \(err)")
if (err != 0) {
NSLog("pthread_create failed with error \(err)")
wg_log(.debug, message: "pthread_create failed with error \(err)")
startCompletion?(.shadowsocksStartFailure)
return
}
err = pthread_attr_destroy(&attr)
wg_log(.debug, message: "pthread_attr_destroy returned \(err)")
if (err != 0) {
NSLog("pthread_create failed with error \(err)")
wg_log(.debug, message: "pthread_attr_destroy failed with error \(err)")
startCompletion?(.shadowsocksStartFailure)
return
}
}
private func startShadowSocks() {
wg_log(.debug, message: "startShadowSocks with config \(config)")
let str = String(decoding: config, as: UTF8.self)
wg_log(.info, message: "startShadowSocks -> config: \(str)")
guard let ssConfig = try? JSONSerialization.jsonObject(with: config, options: []) as? [String: Any] else {
startCompletion?(.configureSystemProxyFailure)
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 {
startCompletion?(.configureSystemProxyFailure)
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
NSLog("Prepare to start shadowsocks proxy server...")
wg_log(.debug, message: "Prepare to start shadowsocks proxy server...")
let observer = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
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)")
NSLog("Inside cb callback: Params: socks_fd -> \(socks_fd), udp_fd -> \(udp_fd)")
if let obs = data {
NSLog("Prepare to call onShadowsocksCallback() with socks port \(socks_fd) and udp port \(udp_fd)")
wg_log(.debug, message: "Prepare to call onShadowsocksCallback() with socks port \(socks_fd) and udp port \(udp_fd)")
let mySelf = Unmanaged<SSProvider>.fromOpaque(obs).takeUnretainedValue()
mySelf.onShadowsocksCallback(socks_fd: socks_fd, udp_fd: udp_fd, obs: obs)
}
}, observer)
if success < 0 {
NSLog("Failed to start ss proxy")
wg_log(.error, message: "Failed to start ss proxy")
startCompletion?(.shadowsocksStartFailure)
return
} else {
NSLog("ss proxy started on port \(localPort)")
wg_log(.error, message: "ss proxy started on port \(localPort)")
stopCompletion?(.noError)
stopCompletion = nil
}
}
private func establishTunnel() {
wg_log(.error, message: "Establishing tunnel")
do {
try ssPacketFlowBridge?.configureSocket()
processQueue.async {
wg_log(.error, message: "Start processing packets")
self.ssPacketFlowBridge?.processPackets()
}
} catch (let err) {
wg_log(.error, message: "ss failed creating sockets \(err.localizedDescription)")
ssPacketFlowBridge?.invalidateSocketsIfNeeded()
}
}
private func checkServerConnectivity() {
guard connectivityCheck else {
startCompletion?(.noError)
return
}
let str = String(decoding: config, as: UTF8.self)
wg_log(.info, message: "checkServerConnectivity -> config: \(str)")
guard let ssConfig = try? JSONSerialization.jsonObject(with: config, options: []) as? [String: Any],
let remoteHost = ssConfig["server"] as? String,
let remotePort = ssConfig["server_port"] as? Int32 else {
startCompletion?(.configureSystemProxyFailure)
return
}
var isRemoteUdpForwardingEnabled = false
var serverCredentialsAreValid = false
var isServerReachable = false
dispatchGroup.enter()
ssConnectivity?.isUdpForwardingEnabled { status in
isRemoteUdpForwardingEnabled = status
self.dispatchGroup.leave()
}
dispatchGroup.enter()
ssConnectivity?.isReachable(remoteHost, port: NSNumber(value: remotePort).uint16Value) { status in
isServerReachable = status
self.dispatchGroup.leave()
}
dispatchGroup.enter()
ssConnectivity?.checkServerCredentials{ status in
serverCredentialsAreValid = status
self.dispatchGroup.leave()
}
dispatchGroup.notify(queue: dispatchQueue) {
if isRemoteUdpForwardingEnabled {
self.startCompletion?(.noError)
} else if serverCredentialsAreValid {
self.startCompletion?(.udpRelayNotEnabled)
} else if isServerReachable {
self.startCompletion?(.invalidServerCredentials)
} else {
self.startCompletion?(.serverUnreachable)
}
}
}
}

View file

@ -0,0 +1,21 @@
import Foundation
import NetworkExtension
import Tun2socks
class AmneziaTun2SocksWriter: NSObject, Tun2socksTunWriterProtocol {
var tunnelFlow: NEPacketTunnelFlow
init( withPacketFlow nepflow: NEPacketTunnelFlow) {
self.tunnelFlow = nepflow
super.init()
}
func write(_ p0: Data?, n: UnsafeMutablePointer<Int>?) throws {
if let packets = p0 {
tunnelFlow.writePackets([packets], withProtocols: [NSNumber(value: PF_INET)])
}
}
func close() throws {}
}

View file

@ -0,0 +1,192 @@
import Foundation
import Darwin
enum TunnelError: Error, Equatable {
case noError
case notFound(String)
case undefinedError(String)
case invalidServerCredentials(String)
case serverUnreachable(String)
case tunnelStartFailure(String)
case illegalServerConfiguration(String)
case systemMisconfigured(String)
var desc: String {
switch self {
case .noError: return "OK. No errors."
case .notFound(let msg): return msg
case .undefinedError(let msg): return msg
case .invalidServerCredentials(let msg): return msg
case .serverUnreachable(let msg): return msg
case .tunnelStartFailure(let msg): return msg
case .illegalServerConfiguration(let msg): return msg
case .systemMisconfigured(let msg): return msg
}
}
}
enum LeafProviderError: Int32 {
case ok = 0
case invalidConfigPath
case invalidConfig
case ioError
case configFileWatcherError
case asyncChannelSendError
case asyncChannelRecvError
case runtimeManagerError
case noConfigFound
static func toValue(from errorCode: Int32) -> LeafProviderError {
switch errorCode {
case ERR_OK: return ok
case ERR_CONFIG_PATH: return invalidConfigPath
case ERR_CONFIG: return invalidConfig
case ERR_IO: return ioError
case ERR_WATCHER: return configFileWatcherError
case ERR_ASYNC_CHANNEL_SEND: return asyncChannelSendError
case ERR_SYNC_CHANNEL_RECV: return asyncChannelRecvError
case ERR_RUNTIME_MANAGER: return runtimeManagerError
case ERR_NO_CONFIG_FILE: return noConfigFound
default: return ok
}
}
var desc: String {
switch self {
case .ok: return "Ok. No Errors."
case .invalidConfigPath: return "Config file path is invalid."
case .invalidConfig: return "Config parsing error."
case .ioError: return "IO error."
case .configFileWatcherError: return "Config file watcher error."
case .asyncChannelSendError: return "Async channel send error."
case .asyncChannelRecvError: return "Sync channel receive error."
case .runtimeManagerError: return "Runtime manager error."
case .noConfigFound: return "No associated config file."
}
}
}
class TunProvider: NSObject {
private var configPath: UnsafePointer<CChar>
private var tunId: UInt16
private var tunThreadId: pthread_t? = nil
private var dispatchQueue: DispatchQueue
private var startCompletion: ((TunnelError?) -> Void)? = nil
private var stopCompletion: ((TunnelError?) -> Void)? = nil
init(withConfig configPath: UnsafePointer<CChar>) {
self.configPath = configPath
self.tunId = NSNumber(value: arc4random_uniform(UInt32.max)).uint16Value
self.dispatchQueue = DispatchQueue(label: "org.amnezia.ss-tun-openvpn")
super.init()
}
func startTunnel(completion: @escaping (TunnelError?) -> Void) {
self.startCompletion = completion
guard tunThreadId == nil else {
let errMsg = "Leaf tunnel detached thread has already been started with id \(tunThreadId!)"
wg_log(.error, message: errMsg)
startCompletion?(.tunnelStartFailure(errMsg))
return
}
wg_log(.info, message: "Starting tunnel thread...")
dispatchQueue.async {
self.startTunnelThread()
}
startCompletion?(.noError)
}
private func startLeafTunnel() {
let err = leaf_run(tunId, configPath)
if (err != ERR_OK) {
let errMsg = LeafProviderError.toValue(from: err).desc
startCompletion?(.tunnelStartFailure(errMsg))
}
}
private func startTunnelThread() {
var attr: pthread_attr_t = .init()
var err = pthread_attr_init(&attr)
wg_log(.debug, message: "pthread_attr_init returned \(err)")
if (err != 0) {
let errMsg = "pthread_attr_init failed with error \(err)"
wg_log(.debug, message: errMsg)
startCompletion?(.tunnelStartFailure(errMsg))
return
}
err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)
wg_log(.debug, message: "pthread_attr_setdetachstate returned \(err)")
if (err != 0) {
let errMsg = "pthread_attr_setdetachstate failed with error \(err)"
wg_log(.debug, message: errMsg)
startCompletion?(.tunnelStartFailure(errMsg))
return
}
let observer = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
err = pthread_create(&tunThreadId, &attr, { obs in
let provider = Unmanaged<TunProvider>.fromOpaque(obs).takeUnretainedValue()
provider.startLeafTunnel()
return nil
}, observer)
wg_log(.debug, message: "pthread_create returned \(err)")
if (err != 0) {
let errMsg = "pthread_create failed with error \(err)"
wg_log(.debug, message: errMsg)
startCompletion?(.tunnelStartFailure(errMsg))
return
}
err = pthread_attr_destroy(&attr)
wg_log(.debug, message: "pthread_attr_destroy returned \(err)")
if (err != 0) {
let errMsg = "pthread_create failed with error \(err)"
wg_log(.debug, message: errMsg)
startCompletion?(.tunnelStartFailure(errMsg))
return
}
}
func reloadTunnel(withId tunId: UInt16) {
let err = leaf_reload(tunId)
if (err != ERR_OK) {
let errMsg = LeafProviderError.toValue(from: err).desc
self.startCompletion?(.systemMisconfigured(errMsg))
}
}
func stopTunnel(completion: @escaping (TunnelError?) -> Void) {
self.stopCompletion = completion
guard tunThreadId != nil else {
let errMsg = "Leaf tunnel is not initialized properly or not started/existed, tunnelId is missed"
wg_log(.error, message: errMsg)
stopCompletion?(.notFound(errMsg))
return
}
dispatchQueue.async {
let success = leaf_shutdown(self.tunId)
if !success {
let errMsg = "Tunnel canot be stopped for some odd reason."
self.stopCompletion?(.undefinedError(errMsg))
}
pthread_kill(self.tunThreadId!, SIGUSR1)
self.stopCompletion?(.noError)
self.tunThreadId = nil
self.stopCompletion = nil
}
}
func testConfig(onPath path: UnsafePointer<CChar>, completion: @escaping (TunnelError?) -> Void) {
self.startCompletion = completion
let err = leaf_test_config(configPath)
if (err != ERR_OK) {
let errMsg = LeafProviderError.toValue(from: err).desc
startCompletion?(.illegalServerConfiguration(errMsg))
return
}
startCompletion?(.noError)
}
}