From c4699d8b739c2b22017f8d88a3143bac48e3a2fa Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Thu, 24 Mar 2016 10:50:18 -0700 Subject: [PATCH] Open source FBPortForwarding Summary:FBPortForwarding is incomplete and flaky implementation of `adb reverse` functionality for iOS. We haven't worked on it internally for a while now, but I think it might be a very usefull tool for the community. Some external contributors (hello philikon!) expressed interest in maintaining it. This diff moves it over to OSS RN `Libraries` folder. Reviewed By: vjeux Differential Revision: D3056293 fb-gh-sync-id: 9ced61fa8480a923771bba26fe09337c344ab3b3 shipit-source-id: 9ced61fa8480a923771bba26fe09337c344ab3b3 --- Tools/FBPortForwarding/Apps/MacApp/main.m | 23 ++ Tools/FBPortForwarding/Apps/iOSApp/Info.plist | 36 +++ .../Apps/iOSApp/PFAppDelegate.h | 16 + .../Apps/iOSApp/PFAppDelegate.m | 65 ++++ Tools/FBPortForwarding/Apps/iOSApp/main.m | 19 ++ .../FBPortForwarding/FBPortForwardingClient.h | 20 ++ .../FBPortForwarding/FBPortForwardingClient.m | 284 ++++++++++++++++++ .../FBPortForwarding/FBPortForwardingCommon.h | 26 ++ .../FBPortForwarding/FBPortForwardingServer.h | 20 ++ .../FBPortForwarding/FBPortForwardingServer.m | 193 ++++++++++++ .../FBPortForwardingTests/PFPingClient.h | 22 ++ .../FBPortForwardingTests/PFPingClient.m | 53 ++++ .../FBPortForwardingTests/PFPingServer.h | 20 ++ .../FBPortForwardingTests/PFPingServer.m | 55 ++++ .../PFSimpleHTTPServer.h | 18 ++ .../PFSimpleHTTPServer.m | 51 ++++ .../FBPortForwardingTests/PFTests.m | 217 +++++++++++++ Tools/FBPortForwarding/README.md | 66 ++++ 18 files changed, 1204 insertions(+) create mode 100644 Tools/FBPortForwarding/Apps/MacApp/main.m create mode 100644 Tools/FBPortForwarding/Apps/iOSApp/Info.plist create mode 100644 Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.h create mode 100644 Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.m create mode 100644 Tools/FBPortForwarding/Apps/iOSApp/main.m create mode 100644 Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.h create mode 100644 Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.m create mode 100644 Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingCommon.h create mode 100644 Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.h create mode 100644 Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.m create mode 100644 Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.h create mode 100644 Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.m create mode 100644 Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.h create mode 100644 Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.m create mode 100644 Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.h create mode 100644 Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.m create mode 100644 Tools/FBPortForwarding/FBPortForwardingTests/PFTests.m create mode 100644 Tools/FBPortForwarding/README.md diff --git a/Tools/FBPortForwarding/Apps/MacApp/main.m b/Tools/FBPortForwarding/Apps/MacApp/main.m new file mode 100644 index 0000000000..bfe623d279 --- /dev/null +++ b/Tools/FBPortForwarding/Apps/MacApp/main.m @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +int main(int argc, char *argv[]) +{ + FBPortForwardingClient *client = [FBPortForwardingClient new]; + [client forwardConnectionsToPort:8081]; + [client connectToMultiplexingChannelOnPort:8025]; + + [[NSRunLoop currentRunLoop] run]; + client = nil; + return 0; +} diff --git a/Tools/FBPortForwarding/Apps/iOSApp/Info.plist b/Tools/FBPortForwarding/Apps/iOSApp/Info.plist new file mode 100644 index 0000000000..54a71b0241 --- /dev/null +++ b/Tools/FBPortForwarding/Apps/iOSApp/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Port Forwarding + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.facebook.example.PortForwarding + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + diff --git a/Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.h b/Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.h new file mode 100644 index 0000000000..e504b34bbb --- /dev/null +++ b/Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface PFAppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.m b/Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.m new file mode 100644 index 0000000000..edeb6e90da --- /dev/null +++ b/Tools/FBPortForwarding/Apps/iOSApp/PFAppDelegate.m @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFAppDelegate.h" + +#import + +@implementation PFAppDelegate +{ + FBPortForwardingServer *_portForwardingServer; +} + +@synthesize window = window_; + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + CGRect rect = [[UIScreen mainScreen] bounds]; + + UIView *view = [[UIView alloc] initWithFrame:rect]; + view.backgroundColor = [UIColor whiteColor]; + UIViewController *controller = [UIViewController new]; + controller.view = view; + + UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + [button setTitle:@"Send request" forState:UIControlStateNormal]; + [button addTarget:self action:@selector(sendRequest) forControlEvents:UIControlEventTouchUpInside]; + button.frame = CGRectMake(0, 0, 200, 50); + button.center = view.center; + [view addSubview:button]; + + self.window = [[UIWindow alloc] initWithFrame:rect]; + self.window.rootViewController = controller; + [self.window makeKeyAndVisible]; + + _portForwardingServer = [FBPortForwardingServer new]; + [_portForwardingServer forwardConnectionsFromPort:8082]; + [_portForwardingServer listenForMultiplexingChannelOnPort:8025]; + + return YES; +} + +- (void)sendRequest +{ + NSURLRequest *req = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://localhost:8082/404"]]; + [[[NSURLConnection alloc] initWithRequest:req delegate:self] start]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSLog(@"Success: %@", content); +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + NSLog(@"Error: %@", error); +} + +@end diff --git a/Tools/FBPortForwarding/Apps/iOSApp/main.m b/Tools/FBPortForwarding/Apps/iOSApp/main.m new file mode 100644 index 0000000000..3b145a7455 --- /dev/null +++ b/Tools/FBPortForwarding/Apps/iOSApp/main.m @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "PFAppDelegate.h" + +int main(int argc, char *argv[]) +{ + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([PFAppDelegate class])); + } +} diff --git a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.h b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.h new file mode 100644 index 0000000000..9a4887829f --- /dev/null +++ b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface FBPortForwardingClient : NSObject + +- (instancetype)init; + +- (void)forwardConnectionsToPort:(NSUInteger)port; +- (void)connectToMultiplexingChannelOnPort:(NSUInteger)port; +- (void)close; + +@end diff --git a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.m b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.m new file mode 100644 index 0000000000..d9d1e97820 --- /dev/null +++ b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingClient.m @@ -0,0 +1,284 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "FBPortForwardingClient.h" + +#import + +#import +#import + +#import "FBPortForwardingCommon.h" + +static const NSTimeInterval ReconnectDelay = 1.0; + +@interface FBPortForwardingClient () +{ + NSUInteger _destPort; + NSUInteger _channelPort; + NSNumber *_connectingToDeviceID; + NSNumber *_connectedDeviceID; + NSDictionary *_connectedDeviceProperties; + BOOL _notConnectedQueueSuspended; + PTChannel *_connectedChannel; + dispatch_queue_t _notConnectedQueue; + dispatch_queue_t _clientSocketsQueue; + NSMutableDictionary *_clientSockets; +} + +@property (atomic, readonly) NSNumber *connectedDeviceID; +@property (atomic, assign) PTChannel *connectedChannel; + +@end + +@implementation FBPortForwardingClient + +@synthesize connectedDeviceID = _connectedDeviceID; + +- (instancetype)init +{ + if (self = [super init]) { + _notConnectedQueue = dispatch_queue_create("FBPortForwarding.notConnectedQueue", DISPATCH_QUEUE_SERIAL); + _clientSocketsQueue = dispatch_queue_create("FBPortForwarding.clients", DISPATCH_QUEUE_SERIAL); + _clientSockets = [NSMutableDictionary dictionary]; + } + return self; +} + +- (void)forwardConnectionsToPort:(NSUInteger)port +{ + _destPort = port; +} + +- (void)connectToMultiplexingChannelOnPort:(NSUInteger)port +{ + _channelPort = port; + [self startListeningForDevices]; + [self enqueueConnectToLocalIPv4Port]; +} + +- (void)close +{ + [self.connectedChannel close]; +} + +- (PTChannel *)connectedChannel { + return _connectedChannel; +} + +- (void)setConnectedChannel:(PTChannel *)connectedChannel { + _connectedChannel = connectedChannel; + + if (!_connectedChannel) { + for (GCDAsyncSocket *sock in [_clientSockets objectEnumerator]) { + [sock setDelegate:nil]; + [sock disconnect]; + } + [_clientSockets removeAllObjects]; + } + + // Toggle the notConnectedQueue_ depending on if we are connected or not + if (!_connectedChannel && _notConnectedQueueSuspended) { + dispatch_resume(_notConnectedQueue); + _notConnectedQueueSuspended = NO; + } else if (_connectedChannel && !_notConnectedQueueSuspended) { + dispatch_suspend(_notConnectedQueue); + _notConnectedQueueSuspended = YES; + } + + if (!_connectedChannel && _connectingToDeviceID) { + [self enqueueConnectToUSBDevice]; + } +} + + +#pragma mark - PTChannelDelegate + +- (void)ioFrameChannel:(PTChannel *)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData *)payload { + //NSLog(@"received %@, %u, %u, %@", channel, type, tag, payload); + + if (type == FBPortForwardingFrameTypeOpenPipe) { + GCDAsyncSocket *sock = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_clientSocketsQueue]; + sock.userData = @(tag); + _clientSockets[@(tag)] = sock; + + NSError *connectError; + if (![sock connectToHost:@"localhost" onPort:_destPort error:&connectError]) { + FBPFLog(@"Failed to connect to local %lu - %@", (unsigned long)_destPort, connectError); + } + + FBPFTrace(@"open socket (%d)", tag); + } + + if (type == FBPortForwardingFrameTypeWriteToPipe) { + GCDAsyncSocket *sock = _clientSockets[@(tag)]; + [sock writeData:[NSData dataWithBytes:payload.data length:payload.length] withTimeout:-1 tag:0]; + FBPFTrace(@"channel -> socket (%d) %zu bytes", tag, payload.length); + } + + if (type == FBPortForwardingFrameTypeClosePipe) { + GCDAsyncSocket *sock = _clientSockets[@(tag)]; + [sock disconnectAfterWriting]; + FBPFTrace(@"close socket (%d)", tag); + } +} + +- (void)ioFrameChannel:(PTChannel *)channel didEndWithError:(NSError *)error { + if (_connectedDeviceID && [_connectedDeviceID isEqualToNumber:channel.userInfo]) { + [self didDisconnectFromDevice:_connectedDeviceID]; + } + + if (_connectedChannel == channel) { + FBPFTrace(@"Disconnected from %@", channel.userInfo); + self.connectedChannel = nil; + } +} + + +#pragma mark - GCDAsyncSocketDelegate + + +- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port +{ + FBPFTrace(@"socket (%ld) connected to %@", (long)[sock.userData integerValue], host); + [sock readDataWithTimeout:-1 tag:0]; +} + +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err +{ + UInt32 tag = [sock.userData unsignedIntValue]; + [_clientSockets removeObjectForKey:@(tag)]; + FBPFTrace(@"socket (%d) disconnected", (unsigned int)tag); + + [_connectedChannel sendFrameOfType:FBPortForwardingFrameTypeClosePipe tag:tag withPayload:nil callback:nil]; +} + +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)_ +{ + UInt32 tag = [sock.userData unsignedIntValue]; + [_connectedChannel sendFrameOfType:FBPortForwardingFrameTypeWriteToPipe tag:tag withPayload:NSDataToGCDData(data) callback:^(NSError *error) { + FBPFTrace(@"channel -> socket (%d), %lu bytes", (unsigned int)tag, (unsigned long)data.length); + [sock readDataWithTimeout:-1 tag:0]; + }]; +} + +#pragma mark - Wired device connections + + +- (void)startListeningForDevices { + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + + [nc addObserverForName:PTUSBDeviceDidAttachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) { + NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; + //NSLog(@"PTUSBDeviceDidAttachNotification: %@", note.userInfo); + FBPFTrace(@"PTUSBDeviceDidAttachNotification: %@", deviceID); + + dispatch_async(_notConnectedQueue, ^{ + if (!_connectingToDeviceID || ![deviceID isEqualToNumber:_connectingToDeviceID]) { + [self disconnectFromCurrentChannel]; + _connectingToDeviceID = deviceID; + _connectedDeviceProperties = [note.userInfo objectForKey:@"Properties"]; + [self enqueueConnectToUSBDevice]; + } + }); + }]; + + [nc addObserverForName:PTUSBDeviceDidDetachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) { + NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; + //NSLog(@"PTUSBDeviceDidDetachNotification: %@", note.userInfo); + FBPFTrace(@"PTUSBDeviceDidDetachNotification: %@", deviceID); + + if ([_connectingToDeviceID isEqualToNumber:deviceID]) { + _connectedDeviceProperties = nil; + _connectingToDeviceID = nil; + if (_connectedChannel) { + [_connectedChannel close]; + } + } + }]; +} + + +- (void)didDisconnectFromDevice:(NSNumber *)deviceID { + FBPFLog(@"Disconnected from device #%@", deviceID); + if ([_connectedDeviceID isEqualToNumber:deviceID]) { + [self willChangeValueForKey:@"connectedDeviceID"]; + _connectedDeviceID = nil; + [self didChangeValueForKey:@"connectedDeviceID"]; + } +} + + +- (void)disconnectFromCurrentChannel { + if (_connectedDeviceID && _connectedChannel) { + [_connectedChannel close]; + self.connectedChannel = nil; + } +} + +- (void)enqueueConnectToLocalIPv4Port { + dispatch_async(_notConnectedQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self connectToLocalIPv4Port]; + }); + }); +} + + +- (void)connectToLocalIPv4Port { + PTChannel *channel = [PTChannel channelWithDelegate:self]; + channel.userInfo = [NSString stringWithFormat:@"127.0.0.1:%lu", (unsigned long)_channelPort]; + [channel connectToPort:_channelPort IPv4Address:INADDR_LOOPBACK callback:^(NSError *error, PTAddress *address) { + if (error) { + if (error.domain == NSPOSIXErrorDomain && (error.code == ECONNREFUSED || error.code == ETIMEDOUT)) { + // this is an expected state + } else { + FBPFTrace(@"Failed to connect to 127.0.0.1:%lu: %@", (unsigned long)_channelPort, error); + } + } else { + [self disconnectFromCurrentChannel]; + self.connectedChannel = channel; + channel.userInfo = address; + FBPFLog(@"Connected to %@", address); + } + [self performSelector:@selector(enqueueConnectToLocalIPv4Port) withObject:nil afterDelay:ReconnectDelay]; + }]; +} + +- (void)enqueueConnectToUSBDevice { + dispatch_async(_notConnectedQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self connectToUSBDevice]; + }); + }); +} + + +- (void)connectToUSBDevice { + PTChannel *channel = [PTChannel channelWithDelegate:self]; + channel.userInfo = _connectingToDeviceID; + channel.delegate = self; + + [channel connectToPort:(int)_channelPort overUSBHub:PTUSBHub.sharedHub deviceID:_connectingToDeviceID callback:^(NSError *error) { + if (error) { + FBPFTrace(@"Failed to connect to device #%@: %@", channel.userInfo, error); + if (channel.userInfo == _connectingToDeviceID) { + [self performSelector:@selector(enqueueConnectToUSBDevice) withObject:nil afterDelay:ReconnectDelay]; + } + } else { + _connectedDeviceID = _connectingToDeviceID; + self.connectedChannel = channel; + FBPFLog(@"Connected to device #%@\n%@", _connectingToDeviceID, _connectedDeviceProperties); + } + }]; +} + + + +@end diff --git a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingCommon.h b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingCommon.h new file mode 100644 index 0000000000..b240b660d1 --- /dev/null +++ b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingCommon.h @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#define FBPFTrace(...) /*NSLog(__VA_ARGS__)*/ +#define FBPFLog(...) NSLog(__VA_ARGS__) + +enum { + FBPortForwardingFrameTypeOpenPipe = 201, + FBPortForwardingFrameTypeWriteToPipe = 202, + FBPortForwardingFrameTypeClosePipe = 203, +}; + +static dispatch_data_t NSDataToGCDData(NSData *data) { + __block NSData *retainedData = data; + return dispatch_data_create(data.bytes, data.length, nil, ^{ + retainedData = nil; + }); +} diff --git a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.h b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.h new file mode 100644 index 0000000000..5bc082fd02 --- /dev/null +++ b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface FBPortForwardingServer : NSObject + +- (instancetype)init; + +- (void)listenForMultiplexingChannelOnPort:(NSUInteger)port; +- (void)forwardConnectionsFromPort:(NSUInteger)port; +- (void)close; + +@end diff --git a/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.m b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.m new file mode 100644 index 0000000000..3bc0daec88 --- /dev/null +++ b/Tools/FBPortForwarding/FBPortForwarding/FBPortForwardingServer.m @@ -0,0 +1,193 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "FBPortForwardingServer.h" + +#import + +#import + +#import + +#import "FBPortForwardingCommon.h" + +@interface FBPortForwardingServer () +{ + __weak PTChannel *_serverChannel; + __weak PTChannel *_peerChannel; + + GCDAsyncSocket *_serverSocket; + NSMutableDictionary *_clientSockets; + UInt32 _lastClientSocketTag; + dispatch_queue_t _socketQueue; + PTProtocol *_protocol; +} + +@end + +@implementation FBPortForwardingServer + +- (instancetype)init +{ + if (self = [super init]) { + _socketQueue = dispatch_queue_create("FBPortForwardingServer", DISPATCH_QUEUE_SERIAL); + _lastClientSocketTag = 0; + _clientSockets = [NSMutableDictionary dictionary]; + _protocol = [[PTProtocol alloc] initWithDispatchQueue:_socketQueue]; + } + return self; +} + +- (void)dealloc +{ + [self close]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)forwardConnectionsFromPort:(NSUInteger)port +{ + [self _forwardConnectionsFromPort:port reportError:YES]; + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) { + [self _forwardConnectionsFromPort:port reportError:NO]; + }]; +} + +- (void)_forwardConnectionsFromPort:(NSUInteger)port reportError:(BOOL)shouldReportError +{ + GCDAsyncSocket *serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_socketQueue]; + NSError *listenError; + if ([serverSocket acceptOnPort:port error:&listenError]) { + _serverSocket = serverSocket; + } else { + if (shouldReportError) { + FBPFLog(@"Failed to listen: %@", listenError); + } + } +} + +- (void)listenForMultiplexingChannelOnPort:(NSUInteger)port +{ + [self _listenForMultiplexingChannelOnPort:port reportError:YES]; + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) { + [self _listenForMultiplexingChannelOnPort:port reportError:NO]; + }]; +} + +- (void)_listenForMultiplexingChannelOnPort:(NSUInteger)port reportError:(BOOL)shouldReportError +{ + PTChannel *channel = [[PTChannel alloc] initWithProtocol:_protocol delegate:self]; + [channel listenOnPort:port IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) { + if (error) { + if (shouldReportError) { + FBPFLog(@"Failed to listen on 127.0.0.1:%lu: %@", (unsigned long)port, error); + } + } else { + FBPFTrace(@"Listening on 127.0.0.1:%lu", (unsigned long)port); + _serverChannel = channel; + } + }]; +} + +- (void)close +{ + if (_serverChannel) { + [_serverChannel close]; + _serverChannel = nil; + } + [_serverSocket disconnect]; +} + +#pragma mark - PTChannelDelegate + +- (void)ioFrameChannel:(PTChannel *)channel didAcceptConnection:(PTChannel *)otherChannel fromAddress:(PTAddress *)address { + // Cancel any other connection. We are FIFO, so the last connection + // established will cancel any previous connection and "take its place". + if (_peerChannel) { + [_peerChannel cancel]; + } + + // Weak pointer to current connection. Connection objects live by themselves + // (owned by its parent dispatch queue) until they are closed. + _peerChannel = otherChannel; + _peerChannel.userInfo = address; + FBPFTrace(@"Connected to %@", address); +} + +- (void)ioFrameChannel:(PTChannel *)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData *)payload { + //NSLog(@"didReceiveFrameOfType: %u, %u, %@", type, tag, payload); + if (type == FBPortForwardingFrameTypeWriteToPipe) { + GCDAsyncSocket *sock = _clientSockets[@(tag)]; + [sock writeData:[NSData dataWithBytes:payload.data length:payload.length] withTimeout:-1 tag:0]; + FBPFTrace(@"channel -> socket (%d), %zu bytes", tag, payload.length); + } + + if (type == FBPortForwardingFrameTypeClosePipe) { + GCDAsyncSocket *sock = _clientSockets[@(tag)]; + [sock disconnectAfterWriting]; + } +} + +- (void)ioFrameChannel:(PTChannel *)channel didEndWithError:(NSError *)error { + for (GCDAsyncSocket *sock in [_clientSockets objectEnumerator]) { + [sock setDelegate:nil]; + [sock disconnect]; + } + [_clientSockets removeAllObjects]; + FBPFTrace(@"Disconnected from %@, error = %@", channel.userInfo, error); +} + + +#pragma mark - GCDAsyncSocketDelegate + +- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket +{ + dispatch_block_t block = ^() { + if (!_peerChannel) { + [newSocket setDelegate:nil]; + [newSocket disconnect]; + } + + UInt32 tag = ++_lastClientSocketTag; + newSocket.userData = @(tag); + newSocket.delegate = self; + _clientSockets[@(tag)] = newSocket; + [_peerChannel sendFrameOfType:FBPortForwardingFrameTypeOpenPipe tag:_lastClientSocketTag withPayload:nil callback:^(NSError *error) { + FBPFTrace(@"open socket (%d), error = %@", (unsigned int)tag, error); + [newSocket readDataWithTimeout:-1 tag:0]; + }]; + }; + + if (_peerChannel) { + block(); + } else { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), _socketQueue, block); + } +} + +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)_ +{ + UInt32 tag = [[sock userData] unsignedIntValue]; + FBPFTrace(@"Incoming data on socket (%d) - %lu bytes", (unsigned int)tag, (unsigned long)data.length); + [_peerChannel sendFrameOfType:FBPortForwardingFrameTypeWriteToPipe tag:tag withPayload:NSDataToGCDData(data) callback:^(NSError *error) { + FBPFTrace(@"socket (%d) -> channel %lu bytes, error = %@", (unsigned int)tag, (unsigned long)data.length, error); + [sock readDataWithTimeout:-1 tag:_]; + }]; +} + +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err +{ + UInt32 tag = [sock.userData unsignedIntValue]; + [_clientSockets removeObjectForKey:@(tag)]; + [_peerChannel sendFrameOfType:FBPortForwardingFrameTypeClosePipe tag:tag withPayload:nil callback:^(NSError *error) { + FBPFTrace(@"socket (%d) disconnected, err = %@, peer error = %@", (unsigned int)tag, err, error); + }]; +} + + +@end diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.h b/Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.h new file mode 100644 index 0000000000..1bc3f98871 --- /dev/null +++ b/Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@interface PFPingClient : NSObject + +- (BOOL)connectToLocalServerOnPort:(NSUInteger)port; +- (void)sendPing:(NSData *)ping; + +@property (nonatomic, copy, readonly) NSArray *pongs; +@property (nonatomic, readonly) BOOL connected; + +@end diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.m b/Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.m new file mode 100644 index 0000000000..3629758508 --- /dev/null +++ b/Tools/FBPortForwarding/FBPortForwardingTests/PFPingClient.m @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFPingClient.h" + +@implementation PFPingClient +{ + GCDAsyncSocket *_client; +} + +- (instancetype)init +{ + if (self = [super init]) { + _pongs = [NSArray array]; + _client = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; + } + return self; +} + +- (BOOL)connectToLocalServerOnPort:(NSUInteger)port +{ + return [_client connectToHost:@"localhost" onPort:port error:nil]; +} + +- (void)sendPing:(NSData *)ping +{ + [_client writeData:ping withTimeout:-1 tag:0]; +} + +- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port +{ + _connected = YES; + [_client readDataWithTimeout:-1 tag:0]; +} + +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag +{ + _pongs = [_pongs arrayByAddingObject:data]; + [_client readDataWithTimeout:-1 tag:0]; +} + +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err +{ + _connected = NO; +} + +@end diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.h b/Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.h new file mode 100644 index 0000000000..c8859b2aa8 --- /dev/null +++ b/Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@interface PFPingServer : NSObject + +- (instancetype)initWithPort:(NSUInteger)port; + +@property (readonly, nonatomic) NSInteger clientsCount; + +@end diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.m b/Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.m new file mode 100644 index 0000000000..7e4e03495a --- /dev/null +++ b/Tools/FBPortForwarding/FBPortForwardingTests/PFPingServer.m @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFPingServer.h" + +@implementation PFPingServer +{ + GCDAsyncSocket *_server; + NSMutableArray *_clients; +} + +- (instancetype)initWithPort:(NSUInteger)port +{ + if (self = [super init]) { + _clients = [NSMutableArray array]; + _server = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; + + NSError *error; + if (![_server acceptOnPort:port error:&error]) { + NSLog(@"Failed to listen on port %lu: %@", (unsigned long)port, error); + return nil; + } + } + return self; +} + +- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket +{ + [_clients addObject:newSocket]; + [newSocket readDataWithTimeout:-1 tag:0]; +} + +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag +{ + [sock writeData:data withTimeout:-1 tag:0]; + [sock readDataWithTimeout:-1 tag:0]; +} + +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err +{ + [_clients removeObject:sock]; +} + +- (NSInteger)clientsCount +{ + return [_clients count]; +} + +@end diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.h b/Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.h new file mode 100644 index 0000000000..d5e1c6994e --- /dev/null +++ b/Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.h @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +@interface PFSimpleHTTPServer : NSObject + +- (instancetype)initWithPort:(NSUInteger)port response:(NSData *)data; + +@end diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.m b/Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.m new file mode 100644 index 0000000000..4d1989bbaf --- /dev/null +++ b/Tools/FBPortForwarding/FBPortForwardingTests/PFSimpleHTTPServer.m @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "PFSimpleHTTPServer.h" + +@implementation PFSimpleHTTPServer +{ + NSData *_response; + GCDAsyncSocket *_server; + NSMutableArray *_clients; +} + +- (instancetype)initWithPort:(NSUInteger)port response:(NSData *)data +{ + if (self = [super init]) { + _response = [data copy]; + _clients = [NSMutableArray array]; + _server = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; + if (![_server acceptOnPort:port error:nil]) { + return nil; + }; + } + return self; +} + +- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket +{ + [_clients addObject:newSocket]; + [newSocket readDataToData:[NSData dataWithBytes:"\r\n\r\n" length:4] withTimeout:-1 tag:0]; +} + +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag +{ + NSString *headers = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\nContent-Length: %lu\r\n\r\n", (unsigned long)[_response length]]; + [sock writeData:[headers dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0]; + [sock writeData:_response withTimeout:-1 tag:0]; + [sock disconnectAfterWriting]; +} + +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err +{ + [_clients removeObject:sock]; +} + +@end diff --git a/Tools/FBPortForwarding/FBPortForwardingTests/PFTests.m b/Tools/FBPortForwarding/FBPortForwardingTests/PFTests.m new file mode 100644 index 0000000000..b7bfd0f7dc --- /dev/null +++ b/Tools/FBPortForwarding/FBPortForwardingTests/PFTests.m @@ -0,0 +1,217 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +#import +#import + +#import + +#import "PFPingClient.h" +#import "PFPingServer.h" +#import "PFSimpleHTTPServer.h" + +#define PFWaitForPongNumber(n) \ + XCTAssertTrue(FBRunRunLoopUntilBlockIsTrue(^BOOL{ \ + return [client1.pongs count] == n; \ + }), @"Failed to receive pong"); + +@interface PFTests : XCTestCase + +@end + +@implementation PFTests + +- (void)simpleHTTPTestWithData:(NSData *)data +{ + FBPortForwardingServer *portForwardingServer = [[FBPortForwardingServer alloc] init]; + [portForwardingServer forwardConnectionsFromPort:9701]; + [portForwardingServer listenForMultiplexingChannelOnPort:8055]; + + FBPortForwardingClient *portForwardingClient = [[FBPortForwardingClient alloc] init]; + [portForwardingClient forwardConnectionsToPort:9702]; + [portForwardingClient connectToMultiplexingChannelOnPort:8055]; + + PFSimpleHTTPServer *httpServer = [[PFSimpleHTTPServer alloc] initWithPort:9702 response:data]; + XCTAssertNotNil(httpServer); + + __block BOOL finished = NO; + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:9701/"]]; + [NSURLConnection sendAsynchronousRequest:request + queue:[NSOperationQueue mainQueue] + completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *connectionError) { + XCTAssertNil(connectionError); + XCTAssertTrue([data isEqualToData:responseData]); + finished = YES; + }]; + + XCTAssert(FBRunRunLoopWithConditionReturningPassed(&finished)); + [portForwardingServer close]; + FBRunRunLoopBarrier(); +} + +- (void)testProxiesHTTPRequests +{ + [self simpleHTTPTestWithData:[@"OK" dataUsingEncoding:NSUTF8StringEncoding]]; +} + +- (void)testLargeHTTPResponse +{ + NSMutableData *largePayload = [NSMutableData data]; + [largePayload setLength:10000000]; + char *bytes = [largePayload mutableBytes]; + for (NSUInteger i = 0; i < largePayload.length; i++) { + bytes[i] = (char)(i % 255); + } + + [self simpleHTTPTestWithData:largePayload]; +} + +- (void)testPingPong +{ + FBPortForwardingServer *portForwardingServer = [[FBPortForwardingServer alloc] init]; + [portForwardingServer forwardConnectionsFromPort:9706]; + [portForwardingServer listenForMultiplexingChannelOnPort:8055]; + + FBPortForwardingClient *portForwardingClient = [[FBPortForwardingClient alloc] init]; + [portForwardingClient forwardConnectionsToPort:9705]; + [portForwardingClient connectToMultiplexingChannelOnPort:8055]; + + PFPingServer *server = [[PFPingServer alloc] initWithPort:9705]; + XCTAssertNotNil(server); + + PFPingClient *client1 = [[PFPingClient alloc] init]; + [client1 connectToLocalServerOnPort:9706]; + + PFPingClient *client2 = [[PFPingClient alloc] init]; + [client2 connectToLocalServerOnPort:9706]; + + XCTAssertTrue(FBRunRunLoopUntilBlockIsTrue(^BOOL{ + return client1.connected && client2.connected; + }), @"Failed to connect"); + + NSData *ping = [NSData dataWithBytes:"PING" length:4]; + [client1 sendPing:ping]; + + PFWaitForPongNumber(1); + + NSData *pong = client1.pongs[0]; + XCTAssert([ping isEqualToData:pong]); + + [client1 sendPing:ping]; + PFWaitForPongNumber(2); + + [client1 sendPing:ping]; + PFWaitForPongNumber(3); + + [client1 sendPing:ping]; + PFWaitForPongNumber(4); + + [client1 sendPing:ping]; + [client1 sendPing:ping]; + PFWaitForPongNumber(6); + + XCTAssertEqual(0, client2.pongs.count); + + [portForwardingServer close]; +} + +- (void)testDisconnectsWhenNoChannel +{ + FBPortForwardingServer *portForwardingServer = [[FBPortForwardingServer alloc] init]; + [portForwardingServer forwardConnectionsFromPort:9707]; + [portForwardingServer listenForMultiplexingChannelOnPort:8056]; + + __block BOOL finished = NO; + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:9707/"]]; + [NSURLConnection sendAsynchronousRequest:request + queue:[NSOperationQueue mainQueue] + completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *connectionError) { + XCTAssertNotNil(connectionError); + finished = YES; + }]; + + NSDate *start = [NSDate date]; + XCTAssert(FBRunRunLoopWithConditionReturningPassed(&finished)); + NSTimeInterval elapsed = -[start timeIntervalSinceNow]; + XCTAssertLessThan(elapsed, 5, @"Must disconnect - no port forwarding client"); + + [portForwardingServer close]; +} + +- (void)testWaitsForChannel +{ + FBPortForwardingServer *portForwardingServer = [[FBPortForwardingServer alloc] init]; + [portForwardingServer forwardConnectionsFromPort:9707]; + [portForwardingServer listenForMultiplexingChannelOnPort:8056]; + + NSData *data = [@"OK" dataUsingEncoding:NSUTF8StringEncoding]; + PFSimpleHTTPServer *httpServer = [[PFSimpleHTTPServer alloc] initWithPort:9702 response:data]; + XCTAssertNotNil(httpServer); + + __block BOOL finished = NO; + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:9707/"]]; + [NSURLConnection sendAsynchronousRequest:request + queue:[NSOperationQueue mainQueue] + completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *connectionError) { + XCTAssertNil(connectionError); + XCTAssertNotNil(responseData); + NSString *res = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; + XCTAssertEqualObjects(res, @"OK"); + finished = YES; + }]; + + NSDate *start = [NSDate date]; + FBRunRunLoopUntilBlockIsTrue(^BOOL{ + return [start timeIntervalSinceNow] < -0.5; + }); + + // NOTE: Establishing port forwarding connection *after* sending HTTP request + FBPortForwardingClient *portForwardingClient = [[FBPortForwardingClient alloc] init]; + [portForwardingClient forwardConnectionsToPort:9702]; + [portForwardingClient connectToMultiplexingChannelOnPort:8056]; + + XCTAssert(FBRunRunLoopWithConditionReturningPassed(&finished)); + [portForwardingServer close]; +} + +- (void)testDisconnectsWhenChannelConnectionLost +{ + FBPortForwardingServer *portForwardingServer = [[FBPortForwardingServer alloc] init]; + [portForwardingServer forwardConnectionsFromPort:9706]; + [portForwardingServer listenForMultiplexingChannelOnPort:8055]; + + FBPortForwardingClient *portForwardingClient = [[FBPortForwardingClient alloc] init]; + [portForwardingClient forwardConnectionsToPort:9705]; + [portForwardingClient connectToMultiplexingChannelOnPort:8055]; + + PFPingServer *server = [[PFPingServer alloc] initWithPort:9705]; + XCTAssertNotNil(server); + + PFPingClient *client1 = [[PFPingClient alloc] init]; + [client1 connectToLocalServerOnPort:9706]; + + XCTAssertTrue(FBRunRunLoopUntilBlockIsTrue(^BOOL{ + return client1.connected; + }), @"Failed to connect"); + + [portForwardingClient close]; + + XCTAssertTrue(FBRunRunLoopUntilBlockIsTrue(^BOOL{ + return !client1.connected; + }), @"Failed to disconnect"); + + XCTAssertEqual(server.clientsCount, 0); + + [portForwardingServer close]; +} + +@end diff --git a/Tools/FBPortForwarding/README.md b/Tools/FBPortForwarding/README.md new file mode 100644 index 0000000000..b67db7a064 --- /dev/null +++ b/Tools/FBPortForwarding/README.md @@ -0,0 +1,66 @@ +# FBPortForwarding + +FBPortForwarding lets you expose your Mac's port to iOS device via lightning +cable. The typical usecase is connecting to a TCP server that runs on OS X +from an iPhone app without common WiFi network. + +## Benefits: + + 1. No need to be on the same WiFi, worry about firewalls (fbguest) or VPN + 2. iOS app doesn't have to know your Mac's IP address + 3. Secure - communication is possible only when connected via USB + +## How it works + +iOS provides a way to connect to device's TCP server from Mac via USBHub, but +there is no API to connect from iOS to TCP server running on Mac. FBPortForwarding +uses [Peertalk](https://github.com/rsms/peertalk) to establish communication +channel from Mac to iOS, creates a TCP server on iOS and multiplexes all +connections to that server via the peertalk channel. Helper app running on Mac +listens for commands on the peertalk channel and initializes TCP connections +to local port and forwards all communication back via the same peertalk channel. + + + | + iOS Device | Mac + | + +----------------+ +----------------+ + |Peertalk Server | connect |Peertalk Client | + | <------------+ | + | | | | + | Port 8025| | | + +----+-----------+ +---------^------+ + | | + | | + incoming +----------------+ | | +--------------+ + connections |Proxy Server | | | |Real Server | + ------------->> | | +-------------+ commands | | | + | Port 8081| | create | | stream | | Port 8081| + +-+--------------+ +---------> Peertalk <----------+ +-^------------+ + | | Channel | ^ + | +--------+ | | +--------+ | outgoing + | | | onConnect | | connect | | | connections + +---> Client +---------------> OpenPipe +---------------> Client +-----+ + | #[tag] | onRead | | write | #[tag] | + | +---------------> WriteToPipe +---------------> | + | | onDisconnect | | disconnect | | + | +---------------> ClosePipe +---------------> | + | | | | | | + | | write | | onRead | | + | <---------------+ WriteToPipe <---------------+ | + | | close | | onDisconnect | | + | <---------------+ ClosePipe <---------------+ | + | | | | | | + +--------+ | | +--------+ + +-------------+ + +First, the library on iOS device creates a TCP server on the port we want to +forward (let's say 8081) and a special Peertalk server on port 8025. Mac helper +app looks for connected iOS devices, and once it finds one it connects to its +peertalk server. Only *one* channel is created that's going to be used for +multiplexing data. + +When a socket connects to local proxy server, FBPortForwarding is going to assign +a tag to the connection and use peertalk channel to tell Mac helper app to connect +to TCP port 8081 on Mac. Now events and data on both sides of the wire are going +to be multiplexed and transferred via the peertalk channel.