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 <seth@python.org> --------- Co-authored-by: Seth Michael Larson <seth@python.org> Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent 2e161e2 commit 66a01b0

File tree

3 files changed

+49
-5
lines changed

3 files changed

+49
-5
lines changed

Lib/ipaddress.py

Lines changed: 22 additions & 5 deletions
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

Lines changed: 24 additions & 0 deletions
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)
Lines changed: 3 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy