From 8e9c47a947954c997d4b725f4551d50a1d896722 Mon Sep 17 00:00:00 2001 From: Pete Wicken <2273100+JamoBox@users.noreply.github.com> Date: Mon, 9 Mar 2020 22:33:45 +0000 Subject: bpo-28577: Special case added to IP v4 and v6 hosts for /32 and /128 networks (GH-18757) The `.hosts()` method now returns the single address present in a /32 or /128 network. --- Doc/library/ipaddress.rst | 7 ++++++- Lib/ipaddress.py | 4 ++++ Lib/test/test_ipaddress.py | 17 +++++++++++++---- Misc/ACKS | 1 + .../Library/2020-03-02-23-52-38.bpo-28577.EK91ae.rst | 1 + 5 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-03-02-23-52-38.bpo-28577.EK91ae.rst diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index 5938439..5f5e664 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -509,7 +509,8 @@ dictionaries. hosts are all the IP addresses that belong to the network, except the network address itself and the network broadcast address. For networks with a mask length of 31, the network address and network broadcast - address are also included in the result. + address are also included in the result. Networks with a mask of 32 + will return a list containing the single host address. >>> list(ip_network('192.0.2.0/29').hosts()) #doctest: +NORMALIZE_WHITESPACE [IPv4Address('192.0.2.1'), IPv4Address('192.0.2.2'), @@ -517,6 +518,8 @@ dictionaries. IPv4Address('192.0.2.5'), IPv4Address('192.0.2.6')] >>> list(ip_network('192.0.2.0/31').hosts()) [IPv4Address('192.0.2.0'), IPv4Address('192.0.2.1')] + >>> list(ip_network('192.0.2.1/32').hosts()) + [IPv4Address('192.0.2.1')] .. method:: overlaps(other) @@ -678,6 +681,8 @@ dictionaries. hosts are all the IP addresses that belong to the network, except the Subnet-Router anycast address. For networks with a mask length of 127, the Subnet-Router anycast address is also included in the result. + Networks with a mask of 128 will return a list containing the + single host address. .. method:: overlaps(other) .. method:: address_exclude(network) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 7024339..ac1143a 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1509,6 +1509,8 @@ class IPv4Network(_BaseV4, _BaseNetwork): if self._prefixlen == (self._max_prefixlen - 1): self.hosts = self.__iter__ + elif self._prefixlen == (self._max_prefixlen): + self.hosts = lambda: [IPv4Address(addr)] @property @functools.lru_cache() @@ -2212,6 +2214,8 @@ class IPv6Network(_BaseV6, _BaseNetwork): if self._prefixlen == (self._max_prefixlen - 1): self.hosts = self.__iter__ + elif self._prefixlen == self._max_prefixlen: + self.hosts = lambda: [IPv6Address(addr)] def hosts(self): """Generate Iterator over usable hosts in a network. diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index f4a4afb..bbb3fc8 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -1424,14 +1424,15 @@ class IpaddrUnitTest(unittest.TestCase): self.assertEqual(list(ipaddress.ip_network(str_args).hosts()), list(ipaddress.ip_network(tpl_args).hosts())) - addrs = [ipaddress.IPv6Address('2001:658:22a:cafe::'), - ipaddress.IPv6Address('2001:658:22a:cafe::1')] - str_args = '2001:658:22a:cafe::/127' - tpl_args = ('2001:658:22a:cafe::', 127) + # special case where the network is a /32 + addrs = [ipaddress.IPv4Address('1.2.3.4')] + str_args = '1.2.3.4/32' + tpl_args = ('1.2.3.4', 32) self.assertEqual(addrs, list(ipaddress.ip_network(str_args).hosts())) self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts())) self.assertEqual(list(ipaddress.ip_network(str_args).hosts()), list(ipaddress.ip_network(tpl_args).hosts())) + addrs = [ipaddress.IPv6Address('2001:658:22a:cafe::'), ipaddress.IPv6Address('2001:658:22a:cafe::1')] str_args = '2001:658:22a:cafe::/127' @@ -1441,6 +1442,14 @@ class IpaddrUnitTest(unittest.TestCase): self.assertEqual(list(ipaddress.ip_network(str_args).hosts()), list(ipaddress.ip_network(tpl_args).hosts())) + addrs = [ipaddress.IPv6Address('2001:658:22a:cafe::1'), ] + str_args = '2001:658:22a:cafe::1/128' + tpl_args = ('2001:658:22a:cafe::1', 128) + self.assertEqual(addrs, list(ipaddress.ip_network(str_args).hosts())) + self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts())) + self.assertEqual(list(ipaddress.ip_network(str_args).hosts()), + list(ipaddress.ip_network(tpl_args).hosts())) + def testFancySubnetting(self): self.assertEqual(sorted(self.ipv4_network.subnets(prefixlen_diff=3)), sorted(self.ipv4_network.subnets(new_prefix=27))) diff --git a/Misc/ACKS b/Misc/ACKS index 5ec93a4..cc5694a 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1823,6 +1823,7 @@ Jeff Wheeler Christopher White David White Mats Wichmann +Pete Wicken Marcel Widjaja Truida Wiedijk Felix Wiemann diff --git a/Misc/NEWS.d/next/Library/2020-03-02-23-52-38.bpo-28577.EK91ae.rst b/Misc/NEWS.d/next/Library/2020-03-02-23-52-38.bpo-28577.EK91ae.rst new file mode 100644 index 0000000..de4c064 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-03-02-23-52-38.bpo-28577.EK91ae.rst @@ -0,0 +1 @@ +The hosts method on 32-bit prefix length IPv4Networks and 128-bit prefix IPv6Networks now returns a list containing the single Address instead of an empty list. -- cgit v0.12