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

tls.connect from node:tls errors with ECONNRESET after sending data #27652

Closed
magurotuna opened this issue Jan 13, 2025 · 0 comments · Fixed by #27707
Closed

tls.connect from node:tls errors with ECONNRESET after sending data #27652

magurotuna opened this issue Jan 13, 2025 · 0 comments · Fixed by #27707
Assignees

Comments

@magurotuna
Copy link
Member

Description

TLS connection established with tls.connect from node:tls errors out with ECONNRESET after sending data to the server.

Reproducible code snippet

import * as tls from 'node:tls';

function connectToExampleCom() {
  const { promise, resolve, reject } = Promise.withResolvers();

  const options = {
    host: 'example.com',
    port: 443,
  };

  const client = tls.connect(options, () => {
    console.log('Connected to server');

    // Send an HTTP GET request
    client.write('GET / HTTP/1.1\r\n');
    client.write('Host: example.com\r\n');
    client.write('Connection: close\r\n\r\n');
  });

  let resp = '';
  client.on('data', (data) => {
    resp += data.toString();
  });

  client.on('end', () => {
    console.log('Connection closed by server');
    resolve(resp);
  });

  client.on('error', (err) => {
    reject(err);
  });

  return promise;
}

const resp = await connectToExampleCom();
console.log(resp);

Output

$ deno run --allow-net index.mjs
Connected to server
error: Uncaught (in promise) Error: read ECONNRESET
    at __node_internal_captureLargerStackTrace (ext:deno_node/internal/errors.ts:93:9)
    at __node_internal_errnoException (ext:deno_node/internal/errors.ts:141:10)
    at TCP.onStreamRead (ext:deno_node/internal/stream_base_commons.ts:209:20)
    at TCP.#read (ext:deno_node/internal_binding/stream_wrap.ts:252:12)
    at eventLoopTick (ext:core/01_core.js:175:7)

Version

deno 2.1.5 (stable, release, aarch64-apple-darwin)
v8 13.0.245.12-rusty
typescript 5.6.2

Additonal info

I did git bisect and found out that this issue seems to have started at c3c2b37 (which is included in v2.1.0).

Both Node.js (v22.11.0) and Bun (v1.1.42) work fine. Here's the result of Node.js:

$ node index.mjs
Connected to server
Connection closed by server
HTTP/1.1 200 OK
Accept-Ranges: bytes
Age: 499485
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Mon, 13 Jan 2025 14:33:12 GMT
Etag: "3147526947"
Expires: Mon, 20 Jan 2025 14:33:12 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECAcc (lac/55A7)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1256
Connection: close

<!doctype html>
<html>
<head>
    <title>Example Domain</title>

    <meta charset="utf-8" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    ... omitted ...
</head>

<body>
<div>
    <h1>Example Domain</h1>
    <p>This domain is for use in illustrative examples in documents. You may use this
    domain in literature without prior coordination or asking for permission.</p>
    <p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
@kt3k kt3k self-assigned this Jan 17, 2025
@kt3k kt3k closed this as completed in b55451b Jan 17, 2025
bartlomieju pushed a commit to bartlomieju/deno that referenced this issue Jan 17, 2025
The TLS start sequence has been broken since denoland#26661 because of the way
how we wrap TCP handle to create TLS handle.

denoland#26661 introduced happy-eyeballs algorithm and some connection could be
dropped because of happy-eyeball attempt timeout. The current
implementation doesn't consider that case and it could start TLS
handshake with timed out TCP connection. That caused denoland#27652 .

This PR fixes it by changing the initialization steps. Now `wrapHandle`
of TLSSocket set up `afterConnectTls` callback in TCP handle, and
`afterConnect` of TCP handle calls it at `connect` event timing if it
exists. This avoids starting TLS session with timed out connection.

closes denoland#27652
crowlKats pushed a commit that referenced this issue Jan 21, 2025
The TLS start sequence has been broken since #26661 because of the way
how we wrap TCP handle to create TLS handle.

#26661 introduced happy-eyeballs algorithm and some connection could be
dropped because of happy-eyeball attempt timeout. The current
implementation doesn't consider that case and it could start TLS
handshake with timed out TCP connection. That caused #27652 .

This PR fixes it by changing the initialization steps. Now `wrapHandle`
of TLSSocket set up `afterConnectTls` callback in TCP handle, and
`afterConnect` of TCP handle calls it at `connect` event timing if it
exists. This avoids starting TLS session with timed out connection.

closes #27652

(cherry picked from commit b55451b)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants
@kt3k @lucacasonato @magurotuna and others