PortMapper/MYPortMapper.m
author Jens Alfke <jens@mooseyard.com>
Mon Apr 27 09:03:56 2009 -0700 (2009-04-27)
changeset 28 732576fa8a0d
parent 27 92581f26073e
child 31 1d6924779df7
permissions -rw-r--r--
Rewrote the Bonjour classes, using the low-level <dns_sd.h> API. They're now subclasses of MYDNSService.
     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.continuous = YES;
    37         [self priv_updateLocalAddress];
    38     }
    39     return self;
    40 }
    41 
    42 - (id) initWithNullMapping
    43 {
    44     // A PortMapper with no port or protocols will cause the DNSService to look up 
    45     // our public address without creating a mapping.
    46     if ([self initWithLocalPort: 0]) {
    47         _mapTCP = _mapUDP = NO;
    48     }
    49     return self;
    50 }
    51 
    52 
    53 - (void) dealloc
    54 {
    55     [_publicAddress release];
    56     [_localAddress release];
    57     [super dealloc];
    58 }
    59 
    60 
    61 @synthesize localAddress=_localAddress, publicAddress=_publicAddress,
    62             mapTCP=_mapTCP, mapUDP=_mapUDP,
    63             desiredPublicPort=_desiredPublicPort;
    64 
    65 
    66 - (BOOL) isMapped
    67 {
    68     return ! $equal(_publicAddress,_localAddress);
    69 }
    70 
    71 - (void) priv_updateLocalAddress 
    72 {
    73     IPAddress *localAddress = [IPAddress localAddressWithPort: _localPort];
    74     if (!$equal(localAddress,_localAddress))
    75         self.localAddress = localAddress;
    76 }
    77 
    78 
    79 static IPAddress* makeIPAddr( UInt32 rawAddr, UInt16 port ) {
    80     if (rawAddr)
    81         return [[[IPAddress alloc] initWithIPv4: rawAddr port: port] autorelease];
    82     else
    83         return nil;
    84 }
    85 
    86 /** Called whenever the port mapping changes (see comment for callback, below.) */
    87 - (void) priv_portMapStatus: (DNSServiceErrorType)errorCode 
    88               publicAddress: (UInt32)rawPublicAddress
    89                  publicPort: (UInt16)publicPort
    90 {
    91     if( errorCode==kDNSServiceErr_NoError ) {
    92         if( rawPublicAddress==0 || (publicPort==0 && (_mapTCP || _mapUDP)) ) {
    93             LogTo(PortMapper,@"%@: No port-map available", self);
    94             errorCode = kDNSServiceErr_NATPortMappingUnsupported;
    95         }
    96     }
    97     if( errorCode != self.error )
    98         self.error = errorCode;
    99 
   100     [self priv_updateLocalAddress];
   101     IPAddress *publicAddress = makeIPAddr(rawPublicAddress,publicPort);
   102     if (!$equal(publicAddress,_publicAddress))
   103         self.publicAddress = publicAddress;
   104     
   105     if( ! errorCode ) {
   106         LogTo(PortMapper,@"%@: Public addr is %@ (mapped=%i)",
   107               self, self.publicAddress, self.isMapped);
   108     }
   109     [[NSNotificationCenter defaultCenter] postNotificationName: MYPortMapperChangedNotification
   110                                                         object: self];
   111 }
   112 
   113 
   114 /** Asynchronous callback from DNSServiceNATPortMappingCreate.
   115     This is invoked whenever the status of the port mapping changes.
   116     All it does is dispatch to the object's priv_portMapStatus:publicAddress:publicPort: method. */
   117 static void portMapCallback (
   118                       DNSServiceRef                    sdRef,
   119                       DNSServiceFlags                  flags,
   120                       uint32_t                         interfaceIndex,
   121                       DNSServiceErrorType              errorCode,
   122                       uint32_t                         publicAddress,    /* four byte IPv4 address in network byte order */
   123                       DNSServiceProtocol               protocol,
   124                       uint16_t                         privatePort,
   125                       uint16_t                         publicPort,       /* may be different than the requested port */
   126                       uint32_t                         ttl,              /* may be different than the requested ttl */
   127                       void                             *context
   128                       )
   129 {
   130     @try{
   131         [(MYPortMapper*)context priv_portMapStatus: errorCode 
   132                                      publicAddress: publicAddress
   133                                         publicPort: ntohs(publicPort)];  // port #s in network byte order!
   134     }catchAndReport(@"PortMapper");
   135 }
   136 
   137 
   138 - (DNSServiceRef) createServiceRef
   139 {
   140     DNSServiceProtocol protocols = 0;
   141     if( _mapTCP ) protocols |= kDNSServiceProtocol_TCP;
   142     if( _mapUDP ) protocols |= kDNSServiceProtocol_UDP;
   143     DNSServiceRef serviceRef = NULL;
   144     self.error = DNSServiceNATPortMappingCreate(&serviceRef, 
   145                                                 0 /*flags*/, 
   146                                                 0 /*interfaceIndex*/, 
   147                                                 protocols,
   148                                                 htons(_localPort),
   149                                                 htons(_desiredPublicPort),
   150                                                 0 /*ttl*/,
   151                                                 &portMapCallback, 
   152                                                 self);
   153     return serviceRef;
   154 }
   155 
   156 
   157 - (BOOL) waitTillOpened
   158 {
   159     if( ! self.serviceRef )
   160         if( ! [self start] )
   161             return NO;
   162     [self waitForReply];
   163     return (self.error==0);
   164 }
   165 
   166 
   167 + (IPAddress*) findPublicAddress
   168 {
   169     IPAddress *addr = nil;
   170     MYPortMapper *mapper = [[self alloc] initWithNullMapping];
   171     mapper.continuous = NO;
   172     if( [mapper waitTillOpened] )
   173         addr = [mapper.publicAddress retain];
   174     [mapper stop];
   175     [mapper release];
   176     return [addr autorelease];
   177 }
   178 
   179 
   180 @end
   181 
   182 
   183 /*
   184  Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   185  
   186  Redistribution and use in source and binary forms, with or without modification, are permitted
   187  provided that the following conditions are met:
   188  
   189  * Redistributions of source code must retain the above copyright notice, this list of conditions
   190  and the following disclaimer.
   191  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   192  and the following disclaimer in the documentation and/or other materials provided with the
   193  distribution.
   194  
   195  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   196  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   197  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   198  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   199  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   200   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   201  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   202  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   203  */