Skip to content

Commit 40d75c2

Browse files
authored
GH-113171: Fix "private" (non-global) IP address ranges (GH-113179)
* GH-113171: Fix "private" (really non-global) IP address ranges 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] #61602
1 parent 3be9b9d commit 40d75c2

File tree

5 files changed

+82
-7
lines changed

5 files changed

+82
-7
lines changed

Doc/library/ipaddress.rst

+16
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.13
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.13
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.13.rst

+2
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,8 @@ ipaddress
401401

402402
* Add the :attr:`ipaddress.IPv4Address.ipv6_mapped` property, which returns the IPv4-mapped IPv6 address.
403403
(Contributed by Charles Machalow in :gh:`109466`.)
404+
* Fix ``is_global`` and ``is_private`` behavior in ``IPv4Address``, ``IPv6Address``, ``IPv4Network``
405+
and ``IPv6Network``.
404406

405407
itertools
406408
---------

Lib/ipaddress.py

+35-6
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()
@@ -1578,13 +1585,15 @@ class _IPv4Constants:
15781585

15791586
_public_network = IPv4Network('100.64.0.0/10')
15801587

1588+
# Not globally reachable address blocks listed on
1589+
# https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
15811590
_private_networks = [
15821591
IPv4Network('0.0.0.0/8'),
15831592
IPv4Network('10.0.0.0/8'),
15841593
IPv4Network('127.0.0.0/8'),
15851594
IPv4Network('169.254.0.0/16'),
15861595
IPv4Network('172.16.0.0/12'),
1587-
IPv4Network('192.0.0.0/29'),
1596+
IPv4Network('192.0.0.0/24'),
15881597
IPv4Network('192.0.0.170/31'),
15891598
IPv4Network('192.0.2.0/24'),
15901599
IPv4Network('192.168.0.0/16'),
@@ -1595,6 +1604,11 @@ class _IPv4Constants:
15951604
IPv4Network('255.255.255.255/32'),
15961605
]
15971606

1607+
_private_networks_exceptions = [
1608+
IPv4Network('192.0.0.9/32'),
1609+
IPv4Network('192.0.0.10/32'),
1610+
]
1611+
15981612
_reserved_network = IPv4Network('240.0.0.0/4')
15991613

16001614
_unspecified_address = IPv4Address('0.0.0.0')
@@ -2086,7 +2100,10 @@ def is_private(self):
20862100
ipv4_mapped = self.ipv4_mapped
20872101
if ipv4_mapped is not None:
20882102
return ipv4_mapped.is_private
2089-
return any(self in net for net in self._constants._private_networks)
2103+
return (
2104+
any(self in net for net in self._constants._private_networks)
2105+
and all(self not in net for net in self._constants._private_networks_exceptions)
2106+
)
20902107

20912108
@property
20922109
def is_global(self):
@@ -2342,19 +2359,31 @@ class _IPv6Constants:
23422359

23432360
_multicast_network = IPv6Network('ff00::/8')
23442361

2362+
# Not globally reachable address blocks listed on
2363+
# https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
23452364
_private_networks = [
23462365
IPv6Network('::1/128'),
23472366
IPv6Network('::/128'),
23482367
IPv6Network('::ffff:0:0/96'),
2368+
IPv6Network('64:ff9b:1::/48'),
23492369
IPv6Network('100::/64'),
23502370
IPv6Network('2001::/23'),
2351-
IPv6Network('2001:2::/48'),
23522371
IPv6Network('2001:db8::/32'),
2353-
IPv6Network('2001:10::/28'),
2372+
# IANA says N/A, let's consider it not globally reachable to be safe
2373+
IPv6Network('2002::/16'),
23542374
IPv6Network('fc00::/7'),
23552375
IPv6Network('fe80::/10'),
23562376
]
23572377

2378+
_private_networks_exceptions = [
2379+
IPv6Network('2001:1::1/128'),
2380+
IPv6Network('2001:1::2/128'),
2381+
IPv6Network('2001:3::/32'),
2382+
IPv6Network('2001:4:112::/48'),
2383+
IPv6Network('2001:20::/28'),
2384+
IPv6Network('2001:30::/28'),
2385+
]
2386+
23582387
_reserved_networks = [
23592388
IPv6Network('::/8'), IPv6Network('100::/8'),
23602389
IPv6Network('200::/7'), IPv6Network('400::/6'),

Lib/test/test_ipaddress.py

+20-1
Original file line numberDiff line numberDiff line change
@@ -2288,6 +2288,10 @@ def testReservedIpv4(self):
22882288
self.assertEqual(True, ipaddress.ip_address(
22892289
'172.31.255.255').is_private)
22902290
self.assertEqual(False, ipaddress.ip_address('172.32.0.0').is_private)
2291+
self.assertFalse(ipaddress.ip_address('192.0.0.0').is_global)
2292+
self.assertTrue(ipaddress.ip_address('192.0.0.9').is_global)
2293+
self.assertTrue(ipaddress.ip_address('192.0.0.10').is_global)
2294+
self.assertFalse(ipaddress.ip_address('192.0.0.255').is_global)
22912295

22922296
self.assertEqual(True,
22932297
ipaddress.ip_address('169.254.100.200').is_link_local)
@@ -2313,6 +2317,7 @@ def testPrivateNetworks(self):
23132317
self.assertEqual(True, ipaddress.ip_network("169.254.0.0/16").is_private)
23142318
self.assertEqual(True, ipaddress.ip_network("172.16.0.0/12").is_private)
23152319
self.assertEqual(True, ipaddress.ip_network("192.0.0.0/29").is_private)
2320+
self.assertEqual(False, ipaddress.ip_network("192.0.0.9/32").is_private)
23162321
self.assertEqual(True, ipaddress.ip_network("192.0.0.170/31").is_private)
23172322
self.assertEqual(True, ipaddress.ip_network("192.0.2.0/24").is_private)
23182323
self.assertEqual(True, ipaddress.ip_network("192.168.0.0/16").is_private)
@@ -2329,8 +2334,8 @@ def testPrivateNetworks(self):
23292334
self.assertEqual(True, ipaddress.ip_network("::/128").is_private)
23302335
self.assertEqual(True, ipaddress.ip_network("::ffff:0:0/96").is_private)
23312336
self.assertEqual(True, ipaddress.ip_network("100::/64").is_private)
2332-
self.assertEqual(True, ipaddress.ip_network("2001::/23").is_private)
23332337
self.assertEqual(True, ipaddress.ip_network("2001:2::/48").is_private)
2338+
self.assertEqual(False, ipaddress.ip_network("2001:3::/48").is_private)
23342339
self.assertEqual(True, ipaddress.ip_network("2001:db8::/32").is_private)
23352340
self.assertEqual(True, ipaddress.ip_network("2001:10::/28").is_private)
23362341
self.assertEqual(True, ipaddress.ip_network("fc00::/7").is_private)
@@ -2409,6 +2414,20 @@ def testReservedIpv6(self):
24092414
self.assertEqual(True, ipaddress.ip_address('0::0').is_unspecified)
24102415
self.assertEqual(False, ipaddress.ip_address('::1').is_unspecified)
24112416

2417+
self.assertFalse(ipaddress.ip_address('64:ff9b:1::').is_global)
2418+
self.assertFalse(ipaddress.ip_address('2001::').is_global)
2419+
self.assertTrue(ipaddress.ip_address('2001:1::1').is_global)
2420+
self.assertTrue(ipaddress.ip_address('2001:1::2').is_global)
2421+
self.assertFalse(ipaddress.ip_address('2001:2::').is_global)
2422+
self.assertTrue(ipaddress.ip_address('2001:3::').is_global)
2423+
self.assertFalse(ipaddress.ip_address('2001:4::').is_global)
2424+
self.assertTrue(ipaddress.ip_address('2001:4:112::').is_global)
2425+
self.assertFalse(ipaddress.ip_address('2001:10::').is_global)
2426+
self.assertTrue(ipaddress.ip_address('2001:20::').is_global)
2427+
self.assertTrue(ipaddress.ip_address('2001:30::').is_global)
2428+
self.assertFalse(ipaddress.ip_address('2001:40::').is_global)
2429+
self.assertFalse(ipaddress.ip_address('2002::').is_global)
2430+
24122431
# some generic IETF reserved addresses
24132432
self.assertEqual(True, ipaddress.ip_address('100::').is_reserved)
24142433
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)