TCP/TCPListener.m
author Jens Alfke <jens@mooseyard.com>
Sat May 16 14:24:06 2009 -0700 (2009-05-16)
changeset 48 2b4ad2067074
parent 44 d8a559a39284
child 49 20cccc7c26ee
permissions -rwxr-xr-x
Yuck -- [TCPConnection initToBonjourService:] was releasing the wrong object, the BonjourService, causing it to dealloc and eventually crash. Fixes #10.
     1 //
     2 //  TCPListener.m
     3 //  MYNetwork
     4 //
     5 //  Created by Jens Alfke on 5/10/08.
     6 //  Copyright 2008 Jens Alfke. All rights reserved.
     7 //  Portions based on TCPServer class from Apple's "CocoaEcho" sample code.
     8 
     9 #import "TCPListener.h"
    10 #import "TCPConnection.h"
    11 
    12 #import "Logging.h"
    13 #import "Test.h"
    14 #import "ExceptionUtils.h"
    15 #import "IPAddress.h"
    16 
    17 #include <sys/socket.h>
    18 #include <netinet/in.h>
    19 #include <unistd.h>
    20 
    21 
    22 static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, 
    23                                       CFDataRef address, const void *data, void *info);
    24 
    25 @interface TCPListener()
    26 - (void) _openBonjour;
    27 - (void) _closeBonjour;
    28 @property BOOL bonjourPublished;
    29 @property NSInteger bonjourError;
    30 - (void) _updateTXTRecord;
    31 @end
    32 
    33 
    34 @implementation TCPListener
    35 
    36 
    37 - (id) initWithPort: (UInt16)port
    38 {
    39     self = [super init];
    40     if (self != nil) {
    41         _port = port;
    42     }
    43     return self;
    44 }
    45 
    46 
    47 - (void) dealloc 
    48 {
    49     [self close];
    50     LogTo(TCP,@"DEALLOC %@",self);
    51     [super dealloc];
    52 }
    53 
    54 
    55 @synthesize port=_port, useIPv6=_useIPv6,
    56             bonjourServiceType=_bonjourServiceType, bonjourServiceOptions=_bonjourServiceOptions,
    57             bonjourPublished=_bonjourPublished, bonjourError=_bonjourError,
    58             bonjourService=_netService,
    59             pickAvailablePort=_pickAvailablePort;
    60 
    61 
    62 - (id<TCPListenerDelegate>) delegate                      {return _delegate;}
    63 - (void) setDelegate: (id<TCPListenerDelegate>) delegate  {_delegate = delegate;}
    64 
    65 
    66 - (NSString*) description
    67 {
    68     return $sprintf(@"%@[port %hu]",self.class,_port);
    69 }
    70 
    71 
    72 // Stores the last error from CFSocketCreate or CFSocketSetAddress into *outError.
    73 static void* getLastCFSocketError( NSError **outError ) {
    74     if( outError )
    75         *outError = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil];
    76     return NULL;
    77 }
    78 
    79 // Closes a socket (if it's not already NULL), and returns NULL to assign to it.
    80 static CFSocketRef closeSocket( CFSocketRef socket ) {
    81     if( socket ) {
    82         CFSocketInvalidate(socket);
    83         CFRelease(socket);
    84     }
    85     return NULL;
    86 }
    87 
    88 // opens a socket of a given protocol, either ipv4 or ipv6.
    89 - (CFSocketRef) _openProtocol: (SInt32) protocolFamily 
    90                       address: (struct sockaddr*)address
    91                         error: (NSError**)error
    92 {
    93     CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
    94     CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP,
    95                                         kCFSocketAcceptCallBack, &TCPListenerAcceptCallBack, &socketCtxt);
    96     if( ! socket ) 
    97         return getLastCFSocketError(error);   // CFSocketCreate leaves error code in errno
    98     
    99     int yes = 1;
   100     setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
   101     
   102     NSData *addressData = [NSData dataWithBytes:address length:address->sa_len];
   103     if (kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)addressData)) {
   104         getLastCFSocketError(error);
   105         return closeSocket(socket);
   106     }
   107     // set up the run loop source for the socket
   108     CFRunLoopRef cfrl = CFRunLoopGetCurrent();
   109     CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
   110     CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
   111     CFRelease(source);
   112     return socket;
   113 }
   114 
   115 - (BOOL) _failedToOpen: (NSError*)error
   116 {
   117     LogTo(TCP,@"%@ failed to open: %@",self,error);
   118     [self tellDelegate: @selector(listener:failedToOpen:) withObject: error];
   119     return NO;
   120 }
   121 
   122 
   123 - (BOOL) open: (NSError**)outError 
   124 {
   125     // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us
   126     do{
   127         struct sockaddr_in addr4;
   128         memset(&addr4, 0, sizeof(addr4));
   129         addr4.sin_len = sizeof(addr4);
   130         addr4.sin_family = AF_INET;
   131         addr4.sin_port = htons(_port);
   132         addr4.sin_addr.s_addr = htonl(INADDR_ANY);
   133 
   134         NSError *error;
   135         _ipv4socket = [self _openProtocol: PF_INET address: (struct sockaddr*)&addr4 error: &error];
   136         if( ! _ipv4socket ) {
   137             if( error.code==EADDRINUSE && _pickAvailablePort && _port<0xFFFF ) {
   138                 LogTo(BLIPVerbose,@"%@: port busy, trying %hu...",self,_port+1);
   139                 self.port += 1;        // try the next port
   140             } else {
   141                 if( outError ) *outError = error;
   142                 return [self _failedToOpen: error];
   143             }
   144         }
   145     }while( ! _ipv4socket );
   146     
   147     if (0 == _port) {
   148         // now that the binding was successful, we get the port number 
   149         NSData *addr = [NSMakeCollectable( CFSocketCopyAddress(_ipv4socket) ) autorelease];
   150         const struct sockaddr_in *addr4 = addr.bytes;
   151         self.port = ntohs(addr4->sin_port);
   152     }
   153     
   154     if( _useIPv6 ) {
   155         // set up the IPv6 endpoint
   156         struct sockaddr_in6 addr6;
   157         memset(&addr6, 0, sizeof(addr6));
   158         addr6.sin6_len = sizeof(addr6);
   159         addr6.sin6_family = AF_INET6;
   160         addr6.sin6_port = htons(_port);
   161         memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));
   162         
   163         NSError *error;
   164         _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: &error];
   165         if( ! _ipv6socket ) {
   166             _ipv4socket = closeSocket(_ipv4socket);
   167             [self _failedToOpen: error];
   168             if (outError) *outError = error;
   169             return NO;
   170         }
   171     }
   172     
   173     [self _openBonjour];
   174 
   175     LogTo(TCP,@"%@ is open",self);
   176     [self tellDelegate: @selector(listenerDidOpen:) withObject: nil];
   177     return YES;
   178 }
   179 
   180 - (BOOL) open
   181 {
   182     return [self open: nil];
   183 }
   184     
   185 
   186 - (void) close 
   187 {
   188     if( _ipv4socket ) {
   189         [self _closeBonjour];
   190         _ipv4socket = closeSocket(_ipv4socket);
   191         _ipv6socket = closeSocket(_ipv6socket);
   192 
   193         LogTo(BLIP,@"%@ is closed",self);
   194         [self tellDelegate: @selector(listenerDidClose:) withObject: nil];
   195     }
   196 }
   197 
   198 
   199 - (BOOL) isOpen
   200 {
   201     return _ipv4socket != NULL;
   202 }
   203 
   204 
   205 #pragma mark -
   206 #pragma mark ACCEPTING CONNECTIONS:
   207 
   208 
   209 @synthesize connectionClass = _connectionClass;
   210 
   211 
   212 - (BOOL) acceptConnection: (CFSocketNativeHandle)socket
   213 {
   214     IPAddress *addr = [IPAddress addressOfSocket: socket];
   215     if( ! addr )
   216         return NO;
   217     if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)]
   218        && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] )
   219         return NO;
   220     
   221     Assert(_connectionClass);
   222     TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket
   223                                                                       listener: self];
   224     if( ! conn )
   225         return NO;
   226     
   227     if( _sslProperties ) {
   228         conn.SSLProperties = _sslProperties;
   229         [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer];
   230     }
   231     [conn open];
   232     [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn];
   233     return YES;
   234 }
   235 
   236 
   237 static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) 
   238 {
   239     TCPListener *server = (TCPListener *)info;
   240     if (kCFSocketAcceptCallBack == type) { 
   241         CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
   242         BOOL accepted = NO;
   243         @try{
   244             accepted = [server acceptConnection: nativeSocketHandle];
   245         }catchAndReport(@"TCPListenerAcceptCallBack");
   246         if( ! accepted )
   247             close(nativeSocketHandle);
   248     }
   249 }
   250 
   251 
   252 #pragma mark -
   253 #pragma mark BONJOUR:
   254 
   255 
   256 - (void) _openBonjour
   257 {
   258     if( self.isOpen && _bonjourServiceType && !_netService) {
   259         // instantiate the NSNetService object that will advertise on our behalf.
   260         _netService = [[NSNetService alloc] initWithDomain: @"local." 
   261                                                       type: _bonjourServiceType
   262                                                       name: _bonjourServiceName ?:@""
   263                                                       port: _port];
   264         if( _netService ) {
   265             [_netService setDelegate:self];
   266             if( _bonjourTXTRecord )
   267                 [self _updateTXTRecord];
   268             [_netService publishWithOptions: _bonjourServiceOptions];
   269         } else {
   270             self.bonjourError = -1;
   271             Warn(@"%@: Failed to create NSNetService",self);
   272         }
   273     }
   274 }
   275 
   276 - (void) _closeBonjour
   277 {
   278     if( _netService ) {
   279         [_netService stop];
   280         [_netService release];
   281         _netService = nil;
   282         self.bonjourPublished = NO;
   283     }
   284     if( self.bonjourError )
   285         self.bonjourError = 0;
   286 }
   287 
   288 
   289 - (NSString*) bonjourServiceName {return _bonjourServiceName;}
   290 
   291 - (void) setBonjourServiceName: (NSString*)name
   292 {
   293     if( ! $equal(name,_bonjourServiceName) ) {
   294         [self _closeBonjour];
   295         setObj(&_bonjourServiceName,name);
   296         [self _openBonjour];
   297     }
   298 }
   299 
   300 
   301 - (NSDictionary*) bonjourTXTRecord
   302 {
   303     return _bonjourTXTRecord;
   304 }
   305 
   306 - (void) setBonjourTXTRecord: (NSDictionary*)txt
   307 {
   308     if( ifSetObj(&_bonjourTXTRecord,txt) )
   309         [self _updateTXTRecord];
   310 }
   311 
   312 - (void) _updateTXTRecord
   313 {
   314     if( _netService ) {
   315         NSData *data;
   316         if( _bonjourTXTRecord ) {
   317             data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord];
   318             if( data )
   319                 LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length);
   320             else
   321                 Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord);
   322         } else
   323             data = nil;
   324         [_netService setTXTRecordData: data];
   325     }
   326 }
   327 
   328 
   329 - (void)netServiceWillPublish:(NSNetService *)sender
   330 {
   331     LogTo(BLIP,@"%@: Advertising %@",self,sender);
   332     self.bonjourPublished = YES;
   333 }
   334 
   335 - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
   336 {
   337     self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue];
   338     LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError);
   339     [_netService release];
   340     _netService = nil;
   341 }
   342 
   343 - (void)netServiceDidStop:(NSNetService *)sender
   344 {
   345     LogTo(BLIP,@"%@: Stopped advertising %@",self,sender);
   346     self.bonjourPublished = NO;
   347     [_netService release];
   348     _netService = nil;
   349 }
   350 
   351 
   352 @end
   353 
   354 
   355 
   356 /*
   357  Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   358  
   359  Redistribution and use in source and binary forms, with or without modification, are permitted
   360  provided that the following conditions are met:
   361  
   362  * Redistributions of source code must retain the above copyright notice, this list of conditions
   363  and the following disclaimer.
   364  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   365  and the following disclaimer in the documentation and/or other materials provided with the
   366  distribution.
   367  
   368  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   369  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   370  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   371  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   372  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   373   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   374  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   375  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   376  */