Skip to content
This repository has been archived by the owner on Oct 20, 2024. It is now read-only.

Dev for: Support IPv6 in PSWebSocketServer #50 #53

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 55 additions & 13 deletions PocketSocket/PSWebSocketServer.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ @interface PSWebSocketServerConnection : NSObject
@end
@implementation PSWebSocketServerConnection

// private member variable
bool _isV6Address = false;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have curly braces around it; otherwise it isn't an instance/member variable, it's a global variable.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it should be declared in PSWebSocketServer, not PSWebSocketServerConnection.
And you can't initialize an instance variable in its declaration, so the = false needs to be deleted.


- (instancetype)init {
if((self = [super init])) {
_identifier = [[NSProcessInfo processInfo] globallyUniqueString];
Expand Down Expand Up @@ -98,6 +101,8 @@ + (instancetype)serverWithHost:(NSString *)host port:(NSUInteger)port {
+ (instancetype)serverWithHost:(NSString *)host port:(NSUInteger)port SSLCertificates:(NSArray *)SSLCertificates {
return [[self alloc] initWithHost:host port:port SSLCertificates:SSLCertificates];
}

// if "host" is equal to nil or "0.0.0.0" or "::" or any format which means ANY ADDRESS, it will bind to ANY for both IPv4 and IPv6.
- (instancetype)initWithHost:(NSString *)host port:(NSUInteger)port SSLCertificates:(NSArray *)SSLCertificates {
NSParameterAssert(port);
if((self = [super init])) {
Expand All @@ -107,22 +112,59 @@ - (instancetype)initWithHost:(NSString *)host port:(NSUInteger)port SSLCertifica
_SSLCertificates = [SSLCertificates copy];
_secure = (_SSLCertificates != nil);

// create addr data
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
if(host && ![host isEqualToString:@"0.0.0.0"]) {
addr.sin_addr.s_addr = inet_addr(host.UTF8String);
if(!addr.sin_addr.s_addr) {
bool isAnyAddress = false;
bool isV6Address = false;
if (host) {
if ([host rangeOfString:@":"].location != NSNotFound) {
isV6Address = true;
if ([host isEqualToString:@"::"]) {
isAnyAddress = true;
}
} else {
if ([host isEqualToString:@"0.0.0.0"]) {
isAnyAddress = true;
}
}
} else {
isAnyAddress = true;
}

// create address data
if (isAnyAddress || isV6Address ) { // IPv6 address or address ANY
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test confused me — I thought it meant that specifying all interfaces would always make the listener accept only IPv6 connections. But then I found a web page explaining that using "in6addr_any … allows connections to be established from any IPv4 or IPv6 client". This is probably worth spelling out in a comment.

struct sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
addr.sin6_len = sizeof(addr);
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(port);

if (isAnyAddress) {
addr.sin6_addr = in6addr_any;
} else {
// Note: "0::0:0" or "0:0:0:0:0:0:0:0" will also be bound to ANY(*) even isAnyAddress is false
int ret = inet_pton(AF_INET6, host.UTF8String, &(addr.sin6_addr));
if(ret <= 0) { // only 1 means OK, either 0 or -1 means error
[NSException raise:@"Invalid host" format:@"Could not formulate internet address from host: %@", host];
return nil;
}
}

_addrData = [NSData dataWithBytes:&addr length:sizeof(addr)];
} else { // IPv4 address
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(port);

addr.sin_addr.s_addr = inet_addr(host.UTF8String); // "host" is non-null here
if(addr.sin_addr.s_addr == INADDR_NONE) {
[NSException raise:@"Invalid host" format:@"Could not formulate internet address from host: %@", host];
return nil;
}
} else {
addr.sin_addr.s_addr = htonl(INADDR_ANY);
_addrData = [NSData dataWithBytes:&addr length:sizeof(addr)];
}
addr.sin_port = htons(port);
_addrData = [NSData dataWithBytes:&addr length:sizeof(addr)];
_isV6Address = isV6Address;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong: If isV6Address is false but isAnyAddress is true, then _addrData will contain an IPv6 address, so the CFSocketCreate call below needs to pass PF_INET6.

I fixed this by removing this line and adding _isV6Address = YES at the end of the IPv6 branch above (after line 151 in this patch.)


// create socket context
_socketContext = (CFSocketContext){0, (__bridge void *)self, NULL, NULL, NULL};
Expand Down Expand Up @@ -158,7 +200,7 @@ - (void)connect:(BOOL)silent {

// create socket
_socket = CFSocketCreate(kCFAllocatorDefault,
PF_INET,
_isV6Address ? PF_INET6 : PF_INET,
SOCK_STREAM,
IPPROTO_TCP,
kCFSocketAcceptCallBack,
Expand Down