Add FBPortForwarding source code

Summary:
See FBPortForwarding/README.md for an explanation of what this is.
It's required for the upcoming support for physical iOS devices.

To simplify development of the JS app, We're going to bundle the pre-built PortForwardingMacApp
inside the repo, and inside the electron app (static/PortForwardingMacApp.app).
Adding this source so users can build it from source if they choose to.

Reviewed By: danielbuechele

Differential Revision: D13276022

fbshipit-source-id: 99b18e0412cf443bb4a67eb4846cc780e0014de1
This commit is contained in:
John Knox
2018-12-03 11:32:37 -08:00
committed by Facebook Github Bot
parent 98110bc230
commit 8d93946739
11 changed files with 515 additions and 96 deletions

View File

@@ -0,0 +1,34 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <Foundation/Foundation.h>
#import <FKPortForwarding/FKPortForwardingClient.h>
static const char kPortForwardParamPrefix[] = "-portForward=";
static const char kMultiplexChannelPortParamPrefix[] = "-multiplexChannelPort=";
static BOOL prefix(const char *pre, const char *str) {
return strncmp(pre, str, strlen(pre)) == 0;
}
int main(int argc, char *argv[])
{
long connectionsPort = 8081;
long multiplexPort = 8025;
for (int i = 1; i < argc; ++i) {
if (prefix(kPortForwardParamPrefix, argv[i])) {
connectionsPort = strtol(argv[i] + strlen(kPortForwardParamPrefix), NULL, 10);
} else if (prefix(kMultiplexChannelPortParamPrefix, argv[i])) {
multiplexPort = strtol(argv[i] + strlen(kMultiplexChannelPortParamPrefix), NULL, 10);
}
}
FKPortForwardingClient *client = [FKPortForwardingClient new];
[client forwardConnectionsToPort:connectionsPort];
[client connectToMultiplexingChannelOnPort:multiplexPort];
[[NSRunLoop currentRunLoop] run];
client = nil;
return 0;
}

View File

@@ -46,17 +46,26 @@ Pod::Spec.new do |spec|
ss.private_header_files = 'iOS/FlipperKit/FBCxxUtils/**/*.h' ss.private_header_files = 'iOS/FlipperKit/FBCxxUtils/**/*.h'
end end
spec.subspec "FKPortForwarding" do |ss|
ss.header_dir = "FKPortForwarding"
ss.dependency 'CocoaAsyncSocket', '~> 7.6'
ss.dependency 'PeerTalk', '~>0.0.2'
ss.compiler_flags = folly_compiler_flags
ss.source_files = 'iOS/FlipperKit/FKPortForwarding/FKPortForwarding{Server,Common}.{h,m}'
ss.private_header_files = 'iOS/FlipperKit/FKPortForwarding/FKPortForwarding{Server,Common}.h'
end
spec.subspec "Core" do |ss| spec.subspec "Core" do |ss|
ss.dependency 'FlipperKit/FBDefines' ss.dependency 'FlipperKit/FBDefines'
ss.dependency 'FlipperKit/FBCxxUtils' ss.dependency 'FlipperKit/FBCxxUtils'
ss.dependency 'FlipperKit/CppBridge' ss.dependency 'FlipperKit/CppBridge'
ss.dependency 'FlipperKit/FKPortForwarding'
ss.dependency 'Folly', '~>1.1' ss.dependency 'Folly', '~>1.1'
ss.dependency 'Flipper', '~>'+flipperkit_version ss.dependency 'Flipper', '~>'+flipperkit_version
ss.dependency 'CocoaAsyncSocket', '~> 7.6'
ss.dependency 'PeerTalk', '~>0.0.2'
ss.dependency 'OpenSSL-Static', '1.0.2.c1' ss.dependency 'OpenSSL-Static', '1.0.2.c1'
ss.compiler_flags = folly_compiler_flags ss.compiler_flags = folly_compiler_flags
ss.source_files = 'iOS/FlipperKit/FBDefines/*.{h,cpp,m,mm}', 'iOS/FlipperKit/CppBridge/*.{h,mm}', 'iOS/FlipperKit/FBCxxUtils/*.{h,mm}', 'iOS/FlipperKit/Utilities/**/*.{h,m}', 'iOS/FlipperKit/*.{h,m,mm}' ss.source_files = 'iOS/FlipperKit/FBDefines/*.{h,cpp,m,mm}', 'iOS/FlipperKit/CppBridge/*.{h,mm}', 'iOS/FlipperKit/FBCxxUtils/*.{h,mm}', 'iOS/FlipperKit/*.{h,m,mm}'
ss.public_header_files = 'iOS/Plugins/FlipperKitNetworkPlugin/SKIOSNetworkPlugin/SKIOSNetworkAdapter.h', ss.public_header_files = 'iOS/Plugins/FlipperKitNetworkPlugin/SKIOSNetworkPlugin/SKIOSNetworkAdapter.h',
'iOS/Plugins/FlipperKitNetworkPlugin/FlipperKitNetworkPlugin/SKBufferingPlugin.h', 'iOS/Plugins/FlipperKitNetworkPlugin/FlipperKitNetworkPlugin/SKBufferingPlugin.h',
'iOS/Plugins/FlipperKitNetworkPlugin/FlipperKitNetworkPlugin/SKNetworkReporter.h', 'iOS/Plugins/FlipperKitNetworkPlugin/FlipperKitNetworkPlugin/SKNetworkReporter.h',

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@interface FKPortForwardingClient : NSObject
- (instancetype)init;
- (void)forwardConnectionsToPort:(NSUInteger)port;
- (void)connectToMultiplexingChannelOnPort:(NSUInteger)port;
- (void)close;
@end

View File

@@ -0,0 +1,297 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "FKPortForwardingClient.h"
#import <CocoaAsyncSocket/GCDAsyncSocket.h>
#import <peertalk/PTChannel.h>
#import <peertalk/PTUSBHub.h>
#import "FKPortForwardingCommon.h"
static const NSTimeInterval ReconnectDelay = 1.0;
@interface FKPortForwardingClient () <GCDAsyncSocketDelegate, PTChannelDelegate>
{
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 FKPortForwardingClient
@synthesize connectedDeviceID = _connectedDeviceID;
- (instancetype)init
{
if (self = [super init]) {
_notConnectedQueue = dispatch_queue_create("FKPortForwarding.notConnectedQueue", DISPATCH_QUEUE_SERIAL);
_clientSocketsQueue = dispatch_queue_create("FKPortForwarding.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 == FKPortForwardingFrameTypeOpenPipe) {
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 == FKPortForwardingFrameTypeWriteToPipe) {
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 == FKPortForwardingFrameTypeClosePipe) {
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:FKPortForwardingFrameTypeClosePipe tag:tag withPayload:nil callback:nil];
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)_
{
UInt32 tag = [sock.userData unsignedIntValue];
[_connectedChannel sendFrameOfType:FKPortForwardingFrameTypeWriteToPipe 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];
__weak typeof(self) weakSelf = self;
[nc addObserverForName:PTUSBDeviceDidAttachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) {
NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"];
//NSLog(@"PTUSBDeviceDidAttachNotification: %@", note.userInfo);
FBPFTrace(@"PTUSBDeviceDidAttachNotification: %@", deviceID);
typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
dispatch_async(strongSelf->_notConnectedQueue, ^{
[strongSelf didAttachToDevice:deviceID note:note];
});
}];
[nc addObserverForName:PTUSBDeviceDidDetachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) {
NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"];
//NSLog(@"PTUSBDeviceDidDetachNotification: %@", note.userInfo);
FBPFTrace(@"PTUSBDeviceDidDetachNotification: %@", deviceID);
[weakSelf didDetachFromDevice:deviceID];
}];
}
- (void)didAttachToDevice:(NSNumber *)deviceID note:(NSNotification *)note
{
if (!_connectingToDeviceID || ![deviceID isEqualToNumber:_connectingToDeviceID]) {
[self disconnectFromCurrentChannel];
_connectingToDeviceID = deviceID;
_connectedDeviceProperties = [note.userInfo objectForKey:@"Properties"];
[self enqueueConnectToUSBDevice];
}
}
- (void)didDetachFromDevice:(NSNumber *)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) {
[self didConnectToChannel:channel withError:error];
}];
}
- (void)didConnectToChannel:(PTChannel *)channel withError:(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

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#define FBPFTrace(...) /*NSLog(__VA_ARGS__)*/
#define FBPFLog(...) NSLog(__VA_ARGS__)
enum {
FKPortForwardingFrameTypeOpenPipe = 201,
FKPortForwardingFrameTypeWriteToPipe = 202,
FKPortForwardingFrameTypeClosePipe = 203,
};
static dispatch_data_t NSDataToGCDData(NSData *data) {
__block NSData *retainedData = data;
return dispatch_data_create(data.bytes, data.length, nil, ^{
retainedData = nil;
});
}

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@interface FKPortForwardingServer : NSObject
- (instancetype)init;
- (void)listenForMultiplexingChannelOnPort:(NSUInteger)port;
- (void)forwardConnectionsFromPort:(NSUInteger)port;
- (void)close;
@end

View File

@@ -1,39 +1,34 @@
/* // Copyright 2004-present Facebook. All Rights Reserved.
* Copyright (c) 2018-present, Facebook, Inc.
* #import "FKPortForwardingServer.h"
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import "SKPortForwardingServer.h"
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <CocoaAsyncSocket/GCDAsyncSocket.h> #import <CocoaAsyncSocket/GCDAsyncSocket.h>
#import <peertalk/PTChannel.h> #import <peertalk/PTChannel.h>
#import "SKMacros.h" #import "FKPortForwardingCommon.h"
#import "SKPortForwardingCommon.h"
@interface SKPortForwardingServer () <PTChannelDelegate, GCDAsyncSocketDelegate> @interface FKPortForwardingServer () <PTChannelDelegate, GCDAsyncSocketDelegate>
{
__weak PTChannel *_serverChannel;
__weak PTChannel *_peerChannel;
@property (nonatomic, weak) PTChannel *serverChannel; GCDAsyncSocket *_serverSocket;
@property (nonatomic, weak) PTChannel *peerChannel; NSMutableDictionary *_clientSockets;
UInt32 _lastClientSocketTag;
@property (nonatomic, strong) GCDAsyncSocket *serverSocket; dispatch_queue_t _socketQueue;
@property (nonatomic, strong) NSMutableDictionary *clientSockets; PTProtocol *_protocol;
@property (nonatomic, assign) UInt32 lastClientSocketTag; }
@property (nonatomic, strong) dispatch_queue_t socketQueue;
@property (nonatomic, strong) PTProtocol *protocol;
@end @end
@implementation SKPortForwardingServer @implementation FKPortForwardingServer
- (instancetype)init - (instancetype)init
{ {
if (self = [super init]) { if (self = [super init]) {
_socketQueue = dispatch_queue_create("SKPortForwardingServer", DISPATCH_QUEUE_SERIAL); _socketQueue = dispatch_queue_create("FKPortForwardingServer", DISPATCH_QUEUE_SERIAL);
_lastClientSocketTag = 0; _lastClientSocketTag = 0;
_clientSockets = [NSMutableDictionary dictionary]; _clientSockets = [NSMutableDictionary dictionary];
_protocol = [[PTProtocol alloc] initWithDispatchQueue:_socketQueue]; _protocol = [[PTProtocol alloc] initWithDispatchQueue:_socketQueue];
@@ -60,10 +55,10 @@
GCDAsyncSocket *serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_socketQueue]; GCDAsyncSocket *serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_socketQueue];
NSError *listenError; NSError *listenError;
if ([serverSocket acceptOnPort:port error:&listenError]) { if ([serverSocket acceptOnPort:port error:&listenError]) {
self.serverSocket = serverSocket; _serverSocket = serverSocket;
} else { } else {
if (shouldReportError) { if (shouldReportError) {
SKLog(@"Failed to listen: %@", listenError); FBPFLog(@"Failed to listen: %@", listenError);
} }
} }
} }
@@ -82,22 +77,22 @@
[channel listenOnPort:port IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) { [channel listenOnPort:port IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) {
if (error) { if (error) {
if (shouldReportError) { if (shouldReportError) {
SKLog(@"Failed to listen on 127.0.0.1:%lu: %@", (unsigned long)port, error); FBPFLog(@"Failed to listen on 127.0.0.1:%lu: %@", (unsigned long)port, error);
} }
} else { } else {
SKTrace(@"Listening on 127.0.0.1:%lu", (unsigned long)port); FBPFTrace(@"Listening on 127.0.0.1:%lu", (unsigned long)port);
self.serverChannel = channel; self->_serverChannel = channel;
} }
}]; }];
} }
- (void)close - (void)close
{ {
if (self.serverChannel) { if (_serverChannel) {
[self.serverChannel close]; [_serverChannel close];
self.serverChannel = nil; _serverChannel = nil;
} }
[self.serverSocket disconnect]; [_serverSocket disconnect];
} }
#pragma mark - PTChannelDelegate #pragma mark - PTChannelDelegate
@@ -105,27 +100,27 @@
- (void)ioFrameChannel:(PTChannel *)channel didAcceptConnection:(PTChannel *)otherChannel fromAddress:(PTAddress *)address { - (void)ioFrameChannel:(PTChannel *)channel didAcceptConnection:(PTChannel *)otherChannel fromAddress:(PTAddress *)address {
// Cancel any other connection. We are FIFO, so the last connection // Cancel any other connection. We are FIFO, so the last connection
// established will cancel any previous connection and "take its place". // established will cancel any previous connection and "take its place".
if (self.peerChannel) { if (_peerChannel) {
[self.peerChannel cancel]; [_peerChannel cancel];
} }
// Weak pointer to current connection. Connection objects live by themselves // Weak pointer to current connection. Connection objects live by themselves
// (owned by its parent dispatch queue) until they are closed. // (owned by its parent dispatch queue) until they are closed.
self.peerChannel = otherChannel; _peerChannel = otherChannel;
self.peerChannel.userInfo = address; _peerChannel.userInfo = address;
SKTrace(@"Connected to %@", address); FBPFTrace(@"Connected to %@", address);
} }
- (void)ioFrameChannel:(PTChannel *)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData *)payload { - (void)ioFrameChannel:(PTChannel *)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData *)payload {
//NSLog(@"didReceiveFrameOfType: %u, %u, %@", type, tag, payload); //NSLog(@"didReceiveFrameOfType: %u, %u, %@", type, tag, payload);
if (type == SKPortForwardingFrameTypeWriteToPipe) { if (type == FKPortForwardingFrameTypeWriteToPipe) {
GCDAsyncSocket *sock = self.clientSockets[@(tag)]; GCDAsyncSocket *sock = _clientSockets[@(tag)];
[sock writeData:[NSData dataWithBytes:payload.data length:payload.length] withTimeout:-1 tag:0]; [sock writeData:[NSData dataWithBytes:payload.data length:payload.length] withTimeout:-1 tag:0];
SKTrace(@"channel -> socket (%d), %zu bytes", tag, payload.length); FBPFTrace(@"channel -> socket (%d), %zu bytes", tag, payload.length);
} }
if (type == SKPortForwardingFrameTypeClosePipe) { if (type == FKPortForwardingFrameTypeClosePipe) {
GCDAsyncSocket *sock = self.clientSockets[@(tag)]; GCDAsyncSocket *sock = _clientSockets[@(tag)];
[sock disconnectAfterWriting]; [sock disconnectAfterWriting];
} }
} }
@@ -135,8 +130,8 @@
[sock setDelegate:nil]; [sock setDelegate:nil];
[sock disconnect]; [sock disconnect];
} }
[self.clientSockets removeAllObjects]; [_clientSockets removeAllObjects];
SKTrace(@"Disconnected from %@, error = %@", channel.userInfo, error); FBPFTrace(@"Disconnected from %@, error = %@", channel.userInfo, error);
} }
@@ -145,7 +140,7 @@
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{ {
dispatch_block_t block = ^() { dispatch_block_t block = ^() {
if (!self.peerChannel) { if (!self->_peerChannel) {
[newSocket setDelegate:nil]; [newSocket setDelegate:nil];
[newSocket disconnect]; [newSocket disconnect];
} }
@@ -153,9 +148,9 @@
UInt32 tag = ++self->_lastClientSocketTag; UInt32 tag = ++self->_lastClientSocketTag;
newSocket.userData = @(tag); newSocket.userData = @(tag);
newSocket.delegate = self; newSocket.delegate = self;
self.clientSockets[@(tag)] = newSocket; self->_clientSockets[@(tag)] = newSocket;
[self.peerChannel sendFrameOfType:SKPortForwardingFrameTypeOpenPipe tag:self->_lastClientSocketTag withPayload:nil callback:^(NSError *error) { [self->_peerChannel sendFrameOfType:FKPortForwardingFrameTypeOpenPipe tag:self->_lastClientSocketTag withPayload:nil callback:^(NSError *error) {
SKTrace(@"open socket (%d), error = %@", (unsigned int)tag, error); FBPFTrace(@"open socket (%d), error = %@", (unsigned int)tag, error);
[newSocket readDataWithTimeout:-1 tag:0]; [newSocket readDataWithTimeout:-1 tag:0];
}]; }];
}; };
@@ -170,9 +165,9 @@
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)_ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)_
{ {
UInt32 tag = [[sock userData] unsignedIntValue]; UInt32 tag = [[sock userData] unsignedIntValue];
SKTrace(@"Incoming data on socket (%d) - %lu bytes", (unsigned int)tag, (unsigned long)data.length); FBPFTrace(@"Incoming data on socket (%d) - %lu bytes", (unsigned int)tag, (unsigned long)data.length);
[_peerChannel sendFrameOfType:SKPortForwardingFrameTypeWriteToPipe tag:tag withPayload:NSDataToGCDData(data) callback:^(NSError *error) { [_peerChannel sendFrameOfType:FKPortForwardingFrameTypeWriteToPipe tag:tag withPayload:NSDataToGCDData(data) callback:^(NSError *error) {
SKTrace(@"socket (%d) -> channel %lu bytes, error = %@", (unsigned int)tag, (unsigned long)data.length, error); FBPFTrace(@"socket (%d) -> channel %lu bytes, error = %@", (unsigned int)tag, (unsigned long)data.length, error);
[sock readDataWithTimeout:-1 tag:_]; [sock readDataWithTimeout:-1 tag:_];
}]; }];
} }
@@ -181,8 +176,8 @@
{ {
UInt32 tag = [sock.userData unsignedIntValue]; UInt32 tag = [sock.userData unsignedIntValue];
[_clientSockets removeObjectForKey:@(tag)]; [_clientSockets removeObjectForKey:@(tag)];
[_peerChannel sendFrameOfType:SKPortForwardingFrameTypeClosePipe tag:tag withPayload:nil callback:^(NSError *error) { [_peerChannel sendFrameOfType:FKPortForwardingFrameTypeClosePipe tag:tag withPayload:nil callback:^(NSError *error) {
SKTrace(@"socket (%d) disconnected, err = %@, peer error = %@", (unsigned int)tag, err, error); FBPFTrace(@"socket (%d) disconnected, err = %@, peer error = %@", (unsigned int)tag, err, error);
}]; }];
} }

View File

@@ -0,0 +1,66 @@
# FKPortForwarding
FKPortForwarding 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. FKPortForwarding
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, FKPortForwarding 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.

View File

@@ -18,7 +18,7 @@
#import "FlipperClient+Testing.h" #import "FlipperClient+Testing.h"
#if !TARGET_OS_SIMULATOR #if !TARGET_OS_SIMULATOR
//#import "SKPortForwardingServer.h" //#import <FKPortForwarding/FKPortForwardingServer.h>
#endif #endif
using WrapperPlugin = facebook::flipper::FlipperCppWrapperPlugin; using WrapperPlugin = facebook::flipper::FlipperCppWrapperPlugin;
@@ -28,7 +28,7 @@ using WrapperPlugin = facebook::flipper::FlipperCppWrapperPlugin;
folly::ScopedEventBaseThread sonarThread; folly::ScopedEventBaseThread sonarThread;
folly::ScopedEventBaseThread connectionThread; folly::ScopedEventBaseThread connectionThread;
#if !TARGET_OS_SIMULATOR #if !TARGET_OS_SIMULATOR
// SKPortForwardingServer *_server; // FKPortForwardingServer *_server;
#endif #endif
} }
@@ -119,7 +119,7 @@ using WrapperPlugin = facebook::flipper::FlipperCppWrapperPlugin;
- (void)start; - (void)start;
{ {
#if !TARGET_OS_SIMULATOR #if !TARGET_OS_SIMULATOR
// _server = [SKPortForwardingServer new]; // _server = [FKPortForwardingServer new];
// [_server forwardConnectionsFromPort:8088]; // [_server forwardConnectionsFromPort:8088];
// [_server listenForMultiplexingChannelOnPort:8078]; // [_server listenForMultiplexingChannelOnPort:8078];
#endif #endif

View File

@@ -1,21 +0,0 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import <Foundation/Foundation.h>
enum {
SKPortForwardingFrameTypeOpenPipe = 201,
SKPortForwardingFrameTypeWriteToPipe = 202,
SKPortForwardingFrameTypeClosePipe = 203,
};
static dispatch_data_t NSDataToGCDData(NSData *data) {
__block NSData *retainedData = data;
return dispatch_data_create(data.bytes, data.length, nil, ^{
retainedData = nil;
});
}

View File

@@ -1,18 +0,0 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import <Foundation/Foundation.h>
@interface SKPortForwardingServer : NSObject
- (instancetype)init;
- (void)listenForMultiplexingChannelOnPort:(NSUInteger)port;
- (void)forwardConnectionsFromPort:(NSUInteger)port;
- (void)close;
@end