mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
[3.12] gh-113171: gh-65056: Fix "private" (non-global) IP address ranges (GH-113179) (GH-113186) (GH-118177)
* GH-113171: Fix "private" (non-global) IP address ranges (GH-113179) The _private_networks variables, used by various is_private implementations, were missing some ranges and at the same time had overly strict ranges (where there are more specific ranges considered globally reachable by the IANA registries). This patch updates the ranges with what was missing or otherwise incorrect. 100.64.0.0/10 is left alone, for now, as it's been made special in [1]. The _address_exclude_many() call returns 8 networks for IPv4, 121 networks for IPv6. [1] https://github.com/python/cpython/issues/61602 * GH-65056: Improve the IP address' is_global/is_private documentation (GH-113186) It wasn't clear what the semantics of is_global/is_private are and, when one gets to the bottom of it, it's not quite so simple (hence the exceptions listed). (cherry picked from commit2a4cbf17af
) (cherry picked from commit40d75c2b7f
) --------- Co-authored-by: Jakub Stasiak <jakub@stasiak.at>
This commit is contained in:
parent
2eaf9ba9ce
commit
f86b17ac51
5 changed files with 157 additions and 24 deletions
|
@ -178,18 +178,53 @@ write code that handles both IP versions correctly. Address objects are
|
||||||
|
|
||||||
.. attribute:: is_private
|
.. attribute:: is_private
|
||||||
|
|
||||||
``True`` if the address is allocated for private networks. See
|
``True`` if the address is defined as not globally reachable by
|
||||||
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
|
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
|
||||||
(for IPv6).
|
(for IPv6) with the following exceptions:
|
||||||
|
|
||||||
|
* ``is_private`` is ``False`` for the shared address space (``100.64.0.0/10``)
|
||||||
|
* For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
|
||||||
|
semantics of the underlying IPv4 addresses and the following condition holds
|
||||||
|
(see :attr:`IPv6Address.ipv4_mapped`)::
|
||||||
|
|
||||||
|
address.is_private == address.ipv4_mapped.is_private
|
||||||
|
|
||||||
|
``is_private`` has value opposite to :attr:`is_global`, except for the shared address space
|
||||||
|
(``100.64.0.0/10`` range) where they are both ``False``.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.12.4
|
||||||
|
|
||||||
|
Fixed some false positives and false negatives.
|
||||||
|
|
||||||
|
* ``192.0.0.0/24`` is considered private with the exception of ``192.0.0.9/32`` and
|
||||||
|
``192.0.0.10/32`` (previously: only the ``192.0.0.0/29`` sub-range was considered private).
|
||||||
|
* ``64:ff9b:1::/48`` is considered private.
|
||||||
|
* ``2002::/16`` is considered private.
|
||||||
|
* There are exceptions within ``2001::/23`` (otherwise considered private): ``2001:1::1/128``,
|
||||||
|
``2001:1::2/128``, ``2001:3::/32``, ``2001:4:112::/48``, ``2001:20::/28``, ``2001:30::/28``.
|
||||||
|
The exceptions are not considered private.
|
||||||
|
|
||||||
.. attribute:: is_global
|
.. attribute:: is_global
|
||||||
|
|
||||||
``True`` if the address is allocated for public networks. See
|
``True`` if the address is defined as globally reachable by
|
||||||
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
|
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
|
||||||
(for IPv6).
|
(for IPv6) with the following exception:
|
||||||
|
|
||||||
|
For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
|
||||||
|
semantics of the underlying IPv4 addresses and the following condition holds
|
||||||
|
(see :attr:`IPv6Address.ipv4_mapped`)::
|
||||||
|
|
||||||
|
address.is_global == address.ipv4_mapped.is_global
|
||||||
|
|
||||||
|
``is_global`` has value opposite to :attr:`is_private`, except for the shared address space
|
||||||
|
(``100.64.0.0/10`` range) where they are both ``False``.
|
||||||
|
|
||||||
.. versionadded:: 3.4
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
|
.. versionchanged:: 3.12.4
|
||||||
|
|
||||||
|
Fixed some false positives and false negatives, see :attr:`is_private` for details.
|
||||||
|
|
||||||
.. attribute:: is_unspecified
|
.. attribute:: is_unspecified
|
||||||
|
|
||||||
``True`` if the address is unspecified. See :RFC:`5735` (for IPv4)
|
``True`` if the address is unspecified. See :RFC:`5735` (for IPv4)
|
||||||
|
|
|
@ -2457,3 +2457,12 @@ Removed
|
||||||
|
|
||||||
* Remove the ``PyUnicode_InternImmortal()`` function macro.
|
* Remove the ``PyUnicode_InternImmortal()`` function macro.
|
||||||
(Contributed by Victor Stinner in :gh:`85858`.)
|
(Contributed by Victor Stinner in :gh:`85858`.)
|
||||||
|
|
||||||
|
Notable changes in 3.12.4
|
||||||
|
=========================
|
||||||
|
|
||||||
|
ipaddress
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``,
|
||||||
|
``IPv6Address``, ``IPv4Network`` and ``IPv6Network``.
|
||||||
|
|
|
@ -1086,7 +1086,11 @@ class _BaseNetwork(_IPAddressBase):
|
||||||
"""
|
"""
|
||||||
return any(self.network_address in priv_network and
|
return any(self.network_address in priv_network and
|
||||||
self.broadcast_address in priv_network
|
self.broadcast_address in priv_network
|
||||||
for priv_network in self._constants._private_networks)
|
for priv_network in self._constants._private_networks) and all(
|
||||||
|
self.network_address not in network and
|
||||||
|
self.broadcast_address not in network
|
||||||
|
for network in self._constants._private_networks_exceptions
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_global(self):
|
def is_global(self):
|
||||||
|
@ -1333,18 +1337,41 @@ class IPv4Address(_BaseV4, _BaseAddress):
|
||||||
@property
|
@property
|
||||||
@functools.lru_cache()
|
@functools.lru_cache()
|
||||||
def is_private(self):
|
def is_private(self):
|
||||||
"""Test if this address is allocated for private networks.
|
"""``True`` if the address is defined as not globally reachable by
|
||||||
|
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
|
||||||
|
(for IPv6) with the following exceptions:
|
||||||
|
|
||||||
Returns:
|
* ``is_private`` is ``False`` for ``100.64.0.0/10``
|
||||||
A boolean, True if the address is reserved per
|
* For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
|
||||||
iana-ipv4-special-registry.
|
semantics of the underlying IPv4 addresses and the following condition holds
|
||||||
|
(see :attr:`IPv6Address.ipv4_mapped`)::
|
||||||
|
|
||||||
|
address.is_private == address.ipv4_mapped.is_private
|
||||||
|
|
||||||
|
``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
|
||||||
|
IPv4 range where they are both ``False``.
|
||||||
"""
|
"""
|
||||||
return any(self in net for net in self._constants._private_networks)
|
return (
|
||||||
|
any(self in net for net in self._constants._private_networks)
|
||||||
|
and all(self not in net for net in self._constants._private_networks_exceptions)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@functools.lru_cache()
|
@functools.lru_cache()
|
||||||
def is_global(self):
|
def is_global(self):
|
||||||
|
"""``True`` if the address is defined as globally reachable by
|
||||||
|
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
|
||||||
|
(for IPv6) with the following exception:
|
||||||
|
|
||||||
|
For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
|
||||||
|
semantics of the underlying IPv4 addresses and the following condition holds
|
||||||
|
(see :attr:`IPv6Address.ipv4_mapped`)::
|
||||||
|
|
||||||
|
address.is_global == address.ipv4_mapped.is_global
|
||||||
|
|
||||||
|
``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
|
||||||
|
IPv4 range where they are both ``False``.
|
||||||
|
"""
|
||||||
return self not in self._constants._public_network and not self.is_private
|
return self not in self._constants._public_network and not self.is_private
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -1548,13 +1575,15 @@ class _IPv4Constants:
|
||||||
|
|
||||||
_public_network = IPv4Network('100.64.0.0/10')
|
_public_network = IPv4Network('100.64.0.0/10')
|
||||||
|
|
||||||
|
# Not globally reachable address blocks listed on
|
||||||
|
# https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
|
||||||
_private_networks = [
|
_private_networks = [
|
||||||
IPv4Network('0.0.0.0/8'),
|
IPv4Network('0.0.0.0/8'),
|
||||||
IPv4Network('10.0.0.0/8'),
|
IPv4Network('10.0.0.0/8'),
|
||||||
IPv4Network('127.0.0.0/8'),
|
IPv4Network('127.0.0.0/8'),
|
||||||
IPv4Network('169.254.0.0/16'),
|
IPv4Network('169.254.0.0/16'),
|
||||||
IPv4Network('172.16.0.0/12'),
|
IPv4Network('172.16.0.0/12'),
|
||||||
IPv4Network('192.0.0.0/29'),
|
IPv4Network('192.0.0.0/24'),
|
||||||
IPv4Network('192.0.0.170/31'),
|
IPv4Network('192.0.0.170/31'),
|
||||||
IPv4Network('192.0.2.0/24'),
|
IPv4Network('192.0.2.0/24'),
|
||||||
IPv4Network('192.168.0.0/16'),
|
IPv4Network('192.168.0.0/16'),
|
||||||
|
@ -1565,6 +1594,11 @@ class _IPv4Constants:
|
||||||
IPv4Network('255.255.255.255/32'),
|
IPv4Network('255.255.255.255/32'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
_private_networks_exceptions = [
|
||||||
|
IPv4Network('192.0.0.9/32'),
|
||||||
|
IPv4Network('192.0.0.10/32'),
|
||||||
|
]
|
||||||
|
|
||||||
_reserved_network = IPv4Network('240.0.0.0/4')
|
_reserved_network = IPv4Network('240.0.0.0/4')
|
||||||
|
|
||||||
_unspecified_address = IPv4Address('0.0.0.0')
|
_unspecified_address = IPv4Address('0.0.0.0')
|
||||||
|
@ -2007,27 +2041,42 @@ class IPv6Address(_BaseV6, _BaseAddress):
|
||||||
@property
|
@property
|
||||||
@functools.lru_cache()
|
@functools.lru_cache()
|
||||||
def is_private(self):
|
def is_private(self):
|
||||||
"""Test if this address is allocated for private networks.
|
"""``True`` if the address is defined as not globally reachable by
|
||||||
|
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
|
||||||
|
(for IPv6) with the following exceptions:
|
||||||
|
|
||||||
Returns:
|
* ``is_private`` is ``False`` for ``100.64.0.0/10``
|
||||||
A boolean, True if the address is reserved per
|
* For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
|
||||||
iana-ipv6-special-registry, or is ipv4_mapped and is
|
semantics of the underlying IPv4 addresses and the following condition holds
|
||||||
reserved in the iana-ipv4-special-registry.
|
(see :attr:`IPv6Address.ipv4_mapped`)::
|
||||||
|
|
||||||
|
address.is_private == address.ipv4_mapped.is_private
|
||||||
|
|
||||||
|
``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
|
||||||
|
IPv4 range where they are both ``False``.
|
||||||
"""
|
"""
|
||||||
ipv4_mapped = self.ipv4_mapped
|
ipv4_mapped = self.ipv4_mapped
|
||||||
if ipv4_mapped is not None:
|
if ipv4_mapped is not None:
|
||||||
return ipv4_mapped.is_private
|
return ipv4_mapped.is_private
|
||||||
return any(self in net for net in self._constants._private_networks)
|
return (
|
||||||
|
any(self in net for net in self._constants._private_networks)
|
||||||
|
and all(self not in net for net in self._constants._private_networks_exceptions)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_global(self):
|
def is_global(self):
|
||||||
"""Test if this address is allocated for public networks.
|
"""``True`` if the address is defined as globally reachable by
|
||||||
|
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
|
||||||
|
(for IPv6) with the following exception:
|
||||||
|
|
||||||
Returns:
|
For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
|
||||||
A boolean, true if the address is not reserved per
|
semantics of the underlying IPv4 addresses and the following condition holds
|
||||||
iana-ipv6-special-registry.
|
(see :attr:`IPv6Address.ipv4_mapped`)::
|
||||||
|
|
||||||
|
address.is_global == address.ipv4_mapped.is_global
|
||||||
|
|
||||||
|
``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
|
||||||
|
IPv4 range where they are both ``False``.
|
||||||
"""
|
"""
|
||||||
return not self.is_private
|
return not self.is_private
|
||||||
|
|
||||||
|
@ -2268,19 +2317,31 @@ class _IPv6Constants:
|
||||||
|
|
||||||
_multicast_network = IPv6Network('ff00::/8')
|
_multicast_network = IPv6Network('ff00::/8')
|
||||||
|
|
||||||
|
# Not globally reachable address blocks listed on
|
||||||
|
# https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
|
||||||
_private_networks = [
|
_private_networks = [
|
||||||
IPv6Network('::1/128'),
|
IPv6Network('::1/128'),
|
||||||
IPv6Network('::/128'),
|
IPv6Network('::/128'),
|
||||||
IPv6Network('::ffff:0:0/96'),
|
IPv6Network('::ffff:0:0/96'),
|
||||||
|
IPv6Network('64:ff9b:1::/48'),
|
||||||
IPv6Network('100::/64'),
|
IPv6Network('100::/64'),
|
||||||
IPv6Network('2001::/23'),
|
IPv6Network('2001::/23'),
|
||||||
IPv6Network('2001:2::/48'),
|
|
||||||
IPv6Network('2001:db8::/32'),
|
IPv6Network('2001:db8::/32'),
|
||||||
IPv6Network('2001:10::/28'),
|
# IANA says N/A, let's consider it not globally reachable to be safe
|
||||||
|
IPv6Network('2002::/16'),
|
||||||
IPv6Network('fc00::/7'),
|
IPv6Network('fc00::/7'),
|
||||||
IPv6Network('fe80::/10'),
|
IPv6Network('fe80::/10'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
_private_networks_exceptions = [
|
||||||
|
IPv6Network('2001:1::1/128'),
|
||||||
|
IPv6Network('2001:1::2/128'),
|
||||||
|
IPv6Network('2001:3::/32'),
|
||||||
|
IPv6Network('2001:4:112::/48'),
|
||||||
|
IPv6Network('2001:20::/28'),
|
||||||
|
IPv6Network('2001:30::/28'),
|
||||||
|
]
|
||||||
|
|
||||||
_reserved_networks = [
|
_reserved_networks = [
|
||||||
IPv6Network('::/8'), IPv6Network('100::/8'),
|
IPv6Network('::/8'), IPv6Network('100::/8'),
|
||||||
IPv6Network('200::/7'), IPv6Network('400::/6'),
|
IPv6Network('200::/7'), IPv6Network('400::/6'),
|
||||||
|
|
|
@ -2269,6 +2269,10 @@ class IpaddrUnitTest(unittest.TestCase):
|
||||||
self.assertEqual(True, ipaddress.ip_address(
|
self.assertEqual(True, ipaddress.ip_address(
|
||||||
'172.31.255.255').is_private)
|
'172.31.255.255').is_private)
|
||||||
self.assertEqual(False, ipaddress.ip_address('172.32.0.0').is_private)
|
self.assertEqual(False, ipaddress.ip_address('172.32.0.0').is_private)
|
||||||
|
self.assertFalse(ipaddress.ip_address('192.0.0.0').is_global)
|
||||||
|
self.assertTrue(ipaddress.ip_address('192.0.0.9').is_global)
|
||||||
|
self.assertTrue(ipaddress.ip_address('192.0.0.10').is_global)
|
||||||
|
self.assertFalse(ipaddress.ip_address('192.0.0.255').is_global)
|
||||||
|
|
||||||
self.assertEqual(True,
|
self.assertEqual(True,
|
||||||
ipaddress.ip_address('169.254.100.200').is_link_local)
|
ipaddress.ip_address('169.254.100.200').is_link_local)
|
||||||
|
@ -2294,6 +2298,7 @@ class IpaddrUnitTest(unittest.TestCase):
|
||||||
self.assertEqual(True, ipaddress.ip_network("169.254.0.0/16").is_private)
|
self.assertEqual(True, ipaddress.ip_network("169.254.0.0/16").is_private)
|
||||||
self.assertEqual(True, ipaddress.ip_network("172.16.0.0/12").is_private)
|
self.assertEqual(True, ipaddress.ip_network("172.16.0.0/12").is_private)
|
||||||
self.assertEqual(True, ipaddress.ip_network("192.0.0.0/29").is_private)
|
self.assertEqual(True, ipaddress.ip_network("192.0.0.0/29").is_private)
|
||||||
|
self.assertEqual(False, ipaddress.ip_network("192.0.0.9/32").is_private)
|
||||||
self.assertEqual(True, ipaddress.ip_network("192.0.0.170/31").is_private)
|
self.assertEqual(True, ipaddress.ip_network("192.0.0.170/31").is_private)
|
||||||
self.assertEqual(True, ipaddress.ip_network("192.0.2.0/24").is_private)
|
self.assertEqual(True, ipaddress.ip_network("192.0.2.0/24").is_private)
|
||||||
self.assertEqual(True, ipaddress.ip_network("192.168.0.0/16").is_private)
|
self.assertEqual(True, ipaddress.ip_network("192.168.0.0/16").is_private)
|
||||||
|
@ -2310,8 +2315,8 @@ class IpaddrUnitTest(unittest.TestCase):
|
||||||
self.assertEqual(True, ipaddress.ip_network("::/128").is_private)
|
self.assertEqual(True, ipaddress.ip_network("::/128").is_private)
|
||||||
self.assertEqual(True, ipaddress.ip_network("::ffff:0:0/96").is_private)
|
self.assertEqual(True, ipaddress.ip_network("::ffff:0:0/96").is_private)
|
||||||
self.assertEqual(True, ipaddress.ip_network("100::/64").is_private)
|
self.assertEqual(True, ipaddress.ip_network("100::/64").is_private)
|
||||||
self.assertEqual(True, ipaddress.ip_network("2001::/23").is_private)
|
|
||||||
self.assertEqual(True, ipaddress.ip_network("2001:2::/48").is_private)
|
self.assertEqual(True, ipaddress.ip_network("2001:2::/48").is_private)
|
||||||
|
self.assertEqual(False, ipaddress.ip_network("2001:3::/48").is_private)
|
||||||
self.assertEqual(True, ipaddress.ip_network("2001:db8::/32").is_private)
|
self.assertEqual(True, ipaddress.ip_network("2001:db8::/32").is_private)
|
||||||
self.assertEqual(True, ipaddress.ip_network("2001:10::/28").is_private)
|
self.assertEqual(True, ipaddress.ip_network("2001:10::/28").is_private)
|
||||||
self.assertEqual(True, ipaddress.ip_network("fc00::/7").is_private)
|
self.assertEqual(True, ipaddress.ip_network("fc00::/7").is_private)
|
||||||
|
@ -2390,6 +2395,20 @@ class IpaddrUnitTest(unittest.TestCase):
|
||||||
self.assertEqual(True, ipaddress.ip_address('0::0').is_unspecified)
|
self.assertEqual(True, ipaddress.ip_address('0::0').is_unspecified)
|
||||||
self.assertEqual(False, ipaddress.ip_address('::1').is_unspecified)
|
self.assertEqual(False, ipaddress.ip_address('::1').is_unspecified)
|
||||||
|
|
||||||
|
self.assertFalse(ipaddress.ip_address('64:ff9b:1::').is_global)
|
||||||
|
self.assertFalse(ipaddress.ip_address('2001::').is_global)
|
||||||
|
self.assertTrue(ipaddress.ip_address('2001:1::1').is_global)
|
||||||
|
self.assertTrue(ipaddress.ip_address('2001:1::2').is_global)
|
||||||
|
self.assertFalse(ipaddress.ip_address('2001:2::').is_global)
|
||||||
|
self.assertTrue(ipaddress.ip_address('2001:3::').is_global)
|
||||||
|
self.assertFalse(ipaddress.ip_address('2001:4::').is_global)
|
||||||
|
self.assertTrue(ipaddress.ip_address('2001:4:112::').is_global)
|
||||||
|
self.assertFalse(ipaddress.ip_address('2001:10::').is_global)
|
||||||
|
self.assertTrue(ipaddress.ip_address('2001:20::').is_global)
|
||||||
|
self.assertTrue(ipaddress.ip_address('2001:30::').is_global)
|
||||||
|
self.assertFalse(ipaddress.ip_address('2001:40::').is_global)
|
||||||
|
self.assertFalse(ipaddress.ip_address('2002::').is_global)
|
||||||
|
|
||||||
# some generic IETF reserved addresses
|
# some generic IETF reserved addresses
|
||||||
self.assertEqual(True, ipaddress.ip_address('100::').is_reserved)
|
self.assertEqual(True, ipaddress.ip_address('100::').is_reserved)
|
||||||
self.assertEqual(True, ipaddress.ip_network('4000::1/128').is_reserved)
|
self.assertEqual(True, ipaddress.ip_network('4000::1/128').is_reserved)
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
Fixed various false positives and false negatives in
|
||||||
|
|
||||||
|
* :attr:`ipaddress.IPv4Address.is_private` (see these docs for details)
|
||||||
|
* :attr:`ipaddress.IPv4Address.is_global`
|
||||||
|
* :attr:`ipaddress.IPv6Address.is_private`
|
||||||
|
* :attr:`ipaddress.IPv6Address.is_global`
|
||||||
|
|
||||||
|
Also in the corresponding :class:`ipaddress.IPv4Network` and :class:`ipaddress.IPv6Network`
|
||||||
|
attributes.
|
Loading…
Add table
Add a link
Reference in a new issue