From ded9141328279dd3faab5d400ea89f7f389533f1 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Fri, 20 Nov 2020 14:25:35 +0100 Subject: [PATCH 1/8] 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. --- ipaddress.py | 111 ++++++++++++++++------------------------------ test_ipaddress.py | 30 +++++++++++-- 2 files changed, 65 insertions(+), 76 deletions(-) diff --git a/ipaddress.py b/ipaddress.py index 3e6f9e4..b9cb84a 100644 --- a/ipaddress.py +++ b/ipaddress.py @@ -1616,46 +1616,28 @@ def __init__(self, address, strict=True): # Constructing from a packed address or integer if isinstance(address, (_compat_int_types, 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__ @@ -2311,47 +2293,30 @@ def __init__(self, address, strict=True): """ _BaseNetwork.__init__(self, address) - # Efficient constructor from integer or packed address - if isinstance(address, (bytes, _compat_int_types)): - 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, (_compat_int_types, 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/test_ipaddress.py b/test_ipaddress.py index a1721b8..1559427 100644 --- a/test_ipaddress.py +++ b/test_ipaddress.py @@ -1190,10 +1190,34 @@ def testHosts(self): 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)), From d618b5618fa6522c9e98072fcbfee22dbcb493bf Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Fri, 20 Nov 2020 14:29:12 +0100 Subject: [PATCH 2/8] bpo-36384: Remove check for leading zeroes in IPv4 addresses (GH-12577) Stop rejecting IPv4 octets with leading zeroes as ambiguously octal. Plenty of other tools generate decimal IPv4 octets with leading zeroes, so keeping this check hurts interoperability. Patch by Joel Croteau. --- ipaddress.py | 6 ------ test_ipaddress.py | 11 ++--------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/ipaddress.py b/ipaddress.py index b9cb84a..bb2f989 100644 --- a/ipaddress.py +++ b/ipaddress.py @@ -1294,12 +1294,6 @@ def _parse_octet(cls, octet_str): raise ValueError(msg % octet_str) # Convert to integer (we know digits are legal) octet_int = int(octet_str, 10) - # Any octets that look like they *might* be written in octal, - # and which don't look exactly the same in both octal and - # decimal are rejected as ambiguous - if octet_int > 7 and octet_str[0] == '0': - msg = "Ambiguous (octal/decimal) value in %r not permitted" - raise ValueError(msg % octet_str) if octet_int > 255: raise ValueError("Octet %d (> 255) not permitted" % octet_int) return octet_int diff --git a/test_ipaddress.py b/test_ipaddress.py index 1559427..e2a3a3a 100644 --- a/test_ipaddress.py +++ b/test_ipaddress.py @@ -112,6 +112,8 @@ class CommonTestMixin_v4(CommonTestMixin): def test_leading_zeros(self): self.assertInstancesEqual("000.000.000.000", "0.0.0.0") self.assertInstancesEqual("192.168.000.001", "192.168.0.1") + self.assertInstancesEqual("016.016.016.016", "16.16.16.16") + self.assertInstancesEqual("001.000.008.016", "1.0.8.16") def test_int(self): self.assertInstancesEqual(0, "0.0.0.0") @@ -246,15 +248,6 @@ def assertBadOctet(addr, octet): assertBadOctet("1.2.3.4::", "4::") assertBadOctet("1.a.2.3", "a") - def test_octal_decimal_ambiguity(self): - def assertBadOctet(addr, octet): - msg = "Ambiguous (octal/decimal) value in %r not permitted in %r" - with self.assertAddressError(re.escape(msg % (octet, addr))): - ipaddress.IPv4Address(addr) - - assertBadOctet("016.016.016.016", "016") - assertBadOctet("001.000.008.016", "008") - def test_octet_length(self): def assertBadOctet(addr, octet): msg = "At most 3 characters permitted in %r in %r" From a312024ddcf15e1b1d384ccdc79df2388bcbd679 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Fri, 20 Nov 2020 14:39:21 +0100 Subject: [PATCH 3/8] In IPv[46]Network, `self.network.prefixlen` is same to `self._prefixlen`. --- ipaddress.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipaddress.py b/ipaddress.py index bb2f989..a3bea84 100644 --- a/ipaddress.py +++ b/ipaddress.py @@ -1503,7 +1503,7 @@ def __init__(self, address): def __str__(self): return '%s/%d' % (self._string_from_ip_int(self._ip), - self.network.prefixlen) + self._prefixlen) def __eq__(self, other): address_equal = IPv4Address.__eq__(self, other) @@ -2178,7 +2178,7 @@ def __init__(self, address): def __str__(self): return '%s/%d' % (self._string_from_ip_int(self._ip), - self.network.prefixlen) + self._prefixlen) def __eq__(self, other): address_equal = IPv6Address.__eq__(self, other) From cbfed8e09a90289690946d966d24b4943f941b84 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Fri, 20 Nov 2020 14:59:58 +0100 Subject: [PATCH 4/8] bpo-27860: ipaddress: fix Interface missed some attributes (GH-12836) IPv4Interface and IPv6Interface did not has netmask and hostmask attributes when its argument is bytes or int. This commit extracts method for constructors of Network and Interface, and ensure Interface class always provides them. --- ipaddress.py | 100 ++++++++++++++-------------------------------- test_ipaddress.py | 21 ++++++++++ 2 files changed, 52 insertions(+), 69 deletions(-) diff --git a/ipaddress.py b/ipaddress.py index a3bea84..b39c36d 100644 --- a/ipaddress.py +++ b/ipaddress.py @@ -655,6 +655,28 @@ def _prefix_from_ip_string(cls, ip_str): except ValueError: cls._report_invalid_netmask(ip_str) + @classmethod + def _split_addr_prefix(cls, address): + """Helper function to parse address of Network/Interface. + Arg: + address: Argument of Network/Interface. + Returns: + (addr, prefix) tuple. + """ + # a packed address or integer + if isinstance(address, (bytes, _compat_int_types)): + return address, cls._max_prefixlen + + if not isinstance(address, tuple): + # Assume input argument to be string or any object representation + # which converts into a formatted IP prefix string. + address = _split_optional_netmask(address) + + # Constructing from a tuple (addr, [mask]) + if len(address) > 1: + return address + return address[0], cls._max_prefixlen + def __reduce__(self): return self.__class__, (_compat_str(self),) @@ -1474,31 +1496,13 @@ def is_link_local(self): class IPv4Interface(IPv4Address): def __init__(self, address): - if isinstance(address, (bytes, _compat_int_types)): - IPv4Address.__init__(self, address) - self.network = IPv4Network(self._ip) - self._prefixlen = self._max_prefixlen - return - - if isinstance(address, tuple): - IPv4Address.__init__(self, address[0]) - if len(address) > 1: - self._prefixlen = int(address[1]) - else: - self._prefixlen = self._max_prefixlen - - self.network = IPv4Network(address, strict=False) - self.netmask = self.network.netmask - self.hostmask = self.network.hostmask - return + addr, mask = self._split_addr_prefix(address) - addr = _split_optional_netmask(address) - IPv4Address.__init__(self, addr[0]) - - self.network = IPv4Network(address, strict=False) + IPv4Address.__init__(self, addr) + self.network = IPv4Network((addr, mask), strict=False) + self.netmask = self.network.netmask self._prefixlen = self.network._prefixlen - self.netmask = self.network.netmask self.hostmask = self.network.hostmask def __str__(self): @@ -1608,20 +1612,7 @@ def __init__(self, address, strict=True): """ _BaseNetwork.__init__(self, address) - # Constructing from a packed address or integer - if isinstance(address, (_compat_int_types, 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. - else: - args = _split_optional_netmask(address) - addr = self._ip_int_from_string(args[0]) - mask = args[1] if len(args) == 2 else self._max_prefixlen + addr, mask = self._split_addr_prefix(address) self.network_address = IPv4Address(addr) self.netmask, self._prefixlen = self._make_netmask(mask) @@ -2153,25 +2144,9 @@ def sixtofour(self): class IPv6Interface(IPv6Address): def __init__(self, address): - if isinstance(address, (bytes, _compat_int_types)): - IPv6Address.__init__(self, address) - self.network = IPv6Network(self._ip) - self._prefixlen = self._max_prefixlen - return - if isinstance(address, tuple): - IPv6Address.__init__(self, address[0]) - if len(address) > 1: - self._prefixlen = int(address[1]) - else: - self._prefixlen = self._max_prefixlen - self.network = IPv6Network(address, strict=False) - self.netmask = self.network.netmask - self.hostmask = self.network.hostmask - return - - addr = _split_optional_netmask(address) - IPv6Address.__init__(self, addr[0]) - self.network = IPv6Network(address, strict=False) + addr, mask = self._split_addr_prefix(address) + IPv6Address.__init__(self, addr) + self.network = IPv6Network((addr, mask), strict=False) self.netmask = self.network.netmask self._prefixlen = self.network._prefixlen self.hostmask = self.network.hostmask @@ -2287,20 +2262,7 @@ def __init__(self, address, strict=True): """ _BaseNetwork.__init__(self, address) - # Constructing from a packed address or integer - if isinstance(address, (_compat_int_types, 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. - else: - args = _split_optional_netmask(address) - addr = self._ip_int_from_string(args[0]) - mask = args[1] if len(args) == 2 else self._max_prefixlen + addr, mask = self._split_addr_prefix(address) self.network_address = IPv6Address(addr) self.netmask, self._prefixlen = self._make_netmask(mask) diff --git a/test_ipaddress.py b/test_ipaddress.py index e2a3a3a..c8651a8 100644 --- a/test_ipaddress.py +++ b/test_ipaddress.py @@ -426,6 +426,15 @@ def test_bytes_message(self): class NetmaskTestMixin_v4(CommonTestMixin_v4): """Input validation on interfaces and networks is very similar""" + def test_no_mask(self): + for address in ('1.2.3.4', 0x01020304, b'\x01\x02\x03\x04'): + net = self.factory(address) + self.assertEqual(_compat_str(net), '1.2.3.4/32') + self.assertEqual(_compat_str(net.netmask), '255.255.255.255') + self.assertEqual(_compat_str(net.hostmask), '0.0.0.0') + # IPv4Network has prefixlen, but IPv4Interface doesn't. + # Should we add it to IPv4Interface too? (bpo-36392) + def test_split_netmask(self): addr = "1.2.3.4/32/24" with self.assertAddressError("Only one '/' permitted in %r" % addr): @@ -563,6 +572,18 @@ def test_subnet_of_mixed_types(self): class NetmaskTestMixin_v6(CommonTestMixin_v6): """Input validation on interfaces and networks is very similar""" + def test_no_mask(self): + for address in ('::1', 1, b'\x00'*15 + b'\x01'): + net = self.factory(address) + self.assertEqual(_compat_str(net), '::1/128') + self.assertEqual( + _compat_str(net.netmask), + 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' + ) + self.assertEqual(_compat_str(net.hostmask), '::') + # IPv6Network has prefixlen, but IPv6Interface doesn't. + # Should we add it to IPv4Interface too? (bpo-36392) + def test_split_netmask(self): addr = "cafe:cafe::/128/190" with self.assertAddressError("Only one '/' permitted in %r" % addr): From 15d1c3e794b226dfcf23def3f4532cddcc0bb5f7 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Fri, 20 Nov 2020 15:01:49 +0100 Subject: [PATCH 5/8] bpo-25430: improve performance of IPNetwork.__contains__ (GH-1785) make a compare in bit-operation manner. --- ipaddress.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ipaddress.py b/ipaddress.py index b39c36d..6058567 100644 --- a/ipaddress.py +++ b/ipaddress.py @@ -826,8 +826,7 @@ def __contains__(self, other): # dealing with another address else: # address - return (int(self.network_address) <= int(other._ip) <= - int(self.broadcast_address)) + return other._ip & self.netmask._ip == self.network_address._ip def overlaps(self, other): """Tell if self is partly contained in other.""" From c97c325231d7904d251cb31d169f9ed29e8af6be Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Fri, 20 Nov 2020 15:06:03 +0100 Subject: [PATCH 6/8] bpo-36845: validate integer network prefix when constructing IP networks (GH-13298) --- ipaddress.py | 4 ++++ test_ipaddress.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/ipaddress.py b/ipaddress.py index 6058567..f5c3a4a 100644 --- a/ipaddress.py +++ b/ipaddress.py @@ -1249,6 +1249,8 @@ def _make_netmask(cls, arg): if arg not in cls._netmask_cache: if isinstance(arg, _compat_int_types): prefixlen = arg + if not (0 <= prefixlen <= cls._max_prefixlen): + cls._report_invalid_netmask(prefixlen) else: try: # Check for a netmask in prefix length form @@ -1707,6 +1709,8 @@ def _make_netmask(cls, arg): if arg not in cls._netmask_cache: if isinstance(arg, _compat_int_types): prefixlen = arg + if not (0 <= prefixlen <= cls._max_prefixlen): + cls._report_invalid_netmask(prefixlen) else: prefixlen = cls._prefix_from_prefix_string(arg) netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen)) diff --git a/test_ipaddress.py b/test_ipaddress.py index c8651a8..b694573 100644 --- a/test_ipaddress.py +++ b/test_ipaddress.py @@ -497,6 +497,14 @@ def assertBadNetmask(addr, netmask): assertBadNetmask("1.1.1.1", "pudding") assertBadNetmask("1.1.1.1", "::") + def test_netmask_in_tuple_errors(self): + def assertBadNetmask(addr, netmask): + msg = "%r is not a valid netmask" % netmask + with self.assertNetmaskError(re.escape(msg)): + self.factory((addr, netmask)) + assertBadNetmask("1.1.1.1", -1) + assertBadNetmask("1.1.1.1", 33) + def test_pickle(self): self.pickle_test('192.0.2.0/27') self.pickle_test('192.0.2.0/31') # IPV4LENGTH - 1 @@ -632,6 +640,14 @@ def assertBadNetmask(addr, netmask): assertBadNetmask("::1", "pudding") assertBadNetmask("::", "::") + def test_netmask_in_tuple_errors(self): + def assertBadNetmask(addr, netmask): + msg = "%r is not a valid netmask" % netmask + with self.assertNetmaskError(re.escape(msg)): + self.factory((addr, netmask)) + assertBadNetmask("::1", -1) + assertBadNetmask("::1", 129) + def test_pickle(self): self.pickle_test('2001:db8::1000/124') self.pickle_test('2001:db8::1000/127') # IPV6LENGTH - 1 From cfa3166638e0960303a6735870077b5f09dc7d08 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Mon, 3 Aug 2020 11:59:23 +0200 Subject: [PATCH 7/8] Resolve hash collisions for IPv4Interface and IPv6Interface The __hash__() methods of classes IPv4Interface and IPv6Interface had issue of generating constant hash values of 32 and 128 respectively causing hash collisions. The fix uses the hash() function to generate hash values for the objects instead of XOR operation Fixes: https://github.com/phihag/ipaddress/issues/55 --- ipaddress.py | 6 ++++-- test_ipaddress.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ipaddress.py b/ipaddress.py index f5c3a4a..2187f92 100644 --- a/ipaddress.py +++ b/ipaddress.py @@ -1535,7 +1535,8 @@ def __lt__(self, other): return False def __hash__(self): - return self._ip ^ self._prefixlen ^ int(self.network.network_address) + return hash((self._ip, self._prefixlen, + int(self.network.network_address))) __reduce__ = _IPAddressBase.__reduce__ @@ -2183,7 +2184,8 @@ def __lt__(self, other): return False def __hash__(self): - return self._ip ^ self._prefixlen ^ int(self.network.network_address) + return hash((self._ip, self._prefixlen, + int(self.network.network_address))) __reduce__ = _IPAddressBase.__reduce__ diff --git a/test_ipaddress.py b/test_ipaddress.py index b694573..125baa4 100644 --- a/test_ipaddress.py +++ b/test_ipaddress.py @@ -2185,6 +2185,18 @@ def testsixtofour(self): sixtofouraddr.sixtofour) self.assertFalse(bad_addr.sixtofour) + # issue41004 Hash collisions in IPv4Interface and IPv6Interface + def testV4HashIsNotConstant(self): + ipv4_address1 = ipaddress.IPv4Interface("1.2.3.4") + ipv4_address2 = ipaddress.IPv4Interface("2.3.4.5") + self.assertNotEqual(ipv4_address1.__hash__(), ipv4_address2.__hash__()) + + # issue41004 Hash collisions in IPv4Interface and IPv6Interface + def testV6HashIsNotConstant(self): + ipv6_address1 = ipaddress.IPv6Interface("2001:658:22a:cafe:200:0:0:1") + ipv6_address2 = ipaddress.IPv6Interface("2001:658:22a:cafe:200:0:0:2") + self.assertNotEqual(ipv6_address1.__hash__(), ipv6_address2.__hash__()) + # Monkey-patch test runner if not hasattr(BaseTestCase, 'assertRaisesRegex'): From 0a0b444218443bb0ec7c8bd8226dbeb7eeeedee2 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Fri, 20 Nov 2020 15:09:33 +0100 Subject: [PATCH 8/8] bpo-41482: Fix error in ipaddress.IPv4Network docstring (GH-21736) --- ipaddress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipaddress.py b/ipaddress.py index 2187f92..8300404 100644 --- a/ipaddress.py +++ b/ipaddress.py @@ -1583,7 +1583,7 @@ def __init__(self, address, strict=True): address: A string or integer representing the IP [& network]. '192.0.2.0/24' '192.0.2.0/255.255.255.0' - '192.0.0.2/0.0.0.255' + '192.0.2.0/0.0.0.255' are all functionally the same in IPv4. Similarly, '192.0.2.1' '192.0.2.1/255.255.255.255'