PortMapper/MYPortMapper.m
author Jens Alfke <jens@mooseyard.com>
Sun May 24 15:03:39 2009 -0700 (2009-05-24)
changeset 49 20cccc7c26ee
parent 28 732576fa8a0d
permissions -rw-r--r--
Misc. tweaks made while porting Chatty to use MYNetwork.
* Allow -[BLIPConnection sendRequest:] to re-send an already-sent or received request.
* Allow use of the basic -init method for BLIPConnection.
* Some new convenience factory methods.
* Broke dependencies on Security.framework out into new TCPEndpoint+Certs.m source file, so client apps aren't forced to link against Security.
     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 
    98     [self priv_updateLocalAddress];
    99     IPAddress *publicAddress = makeIPAddr(rawPublicAddress,publicPort);
   100     if (!$equal(publicAddress,_publicAddress))
   101         self.publicAddress = publicAddress;
   102     
   103     if( ! errorCode ) {
   104         LogTo(PortMapper,@"%@: Public addr is %@ (mapped=%i)",
   105               self, self.publicAddress, self.isMapped);
   106     }
   107 
   108     [self gotResponse: errorCode];
   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 - (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
   139     DNSServiceProtocol protocols = 0;
   140     if( _mapTCP ) protocols |= kDNSServiceProtocol_TCP;
   141     if( _mapUDP ) protocols |= kDNSServiceProtocol_UDP;
   142     return DNSServiceNATPortMappingCreate(sdRefPtr, 
   143                                           kDNSServiceFlagsShareConnection, 
   144                                           0 /*interfaceIndex*/, 
   145                                           protocols,
   146                                           htons(_localPort),
   147                                           htons(_desiredPublicPort),
   148                                           0 /*ttl*/,
   149                                           &portMapCallback, 
   150                                           self);
   151 }
   152 
   153 
   154 - (BOOL) waitTillOpened
   155 {
   156     if( ! self.serviceRef )
   157         if( ! [self start] )
   158             return NO;
   159     [self waitForReply];
   160     return (self.error==0);
   161 }
   162 
   163 
   164 + (IPAddress*) findPublicAddress
   165 {
   166     IPAddress *addr = nil;
   167     MYPortMapper *mapper = [[self alloc] initWithNullMapping];
   168     mapper.continuous = NO;
   169     if( [mapper waitTillOpened] )
   170         addr = [mapper.publicAddress retain];
   171     [mapper stop];
   172     [mapper release];
   173     return [addr autorelease];
   174 }
   175 
   176 
   177 @end
   178 
   179 
   180 /*
   181  Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   182  
   183  Redistribution and use in source and binary forms, with or without modification, are permitted
   184  provided that the following conditions are met:
   185  
   186  * Redistributions of source code must retain the above copyright notice, this list of conditions
   187  and the following disclaimer.
   188  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   189  and the following disclaimer in the documentation and/or other materials provided with the
   190  distribution.
   191  
   192  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   193  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   194  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   195  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   196  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   197   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   198  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   199  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   200  */