From 10b134a07c898c2fbc5fd3582503680a54ed80a2 Mon Sep 17 00:00:00 2001 From: Xiang Zhang Date: Wed, 21 Mar 2018 08:25:13 +0800 Subject: 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. --- Doc/library/ipaddress.rst | 12 ++- Lib/ipaddress.py | 109 +++++++-------------- Lib/test/test_ipaddress.py | 26 ++++- .../2018-03-07-22-28-17.bpo-27683.572Rv4.rst | 3 + 4 files changed, 75 insertions(+), 75 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-03-07-22-28-17.bpo-27683.572Rv4.rst diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index 935add17..b7b502a 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -485,12 +485,16 @@ dictionaries. Returns an iterator over the usable hosts in the network. The usable hosts are all the IP addresses that belong to the network, except the - network address itself and the network broadcast address. + 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. >>> 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')] .. method:: overlaps(other) @@ -647,6 +651,12 @@ dictionaries. .. attribute:: num_addresses .. attribute:: prefixlen .. method:: hosts() + + Returns an iterator over the usable hosts in the network. The usable + 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. + .. method:: overlaps(other) .. method:: address_exclude(network) .. method:: subnets(prefixlen_diff=1, new_prefix=None) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 77df051..15507d6 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1514,45 +1514,28 @@ class IPv4Network(_BaseV4, _BaseNetwork): # Constructing from a packed address or integer if isinstance(address, (int, bytes)): - self.network_address = IPv4Address(address) - self.netmask, self._prefixlen = self._make_netmask(self._max_prefixlen) - #fixme: address/network test here. - return - - if isinstance(address, tuple): - if len(address) > 1: - arg = address[1] - else: - # We weren't given an address[1] - arg = self._max_prefixlen - self.network_address = IPv4Address(address[0]) - self.netmask, self._prefixlen = self._make_netmask(arg) - packed = int(self.network_address) - if packed & int(self.netmask) != packed: - if strict: - raise ValueError('%s has host bits set' % self) - else: - self.network_address = IPv4Address(packed & - int(self.netmask)) - return - + addr = address + mask = self._max_prefixlen + # Constructing from a tuple (addr, [mask]) + elif isinstance(address, tuple): + addr = address[0] + mask = address[1] if len(address) > 1 else self._max_prefixlen # Assume input argument to be string or any object representation # which converts into a formatted IP prefix string. - addr = _split_optional_netmask(address) - self.network_address = IPv4Address(self._ip_int_from_string(addr[0])) - - if len(addr) == 2: - arg = addr[1] else: - arg = self._max_prefixlen - self.netmask, self._prefixlen = self._make_netmask(arg) - - if strict: - if (IPv4Address(int(self.network_address) & int(self.netmask)) != - self.network_address): + args = _split_optional_netmask(address) + addr = self._ip_int_from_string(args[0]) + mask = args[1] if len(args) == 2 else self._max_prefixlen + + self.network_address = IPv4Address(addr) + self.netmask, self._prefixlen = self._make_netmask(mask) + packed = int(self.network_address) + if packed & int(self.netmask) != packed: + if strict: raise ValueError('%s has host bits set' % self) - self.network_address = IPv4Address(int(self.network_address) & - int(self.netmask)) + else: + self.network_address = IPv4Address(packed & + int(self.netmask)) if self._prefixlen == (self._max_prefixlen - 1): self.hosts = self.__iter__ @@ -2207,46 +2190,30 @@ class IPv6Network(_BaseV6, _BaseNetwork): """ _BaseNetwork.__init__(self, address) - # Efficient constructor from integer or packed address - if isinstance(address, (bytes, int)): - self.network_address = IPv6Address(address) - self.netmask, self._prefixlen = self._make_netmask(self._max_prefixlen) - return - - if isinstance(address, tuple): - if len(address) > 1: - arg = address[1] - else: - arg = self._max_prefixlen - self.netmask, self._prefixlen = self._make_netmask(arg) - self.network_address = IPv6Address(address[0]) - packed = int(self.network_address) - if packed & int(self.netmask) != packed: - if strict: - raise ValueError('%s has host bits set' % self) - else: - self.network_address = IPv6Address(packed & - int(self.netmask)) - return - + # Constructing from a packed address or integer + if isinstance(address, (int, bytes)): + addr = address + mask = self._max_prefixlen + # Constructing from a tuple (addr, [mask]) + elif isinstance(address, tuple): + addr = address[0] + mask = address[1] if len(address) > 1 else self._max_prefixlen # Assume input argument to be string or any object representation # which converts into a formatted IP prefix string. - addr = _split_optional_netmask(address) - - self.network_address = IPv6Address(self._ip_int_from_string(addr[0])) - - if len(addr) == 2: - arg = addr[1] else: - arg = self._max_prefixlen - self.netmask, self._prefixlen = self._make_netmask(arg) - - if strict: - if (IPv6Address(int(self.network_address) & int(self.netmask)) != - self.network_address): + args = _split_optional_netmask(address) + addr = self._ip_int_from_string(args[0]) + mask = args[1] if len(args) == 2 else self._max_prefixlen + + self.network_address = IPv6Address(addr) + self.netmask, self._prefixlen = self._make_netmask(mask) + packed = int(self.network_address) + if packed & int(self.netmask) != packed: + if strict: raise ValueError('%s has host bits set' % self) - self.network_address = IPv6Address(int(self.network_address) & - int(self.netmask)) + else: + self.network_address = IPv6Address(packed & + int(self.netmask)) if self._prefixlen == (self._max_prefixlen - 1): self.hosts = self.__iter__ diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index a5aeb79..0e0753f 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -1127,10 +1127,30 @@ class IpaddrUnitTest(unittest.TestCase): self.assertEqual(ipaddress.IPv4Address('1.2.3.1'), hosts[0]) self.assertEqual(ipaddress.IPv4Address('1.2.3.254'), hosts[-1]) + ipv6_network = ipaddress.IPv6Network('2001:658:22a:cafe::/120') + hosts = list(ipv6_network.hosts()) + self.assertEqual(255, len(hosts)) + self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::1'), hosts[0]) + self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::ff'), hosts[-1]) + # special case where only 1 bit is left for address - self.assertEqual([ipaddress.IPv4Address('2.0.0.0'), - ipaddress.IPv4Address('2.0.0.1')], - list(ipaddress.ip_network('2.0.0.0/31').hosts())) + addrs = [ipaddress.IPv4Address('2.0.0.0'), + ipaddress.IPv4Address('2.0.0.1')] + str_args = '2.0.0.0/31' + tpl_args = ('2.0.0.0', 31) + 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' + tpl_args = ('2001:658:22a:cafe::', 127) + 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)), diff --git a/Misc/NEWS.d/next/Library/2018-03-07-22-28-17.bpo-27683.572Rv4.rst b/Misc/NEWS.d/next/Library/2018-03-07-22-28-17.bpo-27683.572Rv4.rst new file mode 100644 index 0000000..4e6dfa8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-03-07-22-28-17.bpo-27683.572Rv4.rst @@ -0,0 +1,3 @@ +Fix a regression in :mod:`ipaddress` that result of :meth:`hosts` +is empty when the network is constructed by a tuple containing an +integer mask and only 1 bit left for addresses. -- cgit v0.12