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.createConnection does not have a connection timeout. #39256

Open
Gcaufy opened this issue Jul 4, 2021 · 3 comments
Open

net.createConnection does not have a connection timeout. #39256

Gcaufy opened this issue Jul 4, 2021 · 3 comments
Labels
net Issues and PRs related to the net subsystem.

Comments

@Gcaufy
Copy link

Gcaufy commented Jul 4, 2021

I was trying to troubleshoot some TCP connect issue that the TCP server doesn't reply a ACK or RST.

So I write a simple test code to connect a port which doesn't have any service.

const net = require('net');

const SocketTimeout = 1000;
const StartTime = Date.now();

function print(msg, ...args) {
  const cost = Date.now() - StartTime;
  console.log(`[+${cost}ms]`, msg, `with ${args.length} args`, args);
}

const opt = { host: '127.0.0.1', port: 12345 };

const conn = net.createConnection(opt, () =>  print('create connection'));
conn.setTimeout(1000);

print('Socket Timeout: ' + SocketTimeout);
conn.on('connect', (...args) => print('$connect$', ...args));
conn.on('timeout', (...args) => print('$timeout$', ...args));
conn.on('error', (e) => print('$error$', e));
conn.on('close', (...args) => print('$close$', ...args));
conn.on('end', (...args) => print('$end$', ...args));

Normally, it will return an ECONNREFUSED error which is expected (because TCP will recevie a RST packet). Like this:

[+4ms] Socket Timeout: 1000 with 0 args []
[+11ms] $error$ with 1 args [ { Error: connect ECONNREFUSED 127.0.0.1:12345
      at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1104:14)
    errno: 'ECONNREFUSED',
    code: 'ECONNREFUSED',
    syscall: 'connect',
    address: '127.0.0.1',
    port: 12345 } ]
[+15ms] $close$ with 1 args [ true ]

I'm trying to represent the situation that a TCP server doesn't reply a RST.

So I changed the iptables rules, like this:

iptables -I OUTPUT -p tcp --tcp-flags ALL RST,ACK -j DROP

and run the code again:

[+3ms] Socket Timeout: 1000 with 0 args []
[+1004ms] $timeout$ with 0 args []
[+132620ms] $error$ with 1 args [ { Error: connect ETIMEDOUT 127.0.0.1:12345
      at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1107:14)
    errno: 'ETIMEDOUT',
    code: 'ETIMEDOUT',
    syscall: 'connect',
    address: '127.0.0.1',
    port: 12345 } ]
[+132730ms] $close$ with 1 args [ true ]

timeout event was emitted in 1000ms as expected, but error event was emitted in 132620ms.

and here is the tcpdump logs:

20:41:34.746042 IP 127.0.0.1.47230 > 127.0.0.1.12345: Flags [S], seq 2918343672, win 43690, options [mss 65495,sackOK,TS val 4291343189 ecr 0,nop,wscale 7], length 0
20:41:35.764631 IP 127.0.0.1.47230 > 127.0.0.1.12345: Flags [S], seq 2918343672, win 43690, options [mss 65495,sackOK,TS val 4291344208 ecr 0,nop,wscale 7], length 0
20:41:37.780633 IP 127.0.0.1.47230 > 127.0.0.1.12345: Flags [S], seq 2918343672, win 43690, options [mss 65495,sackOK,TS val 4291346224 ecr 0,nop,wscale 7], length 0
20:41:41.908635 IP 127.0.0.1.47230 > 127.0.0.1.12345: Flags [S], seq 2918343672, win 43690, options [mss 65495,sackOK,TS val 4291350352 ecr 0,nop,wscale 7], length 0
20:41:50.100621 IP 127.0.0.1.47230 > 127.0.0.1.12345: Flags [S], seq 2918343672, win 43690, options [mss 65495,sackOK,TS val 4291358544 ecr 0,nop,wscale 7], length 0
20:42:06.228631 IP 127.0.0.1.47230 > 127.0.0.1.12345: Flags [S], seq 2918343672, win 43690, options [mss 65495,sackOK,TS val 4291374672 ecr 0,nop,wscale 7], length 0
20:42:40.020636 IP 127.0.0.1.47230 > 127.0.0.1.12345: Flags [S], seq 2918343672, win 43690, options [mss 65495,sackOK,TS val 4291408464 ecr 0,nop,wscale 7], length 0

So basically, there is no way to separate connection timeout and the socket timeout.

Ref: #5757

@mscdex
Copy link
Contributor

mscdex commented Jul 4, 2021

I'm not sure what you're asking for. socket.setTimeout() is for socket inactivity in general and not specifically for connection timeouts. ETIMEDOUT errors come from the OS and there is no way to change that timeout, except possibly at the OS layer (e.g. by changing kernel runtime settings), so there is nothing that node can do about that.

@Gcaufy
Copy link
Author

Gcaufy commented Jul 4, 2021

I found there are some way to it like this:


const conn = net.createConnection(opt, () => {
  clearTimeout(connectionTimer);
})
const connectionTimer = setTimeout(() => {
  conn.emit('connectionTimeout');
  conn.destroy();
}, 200);

but is that possible to support it natively? like this:

net.setConnectionTimeout(200);
net.createConnection(opt)
.on('connectionTimeout, () => {
   ...
})

@lpinca lpinca added the net Issues and PRs related to the net subsystem. label Jul 5, 2021
@Antonius-S
Copy link

Antonius-S commented Jul 21, 2021

Timeout that is set during connection will work as it should.

const sock = require('net').connect(123, 'google.com');
sock.setTimeout(5000); sock.on('timeout', ()=>{console.log('timeout');});

prints timeout after 5 secs and somewhat later

Error: connect ETIMEDOUT 64.233.165.138:123
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1148:16)
    at TCPConnectWrap.callbackTrampoline (internal/async_hooks.js:131:17) {

(Windows has built-in 20 sec connect timeout).
Not raising errors is documented:

When an idle timeout is triggered the socket will receive a 'timeout' event but the connection will not be severed. The user must manually call socket.end() or socket.destroy() to end the connection.

So what's the problem? If you want error on timeout,

/** Error code of timeout error */
const ETIMEDOUT = 'ETIMEDOUT';

/**
  Create and return Error object signaling connect or idle timeout
    @param {Boolean} connecting - if `true`, error is in connect stage. If `false`, error is in idle stage
    @param {String} addr - host:port or IPC path

    @returns {Error}
 */
function createETIMEDOUT(connecting, addr)
{
  const STAGE = { false: 'idle', true: 'connect' };

  const result = new Error(`${STAGE[connecting]} ${ETIMEDOUT} ${addr}`);
  result.errno = ETIMEDOUT;
  result.code = ETIMEDOUT;
  result.syscall = STAGE[connecting];
  result.address = addr;
  return result;
}

sock.on('timeout', ()=>throw createETIMEDOUT(sock.connecting, addr));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
net Issues and PRs related to the net subsystem.
Projects
None yet
Development

No branches or pull requests

4 participants