Skip to content

Commit ae2feb3

Browse files
bpo-27683: Fix a regression for host() of ipaddress network objects (GH-6016)
The result of host() was not empty when the network is constructed by a tuple containing an integer mask and only 1 bit left for addresses. (cherry picked from commit 10b134a) Co-authored-by: Xiang Zhang <[email protected]>
1 parent 481cbe8 commit ae2feb3

File tree

4 files changed

+75
-75
lines changed

4 files changed

+75
-75
lines changed

Doc/library/ipaddress.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,12 +483,16 @@ dictionaries.
483483

484484
Returns an iterator over the usable hosts in the network. The usable
485485
hosts are all the IP addresses that belong to the network, except the
486-
network address itself and the network broadcast address.
486+
network address itself and the network broadcast address. For networks
487+
with a mask length of 31, the network address and network broadcast
488+
address are also included in the result.
487489

488490
>>> list(ip_network('192.0.2.0/29').hosts()) #doctest: +NORMALIZE_WHITESPACE
489491
[IPv4Address('192.0.2.1'), IPv4Address('192.0.2.2'),
490492
IPv4Address('192.0.2.3'), IPv4Address('192.0.2.4'),
491493
IPv4Address('192.0.2.5'), IPv4Address('192.0.2.6')]
494+
>>> list(ip_network('192.0.2.0/31').hosts())
495+
[IPv4Address('192.0.2.0'), IPv4Address('192.0.2.1')]
492496

493497
.. method:: overlaps(other)
494498

@@ -620,6 +624,12 @@ dictionaries.
620624
.. attribute:: num_addresses
621625
.. attribute:: prefixlen
622626
.. method:: hosts()
627+
628+
Returns an iterator over the usable hosts in the network. The usable
629+
hosts are all the IP addresses that belong to the network, except the
630+
Subnet-Router anycast address. For networks with a mask length of 127,
631+
the Subnet-Router anycast address is also included in the result.
632+
623633
.. method:: overlaps(other)
624634
.. method:: address_exclude(network)
625635
.. method:: subnets(prefixlen_diff=1, new_prefix=None)

Lib/ipaddress.py

Lines changed: 38 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,45 +1498,28 @@ def __init__(self, address, strict=True):
14981498

14991499
# Constructing from a packed address or integer
15001500
if isinstance(address, (int, bytes)):
1501-
self.network_address = IPv4Address(address)
1502-
self.netmask, self._prefixlen = self._make_netmask(self._max_prefixlen)
1503-
#fixme: address/network test here.
1504-
return
1505-
1506-
if isinstance(address, tuple):
1507-
if len(address) > 1:
1508-
arg = address[1]
1509-
else:
1510-
# We weren't given an address[1]
1511-
arg = self._max_prefixlen
1512-
self.network_address = IPv4Address(address[0])
1513-
self.netmask, self._prefixlen = self._make_netmask(arg)
1514-
packed = int(self.network_address)
1515-
if packed & int(self.netmask) != packed:
1516-
if strict:
1517-
raise ValueError('%s has host bits set' % self)
1518-
else:
1519-
self.network_address = IPv4Address(packed &
1520-
int(self.netmask))
1521-
return
1522-
1501+
addr = address
1502+
mask = self._max_prefixlen
1503+
# Constructing from a tuple (addr, [mask])
1504+
elif isinstance(address, tuple):
1505+
addr = address[0]
1506+
mask = address[1] if len(address) > 1 else self._max_prefixlen
15231507
# Assume input argument to be string or any object representation
15241508
# which converts into a formatted IP prefix string.
1525-
addr = _split_optional_netmask(address)
1526-
self.network_address = IPv4Address(self._ip_int_from_string(addr[0]))
1527-
1528-
if len(addr) == 2:
1529-
arg = addr[1]
15301509
else:
1531-
arg = self._max_prefixlen
1532-
self.netmask, self._prefixlen = self._make_netmask(arg)
1533-
1534-
if strict:
1535-
if (IPv4Address(int(self.network_address) & int(self.netmask)) !=
1536-
self.network_address):
1510+
args = _split_optional_netmask(address)
1511+
addr = self._ip_int_from_string(args[0])
1512+
mask = args[1] if len(args) == 2 else self._max_prefixlen
1513+
1514+
self.network_address = IPv4Address(addr)
1515+
self.netmask, self._prefixlen = self._make_netmask(mask)
1516+
packed = int(self.network_address)
1517+
if packed & int(self.netmask) != packed:
1518+
if strict:
15371519
raise ValueError('%s has host bits set' % self)
1538-
self.network_address = IPv4Address(int(self.network_address) &
1539-
int(self.netmask))
1520+
else:
1521+
self.network_address = IPv4Address(packed &
1522+
int(self.netmask))
15401523

15411524
if self._prefixlen == (self._max_prefixlen - 1):
15421525
self.hosts = self.__iter__
@@ -2191,46 +2174,30 @@ def __init__(self, address, strict=True):
21912174
"""
21922175
_BaseNetwork.__init__(self, address)
21932176

2194-
# Efficient constructor from integer or packed address
2195-
if isinstance(address, (bytes, int)):
2196-
self.network_address = IPv6Address(address)
2197-
self.netmask, self._prefixlen = self._make_netmask(self._max_prefixlen)
2198-
return
2199-
2200-
if isinstance(address, tuple):
2201-
if len(address) > 1:
2202-
arg = address[1]
2203-
else:
2204-
arg = self._max_prefixlen
2205-
self.netmask, self._prefixlen = self._make_netmask(arg)
2206-
self.network_address = IPv6Address(address[0])
2207-
packed = int(self.network_address)
2208-
if packed & int(self.netmask) != packed:
2209-
if strict:
2210-
raise ValueError('%s has host bits set' % self)
2211-
else:
2212-
self.network_address = IPv6Address(packed &
2213-
int(self.netmask))
2214-
return
2215-
2177+
# Constructing from a packed address or integer
2178+
if isinstance(address, (int, bytes)):
2179+
addr = address
2180+
mask = self._max_prefixlen
2181+
# Constructing from a tuple (addr, [mask])
2182+
elif isinstance(address, tuple):
2183+
addr = address[0]
2184+
mask = address[1] if len(address) > 1 else self._max_prefixlen
22162185
# Assume input argument to be string or any object representation
22172186
# which converts into a formatted IP prefix string.
2218-
addr = _split_optional_netmask(address)
2219-
2220-
self.network_address = IPv6Address(self._ip_int_from_string(addr[0]))
2221-
2222-
if len(addr) == 2:
2223-
arg = addr[1]
22242187
else:
2225-
arg = self._max_prefixlen
2226-
self.netmask, self._prefixlen = self._make_netmask(arg)
2227-
2228-
if strict:
2229-
if (IPv6Address(int(self.network_address) & int(self.netmask)) !=
2230-
self.network_address):
2188+
args = _split_optional_netmask(address)
2189+
addr = self._ip_int_from_string(args[0])
2190+
mask = args[1] if len(args) == 2 else self._max_prefixlen
2191+
2192+
self.network_address = IPv6Address(addr)
2193+
self.netmask, self._prefixlen = self._make_netmask(mask)
2194+
packed = int(self.network_address)
2195+
if packed & int(self.netmask) != packed:
2196+
if strict:
22312197
raise ValueError('%s has host bits set' % self)
2232-
self.network_address = IPv6Address(int(self.network_address) &
2233-
int(self.netmask))
2198+
else:
2199+
self.network_address = IPv6Address(packed &
2200+
int(self.netmask))
22342201

22352202
if self._prefixlen == (self._max_prefixlen - 1):
22362203
self.hosts = self.__iter__

Lib/test/test_ipaddress.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,10 +1042,30 @@ def testHosts(self):
10421042
self.assertEqual(ipaddress.IPv4Address('1.2.3.1'), hosts[0])
10431043
self.assertEqual(ipaddress.IPv4Address('1.2.3.254'), hosts[-1])
10441044

1045+
ipv6_network = ipaddress.IPv6Network('2001:658:22a:cafe::/120')
1046+
hosts = list(ipv6_network.hosts())
1047+
self.assertEqual(255, len(hosts))
1048+
self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::1'), hosts[0])
1049+
self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::ff'), hosts[-1])
1050+
10451051
# special case where only 1 bit is left for address
1046-
self.assertEqual([ipaddress.IPv4Address('2.0.0.0'),
1047-
ipaddress.IPv4Address('2.0.0.1')],
1048-
list(ipaddress.ip_network('2.0.0.0/31').hosts()))
1052+
addrs = [ipaddress.IPv4Address('2.0.0.0'),
1053+
ipaddress.IPv4Address('2.0.0.1')]
1054+
str_args = '2.0.0.0/31'
1055+
tpl_args = ('2.0.0.0', 31)
1056+
self.assertEqual(addrs, list(ipaddress.ip_network(str_args).hosts()))
1057+
self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts()))
1058+
self.assertEqual(list(ipaddress.ip_network(str_args).hosts()),
1059+
list(ipaddress.ip_network(tpl_args).hosts()))
1060+
1061+
addrs = [ipaddress.IPv6Address('2001:658:22a:cafe::'),
1062+
ipaddress.IPv6Address('2001:658:22a:cafe::1')]
1063+
str_args = '2001:658:22a:cafe::/127'
1064+
tpl_args = ('2001:658:22a:cafe::', 127)
1065+
self.assertEqual(addrs, list(ipaddress.ip_network(str_args).hosts()))
1066+
self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts()))
1067+
self.assertEqual(list(ipaddress.ip_network(str_args).hosts()),
1068+
list(ipaddress.ip_network(tpl_args).hosts()))
10491069

10501070
def testFancySubnetting(self):
10511071
self.assertEqual(sorted(self.ipv4_network.subnets(prefixlen_diff=3)),
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a regression in :mod:`ipaddress` that result of :meth:`hosts`
2+
is empty when the network is constructed by a tuple containing an
3+
integer mask and only 1 bit left for addresses.

0 commit comments

Comments
 (0)