Skip to content

Commit a74a45b

Browse files
jstasiakencukou
authored andcommitted
[3.12] pythonGH-113171: Fix "private" (non-global) IP address ranges (pythonGH-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. I left 100.64.0.0/10 alone, for now, as it's been made special in [1] and I'm not sure if we want to undo that as I don't quite understand the motivation behind it. The _address_exclude_many() call returns 8 networks for IPv4, 121 networks for IPv6. [1] python#61602 (cherry picked from commit 40d75c2)
1 parent 12cf493 commit a74a45b

File tree

5 files changed

+89
-7
lines changed

5 files changed

+89
-7
lines changed

Doc/library/ipaddress.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,18 @@ write code that handles both IP versions correctly. Address objects are
192192
``is_private`` has value opposite to :attr:`is_global`, except for the shared address space
193193
(``100.64.0.0/10`` range) where they are both ``False``.
194194

195+
.. versionchanged:: 3.12.4
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.
206+
195207
.. attribute:: is_global
196208

197209
``True`` if the address is defined as globally reachable by
@@ -209,6 +221,10 @@ write code that handles both IP versions correctly. Address objects are
209221

210222
.. versionadded:: 3.4
211223

224+
.. versionchanged:: 3.12.4
225+
226+
Fixed some false positives and false negatives, see :attr:`is_private` for details.
227+
212228
.. attribute:: is_unspecified
213229

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

Doc/whatsnew/3.12.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2457,3 +2457,12 @@ Removed
24572457

24582458
* Remove the ``PyUnicode_InternImmortal()`` function macro.
24592459
(Contributed by Victor Stinner in :gh:`85858`.)
2460+
2461+
Notable changes in 3.12.4
2462+
=========================
2463+
2464+
ipaddress
2465+
---------
2466+
2467+
* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``,
2468+
``IPv6Address``, ``IPv4Network`` and ``IPv6Network``.

Lib/ipaddress.py

Lines changed: 35 additions & 6 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):
@@ -1347,7 +1351,10 @@ def is_private(self):
13471351
``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
13481352
IPv4 range where they are both ``False``.
13491353
"""
1350-
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+
)
13511358

13521359
@property
13531360
@functools.lru_cache()
@@ -1568,13 +1575,15 @@ class _IPv4Constants:
15681575

15691576
_public_network = IPv4Network('100.64.0.0/10')
15701577

1578+
# Not globally reachable address blocks listed on
1579+
# https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
15711580
_private_networks = [
15721581
IPv4Network('0.0.0.0/8'),
15731582
IPv4Network('10.0.0.0/8'),
15741583
IPv4Network('127.0.0.0/8'),
15751584
IPv4Network('169.254.0.0/16'),
15761585
IPv4Network('172.16.0.0/12'),
1577-
IPv4Network('192.0.0.0/29'),
1586+
IPv4Network('192.0.0.0/24'),
15781587
IPv4Network('192.0.0.170/31'),
15791588
IPv4Network('192.0.2.0/24'),
15801589
IPv4Network('192.168.0.0/16'),
@@ -1585,6 +1594,11 @@ class _IPv4Constants:
15851594
IPv4Network('255.255.255.255/32'),
15861595
]
15871596

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

15901604
_unspecified_address = IPv4Address('0.0.0.0')
@@ -2044,7 +2058,10 @@ def is_private(self):
20442058
ipv4_mapped = self.ipv4_mapped
20452059
if ipv4_mapped is not None:
20462060
return ipv4_mapped.is_private
2047-
return any(self in net for net in self._constants._private_networks)
2061+
return (
2062+
any(self in net for net in self._constants._private_networks)
2063+
and all(self not in net for net in self._constants._private_networks_exceptions)
2064+
)
20482065

20492066
@property
20502067
def is_global(self):
@@ -2300,19 +2317,31 @@ class _IPv6Constants:
23002317

23012318
_multicast_network = IPv6Network('ff00::/8')
23022319

2320+
# Not globally reachable address blocks listed on
2321+
# https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
23032322
_private_networks = [
23042323
IPv6Network('::1/128'),
23052324
IPv6Network('::/128'),
23062325
IPv6Network('::ffff:0:0/96'),
2326+
IPv6Network('64:ff9b:1::/48'),
23072327
IPv6Network('100::/64'),
23082328
IPv6Network('2001::/23'),
2309-
IPv6Network('2001:2::/48'),
23102329
IPv6Network('2001:db8::/32'),
2311-
IPv6Network('2001:10::/28'),
2330+
# IANA says N/A, let's consider it not globally reachable to be safe
2331+
IPv6Network('2002::/16'),
23122332
IPv6Network('fc00::/7'),
23132333
IPv6Network('fe80::/10'),
23142334
]
23152335

2336+
_private_networks_exceptions = [
2337+
IPv6Network('2001:1::1/128'),
2338+
IPv6Network('2001:1::2/128'),
2339+
IPv6Network('2001:3::/32'),
2340+
IPv6Network('2001:4:112::/48'),
2341+
IPv6Network('2001:20::/28'),
2342+
IPv6Network('2001:30::/28'),
2343+
]
2344+
23162345
_reserved_networks = [
23172346
IPv6Network('::/8'), IPv6Network('100::/8'),
23182347
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)