Skip to content

Commit 234aea1

Browse files
encukoustratakis
authored andcommitted
00431: pythongh-113171: pythongh-65056: Fix "private" (non-global) IP address ranges (pythonGH-113179) (pythonGH-113186) (pythonGH-118177) (pythonGH-118472)
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] python#61602 In 3.10 and below, is_private checks whether the network and broadcast address are both private. In later versions (where the test wss backported from), it checks whether they both are in the same private network. For 0.0.0.0/0, both 0.0.0.0 and 255.225.255.255 are private, but one is in 0.0.0.0/8 ("This network") and the other in 255.255.255.255/32 ("Limited broadcast").
1 parent 9261527 commit 234aea1

File tree

5 files changed

+157
-24
lines changed

5 files changed

+157
-24
lines changed

Doc/library/ipaddress.rst

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,18 +178,53 @@ write code that handles both IP versions correctly. Address objects are
178178

179179
.. attribute:: is_private
180180

181-
``True`` if the address is allocated for private networks. See
181+
``True`` if the address is defined as not globally reachable by
182182
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
183-
(for IPv6).
183+
(for IPv6) with the following exceptions:
184+
185+
* ``is_private`` is ``False`` for the shared address space (``100.64.0.0/10``)
186+
* For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
187+
semantics of the underlying IPv4 addresses and the following condition holds
188+
(see :attr:`IPv6Address.ipv4_mapped`)::
189+
190+
address.is_private == address.ipv4_mapped.is_private
191+
192+
``is_private`` has value opposite to :attr:`is_global`, except for the shared address space
193+
(``100.64.0.0/10`` range) where they are both ``False``.
194+
195+
.. versionchanged:: 3.11.10
196+
197+
Fixed some false positives and false negatives.
198+
199+
* ``192.0.0.0/24`` is considered private with the exception of ``192.0.0.9/32`` and
200+
``192.0.0.10/32`` (previously: only the ``192.0.0.0/29`` sub-range was considered private).
201+
* ``64:ff9b:1::/48`` is considered private.
202+
* ``2002::/16`` is considered private.
203+
* There are exceptions within ``2001::/23`` (otherwise considered private): ``2001:1::1/128``,
204+
``2001:1::2/128``, ``2001:3::/32``, ``2001:4:112::/48``, ``2001:20::/28``, ``2001:30::/28``.
205+
The exceptions are not considered private.
184206

185207
.. attribute:: is_global
186208

187-
``True`` if the address is allocated for public networks. See
209+
``True`` if the address is defined as globally reachable by
188210
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
189-
(for IPv6).
211+
(for IPv6) with the following exception:
212+
213+
For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
214+
semantics of the underlying IPv4 addresses and the following condition holds
215+
(see :attr:`IPv6Address.ipv4_mapped`)::
216+
217+
address.is_global == address.ipv4_mapped.is_global
218+
219+
``is_global`` has value opposite to :attr:`is_private`, except for the shared address space
220+
(``100.64.0.0/10`` range) where they are both ``False``.
190221

191222
.. versionadded:: 3.4
192223

224+
.. versionchanged:: 3.11.10
225+
226+
Fixed some false positives and false negatives, see :attr:`is_private` for details.
227+
193228
.. attribute:: is_unspecified
194229

195230
``True`` if the address is unspecified. See :RFC:`5735` (for IPv4)

Doc/whatsnew/3.11.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2727,3 +2727,12 @@ OpenSSL
27272727
* Windows builds and macOS installers from python.org now use OpenSSL 3.0.
27282728

27292729
.. _libb2: https://www.blake2.net/
2730+
2731+
Notable changes in 3.11.10
2732+
==========================
2733+
2734+
ipaddress
2735+
---------
2736+
2737+
* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``,
2738+
``IPv6Address``, ``IPv4Network`` and ``IPv6Network``.

Lib/ipaddress.py

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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
@@ -1548,13 +1575,15 @@ class _IPv4Constants:
15481575

15491576
_public_network = IPv4Network('100.64.0.0/10')
15501577

1578+
# Not globally reachable address blocks listed on
1579+
# https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
15511580
_private_networks = [
15521581
IPv4Network('0.0.0.0/8'),
15531582
IPv4Network('10.0.0.0/8'),
15541583
IPv4Network('127.0.0.0/8'),
15551584
IPv4Network('169.254.0.0/16'),
15561585
IPv4Network('172.16.0.0/12'),
1557-
IPv4Network('192.0.0.0/29'),
1586+
IPv4Network('192.0.0.0/24'),
15581587
IPv4Network('192.0.0.170/31'),
15591588
IPv4Network('192.0.2.0/24'),
15601589
IPv4Network('192.168.0.0/16'),
@@ -1565,6 +1594,11 @@ class _IPv4Constants:
15651594
IPv4Network('255.255.255.255/32'),
15661595
]
15671596

1597+
_private_networks_exceptions = [
1598+
IPv4Network('192.0.0.9/32'),
1599+
IPv4Network('192.0.0.10/32'),
1600+
]
1601+
15681602
_reserved_network = IPv4Network('240.0.0.0/4')
15691603

15701604
_unspecified_address = IPv4Address('0.0.0.0')
@@ -2010,27 +2044,42 @@ def is_site_local(self):
20102044
@property
20112045
@functools.lru_cache()
20122046
def is_private(self):
2013-
"""Test if this address is allocated for private networks.
2047+
"""``True`` if the address is defined as not globally reachable by
2048+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
2049+
(for IPv6) with the following exceptions:
20142050
2015-
Returns:
2016-
A boolean, True if the address is reserved per
2017-
iana-ipv6-special-registry, or is ipv4_mapped and is
2018-
reserved in the iana-ipv4-special-registry.
2051+
* ``is_private`` is ``False`` for ``100.64.0.0/10``
2052+
* For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
2053+
semantics of the underlying IPv4 addresses and the following condition holds
2054+
(see :attr:`IPv6Address.ipv4_mapped`)::
20192055
2056+
address.is_private == address.ipv4_mapped.is_private
2057+
2058+
``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
2059+
IPv4 range where they are both ``False``.
20202060
"""
20212061
ipv4_mapped = self.ipv4_mapped
20222062
if ipv4_mapped is not None:
20232063
return ipv4_mapped.is_private
2024-
return any(self in net for net in self._constants._private_networks)
2064+
return (
2065+
any(self in net for net in self._constants._private_networks)
2066+
and all(self not in net for net in self._constants._private_networks_exceptions)
2067+
)
20252068

20262069
@property
20272070
def is_global(self):
2028-
"""Test if this address is allocated for public networks.
2071+
"""``True`` if the address is defined as globally reachable by
2072+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
2073+
(for IPv6) with the following exception:
20292074
2030-
Returns:
2031-
A boolean, true if the address is not reserved per
2032-
iana-ipv6-special-registry.
2075+
For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
2076+
semantics of the underlying IPv4 addresses and the following condition holds
2077+
(see :attr:`IPv6Address.ipv4_mapped`)::
2078+
2079+
address.is_global == address.ipv4_mapped.is_global
20332080
2081+
``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
2082+
IPv4 range where they are both ``False``.
20342083
"""
20352084
return not self.is_private
20362085

@@ -2271,19 +2320,31 @@ class _IPv6Constants:
22712320

22722321
_multicast_network = IPv6Network('ff00::/8')
22732322

2323+
# Not globally reachable address blocks listed on
2324+
# https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
22742325
_private_networks = [
22752326
IPv6Network('::1/128'),
22762327
IPv6Network('::/128'),
22772328
IPv6Network('::ffff:0:0/96'),
2329+
IPv6Network('64:ff9b:1::/48'),
22782330
IPv6Network('100::/64'),
22792331
IPv6Network('2001::/23'),
2280-
IPv6Network('2001:2::/48'),
22812332
IPv6Network('2001:db8::/32'),
2282-
IPv6Network('2001:10::/28'),
2333+
# IANA says N/A, let's consider it not globally reachable to be safe
2334+
IPv6Network('2002::/16'),
22832335
IPv6Network('fc00::/7'),
22842336
IPv6Network('fe80::/10'),
22852337
]
22862338

2339+
_private_networks_exceptions = [
2340+
IPv6Network('2001:1::1/128'),
2341+
IPv6Network('2001:1::2/128'),
2342+
IPv6Network('2001:3::/32'),
2343+
IPv6Network('2001:4:112::/48'),
2344+
IPv6Network('2001:20::/28'),
2345+
IPv6Network('2001:30::/28'),
2346+
]
2347+
22872348
_reserved_networks = [
22882349
IPv6Network('::/8'), IPv6Network('100::/8'),
22892350
IPv6Network('200::/7'), IPv6Network('400::/6'),

Lib/test/test_ipaddress.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2269,6 +2269,10 @@ def testReservedIpv4(self):
22692269
self.assertEqual(True, ipaddress.ip_address(
22702270
'172.31.255.255').is_private)
22712271
self.assertEqual(False, ipaddress.ip_address('172.32.0.0').is_private)
2272+
self.assertFalse(ipaddress.ip_address('192.0.0.0').is_global)
2273+
self.assertTrue(ipaddress.ip_address('192.0.0.9').is_global)
2274+
self.assertTrue(ipaddress.ip_address('192.0.0.10').is_global)
2275+
self.assertFalse(ipaddress.ip_address('192.0.0.255').is_global)
22722276

22732277
self.assertEqual(True,
22742278
ipaddress.ip_address('169.254.100.200').is_link_local)
@@ -2294,6 +2298,7 @@ def testPrivateNetworks(self):
22942298
self.assertEqual(True, ipaddress.ip_network("169.254.0.0/16").is_private)
22952299
self.assertEqual(True, ipaddress.ip_network("172.16.0.0/12").is_private)
22962300
self.assertEqual(True, ipaddress.ip_network("192.0.0.0/29").is_private)
2301+
self.assertEqual(False, ipaddress.ip_network("192.0.0.9/32").is_private)
22972302
self.assertEqual(True, ipaddress.ip_network("192.0.0.170/31").is_private)
22982303
self.assertEqual(True, ipaddress.ip_network("192.0.2.0/24").is_private)
22992304
self.assertEqual(True, ipaddress.ip_network("192.168.0.0/16").is_private)
@@ -2310,8 +2315,8 @@ def testPrivateNetworks(self):
23102315
self.assertEqual(True, ipaddress.ip_network("::/128").is_private)
23112316
self.assertEqual(True, ipaddress.ip_network("::ffff:0:0/96").is_private)
23122317
self.assertEqual(True, ipaddress.ip_network("100::/64").is_private)
2313-
self.assertEqual(True, ipaddress.ip_network("2001::/23").is_private)
23142318
self.assertEqual(True, ipaddress.ip_network("2001:2::/48").is_private)
2319+
self.assertEqual(False, ipaddress.ip_network("2001:3::/48").is_private)
23152320
self.assertEqual(True, ipaddress.ip_network("2001:db8::/32").is_private)
23162321
self.assertEqual(True, ipaddress.ip_network("2001:10::/28").is_private)
23172322
self.assertEqual(True, ipaddress.ip_network("fc00::/7").is_private)
@@ -2390,6 +2395,20 @@ def testReservedIpv6(self):
23902395
self.assertEqual(True, ipaddress.ip_address('0::0').is_unspecified)
23912396
self.assertEqual(False, ipaddress.ip_address('::1').is_unspecified)
23922397

2398+
self.assertFalse(ipaddress.ip_address('64:ff9b:1::').is_global)
2399+
self.assertFalse(ipaddress.ip_address('2001::').is_global)
2400+
self.assertTrue(ipaddress.ip_address('2001:1::1').is_global)
2401+
self.assertTrue(ipaddress.ip_address('2001:1::2').is_global)
2402+
self.assertFalse(ipaddress.ip_address('2001:2::').is_global)
2403+
self.assertTrue(ipaddress.ip_address('2001:3::').is_global)
2404+
self.assertFalse(ipaddress.ip_address('2001:4::').is_global)
2405+
self.assertTrue(ipaddress.ip_address('2001:4:112::').is_global)
2406+
self.assertFalse(ipaddress.ip_address('2001:10::').is_global)
2407+
self.assertTrue(ipaddress.ip_address('2001:20::').is_global)
2408+
self.assertTrue(ipaddress.ip_address('2001:30::').is_global)
2409+
self.assertFalse(ipaddress.ip_address('2001:40::').is_global)
2410+
self.assertFalse(ipaddress.ip_address('2002::').is_global)
2411+
23932412
# some generic IETF reserved addresses
23942413
self.assertEqual(True, ipaddress.ip_address('100::').is_reserved)
23952414
self.assertEqual(True, ipaddress.ip_network('4000::1/128').is_reserved)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Fixed various false positives and false negatives in
2+
3+
* :attr:`ipaddress.IPv4Address.is_private` (see these docs for details)
4+
* :attr:`ipaddress.IPv4Address.is_global`
5+
* :attr:`ipaddress.IPv6Address.is_private`
6+
* :attr:`ipaddress.IPv6Address.is_global`
7+
8+
Also in the corresponding :class:`ipaddress.IPv4Network` and :class:`ipaddress.IPv6Network`
9+
attributes.

0 commit comments

Comments
 (0)