diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 7a3f36f3bc0f48..af1011b4ddd53b 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -499,38 +499,30 @@ def _prefix_from_prefix_string(cls, prefixlen_str): return prefixlen @classmethod - def _prefix_from_ip_string(cls, ip_str): - """Turn a netmask/hostmask string into a prefix length + def _split_addr_prefix(cls, arg): + """Make a (address, prefix) tuple from the given argument. Args: - ip_str: The netmask/hostmask to be converted + arg: A string, bytes object or a tuple representing an address and + a network prefix. Returns: - An integer, the prefix length. - - Raises: - NetmaskValueError: If the input is not a valid netmask/hostmask + A (address, netmask/prefix) tuple. """ - # Parse the netmask/hostmask like an IP address. - try: - ip_int = cls._ip_int_from_string(ip_str) - except AddressValueError: - cls._report_invalid_netmask(ip_str) - - # Try matching a netmask (this would be /1*0*/ as a bitwise regexp). - # Note that the two ambiguous cases (all-ones and all-zeroes) are - # treated as netmasks. - try: - return cls._prefix_from_ip_int(ip_int) - except ValueError: - pass + if isinstance(arg, (int, bytes)): + addr = arg + prefix = cls._max_prefixlen + elif isinstance(arg, tuple) and len(arg) == 2: + addr, prefix = arg + else: + arg = _split_optional_netmask(arg) + addr = arg[0] + if len(arg) == 2: + prefix = arg[1] + else: + prefix = cls._max_prefixlen - # Invert the bits, and try matching a /0+1+/ hostmask instead. - ip_int ^= cls._ALL_ONES - try: - return cls._prefix_from_ip_int(ip_int) - except ValueError: - cls._report_invalid_netmask(ip_str) + return addr, prefix def __reduce__(self): return self.__class__, (str(self),) @@ -1080,20 +1072,41 @@ def _make_netmask(cls, arg): Argument can be: - an integer (the prefix length) - a string representing the prefix length (e.g. "24") - - a string representing the prefix netmask (e.g. "255.255.255.0") + - a string representing the netmask or hostmask (e.g. "255.255.255.0") + - a bytes object representing a netmask or hostmask + (e.g. b"\xff\xff\xff\x00") + - an IPv4Address object representing the netmask or hostmask """ if arg not in cls._netmask_cache: if isinstance(arg, int): prefixlen = arg + netmask = IPv4Address(cls._ip_int_from_prefix(prefixlen)) else: try: + if isinstance(arg, IPv4Address): + netmask = arg + else: + netmask = IPv4Address(arg) + except AddressValueError: # Check for a netmask in prefix length form prefixlen = cls._prefix_from_prefix_string(arg) - except NetmaskValueError: - # Check for a netmask or hostmask in dotted-quad form. - # This may raise NetmaskValueError. - prefixlen = cls._prefix_from_ip_string(arg) - netmask = IPv4Address(cls._ip_int_from_prefix(prefixlen)) + netmask = IPv4Address(cls._ip_int_from_prefix(prefixlen)) + else: + try: + # Try matching a netmask (this would be /1*0*/ as a + # bitwise regexp). Note that the two ambiguous cases + # (all-ones and all-zeroes) are treated as netmasks. + prefixlen = cls._prefix_from_ip_int(netmask._ip) + except ValueError: + # Invert the bits, and try matching a /0+1+/ hostmask + # instead. + ip_int = netmask._ip ^ cls._ALL_ONES + try: + prefixlen = cls._prefix_from_ip_int(ip_int) + except ValueError: + cls._report_invalid_netmask(arg) + else: + netmask = IPv4Address(ip_int) cls._netmask_cache[arg] = netmask, prefixlen return cls._netmask_cache[arg] @@ -1167,6 +1180,25 @@ def _string_from_ip_int(cls, ip_int): """ return '.'.join(map(str, ip_int.to_bytes(4, 'big'))) + @classmethod + def _get_addr_prefix_tuple(cls, arg): + """Make a (address, (netmask, prefix)) tuple from the given argument. + + Args: + arg: A string, bytes object or a tuple representing an address and + a network prefix. + + Returns: + A (address, (netmask, prefix)) tuple. + """ + addr, prefix = cls._split_addr_prefix(arg) + if isinstance(addr, IPv4Address): + if addr.__class__ is not IPv4Address: + addr = IPv4Address(addr._ip) + else: + addr = IPv4Address(addr) + return (addr, cls._make_netmask(prefix)) + def _reverse_pointer(self): """Return the reverse DNS pointer name for the IPv4 address. @@ -1305,30 +1337,11 @@ def is_link_local(self): class IPv4Interface(IPv4Address): def __init__(self, address): - if isinstance(address, (bytes, int)): - 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 = _split_optional_netmask(address) - IPv4Address.__init__(self, addr[0]) - - self.network = IPv4Network(address, strict=False) - self._prefixlen = self.network._prefixlen + addr_prefix = self._get_addr_prefix_tuple(address) + addr, (self.netmask, self._prefixlen) = addr_prefix + super().__init__(addr._ip) + self.network = IPv4Network((addr, self._prefixlen), strict=False) self.netmask = self.network.netmask self.hostmask = self.network.hostmask @@ -1435,30 +1448,15 @@ def __init__(self, address, strict=True): ValueError: If strict is True and a network address is not supplied. """ - # 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. - 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_prefix = self._get_addr_prefix_tuple(address) + self.network_address, (self.netmask, self._prefixlen) = addr_prefix - 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) else: - self.network_address = IPv4Address(packed & - int(self.netmask)) + self.network_address = IPv4Address(packed & int(self.netmask)) if self._prefixlen == (self._max_prefixlen - 1): self.hosts = self.__iter__ @@ -1539,14 +1537,26 @@ def _make_netmask(cls, arg): Argument can be: - an integer (the prefix length) - a string representing the prefix length (e.g. "24") - - a string representing the prefix netmask (e.g. "255.255.255.0") + - a bytes object representing the netmask (e.g. b'\xff'*16) + - an IPv6Address object representing the netmask """ if arg not in cls._netmask_cache: if isinstance(arg, int): prefixlen = arg + netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen)) else: - prefixlen = cls._prefix_from_prefix_string(arg) - netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen)) + if isinstance(arg, (bytes, IPv6Address)): + netmask = arg + if isinstance(netmask, bytes): + netmask = IPv6Address(netmask) + try: + prefixlen = cls._prefix_from_ip_int(netmask._ip) + except ValueError: + cls._report_invalid_netmask(arg) + else: + # Check for a netmask in prefix length form + prefixlen = cls._prefix_from_prefix_string(arg) + netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen)) cls._netmask_cache[arg] = netmask, prefixlen return cls._netmask_cache[arg] @@ -1778,6 +1788,25 @@ def _explode_shorthand_ip_string(self): return '%s/%d' % (':'.join(parts), self._prefixlen) return ':'.join(parts) + @classmethod + def _get_addr_prefix_tuple(cls, arg): + """Make a (address, (netmask, prefix)) tuple from the given argument. + + Args: + arg: A string, bytes object or a tuple representing an address and + a network prefix. + + Returns: + A (address, (netmask, prefix)) tuple. + """ + addr, prefix = cls._split_addr_prefix(arg) + if isinstance(addr, IPv6Address): + if addr.__class__ is not IPv6Address: + addr = IPv4Address(addr._ip) + else: + addr = IPv6Address(addr) + return (addr, cls._make_netmask(prefix)) + def _reverse_pointer(self): """Return the reverse DNS pointer name for the IPv6 address. @@ -1979,27 +2008,12 @@ def sixtofour(self): class IPv6Interface(IPv6Address): def __init__(self, address): - if isinstance(address, (bytes, int)): - 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_prefix = self._get_addr_prefix_tuple(address) + addr, (self.netmask, self._prefixlen) = addr_prefix + super().__init__(addr._ip) - addr = _split_optional_netmask(address) - IPv6Address.__init__(self, addr[0]) - self.network = IPv6Network(address, strict=False) + self.network = IPv6Network((addr, self._prefixlen), strict=False) self.netmask = self.network.netmask - self._prefixlen = self.network._prefixlen self.hostmask = self.network.hostmask def __str__(self): @@ -2110,30 +2124,15 @@ def __init__(self, address, strict=True): ValueError: If strict was True and a network address was not supplied. """ - # 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. - 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_prefix = self._get_addr_prefix_tuple(address) + self.network_address, (self.netmask, self._prefixlen) = addr_prefix - 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) else: - self.network_address = IPv6Address(packed & - int(self.netmask)) + 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 15317c9446306a..670a6e8c65a0fe 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -439,6 +439,22 @@ def test_valid_netmask(self): self.assertEqual( str(self.factory('0.0.0.0/%s' % net.hostmask)), net_str) + def test_tuple_constructor(self): + tests = [ + ('192.168.0.0/24', (('192.168.0.0', b'\xff\xff\xff\x00'))), + ('192.168.0.0/23', ((bytes([192, 168, 0, 0]), b'\xff\xff\xfe\x00'))), + ('10.0.0.0/8', + ((ipaddress.IPv4Address('10.0.0.0'), b'\x00\xff\xff\xff'))), + ('10.0.0.0/8', + ((ipaddress.IPv4Interface('10.0.0.0/32'), b'\x00\xff\xff\xff'))), + ('172.16.0.0/16', + (('172.16.0.0', ipaddress.IPv4Address('255.255.0.0')))), + ('172.16.0.0/16', + (('172.16.0.0', ipaddress.IPv4Address('0.0.255.255')))), + ] + for expected, arg in tests: + self.assertInstancesEqual(arg, expected) + def test_netmask_errors(self): def assertBadNetmask(addr, netmask): msg = "%r is not a valid netmask" % netmask @@ -555,6 +571,18 @@ def test_valid_netmask(self): # Zero prefix is treated as decimal. self.assertEqual(str(self.factory('::/0%d' % i)), net_str) + def test_tuple_constructor(self): + tests = [ + ('fc00::/8', (('fc00::', b'\xff' + 15 * b'\x00'))), + ('fc12:3456::/39', + ((b'\xfc\x12\x34\x56' + 12 * b'\x00', + 4 * b'\xff' + b'\xfe' + 11 * b'\x00'))), + ('2001:db8::/32', + (('2001:db8::', ipaddress.IPv6Address('ffff:ffff::')))), + ] + for expected, arg in tests: + self.assertInstancesEqual(arg, expected) + def test_netmask_errors(self): def assertBadNetmask(addr, netmask): msg = "%r is not a valid netmask" % netmask