diff --git a/doc/api/http.md b/doc/api/http.md index 3cdcab713612..9f0424d03941 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -4607,6 +4607,8 @@ The `NO_PROXY` environment variable supports several formats: * `*.example.com` - Wildcard domain match * `192.168.1.100` - Exact IP address match * `192.168.1.1-192.168.1.100` - IP address range +* `::1` or `[::1]` - Exact IPv6 address match +* `::1-::100` - IPv6 address range * `example.com:8080` - Hostname with specific port Multiple entries should be separated by commas. diff --git a/lib/internal/http.js b/lib/internal/http.js index 304ef04d6638..b17e28720a69 100644 --- a/lib/internal/http.js +++ b/lib/internal/http.js @@ -19,7 +19,8 @@ const { const { URL } = require('internal/url'); const { Buffer } = require('buffer'); -const { isIPv4 } = require('internal/net'); +const { BlockList } = require('internal/blocklist'); +const { isIPv4, isIPv6 } = require('internal/net'); const { ERR_PROXY_INVALID_CONFIG } = require('internal/errors').codes; let utcCache; @@ -173,16 +174,27 @@ class ProxyConfig { if (entry.startsWith('*.') && host.endsWith(entry.substring(1))) return false; // Handle IP ranges (simple format like 192.168.1.0-192.168.1.255) - // TODO(joyeecheung): support IPv6. - if (entry.includes('-') && isIPv4(host)) { + if (entry.includes('-')) { let { 0: startIP, 1: endIP } = entry.split('-'); startIP = startIP.trim(); endIP = endIP.trim(); - if (startIP && endIP && isIPv4(startIP) && isIPv4(endIP)) { + if (startIP && endIP && isIPv4(startIP) && isIPv4(endIP) && isIPv4(host)) { const hostInt = ipToInt(host); const startInt = ipToInt(startIP); const endInt = ipToInt(endIP); if (hostInt >= startInt && hostInt <= endInt) return false; + } else { + const hostIP = host[0] === '[' && host[host.length - 1] === ']' ? + host.slice(1, -1) : host; + if (startIP && endIP && isIPv6(startIP) && isIPv6(endIP) && isIPv6(hostIP)) { + const blockList = new BlockList(); + try { + blockList.addRange(startIP, endIP, 'ipv6'); + } catch { + continue; + } + if (blockList.check(hostIP, 'ipv6')) return false; + } } } diff --git a/test/client-proxy/test-http-proxy-request-no-proxy-ipv6.mjs b/test/client-proxy/test-http-proxy-request-no-proxy-ipv6.mjs new file mode 100644 index 000000000000..015ab30bd4f1 --- /dev/null +++ b/test/client-proxy/test-http-proxy-request-no-proxy-ipv6.mjs @@ -0,0 +1,75 @@ +// This tests that NO_PROXY environment variable supports IPv6 ranges. + +import * as common from '../common/index.mjs'; +import assert from 'node:assert'; +import { once } from 'events'; +import http from 'node:http'; +import { runProxiedRequest } from '../common/proxy-server.js'; + +// Start a server to process the final request. +const server = http.createServer(common.mustCall((req, res) => { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('Hello IPv6\n'); +}, 1)); +server.on('error', common.mustNotCall((err) => { console.error('Server error', err); })); +server.listen(0, '::1'); +await once(server, 'listening'); + +// Start a proxy server that should be used. +const proxy = http.createServer(common.mustCall((req, res) => { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('Proxied Hello IPv6\n'); +}, 2)); +proxy.listen(0, '::1'); +await once(proxy, 'listening'); + +// Test NO_PROXY with IPv6 range (::1-::100 includes ::1) +{ + const { code, signal, stderr, stdout } = await runProxiedRequest({ + NODE_USE_ENV_PROXY: 1, + REQUEST_URL: `http://[::1]:${server.address().port}/test`, + HTTP_PROXY: `http://[::1]:${proxy.address().port}`, + NO_PROXY: '::1-::100', + }); + // The request should succeed and bypass proxy + assert.match(stdout, /Status Code: 200/); + assert.match(stdout, /Hello IPv6/); + assert.strictEqual(stderr.trim(), ''); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); +} + +// Test another IPv6 address within the range (::50) +{ + const { code, signal, stderr, stdout } = await runProxiedRequest({ + NODE_USE_ENV_PROXY: 1, + REQUEST_URL: `http://[::1]:${server.address().port}/test`, + HTTP_PROXY: `http://localhost:${proxy.address().port}`, + NO_PROXY: '::50-::100', + }); + // The request should succeed and bypass proxy + assert.match(stdout, /Status Code: 200/); + assert.match(stdout, /Hello IPv6/); + assert.strictEqual(stderr.trim(), ''); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); +} + +// Test NO_PROXY with an IPv6 address outside the range (::200) +{ + const { code, signal, stderr, stdout } = await runProxiedRequest({ + NODE_USE_ENV_PROXY: 1, + REQUEST_URL: `http://[::200]:${server.address().port}/test`, + HTTP_PROXY: `http://[::1]:${proxy.address().port}`, + NO_PROXY: '::1-::100', + }); + // The request should be proxied + assert.match(stdout, /Status Code: 200/); + assert.match(stdout, /Proxied Hello IPv6/); + assert.strictEqual(stderr.trim(), ''); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); +} + +proxy.close(); +server.close();