Skip to content

Commit a6e2fa6

Browse files
committed
Support CIDR no_proxy entries
1 parent e472f6d commit a6e2fa6

3 files changed

Lines changed: 54 additions & 4 deletions

File tree

Doc/library/urllib.request.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,9 @@ The following classes are provided:
360360

361361
The :envvar:`no_proxy` environment variable can be used to specify hosts
362362
which shouldn't be reached via proxy; if set, it should be a comma-separated
363-
list of hostname suffixes, optionally with ``:port`` appended, for example
364-
``cern.ch,ncsa.uiuc.edu,some.host:8080``.
363+
list of hostname suffixes, optionally with ``:port`` appended, and IP
364+
address CIDR ranges, for example
365+
``cern.ch,ncsa.uiuc.edu,some.host:8080,192.168.0.0/16``.
365366

366367
.. note::
367368

Lib/test/test_urllib.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,28 @@ def test_proxy_bypass_environment_host_match(self):
245245
self.assertFalse(bypass('newdomain.com')) # no port
246246
self.assertFalse(bypass('newdomain.com:1235')) # wrong port
247247

248+
def test_proxy_bypass_environment_cidr_match(self):
249+
bypass = urllib.request.proxy_bypass_environment
250+
self.env.set('NO_PROXY',
251+
'192.168.0.0/16, 2001:db8::/32, 172.16.1.1/24')
252+
self.assertTrue(bypass('192.168.1.1'))
253+
self.assertTrue(bypass('192.168.1.1:1234'))
254+
self.assertTrue(bypass('2001:db8::1'))
255+
self.assertTrue(bypass('[2001:db8::1]:1234'))
256+
self.assertTrue(bypass('172.16.1.255'))
257+
self.assertFalse(bypass('192.169.1.1'))
258+
self.assertFalse(bypass('2001:db9::1'))
259+
self.assertFalse(bypass('172.16.2.1'))
260+
self.assertFalse(bypass('python.org'))
261+
262+
def test_proxy_bypass_environment_invalid_cidr(self):
263+
bypass = urllib.request.proxy_bypass_environment
264+
self.env.set('NO_PROXY',
265+
'192.168.0.0/33, 2001:db8::/129, anotherdomain.com')
266+
self.assertFalse(bypass('192.168.1.1'))
267+
self.assertFalse(bypass('2001:db8::1'))
268+
self.assertTrue(bypass('anotherdomain.com'))
269+
248270
def test_proxy_bypass_environment_always_match(self):
249271
bypass = urllib.request.proxy_bypass_environment
250272
self.env.set('NO_PROXY', '*')

Lib/urllib/request.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,7 +1912,8 @@ def proxy_bypass_environment(host, proxies=None):
19121912
"""Test if proxies should not be used for a particular host.
19131913
19141914
Checks the proxy dict for the value of no_proxy, which should
1915-
be a list of comma separated DNS suffixes, or '*' for all hosts.
1915+
be a list of comma separated DNS suffixes, IP address CIDR ranges,
1916+
or '*' for all hosts.
19161917
19171918
"""
19181919
if proxies is None:
@@ -1928,14 +1929,40 @@ def proxy_bypass_environment(host, proxies=None):
19281929
host = host.lower()
19291930
# strip port off host
19301931
hostonly, port = _splitport(host)
1931-
# check if the host ends with any of the DNS suffixes
1932+
host_ip = None
1933+
checked_host_ip = False
1934+
# for every entry in no_proxy...
19321935
for name in no_proxy.split(','):
19331936
name = name.strip()
19341937
if name:
19351938
name = name.lstrip('.') # ignore leading dots
19361939
name = name.lower()
1940+
1941+
# check for exact match
19371942
if hostonly == name or host == name:
19381943
return True
1944+
1945+
# check if the IP is within CIDR range
1946+
if '/' in name:
1947+
if not checked_host_ip:
1948+
from ipaddress import ip_address
1949+
for candidate in (hostonly, host):
1950+
candidate = candidate.strip('[]')
1951+
try:
1952+
host_ip = ip_address(candidate)
1953+
break
1954+
except ValueError:
1955+
pass
1956+
checked_host_ip = True
1957+
if host_ip is not None:
1958+
from ipaddress import ip_network
1959+
try:
1960+
if host_ip in ip_network(name, strict=False):
1961+
return True
1962+
except ValueError:
1963+
pass
1964+
1965+
# check if the host ends with any of the DNS suffixes
19391966
name = '.' + name
19401967
if hostonly.endswith(name) or host.endswith(name):
19411968
return True

0 commit comments

Comments
 (0)