Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.js 16 -> 17 changed resolution of "localhost"?

Under node.js 16.13.1 (Windows), code like this worked (assume a server exists and works fine):

import net from 'net';

let socket = net.createConnection({
    host: 'localhost',
    port: 12345
})

After updating to node.js 17.7.2, the above now throws ECONNREFUSED (connection refused) errors. However, it works fine with '0.0.0.0' instead of 'localhost'.

In fact, even the documented default of "localhost" (thanks VLAZ) fails on 17.7.2:

// connects with 16.13.1, ECONNREFUSED with 17.7.2:
net.createConnection({port:12345});

// connects with both versions:
net.createConnection({port:12345,host:'0.0.0.0'});

// connects with 16.13.1, ECONNREFUSED with 17.7.2:
net.createConnection({port:12345,host:'localhost'});

I've confirmed that the behavior depends on the version of node. I can reliably avoid / reproduce the behavior at will by switching node versions, using any code that establishes a network connection.

I have a fair amount of code with hosts that default to "localhost". I'd rather not update all of that to "0.0.0.0" if I don't have to. More tedious is that often the user provides the host address and expects "localhost" to work, so now I have to add logic that converts "localhost" to "0.0.0.0" every time I create a socket with a user-specified host address. I mean, I'll do what I have to do, but this kind of stinks.

My question is: What happened between 16.13.1 and 17.7.2 that made "localhost" not useable any more? Is it a bug, or some intentional change? And, is there a way to make it work again or do I have to find+replace "localhost" with "0.0.0.0" everywhere now?

I suspect a lot of the "connection refused to localhost" errors that people ask about on the internet are related to whatever changed...


Update: It seems to have something to do with changes in how interfaces are enumerated (or something like that) in 17, rather than the specific use of "localhost".

For example, with the following test setup:

  • Three TCP servers created with node: one for the default address, one with "0.0.0.0" explicitly set, and one with "localhost" explicitly set.
  • Three clients attempting to connect to each server, one to default host, one to "0.0.0.0", one to "localhost".

Then with 16.13.1:

Listening on default Listening on 0.0.0.0 Listening on localhost
Connect to default OK OK OK
Connect to 0.0.0.0 OK OK OK
Connect to localhost OK OK OK

But with 17.7.2:

Listening on default Listening on 0.0.0.0 Listening on localhost
Connect to default OK ECONNREFUSED OK
Connect to 0.0.0.0 OK OK ECONNREFUSED
Connect to localhost OK ECONNREFUSED OK

Now that's with servers created with node; also same node version for servers and clients. In my original case, my server was created with C++ and the standard socket() API, bound to INADDR_ANY (0.0.0.0).

Test code:

import net from 'net';

console.log(process.version);

const accepted = detail => socket => socket.write(detail, ()=>socket.end());

const serversReady = () => [ 
    new Promise(resolve => net.createServer(accepted('default')).listen(12345, function(){resolve(this)})),
    new Promise(resolve => net.createServer(accepted('localhost')).listen(12346, 'localhost', function(){resolve(this)})),
    new Promise(resolve => net.createServer(accepted('0.0.0.0')).listen(12347, '0.0.0.0', function(){resolve(this)}))
];

const ports = [[12345,'default'], [12346,'localhost'], [12347,'0.0.0.0']];
const hosts = [{}, {host:'localhost'}, {host:'0.0.0.0'}];

const clientsDone = () => ports.map(([port,whichserver]) => hosts.map(host => new Promise((resolve, reject) => {
    let opts = {...host, port:port};
    net.createConnection(opts)
        .on('error', e => (console.log(opts, 'to:'+whichserver, 'error', e.message), reject(e)))
        .on('data', d => console.log(opts, 'to:'+whichserver, 'read', d.toString()))
        .on('end', () => resolve());
}))).flat();

Promise.all(serversReady())
    .then(servers => Promise.allSettled(clientsDone()).then(() => servers))
    .then(servers => servers.forEach(s => s.close()));

More on this after work but figured I'd post this little bit sooner rather than later. Will check 18 too.

like image 998
Jason C Avatar asked Feb 28 '26 00:02

Jason C


1 Answers

I quote answer from @richardlau

Node.js no longer re-sorts results of IP address lookups and returns them as-is (i.e. it no longer ignores how your OS has been configured). You can change the behaviour via the verbatim option to dns.lookup() or set the --dns-result-order command line option to change the default.

You could try:

ping localhost

in you cmd or terminal. If it shows:

Reply from 127.0.0.1

it means your system prefer ipv4 address to resolve localhost. If it shows:

Reply from ::1

it means your system prefer ipv6 address.

You could change system setting to prefer ipv4. Node.js will resolve localhost to 127.0.0.1 as you expect.

like image 75
shuyan Avatar answered Mar 02 '26 14:03

shuyan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!