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.
9 #import "TCPListener.h"
10 #import "TCPConnection.h"
14 #import "ExceptionUtils.h"
17 #include <sys/socket.h>
18 #include <netinet/in.h>
22 static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type,
23 CFDataRef address, const void *data, void *info);
25 @interface TCPListener()
26 - (void) _openBonjour;
27 - (void) _closeBonjour;
28 @property BOOL bonjourPublished;
29 @property NSInteger bonjourError;
30 - (void) _updateTXTRecord;
34 @implementation TCPListener
37 - (id) initWithPort: (UInt16)port
50 LogTo(TCP,@"DEALLOC %@",self);
55 @synthesize delegate=_delegate, port=_port, useIPv6=_useIPv6,
56 bonjourServiceType=_bonjourServiceType,
57 bonjourPublished=_bonjourPublished, bonjourError=_bonjourError,
58 bonjourService=_netService,
59 pickAvailablePort=_pickAvailablePort;
62 - (NSString*) description
64 return $sprintf(@"%@[port %hu]",self.class,_port);
68 // Stores the last error from CFSocketCreate or CFSocketSetAddress into *outError.
69 static void* getLastCFSocketError( NSError **outError ) {
71 *outError = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil];
75 // Closes a socket (if it's not already NULL), and returns NULL to assign to it.
76 static CFSocketRef closeSocket( CFSocketRef socket ) {
78 CFSocketInvalidate(socket);
84 // opens a socket of a given protocol, either ipv4 or ipv6.
85 - (CFSocketRef) _openProtocol: (SInt32) protocolFamily
86 address: (struct sockaddr*)address
87 error: (NSError**)error
89 CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
90 CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP,
91 kCFSocketAcceptCallBack, &TCPListenerAcceptCallBack, &socketCtxt);
93 return getLastCFSocketError(error); // CFSocketCreate leaves error code in errno
96 setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
98 NSData *addressData = [NSData dataWithBytes:address length:address->sa_len];
99 if (kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)addressData)) {
100 getLastCFSocketError(error);
101 return closeSocket(socket);
103 // set up the run loop source for the socket
104 CFRunLoopRef cfrl = CFRunLoopGetCurrent();
105 CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
106 CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
111 - (BOOL) _failedToOpen: (NSError*)error
113 LogTo(TCP,@"%@ failed to open: %@",self,error);
114 [self tellDelegate: @selector(listener:failedToOpen:) withObject: error];
119 - (BOOL) open: (NSError**)outError
121 // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us
123 struct sockaddr_in addr4;
124 memset(&addr4, 0, sizeof(addr4));
125 addr4.sin_len = sizeof(addr4);
126 addr4.sin_family = AF_INET;
127 addr4.sin_port = htons(_port);
128 addr4.sin_addr.s_addr = htonl(INADDR_ANY);
131 _ipv4socket = [self _openProtocol: PF_INET address: (struct sockaddr*)&addr4 error: &error];
132 if( ! _ipv4socket ) {
133 if( error.code==EADDRINUSE && _pickAvailablePort && _port<0xFFFF ) {
134 LogTo(BLIPVerbose,@"%@: port busy, trying %hu...",self,_port+1);
135 self.port += 1; // try the next port
137 if( outError ) *outError = error;
138 return [self _failedToOpen: error];
141 }while( ! _ipv4socket );
144 // now that the binding was successful, we get the port number
145 NSData *addr = [(NSData *)CFSocketCopyAddress(_ipv4socket) autorelease];
146 const struct sockaddr_in *addr4 = addr.bytes;
147 self.port = ntohs(addr4->sin_port);
151 // set up the IPv6 endpoint
152 struct sockaddr_in6 addr6;
153 memset(&addr6, 0, sizeof(addr6));
154 addr6.sin6_len = sizeof(addr6);
155 addr6.sin6_family = AF_INET6;
156 addr6.sin6_port = htons(_port);
157 memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));
160 _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: &error];
161 if( ! _ipv6socket ) {
162 _ipv4socket = closeSocket(_ipv4socket);
163 [self _failedToOpen: error];
164 if (outError) *outError = error;
171 LogTo(TCP,@"%@ is open",self);
172 [self tellDelegate: @selector(listenerDidOpen:) withObject: nil];
178 return [self open: nil];
185 [self _closeBonjour];
186 _ipv4socket = closeSocket(_ipv4socket);
187 _ipv6socket = closeSocket(_ipv6socket);
189 LogTo(BLIP,@"%@ is closed",self);
190 [self tellDelegate: @selector(listenerDidClose:) withObject: nil];
197 return _ipv4socket != NULL;
202 #pragma mark ACCEPTING CONNECTIONS:
205 @synthesize connectionClass = _connectionClass;
208 - (BOOL) acceptConnection: (CFSocketNativeHandle)socket
210 IPAddress *addr = [IPAddress addressOfSocket: socket];
213 if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)]
214 && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] )
217 Assert(_connectionClass);
218 TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket
223 if( _sslProperties ) {
224 conn.SSLProperties = _sslProperties;
225 [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer];
228 [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn];
233 static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
235 TCPListener *server = (TCPListener *)info;
236 if (kCFSocketAcceptCallBack == type) {
237 CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
240 accepted = [server acceptConnection: nativeSocketHandle];
241 }catchAndReport(@"TCPListenerAcceptCallBack");
243 close(nativeSocketHandle);
249 #pragma mark BONJOUR:
252 - (void) _openBonjour
254 if( self.isOpen && _bonjourServiceType && !_netService) {
255 // instantiate the NSNetService object that will advertise on our behalf.
256 _netService = [[NSNetService alloc] initWithDomain: @"local."
257 type: _bonjourServiceType
258 name: _bonjourServiceName ?:@""
261 [_netService setDelegate:self];
262 if( _bonjourTXTRecord )
263 [self _updateTXTRecord];
264 [_netService publish];
266 self.bonjourError = -1;
267 Warn(@"%@: Failed to create NSNetService",self);
272 - (void) _closeBonjour
276 [_netService release];
278 self.bonjourPublished = NO;
280 if( self.bonjourError )
281 self.bonjourError = 0;
285 - (NSString*) bonjourServiceName {return _bonjourServiceName;}
287 - (void) setBonjourServiceName: (NSString*)name
289 if( ! $equal(name,_bonjourServiceName) ) {
290 [self _closeBonjour];
291 setObj(&_bonjourServiceName,name);
297 - (NSDictionary*) bonjourTXTRecord
299 return _bonjourTXTRecord;
302 - (void) setBonjourTXTRecord: (NSDictionary*)txt
304 if( ifSetObj(&_bonjourTXTRecord,txt) )
305 [self _updateTXTRecord];
308 - (void) _updateTXTRecord
312 if( _bonjourTXTRecord ) {
313 data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord];
315 LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length);
317 Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord);
320 [_netService setTXTRecordData: data];
325 - (void)netServiceWillPublish:(NSNetService *)sender
327 LogTo(BLIP,@"%@: Advertising %@",self,sender);
328 self.bonjourPublished = YES;
331 - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
333 self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue];
334 LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError);
335 [_netService release];
339 - (void)netServiceDidStop:(NSNetService *)sender
341 LogTo(BLIP,@"%@: Stopped advertising %@",self,sender);
342 self.bonjourPublished = NO;
343 [_netService release];
353 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
355 Redistribution and use in source and binary forms, with or without modification, are permitted
356 provided that the following conditions are met:
358 * Redistributions of source code must retain the above copyright notice, this list of conditions
359 and the following disclaimer.
360 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
361 and the following disclaimer in the documentation and/or other materials provided with the
364 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
365 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
366 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
367 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
368 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
369 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
370 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
371 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.