IPAddress.m
author Jens Alfke <jens@mooseyard.com>
Tue Jul 15 09:37:15 2008 -0700 (2008-07-15)
changeset 24 ab88e3ee5726
parent 1 8267d5c429c4
child 26 cb9cdf247239
permissions -rw-r--r--
Fixed a memory leak by adding a -dealloc method to HostAddress. (Thanks to Mark Onyschuk)
     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 
    60 - (id) initWithIPv4: (UInt32)ipv4 port: (UInt16)port
    61 {
    62     self = [super init];
    63     if (self != nil) {
    64         _ipv4 = ipv4;
    65         _port = port;
    66     }
    67     return self;
    68 }
    69 
    70 - (id) initWithIPv4: (UInt32)ipv4
    71 {
    72     return [self initWithIPv4: ipv4 port: 0];
    73 }
    74 
    75 - (id) initWithSockAddr: (const struct sockaddr*)sockaddr
    76 {
    77     if( sockaddr->sa_family == AF_INET ) {
    78         const struct sockaddr_in *addr_in = (const struct sockaddr_in*)sockaddr;
    79         return [self initWithIPv4: addr_in->sin_addr.s_addr port: ntohs(addr_in->sin_port)];
    80     } else {
    81         [self release];
    82         return nil;
    83     }
    84 }
    85 
    86 + (IPAddress*) addressOfSocket: (CFSocketNativeHandle)socket
    87 {
    88     uint8_t name[SOCK_MAXADDRLEN];
    89     socklen_t namelen = sizeof(name);
    90     struct sockaddr *addr = (struct sockaddr*)name;
    91     if (0 == getpeername(socket, addr, &namelen))
    92         return [[[self alloc] initWithSockAddr: addr] autorelease];
    93     else
    94         return nil;
    95 }    
    96 
    97 - (id) copyWithZone: (NSZone*)zone
    98 {
    99     return [self retain];
   100 }
   101 
   102 - (void)encodeWithCoder:(NSCoder *)coder
   103 {
   104     if( _ipv4 )
   105         [coder encodeInt32: _ipv4 forKey: @"ipv4"];
   106     if( _port )
   107         [coder encodeInt: _port forKey: @"port"];
   108 }
   109 
   110 - (id)initWithCoder:(NSCoder *)decoder
   111 {
   112     self = [super init];
   113     if( self ) {
   114         _ipv4 = [decoder decodeInt32ForKey: @"ipv4"];
   115         _port = [decoder decodeIntForKey: @"port"];
   116     }
   117     return self;
   118 }
   119 
   120 
   121 @synthesize ipv4=_ipv4, port=_port;
   122 
   123 - (BOOL) isEqual: (IPAddress*)addr
   124 {
   125     return [addr isKindOfClass: [IPAddress class]] && [self isSameHost: addr] && addr->_port==_port;
   126 }
   127 
   128 - (BOOL) isSameHost: (IPAddress*)addr
   129 {
   130     return addr && _ipv4==addr->_ipv4;
   131 }
   132 
   133 - (NSUInteger) hash
   134 {
   135     return _ipv4 ^ _port;
   136 }
   137 
   138 - (NSString*) ipv4name
   139 {
   140     UInt32 ipv4 = self.ipv4;
   141     if( ipv4 != 0 ) {
   142         const UInt8* b = (const UInt8*)&ipv4;
   143         return [NSString stringWithFormat: @"%u.%u.%u.%u",
   144                 (unsigned)b[0],(unsigned)b[1],(unsigned)b[2],(unsigned)b[3]];
   145     } else
   146         return nil;
   147 }
   148 
   149 - (NSString*) hostname
   150 {
   151     return [self ipv4name];
   152 }
   153 
   154 - (NSString*) description
   155 {
   156     NSString *name = self.hostname ?: @"0.0.0.0";
   157     if( _port )
   158         name = [name stringByAppendingFormat: @":%hu",_port];
   159     return name;
   160 }
   161 
   162 
   163 + (IPAddress*) localAddress
   164 {
   165     // getifaddrs returns a linked list of interface entries;
   166     // find the first active non-loopback interface with IPv4:
   167     UInt32 address = 0;
   168     struct ifaddrs *interfaces;
   169     if( getifaddrs(&interfaces) == 0 ) {
   170         struct ifaddrs *interface;
   171         for( interface=interfaces; interface; interface=interface->ifa_next ) {
   172             if( (interface->ifa_flags & IFF_UP) && ! (interface->ifa_flags & IFF_LOOPBACK) ) {
   173                 const struct sockaddr_in *addr = (const struct sockaddr_in*) interface->ifa_addr;
   174                 if( addr && addr->sin_family==AF_INET ) {
   175                     address = addr->sin_addr.s_addr;
   176                     break;
   177                 }
   178             }
   179         }
   180         freeifaddrs(interfaces);
   181     }
   182     return [[[self alloc] initWithIPv4: address] autorelease];
   183 }
   184 
   185 
   186 // Private IP address ranges. See RFC 3330.
   187 static const struct {UInt32 mask, value;} const kPrivateRanges[] = {
   188     {0xFF000000, 0x00000000},       //   0.x.x.x (hosts on "this" network)
   189     {0xFF000000, 0x0A000000},       //  10.x.x.x (private address range)
   190     {0xFF000000, 0x7F000000},       // 127.x.x.x (loopback)
   191     {0xFFFF0000, 0xA9FE0000},       // 169.254.x.x (link-local self-configured addresses)
   192     {0xFFF00000, 0xAC100000},       // 172.(16-31).x.x (private address range)
   193     {0xFFFF0000, 0xC0A80000},       // 192.168.x.x (private address range)
   194     {0,0}
   195 };
   196 
   197 
   198 - (BOOL) isPrivate
   199 {
   200     UInt32 address = ntohl(self.ipv4);
   201     int i;
   202     for( i=0; kPrivateRanges[i].mask; i++ )
   203         if( (address & kPrivateRanges[i].mask) == kPrivateRanges[i].value )
   204             return YES;
   205     return NO;
   206 }
   207 
   208 
   209 @end
   210 
   211 
   212 
   213 
   214 
   215 @implementation HostAddress
   216 
   217 
   218 - (id) initWithHostname: (NSString*)hostname port: (UInt16)port
   219 {
   220     self = [super initWithIPv4: 0 port: port];
   221     if( self ) {
   222         if( [hostname length]==0 ) {
   223             [self release];
   224             return nil;
   225         }
   226         _hostname = [hostname copy];
   227     }
   228     return self;
   229 }
   230 
   231 
   232 - (void)encodeWithCoder:(NSCoder *)coder
   233 {
   234     [super encodeWithCoder: coder];
   235     [coder encodeObject: _hostname forKey: @"host"];
   236 }
   237 
   238 - (id)initWithCoder:(NSCoder *)decoder
   239 {
   240     self = [super initWithCoder: decoder];
   241     if( self ) {
   242         _hostname = [[decoder decodeObjectForKey: @"host"] copy];
   243     }
   244     return self;
   245 }
   246 
   247 - (void)dealloc 
   248 {
   249     [_hostname release];
   250     [super dealloc];
   251 }
   252 
   253 
   254 - (NSUInteger) hash
   255 {
   256     return [_hostname hash] ^ _port;
   257 }
   258 
   259 
   260 - (NSString*) hostname  {return _hostname;}
   261 
   262 
   263 - (UInt32) ipv4
   264 {
   265     struct hostent *ent = gethostbyname(_hostname.UTF8String);
   266     if( ! ent ) {
   267         Log(@"HostAddress: DNS lookup failed for <%@>: %s", _hostname, hstrerror(h_errno));
   268         return 0;
   269     }
   270     return * (const in_addr_t*) ent->h_addr_list[0];
   271 }
   272 
   273 
   274 - (BOOL) isSameHost: (IPAddress*)addr
   275 {
   276     return [addr isKindOfClass: [HostAddress class]] && [_hostname caseInsensitiveCompare: addr.hostname]==0;
   277 }
   278 
   279 
   280 @end
   281 
   282 
   283 
   284 
   285 @implementation RecentAddress
   286 
   287 
   288 - (id) initWithIPAddress: (IPAddress*)addr
   289 {
   290     return [super initWithIPv4: addr.ipv4 port: addr.port];
   291 }
   292 
   293 
   294 @synthesize lastSuccess=_lastSuccess, successes=_successes;
   295 
   296 - (BOOL) noteSuccess
   297 {
   298     if( _successes < 0xFFFF )
   299         _successes++;
   300     _lastSuccess = CFAbsoluteTimeGetCurrent();
   301     return YES;
   302 }
   303 
   304 - (BOOL) noteSeen
   305 {
   306     CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
   307     BOOL significant = ( now-_lastSuccess >= 18*60*60 );
   308     _lastSuccess = now;
   309     return significant;
   310 }
   311 
   312 
   313 - (void)encodeWithCoder:(NSCoder *)coder
   314 {
   315     [super encodeWithCoder: coder];
   316     [coder encodeDouble: _lastSuccess forKey: @"last"];
   317     [coder encodeInt: _successes forKey: @"succ"];
   318 }
   319 
   320 - (id)initWithCoder:(NSCoder *)decoder
   321 {
   322     self = [super initWithCoder: decoder];
   323     if( self ) {
   324         _lastSuccess = [decoder decodeDoubleForKey: @"last"];
   325         _successes = [decoder decodeIntForKey: @"succ"];
   326     }
   327     return self;
   328 }
   329 
   330 
   331 @end
   332 
   333 
   334 
   335 
   336 
   337 #import "Test.h"
   338 
   339 TestCase(IPAddress) {
   340     RequireTestCase(CollectionUtils);
   341     IPAddress *addr = [[IPAddress alloc] initWithIPv4: htonl(0x0A0001FE) port: 8080];
   342     CAssertEq(addr.ipv4,htonl(0x0A0001FE));
   343     CAssertEq(addr.port,8080);
   344     CAssertEqual(addr.hostname,@"10.0.1.254");
   345     CAssertEqual(addr.description,@"10.0.1.254:8080");
   346     CAssert(addr.isPrivate);
   347     
   348     addr = [[IPAddress alloc] initWithHostname: @"66.66.0.255" port: 123];
   349     CAssertEq(addr.class,[IPAddress class]);
   350     CAssertEq(addr.ipv4,htonl(0x424200FF));
   351     CAssertEq(addr.port,123);
   352     CAssertEqual(addr.hostname,@"66.66.0.255");
   353     CAssertEqual(addr.description,@"66.66.0.255:123");
   354     CAssert(!addr.isPrivate);
   355     
   356     addr = [[IPAddress alloc] initWithHostname: @"www.apple.com" port: 80];
   357     CAssertEq(addr.class,[HostAddress class]);
   358     Log(@"www.apple.com = 0x%08X", addr.ipv4);
   359     CAssertEq(addr.ipv4,htonl(0x1195A00A));
   360     CAssertEq(addr.port,80);
   361     CAssertEqual(addr.hostname,@"www.apple.com");
   362     CAssertEqual(addr.description,@"www.apple.com:80");
   363     CAssert(!addr.isPrivate);
   364 }
   365 
   366 
   367 /*
   368  Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   369  
   370  Redistribution and use in source and binary forms, with or without modification, are permitted
   371  provided that the following conditions are met:
   372  
   373  * Redistributions of source code must retain the above copyright notice, this list of conditions
   374  and the following disclaimer.
   375  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   376  and the following disclaimer in the documentation and/or other materials provided with the
   377  distribution.
   378  
   379  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   380  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   381  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   382  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   383  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   384   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   385  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   386  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   387  */