Skip to content

bpo-28577: Special case added to IP v4 and v6 hosts for /32 and /128 networks #18757

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Doc/library/ipaddress.rst
Original file line number Diff line number Diff line change
Expand Up @@ -509,14 +509,17 @@ 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'),
IPv4Address('192.0.2.3'), IPv4Address('192.0.2.4'),
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)

Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions Lib/ipaddress.py
Original file line number Diff line number Diff line change
Expand Up @@ -1509,6 +1509,8 @@ def __init__(self, address, strict=True):

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()
Expand Down Expand Up @@ -2212,6 +2214,8 @@ def __init__(self, address, strict=True):

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.
Expand Down
17 changes: 13 additions & 4 deletions Lib/test/test_ipaddress.py
Original file line number Diff line number Diff line change
Expand Up @@ -1424,14 +1424,15 @@ def testHosts(self):
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'
Expand All @@ -1441,6 +1442,14 @@ def testHosts(self):
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)))
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1823,6 +1823,7 @@ Jeff Wheeler
Christopher White
David White
Mats Wichmann
Pete Wicken
Marcel Widjaja
Truida Wiedijk
Felix Wiemann
Expand Down
Original file line number Diff line number Diff line change
@@ -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.