Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net: exclude ipv6 loopback addresses from server.listen #54264

26 changes: 23 additions & 3 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const {
NumberParseInt,
ObjectDefineProperty,
ObjectSetPrototypeOf,
StringPrototypeStartsWith,
Symbol,
SymbolAsyncDispose,
SymbolDispose,
Expand Down Expand Up @@ -2118,19 +2119,38 @@ Server.prototype.listen = function(...args) {
throw new ERR_INVALID_ARG_VALUE('options', options);
};

function isIpv6Loopback({ address, family }) {
pimterry marked this conversation as resolved.
Show resolved Hide resolved
return family === 6 && StringPrototypeStartsWith(address, 'fe80::');
pimterry marked this conversation as resolved.
Show resolved Hide resolved
}

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,
pimterry marked this conversation as resolved.
Show resolved Hide resolved
backlog, undefined, exclusive, flags);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
puskin94 marked this conversation as resolved.
Show resolved Hide resolved
net.createServer().listen(0, 'localhost', common.mustNotCall()).close();
77 changes: 77 additions & 0 deletions test/sequential/test-net-server-listen-ipv6-loopback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'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();
}));
}