From 35c5efbb3cc849411ac08941b31586c5f11e98a4 Mon Sep 17 00:00:00 2001 From: Faidon Liambotis Date: Fri, 5 Apr 2024 15:25:17 +0300 Subject: [PATCH 1/2] gh-117566: fix IPv6Address.is_loopback for IPv4-mapped loopbacks While properties like IPv6Address.is_private account for IPv4-mapped IPv6 addresses, such as for example: >>> ipaddress.ip_address("192.168.0.1").is_private True >>> ipaddress.ip_address("::ffff:192.168.0.1").is_private True ...the same doesn't currently apply to the is_loopback property: >>> ipaddress.ip_address("127.0.0.1").is_loopback True >>> ipaddress.ip_address("::ffff:127.0.0.1").is_loopback False At minimum, this inconsistency between different properties is counter-intuitive. Moreover, ::ffff:127.0.0.0/104 is for all intents and purposes a loopback address, and should be treated as such. --- Lib/ipaddress.py | 5 ++++- Lib/test/test_ipaddress.py | 16 ++++++++++++++++ ...024-04-05-15-51-01.gh-issue-117566.54nABf.rst | 3 +++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-04-05-15-51-01.gh-issue-117566.54nABf.rst diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 22cdfc93d8ad32..8e4d49c859534d 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -2142,6 +2142,9 @@ def is_loopback(self): 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 @@ -2258,7 +2261,7 @@ def is_unspecified(self): @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): diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index f1519df673747a..e0eea5540bb2db 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -2446,6 +2446,22 @@ def testIpv4MappedPrivateCheck(self): self.assertEqual( False, ipaddress.ip_address('::ffff:172.32.0.0').is_private) + def testIpv4MappedLocalCheck(self): + # test networks + self.assertEqual(True, ipaddress.ip_network( + '::ffff:127.100.200.254/128').is_loopback) + self.assertEqual(True, ipaddress.ip_network( + '::ffff:127.42.0.0/112').is_loopback) + self.assertEqual(False, ipaddress.ip_network( + '::ffff:128.0.0.0').is_loopback) + # test addresses + self.assertEqual(True, ipaddress.ip_address( + '::ffff:127.100.200.254').is_loopback) + self.assertEqual(True, ipaddress.ip_address( + '::ffff:127.42.0.0').is_loopback) + self.assertEqual(False, ipaddress.ip_address( + '::ffff:128.0.0.0').is_loopback) + def testAddrExclude(self): addr1 = ipaddress.ip_network('10.1.1.0/24') addr2 = ipaddress.ip_network('10.1.1.0/26') diff --git a/Misc/NEWS.d/next/Library/2024-04-05-15-51-01.gh-issue-117566.54nABf.rst b/Misc/NEWS.d/next/Library/2024-04-05-15-51-01.gh-issue-117566.54nABf.rst new file mode 100644 index 00000000000000..56c2fb0e25d494 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-04-05-15-51-01.gh-issue-117566.54nABf.rst @@ -0,0 +1,3 @@ +:meth:`ipaddress.IPv6Address.is_loopback` will now return ``True`` for +IPv4-mapped loopback addresses, i.e. addresses in the +``::ffff:127.0.0.0/104`` address space. From 664ac364bfbab79105b6f93d3290569d53b54aa6 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 25 Apr 2024 16:45:20 +0200 Subject: [PATCH 2/2] Rename test --- Lib/test/test_ipaddress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index e0eea5540bb2db..c3ecf2a742941a 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -2446,7 +2446,7 @@ def testIpv4MappedPrivateCheck(self): self.assertEqual( False, ipaddress.ip_address('::ffff:172.32.0.0').is_private) - def testIpv4MappedLocalCheck(self): + def testIpv4MappedLoopbackCheck(self): # test networks self.assertEqual(True, ipaddress.ip_network( '::ffff:127.100.200.254/128').is_loopback)