Merge pull request #53 from amnezia-vpn/ios-wireguard
iOS initial support
This commit is contained in:
commit
cd70f4b1c9
1975 changed files with 395894 additions and 814 deletions
14
client/platforms/ios/QRCodeReader.cpp
Normal file
14
client/platforms/ios/QRCodeReader.cpp
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#include "QRCodeReader.h"
|
||||
|
||||
QRCodeReader::QRCodeReader()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QRect QRCodeReader::cameraSize() {
|
||||
return QRect();
|
||||
}
|
||||
|
||||
void QRCodeReader::startReading() {}
|
||||
void QRCodeReader::stopReading() {}
|
||||
void QRCodeReader::setCameraSize(QRect) {};
|
||||
28
client/platforms/ios/QRCodeReader.h
Normal file
28
client/platforms/ios/QRCodeReader.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef QRCODEREADER_H
|
||||
#define QRCODEREADER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QRect>
|
||||
|
||||
class QRCodeReader: public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QRCodeReader();
|
||||
|
||||
QRect cameraSize();
|
||||
|
||||
public slots:
|
||||
void startReading();
|
||||
void stopReading();
|
||||
void setCameraSize(QRect value);
|
||||
|
||||
signals:
|
||||
void codeReaded(QString code);
|
||||
|
||||
private:
|
||||
void* m_qrCodeReader;
|
||||
QRect m_cameraSize;
|
||||
};
|
||||
|
||||
#endif // QRCODEREADER_H
|
||||
109
client/platforms/ios/QRCodeReader.mm
Normal file
109
client/platforms/ios/QRCodeReader.mm
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#include "QRCodeReader.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@interface QRCodeReaderImpl : UIViewController
|
||||
@end
|
||||
|
||||
@interface QRCodeReaderImpl () <AVCaptureMetadataOutputObjectsDelegate>
|
||||
@property (nonatomic) QRCodeReader* qrCodeReader;
|
||||
@property (nonatomic, strong) AVCaptureSession *captureSession;
|
||||
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *videoPreviewPlayer;
|
||||
@end
|
||||
|
||||
|
||||
@implementation QRCodeReaderImpl
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
_captureSession = nil;
|
||||
}
|
||||
|
||||
- (void)setQrCodeReader: (QRCodeReader*)value {
|
||||
_qrCodeReader = value;
|
||||
}
|
||||
|
||||
- (BOOL)startReading {
|
||||
NSError *error;
|
||||
|
||||
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo];
|
||||
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice: captureDevice error: &error];
|
||||
|
||||
if(!deviceInput) {
|
||||
NSLog(@"Error %@", error.localizedDescription);
|
||||
return NO;
|
||||
}
|
||||
|
||||
_captureSession = [[AVCaptureSession alloc]init];
|
||||
[_captureSession addInput:deviceInput];
|
||||
|
||||
AVCaptureMetadataOutput *capturedMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
|
||||
[_captureSession addOutput:capturedMetadataOutput];
|
||||
|
||||
dispatch_queue_t dispatchQueue;
|
||||
dispatchQueue = dispatch_queue_create("myQueue", NULL);
|
||||
[capturedMetadataOutput setMetadataObjectsDelegate: self queue: dispatchQueue];
|
||||
[capturedMetadataOutput setMetadataObjectTypes: [NSArray arrayWithObject:AVMetadataObjectTypeQRCode]];
|
||||
|
||||
_videoPreviewPlayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession: _captureSession];
|
||||
|
||||
CGFloat tabBarHeight = 20.0;
|
||||
QRect cameraRect = _qrCodeReader->cameraSize();
|
||||
CGRect cameraCGRect = CGRectMake(cameraRect.x(),
|
||||
cameraRect.y() + tabBarHeight,
|
||||
cameraRect.width(),
|
||||
cameraRect.height());
|
||||
|
||||
[_videoPreviewPlayer setVideoGravity: AVLayerVideoGravityResizeAspect];
|
||||
[_videoPreviewPlayer setFrame: cameraCGRect];
|
||||
|
||||
CALayer* layer = [UIApplication sharedApplication].keyWindow.layer;
|
||||
[layer addSublayer: _videoPreviewPlayer];
|
||||
|
||||
[_captureSession startRunning];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)stopReading {
|
||||
[_captureSession stopRunning];
|
||||
_captureSession = nil;
|
||||
|
||||
[_videoPreviewPlayer removeFromSuperlayer];
|
||||
}
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
|
||||
|
||||
if (metadataObjects != nil && metadataObjects.count > 0) {
|
||||
AVMetadataMachineReadableCodeObject *metadataObject = [metadataObjects objectAtIndex:0];
|
||||
|
||||
if ([[metadataObject type] isEqualToString: AVMetadataObjectTypeQRCode]) {
|
||||
_qrCodeReader->emit codeReaded([metadataObject stringValue].UTF8String);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
QRCodeReader::QRCodeReader() {
|
||||
m_qrCodeReader = [[QRCodeReaderImpl alloc] init];
|
||||
[m_qrCodeReader setQrCodeReader: this];
|
||||
}
|
||||
|
||||
QRect QRCodeReader::cameraSize() {
|
||||
return m_cameraSize;
|
||||
}
|
||||
|
||||
void QRCodeReader::setCameraSize(QRect value) {
|
||||
m_cameraSize = value;
|
||||
}
|
||||
|
||||
void QRCodeReader::startReading() {
|
||||
[m_qrCodeReader startReading];
|
||||
}
|
||||
|
||||
void QRCodeReader::stopReading() {
|
||||
[m_qrCodeReader stopReading];
|
||||
}
|
||||
230
client/platforms/ios/iosinterface.swift
Normal file
230
client/platforms/ios/iosinterface.swift
Normal 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
|
||||
|
|
@ -50,4 +50,5 @@ func wg_log(_ type: OSLogType, staticMessage msg: StaticString) {
|
|||
func wg_log(_ type: OSLogType, message msg: String) {
|
||||
os_log("%{public}s", log: OSLog.default, type: type, msg)
|
||||
Logger.global?.log(message: msg)
|
||||
NSLog("AMNEZIA: \(msg)")
|
||||
}
|
||||
|
|
|
|||
21
client/platforms/ios/iosopenvpn2ssadapter.h
Normal file
21
client/platforms/ios/iosopenvpn2ssadapter.h
Normal 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 */
|
||||
139
client/platforms/ios/iosopenvpn2ssadapter.m
Normal file
139
client/platforms/ios/iosopenvpn2ssadapter.m
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#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];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
# 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
|
||||
|
|
@ -1,15 +1,45 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
import os
|
||||
import Darwin
|
||||
import OpenVPNAdapter
|
||||
//import Tun2socks
|
||||
|
||||
enum TunnelProtoType: String {
|
||||
case wireguard, openvpn, none
|
||||
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 {
|
||||
|
||||
private lazy var wgAdapter: WireGuardAdapter = {
|
||||
|
|
@ -24,6 +54,22 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
return adapter
|
||||
}()
|
||||
|
||||
private var shadowSocksConfig: Data? = nil
|
||||
private var openVPNConfig: Data? = nil
|
||||
var ssCompletion: ShadowsocksProxyCompletion = nil
|
||||
|
||||
// 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()
|
||||
|
||||
var startHandler: ((Error?) -> Void)?
|
||||
|
|
@ -31,15 +77,16 @@ 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 {
|
||||
protoType = .openvpn
|
||||
let _: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data {
|
||||
let withoutShadowSocks = providerConfiguration[Constants.ssConfigKey] as? Data == nil
|
||||
protoType = withoutShadowSocks ? .openvpn : .shadowsocks
|
||||
} else {
|
||||
protoType = .wireguard
|
||||
}
|
||||
|
|
@ -51,6 +98,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
completionHandler: completionHandler)
|
||||
case .openvpn:
|
||||
startOpenVPN(completionHandler: completionHandler)
|
||||
case .shadowsocks:
|
||||
break
|
||||
// startShadowSocks(completionHandler: completionHandler)
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
|
|
@ -62,53 +112,25 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
stopWireguard(with: reason, completionHandler: completionHandler)
|
||||
case .openvpn:
|
||||
stopOpenVPN(with: reason, completionHandler: completionHandler)
|
||||
case .shadowsocks:
|
||||
break
|
||||
// stopShadowSocks(with: reason, completionHandler: completionHandler)
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
break
|
||||
// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -168,35 +190,32 @@ 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
|
||||
}
|
||||
|
||||
let configuration = OpenVPNConfiguration()
|
||||
configuration.fileContent = ovpnConfiguration
|
||||
// configuration.settings = [] // Additional setting if needed any
|
||||
// configuration.tunPersist = true // keep tun active during pauses/reconections
|
||||
let evaluation: OpenVPNConfigurationEvaluation
|
||||
do {
|
||||
evaluation = try ovpnAdapter.apply(configuration: configuration)
|
||||
} catch {
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
|
||||
if !evaluation.autologin {
|
||||
print("Implement login with user credentials")
|
||||
}
|
||||
|
||||
vpnReachability.startTracking { [weak self] status in
|
||||
guard status == .reachableViaWiFi else { return }
|
||||
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
|
||||
}
|
||||
|
||||
startHandler = completionHandler
|
||||
ovpnAdapter.connect(using: packetFlow)
|
||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
/*
|
||||
private func startShadowSocks(completionHandler: @escaping (Error?) -> Void) {
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
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
|
||||
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) {
|
||||
wg_log(.info, staticMessage: "Stopping tunnel")
|
||||
|
||||
|
|
@ -224,6 +243,546 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
}
|
||||
ovpnAdapter.disconnect()
|
||||
}
|
||||
/*
|
||||
private func stopShadowSocks(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
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"] = "127.0.0.2"
|
||||
insettings["netmask"] = "255.255.255.0"
|
||||
insettings["gateway"] = "127.0.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: ["127.0.0.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"])
|
||||
dnsSettings.matchDomains = []
|
||||
let settings: NEPacketTunnelNetworkSettings = .init(tunnelRemoteAddress: "127.0.0.1")
|
||||
settings.ipv4Settings = ipv4settings
|
||||
settings.dnsSettings = dnsSettings
|
||||
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"])
|
||||
// let addedRoute1 = NEIPv4Route(destinationAddress: "0.0.0.0", subnetMask: "0.0.0.0")
|
||||
// addedRoute1.gatewayAddress = "192.0.2.1"
|
||||
// ipv4settings.includedRoutes = [addedRoute1]
|
||||
// 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.setupWriteToFlow()
|
||||
self.observer = self.session!.observe(\.state, options: [.new]) { conn, _ in
|
||||
switch conn.state {
|
||||
case .ready:
|
||||
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 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 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)
|
||||
} catch {
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
|
||||
if !evaluation.autologin {
|
||||
wg_log(.info, message: "Implement login with user credentials")
|
||||
}
|
||||
|
||||
vpnReachability.startTracking { [weak self] status in
|
||||
guard status == .reachableViaWiFi else { return }
|
||||
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
|
||||
}
|
||||
|
||||
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)")
|
||||
}
|
||||
|
||||
// MARK: -- Network observing methods
|
||||
|
||||
private func getSockPort(for fd: Int32) -> Int32 {
|
||||
var addr_in = sockaddr_in();
|
||||
addr_in.sin_len = UInt8(MemoryLayout.size(ofValue: addr_in));
|
||||
addr_in.sin_family = sa_family_t(AF_INET);
|
||||
|
||||
var len = socklen_t(addr_in.sin_len);
|
||||
let result = withUnsafeMutablePointer(to: &addr_in, {
|
||||
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
||||
return Darwin.getsockname(fd, $0, &len);
|
||||
}
|
||||
});
|
||||
|
||||
if result == 0 {
|
||||
return Int32(addr_in.sin_port);
|
||||
} else {
|
||||
wg_log(.error, message: "getSockPort(\(fd)) error: \(String(describing: strerror(errno)))")
|
||||
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 {
|
||||
|
|
@ -239,6 +798,8 @@ extension WireGuardLogLevel {
|
|||
|
||||
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
|
||||
|
||||
/* extension NEPacketTunnelFlow: ShadowSocksAdapterPacketFlow {} */
|
||||
|
||||
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||
|
||||
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
|
||||
|
|
@ -276,7 +837,6 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
|||
|
||||
startHandler(nil)
|
||||
self.startHandler = nil
|
||||
|
||||
case .disconnected:
|
||||
guard let stopHandler = stopHandler else { return }
|
||||
|
||||
|
|
@ -286,10 +846,8 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
|||
|
||||
stopHandler()
|
||||
self.stopHandler = nil
|
||||
|
||||
case .reconnecting:
|
||||
reasserting = true
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
@ -316,7 +874,17 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
|||
// Use this method to process any log message returned by OpenVPN library.
|
||||
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
|
||||
// Handle log messages
|
||||
wg_log(.info, message: logMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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 {}
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ public class IOSVpnProtocolImpl : NSObject {
|
|||
private var privateKey : PrivateKey? = nil
|
||||
private var deviceIpv4Address: String? = nil
|
||||
private var deviceIpv6Address: String? = nil
|
||||
private var openVPNConfig: String? = nil
|
||||
private var shadowSocksConfig: String? = nil
|
||||
|
||||
@objc enum ConnectionState: Int { case Error, Connected, Disconnected }
|
||||
|
||||
|
|
@ -45,6 +47,8 @@ public class IOSVpnProtocolImpl : NSObject {
|
|||
precondition(!vpnBundleID.isEmpty)
|
||||
|
||||
stateChangeCallback = callback
|
||||
self.openVPNConfig = config
|
||||
self.shadowSocksConfig = nil
|
||||
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
|
|
@ -117,6 +121,8 @@ public class IOSVpnProtocolImpl : NSObject {
|
|||
self.privateKey = PrivateKey(rawValue: privateKey)
|
||||
self.deviceIpv4Address = deviceIpv4Address
|
||||
self.deviceIpv6Address = deviceIpv6Address
|
||||
self.openVPNConfig = nil
|
||||
self.shadowSocksConfig = nil
|
||||
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
|
||||
|
|
@ -170,6 +176,63 @@ public class IOSVpnProtocolImpl : NSObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc init(bundleID: String,
|
||||
tunnelConfig: String,
|
||||
ssConfig: String,
|
||||
closure: @escaping (ConnectionState, Date?) -> Void,
|
||||
callback: @escaping (Bool) -> Void) {
|
||||
super.init()
|
||||
|
||||
vpnBundleID = bundleID;
|
||||
precondition(!vpnBundleID.isEmpty)
|
||||
|
||||
stateChangeCallback = callback
|
||||
self.openVPNConfig = tunnelConfig
|
||||
self.shadowSocksConfig = ssConfig
|
||||
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.vpnStatusDidChange(notification:)), name: Notification.Name.NEVPNStatusDidChange, object: nil)
|
||||
|
||||
NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in
|
||||
if let error = error {
|
||||
Logger.global?.log(message: "Loading from preference failed: \(error)")
|
||||
closure(ConnectionState.Error, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if self == nil {
|
||||
Logger.global?.log(message: "We are shutting down.")
|
||||
return
|
||||
}
|
||||
|
||||
let nsManagers = managers ?? []
|
||||
Logger.global?.log(message: "We have received \(nsManagers.count) managers.")
|
||||
print("We have received \(nsManagers.count) managers.")
|
||||
|
||||
let tunnel = nsManagers.first(where: IOSVpnProtocolImpl.isOurManager(_:))
|
||||
|
||||
if tunnel == nil {
|
||||
Logger.global?.log(message: "Creating the tunnel via shadowsocks")
|
||||
print("Creating the tunnel via SS")
|
||||
self!.tunnel = NETunnelProviderManager()
|
||||
closure(ConnectionState.Disconnected, nil)
|
||||
return
|
||||
}
|
||||
|
||||
Logger.global?.log(message: "Tunnel already exists")
|
||||
print("SS Tunnel already exists")
|
||||
|
||||
self!.tunnel = tunnel
|
||||
|
||||
if tunnel?.connection.status == .connected {
|
||||
closure(ConnectionState.Connected, tunnel?.connection.connectedDate)
|
||||
} else {
|
||||
closure(ConnectionState.Disconnected, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func vpnStatusDidChange(notification: Notification) {
|
||||
guard let session = (notification.object as? NETunnelProviderSession), tunnel?.connection == session else { return }
|
||||
|
|
@ -230,9 +293,30 @@ public class IOSVpnProtocolImpl : NSObject {
|
|||
return true
|
||||
}
|
||||
|
||||
@objc func connect(ssConfig: String,
|
||||
ovpnConfig: String,
|
||||
failureCallback: @escaping () -> Void) {
|
||||
Logger.global?.log(message: "Connecting")
|
||||
// assert(tunnel != nil)
|
||||
|
||||
self.openVPNConfig = ovpnConfig
|
||||
self.shadowSocksConfig = ssConfig
|
||||
|
||||
let addr: String = ovpnConfig
|
||||
.splitToArray(separator: "\n", trimmingCharacters: nil)
|
||||
.first { $0.starts(with: "remote ") }
|
||||
.splitToArray(separator: " ", trimmingCharacters: nil)[1]
|
||||
print("server: \(addr)")
|
||||
|
||||
// Let's remove the previous config if it exists.
|
||||
(tunnel?.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
|
||||
|
||||
self.configureTunnel(withShadowSocks: self.shadowSocksConfig, serverAddress: addr, config: self.openVPNConfig, failureCallback: failureCallback)
|
||||
}
|
||||
|
||||
@objc func connect(ovpnConfig: String, failureCallback: @escaping () -> Void) {
|
||||
Logger.global?.log(message: "Connecting")
|
||||
assert(tunnel != nil)
|
||||
// assert(tunnel != nil)
|
||||
|
||||
let addr: String = ovpnConfig
|
||||
.splitToArray(separator: "\n", trimmingCharacters: nil)
|
||||
|
|
@ -248,7 +332,7 @@ public class IOSVpnProtocolImpl : NSObject {
|
|||
|
||||
@objc func connect(dnsServer: String, serverIpv6Gateway: String, serverPublicKey: String, presharedKey: String, serverIpv4AddrIn: String, serverPort: Int, allowedIPAddressRanges: Array<VPNIPAddressRange>, ipv6Enabled: Bool, reason: Int, failureCallback: @escaping () -> Void) {
|
||||
Logger.global?.log(message: "Connecting")
|
||||
assert(tunnel != nil)
|
||||
// assert(tunnel != nil)
|
||||
|
||||
// Let's remove the previous config if it exists.
|
||||
(tunnel?.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
|
||||
|
|
@ -299,6 +383,8 @@ public class IOSVpnProtocolImpl : NSObject {
|
|||
failureCallback()
|
||||
return
|
||||
}
|
||||
|
||||
guard tunnel != nil else { failureCallback(); return }
|
||||
proto.providerBundleIdentifier = vpnBundleID
|
||||
|
||||
tunnel!.protocolConfiguration = proto
|
||||
|
|
@ -351,6 +437,47 @@ public class IOSVpnProtocolImpl : NSObject {
|
|||
}
|
||||
}
|
||||
|
||||
func configureTunnel(withShadowSocks ssConfig: String?, serverAddress: String, config: String?, failureCallback: @escaping () -> Void) {
|
||||
guard let ss = ssConfig, let ovpn = config else { failureCallback(); return }
|
||||
let tunnelProtocol = NETunnelProviderProtocol()
|
||||
tunnelProtocol.serverAddress = serverAddress
|
||||
tunnelProtocol.providerBundleIdentifier = vpnBundleID
|
||||
tunnelProtocol.providerConfiguration = ["ovpn": Data(ovpn.utf8), "ss": Data(ss.utf8)]
|
||||
tunnel?.protocolConfiguration = tunnelProtocol
|
||||
tunnel?.localizedDescription = "Amnezia ShadowSocks"
|
||||
tunnel?.isEnabled = true
|
||||
|
||||
tunnel?.saveToPreferences { [unowned self] saveError in
|
||||
if let error = saveError {
|
||||
Logger.global?.log(message: "Connect ShadowSocks Tunnel Save Error: \(error)")
|
||||
failureCallback()
|
||||
return
|
||||
}
|
||||
|
||||
Logger.global?.log(message: "Saving ShadowSocks tunnel succeeded")
|
||||
|
||||
self.tunnel?.loadFromPreferences { error in
|
||||
if let error = error {
|
||||
Logger.global?.log(message: "Connect ShadowSocks Tunnel Load Error: \(error)")
|
||||
failureCallback()
|
||||
return
|
||||
}
|
||||
|
||||
Logger.global?.log(message: "Loading the ShadowSocks tunnel succeeded")
|
||||
print("Loading the ss tunnel succeeded")
|
||||
|
||||
do {
|
||||
print("starting ss tunnel")
|
||||
try self.tunnel?.connection.startVPNTunnel()
|
||||
} catch let error {
|
||||
Logger.global?.log(message: "Something went wrong: \(error)")
|
||||
failureCallback()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func configureOpenVPNTunnel(serverAddress: String, config: String, failureCallback: @escaping () -> Void) {
|
||||
let tunnelProtocol = NETunnelProviderProtocol()
|
||||
tunnelProtocol.serverAddress = serverAddress
|
||||
|
|
@ -393,13 +520,13 @@ public class IOSVpnProtocolImpl : NSObject {
|
|||
|
||||
@objc func disconnect() {
|
||||
Logger.global?.log(message: "Disconnecting")
|
||||
assert(tunnel != nil)
|
||||
guard tunnel != nil else { return }
|
||||
(tunnel!.connection as? NETunnelProviderSession)?.stopTunnel()
|
||||
}
|
||||
|
||||
@objc func checkStatus(callback: @escaping (String, String, String) -> Void) {
|
||||
Logger.global?.log(message: "Check status")
|
||||
assert(tunnel != nil)
|
||||
// assert(tunnel != nil)
|
||||
|
||||
let protoType = (tunnel!.localizedDescription ?? "").toTunnelType
|
||||
|
||||
|
|
@ -408,12 +535,60 @@ public class IOSVpnProtocolImpl : NSObject {
|
|||
checkWireguardStatus(callback: callback)
|
||||
case .openvpn:
|
||||
checkOVPNStatus(callback: callback)
|
||||
case .shadowsocks:
|
||||
checkShadowSocksStatus(callback: callback)
|
||||
case .empty:
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func checkShadowSocksStatus(callback: @escaping (String, String, String) -> Void) {
|
||||
Logger.global?.log(message: "Check ShadowSocks")
|
||||
guard let proto = tunnel?.protocolConfiguration as? NETunnelProviderProtocol else {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
guard let ssData = proto.providerConfiguration?["ss"] as? Data,
|
||||
let ssConfig = try? JSONSerialization.jsonObject(with: ssData, options: []) as? [String: Any],
|
||||
let serverIpv4Gateway = ssConfig["remote_host"] as? String else {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
print("server IP: \(serverIpv4Gateway)")
|
||||
|
||||
let deviceIpv4Address = getTunIPAddress()
|
||||
print("device IP: \(serverIpv4Gateway)")
|
||||
if deviceIpv4Address == nil {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
guard let session = tunnel?.connection as? NETunnelProviderSession else {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try session.sendProviderMessage(Data([UInt8(0)])) { [callback] data in
|
||||
guard let data = data,
|
||||
let configString = String(data: data, encoding: .utf8)
|
||||
else {
|
||||
Logger.global?.log(message: "Failed to convert data to string")
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
callback("\(serverIpv4Gateway)", "\(deviceIpv4Address!)", configString)
|
||||
}
|
||||
} catch {
|
||||
Logger.global?.log(message: "Failed to retrieve data from session")
|
||||
callback("", "", "")
|
||||
}
|
||||
}
|
||||
|
||||
private func checkOVPNStatus(callback: @escaping (String, String, String) -> Void) {
|
||||
Logger.global?.log(message: "Check OpenVPN")
|
||||
guard let proto = tunnel?.protocolConfiguration as? NETunnelProviderProtocol else {
|
||||
|
|
@ -463,7 +638,6 @@ public class IOSVpnProtocolImpl : NSObject {
|
|||
Logger.global?.log(message: "Failed to retrieve data from session")
|
||||
callback("", "", "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func checkWireguardStatus(callback: @escaping (String, String, String) -> Void) {
|
||||
|
|
@ -549,7 +723,7 @@ public class IOSVpnProtocolImpl : NSObject {
|
|||
}
|
||||
|
||||
enum TunnelType: String {
|
||||
case wireguard, openvpn, empty
|
||||
case wireguard, openvpn, shadowsocks, empty
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
|
@ -557,6 +731,7 @@ extension String {
|
|||
switch self {
|
||||
case "wireguard": return .wireguard
|
||||
case "openvpn": return .openvpn
|
||||
case "shadowsocks": return .shadowsocks
|
||||
default:
|
||||
return .empty
|
||||
}
|
||||
|
|
|
|||
12
client/platforms/ios/ssadapterpacketflow.h
Normal file
12
client/platforms/ios/ssadapterpacketflow.h
Normal 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
|
||||
36
client/platforms/ios/ssconnectivity.h
Normal file
36
client/platforms/ios/ssconnectivity.h
Normal 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 */
|
||||
|
||||
334
client/platforms/ios/ssconnectivity.m
Normal file
334
client/platforms/ios/ssconnectivity.m
Normal 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
|
||||
|
||||
16
client/platforms/ios/sspacket.h
Normal file
16
client/platforms/ios/sspacket.h
Normal 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 */
|
||||
57
client/platforms/ios/sspacket.m
Normal file
57
client/platforms/ios/sspacket.m
Normal 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
|
||||
293
client/platforms/ios/ssprovider.swift
Normal file
293
client/platforms/ios/ssprovider.swift
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
client/platforms/ios/tun2sockswriter.swift
Normal file
21
client/platforms/ios/tun2sockswriter.swift
Normal 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 {}
|
||||
}
|
||||
|
||||
192
client/platforms/ios/tun2ssprovider.swift
Normal file
192
client/platforms/ios/tun2ssprovider.swift
Normal 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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue