1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/TCP/TCPListener.m Fri May 23 17:37:36 2008 -0700
1.3 @@ -0,0 +1,342 @@
1.4 +//
1.5 +// TCPListener.m
1.6 +// MYNetwork
1.7 +//
1.8 +// Created by Jens Alfke on 5/10/08.
1.9 +// Copyright 2008 Jens Alfke. All rights reserved.
1.10 +// Portions based on TCPServer class from Apple's "CocoaEcho" sample code.
1.11 +
1.12 +#import "TCPListener.h"
1.13 +#import "TCPConnection.h"
1.14 +
1.15 +#import "ExceptionUtils.h"
1.16 +#import "IPAddress.h"
1.17 +#include <sys/socket.h>
1.18 +
1.19 +#include <netinet/in.h>
1.20 +#include <unistd.h>
1.21 +
1.22 +
1.23 +static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type,
1.24 + CFDataRef address, const void *data, void *info);
1.25 +
1.26 +@interface TCPListener()
1.27 +@property BOOL bonjourPublished;
1.28 +@property NSInteger bonjourError;
1.29 +- (void) _updateTXTRecord;
1.30 +@end
1.31 +
1.32 +
1.33 +@implementation TCPListener
1.34 +
1.35 +
1.36 +- (id) initWithPort: (UInt16)port
1.37 +{
1.38 + self = [super init];
1.39 + if (self != nil) {
1.40 + _port = port;
1.41 + }
1.42 + return self;
1.43 +}
1.44 +
1.45 +
1.46 +- (void) dealloc
1.47 +{
1.48 + [self close];
1.49 + LogTo(TCP,@"DEALLOC %@",self);
1.50 + [super dealloc];
1.51 +}
1.52 +
1.53 +
1.54 +@synthesize delegate=_delegate, port=_port, useIPv6=_useIPv6,
1.55 + bonjourServiceType=_bonjourServiceType, bonjourServiceName=_bonjourServiceName,
1.56 + bonjourPublished=_bonjourPublished, bonjourError=_bonjourError,
1.57 + pickAvailablePort=_pickAvailablePort;
1.58 +
1.59 +
1.60 +- (NSString*) description
1.61 +{
1.62 + return $sprintf(@"%@[port %hu]",self.class,_port);
1.63 +}
1.64 +
1.65 +
1.66 +// Stores the last error from CFSocketCreate or CFSocketSetAddress into *ouError.
1.67 +static void* getLastCFSocketError( NSError **outError ) {
1.68 + if( outError )
1.69 + *outError = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil];
1.70 + return NULL;
1.71 +}
1.72 +
1.73 +// Closes a socket (if it's not already NULL), and returns NULL to assign to it.
1.74 +static CFSocketRef closeSocket( CFSocketRef socket ) {
1.75 + if( socket ) {
1.76 + CFSocketInvalidate(socket);
1.77 + CFRelease(socket);
1.78 + }
1.79 + return NULL;
1.80 +}
1.81 +
1.82 +// opens a socket of a given protocol, either ipv4 or ipv6.
1.83 +- (CFSocketRef) _openProtocol: (SInt32) protocolFamily
1.84 + address: (struct sockaddr*)address
1.85 + error: (NSError**)error
1.86 +{
1.87 + CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
1.88 + CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP,
1.89 + kCFSocketAcceptCallBack, &TCPListenerAcceptCallBack, &socketCtxt);
1.90 + if( ! socket )
1.91 + return getLastCFSocketError(error); // CFSocketCreate leaves error code in errno
1.92 +
1.93 + int yes = 1;
1.94 + setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
1.95 +
1.96 + NSData *addressData = [NSData dataWithBytes:address length:address->sa_len];
1.97 + if (kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)addressData)) {
1.98 + getLastCFSocketError(error);
1.99 + return closeSocket(socket);
1.100 + }
1.101 + // set up the run loop source for the socket
1.102 + CFRunLoopRef cfrl = CFRunLoopGetCurrent();
1.103 + CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
1.104 + CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
1.105 + CFRelease(source);
1.106 + return socket;
1.107 +}
1.108 +
1.109 +- (BOOL) _failedToOpen: (NSError*)error
1.110 +{
1.111 + LogTo(TCP,@"%@ failed to open: %@",self,error);
1.112 + [self tellDelegate: @selector(listener:failedToOpen:) withObject: error];
1.113 + return NO;
1.114 +}
1.115 +
1.116 +
1.117 +- (BOOL) open: (NSError**)outError
1.118 +{
1.119 + // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us
1.120 + do{
1.121 + struct sockaddr_in addr4;
1.122 + memset(&addr4, 0, sizeof(addr4));
1.123 + addr4.sin_len = sizeof(addr4);
1.124 + addr4.sin_family = AF_INET;
1.125 + addr4.sin_port = htons(_port);
1.126 + addr4.sin_addr.s_addr = htonl(INADDR_ANY);
1.127 +
1.128 + NSError *error;
1.129 + _ipv4socket = [self _openProtocol: PF_INET address: (struct sockaddr*)&addr4 error: &error];
1.130 + if( ! _ipv4socket ) {
1.131 + if( error.code==EADDRINUSE && _pickAvailablePort && _port<0xFFFF ) {
1.132 + LogTo(BLIPVerbose,@"%@: port busy, trying %hu...",self,_port+1);
1.133 + self.port++; // try the next port
1.134 + } else {
1.135 + if( outError ) *outError = error;
1.136 + return [self _failedToOpen: error];
1.137 + }
1.138 + }
1.139 + }while( ! _ipv4socket );
1.140 +
1.141 + if (0 == _port) {
1.142 + // now that the binding was successful, we get the port number
1.143 + NSData *addr = [(NSData *)CFSocketCopyAddress(_ipv4socket) autorelease];
1.144 + const struct sockaddr_in *addr4 = addr.bytes;
1.145 + self.port = ntohs(addr4->sin_port);
1.146 + }
1.147 +
1.148 + if( _useIPv6 ) {
1.149 + // set up the IPv6 endpoint
1.150 + struct sockaddr_in6 addr6;
1.151 + memset(&addr6, 0, sizeof(addr6));
1.152 + addr6.sin6_len = sizeof(addr6);
1.153 + addr6.sin6_family = AF_INET6;
1.154 + addr6.sin6_port = htons(_port);
1.155 + memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));
1.156 +
1.157 + _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: outError];
1.158 + if( ! _ipv6socket ) {
1.159 + _ipv4socket = closeSocket(_ipv4socket);
1.160 + return [self _failedToOpen: *outError];
1.161 + }
1.162 + }
1.163 +
1.164 + // Open Bonjour:
1.165 + if( _bonjourServiceType && !_netService) {
1.166 + // instantiate the NSNetService object that will advertise on our behalf.
1.167 + _netService = [[NSNetService alloc] initWithDomain: @"local."
1.168 + type: _bonjourServiceType
1.169 + name: _bonjourServiceName ?:@""
1.170 + port: _port];
1.171 + if( _netService ) {
1.172 + [_netService setDelegate:self];
1.173 + if( _bonjourTXTRecord )
1.174 + [self _updateTXTRecord];
1.175 + [_netService publish];
1.176 + } else {
1.177 + self.bonjourError = -1;
1.178 + Warn(@"%@: Failed to create NSNetService",self);
1.179 + }
1.180 + }
1.181 +
1.182 + LogTo(TCP,@"%@ is open",self);
1.183 + [self tellDelegate: @selector(listenerDidOpen:) withObject: nil];
1.184 + return YES;
1.185 +}
1.186 +
1.187 +- (BOOL) open
1.188 +{
1.189 + return [self open: nil];
1.190 +}
1.191 +
1.192 +
1.193 +- (void) close
1.194 +{
1.195 + if( _ipv4socket ) {
1.196 + if( _netService ) {
1.197 + [_netService stop];
1.198 + [_netService release];
1.199 + _netService = nil;
1.200 + self.bonjourPublished = NO;
1.201 + }
1.202 + self.bonjourError = 0;
1.203 +
1.204 + _ipv4socket = closeSocket(_ipv4socket);
1.205 + _ipv6socket = closeSocket(_ipv6socket);
1.206 +
1.207 + LogTo(BLIP,@"%@ is closed",self);
1.208 + [self tellDelegate: @selector(listenerDidClose:) withObject: nil];
1.209 + }
1.210 +}
1.211 +
1.212 +
1.213 +- (BOOL) isOpen
1.214 +{
1.215 + return _ipv4socket != NULL;
1.216 +}
1.217 +
1.218 +
1.219 +#pragma mark -
1.220 +#pragma mark ACCEPTING CONNECTIONS:
1.221 +
1.222 +
1.223 +@synthesize connectionClass = _connectionClass;
1.224 +
1.225 +
1.226 +- (BOOL) acceptConnection: (CFSocketNativeHandle)socket
1.227 +{
1.228 + IPAddress *addr = [IPAddress addressOfSocket: socket];
1.229 + if( ! addr )
1.230 + return NO;
1.231 + if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)]
1.232 + && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] )
1.233 + return NO;
1.234 +
1.235 + Assert(_connectionClass);
1.236 + TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket
1.237 + listener: self];
1.238 + if( ! conn )
1.239 + return NO;
1.240 +
1.241 + if( _sslProperties ) {
1.242 + conn.SSLProperties = _sslProperties;
1.243 + [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer];
1.244 + }
1.245 + [conn open];
1.246 + [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn];
1.247 + return YES;
1.248 +}
1.249 +
1.250 +
1.251 +static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
1.252 +{
1.253 + TCPListener *server = (TCPListener *)info;
1.254 + if (kCFSocketAcceptCallBack == type) {
1.255 + CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
1.256 + BOOL accepted = NO;
1.257 + @try{
1.258 + accepted = [server acceptConnection: nativeSocketHandle];
1.259 + }catchAndReport(@"TCPListenerAcceptCallBack");
1.260 + if( ! accepted )
1.261 + close(nativeSocketHandle);
1.262 + }
1.263 +}
1.264 +
1.265 +
1.266 +#pragma mark -
1.267 +#pragma mark BONJOUR:
1.268 +
1.269 +
1.270 +- (NSDictionary*) bonjourTXTRecord
1.271 +{
1.272 + return _bonjourTXTRecord;
1.273 +}
1.274 +
1.275 +- (void) setBonjourTXTRecord: (NSDictionary*)txt
1.276 +{
1.277 + if( ifSetObj(&_bonjourTXTRecord,txt) )
1.278 + [self _updateTXTRecord];
1.279 +}
1.280 +
1.281 +- (void) _updateTXTRecord
1.282 +{
1.283 + if( _netService ) {
1.284 + NSData *data;
1.285 + if( _bonjourTXTRecord ) {
1.286 + data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord];
1.287 + if( data )
1.288 + LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length);
1.289 + else
1.290 + Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord);
1.291 + } else
1.292 + data = nil;
1.293 + [_netService setTXTRecordData: data];
1.294 + }
1.295 +}
1.296 +
1.297 +
1.298 +- (void)netServiceWillPublish:(NSNetService *)sender
1.299 +{
1.300 + LogTo(BLIP,@"%@: Advertising %@",self,sender);
1.301 + self.bonjourPublished = YES;
1.302 +}
1.303 +
1.304 +- (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
1.305 +{
1.306 + self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue];
1.307 + LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError);
1.308 + [_netService release];
1.309 + _netService = nil;
1.310 +}
1.311 +
1.312 +- (void)netServiceDidStop:(NSNetService *)sender
1.313 +{
1.314 + LogTo(BLIP,@"%@: Stopped advertising %@",self,sender);
1.315 + self.bonjourPublished = NO;
1.316 + [_netService release];
1.317 + _netService = nil;
1.318 +}
1.319 +
1.320 +
1.321 +@end
1.322 +
1.323 +
1.324 +
1.325 +/*
1.326 + Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
1.327 +
1.328 + Redistribution and use in source and binary forms, with or without modification, are permitted
1.329 + provided that the following conditions are met:
1.330 +
1.331 + * Redistributions of source code must retain the above copyright notice, this list of conditions
1.332 + and the following disclaimer.
1.333 + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
1.334 + and the following disclaimer in the documentation and/or other materials provided with the
1.335 + distribution.
1.336 +
1.337 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
1.338 + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
1.339 + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
1.340 + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
1.341 + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
1.342 + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
1.343 + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
1.344 + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.345 + */