1

He is a bit of code where a server is created to listen on port 2222:

import { createServer } from 'net';

const server = createServer((c) => {
  c.setEncoding('utf8');
  c.on('data', (data) => {
    console.log('server', data);
    c.write(data);
  });
  c.on('error', (e) => { throw e; });
});
server.listen(2222);

and the code to create a connection to the server to send a simple 'hello' that the server will respond back to. After 2 seconds, the socket gets destroyed.

import { createConnection } from 'net';

const socket = createConnection({ localPort: 9999, port: 2222, host: 'localhost' });
socket.setEncoding('utf8');
socket.on('data', (data) => {
  console.log('socket data', data);
});
socket.on('connect', () => {
  socket.write('hello');
});
socket.setTimeout(2000);
socket.on('timeout', () => { socket.destroy(); console.log('destroyed'); });
socket.on('error', (e) => { throw e; });

This code works well the first time it is called.

It will fail on subsequent calls with:

Error: connect EADDRINUSE 127.0.0.1:2222 - Local (0.0.0.0:9999)
  errno: -48,
  code: 'EADDRINUSE',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 2222

It took me a while to figure it out, but the problem comes from trying to bind the socket on an outbound port: localPort: 9999. Without specifying this parameter, the OS will select a free port, and the program won't crash.

When specifying it, a cooldown of ~15s is required before the socket being re-usable again.

  • Is there a way to properly destroy the socket so that it becomes immediately available again?
  • If not, is there a way to verify that the socket is "cooling down", but will eventually be available again? I'd like to distinguish the case where I just have to wait from the one where the socket has been actively taken by another process, and won't be released to the pool of free sockets.

Any theory on why the socket is not available after the program exists is welcome!

qnilab
  • 380
  • 3
  • 18

2 Answers2

1

Why are you specifying a local port in the first place? You almost never want to do that. If you don't, it'll work.

Any theory on why the socket is not available after the program exists is welcome!

The OS keeps the port in use for a while to be able to receive packets and tell the sender that the socket is gone.

Is there a way to properly destroy the socket so that it becomes immediately available again?

You can set the "linger" socket option to 0, so it'll become available immediately again, but again, you shouldn't get yourself in this situation in the first time. Consider whether you want to specify the local port. You usually don't.

is there a way to verify that the socket is "cooling down"

It'll be in the CLOSE_WAIT state.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • Thanks, the resources are helpful! It's a test setup to understand how sockets work. I'm working on a pet project establishing port forwarding between two clients behind different NATs. For TCP hole punching to work, I need to re-use the same outbound port to contact the peer as the one used to contact the server, hence the specification of the localPort on the new socket instance to the peer. If I can keep the very same instance as the one used to contact the server in the first place, maybe I wouldn't need to specify the localPort (unsure if it's kept between 2 connect() calls). I'll try! – qnilab Aug 06 '22 at 16:47
1

The socket is going into a CLOSE_WAIT state, so you have to wait until it is available again before you can reconnect. You can try to avoid this by:

Removing the source socket and letting the platform pick a random ephemeral one for you (as you have already found out).

Closing the connection at the server end first (so the CLOSE_WAIT ends up there). See server.close();

Resetting the client connection. See socket.resetAndDestroy()

Buffoonism
  • 1,669
  • 11
  • 11
  • 1
    No, that will keep it in the TIME_WAIT state like OP encountered. – CodeCaster Aug 05 '22 at 12:39
  • AIUI, closing it at the server end should leave the server socket in the TIME_WAIT state, not the client. Node has the alternative of dropping the connection with a RST though, which is less pretty, but should avoid the TIME_WAIT and allow the connection to be reused. Revised the answer to match. – Buffoonism Aug 05 '22 at 12:48
  • Sorry, CLOSE_WAIT. – CodeCaster Aug 05 '22 at 12:49
  • That is helpful: calling close() on the server does not seem to properly close the connection, but rather calling end() on the socket on the server side seems to free it for later retries. In my TCP hole punching toy app, this means that once peers exchanged public and private addresses, the rendez-vous server should be the one closing the sockets. This way clients are free to retry using the outbound port they used to contact the server initially, after a cool down time maybe. – qnilab Aug 06 '22 at 17:45