* Added MYBonjourBrowser and MYBonjourService.
* Added MYPortMapper.
* Added -[TCPEndpoint setPeerToPeerIdentity:].
* Created a static-library target.
5 // Created by Jens Alfke on 1/4/08.
6 // Copyright 2008 Jens Alfke. All rights reserved.
9 #import "MYPortMapper.h"
11 #import "CollectionUtils.h"
13 #import "ExceptionUtils.h"
17 #import <sys/socket.h>
19 #import <netinet/in.h>
23 NSString* const MYPortMapperChangedNotification = @"MYPortMapperChanged";
26 @interface MYPortMapper ()
27 // Redeclare these properties as settable, internally:
28 @property (readwrite) SInt32 error;
29 @property (retain) IPAddress* publicAddress, *localAddress;
31 @property (readonly) void* _service;
32 - (void) priv_updateLocalAddress;
33 - (void) priv_disconnect;
37 @implementation MYPortMapper
40 - (id) initWithLocalPort: (UInt16)localPort
44 _localPort = localPort;
46 [self priv_updateLocalAddress];
51 - (id) initWithNullMapping
53 // A PortMapper with no port or protocols will cause the DNSService to look up
54 // our public address without creating a mapping.
55 if ([self initWithLocalPort: 0]) {
56 _mapTCP = _mapUDP = NO;
65 [self priv_disconnect];
66 [_publicAddress release];
67 [_localAddress release];
74 [self priv_disconnect];
79 @synthesize localAddress=_localAddress, publicAddress=_publicAddress,
80 error=_error, _service=_service,
81 mapTCP=_mapTCP, mapUDP=_mapUDP,
82 desiredPublicPort=_desiredPublicPort;
87 return ! $equal(_publicAddress,_localAddress);
90 - (void) priv_updateLocalAddress
92 IPAddress *localAddress = [IPAddress localAddressWithPort: _localPort];
93 if (!$equal(localAddress,_localAddress))
94 self.localAddress = localAddress;
98 static IPAddress* makeIPAddr( UInt32 rawAddr, UInt16 port ) {
100 return [[[IPAddress alloc] initWithIPv4: rawAddr port: port] autorelease];
105 /** Called whenever the port mapping changes (see comment for callback, below.) */
106 - (void) priv_portMapStatus: (DNSServiceErrorType)errorCode
107 publicAddress: (UInt32)rawPublicAddress
108 publicPort: (UInt16)publicPort
110 LogTo(PortMapper,@"Callback got err %i, addr %08X:%hu",
111 errorCode, rawPublicAddress, publicPort);
112 if( errorCode==kDNSServiceErr_NoError ) {
113 if( rawPublicAddress==0 || (publicPort==0 && (_mapTCP || _mapUDP)) ) {
114 LogTo(PortMapper,@"(Callback reported no mapping available)");
115 errorCode = kDNSServiceErr_NATPortMappingUnsupported;
118 if( errorCode != self.error )
119 self.error = errorCode;
121 [self priv_updateLocalAddress];
122 IPAddress *publicAddress = makeIPAddr(rawPublicAddress,publicPort);
123 if (!$equal(publicAddress,_publicAddress))
124 self.publicAddress = publicAddress;
127 LogTo(PortMapper,@"Callback got %08X:%hu -> %@ (mapped=%i)",
128 rawPublicAddress,publicPort, self.publicAddress, self.isMapped);
130 [[NSNotificationCenter defaultCenter] postNotificationName: MYPortMapperChangedNotification
135 /** Asynchronous callback from DNSServiceNATPortMappingCreate.
136 This is invoked whenever the status of the port mapping changes.
137 All it does is dispatch to the object's priv_portMapStatus:publicAddress:publicPort: method. */
138 static void portMapCallback (
140 DNSServiceFlags flags,
141 uint32_t interfaceIndex,
142 DNSServiceErrorType errorCode,
143 uint32_t publicAddress, /* four byte IPv4 address in network byte order */
144 DNSServiceProtocol protocol,
145 uint16_t privatePort,
146 uint16_t publicPort, /* may be different than the requested port */
147 uint32_t ttl, /* may be different than the requested ttl */
151 NSAutoreleasePool *pool = [NSAutoreleasePool new];
153 [(MYPortMapper*)context priv_portMapStatus: errorCode
154 publicAddress: publicAddress
155 publicPort: ntohs(publicPort)]; // port #s in network byte order!
156 }catchAndReport(@"PortMapper");
161 /** CFSocket callback, informing us that _socket has data available, which means
162 that the DNS service has an incoming result to be processed. This will end up invoking
163 the portMapCallback. */
164 static void serviceCallback(CFSocketRef s,
165 CFSocketCallBackType type,
166 CFDataRef address, const void *data, void *clientCallBackInfo)
168 MYPortMapper *mapper = (MYPortMapper*)clientCallBackInfo;
169 DNSServiceRef service = mapper._service;
170 DNSServiceErrorType err = DNSServiceProcessResult(service);
172 // An error here means the socket has failed and should be closed.
173 [mapper priv_portMapStatus: err publicAddress: 0 publicPort: 0];
174 [mapper priv_disconnect];
182 NSAssert(!_service,@"Already open");
183 // Create the DNSService:
184 DNSServiceProtocol protocols = 0;
185 if( _mapTCP ) protocols |= kDNSServiceProtocol_TCP;
186 if( _mapUDP ) protocols |= kDNSServiceProtocol_UDP;
187 self.error = DNSServiceNATPortMappingCreate((DNSServiceRef*)&_service,
189 0 /*interfaceIndex*/,
192 htons(_desiredPublicPort),
197 LogTo(PortMapper,@"Error %i creating port mapping",_error);
201 // Wrap a CFSocket around the service's socket:
202 CFSocketContext ctxt = { 0, self, CFRetain, CFRelease, NULL };
203 _socket = CFSocketCreateWithNative(NULL,
204 DNSServiceRefSockFD(_service),
205 kCFSocketReadCallBack,
206 &serviceCallback, &ctxt);
208 CFSocketSetSocketFlags(_socket, CFSocketGetSocketFlags(_socket) & ~kCFSocketCloseOnInvalidate);
209 // Attach the socket to the runloop so the serviceCallback will be invoked:
210 _socketSource = CFSocketCreateRunLoopSource(NULL, _socket, 0);
212 CFRunLoopAddSource(CFRunLoopGetCurrent(), _socketSource, kCFRunLoopCommonModes);
214 if( _socketSource ) {
215 LogTo(PortMapper,@"Opening");
218 Warn(@"Failed to open PortMapper");
220 _error = kDNSServiceErr_Unknown;
226 - (BOOL) waitTillOpened
228 if( ! _socketSource )
231 // Run the runloop until there's either an error or a result:
232 while( _error==0 && _publicAddress==nil )
233 if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
234 beforeDate: [NSDate distantFuture]] )
240 // Close down, but _without_ clearing the 'error' property
241 - (void) priv_disconnect
243 if( _socketSource ) {
244 CFRunLoopSourceInvalidate(_socketSource);
245 CFRelease(_socketSource);
246 _socketSource = NULL;
249 CFSocketInvalidate(_socket);
254 LogTo(PortMapper,@"Deleting port mapping");
255 DNSServiceRefDeallocate(_service);
257 self.publicAddress = nil;
263 [self priv_disconnect];
268 + (IPAddress*) findPublicAddress
270 IPAddress *addr = nil;
271 MYPortMapper *mapper = [[self alloc] initWithNullMapping];
272 if( [mapper waitTillOpened] )
273 addr = [mapper.publicAddress retain];
276 return [addr autorelease];
284 Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
286 Redistribution and use in source and binary forms, with or without modification, are permitted
287 provided that the following conditions are met:
289 * Redistributions of source code must retain the above copyright notice, this list of conditions
290 and the following disclaimer.
291 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
292 and the following disclaimer in the documentation and/or other materials provided with the
295 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
296 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
297 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
298 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
299 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
300 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
301 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
302 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.