amnezia-client/client/platforms/ios/Shadowsocks.m
2021-12-22 17:38:17 +04:00

202 lines
6.8 KiB
Objective-C

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