@@ -310,7 +310,7 @@ def collapse_addresses(addresses):
310310 [IPv4Network('192.0.2.0/24')]
311311
312312 Args:
313- addresses: An iterator of IPv4Network or IPv6Network objects.
313+ addresses: An iterable of IPv4Network or IPv6Network objects.
314314
315315 Returns:
316316 An iterator of the collapsed IPv(4|6)Network objects.
@@ -734,7 +734,7 @@ def __eq__(self, other):
734734 return NotImplemented
735735
736736 def __hash__ (self ):
737- return hash (int (self .network_address ) ^ int (self .netmask ))
737+ return hash (( int (self .network_address ), int (self .netmask ) ))
738738
739739 def __contains__ (self , other ):
740740 # always false if one is v4 and the other is v6.
@@ -1086,7 +1086,11 @@ def is_private(self):
10861086 """
10871087 return any (self .network_address in priv_network and
10881088 self .broadcast_address in priv_network
1089- for priv_network in self ._constants ._private_networks )
1089+ for priv_network in self ._constants ._private_networks ) and all (
1090+ self .network_address not in network and
1091+ self .broadcast_address not in network
1092+ for network in self ._constants ._private_networks_exceptions
1093+ )
10901094
10911095 @property
10921096 def is_global (self ):
@@ -1333,18 +1337,41 @@ def is_reserved(self):
13331337 @property
13341338 @functools .lru_cache ()
13351339 def is_private (self ):
1336- """Test if this address is allocated for private networks.
1340+ """``True`` if the address is defined as not globally reachable by
1341+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
1342+ (for IPv6) with the following exceptions:
13371343
1338- Returns:
1339- A boolean, True if the address is reserved per
1340- iana-ipv4-special-registry.
1344+ * ``is_private`` is ``False`` for ``100.64.0.0/10``
1345+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
1346+ semantics of the underlying IPv4 addresses and the following condition holds
1347+ (see :attr:`IPv6Address.ipv4_mapped`)::
13411348
1349+ address.is_private == address.ipv4_mapped.is_private
1350+
1351+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
1352+ IPv4 range where they are both ``False``.
13421353 """
1343- return any (self in net for net in self ._constants ._private_networks )
1354+ return (
1355+ any (self in net for net in self ._constants ._private_networks )
1356+ and all (self not in net for net in self ._constants ._private_networks_exceptions )
1357+ )
13441358
13451359 @property
13461360 @functools .lru_cache ()
13471361 def is_global (self ):
1362+ """``True`` if the address is defined as globally reachable by
1363+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
1364+ (for IPv6) with the following exception:
1365+
1366+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
1367+ semantics of the underlying IPv4 addresses and the following condition holds
1368+ (see :attr:`IPv6Address.ipv4_mapped`)::
1369+
1370+ address.is_global == address.ipv4_mapped.is_global
1371+
1372+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
1373+ IPv4 range where they are both ``False``.
1374+ """
13481375 return self not in self ._constants ._public_network and not self .is_private
13491376
13501377 @property
@@ -1389,6 +1416,16 @@ def is_link_local(self):
13891416 """
13901417 return self in self ._constants ._linklocal_network
13911418
1419+ @property
1420+ def ipv6_mapped (self ):
1421+ """Return the IPv4-mapped IPv6 address.
1422+
1423+ Returns:
1424+ The IPv4-mapped IPv6 address per RFC 4291.
1425+
1426+ """
1427+ return IPv6Address (f'::ffff:{ self } ' )
1428+
13921429
13931430class IPv4Interface (IPv4Address ):
13941431
@@ -1548,13 +1585,15 @@ class _IPv4Constants:
15481585
15491586 _public_network = IPv4Network ('100.64.0.0/10' )
15501587
1588+ # Not globally reachable address blocks listed on
1589+ # https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
15511590 _private_networks = [
15521591 IPv4Network ('0.0.0.0/8' ),
15531592 IPv4Network ('10.0.0.0/8' ),
15541593 IPv4Network ('127.0.0.0/8' ),
15551594 IPv4Network ('169.254.0.0/16' ),
15561595 IPv4Network ('172.16.0.0/12' ),
1557- IPv4Network ('192.0.0.0/29 ' ),
1596+ IPv4Network ('192.0.0.0/24 ' ),
15581597 IPv4Network ('192.0.0.170/31' ),
15591598 IPv4Network ('192.0.2.0/24' ),
15601599 IPv4Network ('192.168.0.0/16' ),
@@ -1565,6 +1604,11 @@ class _IPv4Constants:
15651604 IPv4Network ('255.255.255.255/32' ),
15661605 ]
15671606
1607+ _private_networks_exceptions = [
1608+ IPv4Network ('192.0.0.9/32' ),
1609+ IPv4Network ('192.0.0.10/32' ),
1610+ ]
1611+
15681612 _reserved_network = IPv4Network ('240.0.0.0/4' )
15691613
15701614 _unspecified_address = IPv4Address ('0.0.0.0' )
@@ -1630,8 +1674,18 @@ def _ip_int_from_string(cls, ip_str):
16301674 """
16311675 if not ip_str :
16321676 raise AddressValueError ('Address cannot be empty' )
1633-
1634- parts = ip_str .split (':' )
1677+ if len (ip_str ) > 45 :
1678+ shorten = ip_str
1679+ if len (shorten ) > 100 :
1680+ shorten = f'{ ip_str [:45 ]} ({ len (ip_str )- 90 } chars elided){ ip_str [- 45 :]} '
1681+ raise AddressValueError (f"At most 45 characters expected in "
1682+ f"{ shorten !r} " )
1683+
1684+ # We want to allow more parts than the max to be 'split'
1685+ # to preserve the correct error message when there are
1686+ # too many parts combined with '::'
1687+ _max_parts = cls ._HEXTET_COUNT + 1
1688+ parts = ip_str .split (':' , maxsplit = _max_parts )
16351689
16361690 # An IPv6 address needs at least 2 colons (3 parts).
16371691 _min_parts = 3
@@ -1651,7 +1705,6 @@ def _ip_int_from_string(cls, ip_str):
16511705 # An IPv6 address can't have more than 8 colons (9 parts).
16521706 # The extra colon comes from using the "::" notation for a single
16531707 # leading or trailing zero part.
1654- _max_parts = cls ._HEXTET_COUNT + 1
16551708 if len (parts ) > _max_parts :
16561709 msg = "At most %d colons permitted in %r" % (_max_parts - 1 , ip_str )
16571710 raise AddressValueError (msg )
@@ -1923,8 +1976,49 @@ def __init__(self, address):
19231976
19241977 self ._ip = self ._ip_int_from_string (addr_str )
19251978
1979+ def _explode_shorthand_ip_string (self ):
1980+ ipv4_mapped = self .ipv4_mapped
1981+ if ipv4_mapped is None :
1982+ return super ()._explode_shorthand_ip_string ()
1983+ prefix_len = 30
1984+ raw_exploded_str = super ()._explode_shorthand_ip_string ()
1985+ return f"{ raw_exploded_str [:prefix_len ]} { ipv4_mapped !s} "
1986+
1987+ def _reverse_pointer (self ):
1988+ ipv4_mapped = self .ipv4_mapped
1989+ if ipv4_mapped is None :
1990+ return super ()._reverse_pointer ()
1991+ prefix_len = 30
1992+ raw_exploded_str = super ()._explode_shorthand_ip_string ()[:prefix_len ]
1993+ # ipv4 encoded using hexadecimal nibbles instead of decimals
1994+ ipv4_int = ipv4_mapped ._ip
1995+ reverse_chars = f"{ raw_exploded_str } { ipv4_int :008x} " [::- 1 ].replace (':' , '' )
1996+ return '.' .join (reverse_chars ) + '.ip6.arpa'
1997+
1998+ def _ipv4_mapped_ipv6_to_str (self ):
1999+ """Return convenient text representation of IPv4-mapped IPv6 address
2000+
2001+ See RFC 4291 2.5.5.2, 2.2 p.3 for details.
2002+
2003+ Returns:
2004+ A string, 'x:x:x:x:x:x:d.d.d.d', where the 'x's are the hexadecimal values of
2005+ the six high-order 16-bit pieces of the address, and the 'd's are
2006+ the decimal values of the four low-order 8-bit pieces of the
2007+ address (standard IPv4 representation) as defined in RFC 4291 2.2 p.3.
2008+
2009+ """
2010+ ipv4_mapped = self .ipv4_mapped
2011+ if ipv4_mapped is None :
2012+ raise AddressValueError ("Can not apply to non-IPv4-mapped IPv6 address %s" % str (self ))
2013+ high_order_bits = self ._ip >> 32
2014+ return "%s:%s" % (self ._string_from_ip_int (high_order_bits ), str (ipv4_mapped ))
2015+
19262016 def __str__ (self ):
1927- ip_str = super ().__str__ ()
2017+ ipv4_mapped = self .ipv4_mapped
2018+ if ipv4_mapped is None :
2019+ ip_str = super ().__str__ ()
2020+ else :
2021+ ip_str = self ._ipv4_mapped_ipv6_to_str ()
19282022 return ip_str + '%' + self ._scope_id if self ._scope_id else ip_str
19292023
19302024 def __hash__ (self ):
@@ -1967,6 +2061,9 @@ def is_multicast(self):
19672061 See RFC 2373 2.7 for details.
19682062
19692063 """
2064+ ipv4_mapped = self .ipv4_mapped
2065+ if ipv4_mapped is not None :
2066+ return ipv4_mapped .is_multicast
19702067 return self in self ._constants ._multicast_network
19712068
19722069 @property
@@ -1978,6 +2075,9 @@ def is_reserved(self):
19782075 reserved IPv6 Network ranges.
19792076
19802077 """
2078+ ipv4_mapped = self .ipv4_mapped
2079+ if ipv4_mapped is not None :
2080+ return ipv4_mapped .is_reserved
19812081 return any (self in x for x in self ._constants ._reserved_networks )
19822082
19832083 @property
@@ -1988,6 +2088,9 @@ def is_link_local(self):
19882088 A boolean, True if the address is reserved per RFC 4291.
19892089
19902090 """
2091+ ipv4_mapped = self .ipv4_mapped
2092+ if ipv4_mapped is not None :
2093+ return ipv4_mapped .is_link_local
19912094 return self in self ._constants ._linklocal_network
19922095
19932096 @property
@@ -2007,28 +2110,46 @@ def is_site_local(self):
20072110 @property
20082111 @functools .lru_cache ()
20092112 def is_private (self ):
2010- """Test if this address is allocated for private networks.
2113+ """``True`` if the address is defined as not globally reachable by
2114+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
2115+ (for IPv6) with the following exceptions:
20112116
2012- Returns:
2013- A boolean, True if the address is reserved per
2014- iana-ipv6-special-registry, or is ipv4_mapped and is
2015- reserved in the iana-ipv4-special-registry.
2117+ * ``is_private`` is ``False`` for ``100.64.0.0/10``
2118+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
2119+ semantics of the underlying IPv4 addresses and the following condition holds
2120+ (see :attr:`IPv6Address.ipv4_mapped`)::
2121+
2122+ address.is_private == address.ipv4_mapped.is_private
20162123
2124+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
2125+ IPv4 range where they are both ``False``.
20172126 """
20182127 ipv4_mapped = self .ipv4_mapped
20192128 if ipv4_mapped is not None :
20202129 return ipv4_mapped .is_private
2021- return any (self in net for net in self ._constants ._private_networks )
2130+ return (
2131+ any (self in net for net in self ._constants ._private_networks )
2132+ and all (self not in net for net in self ._constants ._private_networks_exceptions )
2133+ )
20222134
20232135 @property
20242136 def is_global (self ):
2025- """Test if this address is allocated for public networks.
2137+ """``True`` if the address is defined as globally reachable by
2138+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
2139+ (for IPv6) with the following exception:
20262140
2027- Returns:
2028- A boolean, true if the address is not reserved per
2029- iana-ipv6-special-registry.
2141+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
2142+ semantics of the underlying IPv4 addresses and the following condition holds
2143+ (see :attr:`IPv6Address.ipv4_mapped`)::
20302144
2145+ address.is_global == address.ipv4_mapped.is_global
2146+
2147+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
2148+ IPv4 range where they are both ``False``.
20312149 """
2150+ ipv4_mapped = self .ipv4_mapped
2151+ if ipv4_mapped is not None :
2152+ return ipv4_mapped .is_global
20322153 return not self .is_private
20332154
20342155 @property
@@ -2040,6 +2161,9 @@ def is_unspecified(self):
20402161 RFC 2373 2.5.2.
20412162
20422163 """
2164+ ipv4_mapped = self .ipv4_mapped
2165+ if ipv4_mapped is not None :
2166+ return ipv4_mapped .is_unspecified
20432167 return self ._ip == 0
20442168
20452169 @property
@@ -2051,6 +2175,9 @@ def is_loopback(self):
20512175 RFC 2373 2.5.3.
20522176
20532177 """
2178+ ipv4_mapped = self .ipv4_mapped
2179+ if ipv4_mapped is not None :
2180+ return ipv4_mapped .is_loopback
20542181 return self ._ip == 1
20552182
20562183 @property
@@ -2167,7 +2294,7 @@ def is_unspecified(self):
21672294
21682295 @property
21692296 def is_loopback (self ):
2170- return self . _ip == 1 and self .network .is_loopback
2297+ return super (). is_loopback and self .network .is_loopback
21712298
21722299
21732300class IPv6Network (_BaseV6 , _BaseNetwork ):
@@ -2268,19 +2395,33 @@ class _IPv6Constants:
22682395
22692396 _multicast_network = IPv6Network ('ff00::/8' )
22702397
2398+ # Not globally reachable address blocks listed on
2399+ # https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
22712400 _private_networks = [
22722401 IPv6Network ('::1/128' ),
22732402 IPv6Network ('::/128' ),
22742403 IPv6Network ('::ffff:0:0/96' ),
2404+ IPv6Network ('64:ff9b:1::/48' ),
22752405 IPv6Network ('100::/64' ),
22762406 IPv6Network ('2001::/23' ),
2277- IPv6Network ('2001:2::/48' ),
22782407 IPv6Network ('2001:db8::/32' ),
2279- IPv6Network ('2001:10::/28' ),
2408+ # IANA says N/A, let's consider it not globally reachable to be safe
2409+ IPv6Network ('2002::/16' ),
2410+ # RFC 9637: https://www.rfc-editor.org/rfc/rfc9637.html#section-6-2.2
2411+ IPv6Network ('3fff::/20' ),
22802412 IPv6Network ('fc00::/7' ),
22812413 IPv6Network ('fe80::/10' ),
22822414 ]
22832415
2416+ _private_networks_exceptions = [
2417+ IPv6Network ('2001:1::1/128' ),
2418+ IPv6Network ('2001:1::2/128' ),
2419+ IPv6Network ('2001:3::/32' ),
2420+ IPv6Network ('2001:4:112::/48' ),
2421+ IPv6Network ('2001:20::/28' ),
2422+ IPv6Network ('2001:30::/28' ),
2423+ ]
2424+
22842425 _reserved_networks = [
22852426 IPv6Network ('::/8' ), IPv6Network ('100::/8' ),
22862427 IPv6Network ('200::/7' ), IPv6Network ('400::/6' ),
0 commit comments