Skip to content

Commit 1860b8e

Browse files
committed
nope-ip removal - Phase 5
Adding functions to net_utils.js | nope-ip removal - Phase 5 `net_utils.js` - Updating `is_private` to capture more cases - Adding `ip_to_buffer_safe` that will allocate a buffer for the given IP address - Adding `is_ipv6_loopback_buffer` that will ensure that an IPv6 buffer is a loopback - Adding `is_equal` that will check if two addresses are equal `ice.js` - Calling the native `is_private` from `net_utils` `http_utils` - Calling the native `is_equal` from `net_utils` - Adding jest tests Signed-off-by: liranmauda <[email protected]>
1 parent ed5f39b commit 1860b8e

File tree

4 files changed

+279
-10
lines changed

4 files changed

+279
-10
lines changed

src/rpc/ice.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const dbg = require('../util/debug_module')(__filename);
2020
const stun = require('./stun');
2121
const config = require('../../config');
2222
const js_utils = require('../util/js_utils');
23+
const net_utils = require('../util/net_utils');
2324
const url_utils = require('../util/url_utils');
2425
const FrameStream = require('../util/frame_stream');
2526
const buffer_utils = require('../util/buffer_utils');
@@ -1311,7 +1312,7 @@ Ice.prototype.close = function() {
13111312
function IceCandidate(cand) {
13121313
const is_private_no_throw = address => {
13131314
try {
1314-
return ip_module.isPrivate(address);
1315+
return net_utils.is_private(address);
13151316
} catch (err) {
13161317
return false;
13171318
}

src/test/unit_tests/util_functions_tests/test_net_utils.test.js

Lines changed: 174 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,125 @@ describe('IP Utils', () => {
136136
expect(net_utils.is_localhost('google.com')).toBe(false);
137137
});
138138

139-
it('should return true for private IPv4 addresses or false to any other address', () => {
139+
it('should return true for the IPv6 loopback address ::1', () => {
140+
const buf = Buffer.alloc(16);
141+
buf[15] = 1; // last byte is 1
142+
expect(net_utils.is_ipv6_loopback_buffer(buf)).toBe(true);
143+
});
144+
145+
it('should return false for :: (all zeros)', () => {
146+
const buf = Buffer.alloc(16); // all zeros
147+
expect(net_utils.is_ipv6_loopback_buffer(buf)).toBe(false);
148+
});
149+
150+
it('should return false for random non-loopback IPv6 address', () => {
151+
const buf = Buffer.from([
152+
0x20, 0x01, 0x0d, 0xb8,
153+
0x85, 0xa3, 0x00, 0x00,
154+
0x00, 0x00, 0x8a, 0x2e,
155+
0x03, 0x70, 0x73, 0x34
156+
]);
157+
expect(net_utils.is_ipv6_loopback_buffer(buf)).toBe(false);
158+
});
159+
160+
it('should return false if last byte is not 1', () => {
161+
const buf = Buffer.alloc(16);
162+
buf[15] = 2; // last byte is 2
163+
expect(net_utils.is_ipv6_loopback_buffer(buf)).toBe(false);
164+
});
165+
166+
it('should return false if first 15 bytes are not all zero', () => {
167+
const buf = Buffer.alloc(16);
168+
buf[0] = 1; // first byte not zero
169+
buf[15] = 1; // last byte correct
170+
expect(net_utils.is_ipv6_loopback_buffer(buf)).toBe(false);
171+
});
172+
173+
it('should return false for buffers of incorrect length', () => {
174+
const shortBuf = Buffer.alloc(8);
175+
expect(net_utils.is_ipv6_loopback_buffer(shortBuf)).toBe(false);
176+
177+
const longBuf = Buffer.alloc(32);
178+
longBuf[31] = 1;
179+
expect(net_utils.is_ipv6_loopback_buffer(longBuf)).toBe(false);
180+
});
181+
182+
it('should return true for 10/8', () => {
140183
expect(net_utils.is_private('10.0.0.1')).toBe(true);
184+
expect(net_utils.is_private('10.255.255.255')).toBe(true);
185+
});
186+
187+
it('should return true for 172.16.0.0 – 172.31.255.255', () => {
141188
expect(net_utils.is_private('172.16.0.1')).toBe(true);
142-
expect(net_utils.is_private('192.168.1.1')).toBe(true);
189+
expect(net_utils.is_private('172.31.255.254')).toBe(true);
190+
expect(net_utils.is_private('172.15.0.1')).toBe(false); // outside range
191+
expect(net_utils.is_private('172.32.0.1')).toBe(false); // outside range
192+
});
193+
194+
it('should return true for 192.168/16', () => {
195+
expect(net_utils.is_private('192.168.0.1')).toBe(true);
196+
expect(net_utils.is_private('192.168.255.255')).toBe(true);
197+
});
198+
199+
it('should return true for loopback 127/8', () => {
200+
expect(net_utils.is_private('127.0.0.1')).toBe(true);
201+
expect(net_utils.is_private('127.255.255.255')).toBe(true);
202+
});
203+
204+
it('should return true for link-local 169.254/16', () => {
205+
expect(net_utils.is_private('169.254.1.1')).toBe(true);
206+
});
207+
208+
it('should return false for public IPv4', () => {
143209
expect(net_utils.is_private('8.8.8.8')).toBe(false);
144210
expect(net_utils.is_private('1.1.1.1')).toBe(false);
145-
expect(net_utils.is_private('2001:db8::1')).toBe(false);
211+
expect(net_utils.is_private('172.32.0.1')).toBe(false);
212+
expect(net_utils.is_private('172.15.0.1')).toBe(false);
213+
});
214+
215+
// IPv4 with CIDR
216+
it('should correctly handle IPv4 addresses with CIDR', () => {
217+
expect(net_utils.is_private('10.1.2.3/24')).toBe(true);
218+
expect(net_utils.is_private('8.8.8.8/32')).toBe(false);
219+
});
220+
221+
// IPv6 unspecified (::)
222+
it('should return true for ::', () => {
223+
expect(net_utils.is_private('::')).toBe(true);
224+
});
225+
226+
// IPv6 loopback (::1)
227+
it('should return true for ::1', () => {
228+
expect(net_utils.is_private('::1')).toBe(true);
146229
});
147230

231+
// IPv6 ULA fc00::/7
232+
it('should return true for fc00::/7', () => {
233+
expect(net_utils.is_private('fc00::1')).toBe(true);
234+
expect(net_utils.is_private('fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')).toBe(true);
235+
expect(net_utils.is_private('fe00::1')).toBe(false);
236+
});
237+
238+
// IPv6 link-local fe80::/10
239+
it('should return true for fe80::/10', () => {
240+
expect(net_utils.is_private('fe80::1')).toBe(true);
241+
expect(net_utils.is_private('febf::1')).toBe(true);
242+
expect(net_utils.is_private('fec0::1')).toBe(false);
243+
});
244+
245+
// IPv4-mapped IPv6
246+
it('should correctly detect IPv4-mapped IPv6 addresses', () => {
247+
expect(net_utils.is_private('::ffff:10.0.0.1')).toBe(true);
248+
expect(net_utils.is_private('::ffff:8.8.8.8')).toBe(false);
249+
expect(net_utils.is_private('::ffff:192.168.1.1')).toBe(true);
250+
});
251+
252+
// Invalid addresses
253+
it('should return false for invalid addresses', () => {
254+
expect(net_utils.is_private('invalid')).toBe(false);
255+
expect(net_utils.is_private('300.300.300.300')).toBe(false);
256+
expect(net_utils.is_private('::gggg')).toBe(false);
257+
});
148258

149259
it('should return true for public addresses or false for private addresses', () => {
150260
expect(net_utils.is_public('10.0.0.1')).toBe(false);
@@ -155,4 +265,65 @@ describe('IP Utils', () => {
155265
expect(net_utils.is_public('2001:db8::1')).toBe(true);
156266
});
157267

268+
it('should return a 4-byte buffer for IPv4', () => {
269+
const buf = net_utils.ip_to_buffer_safe('192.168.1.1');
270+
expect(buf).toBeInstanceOf(Buffer);
271+
expect(buf.length).toBe(4);
272+
expect(buf).toEqual(Buffer.from([192, 168, 1, 1]));
273+
});
274+
275+
it('should return a 16-byte buffer for IPv6', () => {
276+
const buf = net_utils.ip_to_buffer_safe('::1');
277+
expect(buf).toBeInstanceOf(Buffer);
278+
expect(buf.length).toBe(16);
279+
// Loopback IPv6 is all zeros except last byte
280+
expect(buf[15]).toBe(1);
281+
});
282+
283+
it('should return null for invalid IP', () => {
284+
expect(net_utils.ip_to_buffer_safe('invalid')).toBeNull();
285+
expect(net_utils.ip_to_buffer_safe('300.300.300.300')).toBeNull();
286+
expect(net_utils.ip_to_buffer_safe('::gggg')).toBeNull();
287+
});
288+
289+
// IPv4 equality
290+
it('should return true for identical IPv4 addresses', () => {
291+
expect(net_utils.is_equal('192.168.1.1', '192.168.1.1')).toBe(true);
292+
});
293+
294+
it('should return false for different IPv4 addresses', () => {
295+
expect(net_utils.is_equal('192.168.1.1', '192.168.1.2')).toBe(false);
296+
});
297+
298+
// IPv6 equality
299+
it('should return true for identical IPv6 addresses', () => {
300+
expect(net_utils.is_equal('::1', '::1')).toBe(true);
301+
});
302+
303+
it('should return false for different IPv6 addresses', () => {
304+
expect(net_utils.is_equal('::1', '::2')).toBe(false);
305+
});
306+
307+
// IPv4 vs IPv6-mapped IPv4
308+
it('should return true for IPv4 and its IPv6-mapped equivalent', () => {
309+
expect(net_utils.is_equal('127.0.0.1', '::ffff:127.0.0.1')).toBe(true);
310+
expect(net_utils.is_equal('192.168.1.1', '::ffff:192.168.1.1')).toBe(true);
311+
});
312+
313+
it('should return false for IPv4 and non-matching IPv6-mapped address', () => {
314+
expect(net_utils.is_equal('192.168.1.1', '::ffff:192.168.1.2')).toBe(false);
315+
});
316+
317+
// Invalid IPs
318+
it('should return false if either address is invalid', () => {
319+
expect(net_utils.is_equal('invalid', '192.168.1.1')).toBe(false);
320+
expect(net_utils.is_equal('192.168.1.1', 'invalid')).toBe(false);
321+
expect(net_utils.is_equal('invalid', 'also.invalid')).toBe(false);
322+
});
323+
324+
// Mixed IPv4 and IPv6
325+
it('should return false for IPv4 and unrelated IPv6', () => {
326+
expect(net_utils.is_equal('192.168.1.1', '::1')).toBe(false);
327+
});
328+
158329
});

src/util/http_utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ function should_proxy(hostname) {
432432
for (const { kind, addr } of no_proxy_list) {
433433
dbg.log3(`should_proxy: an item from no_proxy_list: kind ${kind} addr ${addr}`);
434434
if (isIp) {
435-
if (kind === 'IP' && ip_module.isEqual(addr, hostname)) {
435+
if (kind === 'IP' && net_utils.is_equal(addr, hostname)) {
436436
return false;
437437
}
438438
if (kind === 'CIDR' && ip_module.cidrSubnet(addr).contains(hostname)) {

src/util/net_utils.js

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ function is_loopback(address) {
4949
return address.startsWith('127.') || address === '::1';
5050
}
5151

52+
/**
53+
* is_ipv6_loopback_buffer will check if the buffer is a loopback address
54+
* it will check if the first 15 bytes are 0 and the last byte is 1
55+
* this is the representation of loopback address in IPv6
56+
* @param {Buffer} buf
57+
* @returns {boolean}
58+
*/
59+
function is_ipv6_loopback_buffer(buf) {
60+
if (!Buffer.isBuffer(buf) || buf.length !== 16) return false;
61+
return buf.every((b, i) => (i < 15 ? b === 0 : b === 1));
62+
}
63+
64+
5265
/**
5366
* is_localhost will check if the address is localhost
5467
* @param {string} address
@@ -64,11 +77,50 @@ function is_localhost(address) {
6477
* @returns {boolean}
6578
*/
6679
function is_private(address) {
67-
return (
68-
address.startsWith('10.') ||
69-
address.startsWith('172.') ||
70-
address.startsWith('192.168.')
71-
);
80+
// Normalize and strip optional CIDR suffix
81+
const addr = String(address).split('/')[0];
82+
// Unwrap IPv6-mapped IPv4
83+
const v4 = unwrap_ipv6(addr);
84+
85+
// IPv4 ranges
86+
if (net.isIPv4(v4)) {
87+
return (/^10\./).test(v4) || // 10.0.0.0/8
88+
(/^192\.168\./).test(v4) || // 192.168.0.0/16
89+
(/^172\.(1[6-9]|2[0-9]|3[0-1])\./).test(v4) || // 172.16.0.0 – 172.31.255.255
90+
(/^127\./).test(v4) || // loopback
91+
(/^169\.254\./).test(v4); // link-local
92+
}
93+
94+
// IPv6 ranges
95+
if (net.isIPv6(addr)) {
96+
const buf = ip_to_buffer_safe(addr);
97+
if (!buf) return false;
98+
99+
// :: (unspecified)
100+
const is_unspecified = buf.every(b => b === 0);
101+
if (is_unspecified) return true;
102+
103+
// ::1 (loopback)
104+
if (is_ipv6_loopback_buffer(buf)) return true;
105+
106+
// fc00::/7 (ULA)
107+
if (buf[0] >= 0xfc && buf[0] <= 0xfd) return true;
108+
109+
// fe80::/10 (link-local)
110+
if (buf[0] === 0xfe && buf[1] >= 0x80 && buf[1] <= 0xbf) return true;
111+
112+
// ::ffff:a.b.c.d (IPv4-mapped) – classify by embedded IPv4
113+
const is_v4_mapped = buf.readUInt32BE(0) === 0 &&
114+
buf.readUInt32BE(4) === 0 &&
115+
buf.readUInt16BE(8) === 0 &&
116+
buf.readUInt16BE(10) === 0xffff;
117+
if (is_v4_mapped) {
118+
const embedded_v4 = `${buf[12]}.${buf[13]}.${buf[14]}.${buf[15]}`;
119+
return is_private(embedded_v4);
120+
}
121+
return false;
122+
}
123+
return false;
72124
}
73125

74126
/**
@@ -228,6 +280,48 @@ function find_ifc_containing_address(address) {
228280
}
229281
}
230282

283+
/**
284+
* will allocate a buffer for the given IP address
285+
* buffer size is determined by the IP version
286+
* @param {string} ip
287+
* @returns {Buffer|null}
288+
*/
289+
function ip_to_buffer_safe(ip) {
290+
if (net.isIPv4(ip)) {
291+
return ip_toBuffer(ip, Buffer.alloc(4), 0);
292+
} else if (net.isIPv6(ip)) {
293+
return ip_toBuffer(ip, Buffer.alloc(16), 0);
294+
} else {
295+
return null; // invalid IP
296+
}
297+
}
298+
299+
/**
300+
* is_equal will check if two addresses are equal
301+
* it will handle both IPv4 and IPv6 addresses, including IPv6-mapped IPv4
302+
* @param {string} address1
303+
* @param {string} address2
304+
* @returns {boolean}
305+
*/
306+
function is_equal(address1, address2) {
307+
const buffer1 = ip_to_buffer_safe(address1);
308+
const buffer2 = ip_to_buffer_safe(address2);
309+
310+
// if either address is invalid, return false
311+
if (!buffer1 || !buffer2) return false;
312+
313+
// checks when the ips are of different type (v4 and v6)
314+
if (buffer1.length !== buffer2.length) {
315+
if (buffer1.length === 4) return address2 === `::ffff:${address1}`;
316+
if (buffer2.length === 4) return address1 === `::ffff:${address2}`;
317+
return false;
318+
}
319+
320+
// compare the buffers when the ips are of the same type
321+
return buffer1.equals(buffer2);
322+
}
323+
324+
231325
exports.is_ip = is_ip;
232326
exports.is_fqdn = is_fqdn;
233327
exports.is_cidr = is_cidr;
@@ -238,6 +332,7 @@ exports.ip_toString = ip_toString;
238332
exports.ip_toBuffer = ip_toBuffer;
239333
exports.ip_to_long = ip_to_long;
240334
exports.find_ifc_containing_address = find_ifc_containing_address;
335+
exports.is_equal = is_equal;
241336

242337
/// EXPORTS FOR TESTING:
243338
exports.normalize_family = normalize_family;
@@ -247,3 +342,5 @@ exports.is_public = is_public;
247342
exports.ipv4_to_buffer = ipv4_to_buffer;
248343
exports.ipv6_to_buffer = ipv6_to_buffer;
249344
exports.expend_ipv6 = expend_ipv6;
345+
exports.ip_to_buffer_safe = ip_to_buffer_safe;
346+
exports.is_ipv6_loopback_buffer = is_ipv6_loopback_buffer;

0 commit comments

Comments
 (0)