diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index e5bdfbb144b65a..26c8b1883f71b7 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -548,14 +548,24 @@ dictionaries. These attributes are true for the network as a whole if they are true for both the network address and the broadcast address. + .. attribute:: first_address + + The first address in the network, the one with all the host bits cleared. + This is the same as the network_address for IPv4 networks. + .. attribute:: network_address The network address for the network. The network address and the prefix length together uniquely define a network. + .. attribute:: last_address + + The last address in the network, the address with all the host bits set. + This is the same as the broadcast_address for IPv4 networks. + .. attribute:: broadcast_address - The broadcast address for the network. Packets sent to the broadcast + The same as :attr:`last_address` for IPv4 networks. Packets sent to this address should be received by every host on the network. .. attribute:: hostmask @@ -756,8 +766,21 @@ dictionaries. .. attribute:: is_reserved .. attribute:: is_loopback .. attribute:: is_link_local - .. attribute:: network_address - .. attribute:: broadcast_address + .. attribute:: first_address + + The first address in the network, the one with all the host bits cleared. + This is the same as the subnet_router_anycast_address for IPv6 networks. + + .. attribute:: subnet_router_anycast_address + + The Subnet-Router anycast address for the network, which is the first address + in the network. + + .. attribute:: last_address + + The last address in the network, the one with all the host bits set. + This has no special meaning for IPv6 networks. + .. attribute:: hostmask .. attribute:: netmask .. attribute:: with_prefixlen diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 703fa289dda1fb..e54dd38684d093 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -12,6 +12,7 @@ import functools +import warnings IPV4LENGTH = 32 IPV6LENGTH = 128 @@ -293,9 +294,9 @@ def _collapse_addresses_internal(addresses): last = None for net in sorted(subnets.values()): if last is not None: - # Since they are sorted, last.network_address <= net.network_address + # Since they are sorted, last.first_address <= net.first_address # is a given. - if last.broadcast_address >= net.broadcast_address: + if last.last_address >= net.last_address: continue yield net last = net @@ -337,7 +338,7 @@ def collapse_addresses(addresses): try: ips.append(ip.ip) except AttributeError: - ips.append(ip.network_address) + ips.append(ip.first_address) else: if nets and nets[-1].version != ip.version: raise TypeError("%s and %s are not of the same version" % ( @@ -675,7 +676,7 @@ def __repr__(self): return '%s(%r)' % (self.__class__.__name__, str(self)) def __str__(self): - return '%s/%d' % (self.network_address, self.prefixlen) + return '%s/%d' % (self.first_address, self.prefixlen) def hosts(self): """Generate Iterator over usable hosts in a network. @@ -684,29 +685,29 @@ def hosts(self): or broadcast addresses. """ - network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in range(network + 1, broadcast): + network = int(self.first_address) + last = int(self.last_address) + for x in range(network + 1, last): yield self._address_class(x) def __iter__(self): - network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in range(network, broadcast + 1): + network = int(self.first_address) + last = int(self.last_address) + for x in range(network, last + 1): yield self._address_class(x) def __getitem__(self, n): - network = int(self.network_address) - broadcast = int(self.broadcast_address) + network = int(self.first_address) + last = int(self.last_address) if n >= 0: - if network + n > broadcast: + if network + n > last: raise IndexError('address out of range') return self._address_class(network + n) else: n += 1 - if broadcast + n < network: + if last + n < network: raise IndexError('address out of range') - return self._address_class(broadcast + n) + return self._address_class(last + n) def __lt__(self, other): if not isinstance(other, _BaseNetwork): @@ -714,8 +715,8 @@ def __lt__(self, other): if self.version != other.version: raise TypeError('%s and %s are not of the same version' % ( self, other)) - if self.network_address != other.network_address: - return self.network_address < other.network_address + if self.first_address != other.first_address: + return self.first_address < other.first_address if self.netmask != other.netmask: return self.netmask < other.netmask return False @@ -723,13 +724,13 @@ def __lt__(self, other): def __eq__(self, other): try: return (self.version == other.version and - self.network_address == other.network_address and + self.first_address == other.first_address and int(self.netmask) == int(other.netmask)) except AttributeError: return NotImplemented def __hash__(self): - return hash(int(self.network_address) ^ int(self.netmask)) + return hash(int(self.first_address) ^ int(self.netmask)) def __contains__(self, other): # always false if one is v4 and the other is v6. @@ -741,40 +742,44 @@ def __contains__(self, other): # dealing with another address else: # address - return other._ip & self.netmask._ip == self.network_address._ip + return other._ip & self.netmask._ip == self.first_address._ip def overlaps(self, other): """Tell if self is partly contained in other.""" - return self.network_address in other or ( - self.broadcast_address in other or ( - other.network_address in self or ( - other.broadcast_address in self))) - - @functools.cached_property - def broadcast_address(self): - return self._address_class(int(self.network_address) | - int(self.hostmask)) + return self.first_address in other or ( + self.last_address in other or ( + other.first_address in self or ( + other.last_address in self))) @functools.cached_property def hostmask(self): return self._address_class(int(self.netmask) ^ self._ALL_ONES) + @functools.cached_property + def first_address(self): + return self._address_class(int(self.first_address)) + + @functools.cached_property + def last_address(self): + return self._address_class(int(self.first_address) | + int(self.hostmask)) + @property def with_prefixlen(self): - return '%s/%d' % (self.network_address, self._prefixlen) + return '%s/%d' % (self.first_address, self._prefixlen) @property def with_netmask(self): - return '%s/%s' % (self.network_address, self.netmask) + return '%s/%s' % (self.first_address, self.netmask) @property def with_hostmask(self): - return '%s/%s' % (self.network_address, self.hostmask) + return '%s/%s' % (self.first_address, self.hostmask) @property def num_addresses(self): """Number of hosts in the current subnet.""" - return int(self.broadcast_address) - int(self.network_address) + 1 + return int(self.last_address) - int(self.first_address) + 1 @property def _address_class(self): @@ -837,7 +842,7 @@ def address_exclude(self, other): return # Make sure we're comparing the network of other. - other = other.__class__('%s/%s' % (other.network_address, + other = other.__class__('%s/%s' % (other.first_address, other.prefixlen)) s1, s2 = self.subnets() @@ -900,11 +905,11 @@ def compare_networks(self, other): raise TypeError('%s and %s are not of the same type' % ( self, other)) # self.version == other.version below here: - if self.network_address < other.network_address: + if self.first_address < other.first_address: return -1 - if self.network_address > other.network_address: + if self.first_address > other.first_address: return 1 - # self.network_address == other.network_address below here: + # self.first_address == other.first_address below here: if self.netmask < other.netmask: return -1 if self.netmask > other.netmask: @@ -919,7 +924,7 @@ def _get_networks_key(self): and list.sort(). """ - return (self.version, self.network_address, self.netmask) + return (self.version, self.first_address, self.netmask) def subnets(self, prefixlen_diff=1, new_prefix=None): """The subnets which join to make the current subnet. @@ -967,8 +972,8 @@ def subnets(self, prefixlen_diff=1, new_prefix=None): 'prefix length diff %d is invalid for netblock %s' % ( new_prefixlen, self)) - start = int(self.network_address) - end = int(self.broadcast_address) + 1 + start = int(self.first_address) + end = int(self.last_address) + 1 step = (int(self.hostmask) + 1) >> prefixlen_diff for new_addr in range(start, end, step): current = self.__class__((new_addr, new_prefixlen)) @@ -1011,7 +1016,7 @@ def supernet(self, prefixlen_diff=1, new_prefix=None): 'current prefixlen is %d, cannot have a prefixlen_diff of %d' % (self.prefixlen, prefixlen_diff)) return self.__class__(( - int(self.network_address) & (int(self.netmask) << prefixlen_diff), + int(self.first_address) & (int(self.netmask) << prefixlen_diff), new_prefixlen )) @@ -1024,8 +1029,8 @@ def is_multicast(self): See RFC 2373 2.7 for details. """ - return (self.network_address.is_multicast and - self.broadcast_address.is_multicast) + return (self.first_address.is_multicast and + self.last_address.is_multicast) @staticmethod def _is_subnet_of(a, b): @@ -1033,8 +1038,8 @@ def _is_subnet_of(a, b): # Always false if one is v4 and the other is v6. if a.version != b.version: raise TypeError(f"{a} and {b} are not of the same version") - return (b.network_address <= a.network_address and - b.broadcast_address >= a.broadcast_address) + return (b.first_address <= a.first_address and + b.last_address >= a.last_address) except AttributeError: raise TypeError(f"Unable to test subnet containment " f"between {a} and {b}") @@ -1056,8 +1061,8 @@ def is_reserved(self): reserved IPv6 Network ranges. """ - return (self.network_address.is_reserved and - self.broadcast_address.is_reserved) + return (self.first_address.is_reserved and + self.last_address.is_reserved) @property def is_link_local(self): @@ -1067,8 +1072,8 @@ def is_link_local(self): A boolean, True if the address is reserved per RFC 4291. """ - return (self.network_address.is_link_local and - self.broadcast_address.is_link_local) + return (self.first_address.is_link_local and + self.last_address.is_link_local) @property def is_private(self): @@ -1079,11 +1084,11 @@ def is_private(self): iana-ipv4-special-registry or iana-ipv6-special-registry. """ - return any(self.network_address in priv_network and - self.broadcast_address in priv_network + return any(self.first_address in priv_network and + self.last_address in priv_network for priv_network in self._constants._private_networks) and all( - self.network_address not in network and - self.broadcast_address not in network + self.first_address not in network and + self.last_address not in network for network in self._constants._private_networks_exceptions ) @@ -1107,8 +1112,8 @@ def is_unspecified(self): RFC 2373 2.5.2. """ - return (self.network_address.is_unspecified and - self.broadcast_address.is_unspecified) + return (self.first_address.is_unspecified and + self.last_address.is_unspecified) @property def is_loopback(self): @@ -1119,8 +1124,8 @@ def is_loopback(self): RFC 2373 2.5.3. """ - return (self.network_address.is_loopback and - self.broadcast_address.is_loopback) + return (self.first_address.is_loopback and + self.last_address.is_loopback) class _BaseConstants: @@ -1456,7 +1461,7 @@ def __lt__(self, other): return False def __hash__(self): - return hash((self._ip, self._prefixlen, int(self.network.network_address))) + return hash((self._ip, self._prefixlen, int(self.network.first_address))) __reduce__ = _IPAddressBase.__reduce__ @@ -1485,7 +1490,7 @@ class IPv4Network(_BaseV4, _BaseNetwork): """This class represents and manipulates 32-bit IPv4 network + addresses.. Attributes: [examples for IPv4Network('192.0.2.0/27')] - .network_address: IPv4Address('192.0.2.0') + .first_address: IPv4Address('192.0.2.0') .hostmask: IPv4Address('0.0.0.31') .broadcast_address: IPv4Address('192.0.2.32') .netmask: IPv4Address('255.255.255.224') @@ -1532,14 +1537,14 @@ def __init__(self, address, strict=True): """ addr, mask = self._split_addr_prefix(address) - self.network_address = IPv4Address(addr) + self.first_address = IPv4Address(addr) self.netmask, self._prefixlen = self._make_netmask(mask) - packed = int(self.network_address) + packed = int(self.first_address) if packed & int(self.netmask) != packed: if strict: raise ValueError('%s has host bits set' % self) else: - self.network_address = IPv4Address(packed & + self.first_address = IPv4Address(packed & int(self.netmask)) if self._prefixlen == (self.max_prefixlen - 1): @@ -1557,10 +1562,17 @@ def is_global(self): iana-ipv4-special-registry. """ - return (not (self.network_address in IPv4Network('100.64.0.0/10') and - self.broadcast_address in IPv4Network('100.64.0.0/10')) and + return (not (self.first_address in IPv4Network('100.64.0.0/10') and + self.last_address in IPv4Network('100.64.0.0/10')) and not self.is_private) + @functools.cached_property + def broadcast_address(self): + return self.last_address + + @functools.cached_property + def network_address(self): + return self.first_address class _IPv4Constants: _linklocal_network = IPv4Network('169.254.0.0/16') @@ -1856,7 +1868,7 @@ def _explode_shorthand_ip_string(self): """ if isinstance(self, IPv6Network): - ip_str = str(self.network_address) + ip_str = str(self.first_address) elif isinstance(self, IPv6Interface): ip_str = str(self.ip) else: @@ -2233,7 +2245,7 @@ def __lt__(self, other): return False def __hash__(self): - return hash((self._ip, self._prefixlen, int(self.network.network_address))) + return hash((self._ip, self._prefixlen, int(self.network.first_address))) __reduce__ = _IPAddressBase.__reduce__ @@ -2270,9 +2282,9 @@ class IPv6Network(_BaseV6, _BaseNetwork): """This class represents and manipulates 128-bit IPv6 networks. Attributes: [examples for IPv6('2001:db8::1000/124')] - .network_address: IPv6Address('2001:db8::1000') + .first_address: IPv6Address('2001:db8::1000') .hostmask: IPv6Address('::f') - .broadcast_address: IPv6Address('2001:db8::100f') + .last_address: IPv6Address('2001:db8::100f') .netmask: IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0') .prefixlen: 124 @@ -2314,14 +2326,14 @@ def __init__(self, address, strict=True): """ addr, mask = self._split_addr_prefix(address) - self.network_address = IPv6Address(addr) + self.first_address = IPv6Address(addr) self.netmask, self._prefixlen = self._make_netmask(mask) - packed = int(self.network_address) + packed = int(self.first_address) if packed & int(self.netmask) != packed: if strict: raise ValueError('%s has host bits set' % self) else: - self.network_address = IPv6Address(packed & + self.first_address = IPv6Address(packed & int(self.netmask)) if self._prefixlen == (self.max_prefixlen - 1): @@ -2336,9 +2348,9 @@ def hosts(self): Subnet-Router anycast address. """ - network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in range(network + 1, broadcast + 1): + network = int(self.first_address) + last = int(self.last_address) + for x in range(network + 1, last + 1): yield self._address_class(x) @property @@ -2353,8 +2365,22 @@ def is_site_local(self): A boolean, True if the address is reserved per RFC 3513 2.5.6. """ - return (self.network_address.is_site_local and - self.broadcast_address.is_site_local) + return (self.first_address.is_site_local and + self.last_address.is_site_local) + + @functools.cached_property + def subnet_router_anycast_address(self): + return self.first_address + + @functools.cached_property + @warnings.deprecated("IPv6 has no network addresses, consider using first_address or subnet_router_anycast_address instead.") + def network_address(self): + return self.first_address + + @functools.cached_property + @warnings.deprecated("IPv6 has no broadcast addresses, consider using last_address instead.") + def broadcast_address(self): + return self.last_address class _IPv6Constants: diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index d04012d1afd540..40a8b4a0b7ceb6 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -1393,16 +1393,6 @@ def testGetBroadcast(self): self.assertEqual(int(self.ipv4_network.broadcast_address), 16909311) self.assertEqual(str(self.ipv4_network.broadcast_address), '1.2.3.255') - self.assertEqual(int(self.ipv6_network.broadcast_address), - 42540616829182469451850391367731642367) - self.assertEqual(str(self.ipv6_network.broadcast_address), - '2001:658:22a:cafe:ffff:ffff:ffff:ffff') - - self.assertEqual(int(self.ipv6_scoped_network.broadcast_address), - 42540616829182469451850391367731642367) - self.assertEqual(str(self.ipv6_scoped_network.broadcast_address), - '2001:658:22a:cafe:ffff:ffff:ffff:ffff') - def testGetPrefixlen(self): self.assertEqual(self.ipv4_interface.network.prefixlen, 24) self.assertEqual(self.ipv6_interface.network.prefixlen, 64) @@ -2708,21 +2698,13 @@ def testNetworkElementCaching(self): self.assertEqual(self.ipv6_interface.network.network_address, ipaddress.IPv6Address('2001:658:22a:cafe::')) - self.assertEqual( - self.ipv6_network.broadcast_address, - ipaddress.IPv6Address('2001:658:22a:cafe:ffff:ffff:ffff:ffff')) self.assertEqual(self.ipv6_network.hostmask, ipaddress.IPv6Address('::ffff:ffff:ffff:ffff')) - self.assertEqual( - self.ipv6_interface.network.broadcast_address, - ipaddress.IPv6Address('2001:658:22a:cafe:ffff:ffff:ffff:ffff')) self.assertEqual(self.ipv6_interface.network.hostmask, ipaddress.IPv6Address('::ffff:ffff:ffff:ffff')) # V6 - check we're cached - self.assertIn('broadcast_address', self.ipv6_network.__dict__) self.assertIn('hostmask', self.ipv6_network.__dict__) - self.assertIn('broadcast_address', self.ipv6_interface.network.__dict__) self.assertIn('hostmask', self.ipv6_interface.network.__dict__) def testTeredo(self): diff --git a/Misc/ACKS b/Misc/ACKS index 814a530f7b79d4..f433630efd307b 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -687,6 +687,7 @@ Fabian Groffen Linus Groh Eric Groo Daniel Andrade Groppe +David Groves Dag Gruneau Filip GruszczyƄski Andrii Grynenko diff --git a/Misc/NEWS.d/next/Library/2025-04-11-21-01-53.gh-issue-111442.40zGdE.rst b/Misc/NEWS.d/next/Library/2025-04-11-21-01-53.gh-issue-111442.40zGdE.rst new file mode 100644 index 00000000000000..d4e91cc4140c18 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-11-21-01-53.gh-issue-111442.40zGdE.rst @@ -0,0 +1,4 @@ +Remove broadcast from IPv6Networks, as IPv6 does not have broadcasts in the +same way IPv4 does. Internally last_address is now used instead of +broadcast_address, and broadcast_address now aliases to last_address for +IPv4Networks.