Skip to content

Timers headersTimeout and requestTimeout not restarted on keep-alive HTTP connections #58140

Open
@janakj

Description

@janakj

Version

v23.10.0

Platform

Tested on Linux and macOS:

Linux rimmer 6.1.0-18-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.76-1 (2024-02-01) x86_64 GNU/Linux
----
Darwin holly 24.4.0 Darwin Kernel Version 24.4.0: Wed Mar 19 21:17:25 PDT 2025; root:xnu-11417.101.15~1/RELEASE_ARM64_T6020 arm64

Subsystem

http

What steps will reproduce the bug?

Create a minimal Node HTTP server with configured headersTimeout, requestTimeout, and keepAliveTimeout timers:

import { createServer } from 'node:http';

const server = createServer({
  connectionsCheckingInterval: 100,
  headersTimeout: 1000,
  requestTimeout: 4000,
  keepAliveTimeout: 5000
}, (req, res) => {
  res.writeHead(404);
  res.end();
});

server.listen(3000);

Send an HTTP request followed by an empty line (CRLF) to the server with the following Python client:

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('localhost', 3000))

# Send a minimal HTTP request to the server
client.send(b'GET / HTTP/1.1\r\nHost: localhost:3000\r\nContent-Length: 0\r\n\r\n')

# Print received responses until the server closes the connection
while True:
  data = client.recv(1024 * 1024)
  if data == b'':  # Server closed connection
    return
  print(data)
  # Send an empty line (CRLF)
  client.send(b'\r\n')

The Python client never terminates because the server never closes the connection, despite having set all three timers (headersTimeout, requestTimeout, 'keepAliveTimeout`).

The empty line cancels the keepAliveTimeout timer but does not start either the headersTimeout or requestTimeout timers. The server will keep the connection open forever.

A malicious client could force the server to keep the connection open indefinitely.

How often does it reproduce? Is there a required condition?

Every time. I believe this is a design flaw in NodeJS.

What is the expected behavior? Why is that the expected behavior?

If the NodeJS server has all the connection-related timers set, it should never be possible for the connection to transition into a state where none of the timers are in effect. This is what seems to happen when the client sends a CRLF after the first request on a connection. What I think happens is that NodeJS stops the keepAliveTimeout timer upon receiving the CRLF, but does not start headersTimeout and requestTimeout timers until the first line of the next message arrives from the client.

I think NodeJS should NOT stop the keepAliveTimeout timer until it really receives the beginning of the following HTTP request. In other words, receiving just CRLF (empty line) should not stop the timer.

An alternative fix would be to keep the keepAliveTimeout as it is, and instead start the headersTimeout and requestTimeout timers upon receiving ANY communication from the client, including CRLF.

In any case, headerTimeout and requestTimeout timers should be started whenever the keepAliveTimeout is stopped. This prevents clients from tricking the server into a timer-less connection state.

What do you see instead?

When the attached Python client is run against the attached trivial NodeJS server, the Python client never terminates, despite the server having all connection timers configured.

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    httpIssues or PRs related to the http subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions