PortMapper/MYPortMapper.m
author Jens Alfke <jens@mooseyard.com>
Fri Apr 24 10:10:32 2009 -0700 (2009-04-24)
changeset 27 92581f26073e
parent 26 cb9cdf247239
child 28 732576fa8a0d
permissions -rw-r--r--
* Refactored MYPortMapper to use a new abstract base class MYDNSService; that way I can re-use it later for implementing Bonjour.
* Fixed issue #1: a memory leak in BLIPProperties, reported by codechemist.
     1 //
     2 //  MYPortMapper.m
     3 //  MYNetwork
     4 //
     5 //  Created by Jens Alfke on 1/4/08.
     6 //  Copyright 2008 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "MYPortMapper.h"
    10 #import "IPAddress.h"
    11 #import "CollectionUtils.h"
    12 #import "Logging.h"
    13 #import "ExceptionUtils.h"
    14 
    15 #import <dns_sd.h>
    16 
    17 
    18 NSString* const MYPortMapperChangedNotification = @"MYPortMapperChanged";
    19 
    20 
    21 @interface MYPortMapper ()
    22 @property (retain) IPAddress* publicAddress, *localAddress; // redeclare as settable
    23 - (void) priv_updateLocalAddress;
    24 @end
    25 
    26 
    27 @implementation MYPortMapper
    28 
    29 
    30 - (id) initWithLocalPort: (UInt16)localPort
    31 {
    32     self = [super init];
    33     if (self != nil) {
    34         _localPort = localPort;
    35         _mapTCP = YES;
    36         [self priv_updateLocalAddress];
    37     }
    38     return self;
    39 }
    40 
    41 - (id) initWithNullMapping
    42 {
    43     // A PortMapper with no port or protocols will cause the DNSService to look up 
    44     // our public address without creating a mapping.
    45     if ([self initWithLocalPort: 0]) {
    46         _mapTCP = _mapUDP = NO;
    47     }
    48     return self;
    49 }
    50 
    51 
    52 - (void) dealloc
    53 {
    54     [_publicAddress release];
    55     [_localAddress release];
    56     [super dealloc];
    57 }
    58 
    59 
    60 @synthesize localAddress=_localAddress, publicAddress=_publicAddress,
    61             mapTCP=_mapTCP, mapUDP=_mapUDP,
    62             desiredPublicPort=_desiredPublicPort;
    63 
    64 
    65 - (BOOL) isMapped
    66 {
    67     return ! $equal(_publicAddress,_localAddress);
    68 }
    69 
    70 - (void) priv_updateLocalAddress 
    71 {
    72     IPAddress *localAddress = [IPAddress localAddressWithPort: _localPort];
    73     if (!$equal(localAddress,_localAddress))
    74         self.localAddress = localAddress;
    75 }
    76 
    77 
    78 static IPAddress* makeIPAddr( UInt32 rawAddr, UInt16 port ) {
    79     if (rawAddr)
    80         return [[[IPAddress alloc] initWithIPv4: rawAddr port: port] autorelease];
    81     else
    82         return nil;
    83 }
    84 
    85 /** Called whenever the port mapping changes (see comment for callback, below.) */
    86 - (void) priv_portMapStatus: (DNSServiceErrorType)errorCode 
    87               publicAddress: (UInt32)rawPublicAddress
    88                  publicPort: (UInt16)publicPort
    89 {
    90     LogTo(PortMapper,@"Callback got err %i, addr %08X:%hu",
    91           errorCode, rawPublicAddress, publicPort);
    92     if( errorCode==kDNSServiceErr_NoError ) {
    93         if( rawPublicAddress==0 || (publicPort==0 && (_mapTCP || _mapUDP)) ) {
    94             LogTo(PortMapper,@"(Callback reported no mapping available)");
    95             errorCode = kDNSServiceErr_NATPortMappingUnsupported;
    96         }
    97     }
    98     if( errorCode != self.error )
    99         self.error = errorCode;
   100 
   101     [self priv_updateLocalAddress];
   102     IPAddress *publicAddress = makeIPAddr(rawPublicAddress,publicPort);
   103     if (!$equal(publicAddress,_publicAddress))
   104         self.publicAddress = publicAddress;
   105     
   106     if( ! errorCode ) {
   107         LogTo(PortMapper,@"Callback got %08X:%hu -> %@ (mapped=%i)",
   108               rawPublicAddress,publicPort, self.publicAddress, self.isMapped);
   109     }
   110     [[NSNotificationCenter defaultCenter] postNotificationName: MYPortMapperChangedNotification
   111                                                         object: self];
   112 }
   113 
   114 
   115 /** Asynchronous callback from DNSServiceNATPortMappingCreate.
   116     This is invoked whenever the status of the port mapping changes.
   117     All it does is dispatch to the object's priv_portMapStatus:publicAddress:publicPort: method. */
   118 static void portMapCallback (
   119                       DNSServiceRef                    sdRef,
   120                       DNSServiceFlags                  flags,
   121                       uint32_t                         interfaceIndex,
   122                       DNSServiceErrorType              errorCode,
   123                       uint32_t                         publicAddress,    /* four byte IPv4 address in network byte order */
   124                       DNSServiceProtocol               protocol,
   125                       uint16_t                         privatePort,
   126                       uint16_t                         publicPort,       /* may be different than the requested port */
   127                       uint32_t                         ttl,              /* may be different than the requested ttl */
   128                       void                             *context
   129                       )
   130 {
   131     NSAutoreleasePool *pool = [NSAutoreleasePool new];
   132     @try{
   133         [(MYPortMapper*)context priv_portMapStatus: errorCode 
   134                                      publicAddress: publicAddress
   135                                         publicPort: ntohs(publicPort)];  // port #s in network byte order!
   136     }catchAndReport(@"PortMapper");
   137     [pool drain];
   138 }
   139 
   140 
   141 - (DNSServiceRef) createServiceRef
   142 {
   143     DNSServiceProtocol protocols = 0;
   144     if( _mapTCP ) protocols |= kDNSServiceProtocol_TCP;
   145     if( _mapUDP ) protocols |= kDNSServiceProtocol_UDP;
   146     DNSServiceRef serviceRef = NULL;
   147     self.error = DNSServiceNATPortMappingCreate(&serviceRef, 
   148                                                 0 /*flags*/, 
   149                                                 0 /*interfaceIndex*/, 
   150                                                 protocols,
   151                                                 htons(_localPort),
   152                                                 htons(_desiredPublicPort),
   153                                                 0 /*ttl*/,
   154                                                 &portMapCallback, 
   155                                                 self);
   156     return serviceRef;
   157 }
   158 
   159 
   160 - (void) stopService
   161 {
   162     [super stopService];
   163     if (_publicAddress)
   164         self.publicAddress = nil;
   165 }
   166 
   167 
   168 - (BOOL) waitTillOpened
   169 {
   170     if( ! self.serviceRef )
   171         if( ! [self open] )
   172             return NO;
   173     // Run the runloop until there's either an error or a result:
   174     while( self.error==0 && _publicAddress==nil )
   175         if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
   176                                        beforeDate: [NSDate distantFuture]] )
   177             break;
   178     return (self.error==0);
   179 }
   180 
   181 
   182 + (IPAddress*) findPublicAddress
   183 {
   184     IPAddress *addr = nil;
   185     MYPortMapper *mapper = [[self alloc] initWithNullMapping];
   186     if( [mapper waitTillOpened] )
   187         addr = [mapper.publicAddress retain];
   188     [mapper close];
   189     [mapper release];
   190     return [addr autorelease];
   191 }
   192 
   193 
   194 @end
   195 
   196 
   197 /*
   198  Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   199  
   200  Redistribution and use in source and binary forms, with or without modification, are permitted
   201  provided that the following conditions are met:
   202  
   203  * Redistributions of source code must retain the above copyright notice, this list of conditions
   204  and the following disclaimer.
   205  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   206  and the following disclaimer in the documentation and/or other materials provided with the
   207  distribution.
   208  
   209  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   210  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   211  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   212  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   213  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   214   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   215  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   216  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   217  */