diff options
author | Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> | 2024-12-03 17:07:43 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-12-03 17:07:43 (GMT) |
commit | b58da409aac90123c1159916908a4c49144925ee (patch) | |
tree | 4880ae790486224a39296a7bd572065c15ddba89 | |
parent | b3a6042361123b05108af25dcb1ea34cd1fc5aa9 (diff) | |
download | cpython-b58da409aac90123c1159916908a4c49144925ee.zip cpython-b58da409aac90123c1159916908a4c49144925ee.tar.gz cpython-b58da409aac90123c1159916908a4c49144925ee.tar.bz2 |
[3.10] gh-122792: Make IPv4-mapped IPv6 address properties consistent with IPv4 (GH-122793) (GH-123819)
Make IPv4-mapped IPv6 address properties consistent with IPv4.
(cherry picked from commit 76a1c5d18312712baed4699fe7333abb050ec9b7)
Co-authored-by: Seth Michael Larson <seth@python.org>
Co-authored-by: Ćukasz Langa <lukasz@langa.pl>
-rw-r--r-- | Lib/ipaddress.py | 27 | ||||
-rw-r--r-- | Lib/test/test_ipaddress.py | 24 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Security/2024-08-07-10-42-13.gh-issue-122792.oiTMo9.rst | 3 |
3 files changed, 49 insertions, 5 deletions
diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 94b1761..ac284ca 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -310,7 +310,7 @@ def collapse_addresses(addresses): [IPv4Network('192.0.2.0/24')] Args: - addresses: An iterator of IPv4Network or IPv6Network objects. + addresses: An iterable of IPv4Network or IPv6Network objects. Returns: An iterator of the collapsed IPv(4|6)Network objects. @@ -1840,9 +1840,6 @@ class _BaseV6: def _explode_shorthand_ip_string(self): """Expand a shortened IPv6 address. - Args: - ip_str: A string, the IPv6 address. - Returns: A string, the expanded IPv6 address. @@ -1986,6 +1983,9 @@ class IPv6Address(_BaseV6, _BaseAddress): See RFC 2373 2.7 for details. """ + ipv4_mapped = self.ipv4_mapped + if ipv4_mapped is not None: + return ipv4_mapped.is_multicast return self in self._constants._multicast_network @property @@ -1997,6 +1997,9 @@ class IPv6Address(_BaseV6, _BaseAddress): reserved IPv6 Network ranges. """ + ipv4_mapped = self.ipv4_mapped + if ipv4_mapped is not None: + return ipv4_mapped.is_reserved return any(self in x for x in self._constants._reserved_networks) @property @@ -2007,6 +2010,9 @@ class IPv6Address(_BaseV6, _BaseAddress): A boolean, True if the address is reserved per RFC 4291. """ + ipv4_mapped = self.ipv4_mapped + if ipv4_mapped is not None: + return ipv4_mapped.is_link_local return self in self._constants._linklocal_network @property @@ -2063,6 +2069,9 @@ class IPv6Address(_BaseV6, _BaseAddress): ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10`` IPv4 range where they are both ``False``. """ + ipv4_mapped = self.ipv4_mapped + if ipv4_mapped is not None: + return ipv4_mapped.is_global return not self.is_private @property @@ -2074,6 +2083,9 @@ class IPv6Address(_BaseV6, _BaseAddress): RFC 2373 2.5.2. """ + ipv4_mapped = self.ipv4_mapped + if ipv4_mapped is not None: + return ipv4_mapped.is_unspecified return self._ip == 0 @property @@ -2085,6 +2097,9 @@ class IPv6Address(_BaseV6, _BaseAddress): RFC 2373 2.5.3. """ + ipv4_mapped = self.ipv4_mapped + if ipv4_mapped is not None: + return ipv4_mapped.is_loopback return self._ip == 1 @property @@ -2201,7 +2216,7 @@ class IPv6Interface(IPv6Address): @property def is_loopback(self): - return self._ip == 1 and self.network.is_loopback + return super().is_loopback and self.network.is_loopback class IPv6Network(_BaseV6, _BaseNetwork): @@ -2314,6 +2329,8 @@ class _IPv6Constants: IPv6Network('2001:db8::/32'), # IANA says N/A, let's consider it not globally reachable to be safe IPv6Network('2002::/16'), + # RFC 9637: https://www.rfc-editor.org/rfc/rfc9637.html#section-6-2.2 + IPv6Network('3fff::/20'), IPv6Network('fc00::/7'), IPv6Network('fe80::/10'), ] diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index 86f3bb5..47ad8f4 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -2415,6 +2415,30 @@ class IpaddrUnitTest(unittest.TestCase): self.assertEqual(ipaddress.ip_address('::ffff:c0a8:101').ipv4_mapped, ipaddress.ip_address('192.168.1.1')) + def testIpv4MappedProperties(self): + # Test that an IPv4 mapped IPv6 address has + # the same properties as an IPv4 address. + for addr4 in ( + "178.62.3.251", # global + "169.254.169.254", # link local + "127.0.0.1", # loopback + "224.0.0.1", # multicast + "192.168.0.1", # private + "0.0.0.0", # unspecified + "100.64.0.1", # public and not global + ): + with self.subTest(addr4): + ipv4 = ipaddress.IPv4Address(addr4) + ipv6 = ipaddress.IPv6Address(f"::ffff:{addr4}") + + self.assertEqual(ipv4.is_global, ipv6.is_global) + self.assertEqual(ipv4.is_private, ipv6.is_private) + self.assertEqual(ipv4.is_reserved, ipv6.is_reserved) + self.assertEqual(ipv4.is_multicast, ipv6.is_multicast) + self.assertEqual(ipv4.is_unspecified, ipv6.is_unspecified) + self.assertEqual(ipv4.is_link_local, ipv6.is_link_local) + self.assertEqual(ipv4.is_loopback, ipv6.is_loopback) + def testIpv4MappedPrivateCheck(self): self.assertEqual( True, ipaddress.ip_address('::ffff:192.168.1.1').is_private) diff --git a/Misc/NEWS.d/next/Security/2024-08-07-10-42-13.gh-issue-122792.oiTMo9.rst b/Misc/NEWS.d/next/Security/2024-08-07-10-42-13.gh-issue-122792.oiTMo9.rst new file mode 100644 index 0000000..18e293b --- /dev/null +++ b/Misc/NEWS.d/next/Security/2024-08-07-10-42-13.gh-issue-122792.oiTMo9.rst @@ -0,0 +1,3 @@ +Changed IPv4-mapped ``ipaddress.IPv6Address`` to consistently use the mapped IPv4 +address value for deciding properties. Properties which have their behavior fixed +are ``is_multicast``, ``is_reserved``, ``is_link_local``, ``is_global``, and ``is_unspecified``. |