From 1be8d7fc7f616175c46e4f323b0351979174f1da Mon Sep 17 00:00:00 2001 From: David Groves Date: Fri, 11 Apr 2025 18:02:06 +0100 Subject: [PATCH 1/9] Renamed broadcast_address to more generic last_address. Removed tests looking for broadcast_address --- Lib/ipaddress.py | 76 +++++++++++++++++++++++--------------- Lib/test/test_ipaddress.py | 18 --------- 2 files changed, 46 insertions(+), 48 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 703fa289dda1fb..e6af675d0dc141 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -12,6 +12,7 @@ import functools +import warnings IPV4LENGTH = 32 IPV6LENGTH = 128 @@ -295,7 +296,7 @@ def _collapse_addresses_internal(addresses): if last is not None: # Since they are sorted, last.network_address <= net.network_address # is a given. - if last.broadcast_address >= net.broadcast_address: + if last.last_address >= net.last_address: continue yield net last = net @@ -685,28 +686,28 @@ def hosts(self): """ network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in range(network + 1, broadcast): + 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): + 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) + 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): @@ -746,14 +747,9 @@ def __contains__(self, other): 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 ( + self.last_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)) + other.last_address in self))) @functools.cached_property def hostmask(self): @@ -774,7 +770,7 @@ def with_hostmask(self): @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.network_address) + 1 @property def _address_class(self): @@ -968,7 +964,7 @@ def subnets(self, prefixlen_diff=1, new_prefix=None): new_prefixlen, self)) start = int(self.network_address) - end = int(self.broadcast_address) + 1 + 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)) @@ -1025,7 +1021,7 @@ def is_multicast(self): """ return (self.network_address.is_multicast and - self.broadcast_address.is_multicast) + self.last_address.is_multicast) @staticmethod def _is_subnet_of(a, b): @@ -1034,7 +1030,7 @@ def _is_subnet_of(a, b): 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) + b.last_address >= a.last_address) except AttributeError: raise TypeError(f"Unable to test subnet containment " f"between {a} and {b}") @@ -1057,7 +1053,7 @@ def is_reserved(self): """ return (self.network_address.is_reserved and - self.broadcast_address.is_reserved) + self.last_address.is_reserved) @property def is_link_local(self): @@ -1068,7 +1064,7 @@ def is_link_local(self): """ return (self.network_address.is_link_local and - self.broadcast_address.is_link_local) + self.last_address.is_link_local) @property def is_private(self): @@ -1080,10 +1076,10 @@ def is_private(self): """ return any(self.network_address in priv_network and - self.broadcast_address in priv_network + 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.last_address not in network for network in self._constants._private_networks_exceptions ) @@ -1108,7 +1104,7 @@ def is_unspecified(self): """ return (self.network_address.is_unspecified and - self.broadcast_address.is_unspecified) + self.last_address.is_unspecified) @property def is_loopback(self): @@ -1120,7 +1116,7 @@ def is_loopback(self): """ return (self.network_address.is_loopback and - self.broadcast_address.is_loopback) + self.last_address.is_loopback) class _BaseConstants: @@ -1561,6 +1557,15 @@ def is_global(self): self.broadcast_address in IPv4Network('100.64.0.0/10')) and not self.is_private) + @functools.cached_property + def last_address(self): + return self._address_class(int(self.network_address) | + int(self.hostmask)) + + @functools.cached_property + def broadcast_address(self): + return self.last_address + class _IPv4Constants: _linklocal_network = IPv4Network('169.254.0.0/16') @@ -2272,7 +2277,7 @@ class IPv6Network(_BaseV6, _BaseNetwork): Attributes: [examples for IPv6('2001:db8::1000/124')] .network_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 @@ -2337,8 +2342,8 @@ def hosts(self): """ network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in range(network + 1, broadcast + 1): + last = int(self.last_address) + for x in range(network + 1, last + 1): yield self._address_class(x) @property @@ -2354,7 +2359,18 @@ def is_site_local(self): """ return (self.network_address.is_site_local and - self.broadcast_address.is_site_local) + self.last_address.is_site_local) + + @property + def last_address(self): + """The last address in the network, the address with all the host bits set.""" + return self._address_class(int(self.network_address) | + int(self.hostmask)) + + @property + @warnings.deprecated("IPv6 has no broadcast addresses, use last_address instead for the address with all the host bits set.") + 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): From aa6e33f0ec27c24b1479ff09f9a9cad3b5eb9e57 Mon Sep 17 00:00:00 2001 From: David Groves Date: Fri, 11 Apr 2025 21:03:23 +0100 Subject: [PATCH 2/9] Added news and ACK --- Misc/ACKS | 1 + .../Library/2025-04-11-21-01-53.gh-issue-111442.40zGdE.rst | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-04-11-21-01-53.gh-issue-111442.40zGdE.rst 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. From 39e4d86022191ea103bda8ac8ce24c35709f7409 Mon Sep 17 00:00:00 2001 From: David Groves Date: Fri, 11 Apr 2025 21:07:58 +0100 Subject: [PATCH 3/9] Updated docs to remove broadcast_address from IPv6Network and add last_address to both IPv4Network and IPv6Network. --- Doc/library/ipaddress.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index e5bdfbb144b65a..58068335f3306e 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -553,6 +553,11 @@ dictionaries. 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 @@ -757,7 +762,7 @@ dictionaries. .. attribute:: is_loopback .. attribute:: is_link_local .. attribute:: network_address - .. attribute:: broadcast_address + .. attribute:: last_address .. attribute:: hostmask .. attribute:: netmask .. attribute:: with_prefixlen From 5995cad64e78fa18e69b2015ef3865ab5e4c748f Mon Sep 17 00:00:00 2001 From: David Groves Date: Fri, 11 Apr 2025 21:23:56 +0100 Subject: [PATCH 4/9] Updated docs --- Doc/library/ipaddress.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index 58068335f3306e..75faf01a00cee5 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -560,7 +560,7 @@ dictionaries. .. 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 From f8d797fd33c4a6a7b2cee1002f687b85bf79f04f Mon Sep 17 00:00:00 2001 From: David Groves Date: Fri, 11 Apr 2025 21:38:11 +0100 Subject: [PATCH 5/9] Docs updated on first and last addresses --- Doc/library/ipaddress.rst | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index 75faf01a00cee5..c467bc486b09e1 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -548,6 +548,11 @@ 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 @@ -761,8 +766,21 @@ dictionaries. .. attribute:: is_reserved .. attribute:: is_loopback .. attribute:: is_link_local - .. attribute:: network_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 From c48a49619cec56d46d71409df374b2590ad64326 Mon Sep 17 00:00:00 2001 From: David Groves Date: Fri, 11 Apr 2025 21:47:36 +0100 Subject: [PATCH 6/9] Moved first_address and last_address to correct classes, and added network_address and subnet_router_anycast_address --- Lib/ipaddress.py | 125 ++++++++++++++++++++++++++--------------------- 1 file changed, 68 insertions(+), 57 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index e6af675d0dc141..3184223a336880 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -294,7 +294,7 @@ 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.last_address >= net.last_address: continue @@ -338,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" % ( @@ -676,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. @@ -685,19 +685,19 @@ def hosts(self): or broadcast addresses. """ - network = int(self.network_address) + 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) + 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) + network = int(self.first_address) last = int(self.last_address) if n >= 0: if network + n > last: @@ -715,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 @@ -724,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. @@ -742,35 +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 ( + return self.first_address in other or ( self.last_address in other or ( - other.network_address in self 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.network_address)) + + @functools.cached_property + def last_address(self): + return self._address_class(int(self.network_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.last_address) - int(self.network_address) + 1 + return int(self.last_address) - int(self.first_address) + 1 @property def _address_class(self): @@ -833,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() @@ -896,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: @@ -915,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. @@ -963,7 +972,7 @@ 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) + 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): @@ -1007,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 )) @@ -1020,7 +1029,7 @@ def is_multicast(self): See RFC 2373 2.7 for details. """ - return (self.network_address.is_multicast and + return (self.first_address.is_multicast and self.last_address.is_multicast) @staticmethod @@ -1029,7 +1038,7 @@ 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 + return (b.first_address <= a.first_address and b.last_address >= a.last_address) except AttributeError: raise TypeError(f"Unable to test subnet containment " @@ -1052,7 +1061,7 @@ def is_reserved(self): reserved IPv6 Network ranges. """ - return (self.network_address.is_reserved and + return (self.first_address.is_reserved and self.last_address.is_reserved) @property @@ -1063,7 +1072,7 @@ def is_link_local(self): A boolean, True if the address is reserved per RFC 4291. """ - return (self.network_address.is_link_local and + return (self.first_address.is_link_local and self.last_address.is_link_local) @property @@ -1075,10 +1084,10 @@ def is_private(self): iana-ipv4-special-registry or iana-ipv6-special-registry. """ - return any(self.network_address in priv_network and + 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.first_address not in network and self.last_address not in network for network in self._constants._private_networks_exceptions ) @@ -1103,7 +1112,7 @@ def is_unspecified(self): RFC 2373 2.5.2. """ - return (self.network_address.is_unspecified and + return (self.first_address.is_unspecified and self.last_address.is_unspecified) @property @@ -1115,7 +1124,7 @@ def is_loopback(self): RFC 2373 2.5.3. """ - return (self.network_address.is_loopback and + return (self.first_address.is_loopback and self.last_address.is_loopback) @@ -1452,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__ @@ -1481,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') @@ -1528,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): @@ -1553,19 +1562,17 @@ def is_global(self): iana-ipv4-special-registry. """ - return (not (self.network_address in IPv4Network('100.64.0.0/10') and + return (not (self.first_address in IPv4Network('100.64.0.0/10') and self.broadcast_address in IPv4Network('100.64.0.0/10')) and not self.is_private) - @functools.cached_property - def last_address(self): - return self._address_class(int(self.network_address) | - int(self.hostmask)) - @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') @@ -1861,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: @@ -2238,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__ @@ -2275,7 +2282,7 @@ 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') .last_address: IPv6Address('2001:db8::100f') .netmask: IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0') @@ -2319,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): @@ -2341,7 +2348,7 @@ def hosts(self): Subnet-Router anycast address. """ - network = int(self.network_address) + network = int(self.first_address) last = int(self.last_address) for x in range(network + 1, last + 1): yield self._address_class(x) @@ -2358,21 +2365,25 @@ 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 + return (self.first_address.is_site_local and self.last_address.is_site_local) - @property - def last_address(self): - """The last address in the network, the address with all the host bits set.""" - return self._address_class(int(self.network_address) | - int(self.hostmask)) + @functools.cached_property + def subnet_router_anycast_address(self): + return self.first_address - @property + @functools.cached_property + @warnings.deprecated("IPv6 has no network addresses, use 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, use last_address instead for the address with all the host bits set.") def broadcast_address(self): return self.last_address + class _IPv6Constants: _linklocal_network = IPv6Network('fe80::/10') From d4bdcbccf23d7b35cfee6dde1303f62b62fe7047 Mon Sep 17 00:00:00 2001 From: David Groves Date: Fri, 11 Apr 2025 22:00:01 +0100 Subject: [PATCH 7/9] Fixes --- Lib/ipaddress.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 3184223a336880..0501c99f75acb9 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -757,11 +757,11 @@ def hostmask(self): @functools.cached_property def first_address(self): - return self._address_class(int(self.network_address)) + return self._address_class(int(self.first_address)) @functools.cached_property def last_address(self): - return self._address_class(int(self.network_address) | + return self._address_class(int(self.first_address) | int(self.hostmask)) @property @@ -1563,7 +1563,7 @@ def is_global(self): """ return (not (self.first_address in IPv4Network('100.64.0.0/10') and - self.broadcast_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 @@ -2373,12 +2373,12 @@ def subnet_router_anycast_address(self): return self.first_address @functools.cached_property - @warnings.deprecated("IPv6 has no network addresses, use first_address or subnet_router_anycast_address instead.") + @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, use last_address instead for the address with all the host bits set.") + @warnings.deprecated("IPv6 has no broadcast addresses, consider using last_address instead.") def broadcast_address(self): return self.last_address From b7dae66957a2cfea06ba004edf1fcb682d988d51 Mon Sep 17 00:00:00 2001 From: David Groves Date: Fri, 11 Apr 2025 22:53:23 +0100 Subject: [PATCH 8/9] Run Pre-commit --- Lib/ipaddress.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 0501c99f75acb9..e54dd38684d093 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -2383,7 +2383,6 @@ def broadcast_address(self): return self.last_address - class _IPv6Constants: _linklocal_network = IPv6Network('fe80::/10') From 83529adf7f3d264923bc71a2a48fb7494c8e06d7 Mon Sep 17 00:00:00 2001 From: David Groves Date: Fri, 11 Apr 2025 22:54:38 +0100 Subject: [PATCH 9/9] lint / format --- Doc/library/ipaddress.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index c467bc486b09e1..26c8b1883f71b7 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -562,7 +562,7 @@ dictionaries. 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 same as :attr:`last_address` for IPv4 networks. Packets sent to this