Skip to content

Commit 3e4d1e1

Browse files
committed
Added the ability to specify the local port for outgoing connections.
1 parent 076eb7a commit 3e4d1e1

File tree

3 files changed

+77
-10
lines changed

3 files changed

+77
-10
lines changed

GCD/GCDAsyncSocket.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
224224
*
225225
* The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
226226
* The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
227+
* The interface may also be used to specify the local port (see below).
227228
*
228229
* To not time out use a negative time interval.
229230
*
@@ -236,6 +237,16 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
236237
* Since this class supports queued reads and writes, you can immediately start reading and/or writing.
237238
* All read/write operations will be queued, and upon socket connection,
238239
* the operations will be dequeued and processed in order.
240+
*
241+
* The interface may optionally contain a port number at the end of the string, separated by a colon.
242+
* This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end)
243+
* To specify both interface and local port: "en1:8082" or "192.168.4.35:2424".
244+
* To specify only local port: ":8082".
245+
* Please note this is an advanced feature, and is somewhat hidden on purpose.
246+
* You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection.
247+
* If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere.
248+
* Local ports do NOT need to match remote ports. In fact, they almost never do.
249+
* This feature is here for networking professionals using very advanced techniques.
239250
**/
240251
- (BOOL)connectToHost:(NSString *)host
241252
onPort:(UInt16)port
@@ -272,6 +283,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
272283
* struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
273284
*
274285
* The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
286+
* The interface may also be used to specify the local port (see below).
275287
*
276288
* The timeout is optional. To not time out use a negative time interval.
277289
*
@@ -284,6 +296,16 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
284296
* Since this class supports queued reads and writes, you can immediately start reading and/or writing.
285297
* All read/write operations will be queued, and upon socket connection,
286298
* the operations will be dequeued and processed in order.
299+
*
300+
* The interface may optionally contain a port number at the end of the string, separated by a colon.
301+
* This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end)
302+
* To specify both interface and local port: "en1:8082" or "192.168.4.35:2424".
303+
* To specify only local port: ":8082".
304+
* Please note this is an advanced feature, and is somewhat hidden on purpose.
305+
* You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection.
306+
* If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere.
307+
* Local ports do NOT need to match remote ports. In fact, they almost never do.
308+
* This feature is here for networking professionals using very advanced techniques.
287309
**/
288310
- (BOOL)connectToAddress:(NSData *)remoteAddr
289311
viaInterface:(NSString *)interface

GCD/GCDAsyncSocket.m

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2152,6 +2152,15 @@ - (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error
21522152
{
21532153
LogVerbose(@"Binding socket...");
21542154

2155+
if ([[self class] portFromAddress:connectInterface] > 0)
2156+
{
2157+
// Since we're going to be binding to a specific port,
2158+
// we should turn on reuseaddr to allow us to override sockets in time_wait.
2159+
2160+
int reuseOn = 1;
2161+
setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
2162+
}
2163+
21552164
struct sockaddr *interfaceAddr = (struct sockaddr *)[connectInterface bytes];
21562165

21572166
int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]);
@@ -3136,6 +3145,10 @@ - (BOOL)isIPv6
31363145
/**
31373146
* Finds the address of an interface description.
31383147
* An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34).
3148+
*
3149+
* The interface description may optionally contain a port number at the end, separated by a colon.
3150+
* If a non-zeor port parameter is provided, any port number in the interface description is ignored.
3151+
*
31393152
* The returned value is a 'struct sockaddr' wrapped in an NSData object.
31403153
**/
31413154
- (void)getInterfaceAddress4:(NSData **)interfaceAddr4Ptr
@@ -3146,7 +3159,28 @@ - (void)getInterfaceAddress4:(NSData **)interfaceAddr4Ptr
31463159
NSData *addr4 = nil;
31473160
NSData *addr6 = nil;
31483161

3149-
if (interfaceDescription == nil)
3162+
NSString *interface = nil;
3163+
3164+
NSArray *components = [interfaceDescription componentsSeparatedByString:@":"];
3165+
if ([components count] > 0)
3166+
{
3167+
NSString *temp = [components objectAtIndex:0];
3168+
if ([temp length] > 0)
3169+
{
3170+
interface = temp;
3171+
}
3172+
}
3173+
if ([components count] > 1 && port == 0)
3174+
{
3175+
long portL = strtol([[components objectAtIndex:1] UTF8String], NULL, 10);
3176+
3177+
if (portL > 0 && portL <= UINT16_MAX)
3178+
{
3179+
port = (UInt16)portL;
3180+
}
3181+
}
3182+
3183+
if (interface == nil)
31503184
{
31513185
// ANY address
31523186

@@ -3169,7 +3203,7 @@ - (void)getInterfaceAddress4:(NSData **)interfaceAddr4Ptr
31693203
addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
31703204
addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
31713205
}
3172-
else if ([interfaceDescription isEqualToString:@"localhost"] || [interfaceDescription isEqualToString:@"loopback"])
3206+
else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"])
31733207
{
31743208
// LOOPBACK address
31753209

@@ -3194,7 +3228,7 @@ - (void)getInterfaceAddress4:(NSData **)interfaceAddr4Ptr
31943228
}
31953229
else
31963230
{
3197-
const char *interface = [interfaceDescription UTF8String];
3231+
const char *iface = [interface UTF8String];
31983232

31993233
struct ifaddrs *addrs;
32003234
const struct ifaddrs *cursor;
@@ -3210,7 +3244,7 @@ - (void)getInterfaceAddress4:(NSData **)interfaceAddr4Ptr
32103244

32113245
struct sockaddr_in *addr = (struct sockaddr_in *)cursor->ifa_addr;
32123246

3213-
if (strcmp(cursor->ifa_name, interface) == 0)
3247+
if (strcmp(cursor->ifa_name, iface) == 0)
32143248
{
32153249
// Name match
32163250

@@ -3226,7 +3260,7 @@ - (void)getInterfaceAddress4:(NSData **)interfaceAddr4Ptr
32263260
const char *conversion;
32273261
conversion = inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip));
32283262

3229-
if ((conversion != NULL) && (strcmp(ip, interface) == 0))
3263+
if ((conversion != NULL) && (strcmp(ip, iface) == 0))
32303264
{
32313265
// IP match
32323266

@@ -3243,7 +3277,7 @@ - (void)getInterfaceAddress4:(NSData **)interfaceAddr4Ptr
32433277

32443278
struct sockaddr_in6 *addr = (struct sockaddr_in6 *)cursor->ifa_addr;
32453279

3246-
if (strcmp(cursor->ifa_name, interface) == 0)
3280+
if (strcmp(cursor->ifa_name, iface) == 0)
32473281
{
32483282
// Name match
32493283

@@ -3259,7 +3293,7 @@ - (void)getInterfaceAddress4:(NSData **)interfaceAddr4Ptr
32593293
const char *conversion;
32603294
conversion = inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip));
32613295

3262-
if ((conversion != NULL) && (strcmp(ip, interface) == 0))
3296+
if ((conversion != NULL) && (strcmp(ip, iface) == 0))
32633297
{
32643298
// IP match
32653299

GCD/Xcode/IPhoneConnectTest/Classes/IPhoneConnectTestAppDelegate.m

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,19 @@ @implementation IPhoneConnectTestAppDelegate
1616
- (void)normalConnect
1717
{
1818
NSError *error = nil;
19-
if (![asyncSocket connectToHost:@"google.com" onPort:80 error:&error])
19+
20+
NSString *host = @"google.com";
21+
// NSString *host = @"deusty.com";
22+
23+
if (![asyncSocket connectToHost:host onPort:80 error:&error])
2024
{
2125
DDLogError(@"Error connecting: %@", error);
2226
}
27+
28+
// if (![asyncSocket connectToHost:host onPort:80 viaInterface:@":12345" withTimeout:-1 error:&error])
29+
// {
30+
// DDLogError(@"Error connecting: %@", error);
31+
// }
2332
}
2433

2534
- (void)secureConnect
@@ -52,8 +61,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
5261

5362
asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:mainQueue];
5463

55-
// [self normalConnect];
56-
[self secureConnect];
64+
[self normalConnect];
65+
// [self secureConnect];
5766

5867
// Add the view controller's view to the window and display.
5968
[window addSubview:viewController.view];
@@ -66,6 +75,8 @@ - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UI
6675
{
6776
DDLogInfo(@"socket:%p didConnectToHost:%@ port:%hu", sock, host, port);
6877

78+
// DDLogInfo(@"localHost:%@ port:%hu", [sock localHost], [sock localPort]);
79+
6980
if (port == 443)
7081
{
7182

0 commit comments

Comments
 (0)