jens@26
|
1 |
//
|
jens@26
|
2 |
// MYPortMapper.m
|
jens@26
|
3 |
// MYNetwork
|
jens@26
|
4 |
//
|
jens@26
|
5 |
// Created by Jens Alfke on 1/4/08.
|
jens@26
|
6 |
// Copyright 2008 Jens Alfke. All rights reserved.
|
jens@26
|
7 |
//
|
jens@26
|
8 |
|
jens@26
|
9 |
#import "MYPortMapper.h"
|
jens@26
|
10 |
#import "IPAddress.h"
|
jens@26
|
11 |
#import "CollectionUtils.h"
|
jens@26
|
12 |
#import "Logging.h"
|
jens@26
|
13 |
#import "ExceptionUtils.h"
|
jens@26
|
14 |
|
jens@26
|
15 |
#import <dns_sd.h>
|
jens@26
|
16 |
#import <sys/types.h>
|
jens@26
|
17 |
#import <sys/socket.h>
|
jens@26
|
18 |
#import <net/if.h>
|
jens@26
|
19 |
#import <netinet/in.h>
|
jens@26
|
20 |
#import <ifaddrs.h>
|
jens@26
|
21 |
|
jens@26
|
22 |
|
jens@26
|
23 |
NSString* const MYPortMapperChangedNotification = @"MYPortMapperChanged";
|
jens@26
|
24 |
|
jens@26
|
25 |
|
jens@26
|
26 |
@interface MYPortMapper ()
|
jens@26
|
27 |
// Redeclare these properties as settable, internally:
|
jens@26
|
28 |
@property (readwrite) SInt32 error;
|
jens@26
|
29 |
@property (retain) IPAddress* publicAddress, *localAddress;
|
jens@26
|
30 |
// Private getter:
|
jens@26
|
31 |
@property (readonly) void* _service;
|
jens@26
|
32 |
- (void) priv_updateLocalAddress;
|
jens@26
|
33 |
- (void) priv_disconnect;
|
jens@26
|
34 |
@end
|
jens@26
|
35 |
|
jens@26
|
36 |
|
jens@26
|
37 |
@implementation MYPortMapper
|
jens@26
|
38 |
|
jens@26
|
39 |
|
jens@26
|
40 |
- (id) initWithLocalPort: (UInt16)localPort
|
jens@26
|
41 |
{
|
jens@26
|
42 |
self = [super init];
|
jens@26
|
43 |
if (self != nil) {
|
jens@26
|
44 |
_localPort = localPort;
|
jens@26
|
45 |
_mapTCP = YES;
|
jens@26
|
46 |
[self priv_updateLocalAddress];
|
jens@26
|
47 |
}
|
jens@26
|
48 |
return self;
|
jens@26
|
49 |
}
|
jens@26
|
50 |
|
jens@26
|
51 |
- (id) initWithNullMapping
|
jens@26
|
52 |
{
|
jens@26
|
53 |
// A PortMapper with no port or protocols will cause the DNSService to look up
|
jens@26
|
54 |
// our public address without creating a mapping.
|
jens@26
|
55 |
if ([self initWithLocalPort: 0]) {
|
jens@26
|
56 |
_mapTCP = _mapUDP = NO;
|
jens@26
|
57 |
}
|
jens@26
|
58 |
return self;
|
jens@26
|
59 |
}
|
jens@26
|
60 |
|
jens@26
|
61 |
|
jens@26
|
62 |
- (void) dealloc
|
jens@26
|
63 |
{
|
jens@26
|
64 |
if( _service )
|
jens@26
|
65 |
[self priv_disconnect];
|
jens@26
|
66 |
[_publicAddress release];
|
jens@26
|
67 |
[_localAddress release];
|
jens@26
|
68 |
[super dealloc];
|
jens@26
|
69 |
}
|
jens@26
|
70 |
|
jens@26
|
71 |
- (void) finalize
|
jens@26
|
72 |
{
|
jens@26
|
73 |
if( _service )
|
jens@26
|
74 |
[self priv_disconnect];
|
jens@26
|
75 |
[super finalize];
|
jens@26
|
76 |
}
|
jens@26
|
77 |
|
jens@26
|
78 |
|
jens@26
|
79 |
@synthesize localAddress=_localAddress, publicAddress=_publicAddress,
|
jens@26
|
80 |
error=_error, _service=_service,
|
jens@26
|
81 |
mapTCP=_mapTCP, mapUDP=_mapUDP,
|
jens@26
|
82 |
desiredPublicPort=_desiredPublicPort;
|
jens@26
|
83 |
|
jens@26
|
84 |
|
jens@26
|
85 |
- (BOOL) isMapped
|
jens@26
|
86 |
{
|
jens@26
|
87 |
return ! $equal(_publicAddress,_localAddress);
|
jens@26
|
88 |
}
|
jens@26
|
89 |
|
jens@26
|
90 |
- (void) priv_updateLocalAddress
|
jens@26
|
91 |
{
|
jens@26
|
92 |
IPAddress *localAddress = [IPAddress localAddressWithPort: _localPort];
|
jens@26
|
93 |
if (!$equal(localAddress,_localAddress))
|
jens@26
|
94 |
self.localAddress = localAddress;
|
jens@26
|
95 |
}
|
jens@26
|
96 |
|
jens@26
|
97 |
|
jens@26
|
98 |
static IPAddress* makeIPAddr( UInt32 rawAddr, UInt16 port ) {
|
jens@26
|
99 |
if (rawAddr)
|
jens@26
|
100 |
return [[[IPAddress alloc] initWithIPv4: rawAddr port: port] autorelease];
|
jens@26
|
101 |
else
|
jens@26
|
102 |
return nil;
|
jens@26
|
103 |
}
|
jens@26
|
104 |
|
jens@26
|
105 |
/** Called whenever the port mapping changes (see comment for callback, below.) */
|
jens@26
|
106 |
- (void) priv_portMapStatus: (DNSServiceErrorType)errorCode
|
jens@26
|
107 |
publicAddress: (UInt32)rawPublicAddress
|
jens@26
|
108 |
publicPort: (UInt16)publicPort
|
jens@26
|
109 |
{
|
jens@26
|
110 |
LogTo(PortMapper,@"Callback got err %i, addr %08X:%hu",
|
jens@26
|
111 |
errorCode, rawPublicAddress, publicPort);
|
jens@26
|
112 |
if( errorCode==kDNSServiceErr_NoError ) {
|
jens@26
|
113 |
if( rawPublicAddress==0 || (publicPort==0 && (_mapTCP || _mapUDP)) ) {
|
jens@26
|
114 |
LogTo(PortMapper,@"(Callback reported no mapping available)");
|
jens@26
|
115 |
errorCode = kDNSServiceErr_NATPortMappingUnsupported;
|
jens@26
|
116 |
}
|
jens@26
|
117 |
}
|
jens@26
|
118 |
if( errorCode != self.error )
|
jens@26
|
119 |
self.error = errorCode;
|
jens@26
|
120 |
|
jens@26
|
121 |
[self priv_updateLocalAddress];
|
jens@26
|
122 |
IPAddress *publicAddress = makeIPAddr(rawPublicAddress,publicPort);
|
jens@26
|
123 |
if (!$equal(publicAddress,_publicAddress))
|
jens@26
|
124 |
self.publicAddress = publicAddress;
|
jens@26
|
125 |
|
jens@26
|
126 |
if( ! errorCode ) {
|
jens@26
|
127 |
LogTo(PortMapper,@"Callback got %08X:%hu -> %@ (mapped=%i)",
|
jens@26
|
128 |
rawPublicAddress,publicPort, self.publicAddress, self.isMapped);
|
jens@26
|
129 |
}
|
jens@26
|
130 |
[[NSNotificationCenter defaultCenter] postNotificationName: MYPortMapperChangedNotification
|
jens@26
|
131 |
object: self];
|
jens@26
|
132 |
}
|
jens@26
|
133 |
|
jens@26
|
134 |
|
jens@26
|
135 |
/** Asynchronous callback from DNSServiceNATPortMappingCreate.
|
jens@26
|
136 |
This is invoked whenever the status of the port mapping changes.
|
jens@26
|
137 |
All it does is dispatch to the object's priv_portMapStatus:publicAddress:publicPort: method. */
|
jens@26
|
138 |
static void portMapCallback (
|
jens@26
|
139 |
DNSServiceRef sdRef,
|
jens@26
|
140 |
DNSServiceFlags flags,
|
jens@26
|
141 |
uint32_t interfaceIndex,
|
jens@26
|
142 |
DNSServiceErrorType errorCode,
|
jens@26
|
143 |
uint32_t publicAddress, /* four byte IPv4 address in network byte order */
|
jens@26
|
144 |
DNSServiceProtocol protocol,
|
jens@26
|
145 |
uint16_t privatePort,
|
jens@26
|
146 |
uint16_t publicPort, /* may be different than the requested port */
|
jens@26
|
147 |
uint32_t ttl, /* may be different than the requested ttl */
|
jens@26
|
148 |
void *context
|
jens@26
|
149 |
)
|
jens@26
|
150 |
{
|
jens@26
|
151 |
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
jens@26
|
152 |
@try{
|
jens@26
|
153 |
[(MYPortMapper*)context priv_portMapStatus: errorCode
|
jens@26
|
154 |
publicAddress: publicAddress
|
jens@26
|
155 |
publicPort: ntohs(publicPort)]; // port #s in network byte order!
|
jens@26
|
156 |
}catchAndReport(@"PortMapper");
|
jens@26
|
157 |
[pool drain];
|
jens@26
|
158 |
}
|
jens@26
|
159 |
|
jens@26
|
160 |
|
jens@26
|
161 |
/** CFSocket callback, informing us that _socket has data available, which means
|
jens@26
|
162 |
that the DNS service has an incoming result to be processed. This will end up invoking
|
jens@26
|
163 |
the portMapCallback. */
|
jens@26
|
164 |
static void serviceCallback(CFSocketRef s,
|
jens@26
|
165 |
CFSocketCallBackType type,
|
jens@26
|
166 |
CFDataRef address, const void *data, void *clientCallBackInfo)
|
jens@26
|
167 |
{
|
jens@26
|
168 |
MYPortMapper *mapper = (MYPortMapper*)clientCallBackInfo;
|
jens@26
|
169 |
DNSServiceRef service = mapper._service;
|
jens@26
|
170 |
DNSServiceErrorType err = DNSServiceProcessResult(service);
|
jens@26
|
171 |
if( err ) {
|
jens@26
|
172 |
// An error here means the socket has failed and should be closed.
|
jens@26
|
173 |
[mapper priv_portMapStatus: err publicAddress: 0 publicPort: 0];
|
jens@26
|
174 |
[mapper priv_disconnect];
|
jens@26
|
175 |
}
|
jens@26
|
176 |
}
|
jens@26
|
177 |
|
jens@26
|
178 |
|
jens@26
|
179 |
|
jens@26
|
180 |
- (BOOL) open
|
jens@26
|
181 |
{
|
jens@26
|
182 |
NSAssert(!_service,@"Already open");
|
jens@26
|
183 |
// Create the DNSService:
|
jens@26
|
184 |
DNSServiceProtocol protocols = 0;
|
jens@26
|
185 |
if( _mapTCP ) protocols |= kDNSServiceProtocol_TCP;
|
jens@26
|
186 |
if( _mapUDP ) protocols |= kDNSServiceProtocol_UDP;
|
jens@26
|
187 |
self.error = DNSServiceNATPortMappingCreate((DNSServiceRef*)&_service,
|
jens@26
|
188 |
0 /*flags*/,
|
jens@26
|
189 |
0 /*interfaceIndex*/,
|
jens@26
|
190 |
protocols,
|
jens@26
|
191 |
htons(_localPort),
|
jens@26
|
192 |
htons(_desiredPublicPort),
|
jens@26
|
193 |
0 /*ttl*/,
|
jens@26
|
194 |
&portMapCallback,
|
jens@26
|
195 |
self);
|
jens@26
|
196 |
if( _error ) {
|
jens@26
|
197 |
LogTo(PortMapper,@"Error %i creating port mapping",_error);
|
jens@26
|
198 |
return NO;
|
jens@26
|
199 |
}
|
jens@26
|
200 |
|
jens@26
|
201 |
// Wrap a CFSocket around the service's socket:
|
jens@26
|
202 |
CFSocketContext ctxt = { 0, self, CFRetain, CFRelease, NULL };
|
jens@26
|
203 |
_socket = CFSocketCreateWithNative(NULL,
|
jens@26
|
204 |
DNSServiceRefSockFD(_service),
|
jens@26
|
205 |
kCFSocketReadCallBack,
|
jens@26
|
206 |
&serviceCallback, &ctxt);
|
jens@26
|
207 |
if( _socket ) {
|
jens@26
|
208 |
CFSocketSetSocketFlags(_socket, CFSocketGetSocketFlags(_socket) & ~kCFSocketCloseOnInvalidate);
|
jens@26
|
209 |
// Attach the socket to the runloop so the serviceCallback will be invoked:
|
jens@26
|
210 |
_socketSource = CFSocketCreateRunLoopSource(NULL, _socket, 0);
|
jens@26
|
211 |
if( _socketSource )
|
jens@26
|
212 |
CFRunLoopAddSource(CFRunLoopGetCurrent(), _socketSource, kCFRunLoopCommonModes);
|
jens@26
|
213 |
}
|
jens@26
|
214 |
if( _socketSource ) {
|
jens@26
|
215 |
LogTo(PortMapper,@"Opening");
|
jens@26
|
216 |
return YES;
|
jens@26
|
217 |
} else {
|
jens@26
|
218 |
Warn(@"Failed to open PortMapper");
|
jens@26
|
219 |
[self close];
|
jens@26
|
220 |
_error = kDNSServiceErr_Unknown;
|
jens@26
|
221 |
return NO;
|
jens@26
|
222 |
}
|
jens@26
|
223 |
}
|
jens@26
|
224 |
|
jens@26
|
225 |
|
jens@26
|
226 |
- (BOOL) waitTillOpened
|
jens@26
|
227 |
{
|
jens@26
|
228 |
if( ! _socketSource )
|
jens@26
|
229 |
if( ! [self open] )
|
jens@26
|
230 |
return NO;
|
jens@26
|
231 |
// Run the runloop until there's either an error or a result:
|
jens@26
|
232 |
while( _error==0 && _publicAddress==nil )
|
jens@26
|
233 |
if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
|
jens@26
|
234 |
beforeDate: [NSDate distantFuture]] )
|
jens@26
|
235 |
break;
|
jens@26
|
236 |
return (_error==0);
|
jens@26
|
237 |
}
|
jens@26
|
238 |
|
jens@26
|
239 |
|
jens@26
|
240 |
// Close down, but _without_ clearing the 'error' property
|
jens@26
|
241 |
- (void) priv_disconnect
|
jens@26
|
242 |
{
|
jens@26
|
243 |
if( _socketSource ) {
|
jens@26
|
244 |
CFRunLoopSourceInvalidate(_socketSource);
|
jens@26
|
245 |
CFRelease(_socketSource);
|
jens@26
|
246 |
_socketSource = NULL;
|
jens@26
|
247 |
}
|
jens@26
|
248 |
if( _socket ) {
|
jens@26
|
249 |
CFSocketInvalidate(_socket);
|
jens@26
|
250 |
CFRelease(_socket);
|
jens@26
|
251 |
_socket = NULL;
|
jens@26
|
252 |
}
|
jens@26
|
253 |
if( _service ) {
|
jens@26
|
254 |
LogTo(PortMapper,@"Deleting port mapping");
|
jens@26
|
255 |
DNSServiceRefDeallocate(_service);
|
jens@26
|
256 |
_service = NULL;
|
jens@26
|
257 |
self.publicAddress = nil;
|
jens@26
|
258 |
}
|
jens@26
|
259 |
}
|
jens@26
|
260 |
|
jens@26
|
261 |
- (void) close
|
jens@26
|
262 |
{
|
jens@26
|
263 |
[self priv_disconnect];
|
jens@26
|
264 |
self.error = 0;
|
jens@26
|
265 |
}
|
jens@26
|
266 |
|
jens@26
|
267 |
|
jens@26
|
268 |
+ (IPAddress*) findPublicAddress
|
jens@26
|
269 |
{
|
jens@26
|
270 |
IPAddress *addr = nil;
|
jens@26
|
271 |
MYPortMapper *mapper = [[self alloc] initWithNullMapping];
|
jens@26
|
272 |
if( [mapper waitTillOpened] )
|
jens@26
|
273 |
addr = [mapper.publicAddress retain];
|
jens@26
|
274 |
[mapper close];
|
jens@26
|
275 |
[mapper release];
|
jens@26
|
276 |
return [addr autorelease];
|
jens@26
|
277 |
}
|
jens@26
|
278 |
|
jens@26
|
279 |
|
jens@26
|
280 |
@end
|
jens@26
|
281 |
|
jens@26
|
282 |
|
jens@26
|
283 |
/*
|
jens@26
|
284 |
Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
|
jens@26
|
285 |
|
jens@26
|
286 |
Redistribution and use in source and binary forms, with or without modification, are permitted
|
jens@26
|
287 |
provided that the following conditions are met:
|
jens@26
|
288 |
|
jens@26
|
289 |
* Redistributions of source code must retain the above copyright notice, this list of conditions
|
jens@26
|
290 |
and the following disclaimer.
|
jens@26
|
291 |
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions
|
jens@26
|
292 |
and the following disclaimer in the documentation and/or other materials provided with the
|
jens@26
|
293 |
distribution.
|
jens@26
|
294 |
|
jens@26
|
295 |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
jens@26
|
296 |
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
jens@26
|
297 |
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
|
jens@26
|
298 |
BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
jens@26
|
299 |
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
jens@26
|
300 |
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
jens@26
|
301 |
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
jens@26
|
302 |
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
jens@26
|
303 |
*/
|