jens@26: // jens@26: // MYPortMapper.m jens@26: // MYNetwork jens@26: // jens@26: // Created by Jens Alfke on 1/4/08. jens@26: // Copyright 2008 Jens Alfke. All rights reserved. jens@26: // jens@26: jens@26: #import "MYPortMapper.h" jens@26: #import "IPAddress.h" jens@26: #import "CollectionUtils.h" jens@26: #import "Logging.h" jens@26: #import "ExceptionUtils.h" jens@26: jens@26: #import jens@26: jens@26: jens@26: NSString* const MYPortMapperChangedNotification = @"MYPortMapperChanged"; jens@26: jens@26: jens@26: @interface MYPortMapper () jens@27: @property (retain) IPAddress* publicAddress, *localAddress; // redeclare as settable jens@26: - (void) priv_updateLocalAddress; jens@26: @end jens@26: jens@26: jens@26: @implementation MYPortMapper jens@26: jens@26: jens@26: - (id) initWithLocalPort: (UInt16)localPort jens@26: { jens@26: self = [super init]; jens@26: if (self != nil) { jens@26: _localPort = localPort; jens@26: _mapTCP = YES; jens@28: self.continuous = YES; jens@26: [self priv_updateLocalAddress]; jens@26: } jens@26: return self; jens@26: } jens@26: jens@26: - (id) initWithNullMapping jens@26: { jens@26: // A PortMapper with no port or protocols will cause the DNSService to look up jens@26: // our public address without creating a mapping. jens@26: if ([self initWithLocalPort: 0]) { jens@26: _mapTCP = _mapUDP = NO; jens@26: } jens@26: return self; jens@26: } jens@26: jens@26: jens@26: - (void) dealloc jens@26: { jens@26: [_publicAddress release]; jens@26: [_localAddress release]; jens@26: [super dealloc]; jens@26: } jens@26: jens@26: jens@26: @synthesize localAddress=_localAddress, publicAddress=_publicAddress, jens@26: mapTCP=_mapTCP, mapUDP=_mapUDP, jens@26: desiredPublicPort=_desiredPublicPort; jens@26: jens@26: jens@26: - (BOOL) isMapped jens@26: { jens@26: return ! $equal(_publicAddress,_localAddress); jens@26: } jens@26: jens@26: - (void) priv_updateLocalAddress jens@26: { jens@26: IPAddress *localAddress = [IPAddress localAddressWithPort: _localPort]; jens@26: if (!$equal(localAddress,_localAddress)) jens@26: self.localAddress = localAddress; jens@26: } jens@26: jens@26: jens@26: static IPAddress* makeIPAddr( UInt32 rawAddr, UInt16 port ) { jens@26: if (rawAddr) jens@26: return [[[IPAddress alloc] initWithIPv4: rawAddr port: port] autorelease]; jens@26: else jens@26: return nil; jens@26: } jens@26: jens@26: /** Called whenever the port mapping changes (see comment for callback, below.) */ jens@26: - (void) priv_portMapStatus: (DNSServiceErrorType)errorCode jens@26: publicAddress: (UInt32)rawPublicAddress jens@26: publicPort: (UInt16)publicPort jens@26: { jens@26: if( errorCode==kDNSServiceErr_NoError ) { jens@26: if( rawPublicAddress==0 || (publicPort==0 && (_mapTCP || _mapUDP)) ) { jens@28: LogTo(PortMapper,@"%@: No port-map available", self); jens@26: errorCode = kDNSServiceErr_NATPortMappingUnsupported; jens@26: } jens@26: } jens@26: jens@26: [self priv_updateLocalAddress]; jens@26: IPAddress *publicAddress = makeIPAddr(rawPublicAddress,publicPort); jens@26: if (!$equal(publicAddress,_publicAddress)) jens@26: self.publicAddress = publicAddress; jens@26: jens@26: if( ! errorCode ) { jens@28: LogTo(PortMapper,@"%@: Public addr is %@ (mapped=%i)", jens@28: self, self.publicAddress, self.isMapped); jens@26: } jens@31: jens@31: [self gotResponse: errorCode]; jens@26: [[NSNotificationCenter defaultCenter] postNotificationName: MYPortMapperChangedNotification jens@26: object: self]; jens@26: } jens@26: jens@26: jens@26: /** Asynchronous callback from DNSServiceNATPortMappingCreate. jens@26: This is invoked whenever the status of the port mapping changes. jens@26: All it does is dispatch to the object's priv_portMapStatus:publicAddress:publicPort: method. */ jens@26: static void portMapCallback ( jens@26: DNSServiceRef sdRef, jens@26: DNSServiceFlags flags, jens@26: uint32_t interfaceIndex, jens@26: DNSServiceErrorType errorCode, jens@26: uint32_t publicAddress, /* four byte IPv4 address in network byte order */ jens@26: DNSServiceProtocol protocol, jens@26: uint16_t privatePort, jens@26: uint16_t publicPort, /* may be different than the requested port */ jens@26: uint32_t ttl, /* may be different than the requested ttl */ jens@26: void *context jens@26: ) jens@26: { jens@26: @try{ jens@26: [(MYPortMapper*)context priv_portMapStatus: errorCode jens@26: publicAddress: publicAddress jens@26: publicPort: ntohs(publicPort)]; // port #s in network byte order! jens@26: }catchAndReport(@"PortMapper"); jens@26: } jens@26: jens@26: jens@31: - (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr { jens@26: DNSServiceProtocol protocols = 0; jens@26: if( _mapTCP ) protocols |= kDNSServiceProtocol_TCP; jens@26: if( _mapUDP ) protocols |= kDNSServiceProtocol_UDP; jens@31: return DNSServiceNATPortMappingCreate(sdRefPtr, jens@31: kDNSServiceFlagsShareConnection, jens@31: 0 /*interfaceIndex*/, jens@31: protocols, jens@31: htons(_localPort), jens@31: htons(_desiredPublicPort), jens@31: 0 /*ttl*/, jens@31: &portMapCallback, jens@31: self); jens@27: } jens@27: jens@27: jens@26: - (BOOL) waitTillOpened jens@26: { jens@27: if( ! self.serviceRef ) jens@28: if( ! [self start] ) jens@26: return NO; jens@28: [self waitForReply]; jens@27: return (self.error==0); jens@26: } jens@26: jens@26: jens@26: + (IPAddress*) findPublicAddress jens@26: { jens@26: IPAddress *addr = nil; jens@26: MYPortMapper *mapper = [[self alloc] initWithNullMapping]; jens@28: mapper.continuous = NO; jens@26: if( [mapper waitTillOpened] ) jens@26: addr = [mapper.publicAddress retain]; jens@28: [mapper stop]; jens@26: [mapper release]; jens@26: return [addr autorelease]; jens@26: } jens@26: jens@26: jens@26: @end jens@26: jens@26: jens@26: /* jens@26: Copyright (c) 2008-2009, Jens Alfke . All rights reserved. jens@26: jens@26: Redistribution and use in source and binary forms, with or without modification, are permitted jens@26: provided that the following conditions are met: jens@26: jens@26: * Redistributions of source code must retain the above copyright notice, this list of conditions jens@26: and the following disclaimer. jens@26: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions jens@26: and the following disclaimer in the documentation and/or other materials provided with the jens@26: distribution. jens@26: jens@26: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR jens@26: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND jens@26: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- jens@26: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES jens@26: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR jens@26: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN jens@26: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF jens@26: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jens@26: */