mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
fix(client-certificates): improve close handling from target and proxy (#32158)
This commit is contained in:
parent
aac3a84321
commit
a1d32d997c
@ -79,20 +79,32 @@ class SocksProxyConnection {
|
|||||||
target!: net.Socket;
|
target!: net.Socket;
|
||||||
// In case of http, we just pipe data to the target socket and they are |undefined|.
|
// In case of http, we just pipe data to the target socket and they are |undefined|.
|
||||||
internal: stream.Duplex | undefined;
|
internal: stream.Duplex | undefined;
|
||||||
|
internalTLS: tls.TLSSocket | undefined;
|
||||||
private _targetCloseEventListener: () => void;
|
private _targetCloseEventListener: () => void;
|
||||||
|
private _dummyServer: tls.Server | undefined;
|
||||||
|
private _closed = false;
|
||||||
|
|
||||||
constructor(socksProxy: ClientCertificatesProxy, uid: string, host: string, port: number) {
|
constructor(socksProxy: ClientCertificatesProxy, uid: string, host: string, port: number) {
|
||||||
this.socksProxy = socksProxy;
|
this.socksProxy = socksProxy;
|
||||||
this.uid = uid;
|
this.uid = uid;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this._targetCloseEventListener = () => this.socksProxy._socksProxy.sendSocketEnd({ uid: this.uid });
|
this._targetCloseEventListener = () => {
|
||||||
|
// Close the other end and cleanup TLS resources.
|
||||||
|
this.socksProxy._socksProxy.sendSocketEnd({ uid: this.uid });
|
||||||
|
this.internalTLS?.destroy();
|
||||||
|
this._dummyServer?.close();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect() {
|
async connect() {
|
||||||
this.target = await createSocket(rewriteToLocalhostIfNeeded(this.host), this.port);
|
this.target = await createSocket(rewriteToLocalhostIfNeeded(this.host), this.port);
|
||||||
this.target.once('close', this._targetCloseEventListener);
|
this.target.once('close', this._targetCloseEventListener);
|
||||||
this.target.once('error', error => this.socksProxy._socksProxy.sendSocketError({ uid: this.uid, error: error.message }));
|
this.target.once('error', error => this.socksProxy._socksProxy.sendSocketError({ uid: this.uid, error: error.message }));
|
||||||
|
if (this._closed) {
|
||||||
|
this.target.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.socksProxy._socksProxy.socketConnected({
|
this.socksProxy._socksProxy.socketConnected({
|
||||||
uid: this.uid,
|
uid: this.uid,
|
||||||
host: this.target.localAddress!,
|
host: this.target.localAddress!,
|
||||||
@ -101,8 +113,11 @@ class SocksProxyConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onClose() {
|
public onClose() {
|
||||||
this.internal?.destroy();
|
// Close the other end and cleanup TLS resources.
|
||||||
this.target.destroy();
|
this.target.destroy();
|
||||||
|
this.internalTLS?.destroy();
|
||||||
|
this._dummyServer?.close();
|
||||||
|
this._closed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onData(data: Buffer) {
|
public onData(data: Buffer) {
|
||||||
@ -132,20 +147,18 @@ class SocksProxyConnection {
|
|||||||
});
|
});
|
||||||
this.socksProxy.alpnCache.get(rewriteToLocalhostIfNeeded(this.host), this.port, alpnProtocolChosenByServer => {
|
this.socksProxy.alpnCache.get(rewriteToLocalhostIfNeeded(this.host), this.port, alpnProtocolChosenByServer => {
|
||||||
debugLogger.log('client-certificates', `Proxy->Target ${this.host}:${this.port} chooses ALPN ${alpnProtocolChosenByServer}`);
|
debugLogger.log('client-certificates', `Proxy->Target ${this.host}:${this.port} chooses ALPN ${alpnProtocolChosenByServer}`);
|
||||||
const dummyServer = tls.createServer({
|
if (this._closed)
|
||||||
|
return;
|
||||||
|
this._dummyServer = tls.createServer({
|
||||||
...dummyServerTlsOptions,
|
...dummyServerTlsOptions,
|
||||||
ALPNProtocols: alpnProtocolChosenByServer === 'h2' ? ['h2', 'http/1.1'] : ['http/1.1'],
|
ALPNProtocols: alpnProtocolChosenByServer === 'h2' ? ['h2', 'http/1.1'] : ['http/1.1'],
|
||||||
});
|
});
|
||||||
this.internal?.once('close', () => dummyServer.close());
|
this._dummyServer.emit('connection', this.internal);
|
||||||
dummyServer.emit('connection', this.internal);
|
this._dummyServer.once('secureConnection', internalTLS => {
|
||||||
dummyServer.once('secureConnection', internalTLS => {
|
this.internalTLS = internalTLS;
|
||||||
debugLogger.log('client-certificates', `Browser->Proxy ${this.host}:${this.port} chooses ALPN ${internalTLS.alpnProtocol}`);
|
debugLogger.log('client-certificates', `Browser->Proxy ${this.host}:${this.port} chooses ALPN ${internalTLS.alpnProtocol}`);
|
||||||
|
|
||||||
let targetTLS: tls.TLSSocket | undefined = undefined;
|
let targetTLS: tls.TLSSocket | undefined = undefined;
|
||||||
const closeBothSockets = () => {
|
|
||||||
internalTLS.end();
|
|
||||||
targetTLS?.end();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleError = (error: Error) => {
|
const handleError = (error: Error) => {
|
||||||
error = rewriteOpenSSLErrorIfNeeded(error);
|
error = rewriteOpenSSLErrorIfNeeded(error);
|
||||||
@ -156,7 +169,8 @@ class SocksProxyConnection {
|
|||||||
// This method is available only in Node.js 20+
|
// This method is available only in Node.js 20+
|
||||||
if ('performServerHandshake' in http2) {
|
if ('performServerHandshake' in http2) {
|
||||||
// In case of an 'error' event on the target connection, we still need to perform the http2 handshake on the browser side.
|
// In case of an 'error' event on the target connection, we still need to perform the http2 handshake on the browser side.
|
||||||
// This is an async operation, so we need to intercept the close event to prevent the socket from being closed too early.
|
// This is an async operation, so we need to remove the listener to prevent the socket from being closed too early.
|
||||||
|
// This means we call this._targetCloseEventListener manually.
|
||||||
this.target.removeListener('close', this._targetCloseEventListener);
|
this.target.removeListener('close', this._targetCloseEventListener);
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const session: http2.ServerHttp2Session = http2.performServerHandshake(internalTLS);
|
const session: http2.ServerHttp2Session = http2.performServerHandshake(internalTLS);
|
||||||
@ -165,14 +179,16 @@ class SocksProxyConnection {
|
|||||||
'content-type': 'text/html',
|
'content-type': 'text/html',
|
||||||
[http2.constants.HTTP2_HEADER_STATUS]: 503,
|
[http2.constants.HTTP2_HEADER_STATUS]: 503,
|
||||||
});
|
});
|
||||||
stream.end(responseBody, () => {
|
const cleanup = () => {
|
||||||
session.close();
|
session.close();
|
||||||
closeBothSockets();
|
this.target.destroy();
|
||||||
});
|
this._targetCloseEventListener();
|
||||||
stream.once('error', () => closeBothSockets());
|
};
|
||||||
|
stream.end(responseBody, cleanup);
|
||||||
|
stream.once('error', cleanup);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
closeBothSockets();
|
this.target.destroy();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
internalTLS.end([
|
internalTLS.end([
|
||||||
@ -182,7 +198,7 @@ class SocksProxyConnection {
|
|||||||
'',
|
'',
|
||||||
responseBody,
|
responseBody,
|
||||||
].join('\r\n'));
|
].join('\r\n'));
|
||||||
closeBothSockets();
|
this.target.destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -194,7 +210,11 @@ class SocksProxyConnection {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tlsOptions: tls.ConnectionOptions = {
|
if (this._closed) {
|
||||||
|
internalTLS.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
targetTLS = tls.connect({
|
||||||
socket: this.target,
|
socket: this.target,
|
||||||
host: this.host,
|
host: this.host,
|
||||||
port: this.port,
|
port: this.port,
|
||||||
@ -202,18 +222,14 @@ class SocksProxyConnection {
|
|||||||
ALPNProtocols: [internalTLS.alpnProtocol || 'http/1.1'],
|
ALPNProtocols: [internalTLS.alpnProtocol || 'http/1.1'],
|
||||||
servername: !net.isIP(this.host) ? this.host : undefined,
|
servername: !net.isIP(this.host) ? this.host : undefined,
|
||||||
secureContext,
|
secureContext,
|
||||||
};
|
});
|
||||||
|
|
||||||
targetTLS = tls.connect(tlsOptions);
|
|
||||||
|
|
||||||
targetTLS.once('secureConnect', () => {
|
targetTLS.once('secureConnect', () => {
|
||||||
internalTLS.pipe(targetTLS);
|
internalTLS.pipe(targetTLS);
|
||||||
targetTLS.pipe(internalTLS);
|
targetTLS.pipe(internalTLS);
|
||||||
});
|
});
|
||||||
|
|
||||||
internalTLS.once('close', () => closeBothSockets());
|
internalTLS.once('error', () => this.target.destroy());
|
||||||
|
|
||||||
internalTLS.once('error', () => closeBothSockets());
|
|
||||||
targetTLS.once('error', handleError);
|
targetTLS.once('error', handleError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user