From a86bf59c63cf8f1d59282e538cc5f5c3e650dbfa Mon Sep 17 00:00:00 2001 From: "pmoody@google.com" Date: Sat, 21 Jan 2012 03:05:30 +0000 Subject: tag/release 2.1.10 git-svn-id: https://ipaddr-py.googlecode.com/svn@244 09200d28-7f98-11dd-ad27-0f66e57d2035 --- branches/2.1.x/RELEASENOTES | 15 +- branches/2.1.x/ipaddr.py | 450 ++++++++++++++++++------------------------ branches/2.1.x/ipaddr_test.py | 166 ++++++++++------ 3 files changed, 314 insertions(+), 317 deletions(-) (limited to 'branches') diff --git a/branches/2.1.x/RELEASENOTES b/branches/2.1.x/RELEASENOTES index 34ebd13..fdfb9fc 100644 --- a/branches/2.1.x/RELEASENOTES +++ b/branches/2.1.x/RELEASENOTES @@ -4,11 +4,24 @@ Here are the visible changes for each release. +== 2.1.10 == + +(2012-01-20) + +Friday night, LAUNCH LAUNCH LAUNCH! + + * i84, fix iterhosts for /31's or /127's + * private method arg cleanup. + * i83, docstring issue. + * i87, new ipv4/ipv6 parser. patch from pmarks + * i90, fix copyright. + * bytes fix. patch from pmarks. + == 2.1.9 == (2011-02-22) -The last outstanding issue. +The last outstanding issues. * fix warnings from python3.2 * fix bug in _is_shorthand_ip resulting in bad teredo addresses. diff --git a/branches/2.1.x/ipaddr.py b/branches/2.1.x/ipaddr.py index 2cc4f59..ad27ae9 100644 --- a/branches/2.1.x/ipaddr.py +++ b/branches/2.1.x/ipaddr.py @@ -22,7 +22,7 @@ and networks. """ -__version__ = '2.1.9' +__version__ = '2.1.10' import struct @@ -134,7 +134,7 @@ def v4_int_to_packed(address): """ if address > _BaseV4._ALL_ONES: raise ValueError('Address too large for IPv4') - return struct.pack('!I', address) + return Bytes(struct.pack('!I', address)) def v6_int_to_packed(address): @@ -146,7 +146,7 @@ def v6_int_to_packed(address): Returns: The binary representation of this address. """ - return struct.pack('!QQ', address >> 64, address & (2**64 - 1)) + return Bytes(struct.pack('!QQ', address >> 64, address & (2**64 - 1))) def _find_address_range(addresses): @@ -270,12 +270,12 @@ def _collapse_address_list_recursive(addresses): Example: - ip1 = IPv4Network'1.1.0.0/24') - ip2 = IPv4Network'1.1.1.0/24') - ip3 = IPv4Network'1.1.2.0/24') - ip4 = IPv4Network'1.1.3.0/24') - ip5 = IPv4Network'1.1.4.0/24') - ip6 = IPv4Network'1.1.0.1/22') + ip1 = IPv4Network('1.1.0.0/24') + ip2 = IPv4Network('1.1.1.0/24') + ip3 = IPv4Network('1.1.2.0/24') + ip4 = IPv4Network('1.1.3.0/24') + ip5 = IPv4Network('1.1.4.0/24') + ip6 = IPv4Network('1.1.0.1/22') _collapse_address_list_recursive([ip1, ip2, ip3, ip4, ip5, ip6]) -> [IPv4Network('1.1.0.0/22'), IPv4Network('1.1.4.0/24')] @@ -368,15 +368,27 @@ def collapse_address_list(addresses): # backwards compatibility CollapseAddrList = collapse_address_list -# Test whether this Python implementation supports byte objects that -# are not identical to str ones. -# We need to exclude platforms where bytes == str so that we can -# distinguish between packed representations and strings, for example -# b'12::' (the IPv4 address 49.50.58.58) and '12::' (an IPv6 address). +# We need to distinguish between the string and packed-bytes representations +# of an IP address. For example, b'0::1' is the IPv4 address 48.58.58.49, +# while '0::1' is an IPv6 address. +# +# In Python 3, the native 'bytes' type already provides this functionality, +# so we use it directly. For earlier implementations where bytes is not a +# distinct type, we create a subclass of str to serve as a tag. +# +# Usage example (Python 2): +# ip = ipaddr.IPAddress(ipaddr.Bytes('xxxx')) +# +# Usage example (Python 3): +# ip = ipaddr.IPAddress(b'xxxx') try: - _compat_has_real_bytes = bytes is not str -except NameError: # 255 or (octet_str[0] == '0' and len(octet_str) > 1): + raise ValueError + return octet_int + def _string_from_ip_int(self, ip_int): """Turns a 32-bit integer into dotted decimal notation. @@ -1059,37 +1089,6 @@ class _BaseV4(object): ip_int >>= 8 return '.'.join(octets) - def _is_valid_ip(self, address): - """Validate the dotted decimal notation IP/netmask string. - - Args: - address: A string, either representing a quad-dotted ip - or an integer which is a valid IPv4 IP address. - - Returns: - A boolean, True if the string is a valid dotted decimal IP - string. - - """ - octets = address.split('.') - if len(octets) == 1: - # We have an integer rather than a dotted decimal IP. - try: - return int(address) >= 0 and int(address) <= self._ALL_ONES - except ValueError: - return False - - if len(octets) != 4: - return False - - for octet in octets: - try: - if not 0 <= int(octet) <= 255: - return False - except ValueError: - return False - return True - @property def max_prefixlen(self): return self._max_prefixlen @@ -1190,7 +1189,6 @@ class IPv4Address(_BaseV4, _BaseIP): AddressValueError: If ipaddr isn't a valid IPv4 address. """ - _BaseIP.__init__(self, address) _BaseV4.__init__(self, address) # Efficient constructor from integer. @@ -1201,17 +1199,16 @@ class IPv4Address(_BaseV4, _BaseIP): return # Constructing from a packed address - if _compat_has_real_bytes: - if isinstance(address, bytes) and len(address) == 4: - self._ip = struct.unpack('!I', address)[0] - return + if isinstance(address, Bytes): + try: + self._ip, = struct.unpack('!I', address) + except struct.error: + raise AddressValueError(address) # Wrong length. + return # Assume input argument to be string or any object representation # which converts into a formatted IP string. addr_str = str(address) - if not self._is_valid_ip(addr_str): - raise AddressValueError(addr_str) - self._ip = self._ip_int_from_string(addr_str) @@ -1276,25 +1273,14 @@ class IPv4Network(_BaseV4, _BaseNet): _BaseNet.__init__(self, address) _BaseV4.__init__(self, address) - # Efficient constructor from integer. - if isinstance(address, (int, long)): - self._ip = address - self.ip = IPv4Address(self._ip) + # Constructing from an integer or packed bytes. + if isinstance(address, (int, long, Bytes)): + self.ip = IPv4Address(address) + self._ip = self.ip._ip self._prefixlen = self._max_prefixlen self.netmask = IPv4Address(self._ALL_ONES) - if address < 0 or address > self._ALL_ONES: - raise AddressValueError(address) return - # Constructing from a packed address - if _compat_has_real_bytes: - if isinstance(address, bytes) and len(address) == 4: - self._ip = struct.unpack('!I', address)[0] - self.ip = IPv4Address(self._ip) - self._prefixlen = self._max_prefixlen - self.netmask = IPv4Address(self._ALL_ONES) - return - # Assume input argument to be string or any object representation # which converts into a formatted IP prefix string. addr = str(address).split('/') @@ -1302,9 +1288,6 @@ class IPv4Network(_BaseV4, _BaseNet): if len(addr) > 2: raise AddressValueError(address) - if not self._is_valid_ip(addr[0]): - raise AddressValueError(addr[0]) - self._ip = self._ip_int_from_string(addr[0]) self.ip = IPv4Address(self._ip) @@ -1338,6 +1321,8 @@ class IPv4Network(_BaseV4, _BaseNet): if self.ip != self.network: raise ValueError('%s has host bits set' % self.ip) + if self._prefixlen == (self._max_prefixlen - 1): + self.iterhosts = self.__iter__ def _is_hostmask(self, ip_str): """Test if the IP string is a hostmask (rather than a netmask). @@ -1403,12 +1388,14 @@ class _BaseV6(object): """ _ALL_ONES = (2**IPV6LENGTH) - 1 + _HEXTET_COUNT = 8 + _HEX_DIGITS = frozenset('0123456789ABCDEFabcdef') def __init__(self, address): self._version = 6 self._max_prefixlen = IPV6LENGTH - def _ip_int_from_string(self, ip_str=None): + def _ip_int_from_string(self, ip_str): """Turn an IPv6 ip_str into an integer. Args: @@ -1418,35 +1405,95 @@ class _BaseV6(object): A long, the IPv6 ip_str. Raises: - AddressValueError: if ip_str isn't a valid IP Address. + AddressValueError: if ip_str isn't a valid IPv6 Address. """ - if not ip_str: - ip_str = str(self.ip) + parts = ip_str.split(':') - ip_int = 0 - - # Do we have an IPv4 mapped (::ffff:a.b.c.d) or compact (::a.b.c.d) - # ip_str? - fields = ip_str.split(':') - if fields[-1].count('.') == 3: - ipv4_string = fields.pop() - ipv4_int = IPv4Network(ipv4_string)._ip - octets = [] - for _ in xrange(2): - octets.append(hex(ipv4_int & 0xFFFF).lstrip('0x').rstrip('L')) - ipv4_int >>= 16 - fields.extend(reversed(octets)) - ip_str = ':'.join(fields) - - fields = self._explode_shorthand_ip_string(ip_str).split(':') - for field in fields: - try: - ip_int = (ip_int << 16) + int(field or '0', 16) - except ValueError: + # An IPv6 address needs at least 2 colons (3 parts). + if len(parts) < 3: + raise AddressValueError(ip_str) + + # If the address has an IPv4-style suffix, convert it to hexadecimal. + if '.' in parts[-1]: + ipv4_int = IPv4Address(parts.pop())._ip + parts.append('%x' % ((ipv4_int >> 16) & 0xFFFF)) + parts.append('%x' % (ipv4_int & 0xFFFF)) + + # An IPv6 address can't have more than 8 colons (9 parts). + if len(parts) > self._HEXTET_COUNT + 1: + raise AddressValueError(ip_str) + + # Disregarding the endpoints, find '::' with nothing in between. + # This indicates that a run of zeroes has been skipped. + try: + skip_index, = ( + [i for i in xrange(1, len(parts) - 1) if not parts[i]] or + [None]) + except ValueError: + # Can't have more than one '::' + raise AddressValueError(ip_str) + + # parts_hi is the number of parts to copy from above/before the '::' + # parts_lo is the number of parts to copy from below/after the '::' + if skip_index is not None: + # If we found a '::', then check if it also covers the endpoints. + parts_hi = skip_index + parts_lo = len(parts) - skip_index - 1 + if not parts[0]: + parts_hi -= 1 + if parts_hi: + raise AddressValueError(ip_str) # ^: requires ^:: + if not parts[-1]: + parts_lo -= 1 + if parts_lo: + raise AddressValueError(ip_str) # :$ requires ::$ + parts_skipped = self._HEXTET_COUNT - (parts_hi + parts_lo) + if parts_skipped < 1: + raise AddressValueError(ip_str) + else: + # Otherwise, allocate the entire address to parts_hi. The endpoints + # could still be empty, but _parse_hextet() will check for that. + if len(parts) != self._HEXTET_COUNT: raise AddressValueError(ip_str) + parts_hi = len(parts) + parts_lo = 0 + parts_skipped = 0 + + try: + # Now, parse the hextets into a 128-bit integer. + ip_int = 0L + for i in xrange(parts_hi): + ip_int <<= 16 + ip_int |= self._parse_hextet(parts[i]) + ip_int <<= 16 * parts_skipped + for i in xrange(-parts_lo, 0): + ip_int <<= 16 + ip_int |= self._parse_hextet(parts[i]) + return ip_int + except ValueError: + raise AddressValueError(ip_str) - return ip_int + def _parse_hextet(self, hextet_str): + """Convert an IPv6 hextet string into an integer. + + Args: + hextet_str: A string, the number to parse. + + Returns: + The hextet as an integer. + + Raises: + ValueError: if the input isn't strictly a hex number from [0..FFFF]. + + """ + # Whitelist the characters, since int() allows a lot of bizarre stuff. + if not self._HEX_DIGITS.issuperset(hextet_str): + raise ValueError + hextet_int = int(hextet_str, 16) + if hextet_int > 0xFFFF: + raise ValueError + return hextet_int def _compress_hextets(self, hextets): """Compresses a list of hextets. @@ -1522,7 +1569,7 @@ class _BaseV6(object): hextets = self._compress_hextets(hextets) return ':'.join(hextets) - def _explode_shorthand_ip_string(self, ip_str=None): + def _explode_shorthand_ip_string(self): """Expand a shortened IPv6 address. Args: @@ -1532,108 +1579,20 @@ class _BaseV6(object): A string, the expanded IPv6 address. """ - if not ip_str: + if isinstance(self, _BaseNet): + ip_str = str(self.ip) + else: ip_str = str(self) - if isinstance(self, _BaseNet): - ip_str = str(self.ip) - - if self._is_shorthand_ip(ip_str): - new_ip = [] - hextet = ip_str.split('::') - - if len(hextet) > 1: - sep = len(hextet[0].split(':')) + len(hextet[1].split(':')) - new_ip = hextet[0].split(':') - - for _ in xrange(8 - sep): - new_ip.append('0000') - new_ip += hextet[1].split(':') - - else: - new_ip = ip_str.split(':') - # Now need to make sure every hextet is 4 lower case characters. - # If a hextet is < 4 characters, we've got missing leading 0's. - ret_ip = [] - for hextet in new_ip: - ret_ip.append(('0' * (4 - len(hextet)) + hextet).lower()) - return ':'.join(ret_ip) - # We've already got a longhand ip_str. - return ip_str - - def _is_valid_ip(self, ip_str): - """Ensure we have a valid IPv6 address. - - Probably not as exhaustive as it should be. - - Args: - ip_str: A string, the IPv6 address. - - Returns: - A boolean, True if this is a valid IPv6 address. - - """ - # We need to have at least one ':'. - if ':' not in ip_str: - return False - - # We can only have one '::' shortener. - if ip_str.count('::') > 1: - return False - - # '::' should be encompassed by start, digits or end. - if ':::' in ip_str: - return False - - # A single colon can neither start nor end an address. - if ((ip_str.startswith(':') and not ip_str.startswith('::')) or - (ip_str.endswith(':') and not ip_str.endswith('::'))): - return False - - # If we have no concatenation, we need to have 8 fields with 7 ':'. - if '::' not in ip_str and ip_str.count(':') != 7: - # We might have an IPv4 mapped address. - if ip_str.count('.') != 3: - return False - - ip_str = self._explode_shorthand_ip_string(ip_str) - - # Now that we have that all squared away, let's check that each of the - # hextets are between 0x0 and 0xFFFF. - for hextet in ip_str.split(':'): - if hextet.count('.') == 3: - # If we have an IPv4 mapped address, the IPv4 portion has to - # be at the end of the IPv6 portion. - if not ip_str.split(':')[-1] == hextet: - return False - try: - IPv4Network(hextet) - except AddressValueError: - return False - else: - try: - # a value error here means that we got a bad hextet, - # something like 0xzzzz - if int(hextet, 16) < 0x0 or int(hextet, 16) > 0xFFFF: - return False - except ValueError: - return False - return True - - def _is_shorthand_ip(self, ip_str=None): - """Determine if the address is shortened. - Args: - ip_str: A string, the IPv6 address. - - Returns: - A boolean, True if the address is shortened. - - """ - if ip_str.count('::') == 1: - return True - if filter(lambda x: len(x) < 4, ip_str.split(':')): - return True - return False + ip_int = self._ip_int_from_string(ip_str) + parts = [] + for i in xrange(self._HEXTET_COUNT): + parts.append('%04x' % (ip_int & 0xFFFF)) + ip_int >>= 16 + parts.reverse() + if isinstance(self, _BaseNet): + return '%s/%d' % (':'.join(parts), self.prefixlen) + return ':'.join(parts) @property def max_prefixlen(self): @@ -1749,13 +1708,9 @@ class _BaseV6(object): IPv4 mapped address. Return None otherwise. """ - hextets = self._explode_shorthand_ip_string().split(':') - if hextets[-3] != 'ffff': - return None - try: - return IPv4Address(int('%s%s' % (hextets[-2], hextets[-1]), 16)) - except AddressValueError: + if (self._ip >> 32) != 0xFFFF: return None + return IPv4Address(self._ip & 0xFFFFFFFF) @property def teredo(self): @@ -1764,14 +1719,13 @@ class _BaseV6(object): Returns: Tuple of the (server, client) IPs or None if the address doesn't appear to be a teredo address (doesn't start with - 2001) + 2001::/32) """ - bits = self._explode_shorthand_ip_string().split(':') - if not bits[0] == '2001': + if (self._ip >> 96) != 0x20010000: return None - return (IPv4Address(int(''.join(bits[2:4]), 16)), - IPv4Address(int(''.join(bits[6:]), 16) ^ 0xFFFFFFFF)) + return (IPv4Address((self._ip >> 64) & 0xFFFFFFFF), + IPv4Address(~self._ip & 0xFFFFFFFF)) @property def sixtofour(self): @@ -1782,10 +1736,9 @@ class _BaseV6(object): address doesn't appear to contain a 6to4 embedded address. """ - bits = self._explode_shorthand_ip_string().split(':') - if not bits[0] == '2002': + if (self._ip >> 112) != 0x2002: return None - return IPv4Address(int(''.join(bits[1:3]), 16)) + return IPv4Address((self._ip >> 80) & 0xFFFFFFFF) class IPv6Address(_BaseV6, _BaseIP): @@ -1810,7 +1763,6 @@ class IPv6Address(_BaseV6, _BaseIP): AddressValueError: If address isn't a valid IPv6 address. """ - _BaseIP.__init__(self, address) _BaseV6.__init__(self, address) # Efficient constructor from integer. @@ -1821,11 +1773,13 @@ class IPv6Address(_BaseV6, _BaseIP): return # Constructing from a packed address - if _compat_has_real_bytes: - if isinstance(address, bytes) and len(address) == 16: - tmp = struct.unpack('!QQ', address) - self._ip = (tmp[0] << 64) | tmp[1] - return + if isinstance(address, Bytes): + try: + hi, lo = struct.unpack('!QQ', address) + except struct.error: + raise AddressValueError(address) # Wrong length. + self._ip = (hi << 64) | lo + return # Assume input argument to be string or any object representation # which converts into a formatted IP string. @@ -1833,9 +1787,6 @@ class IPv6Address(_BaseV6, _BaseIP): if not addr_str: raise AddressValueError('') - if not self._is_valid_ip(addr_str): - raise AddressValueError(addr_str) - self._ip = self._ip_int_from_string(addr_str) @@ -1889,26 +1840,14 @@ class IPv6Network(_BaseV6, _BaseNet): _BaseNet.__init__(self, address) _BaseV6.__init__(self, address) - # Efficient constructor from integer. - if isinstance(address, (int, long)): - self._ip = address - self.ip = IPv6Address(self._ip) + # Constructing from an integer or packed bytes. + if isinstance(address, (int, long, Bytes)): + self.ip = IPv6Address(address) + self._ip = self.ip._ip self._prefixlen = self._max_prefixlen self.netmask = IPv6Address(self._ALL_ONES) - if address < 0 or address > self._ALL_ONES: - raise AddressValueError(address) return - # Constructing from a packed address - if _compat_has_real_bytes: - if isinstance(address, bytes) and len(address) == 16: - tmp = struct.unpack('!QQ', address) - self._ip = (tmp[0] << 64) | tmp[1] - self.ip = IPv6Address(self._ip) - self._prefixlen = self._max_prefixlen - self.netmask = IPv6Address(self._ALL_ONES) - return - # Assume input argument to be string or any object representation # which converts into a formatted IP prefix string. addr = str(address).split('/') @@ -1916,8 +1855,8 @@ class IPv6Network(_BaseV6, _BaseNet): if len(addr) > 2: raise AddressValueError(address) - if not self._is_valid_ip(addr[0]): - raise AddressValueError(addr[0]) + self._ip = self._ip_int_from_string(addr[0]) + self.ip = IPv6Address(self._ip) if len(addr) == 2: if self._is_valid_netmask(addr[1]): @@ -1929,13 +1868,12 @@ class IPv6Network(_BaseV6, _BaseNet): self.netmask = IPv6Address(self._ip_int_from_prefix(self._prefixlen)) - self._ip = self._ip_int_from_string(addr[0]) - self.ip = IPv6Address(self._ip) - if strict: if self.ip != self.network: raise ValueError('%s has host bits set' % self.ip) + if self._prefixlen == (self._max_prefixlen - 1): + self.iterhosts = self.__iter__ def _is_valid_netmask(self, prefixlen): """Verify that the netmask/prefixlen is valid. diff --git a/branches/2.1.x/ipaddr_test.py b/branches/2.1.x/ipaddr_test.py index 64bc2b4..9446889 100755 --- a/branches/2.1.x/ipaddr_test.py +++ b/branches/2.1.x/ipaddr_test.py @@ -23,10 +23,10 @@ import time import ipaddr # Compatibility function to cast str to bytes objects -if ipaddr._compat_has_real_bytes: - _cb = lambda bytestr: bytes(bytestr, 'charmap') +if issubclass(ipaddr.Bytes, str): + _cb = ipaddr.Bytes else: - _cb = str + _cb = lambda bytestr: bytes(bytestr, 'charmap') class IpaddrUnitTest(unittest.TestCase): @@ -68,25 +68,72 @@ class IpaddrUnitTest(unittest.TestCase): ipaddr.IPv6Address('::1')) def testInvalidStrings(self): - self.assertRaises(ValueError, ipaddr.IPNetwork, '') - self.assertRaises(ValueError, ipaddr.IPNetwork, 'www.google.com') - self.assertRaises(ValueError, ipaddr.IPNetwork, '1.2.3') - self.assertRaises(ValueError, ipaddr.IPNetwork, '1.2.3.4.5') - self.assertRaises(ValueError, ipaddr.IPNetwork, '301.2.2.2') - self.assertRaises(ValueError, ipaddr.IPNetwork, '1:2:3:4:5:6:7') - self.assertRaises(ValueError, ipaddr.IPNetwork, '1:2:3:4:5:6:7:') - self.assertRaises(ValueError, ipaddr.IPNetwork, ':2:3:4:5:6:7:8') - self.assertRaises(ValueError, ipaddr.IPNetwork, '1:2:3:4:5:6:7:8:9') - self.assertRaises(ValueError, ipaddr.IPNetwork, '1:2:3:4:5:6:7:8:') - self.assertRaises(ValueError, ipaddr.IPNetwork, '1::3:4:5:6::8') - self.assertRaises(ValueError, ipaddr.IPNetwork, 'a:') - self.assertRaises(ValueError, ipaddr.IPNetwork, ':') - self.assertRaises(ValueError, ipaddr.IPNetwork, ':::') - self.assertRaises(ValueError, ipaddr.IPNetwork, '::a:') - self.assertRaises(ValueError, ipaddr.IPNetwork, '1ffff::') - self.assertRaises(ValueError, ipaddr.IPNetwork, '0xa::') - self.assertRaises(ValueError, ipaddr.IPNetwork, '1:2:3:4:5:6:1a.2.3.4') - self.assertRaises(ValueError, ipaddr.IPNetwork, '1:2:3:4:5:1.2.3.4:8') + def AssertInvalidIP(ip_str): + self.assertRaises(ValueError, ipaddr.IPAddress, ip_str) + AssertInvalidIP("") + AssertInvalidIP("016.016.016.016") + AssertInvalidIP("016.016.016") + AssertInvalidIP("016.016") + AssertInvalidIP("016") + AssertInvalidIP("000.000.000.000") + AssertInvalidIP("000") + AssertInvalidIP("0x0a.0x0a.0x0a.0x0a") + AssertInvalidIP("0x0a.0x0a.0x0a") + AssertInvalidIP("0x0a.0x0a") + AssertInvalidIP("0x0a") + AssertInvalidIP("42.42.42.42.42") + AssertInvalidIP("42.42.42") + AssertInvalidIP("42.42") + AssertInvalidIP("42") + AssertInvalidIP("42..42.42") + AssertInvalidIP("42..42.42.42") + AssertInvalidIP("42.42.42.42.") + AssertInvalidIP("42.42.42.42...") + AssertInvalidIP(".42.42.42.42") + AssertInvalidIP("...42.42.42.42") + AssertInvalidIP("42.42.42.-0") + AssertInvalidIP("42.42.42.+0") + AssertInvalidIP(".") + AssertInvalidIP("...") + AssertInvalidIP("bogus") + AssertInvalidIP("bogus.com") + AssertInvalidIP("192.168.0.1.com") + AssertInvalidIP("12345.67899.-54321.-98765") + AssertInvalidIP("257.0.0.0") + AssertInvalidIP("42.42.42.-42") + AssertInvalidIP("3ffe::1.net") + AssertInvalidIP("3ffe::1::1") + AssertInvalidIP("1::2::3::4:5") + AssertInvalidIP("::7:6:5:4:3:2:") + AssertInvalidIP(":6:5:4:3:2:1::") + AssertInvalidIP("2001::db:::1") + AssertInvalidIP("FEDC:9878") + AssertInvalidIP("+1.+2.+3.4") + AssertInvalidIP("1.2.3.4e0") + AssertInvalidIP("::7:6:5:4:3:2:1:0") + AssertInvalidIP("7:6:5:4:3:2:1:0::") + AssertInvalidIP("9:8:7:6:5:4:3::2:1") + AssertInvalidIP("0:1:2:3::4:5:6:7") + AssertInvalidIP("3ffe:0:0:0:0:0:0:0:1") + AssertInvalidIP("3ffe::10000") + AssertInvalidIP("3ffe::goog") + AssertInvalidIP("3ffe::-0") + AssertInvalidIP("3ffe::+0") + AssertInvalidIP("3ffe::-1") + AssertInvalidIP(":") + AssertInvalidIP(":::") + AssertInvalidIP("::1.2.3") + AssertInvalidIP("::1.2.3.4.5") + AssertInvalidIP("::1.2.3.4:") + AssertInvalidIP("1.2.3.4::") + AssertInvalidIP("2001:db8::1:") + AssertInvalidIP(":2001:db8::1") + AssertInvalidIP(":1:2:3:4:5:6:7") + AssertInvalidIP("1:2:3:4:5:6:7:") + AssertInvalidIP(":1:2:3:4:5:6:") + AssertInvalidIP("192.0.2.1/32") + AssertInvalidIP("2001:db8::1/128") + self.assertRaises(ipaddr.AddressValueError, ipaddr.IPv4Network, '') self.assertRaises(ipaddr.AddressValueError, ipaddr.IPv4Network, 'google.com') @@ -188,26 +235,25 @@ class IpaddrUnitTest(unittest.TestCase): self.assertEqual(ipaddr.IPNetwork(self.ipv4.ip).version, 4) self.assertEqual(ipaddr.IPNetwork(self.ipv6.ip).version, 6) - if ipaddr._compat_has_real_bytes: # on python3+ - def testIpFromPacked(self): - ip = ipaddr.IPNetwork - - self.assertEqual(self.ipv4.ip, - ip(_cb('\x01\x02\x03\x04')).ip) - self.assertEqual(ip('255.254.253.252'), - ip(_cb('\xff\xfe\xfd\xfc'))) - self.assertRaises(ValueError, ipaddr.IPNetwork, _cb('\x00' * 3)) - self.assertRaises(ValueError, ipaddr.IPNetwork, _cb('\x00' * 5)) - self.assertEqual(self.ipv6.ip, - ip(_cb('\x20\x01\x06\x58\x02\x2a\xca\xfe' - '\x02\x00\x00\x00\x00\x00\x00\x01')).ip) - self.assertEqual(ip('ffff:2:3:4:ffff::'), - ip(_cb('\xff\xff\x00\x02\x00\x03\x00\x04' + - '\xff\xff' + '\x00' * 6))) - self.assertEqual(ip('::'), - ip(_cb('\x00' * 16))) - self.assertRaises(ValueError, ip, _cb('\x00' * 15)) - self.assertRaises(ValueError, ip, _cb('\x00' * 17)) + def testIpFromPacked(self): + ip = ipaddr.IPNetwork + + self.assertEqual(self.ipv4.ip, + ip(_cb('\x01\x02\x03\x04')).ip) + self.assertEqual(ip('255.254.253.252'), + ip(_cb('\xff\xfe\xfd\xfc'))) + self.assertRaises(ValueError, ipaddr.IPNetwork, _cb('\x00' * 3)) + self.assertRaises(ValueError, ipaddr.IPNetwork, _cb('\x00' * 5)) + self.assertEqual(self.ipv6.ip, + ip(_cb('\x20\x01\x06\x58\x02\x2a\xca\xfe' + '\x02\x00\x00\x00\x00\x00\x00\x01')).ip) + self.assertEqual(ip('ffff:2:3:4:ffff::'), + ip(_cb('\xff\xff\x00\x02\x00\x03\x00\x04' + + '\xff\xff' + '\x00' * 6))) + self.assertEqual(ip('::'), + ip(_cb('\x00' * 16))) + self.assertRaises(ValueError, ip, _cb('\x00' * 15)) + self.assertRaises(ValueError, ip, _cb('\x00' * 17)) def testGetIp(self): self.assertEqual(int(self.ipv4.ip), 16909060) @@ -287,6 +333,11 @@ class IpaddrUnitTest(unittest.TestCase): self.assertEqual(self.ipv4.subnet(), list(self.ipv4.iter_subnets())) self.assertEqual(self.ipv6.subnet(), list(self.ipv6.iter_subnets())) + def testIterHosts(self): + self.assertEqual([ipaddr.IPv4Address('2.0.0.0'), + ipaddr.IPv4Address('2.0.0.1')], + list(ipaddr.IPNetwork('2.0.0.0/31').iterhosts())) + def testFancySubnetting(self): self.assertEqual(sorted(self.ipv4.subnet(prefixlen_diff=3)), sorted(self.ipv4.subnet(new_prefix=27))) @@ -893,7 +944,7 @@ class IpaddrUnitTest(unittest.TestCase): '2001:0:0:4:0:0:0:8': '2001:0:0:4::8/128', '2001:0:0:4:5:6:7:8': '2001::4:5:6:7:8/128', '2001:0:3:4:5:6:7:8': '2001:0:3:4:5:6:7:8/128', - '2001:0::3:4:5:6:7:8': '2001:0:3:4:5:6:7:8/128', + '2001:0:3:4:5:6:7:8': '2001:0:3:4:5:6:7:8/128', '0:0:3:0:0:0:0:ffff': '0:0:3::ffff/128', '0:0:0:4:0:0:0:ffff': '::4:0:0:0:ffff/128', '0:0:0:0:5:0:0:ffff': '::5:0:0:ffff/128', @@ -903,6 +954,12 @@ class IpaddrUnitTest(unittest.TestCase): '0:0:0:0:0:0:0:1': '::1/128', '2001:0658:022a:cafe:0000:0000:0000:0000/66': '2001:658:22a:cafe::/66', + '::1.2.3.4': '::102:304/128', + '1:2:3:4:5:ffff:1.2.3.4': '1:2:3:4:5:ffff:102:304/128', + '::7:6:5:4:3:2:1': '0:7:6:5:4:3:2:1/128', + '::7:6:5:4:3:2:0': '0:7:6:5:4:3:2:0/128', + '7:6:5:4:3:2:1::': '7:6:5:4:3:2:1:0/128', + '0:6:5:4:3:2:1::': '0:6:5:4:3:2:1:0/128', } for uncompressed, compressed in test_addresses.items(): self.assertEqual(compressed, str(ipaddr.IPv6Network(uncompressed))) @@ -910,9 +967,9 @@ class IpaddrUnitTest(unittest.TestCase): def testExplodeShortHandIpStr(self): addr1 = ipaddr.IPv6Network('2001::1') addr2 = ipaddr.IPv6Address('2001:0:5ef5:79fd:0:59d:a0e5:ba1') - self.assertEqual('2001:0000:0000:0000:0000:0000:0000:0001', - addr1._explode_shorthand_ip_string(str(addr1.ip))) - self.assertEqual('0000:0000:0000:0000:0000:0000:0000:0001', + self.assertEqual('2001:0000:0000:0000:0000:0000:0000:0001/128', + addr1.exploded) + self.assertEqual('0000:0000:0000:0000:0000:0000:0000:0001/128', ipaddr.IPv6Network('::1/128').exploded) # issue 77 self.assertEqual('2001:0000:5ef5:79fd:0000:059d:a0e5:0ba1', @@ -957,7 +1014,7 @@ class IpaddrUnitTest(unittest.TestCase): self.assertEqual(ipaddr.IPNetwork('::/121').Supernet(), ipaddr.IPNetwork('::/120')) - self.assertEqual(ipaddr.IPNetwork('10.0.0.02').IsRFC1918(), True) + self.assertEqual(ipaddr.IPNetwork('10.0.0.2').IsRFC1918(), True) self.assertEqual(ipaddr.IPNetwork('10.0.0.0').IsMulticast(), False) self.assertEqual(ipaddr.IPNetwork('127.255.255.255').IsLoopback(), True) self.assertEqual(ipaddr.IPNetwork('169.255.255.255').IsLinkLocal(), @@ -1017,19 +1074,6 @@ class IpaddrUnitTest(unittest.TestCase): self.assertTrue(self.ipv6._cache.has_key('broadcast')) self.assertTrue(self.ipv6._cache.has_key('hostmask')) - def testIsValidIp(self): - ip = ipaddr.IPv6Address('::') - self.assertTrue(ip._is_valid_ip('2001:658:22a:cafe:200::1')) - self.assertTrue(ip._is_valid_ip('::ffff:10.10.0.0')) - self.assertTrue(ip._is_valid_ip('::ffff:192.168.0.0')) - self.assertFalse(ip._is_valid_ip('2001:658:22a::::1')) - self.assertFalse(ip._is_valid_ip(':658:22a:cafe:200::1')) - self.assertFalse(ip._is_valid_ip('2001:658:22a:cafe:200:')) - self.assertFalse(ip._is_valid_ip('2001:658:22a:cafe:200:127.0.0.1::1')) - self.assertFalse(ip._is_valid_ip('2001:658:22a:cafe:200::127.0.1')) - self.assertFalse(ip._is_valid_ip('2001:658:22a:zzzz:200::1')) - self.assertFalse(ip._is_valid_ip('2001:658:22a:cafe1:200::1')) - def testTeredo(self): # stolen from wikipedia server = ipaddr.IPv4Address('65.54.227.120') @@ -1039,6 +1083,8 @@ class IpaddrUnitTest(unittest.TestCase): ipaddr.IPAddress(teredo_addr).teredo) bad_addr = '2000::4136:e378:8000:63bf:3fff:fdd2' self.assertFalse(ipaddr.IPAddress(bad_addr).teredo) + bad_addr = '2001:0001:4136:e378:8000:63bf:3fff:fdd2' + self.assertFalse(ipaddr.IPAddress(bad_addr).teredo) # i77 teredo_addr = ipaddr.IPv6Address('2001:0:5ef5:79fd:0:59d:a0e5:ba1') -- cgit v1.2.1