jens@0: // jens@0: // IPAddress.m jens@0: // MYNetwork jens@0: // jens@0: // Created by Jens Alfke on 1/4/08. jens@0: // Copyright 2008 Jens Alfke. All rights reserved. jens@0: // jens@0: jens@0: #import "IPAddress.h" jens@1: jens@1: #import "Logging.h" jens@1: #import "Test.h" jens@1: jens@0: #import jens@0: #import jens@0: #import jens@0: #import jens@0: #import jens@1: #import jens@0: jens@0: jens@0: @implementation IPAddress jens@0: jens@0: jens@0: + (UInt32) IPv4FromDottedQuadString: (NSString*)str jens@0: { jens@0: Assert(str); jens@0: UInt32 ipv4 = 0; jens@0: NSScanner *scanner = [NSScanner scannerWithString: str]; jens@0: for( int i=0; i<4; i++ ) { jens@0: if( i>0 && ! [scanner scanString: @"." intoString: nil] ) jens@0: return 0; jens@0: NSInteger octet; jens@0: if( ! [scanner scanInteger: &octet] || octet<0 || octet>255 ) jens@0: return 0; jens@0: ipv4 = (ipv4<<8) | octet; jens@0: } jens@0: if( ! [scanner isAtEnd] ) jens@0: return 0; jens@0: return htonl(ipv4); jens@0: } jens@0: jens@0: jens@0: - (id) initWithHostname: (NSString*)hostname port: (UInt16)port jens@0: { jens@0: Assert(hostname); jens@0: self = [super init]; jens@0: if (self != nil) { jens@0: _ipv4 = [[self class] IPv4FromDottedQuadString: hostname]; jens@0: if( ! _ipv4 ) { jens@0: [self release]; jens@0: return [[HostAddress alloc] initWithHostname: hostname port: port]; jens@0: } jens@0: _port = port; jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: jens@0: - (id) initWithIPv4: (UInt32)ipv4 port: (UInt16)port jens@0: { jens@0: self = [super init]; jens@0: if (self != nil) { jens@0: _ipv4 = ipv4; jens@0: _port = port; jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: - (id) initWithIPv4: (UInt32)ipv4 jens@0: { jens@0: return [self initWithIPv4: ipv4 port: 0]; jens@0: } jens@0: jens@0: - (id) initWithSockAddr: (const struct sockaddr*)sockaddr jens@0: { jens@0: if( sockaddr->sa_family == AF_INET ) { jens@0: const struct sockaddr_in *addr_in = (const struct sockaddr_in*)sockaddr; jens@0: return [self initWithIPv4: addr_in->sin_addr.s_addr port: ntohs(addr_in->sin_port)]; jens@0: } else { jens@0: [self release]; jens@0: return nil; jens@0: } jens@0: } jens@0: jens@0: + (IPAddress*) addressOfSocket: (CFSocketNativeHandle)socket jens@0: { jens@0: uint8_t name[SOCK_MAXADDRLEN]; jens@0: socklen_t namelen = sizeof(name); jens@0: struct sockaddr *addr = (struct sockaddr*)name; jens@0: if (0 == getpeername(socket, addr, &namelen)) jens@0: return [[[self alloc] initWithSockAddr: addr] autorelease]; jens@0: else jens@0: return nil; jens@0: } jens@0: jens@0: - (id) copyWithZone: (NSZone*)zone jens@0: { jens@0: return [self retain]; jens@0: } jens@0: jens@0: - (void)encodeWithCoder:(NSCoder *)coder jens@0: { jens@0: if( _ipv4 ) jens@0: [coder encodeInt32: _ipv4 forKey: @"ipv4"]; jens@0: if( _port ) jens@0: [coder encodeInt: _port forKey: @"port"]; jens@0: } jens@0: jens@0: - (id)initWithCoder:(NSCoder *)decoder jens@0: { jens@0: self = [super init]; jens@0: if( self ) { jens@0: _ipv4 = [decoder decodeInt32ForKey: @"ipv4"]; jens@0: _port = [decoder decodeIntForKey: @"port"]; jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: jens@0: @synthesize ipv4=_ipv4, port=_port; jens@0: jens@0: - (BOOL) isEqual: (IPAddress*)addr jens@0: { jens@0: return [addr isKindOfClass: [IPAddress class]] && [self isSameHost: addr] && addr->_port==_port; jens@0: } jens@0: jens@0: - (BOOL) isSameHost: (IPAddress*)addr jens@0: { jens@0: return addr && _ipv4==addr->_ipv4; jens@0: } jens@0: jens@0: - (NSUInteger) hash jens@0: { jens@0: return _ipv4 ^ _port; jens@0: } jens@0: jens@0: - (NSString*) ipv4name jens@0: { jens@0: UInt32 ipv4 = self.ipv4; jens@0: if( ipv4 != 0 ) { jens@0: const UInt8* b = (const UInt8*)&ipv4; jens@0: return [NSString stringWithFormat: @"%u.%u.%u.%u", jens@0: (unsigned)b[0],(unsigned)b[1],(unsigned)b[2],(unsigned)b[3]]; jens@0: } else jens@0: return nil; jens@0: } jens@0: jens@0: - (NSString*) hostname jens@0: { jens@0: return [self ipv4name]; jens@0: } jens@0: jens@0: - (NSString*) description jens@0: { jens@0: NSString *name = self.hostname ?: @"0.0.0.0"; jens@0: if( _port ) jens@0: name = [name stringByAppendingFormat: @":%hu",_port]; jens@0: return name; jens@0: } jens@0: jens@0: jens@0: + (IPAddress*) localAddress jens@0: { jens@0: // getifaddrs returns a linked list of interface entries; jens@0: // find the first active non-loopback interface with IPv4: jens@0: UInt32 address = 0; jens@0: struct ifaddrs *interfaces; jens@0: if( getifaddrs(&interfaces) == 0 ) { jens@0: struct ifaddrs *interface; jens@0: for( interface=interfaces; interface; interface=interface->ifa_next ) { jens@0: if( (interface->ifa_flags & IFF_UP) && ! (interface->ifa_flags & IFF_LOOPBACK) ) { jens@0: const struct sockaddr_in *addr = (const struct sockaddr_in*) interface->ifa_addr; jens@0: if( addr && addr->sin_family==AF_INET ) { jens@0: address = addr->sin_addr.s_addr; jens@0: break; jens@0: } jens@0: } jens@0: } jens@0: freeifaddrs(interfaces); jens@0: } jens@0: return [[[self alloc] initWithIPv4: address] autorelease]; jens@0: } jens@0: jens@0: jens@0: // Private IP address ranges. See RFC 3330. jens@0: static const struct {UInt32 mask, value;} const kPrivateRanges[] = { jens@0: {0xFF000000, 0x00000000}, // 0.x.x.x (hosts on "this" network) jens@0: {0xFF000000, 0x0A000000}, // 10.x.x.x (private address range) jens@0: {0xFF000000, 0x7F000000}, // 127.x.x.x (loopback) jens@0: {0xFFFF0000, 0xA9FE0000}, // 169.254.x.x (link-local self-configured addresses) jens@0: {0xFFF00000, 0xAC100000}, // 172.(16-31).x.x (private address range) jens@0: {0xFFFF0000, 0xC0A80000}, // 192.168.x.x (private address range) jens@0: {0,0} jens@0: }; jens@0: jens@0: jens@0: - (BOOL) isPrivate jens@0: { jens@0: UInt32 address = ntohl(self.ipv4); jens@0: int i; jens@0: for( i=0; kPrivateRanges[i].mask; i++ ) jens@0: if( (address & kPrivateRanges[i].mask) == kPrivateRanges[i].value ) jens@0: return YES; jens@0: return NO; jens@0: } jens@0: jens@0: jens@0: @end jens@0: jens@0: jens@0: jens@0: jens@0: jens@0: @implementation HostAddress jens@0: jens@0: jens@0: - (id) initWithHostname: (NSString*)hostname port: (UInt16)port jens@0: { jens@0: self = [super initWithIPv4: 0 port: port]; jens@0: if( self ) { jens@0: if( [hostname length]==0 ) { jens@0: [self release]; jens@0: return nil; jens@0: } jens@0: _hostname = [hostname copy]; jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: jens@0: - (void)encodeWithCoder:(NSCoder *)coder jens@0: { jens@0: [super encodeWithCoder: coder]; jens@0: [coder encodeObject: _hostname forKey: @"host"]; jens@0: } jens@0: jens@0: - (id)initWithCoder:(NSCoder *)decoder jens@0: { jens@0: self = [super initWithCoder: decoder]; jens@0: if( self ) { jens@0: _hostname = [[decoder decodeObjectForKey: @"host"] copy]; jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: - (NSUInteger) hash jens@0: { jens@0: return [_hostname hash] ^ _port; jens@0: } jens@0: jens@0: jens@0: - (NSString*) hostname {return _hostname;} jens@0: jens@0: jens@0: - (UInt32) ipv4 jens@0: { jens@0: struct hostent *ent = gethostbyname(_hostname.UTF8String); jens@0: if( ! ent ) { jens@0: Log(@"HostAddress: DNS lookup failed for <%@>: %s", _hostname, hstrerror(h_errno)); jens@0: return 0; jens@0: } jens@0: return * (const in_addr_t*) ent->h_addr_list[0]; jens@0: } jens@0: jens@0: jens@0: - (BOOL) isSameHost: (IPAddress*)addr jens@0: { jens@0: return [addr isKindOfClass: [HostAddress class]] && [_hostname caseInsensitiveCompare: addr.hostname]==0; jens@0: } jens@0: jens@0: jens@0: @end jens@0: jens@0: jens@0: jens@0: jens@0: @implementation RecentAddress jens@0: jens@0: jens@0: - (id) initWithIPAddress: (IPAddress*)addr jens@0: { jens@0: return [super initWithIPv4: addr.ipv4 port: addr.port]; jens@0: } jens@0: jens@0: jens@0: @synthesize lastSuccess=_lastSuccess, successes=_successes; jens@0: jens@0: - (BOOL) noteSuccess jens@0: { jens@0: if( _successes < 0xFFFF ) jens@0: _successes++; jens@0: _lastSuccess = CFAbsoluteTimeGetCurrent(); jens@0: return YES; jens@0: } jens@0: jens@0: - (BOOL) noteSeen jens@0: { jens@0: CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); jens@0: BOOL significant = ( now-_lastSuccess >= 18*60*60 ); jens@0: _lastSuccess = now; jens@0: return significant; jens@0: } jens@0: jens@0: jens@0: - (void)encodeWithCoder:(NSCoder *)coder jens@0: { jens@0: [super encodeWithCoder: coder]; jens@0: [coder encodeDouble: _lastSuccess forKey: @"last"]; jens@0: [coder encodeInt: _successes forKey: @"succ"]; jens@0: } jens@0: jens@0: - (id)initWithCoder:(NSCoder *)decoder jens@0: { jens@0: self = [super initWithCoder: decoder]; jens@0: if( self ) { jens@0: _lastSuccess = [decoder decodeDoubleForKey: @"last"]; jens@0: _successes = [decoder decodeIntForKey: @"succ"]; jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: jens@0: @end jens@0: jens@0: jens@0: jens@0: jens@0: jens@0: #import "Test.h" jens@0: jens@0: TestCase(IPAddress) { jens@0: RequireTestCase(CollectionUtils); jens@0: IPAddress *addr = [[IPAddress alloc] initWithIPv4: htonl(0x0A0001FE) port: 8080]; jens@0: CAssertEq(addr.ipv4,htonl(0x0A0001FE)); jens@0: CAssertEq(addr.port,8080); jens@0: CAssertEqual(addr.hostname,@"10.0.1.254"); jens@0: CAssertEqual(addr.description,@"10.0.1.254:8080"); jens@0: CAssert(addr.isPrivate); jens@0: jens@0: addr = [[IPAddress alloc] initWithHostname: @"66.66.0.255" port: 123]; jens@0: CAssertEq(addr.class,[IPAddress class]); jens@0: CAssertEq(addr.ipv4,htonl(0x424200FF)); jens@0: CAssertEq(addr.port,123); jens@0: CAssertEqual(addr.hostname,@"66.66.0.255"); jens@0: CAssertEqual(addr.description,@"66.66.0.255:123"); jens@0: CAssert(!addr.isPrivate); jens@0: jens@0: addr = [[IPAddress alloc] initWithHostname: @"www.apple.com" port: 80]; jens@0: CAssertEq(addr.class,[HostAddress class]); jens@0: Log(@"www.apple.com = 0x%08X", addr.ipv4); jens@0: CAssertEq(addr.ipv4,htonl(0x1195A00A)); jens@0: CAssertEq(addr.port,80); jens@0: CAssertEqual(addr.hostname,@"www.apple.com"); jens@0: CAssertEqual(addr.description,@"www.apple.com:80"); jens@0: CAssert(!addr.isPrivate); jens@0: } jens@0: jens@0: jens@0: /* jens@0: Copyright (c) 2008, Jens Alfke . All rights reserved. jens@0: jens@0: Redistribution and use in source and binary forms, with or without modification, are permitted jens@0: provided that the following conditions are met: jens@0: jens@0: * Redistributions of source code must retain the above copyright notice, this list of conditions jens@0: and the following disclaimer. jens@0: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions jens@0: and the following disclaimer in the documentation and/or other materials provided with the jens@0: distribution. jens@0: jens@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR jens@0: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND jens@0: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- jens@0: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES jens@0: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR jens@0: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN jens@0: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF jens@0: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jens@0: */