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