From eecb7baaaa243facfe79940410e73d3d37d4ea7c Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> Date: Sun, 1 Nov 2026 12:36:11 -0800 Subject: [PATCH] [3.15] gh-161100: Fix `IPv6Interface.is_unspecified` for IPv4-mapped addresses (GH-161101) Defer to IPv6Address.is_unspecified, as the is_loopback override does, so ::ffff:0.0.0.0/128 interfaces are unspecified like the address. ANT-2026-DIFE27WF (cherry picked from commit 6e6e83eb0da240e618806b3ded86e817dcaa4f18) --- Lib/ipaddress.py | 2 +- Lib/test/test_ipaddress.py | 19 +++++++++++++++++++ ...-10-22-05-20-06.gh-issue-161100.pLgnlA.rst | 3 +++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-22-05-20-06.gh-issue-161100.pLgnlA.rst diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 198dcd6de32a440..4c7b5b751d6d27f 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -2272,7 +2272,7 @@ def with_hostmask(self): @property def is_unspecified(self): - return self._ip == 0 and self.network.is_unspecified + return super().is_unspecified and self.network.is_unspecified @property def is_loopback(self): diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index 8cb1e6549737cf2..595288a5cd62574 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -2447,6 +2447,14 @@ def testReservedIpv6(self): self.assertEqual(False, ipaddress.ip_network('::1').is_unspecified) self.assertEqual(False, ipaddress.ip_network('::/127').is_unspecified) + self.assertTrue(ipaddress.ip_interface('::/128').is_unspecified) + self.assertTrue( + ipaddress.ip_interface('::ffff:0.0.0.0/128').is_unspecified) + self.assertFalse(ipaddress.ip_interface('::/127').is_unspecified) + self.assertFalse( + ipaddress.ip_interface('::ffff:0.0.0.0/127').is_unspecified) + self.assertFalse(ipaddress.ip_interface('::1/128').is_unspecified) + self.assertEqual(True, ipaddress.ip_network('2001::1/128').is_private) self.assertEqual(True, @@ -2536,6 +2544,17 @@ def testIpv4MappedProperties(self): self.assertEqual(ipv4.is_link_local, ipv6.is_link_local) self.assertEqual(ipv4.is_loopback, ipv6.is_loopback) + ipv4 = ipaddress.IPv4Interface(addr4) + ipv6 = ipaddress.IPv6Interface(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/Library/2026-10-22-05-20-06.gh-issue-161100.pLgnlA.rst b/Misc/NEWS.d/next/Library/2026-10-22-05-20-06.gh-issue-161100.pLgnlA.rst new file mode 100644 index 000000000000000..b23de8a22ca9683 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-22-05-20-06.gh-issue-161100.pLgnlA.rst @@ -0,0 +1,3 @@ +Fix :attr:`!ipaddress.IPv6Interface.is_unspecified` returning ``False`` for +the IPv4-mapped address ``::ffff:0.0.0.0``. It now gives the same result as +:attr:`ipaddress.IPv6Address.is_unspecified`.