Skip to content

Commit 82b9bef

Browse files
committed
http: Add IPv6 range support for NO_PROXY environment variable
1 parent 4612c79 commit 82b9bef

File tree

3 files changed

+116
-3
lines changed

3 files changed

+116
-3
lines changed

doc/api/http.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4371,6 +4371,8 @@ The `NO_PROXY` environment variable supports several formats:
43714371
* `*.example.com` - Wildcard domain match
43724372
* `192.168.1.100` - Exact IP address match
43734373
* `192.168.1.1-192.168.1.100` - IP address range
4374+
* `::1` or `[::1]` - Exact IPv6 address match
4375+
* `::1-::100` - IPv6 address range
43744376
* `example.com:8080` - Hostname with specific port
43754377
43764378
Multiple entries should be separated by commas.

lib/internal/http.js

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
'use strict';
22

33
const {
4+
Array,
5+
BigInt,
46
Date,
57
NumberParseInt,
68
Symbol,
@@ -16,7 +18,7 @@ const {
1618

1719
const { URL } = require('internal/url');
1820
const { Buffer } = require('buffer');
19-
const { isIPv4 } = require('internal/net');
21+
const { isIPv4, isIPv6 } = require('internal/net');
2022
const { ERR_PROXY_INVALID_CONFIG } = require('internal/errors').codes;
2123
let utcCache;
2224

@@ -67,6 +69,35 @@ function ipToInt(ip) {
6769
return result >>> 0;
6870
}
6971

72+
function ipv6ToBigInt(ip) {
73+
const cleanIp = ip.startsWith('[') ? ip.slice(1, -1) : ip;
74+
75+
const parts = cleanIp.split('::');
76+
let left = parts[0];
77+
let right = parts[1] || '';
78+
79+
if (parts.length === 2) {
80+
const leftParts = left.split(':').filter((p) => p.length > 0);
81+
const rightParts = right.split(':').filter((p) => p.length > 0);
82+
const missingParts = 8 - leftParts.length - rightParts.length;
83+
84+
left = leftParts.join(':');
85+
right = Array(missingParts).fill('0').join(':') + (right ? ':' + rightParts.join(':') : '');
86+
}
87+
88+
const fullAddress = (left + (right ? ':' + right : '')).replace(/^:|:$/g, '');
89+
const hexParts = fullAddress.split(':');
90+
91+
let result = BigInt(0);
92+
for (const hexVal of hexParts) {
93+
if (hexVal) {
94+
result = (result << BigInt(16)) + BigInt('0x' + hexVal);
95+
}
96+
}
97+
98+
return result;
99+
}
100+
70101
// There are two factors in play when proxying the request:
71102
// 1. What the request protocol is, that is, whether users are sending it via
72103
// http.request or https.request, or whether they are sending
@@ -162,15 +193,20 @@ class ProxyConfig {
162193

163194
// Handle IP ranges (simple format like 192.168.1.0-192.168.1.255)
164195
// TODO(joyeecheung): support IPv6.
165-
if (entry.includes('-') && isIPv4(host)) {
196+
if (entry.includes('-')) {
166197
let { 0: startIP, 1: endIP } = entry.split('-');
167198
startIP = startIP.trim();
168199
endIP = endIP.trim();
169-
if (startIP && endIP && isIPv4(startIP) && isIPv4(endIP)) {
200+
if (isIPv4(startIP) && isIPv4(endIP) && isIPv4(host)) {
170201
const hostInt = ipToInt(host);
171202
const startInt = ipToInt(startIP);
172203
const endInt = ipToInt(endIP);
173204
if (hostInt >= startInt && hostInt <= endInt) return false;
205+
} else if (isIPv6(startIP) && isIPv6(endIP) && isIPv6(host)) {
206+
const hostInt = ipv6ToBigInt(host);
207+
const startInt = ipv6ToBigInt(startIP);
208+
const endInt = ipv6ToBigInt(endIP);
209+
if (hostInt >= startInt && hostInt <= endInt) return false;
174210
}
175211
}
176212

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// This tests that NO_PROXY environment variable supports IPv6 ranges.
2+
3+
import * as common from '../common/index.mjs';
4+
import assert from 'node:assert';
5+
import { once } from 'events';
6+
import http from 'node:http';
7+
import { runProxiedRequest } from '../common/proxy-server.js';
8+
9+
// Start a server to process the final request.
10+
const server = http.createServer(common.mustCall((req, res) => {
11+
res.writeHead(200, { 'Content-Type': 'text/plain' });
12+
res.end('Hello IPv6\n');
13+
}, 1));
14+
server.on('error', common.mustNotCall((err) => { console.error('Server error', err); }));
15+
server.listen(0, '::1');
16+
await once(server, 'listening');
17+
18+
// Start a proxy server that should be used.
19+
const proxy = http.createServer(common.mustCall((req, res) => {
20+
res.writeHead(200, { 'Content-Type': 'text/plain' });
21+
res.end('Proxied Hello IPv6\n');
22+
}, 2));
23+
proxy.listen(0, '::1');
24+
await once(proxy, 'listening');
25+
26+
// Test NO_PROXY with IPv6 range (::1-::100 includes ::1)
27+
{
28+
const { code, signal, stderr, stdout } = await runProxiedRequest({
29+
NODE_USE_ENV_PROXY: 1,
30+
REQUEST_URL: `http://[::1]:${server.address().port}/test`,
31+
HTTP_PROXY: `http://[::1]:${proxy.address().port}`,
32+
NO_PROXY: '::1-::100',
33+
});
34+
// The request should succeed and bypass proxy
35+
assert.match(stdout, /Status Code: 200/);
36+
assert.match(stdout, /Hello IPv6/);
37+
assert.strictEqual(stderr.trim(), '');
38+
assert.strictEqual(code, 0);
39+
assert.strictEqual(signal, null);
40+
}
41+
42+
// Test another IPv6 address within the range (::50)
43+
{
44+
const { code, signal, stderr, stdout } = await runProxiedRequest({
45+
NODE_USE_ENV_PROXY: 1,
46+
REQUEST_URL: `http://[::1]:${server.address().port}/test`,
47+
HTTP_PROXY: `http://localhost:${proxy.address().port}`,
48+
NO_PROXY: '::50-::100',
49+
});
50+
// The request should succeed and bypass proxy
51+
assert.match(stdout, /Status Code: 200/);
52+
assert.match(stdout, /Hello IPv6/);
53+
assert.strictEqual(stderr.trim(), '');
54+
assert.strictEqual(code, 0);
55+
assert.strictEqual(signal, null);
56+
}
57+
58+
// Test NO_PROXY with an IPv6 address outside the range (::200)
59+
{
60+
const { code, signal, stderr, stdout } = await runProxiedRequest({
61+
NODE_USE_ENV_PROXY: 1,
62+
REQUEST_URL: `http://[::200]:${server.address().port}/test`,
63+
HTTP_PROXY: `http://[::1]:${proxy.address().port}`,
64+
NO_PROXY: '::1-::100',
65+
});
66+
// The request should be proxied
67+
assert.match(stdout, /Status Code: 200/);
68+
assert.match(stdout, /Proxied Hello IPv6/);
69+
assert.strictEqual(stderr.trim(), '');
70+
assert.strictEqual(code, 0);
71+
assert.strictEqual(signal, null);
72+
}
73+
74+
proxy.close();
75+
server.close();

0 commit comments

Comments
 (0)