TCP/TCPListener.m
changeset 0 9d67172bb323
child 1 8267d5c429c4
     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 + */