|
|
@@ -37,6 +37,10 @@
|
|
|
@end
|
|
|
|
|
|
@implementation PsiphonTunnel {
|
|
|
+ dispatch_queue_t workQueue;
|
|
|
+ dispatch_queue_t callbackQueue;
|
|
|
+ dispatch_semaphore_t noticeHandlingSemaphore;
|
|
|
+
|
|
|
_Atomic PsiphonConnectionState connectionState;
|
|
|
|
|
|
_Atomic NSInteger localSocksProxyPort;
|
|
|
@@ -49,11 +53,17 @@
|
|
|
}
|
|
|
|
|
|
- (id)init {
|
|
|
- atomic_init(&connectionState, PsiphonConnectionStateDisconnected);
|
|
|
- atomic_init(&localSocksProxyPort, 0);
|
|
|
- atomic_init(&localHttpProxyPort, 0);
|
|
|
- reachability = [Reachability reachabilityForInternetConnection];
|
|
|
- tunnelWholeDevice = FALSE;
|
|
|
+ self.tunneledAppDelegate = nil;
|
|
|
+
|
|
|
+ self->workQueue = dispatch_queue_create("com.psiphon3.library.WorkQueue", DISPATCH_QUEUE_SERIAL);
|
|
|
+ self->callbackQueue = dispatch_queue_create("com.psiphon3.library.CallbackQueue", DISPATCH_QUEUE_SERIAL);
|
|
|
+ self->noticeHandlingSemaphore = dispatch_semaphore_create(1);
|
|
|
+
|
|
|
+ atomic_init(&self->connectionState, PsiphonConnectionStateDisconnected);
|
|
|
+ atomic_init(&self->localSocksProxyPort, 0);
|
|
|
+ atomic_init(&self->localHttpProxyPort, 0);
|
|
|
+ self->reachability = [Reachability reachabilityForInternetConnection];
|
|
|
+ self->tunnelWholeDevice = FALSE;
|
|
|
|
|
|
return self;
|
|
|
}
|
|
|
@@ -88,6 +98,9 @@
|
|
|
return [self start];
|
|
|
}
|
|
|
|
|
|
+/*!
|
|
|
+ Start the tunnel. If the tunnel is already started it will be stopped first.
|
|
|
+ */
|
|
|
- (BOOL)start {
|
|
|
@synchronized (PsiphonTunnel.self) {
|
|
|
[self stop];
|
|
|
@@ -102,7 +115,11 @@
|
|
|
return FALSE;
|
|
|
}
|
|
|
|
|
|
- NSString *embeddedServerEntries = [self.tunneledAppDelegate getEmbeddedServerEntries];
|
|
|
+ __block NSString *embeddedServerEntries = nil;
|
|
|
+ dispatch_sync(self->callbackQueue, ^{
|
|
|
+ embeddedServerEntries = [self.tunneledAppDelegate getEmbeddedServerEntries];
|
|
|
+ });
|
|
|
+
|
|
|
if (embeddedServerEntries == nil) {
|
|
|
[self logMessage:@"Error getting embedded server entries from delegate"];
|
|
|
return FALSE;
|
|
|
@@ -117,7 +134,7 @@
|
|
|
configStr,
|
|
|
embeddedServerEntries,
|
|
|
self,
|
|
|
- tunnelWholeDevice, // useDeviceBinder
|
|
|
+ self->tunnelWholeDevice, // useDeviceBinder
|
|
|
useIPv6Synthesizer,
|
|
|
&e);
|
|
|
|
|
|
@@ -143,6 +160,9 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/*!
|
|
|
+ Start the tunnel if it's not already started.
|
|
|
+ */
|
|
|
- (BOOL)startIfNeeded {
|
|
|
PsiphonConnectionState connState = [self getConnectionState];
|
|
|
BOOL localProxyAlive = [self isLocalProxyAlive];
|
|
|
@@ -157,9 +177,7 @@
|
|
|
|
|
|
// Otherwise we're already connected, so let the app know via the same signaling
|
|
|
// that we'd use if we were doing a connection sequence.
|
|
|
- dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
- [self changeConnectionStateTo:connState evenIfSameState:YES];
|
|
|
- });
|
|
|
+ [self changeConnectionStateTo:connState evenIfSameState:YES];
|
|
|
|
|
|
return TRUE;
|
|
|
}
|
|
|
@@ -175,8 +193,8 @@
|
|
|
|
|
|
[self logMessage: @"Psiphon library stopped"];
|
|
|
|
|
|
- atomic_store(&localSocksProxyPort, 0);
|
|
|
- atomic_store(&localHttpProxyPort, 0);
|
|
|
+ atomic_store(&self->localSocksProxyPort, 0);
|
|
|
+ atomic_store(&self->localHttpProxyPort, 0);
|
|
|
|
|
|
[self changeConnectionStateTo:PsiphonConnectionStateDisconnected evenIfSameState:NO];
|
|
|
}
|
|
|
@@ -184,17 +202,17 @@
|
|
|
|
|
|
// See comment in header.
|
|
|
- (PsiphonConnectionState)getConnectionState {
|
|
|
- return atomic_load(&connectionState);
|
|
|
+ return atomic_load(&self->connectionState);
|
|
|
}
|
|
|
|
|
|
// See comment in header.
|
|
|
- (NSInteger)getLocalSocksProxyPort {
|
|
|
- return atomic_load(&localSocksProxyPort);
|
|
|
+ return atomic_load(&self->localSocksProxyPort);
|
|
|
}
|
|
|
|
|
|
// See comment in header.
|
|
|
- (NSInteger)getLocalHttpProxyPort {
|
|
|
- return atomic_load(&localHttpProxyPort);
|
|
|
+ return atomic_load(&self->localHttpProxyPort);
|
|
|
}
|
|
|
|
|
|
// See comment in header.
|
|
|
@@ -217,11 +235,14 @@
|
|
|
publicKey:(NSString * _Nonnull)b64EncodedPublicKey
|
|
|
uploadServer:(NSString * _Nonnull)uploadServer
|
|
|
uploadServerHeaders:(NSString * _Nonnull)uploadServerHeaders {
|
|
|
- NSString *connectionConfigJson = [self getConfig];
|
|
|
- if (connectionConfigJson == nil) {
|
|
|
- [self logMessage:@"Error getting config for feedback upload"];
|
|
|
- }
|
|
|
- GoPsiSendFeedback(connectionConfigJson, feedbackJson, b64EncodedPublicKey, uploadServer, @"", uploadServerHeaders);
|
|
|
+ dispatch_async(self->workQueue, ^{
|
|
|
+ NSString *connectionConfigJson = [self getConfig];
|
|
|
+ if (connectionConfigJson == nil) {
|
|
|
+ [self logMessage:@"Error getting config for feedback upload"];
|
|
|
+ }
|
|
|
+
|
|
|
+ GoPsiSendFeedback(connectionConfigJson, feedbackJson, b64EncodedPublicKey, uploadServer, @"", uploadServerHeaders);
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -237,8 +258,11 @@
|
|
|
[self logMessage:@"tunneledApp delegate lost"];
|
|
|
return nil;
|
|
|
}
|
|
|
-
|
|
|
- NSString *configStr = [self.tunneledAppDelegate getPsiphonConfig];
|
|
|
+
|
|
|
+ __block NSString *configStr = nil;
|
|
|
+ dispatch_sync(self->callbackQueue, ^{
|
|
|
+ configStr = [self.tunneledAppDelegate getPsiphonConfig];
|
|
|
+ });
|
|
|
if (configStr == nil) {
|
|
|
[self logMessage:@"Error getting config from delegate"];
|
|
|
return nil;
|
|
|
@@ -384,7 +408,7 @@
|
|
|
//
|
|
|
|
|
|
// We'll record our state about what mode we're in.
|
|
|
- tunnelWholeDevice = ([config[@"TunnelWholeDevice"] integerValue] == 1);
|
|
|
+ self->tunnelWholeDevice = ([config[@"TunnelWholeDevice"] integerValue] == 1);
|
|
|
|
|
|
// Other optional fields not being altered. If not set, their defaults will be used:
|
|
|
// * TunnelWholeDevice
|
|
|
@@ -509,7 +533,9 @@
|
|
|
}
|
|
|
else if ([noticeType isEqualToString:@"Exiting"]) {
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onExiting)]) {
|
|
|
- [self.tunneledAppDelegate onExiting];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onExiting];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
else if ([noticeType isEqualToString:@"AvailableEgressRegions"]) {
|
|
|
@@ -520,7 +546,9 @@
|
|
|
}
|
|
|
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onAvailableEgressRegions:)]) {
|
|
|
- [self.tunneledAppDelegate onAvailableEgressRegions:regions];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onAvailableEgressRegions:regions];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
else if ([noticeType isEqualToString:@"SocksProxyPortInUse"]) {
|
|
|
@@ -531,7 +559,9 @@
|
|
|
}
|
|
|
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onSocksProxyPortInUse:)]) {
|
|
|
- [self.tunneledAppDelegate onSocksProxyPortInUse:[port integerValue]];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onSocksProxyPortInUse:[port integerValue]];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
else if ([noticeType isEqualToString:@"HttpProxyPortInUse"]) {
|
|
|
@@ -542,7 +572,9 @@
|
|
|
}
|
|
|
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onHttpProxyPortInUse:)]) {
|
|
|
- [self.tunneledAppDelegate onHttpProxyPortInUse:[port integerValue]];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onHttpProxyPortInUse:[port integerValue]];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
else if ([noticeType isEqualToString:@"ListeningSocksProxyPort"]) {
|
|
|
@@ -554,10 +586,12 @@
|
|
|
|
|
|
NSInteger portInt = [port integerValue];
|
|
|
|
|
|
- atomic_store(&localSocksProxyPort, portInt);
|
|
|
+ atomic_store(&self->localSocksProxyPort, portInt);
|
|
|
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onListeningSocksProxyPort:)]) {
|
|
|
- [self.tunneledAppDelegate onListeningSocksProxyPort:portInt];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onListeningSocksProxyPort:portInt];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
else if ([noticeType isEqualToString:@"ListeningHttpProxyPort"]) {
|
|
|
@@ -569,10 +603,12 @@
|
|
|
|
|
|
NSInteger portInt = [port integerValue];
|
|
|
|
|
|
- atomic_store(&localHttpProxyPort, portInt);
|
|
|
+ atomic_store(&self->localHttpProxyPort, portInt);
|
|
|
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onListeningHttpProxyPort:)]) {
|
|
|
- [self.tunneledAppDelegate onListeningHttpProxyPort:portInt];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onListeningHttpProxyPort:portInt];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
else if ([noticeType isEqualToString:@"UpstreamProxyError"]) {
|
|
|
@@ -583,7 +619,9 @@
|
|
|
}
|
|
|
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onUpstreamProxyError:)]) {
|
|
|
- [self.tunneledAppDelegate onUpstreamProxyError:message];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onUpstreamProxyError:message];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
else if ([noticeType isEqualToString:@"ClientUpgradeDownloaded"]) {
|
|
|
@@ -600,7 +638,9 @@
|
|
|
}
|
|
|
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onHomepage:)]) {
|
|
|
- [self.tunneledAppDelegate onHomepage:url];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onHomepage:url];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
else if ([noticeType isEqualToString:@"ClientRegion"]) {
|
|
|
@@ -611,7 +651,9 @@
|
|
|
}
|
|
|
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onClientRegion:)]) {
|
|
|
- [self.tunneledAppDelegate onClientRegion:region];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onClientRegion:region];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
else if ([noticeType isEqualToString:@"SplitTunnelRegion"]) {
|
|
|
@@ -622,7 +664,9 @@
|
|
|
}
|
|
|
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onSplitTunnelRegion:)]) {
|
|
|
- [self.tunneledAppDelegate onSplitTunnelRegion:region];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onSplitTunnelRegion:region];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
else if ([noticeType isEqualToString:@"Untunneled"]) {
|
|
|
@@ -633,7 +677,9 @@
|
|
|
}
|
|
|
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onUntunneledAddress:)]) {
|
|
|
- [self.tunneledAppDelegate onUntunneledAddress:address];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onUntunneledAddress:address];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
else if ([noticeType isEqualToString:@"BytesTransferred"]) {
|
|
|
@@ -647,7 +693,9 @@
|
|
|
}
|
|
|
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onBytesTransferred::)]) {
|
|
|
- [self.tunneledAppDelegate onBytesTransferred:[sent longLongValue]:[received longLongValue]];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onBytesTransferred:[sent longLongValue]:[received longLongValue]];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -669,7 +717,7 @@
|
|
|
#pragma mark - GoPsiPsiphonProvider protocol implementation (private)
|
|
|
|
|
|
- (BOOL)bindToDevice:(long)fileDescriptor error:(NSError **)error {
|
|
|
- if (!tunnelWholeDevice) {
|
|
|
+ if (!self->tunnelWholeDevice) {
|
|
|
return FALSE;
|
|
|
}
|
|
|
|
|
|
@@ -758,7 +806,7 @@
|
|
|
}
|
|
|
|
|
|
- (long)hasNetworkConnectivity {
|
|
|
- BOOL hasConnectivity = [reachability currentReachabilityStatus] != NotReachable;
|
|
|
+ BOOL hasConnectivity = [self->reachability currentReachabilityStatus] != NotReachable;
|
|
|
|
|
|
if (!hasConnectivity) {
|
|
|
// changeConnectionStateTo self-throttles, so even if called multiple
|
|
|
@@ -781,7 +829,15 @@
|
|
|
}
|
|
|
|
|
|
- (void)notice:(NSString *)noticeJSON {
|
|
|
- [self handlePsiphonNotice:noticeJSON];
|
|
|
+ // To prevent out-of-control memory usage, we want to limit the number of notices
|
|
|
+ // we asynchronously queue. Note that this means we'll start blocking Go threads
|
|
|
+ // after the first notice, but that's still preferable to a memory explosion.
|
|
|
+ dispatch_semaphore_wait(self->noticeHandlingSemaphore, DISPATCH_TIME_FOREVER);
|
|
|
+
|
|
|
+ dispatch_async(self->workQueue, ^{
|
|
|
+ [self handlePsiphonNotice:noticeJSON];
|
|
|
+ dispatch_semaphore_signal(self->noticeHandlingSemaphore);
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -789,18 +845,22 @@
|
|
|
|
|
|
- (void)logMessage:(NSString *)message {
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onDiagnosticMessage:)]) {
|
|
|
- [self.tunneledAppDelegate onDiagnosticMessage:message];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onDiagnosticMessage:message];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)changeConnectionStateTo:(PsiphonConnectionState)newState evenIfSameState:(BOOL)forceNotification {
|
|
|
// Store the new state and get the old state.
|
|
|
- PsiphonConnectionState oldState = atomic_exchange(&connectionState, newState);
|
|
|
+ PsiphonConnectionState oldState = atomic_exchange(&self->connectionState, newState);
|
|
|
|
|
|
// If the state has changed, inform the app.
|
|
|
if (forceNotification || oldState != newState) {
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onConnectionStateChangedFrom:to:)]) {
|
|
|
- [self.tunneledAppDelegate onConnectionStateChangedFrom:oldState to:newState];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onConnectionStateChangedFrom:oldState to:newState];
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
if (newState == PsiphonConnectionStateDisconnected) {
|
|
|
@@ -808,15 +868,21 @@
|
|
|
}
|
|
|
else if (newState == PsiphonConnectionStateConnecting &&
|
|
|
[self.tunneledAppDelegate respondsToSelector:@selector(onConnecting)]) {
|
|
|
- [self.tunneledAppDelegate onConnecting];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onConnecting];
|
|
|
+ });
|
|
|
}
|
|
|
else if (newState == PsiphonConnectionStateConnected &&
|
|
|
[self.tunneledAppDelegate respondsToSelector:@selector(onConnected)]) {
|
|
|
- [self.tunneledAppDelegate onConnected];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onConnected];
|
|
|
+ });
|
|
|
}
|
|
|
else if (newState == PsiphonConnectionStateWaitingForNetwork &&
|
|
|
[self.tunneledAppDelegate respondsToSelector:@selector(onStartedWaitingForNetworkConnectivity)]) {
|
|
|
- [self.tunneledAppDelegate onStartedWaitingForNetworkConnectivity];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onStartedWaitingForNetworkConnectivity];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -868,14 +934,14 @@
|
|
|
// time for the tunnel to notice the network is gone (depending on attempts to
|
|
|
// use the tunnel).
|
|
|
- (void)startInternetReachabilityMonitoring {
|
|
|
- previousNetworkStatus = [reachability currentReachabilityStatus];
|
|
|
+ self->previousNetworkStatus = [self->reachability currentReachabilityStatus];
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(internetReachabilityChanged:) name:kReachabilityChangedNotification object:nil];
|
|
|
- [reachability startNotifier];
|
|
|
+ [self->reachability startNotifier];
|
|
|
}
|
|
|
|
|
|
- (void)stopInternetReachabilityMonitoring {
|
|
|
- [reachability stopNotifier];
|
|
|
+ [self->reachability stopNotifier];
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
|
|
|
}
|
|
|
|
|
|
@@ -891,14 +957,16 @@
|
|
|
PsiphonConnectionState currentConnectionState = [self getConnectionState];
|
|
|
|
|
|
if (currentConnectionState == PsiphonConnectionStateConnected &&
|
|
|
- previousNetworkStatus != NotReachable &&
|
|
|
- previousNetworkStatus != networkStatus) {
|
|
|
+ self->previousNetworkStatus != NotReachable &&
|
|
|
+ self->previousNetworkStatus != networkStatus) {
|
|
|
if ([self.tunneledAppDelegate respondsToSelector:@selector(onDeviceInternetConnectivityInterrupted)]) {
|
|
|
- [self.tunneledAppDelegate onDeviceInternetConnectivityInterrupted];
|
|
|
+ dispatch_async(self->callbackQueue, ^{
|
|
|
+ [self.tunneledAppDelegate onDeviceInternetConnectivityInterrupted];
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- previousNetworkStatus = networkStatus;
|
|
|
+ self->previousNetworkStatus = networkStatus;
|
|
|
}
|
|
|
|
|
|
/*!
|