IPAddress.m
author Jens Alfke <jens@mooseyard.com>
Mon Jul 20 14:50:49 2009 -0700 (2009-07-20)
changeset 60 dd637bdd214e
parent 59 46c7844cb592
permissions -rw-r--r--
DNS NULL record support in MYBonjourRegistration. Minor fix to IPAddress init. Force 4-char indent in source files.
     1 //
     2 //  IPAddress.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 "IPAddress.h"
    10 
    11 #import "Logging.h"
    12 #import "Test.h"
    13 
    14 #import <sys/types.h>
    15 #import <sys/socket.h>
    16 #import <net/if.h>
    17 #import <netinet/in.h>
    18 #import <ifaddrs.h>
    19 #import <netdb.h>
    20 
    21 
    22 @implementation IPAddress
    23 
    24 
    25 + (UInt32) IPv4FromDottedQuadString: (NSString*)str
    26 {
    27     Assert(str);
    28     UInt32 ipv4 = 0;
    29     NSScanner *scanner = [NSScanner scannerWithString: str];
    30     for( int i=0; i<4; i++ ) {
    31         if( i>0 && ! [scanner scanString: @"." intoString: nil] )
    32             return 0;
    33         NSInteger octet;
    34         if( ! [scanner scanInteger: &octet] || octet<0 || octet>255 )
    35             return 0;
    36         ipv4 = (ipv4<<8) | octet;
    37     }
    38     if( ! [scanner isAtEnd] )
    39         return 0;
    40     return htonl(ipv4);
    41 }
    42          
    43          
    44 - (id) initWithHostname: (NSString*)hostname port: (UInt16)port
    45 {
    46     Assert(hostname);
    47     self = [super init];
    48     if (self != nil) {
    49         _ipv4 = [[self class] IPv4FromDottedQuadString: hostname];
    50         if( ! _ipv4 ) {
    51             [self release];
    52             return [[HostAddress alloc] initWithHostname: hostname port: port];
    53         }
    54         _port = port;
    55     }
    56     return self;
    57 }
    58 
    59 + (IPAddress*) addressWithHostname: (NSString*)hostname port: (UInt16)port
    60 {
    61     return [[[self alloc] initWithHostname: hostname port: port] autorelease];
    62 }
    63 
    64 
    65 - (id) initWithIPv4: (UInt32)ipv4 port: (UInt16)port
    66 {
    67     self = [super init];
    68     if (self != nil) {
    69         _ipv4 = ipv4;
    70         _port = port;
    71     }
    72     return self;
    73 }
    74 
    75 - (id) initWithIPv4: (UInt32)ipv4
    76 {
    77     return [self initWithIPv4: ipv4 port: 0];
    78 }
    79 
    80 - (id) initWithSockAddr: (const struct sockaddr*)sockaddr
    81 {
    82     if( sockaddr->sa_family == AF_INET ) {
    83         const struct sockaddr_in *addr_in = (const struct sockaddr_in*)sockaddr;
    84         return [self initWithIPv4: addr_in->sin_addr.s_addr port: ntohs(addr_in->sin_port)];
    85     } else {
    86         [self release];
    87         return nil;
    88     }
    89 }
    90 
    91 - (id) initWithSockAddr: (const struct sockaddr*)sockaddr
    92                    port: (UInt16)port
    93 {
    94     self = [self initWithSockAddr: sockaddr];
    95     if (self)
    96         _port = port;
    97     return self;
    98 }
    99 
   100 - (id) initWithData: (NSData*)data
   101 {
   102     if (!data) {
   103         [self release];
   104         return nil;
   105     }
   106     const struct sockaddr* addr = data.bytes;
   107     if (data.length < sizeof(struct sockaddr_in))
   108         addr = nil;
   109     return [self initWithSockAddr: addr];
   110 }
   111 
   112 
   113 + (IPAddress*) addressOfSocket: (CFSocketNativeHandle)socket
   114 {
   115     uint8_t name[SOCK_MAXADDRLEN];
   116     socklen_t namelen = sizeof(name);
   117     struct sockaddr *addr = (struct sockaddr*)name;
   118     if (0 == getpeername(socket, addr, &namelen))
   119         return [[[self alloc] initWithSockAddr: addr] autorelease];
   120     else
   121         return nil;
   122 }    
   123 
   124 - (id) copyWithZone: (NSZone*)zone
   125 {
   126     return [self retain];
   127 }
   128 
   129 - (void)encodeWithCoder:(NSCoder *)coder
   130 {
   131     if( _ipv4 )
   132         [coder encodeInt32: _ipv4 forKey: @"ipv4"];
   133     if( _port )
   134         [coder encodeInt: _port forKey: @"port"];
   135 }
   136 
   137 - (id)initWithCoder:(NSCoder *)decoder
   138 {
   139     self = [super init];
   140     if( self ) {
   141         _ipv4 = [decoder decodeInt32ForKey: @"ipv4"];
   142         _port = [decoder decodeIntForKey: @"port"];
   143     }
   144     return self;
   145 }
   146 
   147 
   148 @synthesize ipv4=_ipv4, port=_port;
   149 
   150 - (BOOL) isEqual: (IPAddress*)addr
   151 {
   152     return [addr isKindOfClass: [IPAddress class]] && [self isSameHost: addr] && addr->_port==_port;
   153 }
   154 
   155 - (BOOL) isSameHost: (IPAddress*)addr
   156 {
   157     return addr && _ipv4==addr->_ipv4;
   158 }
   159 
   160 - (NSUInteger) hash
   161 {
   162     return _ipv4 ^ _port;
   163 }
   164 
   165 - (NSString*) ipv4name
   166 {
   167     UInt32 ipv4 = self.ipv4;
   168     if( ipv4 != 0 ) {
   169         const UInt8* b = (const UInt8*)&ipv4;
   170         return [NSString stringWithFormat: @"%u.%u.%u.%u",
   171                 (unsigned)b[0],(unsigned)b[1],(unsigned)b[2],(unsigned)b[3]];
   172     } else
   173         return nil;
   174 }
   175 
   176 - (NSString*) hostname
   177 {
   178     return [self ipv4name];
   179 }
   180 
   181 - (NSData*) asData
   182 {
   183     struct sockaddr_in addr = {
   184         .sin_len    = sizeof(struct sockaddr_in),
   185         .sin_family = AF_INET,
   186         .sin_port   = htons(_port),
   187         .sin_addr   = {htonl(_ipv4)} };
   188     return [NSData dataWithBytes: &addr length: sizeof(addr)];
   189 }
   190 
   191 - (NSString*) description
   192 {
   193     NSString *name = self.hostname ?: @"0.0.0.0";
   194     if( _port )
   195         name = [name stringByAppendingFormat: @":%hu",_port];
   196     return name;
   197 }
   198 
   199 
   200 + (IPAddress*) localAddressWithPort: (UInt16)port
   201 {
   202     // getifaddrs returns a linked list of interface entries;
   203     // find the first active non-loopback interface with IPv4:
   204     UInt32 address = 0;
   205     struct ifaddrs *interfaces;
   206     if( getifaddrs(&interfaces) == 0 ) {
   207         struct ifaddrs *interface;
   208         for( interface=interfaces; interface; interface=interface->ifa_next ) {
   209             if( (interface->ifa_flags & IFF_UP) && ! (interface->ifa_flags & IFF_LOOPBACK) ) {
   210                 const struct sockaddr_in *addr = (const struct sockaddr_in*) interface->ifa_addr;
   211                 if( addr && addr->sin_family==AF_INET ) {
   212                     address = addr->sin_addr.s_addr;
   213                     break;
   214                 }
   215             }
   216         }
   217         freeifaddrs(interfaces);
   218     }
   219     return [[[self alloc] initWithIPv4: address port: port] autorelease];
   220 }
   221 
   222 + (IPAddress*) localAddress
   223 {
   224     return [self localAddressWithPort: 0];
   225 }
   226 
   227 
   228 // Private IP address ranges. See RFC 3330.
   229 static const struct {UInt32 mask, value;} const kPrivateRanges[] = {
   230     {0xFF000000, 0x00000000},       //   0.x.x.x (hosts on "this" network)
   231     {0xFF000000, 0x0A000000},       //  10.x.x.x (private address range)
   232     {0xFF000000, 0x7F000000},       // 127.x.x.x (loopback)
   233     {0xFFFF0000, 0xA9FE0000},       // 169.254.x.x (link-local self-configured addresses)
   234     {0xFFF00000, 0xAC100000},       // 172.(16-31).x.x (private address range)
   235     {0xFFFF0000, 0xC0A80000},       // 192.168.x.x (private address range)
   236     {0,0}
   237 };
   238 
   239 
   240 - (BOOL) isPrivate
   241 {
   242     UInt32 address = ntohl(self.ipv4);
   243     int i;
   244     for( i=0; kPrivateRanges[i].mask; i++ )
   245         if( (address & kPrivateRanges[i].mask) == kPrivateRanges[i].value )
   246             return YES;
   247     return NO;
   248 }
   249 
   250 
   251 @end
   252 
   253 
   254 
   255 
   256 
   257 @implementation HostAddress
   258 
   259 
   260 - (id) initWithHostname: (NSString*)hostname port: (UInt16)port
   261 {
   262     self = [super initWithIPv4: 0 port: port];
   263     if( self ) {
   264         if( [hostname length]==0 ) {
   265             [self release];
   266             return nil;
   267         }
   268         _hostname = [hostname copy];
   269     }
   270     return self;
   271 }
   272 
   273 - (id) initWithHostname: (NSString*)hostname
   274                sockaddr: (const struct sockaddr*)sockaddr
   275                    port: (UInt16)port;
   276 {
   277     if( [hostname length]==0 ) {
   278         [self release];
   279         return nil;
   280     }
   281     self = [super initWithSockAddr: sockaddr port: port];
   282     if( self ) {
   283         _hostname = [hostname copy];
   284     }
   285     return self;
   286 }    
   287 
   288 
   289 - (void)encodeWithCoder:(NSCoder *)coder
   290 {
   291     [super encodeWithCoder: coder];
   292     [coder encodeObject: _hostname forKey: @"host"];
   293 }
   294 
   295 - (id)initWithCoder:(NSCoder *)decoder
   296 {
   297     self = [super initWithCoder: decoder];
   298     if( self ) {
   299         _hostname = [[decoder decodeObjectForKey: @"host"] copy];
   300     }
   301     return self;
   302 }
   303 
   304 - (void)dealloc 
   305 {
   306     [_hostname release];
   307     [super dealloc];
   308 }
   309 
   310 
   311 - (NSString*) description
   312 {
   313     NSMutableString *desc = [[_hostname mutableCopy] autorelease];
   314     NSString *addr = self.ipv4name;
   315     if (addr)
   316         [desc appendFormat: @"(%@)", addr];
   317     if( self.port )
   318         [desc appendFormat: @":%hu",self.port];
   319     return desc;
   320 }
   321 
   322 
   323 - (NSUInteger) hash
   324 {
   325     return [_hostname hash] ^ self.port;
   326 }
   327 
   328 
   329 - (NSString*) hostname  {return _hostname;}
   330 
   331 
   332 - (UInt32) ipv4
   333 {
   334     struct hostent *ent = gethostbyname(_hostname.UTF8String);
   335     if( ! ent ) {
   336         Log(@"HostAddress: DNS lookup failed for <%@>: %s", _hostname, hstrerror(h_errno));
   337         return 0;
   338     }
   339     return * (const in_addr_t*) ent->h_addr_list[0];
   340 }
   341 
   342 
   343 - (BOOL) isSameHost: (IPAddress*)addr
   344 {
   345     return [addr isKindOfClass: [HostAddress class]] && [_hostname caseInsensitiveCompare: addr.hostname]==0;
   346 }
   347 
   348 
   349 @end
   350 
   351 
   352 
   353 
   354 @implementation RecentAddress
   355 
   356 
   357 - (id) initWithIPAddress: (IPAddress*)addr
   358 {
   359     return [super initWithIPv4: addr.ipv4 port: addr.port];
   360 }
   361 
   362 
   363 @synthesize lastSuccess=_lastSuccess, successes=_successes;
   364 
   365 - (BOOL) noteSuccess
   366 {
   367     if( _successes < 0xFFFF )
   368         _successes++;
   369     _lastSuccess = CFAbsoluteTimeGetCurrent();
   370     return YES;
   371 }
   372 
   373 - (BOOL) noteSeen
   374 {
   375     CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
   376     BOOL significant = ( now-_lastSuccess >= 18*60*60 );
   377     _lastSuccess = now;
   378     return significant;
   379 }
   380 
   381 
   382 - (void)encodeWithCoder:(NSCoder *)coder
   383 {
   384     [super encodeWithCoder: coder];
   385     [coder encodeDouble: _lastSuccess forKey: @"last"];
   386     [coder encodeInt: _successes forKey: @"succ"];
   387 }
   388 
   389 - (id)initWithCoder:(NSCoder *)decoder
   390 {
   391     self = [super initWithCoder: decoder];
   392     if( self ) {
   393         _lastSuccess = [decoder decodeDoubleForKey: @"last"];
   394         _successes = [decoder decodeIntForKey: @"succ"];
   395     }
   396     return self;
   397 }
   398 
   399 
   400 @end
   401 
   402 
   403 
   404 
   405 
   406 #import "Test.h"
   407 
   408 TestCase(IPAddress) {
   409     RequireTestCase(CollectionUtils);
   410     IPAddress *addr = [[IPAddress alloc] initWithIPv4: htonl(0x0A0001FE) port: 8080];
   411     CAssertEq(addr.ipv4,(UInt32)htonl(0x0A0001FE));
   412     CAssertEq(addr.port,8080);
   413     CAssertEqual(addr.hostname,@"10.0.1.254");
   414     CAssertEqual(addr.description,@"10.0.1.254:8080");
   415     CAssert(addr.isPrivate);
   416 	[addr release];
   417     
   418     addr = [[IPAddress alloc] initWithHostname: @"66.66.0.255" port: 123];
   419     CAssertEq(addr.class,[IPAddress class]);
   420     CAssertEq(addr.ipv4,(UInt32)htonl(0x424200FF));
   421     CAssertEq(addr.port,123);
   422     CAssertEqual(addr.hostname,@"66.66.0.255");
   423     CAssertEqual(addr.description,@"66.66.0.255:123");
   424     CAssert(!addr.isPrivate);
   425  	[addr release];
   426    
   427     addr = [[IPAddress alloc] initWithHostname: @"www.apple.com" port: 80];
   428     CAssertEq(addr.class,[HostAddress class]);
   429     Log(@"www.apple.com = %@ [0x%08X]", addr.ipv4name, ntohl(addr.ipv4));
   430     CAssertEq(addr.ipv4,(UInt32)htonl(0x11FBC820));
   431     CAssertEq(addr.port,80);
   432     CAssertEqual(addr.hostname,@"www.apple.com");
   433     CAssertEqual(addr.description,@"www.apple.com:80");
   434     CAssert(!addr.isPrivate);
   435 	[addr release];
   436 }
   437 
   438 
   439 /*
   440  Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   441  
   442  Redistribution and use in source and binary forms, with or without modification, are permitted
   443  provided that the following conditions are met:
   444  
   445  * Redistributions of source code must retain the above copyright notice, this list of conditions
   446  and the following disclaimer.
   447  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   448  and the following disclaimer in the documentation and/or other materials provided with the
   449  distribution.
   450  
   451  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   452  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   453  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   454  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   455  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   456   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   457  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   458  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   459  */