Skip to content

Commit 895f7e2

Browse files
encukoujstasiak
andauthored
[3.8] gh-113171: gh-65056: Fix "private" (non-global) IP address ranges (GH-113179) (GH-113186) (GH-118177) (GH-118479)
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] #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"). --------- Co-authored-by: Jakub Stasiak <jakub@stasiak.at>
1 parent f791cda commit 895f7e2

File tree

6 files changed

+195
-21
lines changed

6 files changed

+195
-21
lines changed

Doc/library/ipaddress.rst

+39-4
Original file line numberDiff line numberDiff line change
@@ -179,18 +179,53 @@ write code that handles both IP versions correctly. Address objects are
179179

180180
.. attribute:: is_private
181181

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

186208
.. attribute:: is_global
187209

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

192223
.. versionadded:: 3.4
193224

225+
.. versionchanged:: 3.8.20
226+
227+
Fixed some false positives and false negatives, see :attr:`is_private` for details.
228+
194229
.. attribute:: is_unspecified
195230

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

Doc/tools/susp-ignored.csv

+8
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,14 @@ library/ipaddress,,:db00,2001:db00::0/24
158158
library/ipaddress,,::,2001:db00::0/24
159159
library/ipaddress,,:db00,2001:db00::0/ffff:ff00::
160160
library/ipaddress,,::,2001:db00::0/ffff:ff00::
161+
library/ipaddress,,:ff9b,64:ff9b:1::/48
162+
library/ipaddress,,::,64:ff9b:1::/48
163+
library/ipaddress,,::,2001::
164+
library/ipaddress,,::,2001:1::
165+
library/ipaddress,,::,2001:3::
166+
library/ipaddress,,::,2001:4:112::
167+
library/ipaddress,,::,2001:20::
168+
library/ipaddress,,::,2001:30::
161169
library/itertools,,:step,elements from seq[start:stop:step]
162170
library/itertools,,:stop,elements from seq[start:stop:step]
163171
library/logging.handlers,,:port,host:port

Doc/whatsnew/3.8.rst

+9
Original file line numberDiff line numberDiff line change
@@ -2355,3 +2355,12 @@ tarfile
23552355
:exc:`DeprecationWarning`.
23562356
In Python 3.14, the default will switch to ``'data'``.
23572357
(Contributed by Petr Viktorin in :pep:`706`.)
2358+
2359+
Notable changes in 3.8.20
2360+
=========================
2361+
2362+
ipaddress
2363+
---------
2364+
2365+
* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``,
2366+
``IPv6Address``, ``IPv4Network`` and ``IPv6Network``.

Lib/ipaddress.py

+78-17
Original file line numberDiff line numberDiff line change
@@ -1275,18 +1275,41 @@ def is_reserved(self):
12751275
@property
12761276
@functools.lru_cache()
12771277
def is_private(self):
1278-
"""Test if this address is allocated for private networks.
1278+
"""``True`` if the address is defined as not globally reachable by
1279+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
1280+
(for IPv6) with the following exceptions:
12791281
1280-
Returns:
1281-
A boolean, True if the address is reserved per
1282-
iana-ipv4-special-registry.
1282+
* ``is_private`` is ``False`` for ``100.64.0.0/10``
1283+
* For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
1284+
semantics of the underlying IPv4 addresses and the following condition holds
1285+
(see :attr:`IPv6Address.ipv4_mapped`)::
1286+
1287+
address.is_private == address.ipv4_mapped.is_private
12831288
1289+
``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
1290+
IPv4 range where they are both ``False``.
12841291
"""
1285-
return any(self in net for net in self._constants._private_networks)
1292+
return (
1293+
any(self in net for net in self._constants._private_networks)
1294+
and all(self not in net for net in self._constants._private_networks_exceptions)
1295+
)
12861296

12871297
@property
12881298
@functools.lru_cache()
12891299
def is_global(self):
1300+
"""``True`` if the address is defined as globally reachable by
1301+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
1302+
(for IPv6) with the following exception:
1303+
1304+
For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
1305+
semantics of the underlying IPv4 addresses and the following condition holds
1306+
(see :attr:`IPv6Address.ipv4_mapped`)::
1307+
1308+
address.is_global == address.ipv4_mapped.is_global
1309+
1310+
``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
1311+
IPv4 range where they are both ``False``.
1312+
"""
12901313
return self not in self._constants._public_network and not self.is_private
12911314

12921315
@property
@@ -1490,13 +1513,15 @@ class _IPv4Constants:
14901513

14911514
_public_network = IPv4Network('100.64.0.0/10')
14921515

1516+
# Not globally reachable address blocks listed on
1517+
# https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
14931518
_private_networks = [
14941519
IPv4Network('0.0.0.0/8'),
14951520
IPv4Network('10.0.0.0/8'),
14961521
IPv4Network('127.0.0.0/8'),
14971522
IPv4Network('169.254.0.0/16'),
14981523
IPv4Network('172.16.0.0/12'),
1499-
IPv4Network('192.0.0.0/29'),
1524+
IPv4Network('192.0.0.0/24'),
15001525
IPv4Network('192.0.0.170/31'),
15011526
IPv4Network('192.0.2.0/24'),
15021527
IPv4Network('192.168.0.0/16'),
@@ -1507,6 +1532,11 @@ class _IPv4Constants:
15071532
IPv4Network('255.255.255.255/32'),
15081533
]
15091534

1535+
_private_networks_exceptions = [
1536+
IPv4Network('192.0.0.9/32'),
1537+
IPv4Network('192.0.0.10/32'),
1538+
]
1539+
15101540
_reserved_network = IPv4Network('240.0.0.0/4')
15111541

15121542
_unspecified_address = IPv4Address('0.0.0.0')
@@ -1897,23 +1927,42 @@ def is_site_local(self):
18971927
@property
18981928
@functools.lru_cache()
18991929
def is_private(self):
1900-
"""Test if this address is allocated for private networks.
1930+
"""``True`` if the address is defined as not globally reachable by
1931+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
1932+
(for IPv6) with the following exceptions:
19011933
1902-
Returns:
1903-
A boolean, True if the address is reserved per
1904-
iana-ipv6-special-registry.
1934+
* ``is_private`` is ``False`` for ``100.64.0.0/10``
1935+
* For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
1936+
semantics of the underlying IPv4 addresses and the following condition holds
1937+
(see :attr:`IPv6Address.ipv4_mapped`)::
1938+
1939+
address.is_private == address.ipv4_mapped.is_private
19051940
1941+
``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
1942+
IPv4 range where they are both ``False``.
19061943
"""
1907-
return any(self in net for net in self._constants._private_networks)
1944+
ipv4_mapped = self.ipv4_mapped
1945+
if ipv4_mapped is not None:
1946+
return ipv4_mapped.is_private
1947+
return (
1948+
any(self in net for net in self._constants._private_networks)
1949+
and all(self not in net for net in self._constants._private_networks_exceptions)
1950+
)
19081951

19091952
@property
19101953
def is_global(self):
1911-
"""Test if this address is allocated for public networks.
1954+
"""``True`` if the address is defined as globally reachable by
1955+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
1956+
(for IPv6) with the following exception:
19121957
1913-
Returns:
1914-
A boolean, true if the address is not reserved per
1915-
iana-ipv6-special-registry.
1958+
For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
1959+
semantics of the underlying IPv4 addresses and the following condition holds
1960+
(see :attr:`IPv6Address.ipv4_mapped`)::
1961+
1962+
address.is_global == address.ipv4_mapped.is_global
19161963
1964+
``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
1965+
IPv4 range where they are both ``False``.
19171966
"""
19181967
return not self.is_private
19191968

@@ -2154,19 +2203,31 @@ class _IPv6Constants:
21542203

21552204
_multicast_network = IPv6Network('ff00::/8')
21562205

2206+
# Not globally reachable address blocks listed on
2207+
# https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
21572208
_private_networks = [
21582209
IPv6Network('::1/128'),
21592210
IPv6Network('::/128'),
21602211
IPv6Network('::ffff:0:0/96'),
2212+
IPv6Network('64:ff9b:1::/48'),
21612213
IPv6Network('100::/64'),
21622214
IPv6Network('2001::/23'),
2163-
IPv6Network('2001:2::/48'),
21642215
IPv6Network('2001:db8::/32'),
2165-
IPv6Network('2001:10::/28'),
2216+
# IANA says N/A, let's consider it not globally reachable to be safe
2217+
IPv6Network('2002::/16'),
21662218
IPv6Network('fc00::/7'),
21672219
IPv6Network('fe80::/10'),
21682220
]
21692221

2222+
_private_networks_exceptions = [
2223+
IPv6Network('2001:1::1/128'),
2224+
IPv6Network('2001:1::2/128'),
2225+
IPv6Network('2001:3::/32'),
2226+
IPv6Network('2001:4:112::/48'),
2227+
IPv6Network('2001:20::/28'),
2228+
IPv6Network('2001:30::/28'),
2229+
]
2230+
21702231
_reserved_networks = [
21712232
IPv6Network('::/8'), IPv6Network('100::/8'),
21722233
IPv6Network('200::/7'), IPv6Network('400::/6'),

Lib/test/test_ipaddress.py

+52
Original file line numberDiff line numberDiff line change
@@ -1761,6 +1761,10 @@ def testReservedIpv4(self):
17611761
self.assertEqual(True, ipaddress.ip_address(
17621762
'172.31.255.255').is_private)
17631763
self.assertEqual(False, ipaddress.ip_address('172.32.0.0').is_private)
1764+
self.assertFalse(ipaddress.ip_address('192.0.0.0').is_global)
1765+
self.assertTrue(ipaddress.ip_address('192.0.0.9').is_global)
1766+
self.assertTrue(ipaddress.ip_address('192.0.0.10').is_global)
1767+
self.assertFalse(ipaddress.ip_address('192.0.0.255').is_global)
17641768

17651769
self.assertEqual(True,
17661770
ipaddress.ip_address('169.254.100.200').is_link_local)
@@ -1776,6 +1780,40 @@ def testReservedIpv4(self):
17761780
self.assertEqual(False, ipaddress.ip_address('128.0.0.0').is_loopback)
17771781
self.assertEqual(True, ipaddress.ip_network('0.0.0.0').is_unspecified)
17781782

1783+
def testPrivateNetworks(self):
1784+
self.assertEqual(True, ipaddress.ip_network("0.0.0.0/0").is_private)
1785+
self.assertEqual(False, ipaddress.ip_network("1.0.0.0/8").is_private)
1786+
1787+
self.assertEqual(True, ipaddress.ip_network("0.0.0.0/8").is_private)
1788+
self.assertEqual(True, ipaddress.ip_network("10.0.0.0/8").is_private)
1789+
self.assertEqual(True, ipaddress.ip_network("127.0.0.0/8").is_private)
1790+
self.assertEqual(True, ipaddress.ip_network("169.254.0.0/16").is_private)
1791+
self.assertEqual(True, ipaddress.ip_network("172.16.0.0/12").is_private)
1792+
self.assertEqual(True, ipaddress.ip_network("192.0.0.0/29").is_private)
1793+
self.assertEqual(False, ipaddress.ip_network("192.0.0.9/32").is_private)
1794+
self.assertEqual(True, ipaddress.ip_network("192.0.0.170/31").is_private)
1795+
self.assertEqual(True, ipaddress.ip_network("192.0.2.0/24").is_private)
1796+
self.assertEqual(True, ipaddress.ip_network("192.168.0.0/16").is_private)
1797+
self.assertEqual(True, ipaddress.ip_network("198.18.0.0/15").is_private)
1798+
self.assertEqual(True, ipaddress.ip_network("198.51.100.0/24").is_private)
1799+
self.assertEqual(True, ipaddress.ip_network("203.0.113.0/24").is_private)
1800+
self.assertEqual(True, ipaddress.ip_network("240.0.0.0/4").is_private)
1801+
self.assertEqual(True, ipaddress.ip_network("255.255.255.255/32").is_private)
1802+
1803+
self.assertEqual(False, ipaddress.ip_network("::/0").is_private)
1804+
self.assertEqual(False, ipaddress.ip_network("::ff/128").is_private)
1805+
1806+
self.assertEqual(True, ipaddress.ip_network("::1/128").is_private)
1807+
self.assertEqual(True, ipaddress.ip_network("::/128").is_private)
1808+
self.assertEqual(True, ipaddress.ip_network("::ffff:0:0/96").is_private)
1809+
self.assertEqual(True, ipaddress.ip_network("100::/64").is_private)
1810+
self.assertEqual(True, ipaddress.ip_network("2001:2::/48").is_private)
1811+
self.assertEqual(False, ipaddress.ip_network("2001:3::/48").is_private)
1812+
self.assertEqual(True, ipaddress.ip_network("2001:db8::/32").is_private)
1813+
self.assertEqual(True, ipaddress.ip_network("2001:10::/28").is_private)
1814+
self.assertEqual(True, ipaddress.ip_network("fc00::/7").is_private)
1815+
self.assertEqual(True, ipaddress.ip_network("fe80::/10").is_private)
1816+
17791817
def testReservedIpv6(self):
17801818

17811819
self.assertEqual(True, ipaddress.ip_network('ffff::').is_multicast)
@@ -1849,6 +1887,20 @@ def testReservedIpv6(self):
18491887
self.assertEqual(True, ipaddress.ip_address('0::0').is_unspecified)
18501888
self.assertEqual(False, ipaddress.ip_address('::1').is_unspecified)
18511889

1890+
self.assertFalse(ipaddress.ip_address('64:ff9b:1::').is_global)
1891+
self.assertFalse(ipaddress.ip_address('2001::').is_global)
1892+
self.assertTrue(ipaddress.ip_address('2001:1::1').is_global)
1893+
self.assertTrue(ipaddress.ip_address('2001:1::2').is_global)
1894+
self.assertFalse(ipaddress.ip_address('2001:2::').is_global)
1895+
self.assertTrue(ipaddress.ip_address('2001:3::').is_global)
1896+
self.assertFalse(ipaddress.ip_address('2001:4::').is_global)
1897+
self.assertTrue(ipaddress.ip_address('2001:4:112::').is_global)
1898+
self.assertFalse(ipaddress.ip_address('2001:10::').is_global)
1899+
self.assertTrue(ipaddress.ip_address('2001:20::').is_global)
1900+
self.assertTrue(ipaddress.ip_address('2001:30::').is_global)
1901+
self.assertFalse(ipaddress.ip_address('2001:40::').is_global)
1902+
self.assertFalse(ipaddress.ip_address('2002::').is_global)
1903+
18521904
# some generic IETF reserved addresses
18531905
self.assertEqual(True, ipaddress.ip_address('100::').is_reserved)
18541906
self.assertEqual(True, ipaddress.ip_network('4000::1/128').is_reserved)
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)