From 2093d2042cff3bc8a327c066fdd35f42620c8535 Mon Sep 17 00:00:00 2001 From: Alex Perry Date: Mon, 22 May 2017 16:19:34 -0700 Subject: [PATCH] bpo-27269: Treat ::FFFF: addresses as IPv4 for ordering and contains. --- Lib/ipaddress.py | 39 +++++++++++++++++++++++++------------- Lib/test/test_ipaddress.py | 7 +++++-- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 70746f8de85c09..f2d941aa68e8a3 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -376,8 +376,10 @@ def get_mixed_type_key(obj): """ if isinstance(obj, _BaseNetwork): + obj = obj.ipv4_mapped or obj return obj._get_networks_key() elif isinstance(obj, _BaseAddress): + obj = obj.ipv4_mapped or obj return obj._get_address_key() return NotImplemented @@ -423,6 +425,15 @@ def _check_int_address(self, address): raise AddressValueError(msg % (address, self._max_prefixlen, self._version)) + @property + def ipv4_mapped(self): + """Returns an IPv4 address, maybe mapped by RFC 5156 from ::ffff: + + Returns: + An IPv4Address instance or None + """ + return None + def _check_packed_address(self, address, expected_len): address_len = len(address) if address_len != expected_len: @@ -668,6 +679,8 @@ def __hash__(self): return hash(int(self.network_address) ^ int(self.netmask)) def __contains__(self, other): + if other._version == 6 and self._version == 4: + other = other.ipv4_mapped or other # always false if one is v4 and the other is v6. if self._version != other._version: return False @@ -1916,6 +1929,19 @@ def __init__(self, address): raise AddressValueError("Unexpected '/' in %r" % address) self._ip = self._ip_int_from_string(addr_str) + @property + def ipv4_mapped(self): + """Return the IPv4 mapped address. + + Returns: + If the IPv6 address is a v4 mapped address, return the + IPv4 mapped address. Return None otherwise. + + """ + if (self._ip >> 32) != 0xFFFF: + return None + return IPv4Address(self._ip & 0xFFFFFFFF) + @property def packed(self): """The binary representation of this address.""" @@ -2012,19 +2038,6 @@ def is_loopback(self): """ return self._ip == 1 - @property - def ipv4_mapped(self): - """Return the IPv4 mapped address. - - Returns: - If the IPv6 address is a v4 mapped address, return the - IPv4 mapped address. Return None otherwise. - - """ - if (self._ip >> 32) != 0xFFFF: - return None - return IPv4Address(self._ip & 0xFFFFFFFF) - @property def teredo(self): """Tuple of embedded teredo IPs. diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index 5d9633024f7064..3e375ce81cd37f 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -583,7 +583,10 @@ class ComparisonTests(unittest.TestCase): v6net = ipaddress.IPv6Network(1) v6intf = ipaddress.IPv6Interface(1) - v4_addresses = [v4addr, v4intf] + v4mapaddr = ipaddress.IPv6Address('::ffff:0:1') + v4mapnet = ipaddress.IPv6Address('::ffff:0:1') + + v4_addresses = [v4addr, v4intf, v4mapaddr] v4_objects = v4_addresses + [v4net] v6_addresses = [v6addr, v6intf] v6_objects = v6_addresses + [v6net] @@ -684,7 +687,7 @@ def test_foreign_type_ordering(self): def test_mixed_type_key(self): # with get_mixed_type_key, you can sort addresses and network. - v4_ordered = [self.v4addr, self.v4net, self.v4intf] + v4_ordered = [self.v4addr, self.v4mapaddr, self.v4net, self.v4intf] v6_ordered = [self.v6addr, self.v6net, self.v6intf] self.assertEqual(v4_ordered, sorted(self.v4_objects,