mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 04:52:26 -05:00
fix(ext/node): tls.connect regression (#27707)
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
This commit is contained in:
parent
342ccbb99d
commit
b55451b178
3 changed files with 22 additions and 6 deletions
|
@ -79,7 +79,7 @@ export class TLSSocket extends net.Socket {
|
||||||
ssl: any;
|
ssl: any;
|
||||||
|
|
||||||
_start() {
|
_start() {
|
||||||
this[kHandle].afterConnect();
|
this[kHandle].afterConnectTls();
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(socket: any, opts: any = kEmptyObject) {
|
constructor(socket: any, opts: any = kEmptyObject) {
|
||||||
|
@ -150,16 +150,14 @@ export class TLSSocket extends net.Socket {
|
||||||
|
|
||||||
const { promise, resolve } = Promise.withResolvers();
|
const { promise, resolve } = Promise.withResolvers();
|
||||||
|
|
||||||
// Patches `afterConnect` hook to replace TCP conn with TLS conn
|
// Set `afterConnectTls` hook. This is called in the `afterConnect` method of net.Socket
|
||||||
const afterConnect = handle.afterConnect;
|
handle.afterConnectTls = async () => {
|
||||||
handle.afterConnect = async (req: any, status: number) => {
|
|
||||||
options.hostname ??= undefined; // coerce to undefined if null, startTls expects hostname to be undefined
|
options.hostname ??= undefined; // coerce to undefined if null, startTls expects hostname to be undefined
|
||||||
if (tlssock._needsSockInitWorkaround) {
|
if (tlssock._needsSockInitWorkaround) {
|
||||||
// skips the TLS handshake for @npmcli/agent as it's handled by
|
// skips the TLS handshake for @npmcli/agent as it's handled by
|
||||||
// onSocket handler of ClientRequest object.
|
// onSocket handler of ClientRequest object.
|
||||||
tlssock.emit("secure");
|
tlssock.emit("secure");
|
||||||
tlssock.removeListener("end", onConnectEnd);
|
tlssock.removeListener("end", onConnectEnd);
|
||||||
return afterConnect.call(handle, req, status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -190,7 +188,6 @@ export class TLSSocket extends net.Socket {
|
||||||
} catch {
|
} catch {
|
||||||
// TODO(kt3k): Handle this
|
// TODO(kt3k): Handle this
|
||||||
}
|
}
|
||||||
return afterConnect.call(handle, req, status);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handle.upgrading = promise;
|
handle.upgrading = promise;
|
||||||
|
|
|
@ -378,6 +378,12 @@ function _afterConnect(
|
||||||
socket.emit("connect");
|
socket.emit("connect");
|
||||||
socket.emit("ready");
|
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.
|
// Start the first read, or get an immediate EOF.
|
||||||
// this doesn't actually consume any bytes, because len=0.
|
// this doesn't actually consume any bytes, because len=0.
|
||||||
if (readable && !socket.isPaused()) {
|
if (readable && !socket.isPaused()) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { fromFileUrl, join } from "@std/path";
|
||||||
import * as tls from "node:tls";
|
import * as tls from "node:tls";
|
||||||
import * as net from "node:net";
|
import * as net from "node:net";
|
||||||
import * as stream from "node:stream";
|
import * as stream from "node:stream";
|
||||||
|
import { text } from "node:stream/consumers";
|
||||||
import { execCode } from "../unit/test_util.ts";
|
import { execCode } from "../unit/test_util.ts";
|
||||||
|
|
||||||
const tlsTestdataDir = fromFileUrl(
|
const tlsTestdataDir = fromFileUrl(
|
||||||
|
@ -97,6 +98,18 @@ Connection: close
|
||||||
assertEquals(bodyText, "hello");
|
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
|
// https://github.com/denoland/deno/pull/20120
|
||||||
Deno.test("tls.connect mid-read tcp->tls upgrade", async () => {
|
Deno.test("tls.connect mid-read tcp->tls upgrade", async () => {
|
||||||
const { promise, resolve } = Promise.withResolvers<void>();
|
const { promise, resolve } = Promise.withResolvers<void>();
|
||||||
|
|
Loading…
Add table
Reference in a new issue