Skip to content
Merged
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 }) {
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);
}
});
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.
net.createServer().listen(0, 'localhost', common.mustNotCall()).close();
78 changes: 78 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,78 @@
'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');
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.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();
}));
}