]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-117566: fix IPv6Address.is_loopback for IPv4-mapped loopbacks (GH-117567)
authorFaidon Liambotis <paravoid@debian.org>
Thu, 25 Apr 2024 15:17:40 +0000 (18:17 +0300)
committerGitHub <noreply@github.com>
Thu, 25 Apr 2024 15:17:40 +0000 (15:17 +0000)
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
Lib/test/test_ipaddress.py
Misc/NEWS.d/next/Library/2024-04-05-15-51-01.gh-issue-117566.54nABf.rst [new file with mode: 0644]

index 22cdfc93d8ad32d7da1b9aacccf504e0fd838630..8e4d49c859534d816efd70b8e0b14d9da407f14e 100644 (file)
@@ -2142,6 +2142,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
@@ -2258,7 +2261,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):
index f1519df673747a954e1a14cd43e25c7634c65f57..c3ecf2a742941a6fa5e512c97e5f5a0df1a7bdfc 100644 (file)
@@ -2446,6 +2446,22 @@ class IpaddrUnitTest(unittest.TestCase):
         self.assertEqual(
                 False, ipaddress.ip_address('::ffff:172.32.0.0').is_private)
 
+    def testIpv4MappedLoopbackCheck(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 (file)
index 0000000..56c2fb0
--- /dev/null
@@ -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.