chore: use happy eyeballs for client-certificates (#31859)

This commit is contained in:
Max Schmitt 2024-07-25 18:55:47 +02:00 committed by GitHub
parent d3e2c6cbb2
commit 0c6ecf8df4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 45 additions and 11 deletions

View File

@ -20,7 +20,7 @@ import type https from 'https';
import fs from 'fs';
import tls from 'tls';
import stream from 'stream';
import { createSocket } from '../utils/happy-eyeballs';
import { createSocket, createTLSSocket } from '../utils/happy-eyeballs';
import { isUnderTest, ManualPromise } from '../utils';
import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../common/socksProxy';
import { SocksProxy } from '../common/socksProxy';
@ -42,22 +42,21 @@ class ALPNCache {
const result = new ManualPromise<string>();
this._cache.set(cacheKey, result);
result.then(success);
const socket = tls.connect({
createTLSSocket({
host,
port,
servername: net.isIP(host) ? undefined : host,
ALPNProtocols: ['h2', 'http/1.1'],
rejectUnauthorized: false,
});
socket.on('secureConnect', () => {
// The server may not respond with ALPN, in which case we default to http/1.1.
result.resolve(socket.alpnProtocol || 'http/1.1');
socket.end();
});
socket.on('error', error => {
}).then(socket => {
socket.on('secureConnect', () => {
// The server may not respond with ALPN, in which case we default to http/1.1.
result.resolve(socket.alpnProtocol || 'http/1.1');
socket.end();
});
}).catch(error => {
debugLogger.log('client-certificates', `ALPN error: ${error.message}`);
result.resolve('http/1.1');
socket.end();
});
}
}

View File

@ -20,6 +20,7 @@ import * as https from 'https';
import * as net from 'net';
import * as tls from 'tls';
import { ManualPromise } from './manualPromise';
import { assert } from './debug';
// Implementation(partial) of Happy Eyeballs 2 algorithm described in
// https://www.rfc-editor.org/rfc/rfc8305
@ -66,7 +67,41 @@ export async function createSocket(host: string, port: number): Promise<net.Sock
});
}
async function createConnectionAsync(options: http.ClientRequestArgs, oncreate: ((err: Error | null, socket?: net.Socket) => void) | undefined, useTLS: boolean) {
export async function createTLSSocket(options: tls.ConnectionOptions): Promise<tls.TLSSocket> {
return new Promise((resolve, reject) => {
assert(options.host, 'host is required');
if (net.isIP(options.host)) {
const socket = tls.connect(options)
socket.on('connect', () => resolve(socket));
socket.on('error', error => reject(error));
} else {
createConnectionAsync(options, (err, socket) => {
if (err)
reject(err);
if (socket)
resolve(socket);
}, true).catch(err => reject(err));
}
});
}
export async function createConnectionAsync(
options: http.ClientRequestArgs,
oncreate: ((err: Error | null, socket?: tls.TLSSocket) => void) | undefined,
useTLS: true
): Promise<void>;
export async function createConnectionAsync(
options: http.ClientRequestArgs,
oncreate: ((err: Error | null, socket?: net.Socket) => void) | undefined,
useTLS: false
): Promise<void>;
export async function createConnectionAsync(
options: http.ClientRequestArgs,
oncreate: ((err: Error | null, socket?: any) => void) | undefined,
useTLS: boolean
): Promise<void> {
const lookup = (options as any).__testHookLookup || lookupAddresses;
const hostname = clientRequestArgsToHostName(options);
const addresses = await lookup(hostname);