From 9e65b33b359132a51c987188871654ac2f3014dc Mon Sep 17 00:00:00 2001 From: Giovanni Date: Thu, 8 Aug 2024 12:15:19 +0200 Subject: [PATCH 01/10] net: exclude ipv6 loopback addresses from server.listen Fixes: https://github.com/nodejs/node/issues/51732 --- lib/net.js | 26 ++++++- ...er-close-before-calling-lookup-callback.js | 2 +- .../test-net-server-listen-ipv6-loopback.js | 78 +++++++++++++++++++ 3 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 test/sequential/test-net-server-listen-ipv6-loopback.js diff --git a/lib/net.js b/lib/net.js index 4de6f9c5f6f23a..47eab28c0184cb 100644 --- a/lib/net.js +++ b/lib/net.js @@ -35,6 +35,7 @@ const { NumberParseInt, ObjectDefineProperty, ObjectSetPrototypeOf, + StringPrototypeStartsWith, Symbol, SymbolAsyncDispose, SymbolDispose, @@ -2118,19 +2119,38 @@ Server.prototype.listen = function(...args) { throw new ERR_INVALID_ARG_VALUE('options', options); }; +function isIpv6Loopback({ address, family }) { + return family === 6 && StringPrototypeStartsWith(address, 'fe80::'); +} + +function filterOnlyValidAddress(addresses) { + // Return the first non IPV6 loopback address if present + for (const address of addresses) { + if (!isIpv6Loopback(address)) { + return address; + } + } + + // Otherwise return the first address + return addresses[0]; +} + function lookupAndListen(self, port, address, backlog, exclusive, flags) { if (dns === undefined) dns = require('dns'); const listeningId = self._listeningId; - dns.lookup(address, function doListen(err, ip, addressType) { + + dns.lookup(address, { all: true }, (err, addresses) => { if (listeningId !== self._listeningId) { return; } if (err) { self.emit('error', err); } else { - addressType = ip ? addressType : 4; - listenInCluster(self, ip, port, addressType, + const validAddress = filterOnlyValidAddress(addresses); + const family = validAddress?.family || 4; + + listenInCluster(self, validAddress.address, port, family, backlog, undefined, exclusive, flags); } }); diff --git a/test/parallel/test-net-server-close-before-calling-lookup-callback.js b/test/parallel/test-net-server-close-before-calling-lookup-callback.js index 58cfc5504c04fa..114424b36d1be9 100644 --- a/test/parallel/test-net-server-close-before-calling-lookup-callback.js +++ b/test/parallel/test-net-server-close-before-calling-lookup-callback.js @@ -3,5 +3,5 @@ const common = require('../common'); const net = require('net'); // Process should exit because it does not create a real TCP server. -// Paas localhost to ensure create TCP handle asynchronously because It causes DNS resolution. +// Pass localhost to ensure create TCP handle asynchronously because It causes DNS resolution. net.createServer().listen(0, 'localhost', common.mustNotCall()).close(); diff --git a/test/sequential/test-net-server-listen-ipv6-loopback.js b/test/sequential/test-net-server-listen-ipv6-loopback.js new file mode 100644 index 00000000000000..f4a255365fb7d7 --- /dev/null +++ b/test/sequential/test-net-server-listen-ipv6-loopback.js @@ -0,0 +1,78 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); +const dns = require('dns'); + +if (!common.hasIPv6) { + common.printSkipMessage('ipv6 part of test, no IPv6 support'); + return; +} + +// Test on IPv6 Server, throws EADDRNOTAVAIL +{ + const host = 'fe80::1'; + + const server = net.createServer(); + + server.on('error', common.mustCall((e) => { + assert.strictEqual(e.address, host); + assert.strictEqual(e.syscall, 'listen'); + assert.strictEqual(e.code, 'EADDRNOTAVAIL'); + assert.strictEqual(e.errno, -49); + })); + + server.listen(common.PORT + 2, host); +} + +// Test on IPv6 Server, picks 127.0.0.1 between that and fe80::1 +{ + + // Mock dns.lookup + const originalLookup = dns.lookup; + dns.lookup = (hostname, options, callback) => { + if (hostname === 'ipv6_loopback_with_double_entry') { + callback(null, [{ address: 'fe80::1', family: 6 }, + { address: '127.0.0.1', family: 4 }]); + } else { + originalLookup(hostname, options, callback); + } + }; + + const host = 'ipv6_loopback_with_double_entry'; + const family4 = 'IPv4'; + + const server = net.createServer(); + + server.on('error', common.mustNotCall()); + + server.listen(common.PORT + 2, host, common.mustCall(() => { + const address = server.address(); + assert.strictEqual(address.address, '127.0.0.1'); + assert.strictEqual(address.port, common.PORT + 2); + assert.strictEqual(address.family, family4); + server.close(); + dns.lookup = originalLookup; + })); +} From 804842d917732d0a4b76ca0b0834ea7fb23c2b35 Mon Sep 17 00:00:00 2001 From: Giovanni Date: Thu, 8 Aug 2024 12:28:55 +0200 Subject: [PATCH 02/10] net: changed the replacing of the dns module with a proper mock --- .../sequential/test-net-server-listen-ipv6-loopback.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/sequential/test-net-server-listen-ipv6-loopback.js b/test/sequential/test-net-server-listen-ipv6-loopback.js index f4a255365fb7d7..c0a3b8c2fd2b3c 100644 --- a/test/sequential/test-net-server-listen-ipv6-loopback.js +++ b/test/sequential/test-net-server-listen-ipv6-loopback.js @@ -24,6 +24,7 @@ const common = require('../common'); const assert = require('assert'); const net = require('net'); const dns = require('dns'); +const { mock } = require('node:test'); if (!common.hasIPv6) { common.printSkipMessage('ipv6 part of test, no IPv6 support'); @@ -49,16 +50,14 @@ if (!common.hasIPv6) { // Test on IPv6 Server, picks 127.0.0.1 between that and fe80::1 { - // Mock dns.lookup - const originalLookup = dns.lookup; - dns.lookup = (hostname, options, callback) => { + mock.method(dns, 'lookup', (hostname, options, callback) => { if (hostname === 'ipv6_loopback_with_double_entry') { callback(null, [{ address: 'fe80::1', family: 6 }, { address: '127.0.0.1', family: 4 }]); } else { - originalLookup(hostname, options, callback); + dns.lookup.wrappedMethod(hostname, options, callback); } - }; + }); const host = 'ipv6_loopback_with_double_entry'; const family4 = 'IPv4'; @@ -73,6 +72,5 @@ if (!common.hasIPv6) { assert.strictEqual(address.port, common.PORT + 2); assert.strictEqual(address.family, family4); server.close(); - dns.lookup = originalLookup; })); } From 5dec57944583fe1459f2282d77f224845b878eb8 Mon Sep 17 00:00:00 2001 From: Giovanni Date: Fri, 9 Aug 2024 08:13:49 +0200 Subject: [PATCH 03/10] dns: removed copyright from new test and fixed broken one --- .../test-net-server-listen-ipv6-loopback.js | 39 ++++++------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/test/sequential/test-net-server-listen-ipv6-loopback.js b/test/sequential/test-net-server-listen-ipv6-loopback.js index c0a3b8c2fd2b3c..42011c29a99422 100644 --- a/test/sequential/test-net-server-listen-ipv6-loopback.js +++ b/test/sequential/test-net-server-listen-ipv6-loopback.js @@ -1,24 +1,3 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - 'use strict'; const common = require('../common'); const assert = require('assert'); @@ -31,16 +10,22 @@ if (!common.hasIPv6) { return; } -// Test on IPv6 Server, throws EADDRNOTAVAIL +// Test on IPv6 Server, throws an error { - const host = 'fe80::1'; + mock.method(dns, 'lookup', (hostname, options, callback) => { + if (hostname === 'ipv6_loopback') { + callback(null, [{ address: 'fe80::1', family: 6 }]); + } else { + dns.lookup.wrappedMethod(hostname, options, callback); + } + }); + const host = 'ipv6_loopback'; const server = net.createServer(); server.on('error', common.mustCall((e) => { - assert.strictEqual(e.address, host); + assert.strictEqual(e.address, 'fe80::1'); assert.strictEqual(e.syscall, 'listen'); - assert.strictEqual(e.code, 'EADDRNOTAVAIL'); assert.strictEqual(e.errno, -49); })); @@ -66,10 +51,10 @@ if (!common.hasIPv6) { server.on('error', common.mustNotCall()); - server.listen(common.PORT + 2, host, common.mustCall(() => { + server.listen(common.PORT + 3, host, common.mustCall(() => { const address = server.address(); assert.strictEqual(address.address, '127.0.0.1'); - assert.strictEqual(address.port, common.PORT + 2); + assert.strictEqual(address.port, common.PORT + 3); assert.strictEqual(address.family, family4); server.close(); })); From bbf4e0b5757ea712e063917732becd42811e6b74 Mon Sep 17 00:00:00 2001 From: Giovanni Date: Fri, 9 Aug 2024 08:18:17 +0200 Subject: [PATCH 04/10] dns: added automated test to check the error emission in lookupAndListen --- .../test-net-server-listen-ipv6-loopback.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/sequential/test-net-server-listen-ipv6-loopback.js b/test/sequential/test-net-server-listen-ipv6-loopback.js index 42011c29a99422..f327fa9ef23fcb 100644 --- a/test/sequential/test-net-server-listen-ipv6-loopback.js +++ b/test/sequential/test-net-server-listen-ipv6-loopback.js @@ -10,7 +10,24 @@ if (!common.hasIPv6) { return; } -// Test on IPv6 Server, throws an error +// Test on IPv6 Server, dns.lookup throws an error +{ + mock.method(dns, 'lookup', (hostname, options, callback) => { + callback(new Error('Mocked error')); + }); + const host = 'ipv6_loopback'; + + const server = net.createServer(); + + server.on('error', common.mustCall((e) => { + assert.strictEqual(e.message, 'Mocked error'); + })); + + server.listen(common.PORT + 2, host); +} + + +// Test on IPv6 Server, server.listen throws an error { mock.method(dns, 'lookup', (hostname, options, callback) => { if (hostname === 'ipv6_loopback') { From 6a5f988b6e963227ad29d597a9f702229a8b1ea2 Mon Sep 17 00:00:00 2001 From: Giovanni Date: Fri, 9 Aug 2024 13:14:01 +0200 Subject: [PATCH 05/10] dns: fixed the broken test. On mac it is failing with a different code --- test/sequential/test-net-server-listen-ipv6-loopback.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/sequential/test-net-server-listen-ipv6-loopback.js b/test/sequential/test-net-server-listen-ipv6-loopback.js index f327fa9ef23fcb..8753d6c635d280 100644 --- a/test/sequential/test-net-server-listen-ipv6-loopback.js +++ b/test/sequential/test-net-server-listen-ipv6-loopback.js @@ -43,7 +43,6 @@ if (!common.hasIPv6) { server.on('error', common.mustCall((e) => { assert.strictEqual(e.address, 'fe80::1'); assert.strictEqual(e.syscall, 'listen'); - assert.strictEqual(e.errno, -49); })); server.listen(common.PORT + 2, host); From 1896a8ef599310ac348c06b695a9e23a4217a43c Mon Sep 17 00:00:00 2001 From: Giovanni Date: Sat, 10 Aug 2024 10:57:15 +0200 Subject: [PATCH 06/10] net: checking the whole range of ipv6 link-back addresses --- lib/net.js | 19 ++- .../test-net-server-listen-ipv6-link-local.js | 131 ++++++++++++++++++ .../test-net-server-listen-ipv6-loopback.js | 77 ---------- 3 files changed, 145 insertions(+), 82 deletions(-) create mode 100644 test/sequential/test-net-server-listen-ipv6-link-local.js delete mode 100644 test/sequential/test-net-server-listen-ipv6-loopback.js diff --git a/lib/net.js b/lib/net.js index 47eab28c0184cb..322e9200bf1745 100644 --- a/lib/net.js +++ b/lib/net.js @@ -35,7 +35,6 @@ const { NumberParseInt, ObjectDefineProperty, ObjectSetPrototypeOf, - StringPrototypeStartsWith, Symbol, SymbolAsyncDispose, SymbolDispose, @@ -2119,14 +2118,24 @@ Server.prototype.listen = function(...args) { throw new ERR_INVALID_ARG_VALUE('options', options); }; -function isIpv6Loopback({ address, family }) { - return family === 6 && StringPrototypeStartsWith(address, 'fe80::'); +function IPv6ToBinary(ipv6) { + return ipv6.split(':').reduce((acc, part, index, arr) => { + if (part === '') { + const zeroSegments = 8 - arr.length + 1; + return acc + '0'.repeat(zeroSegments * 16); + } + return acc + parseInt(part, 16).toString(2).padStart(16, '0'); + }, ''); +} + +function isIpv6LinkLocal(s) { + return isIPv6(s) && IPv6ToBinary(s).slice(0, 10) === '1111111010'; } function filterOnlyValidAddress(addresses) { - // Return the first non IPV6 loopback address if present + // Return the first non IPV6 link-local address if present for (const address of addresses) { - if (!isIpv6Loopback(address)) { + if (!isIpv6LinkLocal(address.address)) { return address; } } diff --git a/test/sequential/test-net-server-listen-ipv6-link-local.js b/test/sequential/test-net-server-listen-ipv6-link-local.js new file mode 100644 index 00000000000000..6808a26eb55c34 --- /dev/null +++ b/test/sequential/test-net-server-listen-ipv6-link-local.js @@ -0,0 +1,131 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); +const dns = require('dns'); +const { mock } = require('node:test'); + +if (!common.hasIPv6) { + common.printSkipMessage('ipv6 part of test, no IPv6 support'); + return; +} + +// Test on IPv6 Server, dns.lookup throws an error +{ + mock.method(dns, 'lookup', (hostname, options, callback) => { + callback(new Error('Mocked error')); + }); + const host = 'ipv6_link_local'; + + const server = net.createServer(); + + server.on('error', common.mustCall((e) => { + assert.strictEqual(e.message, 'Mocked error'); + })); + + server.listen(common.PORT + 2, host); +} + + +// Test on IPv6 Server, server.listen throws an error +{ + mock.method(dns, 'lookup', (hostname, options, callback) => { + if (hostname === 'ipv6_link_local') { + callback(null, [{ address: 'fe80::1', family: 6 }]); + } else { + dns.lookup.wrappedMethod(hostname, options, callback); + } + }); + const host = 'ipv6_link_local'; + + const server = net.createServer(); + + server.on('error', common.mustCall((e) => { + assert.strictEqual(e.address, 'fe80::1'); + assert.strictEqual(e.syscall, 'listen'); + })); + + server.listen(common.PORT + 2, host); +} + +// Test on IPv6 Server, picks 127.0.0.1 between that and a bunch of link-local addresses +{ + + mock.method(dns, 'lookup', (hostname, options, callback) => { + if (hostname === 'ipv6_link_local_with_many_entries') { + callback(null, [ + { address: 'fe80::1', family: 6 }, + { address: 'fe80::abcd:1234', family: 6 }, + { address: 'fe80::1ff:fe23:4567:890a', family: 6 }, + { address: 'fe80::200:5aee:feaa:20a2', family: 6 }, + { address: 'fe80::f2de:f1ff:fe2b:3c4b', family: 6 }, + { address: 'fe81::1', family: 6 }, + { address: 'fe82::abcd:1234', family: 6 }, + { address: 'fe83::1ff:fe23:4567:890a', family: 6 }, + { address: 'fe84::200:5aee:feaa:20a2', family: 6 }, + { address: 'fe85::f2de:f1ff:fe2b:3c4b', family: 6 }, + { address: 'fe86::1', family: 6 }, + { address: 'fe87::abcd:1234', family: 6 }, + { address: 'fe88::1ff:fe23:4567:890a', family: 6 }, + { address: 'fe89::200:5aee:feaa:20a2', family: 6 }, + { address: 'fe8a::f2de:f1ff:fe2b:3c4b', family: 6 }, + { address: 'fe8b::1', family: 6 }, + { address: 'fe8c::abcd:1234', family: 6 }, + { address: 'fe8d::1ff:fe23:4567:890a', family: 6 }, + { address: 'fe8e::200:5aee:feaa:20a2', family: 6 }, + { address: 'fe8f::f2de:f1ff:fe2b:3c4b', family: 6 }, + { address: 'fea0::1', family: 6 }, + { address: 'febf::abcd:1234', family: 6 }, + { address: 'febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff', family: 6 }, + { address: '127.0.0.1', family: 4 }, + ]); + } else { + dns.lookup.wrappedMethod(hostname, options, callback); + } + }); + + const host = 'ipv6_link_local_with_many_entries'; + + const server = net.createServer(); + + server.on('error', common.mustNotCall()); + + server.listen(common.PORT + 3, host, common.mustCall(() => { + const address = server.address(); + assert.strictEqual(address.address, '127.0.0.1'); + assert.strictEqual(address.port, common.PORT + 3); + assert.strictEqual(address.family, 'IPv4'); + server.close(); + })); +} + + +// Test on IPv6 Server, picks ::1 because the other address is a link-local address +{ + + const host = 'ipv6_link_local_with_double_entry'; + const validIpv6Address = '::1'; + + mock.method(dns, 'lookup', (hostname, options, callback) => { + if (hostname === 'ipv6_link_local_with_double_entry') { + callback(null, [ + { address: 'fe80::1', family: 6 }, + { address: validIpv6Address, family: 6 }, + ]); + } else { + dns.lookup.wrappedMethod(hostname, options, callback); + } + }); + + const server = net.createServer(); + + server.on('error', common.mustNotCall()); + + server.listen(common.PORT + 4, host, common.mustCall(() => { + const address = server.address(); + assert.strictEqual(address.address, validIpv6Address); + assert.strictEqual(address.port, common.PORT + 4); + assert.strictEqual(address.family, 'IPv6'); + server.close(); + })); +} diff --git a/test/sequential/test-net-server-listen-ipv6-loopback.js b/test/sequential/test-net-server-listen-ipv6-loopback.js deleted file mode 100644 index 8753d6c635d280..00000000000000 --- a/test/sequential/test-net-server-listen-ipv6-loopback.js +++ /dev/null @@ -1,77 +0,0 @@ -'use strict'; -const common = require('../common'); -const assert = require('assert'); -const net = require('net'); -const dns = require('dns'); -const { mock } = require('node:test'); - -if (!common.hasIPv6) { - common.printSkipMessage('ipv6 part of test, no IPv6 support'); - return; -} - -// Test on IPv6 Server, dns.lookup throws an error -{ - mock.method(dns, 'lookup', (hostname, options, callback) => { - callback(new Error('Mocked error')); - }); - const host = 'ipv6_loopback'; - - const server = net.createServer(); - - server.on('error', common.mustCall((e) => { - assert.strictEqual(e.message, 'Mocked error'); - })); - - server.listen(common.PORT + 2, host); -} - - -// Test on IPv6 Server, server.listen throws an error -{ - mock.method(dns, 'lookup', (hostname, options, callback) => { - if (hostname === 'ipv6_loopback') { - callback(null, [{ address: 'fe80::1', family: 6 }]); - } else { - dns.lookup.wrappedMethod(hostname, options, callback); - } - }); - const host = 'ipv6_loopback'; - - const server = net.createServer(); - - server.on('error', common.mustCall((e) => { - assert.strictEqual(e.address, 'fe80::1'); - assert.strictEqual(e.syscall, 'listen'); - })); - - server.listen(common.PORT + 2, host); -} - -// Test on IPv6 Server, picks 127.0.0.1 between that and fe80::1 -{ - - mock.method(dns, 'lookup', (hostname, options, callback) => { - if (hostname === 'ipv6_loopback_with_double_entry') { - callback(null, [{ address: 'fe80::1', family: 6 }, - { address: '127.0.0.1', family: 4 }]); - } else { - dns.lookup.wrappedMethod(hostname, options, callback); - } - }); - - const host = 'ipv6_loopback_with_double_entry'; - const family4 = 'IPv4'; - - const server = net.createServer(); - - server.on('error', common.mustNotCall()); - - server.listen(common.PORT + 3, host, common.mustCall(() => { - const address = server.address(); - assert.strictEqual(address.address, '127.0.0.1'); - assert.strictEqual(address.port, common.PORT + 3); - assert.strictEqual(address.family, family4); - server.close(); - })); -} From 97d8fc3f270c5a6ac5629d7ad1eae13b9afa9f95 Mon Sep 17 00:00:00 2001 From: Giovanni Date: Mon, 19 Aug 2024 19:02:14 +0200 Subject: [PATCH 07/10] net: bridging native uv_inet_pton to js to parse ip to binary buffer --- lib/net.js | 26 +++++++++++++++----------- src/cares_wrap.cc | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/lib/net.js b/lib/net.js index 322e9200bf1745..be21c566610286 100644 --- a/lib/net.js +++ b/lib/net.js @@ -62,6 +62,7 @@ const { UV_ECANCELED, UV_ETIMEDOUT, } = internalBinding('uv'); +const { convertIpv6StringToBuffer } = internalBinding('cares_wrap'); const { Buffer } = require('buffer'); const { ShutdownWrap } = internalBinding('stream_wrap'); @@ -2118,18 +2119,21 @@ Server.prototype.listen = function(...args) { throw new ERR_INVALID_ARG_VALUE('options', options); }; -function IPv6ToBinary(ipv6) { - return ipv6.split(':').reduce((acc, part, index, arr) => { - if (part === '') { - const zeroSegments = 8 - arr.length + 1; - return acc + '0'.repeat(zeroSegments * 16); - } - return acc + parseInt(part, 16).toString(2).padStart(16, '0'); - }, ''); -} +function isIpv6LinkLocal(ip) { + if (!isIPv6(ip)) { return false; } + + const ipv6Buffer = convertIpv6StringToBuffer(ip); + const firstByte = ipv6Buffer[0]; // The first 8 bits + const secondByte = ipv6Buffer[1]; // The next 8 bits + + // The link-local prefix is `1111111010`, which in hexadecimal is `fe80` + // First 8 bits (firstByte) should be `11111110` (0xfe) + // The next 2 bits of the second byte should be `10` (0x80) + + const isFirstByteCorrect = (firstByte === 0xfe); // 0b11111110 == 0xfe + const isSecondByteCorrect = (secondByte & 0xc0) === 0x80; // 0b10xxxxxx == 0x80 -function isIpv6LinkLocal(s) { - return isIPv6(s) && IPv6ToBinary(s).slice(0, 10) === '1111111010'; + return isFirstByteCorrect && isSecondByteCorrect; } function filterOnlyValidAddress(addresses) { diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 26877f3ddd8f69..a633af1fa3208f 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -1566,6 +1566,24 @@ void CanonicalizeIP(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(val); } +void ConvertIpv6StringToBuffer(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + node::Utf8Value ip(isolate, args[0]); + unsigned char dst[16]; // IPv6 addresses are 128 bits (16 bytes) + + if (uv_inet_pton(AF_INET6, *ip, dst) != 0) { + isolate->ThrowException( + v8::Exception::Error( + String::NewFromUtf8(isolate, "Invalid IPv6 address").ToLocalChecked())); + return; + } + + Local buffer = node::Buffer::Copy( + isolate, + reinterpret_cast(dst), sizeof(dst)).ToLocalChecked(); + args.GetReturnValue().Set(buffer); +} + void GetAddrInfo(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -1902,6 +1920,10 @@ void Initialize(Local target, SetMethod(context, target, "getaddrinfo", GetAddrInfo); SetMethod(context, target, "getnameinfo", GetNameInfo); SetMethodNoSideEffect(context, target, "canonicalizeIP", CanonicalizeIP); + SetMethodNoSideEffect(context, + target, + "convertIpv6StringToBuffer", + ConvertIpv6StringToBuffer); SetMethod(context, target, "strerror", StrError); @@ -1995,6 +2017,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(GetAddrInfo); registry->Register(GetNameInfo); registry->Register(CanonicalizeIP); + registry->Register(ConvertIpv6StringToBuffer); registry->Register(StrError); registry->Register(ChannelWrap::New); From 195b6311b85dc8513b693b38aa9e1a5a1fb603af Mon Sep 17 00:00:00 2001 From: Giovanni Bucci Date: Mon, 19 Aug 2024 19:50:14 +0200 Subject: [PATCH 08/10] test: update case of comment in a test Co-authored-by: Aviv Keller <38299977+RedYetiDev@users.noreply.github.com> --- .../test-net-server-close-before-calling-lookup-callback.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-net-server-close-before-calling-lookup-callback.js b/test/parallel/test-net-server-close-before-calling-lookup-callback.js index 114424b36d1be9..0c42639523288f 100644 --- a/test/parallel/test-net-server-close-before-calling-lookup-callback.js +++ b/test/parallel/test-net-server-close-before-calling-lookup-callback.js @@ -3,5 +3,5 @@ const common = require('../common'); const net = require('net'); // Process should exit because it does not create a real TCP server. -// Pass localhost to ensure create TCP handle asynchronously because It causes DNS resolution. +// Pass localhost to ensure create TCP handle asynchronously because it causes DNS resolution. net.createServer().listen(0, 'localhost', common.mustNotCall()).close(); From 0f8eb482b4c892890bae440af343eeb44ce960e2 Mon Sep 17 00:00:00 2001 From: Giovanni Bucci Date: Mon, 19 Aug 2024 19:50:52 +0200 Subject: [PATCH 09/10] test: updated the message when ipv6 is not enabled for link-local test Co-authored-by: Aviv Keller <38299977+RedYetiDev@users.noreply.github.com> --- test/sequential/test-net-server-listen-ipv6-link-local.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sequential/test-net-server-listen-ipv6-link-local.js b/test/sequential/test-net-server-listen-ipv6-link-local.js index 6808a26eb55c34..56664c227cb5b7 100644 --- a/test/sequential/test-net-server-listen-ipv6-link-local.js +++ b/test/sequential/test-net-server-listen-ipv6-link-local.js @@ -6,7 +6,7 @@ const dns = require('dns'); const { mock } = require('node:test'); if (!common.hasIPv6) { - common.printSkipMessage('ipv6 part of test, no IPv6 support'); + common.printSkipMessage('IPv6 support is required for this test'); return; } From f04cc46f55696b518898e694caf6c34264bad332 Mon Sep 17 00:00:00 2001 From: Giovanni Date: Tue, 20 Aug 2024 10:29:01 +0200 Subject: [PATCH 10/10] net: formatted cares_wrap.cc to fix linting issue --- src/cares_wrap.cc | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index a633af1fa3208f..436b2e3e002d81 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -1572,15 +1572,15 @@ void ConvertIpv6StringToBuffer(const FunctionCallbackInfo& args) { unsigned char dst[16]; // IPv6 addresses are 128 bits (16 bytes) if (uv_inet_pton(AF_INET6, *ip, dst) != 0) { - isolate->ThrowException( - v8::Exception::Error( + isolate->ThrowException(v8::Exception::Error( String::NewFromUtf8(isolate, "Invalid IPv6 address").ToLocalChecked())); return; } - Local buffer = node::Buffer::Copy( - isolate, - reinterpret_cast(dst), sizeof(dst)).ToLocalChecked(); + Local buffer = + node::Buffer::Copy( + isolate, reinterpret_cast(dst), sizeof(dst)) + .ToLocalChecked(); args.GetReturnValue().Set(buffer); } @@ -1920,10 +1920,8 @@ void Initialize(Local target, SetMethod(context, target, "getaddrinfo", GetAddrInfo); SetMethod(context, target, "getnameinfo", GetNameInfo); SetMethodNoSideEffect(context, target, "canonicalizeIP", CanonicalizeIP); - SetMethodNoSideEffect(context, - target, - "convertIpv6StringToBuffer", - ConvertIpv6StringToBuffer); + SetMethodNoSideEffect( + context, target, "convertIpv6StringToBuffer", ConvertIpv6StringToBuffer); SetMethod(context, target, "strerror", StrError);