Skip to content

Commit

Permalink
fix(ext/node): tls.connect regression (#27707)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
kt3k authored Jan 17, 2025
1 parent 342ccbb commit b55451b
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 6 deletions.
9 changes: 3 additions & 6 deletions ext/node/polyfills/_tls_wrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class TLSSocket extends net.Socket {
ssl: any;

_start() {
this[kHandle].afterConnect();
this[kHandle].afterConnectTls();
}

constructor(socket: any, opts: any = kEmptyObject) {
Expand Down Expand Up @@ -150,16 +150,14 @@ export class TLSSocket extends net.Socket {

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

// Patches `afterConnect` hook to replace TCP conn with TLS conn
const afterConnect = handle.afterConnect;
handle.afterConnect = async (req: any, status: number) => {
// Set `afterConnectTls` hook. This is called in the `afterConnect` method of net.Socket
handle.afterConnectTls = async () => {
options.hostname ??= undefined; // coerce to undefined if null, startTls expects hostname to be undefined
if (tlssock._needsSockInitWorkaround) {
// skips the TLS handshake for @npmcli/agent as it's handled by
// onSocket handler of ClientRequest object.
tlssock.emit("secure");
tlssock.removeListener("end", onConnectEnd);
return afterConnect.call(handle, req, status);
}

try {
Expand Down Expand Up @@ -190,7 +188,6 @@ export class TLSSocket extends net.Socket {
} catch {
// TODO(kt3k): Handle this
}
return afterConnect.call(handle, req, status);
};

handle.upgrading = promise;
Expand Down
6 changes: 6 additions & 0 deletions ext/node/polyfills/net.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,12 @@ function _afterConnect(
socket.emit("connect");
socket.emit("ready");

// Deno specific: run tls handshake if it's from a tls socket
// This swaps the handle[kStreamBaseField] from TcpConn to TlsConn
if (typeof handle.afterConnectTls === "function") {
handle.afterConnectTls();
}

// Start the first read, or get an immediate EOF.
// this doesn't actually consume any bytes, because len=0.
if (readable && !socket.isPaused()) {
Expand Down
13 changes: 13 additions & 0 deletions tests/unit_node/tls_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { fromFileUrl, join } from "@std/path";
import * as tls from "node:tls";
import * as net from "node:net";
import * as stream from "node:stream";
import { text } from "node:stream/consumers";
import { execCode } from "../unit/test_util.ts";

const tlsTestdataDir = fromFileUrl(
Expand Down Expand Up @@ -97,6 +98,18 @@ Connection: close
assertEquals(bodyText, "hello");
});

// Regression at https://github.com/denoland/deno/issues/27652
Deno.test("tls.connect makes tls connection to example.com", async () => {
const socket = tls.connect(443, "example.com");
await new Promise((resolve) => {
socket.on("secureConnect", resolve);
});
socket.write(
"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n",
);
assertStringIncludes(await text(socket), "<title>Example Domain</title>");
});

// https://github.com/denoland/deno/pull/20120
Deno.test("tls.connect mid-read tcp->tls upgrade", async () => {
const { promise, resolve } = Promise.withResolvers<void>();
Expand Down

0 comments on commit b55451b

Please sign in to comment.