noproxy: support proxies specified using cidr notation

Resolves: RHEL-86910
This commit is contained in:
Jacek Migacz 2026-01-13 14:50:38 +00:00
parent 66cf34993a
commit ec7cc6fa07
2 changed files with 280 additions and 3 deletions

View File

@ -0,0 +1,268 @@
From 1e9a538e05c0107c54ef81d9de7cd0b27cd13309 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Thu, 20 Oct 2022 15:21:12 +0200
Subject: [PATCH] noproxy: support proxies specified using cidr notation
For both IPv4 and IPv6 addresses. Now also checks IPv6 addresses "correctly"
and not with string comparisons.
Backported to curl 7.61.1 for RHEL 8.
Reported-by: Mathieu Carbonneaux
Fixes #9773
Fixes #5745
Closes #9775
---
index f159008..d151f5c 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -2509,6 +2509,75 @@ void Curl_free_request_state(struct Curl_easy *data)
#ifndef CURL_DISABLE_PROXY
+
+/*
+ * Curl_cidr4_match() returns TRUE if the given IPv4 address is within the
+ * specified CIDR address range.
+ */
+static bool Curl_cidr4_match(const char *ipv4, /* 1.2.3.4 address */
+ const char *network, /* 1.2.3.4 address */
+ unsigned int bits)
+{
+ unsigned int address = 0;
+ unsigned int check = 0;
+
+ if(bits > 32)
+ /* strange input */
+ return FALSE;
+
+ if(1 != Curl_inet_pton(AF_INET, ipv4, &address))
+ return FALSE;
+ if(1 != Curl_inet_pton(AF_INET, network, &check))
+ return FALSE;
+
+ if(!bits)
+ return TRUE; /* /0 means all addresses match */
+
+ if(bits && (bits != 32)) {
+ unsigned int mask = 0xffffffff << (32 - bits);
+ unsigned int haddr = htonl(address);
+ unsigned int hcheck = htonl(check);
+ if((haddr ^ hcheck) & mask)
+ return FALSE;
+ return TRUE;
+ }
+ return (address == check);
+}
+
+static bool Curl_cidr6_match(const char *ipv6,
+ const char *network,
+ unsigned int bits)
+{
+ int bytes;
+ int rest;
+ unsigned char address[16];
+ unsigned char check[16];
+
+ if(!bits)
+ bits = 128;
+
+ bytes = bits/8;
+ rest = bits & 0x07;
+ if((bytes > 16) || ((bytes == 16) && rest))
+ return FALSE;
+ if(1 != Curl_inet_pton(AF_INET6, ipv6, address))
+ return FALSE;
+ if(1 != Curl_inet_pton(AF_INET6, network, check))
+ return FALSE;
+ if(bytes && memcmp(address, check, bytes))
+ return FALSE;
+ if(rest && !((address[bytes] ^ check[bytes]) & (0xff << (8 - rest))))
+ return FALSE;
+
+ return TRUE;
+}
+
+enum nametype {
+ TYPE_HOST,
+ TYPE_IPV4,
+ TYPE_IPV6
+};
+
/****************************************************************
* Checks if the host is in the noproxy list. returns true if it matches
* and therefore the proxy should NOT be used.
@@ -2521,70 +2590,127 @@ static bool check_noproxy(const char *name, const char *no_proxy)
* all proxy variables)
*/
if(no_proxy && no_proxy[0]) {
- size_t tok_start;
- size_t tok_end;
- const char *separator = ", ";
- size_t no_proxy_len;
+ const char *p = no_proxy;
size_t namelen;
- char *endptr;
- if(strcasecompare("*", no_proxy)) {
+ enum nametype type = TYPE_HOST;
+ char hostip[128];
+ if(strcasecompare("*", no_proxy))
return TRUE;
- }
/* NO_PROXY was specified and it wasn't just an asterisk */
- no_proxy_len = strlen(no_proxy);
if(name[0] == '[') {
+ char *endptr;
/* IPv6 numerical address */
endptr = strchr(name, ']');
if(!endptr)
return FALSE;
name++;
- }
- else
- endptr = strchr(name, ':');
- if(endptr)
namelen = endptr - name;
- else
- namelen = strlen(name);
-
- for(tok_start = 0; tok_start < no_proxy_len; tok_start = tok_end + 1) {
- while(tok_start < no_proxy_len &&
- strchr(separator, no_proxy[tok_start]) != NULL) {
- /* Look for the beginning of the token. */
- ++tok_start;
+ if(namelen >= sizeof(hostip))
+ return FALSE;
+ memcpy(hostip, name, namelen);
+ hostip[namelen] = 0;
+ name = hostip;
+ type = TYPE_IPV6;
+ }
+ else {
+ char *portptr;
+ unsigned int address;
+ /* First, strip off any port number */
+ portptr = strchr(name, ':');
+ if(portptr)
+ namelen = portptr - name;
+ else
+ namelen = strlen(name);
+
+ /* Copy name without port to buffer for type detection */
+ if(namelen >= sizeof(hostip))
+ namelen = sizeof(hostip) - 1;
+ memcpy(hostip, name, namelen);
+ hostip[namelen] = 0;
+
+ /* Now check if it's an IPv4 address */
+ if(1 == Curl_inet_pton(AF_INET, hostip, &address)) {
+ type = TYPE_IPV4;
+ name = hostip;
}
+ else {
+ /* It's a hostname - namelen already excludes port */
+ }
+ }
- if(tok_start == no_proxy_len)
- break; /* It was all trailing separator chars, no more tokens. */
+ while(*p) {
+ const char *token;
+ size_t tokenlen = 0;
+ bool match = FALSE;
- for(tok_end = tok_start; tok_end < no_proxy_len &&
- strchr(separator, no_proxy[tok_end]) == NULL; ++tok_end)
- /* Look for the end of the token. */
- ;
+ /* pass blanks */
+ while(*p && ISBLANK(*p))
+ p++;
- /* To match previous behaviour, where it was necessary to specify
- * ".local.com" to prevent matching "notlocal.com", we will leave
- * the '.' off.
- */
- if(no_proxy[tok_start] == '.')
- ++tok_start;
-
- if((tok_end - tok_start) <= namelen) {
- /* Match the last part of the name to the domain we are checking. */
- const char *checkn = name + namelen - (tok_end - tok_start);
- if(strncasecompare(no_proxy + tok_start, checkn,
- tok_end - tok_start)) {
- if((tok_end - tok_start) == namelen || *(checkn - 1) == '.') {
- /* We either have an exact match, or the previous character is a .
- * so it is within the same domain, so no proxy for this host.
- */
- return TRUE;
+ token = p;
+ /* pass over the pattern */
+ while(*p && !ISBLANK(*p) && (*p != ',')) {
+ p++;
+ tokenlen++;
+ }
+
+ if(tokenlen) {
+ switch(type) {
+ case TYPE_HOST:
+ /* Skip leading dot in token */
+ if(tokenlen && (*token == '.')) {
+ token++;
+ tokenlen--;
+ }
+ /* A: example.com matches 'example.com'
+ B: www.example.com matches 'example.com'
+ C: nonexample.com DOES NOT match 'example.com' */
+ if(tokenlen == namelen)
+ /* case A, exact match */
+ match = strncasecompare(token, name, namelen);
+ else if(tokenlen < namelen) {
+ /* case B, tailmatch domain */
+ match = (name[namelen - tokenlen - 1] == '.') &&
+ strncasecompare(token, name + (namelen - tokenlen), tokenlen);
}
+ /* case C passes through, not a match */
+ break;
+ case TYPE_IPV4:
+ /* FALLTHROUGH */
+ case TYPE_IPV6: {
+ const char *check = token;
+ char *slash;
+ unsigned int bits = 0;
+ char checkip[128];
+ if(tokenlen >= sizeof(checkip))
+ /* this cannot match */
+ break;
+ /* copy the check name to a temp buffer */
+ memcpy(checkip, check, tokenlen);
+ checkip[tokenlen] = 0;
+ check = checkip;
+
+ slash = strchr(check, '/');
+ /* if the slash is part of this token, use it */
+ if(slash) {
+ bits = atoi(slash + 1);
+ *slash = 0;
+ }
+ if(type == TYPE_IPV6)
+ match = Curl_cidr6_match(name, check, bits);
+ else
+ match = Curl_cidr4_match(name, check, bits);
+ break;
+ }
}
- } /* if((tok_end - tok_start) <= namelen) */
- } /* for(tok_start = 0; tok_start < no_proxy_len;
- tok_start = tok_end + 1) */
+ if(match)
+ return TRUE;
+ } /* if(tokenlen) */
+ while(*p == ',')
+ p++;
+ } /* while(*p) */
} /* NO_PROXY was specified and it wasn't just an asterisk */
return FALSE;

View File

@ -1,7 +1,7 @@
Summary: A utility for getting files from remote servers (FTP, HTTP, and others)
Name: curl
Version: 7.61.1
Release: 34%{?dist}.10
Release: 34%{?dist}.11
License: MIT
Source: https://curl.haxx.se/download/%{name}-%{version}.tar.xz
@ -202,6 +202,9 @@ Patch68: 0068-curl-7.61.1-CVE-2025-9086.patch
# AWS Signature Version 4 authentication support
Patch69: 0069-curl-7.61.1-aws-sigv4.patch
# noproxy: support proxies specified using cidr notation
Patch70: 0070-curl-7.61.1-noproxy-support-using-cidr.patch
# patch making libcurl multilib ready
Patch101: 0101-curl-7.32.0-multilib.patch
@ -409,7 +412,6 @@ sed -e 's|%%HTTPPORT|%{?__isa_bits}90|g' -i tests/data/test1448
%patch -P 35 -p1
%patch -P 36 -p1
%patch -P 37 -p1
%patch -P 38 -p1
sed -e 's|:8992/|:%{?__isa_bits}92/|g' -i tests/data/test97{3..6}
@ -444,6 +446,7 @@ git apply %{PATCH52}
%patch -P 67 -p1
%patch -P 68 -p1
%patch -P 69 -p1
%patch -P 70 -p1
# make tests/*.py use Python 3
sed -e '1 s|^#!/.*python|#!%{__python3}|' -i tests/*.py
@ -457,7 +460,9 @@ automake
# <https://github.com/bagder/curl/commit/21e82bd6#commitcomment-12226582>
# and test 1900, which is flaky and covers a deprecated feature of libcurl
# <https://github.com/curl/curl/pull/2705>
printf "1112\n1455\n1801\n1900\n" >> tests/data/DISABLED
# disable test 1456 - requires test framework features not available in 7.61.1
# (crlf attribute support for <protocol> tag)
printf "1112\n1455\n1456\n1801\n1900\n" >> tests/data/DISABLED
# disable test 1319 on ppc64 (server times out)
%ifarch ppc64
@ -606,6 +611,10 @@ rm -f ${RPM_BUILD_ROOT}%{_libdir}/libcurl.la
%{_libdir}/libcurl.so.4.[0-9].[0-9].minimal
%changelog
* Tue Jan 13 2026 Jacek Migacz <jmigacz@redhat.com> - 7.61.1-34.el8_10.11
- noproxy: support proxies specified using cidr notation (RHEL-86910)
- disable test1456 (requires test framework features not in 7.61.1)
* Wed Dec 03 2025 Jacek Migacz <jmigacz@redhat.com> - 7.61.1-34.el8_10.10
- AWS Signature Version 4 authentication support (RHEL-116183)