Skip to content

Commit 66a01b0

Browse files
miss-islingtonsethmlarsonambv
authored
[3.11] gh-122792: Make IPv4-mapped IPv6 address properties consistent with IPv4 (GH-122793) (GH-123818)
Make IPv4-mapped IPv6 address properties consistent with IPv4. (cherry picked from commit 76a1c5d) Co-authored-by: Seth Michael Larson <[email protected]> --------- Co-authored-by: Seth Michael Larson <[email protected]> Co-authored-by: Łukasz Langa <[email protected]>
1 parent 2e161e2 commit 66a01b0

File tree

3 files changed

+49
-5
lines changed

3 files changed

+49
-5
lines changed

Lib/ipaddress.py

+22-5
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def collapse_addresses(addresses):
310310
[IPv4Network('192.0.2.0/24')]
311311
312312
Args:
313-
addresses: An iterator of IPv4Network or IPv6Network objects.
313+
addresses: An iterable of IPv4Network or IPv6Network objects.
314314
315315
Returns:
316316
An iterator of the collapsed IPv(4|6)Network objects.
@@ -1855,9 +1855,6 @@ def _string_from_ip_int(cls, ip_int=None):
18551855
def _explode_shorthand_ip_string(self):
18561856
"""Expand a shortened IPv6 address.
18571857
1858-
Args:
1859-
ip_str: A string, the IPv6 address.
1860-
18611858
Returns:
18621859
A string, the expanded IPv6 address.
18631860
@@ -2004,6 +2001,9 @@ def is_multicast(self):
20042001
See RFC 2373 2.7 for details.
20052002
20062003
"""
2004+
ipv4_mapped = self.ipv4_mapped
2005+
if ipv4_mapped is not None:
2006+
return ipv4_mapped.is_multicast
20072007
return self in self._constants._multicast_network
20082008

20092009
@property
@@ -2015,6 +2015,9 @@ def is_reserved(self):
20152015
reserved IPv6 Network ranges.
20162016
20172017
"""
2018+
ipv4_mapped = self.ipv4_mapped
2019+
if ipv4_mapped is not None:
2020+
return ipv4_mapped.is_reserved
20182021
return any(self in x for x in self._constants._reserved_networks)
20192022

20202023
@property
@@ -2025,6 +2028,9 @@ def is_link_local(self):
20252028
A boolean, True if the address is reserved per RFC 4291.
20262029
20272030
"""
2031+
ipv4_mapped = self.ipv4_mapped
2032+
if ipv4_mapped is not None:
2033+
return ipv4_mapped.is_link_local
20282034
return self in self._constants._linklocal_network
20292035

20302036
@property
@@ -2081,6 +2087,9 @@ def is_global(self):
20812087
``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
20822088
IPv4 range where they are both ``False``.
20832089
"""
2090+
ipv4_mapped = self.ipv4_mapped
2091+
if ipv4_mapped is not None:
2092+
return ipv4_mapped.is_global
20842093
return not self.is_private
20852094

20862095
@property
@@ -2092,6 +2101,9 @@ def is_unspecified(self):
20922101
RFC 2373 2.5.2.
20932102
20942103
"""
2104+
ipv4_mapped = self.ipv4_mapped
2105+
if ipv4_mapped is not None:
2106+
return ipv4_mapped.is_unspecified
20952107
return self._ip == 0
20962108

20972109
@property
@@ -2103,6 +2115,9 @@ def is_loopback(self):
21032115
RFC 2373 2.5.3.
21042116
21052117
"""
2118+
ipv4_mapped = self.ipv4_mapped
2119+
if ipv4_mapped is not None:
2120+
return ipv4_mapped.is_loopback
21062121
return self._ip == 1
21072122

21082123
@property
@@ -2219,7 +2234,7 @@ def is_unspecified(self):
22192234

22202235
@property
22212236
def is_loopback(self):
2222-
return self._ip == 1 and self.network.is_loopback
2237+
return super().is_loopback and self.network.is_loopback
22232238

22242239

22252240
class IPv6Network(_BaseV6, _BaseNetwork):
@@ -2332,6 +2347,8 @@ class _IPv6Constants:
23322347
IPv6Network('2001:db8::/32'),
23332348
# IANA says N/A, let's consider it not globally reachable to be safe
23342349
IPv6Network('2002::/16'),
2350+
# RFC 9637: https://www.rfc-editor.org/rfc/rfc9637.html#section-6-2.2
2351+
IPv6Network('3fff::/20'),
23352352
IPv6Network('fc00::/7'),
23362353
IPv6Network('fe80::/10'),
23372354
]

Lib/test/test_ipaddress.py

+24
Original file line numberDiff line numberDiff line change
@@ -2421,6 +2421,30 @@ def testIpv4Mapped(self):
24212421
self.assertEqual(ipaddress.ip_address('::ffff:c0a8:101').ipv4_mapped,
24222422
ipaddress.ip_address('192.168.1.1'))
24232423

2424+
def testIpv4MappedProperties(self):
2425+
# Test that an IPv4 mapped IPv6 address has
2426+
# the same properties as an IPv4 address.
2427+
for addr4 in (
2428+
"178.62.3.251", # global
2429+
"169.254.169.254", # link local
2430+
"127.0.0.1", # loopback
2431+
"224.0.0.1", # multicast
2432+
"192.168.0.1", # private
2433+
"0.0.0.0", # unspecified
2434+
"100.64.0.1", # public and not global
2435+
):
2436+
with self.subTest(addr4):
2437+
ipv4 = ipaddress.IPv4Address(addr4)
2438+
ipv6 = ipaddress.IPv6Address(f"::ffff:{addr4}")
2439+
2440+
self.assertEqual(ipv4.is_global, ipv6.is_global)
2441+
self.assertEqual(ipv4.is_private, ipv6.is_private)
2442+
self.assertEqual(ipv4.is_reserved, ipv6.is_reserved)
2443+
self.assertEqual(ipv4.is_multicast, ipv6.is_multicast)
2444+
self.assertEqual(ipv4.is_unspecified, ipv6.is_unspecified)
2445+
self.assertEqual(ipv4.is_link_local, ipv6.is_link_local)
2446+
self.assertEqual(ipv4.is_loopback, ipv6.is_loopback)
2447+
24242448
def testIpv4MappedPrivateCheck(self):
24252449
self.assertEqual(
24262450
True, ipaddress.ip_address('::ffff:192.168.1.1').is_private)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Changed IPv4-mapped ``ipaddress.IPv6Address`` to consistently use the mapped IPv4
2+
address value for deciding properties. Properties which have their behavior fixed
3+
are ``is_multicast``, ``is_reserved``, ``is_link_local``, ``is_global``, and ``is_unspecified``.

0 commit comments

Comments
 (0)