jens@0: // jens@0: // TCPListener.m jens@0: // MYNetwork jens@0: // jens@0: // Created by Jens Alfke on 5/10/08. jens@0: // Copyright 2008 Jens Alfke. All rights reserved. jens@0: // Portions based on TCPServer class from Apple's "CocoaEcho" sample code. jens@0: jens@0: #import "TCPListener.h" jens@0: #import "TCPConnection.h" jens@0: jens@1: #import "Logging.h" jens@1: #import "Test.h" jens@0: #import "ExceptionUtils.h" jens@0: #import "IPAddress.h" jens@1: jens@0: #include jens@0: #include jens@0: #include jens@0: jens@0: jens@0: static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, jens@0: CFDataRef address, const void *data, void *info); jens@0: jens@0: @interface TCPListener() jens@0: @property BOOL bonjourPublished; jens@0: @property NSInteger bonjourError; jens@0: - (void) _updateTXTRecord; jens@0: @end jens@0: jens@0: jens@0: @implementation TCPListener jens@0: jens@0: jens@0: - (id) initWithPort: (UInt16)port jens@0: { jens@0: self = [super init]; jens@0: if (self != nil) { jens@0: _port = port; jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: jens@0: - (void) dealloc jens@0: { jens@0: [self close]; jens@0: LogTo(TCP,@"DEALLOC %@",self); jens@0: [super dealloc]; jens@0: } jens@0: jens@0: jens@0: @synthesize delegate=_delegate, port=_port, useIPv6=_useIPv6, jens@0: bonjourServiceType=_bonjourServiceType, bonjourServiceName=_bonjourServiceName, jens@0: bonjourPublished=_bonjourPublished, bonjourError=_bonjourError, jens@0: pickAvailablePort=_pickAvailablePort; jens@0: jens@0: jens@0: - (NSString*) description jens@0: { jens@0: return $sprintf(@"%@[port %hu]",self.class,_port); jens@0: } jens@0: jens@0: jens@0: // Stores the last error from CFSocketCreate or CFSocketSetAddress into *ouError. jens@0: static void* getLastCFSocketError( NSError **outError ) { jens@0: if( outError ) jens@0: *outError = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil]; jens@0: return NULL; jens@0: } jens@0: jens@0: // Closes a socket (if it's not already NULL), and returns NULL to assign to it. jens@0: static CFSocketRef closeSocket( CFSocketRef socket ) { jens@0: if( socket ) { jens@0: CFSocketInvalidate(socket); jens@0: CFRelease(socket); jens@0: } jens@0: return NULL; jens@0: } jens@0: jens@0: // opens a socket of a given protocol, either ipv4 or ipv6. jens@0: - (CFSocketRef) _openProtocol: (SInt32) protocolFamily jens@0: address: (struct sockaddr*)address jens@0: error: (NSError**)error jens@0: { jens@0: CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL}; jens@0: CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, jens@0: kCFSocketAcceptCallBack, &TCPListenerAcceptCallBack, &socketCtxt); jens@0: if( ! socket ) jens@0: return getLastCFSocketError(error); // CFSocketCreate leaves error code in errno jens@0: jens@0: int yes = 1; jens@0: setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)); jens@0: jens@0: NSData *addressData = [NSData dataWithBytes:address length:address->sa_len]; jens@0: if (kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)addressData)) { jens@0: getLastCFSocketError(error); jens@0: return closeSocket(socket); jens@0: } jens@0: // set up the run loop source for the socket jens@0: CFRunLoopRef cfrl = CFRunLoopGetCurrent(); jens@0: CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0); jens@0: CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes); jens@0: CFRelease(source); jens@0: return socket; jens@0: } jens@0: jens@0: - (BOOL) _failedToOpen: (NSError*)error jens@0: { jens@0: LogTo(TCP,@"%@ failed to open: %@",self,error); jens@0: [self tellDelegate: @selector(listener:failedToOpen:) withObject: error]; jens@0: return NO; jens@0: } jens@0: jens@0: jens@0: - (BOOL) open: (NSError**)outError jens@0: { jens@0: // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us jens@0: do{ jens@0: struct sockaddr_in addr4; jens@0: memset(&addr4, 0, sizeof(addr4)); jens@0: addr4.sin_len = sizeof(addr4); jens@0: addr4.sin_family = AF_INET; jens@0: addr4.sin_port = htons(_port); jens@0: addr4.sin_addr.s_addr = htonl(INADDR_ANY); jens@0: jens@0: NSError *error; jens@0: _ipv4socket = [self _openProtocol: PF_INET address: (struct sockaddr*)&addr4 error: &error]; jens@0: if( ! _ipv4socket ) { jens@0: if( error.code==EADDRINUSE && _pickAvailablePort && _port<0xFFFF ) { jens@0: LogTo(BLIPVerbose,@"%@: port busy, trying %hu...",self,_port+1); jens@0: self.port++; // try the next port jens@0: } else { jens@0: if( outError ) *outError = error; jens@0: return [self _failedToOpen: error]; jens@0: } jens@0: } jens@0: }while( ! _ipv4socket ); jens@0: jens@0: if (0 == _port) { jens@0: // now that the binding was successful, we get the port number jens@0: NSData *addr = [(NSData *)CFSocketCopyAddress(_ipv4socket) autorelease]; jens@0: const struct sockaddr_in *addr4 = addr.bytes; jens@0: self.port = ntohs(addr4->sin_port); jens@0: } jens@0: jens@0: if( _useIPv6 ) { jens@0: // set up the IPv6 endpoint jens@0: struct sockaddr_in6 addr6; jens@0: memset(&addr6, 0, sizeof(addr6)); jens@0: addr6.sin6_len = sizeof(addr6); jens@0: addr6.sin6_family = AF_INET6; jens@0: addr6.sin6_port = htons(_port); jens@0: memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr)); jens@0: jens@0: _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: outError]; jens@0: if( ! _ipv6socket ) { jens@0: _ipv4socket = closeSocket(_ipv4socket); jens@0: return [self _failedToOpen: *outError]; jens@0: } jens@0: } jens@0: jens@0: // Open Bonjour: jens@0: if( _bonjourServiceType && !_netService) { jens@0: // instantiate the NSNetService object that will advertise on our behalf. jens@0: _netService = [[NSNetService alloc] initWithDomain: @"local." jens@0: type: _bonjourServiceType jens@0: name: _bonjourServiceName ?:@"" jens@0: port: _port]; jens@0: if( _netService ) { jens@0: [_netService setDelegate:self]; jens@0: if( _bonjourTXTRecord ) jens@0: [self _updateTXTRecord]; jens@0: [_netService publish]; jens@0: } else { jens@0: self.bonjourError = -1; jens@0: Warn(@"%@: Failed to create NSNetService",self); jens@0: } jens@0: } jens@0: jens@0: LogTo(TCP,@"%@ is open",self); jens@0: [self tellDelegate: @selector(listenerDidOpen:) withObject: nil]; jens@0: return YES; jens@0: } jens@0: jens@0: - (BOOL) open jens@0: { jens@0: return [self open: nil]; jens@0: } jens@0: jens@0: jens@0: - (void) close jens@0: { jens@0: if( _ipv4socket ) { jens@0: if( _netService ) { jens@0: [_netService stop]; jens@0: [_netService release]; jens@0: _netService = nil; jens@0: self.bonjourPublished = NO; jens@0: } jens@0: self.bonjourError = 0; jens@0: jens@0: _ipv4socket = closeSocket(_ipv4socket); jens@0: _ipv6socket = closeSocket(_ipv6socket); jens@0: jens@0: LogTo(BLIP,@"%@ is closed",self); jens@0: [self tellDelegate: @selector(listenerDidClose:) withObject: nil]; jens@0: } jens@0: } jens@0: jens@0: jens@0: - (BOOL) isOpen jens@0: { jens@0: return _ipv4socket != NULL; jens@0: } jens@0: jens@0: jens@0: #pragma mark - jens@0: #pragma mark ACCEPTING CONNECTIONS: jens@0: jens@0: jens@0: @synthesize connectionClass = _connectionClass; jens@0: jens@0: jens@0: - (BOOL) acceptConnection: (CFSocketNativeHandle)socket jens@0: { jens@0: IPAddress *addr = [IPAddress addressOfSocket: socket]; jens@0: if( ! addr ) jens@0: return NO; jens@0: if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)] jens@0: && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] ) jens@0: return NO; jens@0: jens@0: Assert(_connectionClass); jens@0: TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket jens@0: listener: self]; jens@0: if( ! conn ) jens@0: return NO; jens@0: jens@0: if( _sslProperties ) { jens@0: conn.SSLProperties = _sslProperties; jens@0: [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer]; jens@0: } jens@0: [conn open]; jens@0: [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn]; jens@0: return YES; jens@0: } jens@0: jens@0: jens@0: static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) jens@0: { jens@0: TCPListener *server = (TCPListener *)info; jens@0: if (kCFSocketAcceptCallBack == type) { jens@0: CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data; jens@0: BOOL accepted = NO; jens@0: @try{ jens@0: accepted = [server acceptConnection: nativeSocketHandle]; jens@0: }catchAndReport(@"TCPListenerAcceptCallBack"); jens@0: if( ! accepted ) jens@0: close(nativeSocketHandle); jens@0: } jens@0: } jens@0: jens@0: jens@0: #pragma mark - jens@0: #pragma mark BONJOUR: jens@0: jens@0: jens@0: - (NSDictionary*) bonjourTXTRecord jens@0: { jens@0: return _bonjourTXTRecord; jens@0: } jens@0: jens@0: - (void) setBonjourTXTRecord: (NSDictionary*)txt jens@0: { jens@0: if( ifSetObj(&_bonjourTXTRecord,txt) ) jens@0: [self _updateTXTRecord]; jens@0: } jens@0: jens@0: - (void) _updateTXTRecord jens@0: { jens@0: if( _netService ) { jens@0: NSData *data; jens@0: if( _bonjourTXTRecord ) { jens@0: data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord]; jens@0: if( data ) jens@0: LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length); jens@0: else jens@0: Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord); jens@0: } else jens@0: data = nil; jens@0: [_netService setTXTRecordData: data]; jens@0: } jens@0: } jens@0: jens@0: jens@0: - (void)netServiceWillPublish:(NSNetService *)sender jens@0: { jens@0: LogTo(BLIP,@"%@: Advertising %@",self,sender); jens@0: self.bonjourPublished = YES; jens@0: } jens@0: jens@0: - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict jens@0: { jens@0: self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue]; jens@0: LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError); jens@0: [_netService release]; jens@0: _netService = nil; jens@0: } jens@0: jens@0: - (void)netServiceDidStop:(NSNetService *)sender jens@0: { jens@0: LogTo(BLIP,@"%@: Stopped advertising %@",self,sender); jens@0: self.bonjourPublished = NO; jens@0: [_netService release]; jens@0: _netService = nil; jens@0: } jens@0: jens@0: jens@0: @end jens@0: jens@0: jens@0: jens@0: /* jens@0: Copyright (c) 2008, Jens Alfke . All rights reserved. jens@0: jens@0: Redistribution and use in source and binary forms, with or without modification, are permitted jens@0: provided that the following conditions are met: jens@0: jens@0: * Redistributions of source code must retain the above copyright notice, this list of conditions jens@0: and the following disclaimer. jens@0: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions jens@0: and the following disclaimer in the documentation and/or other materials provided with the jens@0: distribution. jens@0: jens@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR jens@0: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND jens@0: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- jens@0: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES jens@0: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR jens@0: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN jens@0: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF jens@0: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jens@0: */