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