fix(socks): use happy-eyeballs to create a connection (#21847)
This commit is contained in:
parent
21e1c50bcd
commit
80a37ec171
|
|
@ -14,17 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import dns from 'dns';
|
||||
import EventEmitter from 'events';
|
||||
import type { AddressInfo } from 'net';
|
||||
import net from 'net';
|
||||
import util from 'util';
|
||||
import { debugLogger } from './debugLogger';
|
||||
import { createSocket } from '../utils/network';
|
||||
import { createSocket } from '../utils/happy-eyeballs';
|
||||
import { assert, createGuid, } from '../utils';
|
||||
|
||||
const dnsLookupAsync = util.promisify(dns.lookup);
|
||||
|
||||
// https://tools.ietf.org/html/rfc1928
|
||||
|
||||
enum SocksAuth {
|
||||
|
|
@ -412,9 +408,7 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
|
|||
|
||||
private async _handleDirect(request: SocksSocketRequestedPayload) {
|
||||
try {
|
||||
// TODO: Node.js 17 does resolve localhost to ipv6
|
||||
const { address } = await dnsLookupAsync(request.host === 'localhost' ? '127.0.0.1' : request.host);
|
||||
const socket = await createSocket(address, request.port);
|
||||
const socket = await createSocket(request.host, request.port);
|
||||
socket.on('data', data => this._connections.get(request.uid)?.sendData(data));
|
||||
socket.on('error', error => {
|
||||
this._connections.get(request.uid)?.error(error.message);
|
||||
|
|
@ -538,15 +532,11 @@ export class SocksProxyHandler extends EventEmitter {
|
|||
}
|
||||
|
||||
if (host === 'local.playwright')
|
||||
host = '127.0.0.1';
|
||||
// Node.js 17 does resolve localhost to ipv6
|
||||
if (host === 'localhost')
|
||||
host = '127.0.0.1';
|
||||
host = 'localhost';
|
||||
try {
|
||||
if (this._redirectPortForTest)
|
||||
port = this._redirectPortForTest;
|
||||
const { address } = await dnsLookupAsync(host);
|
||||
const socket = await createSocket(address, port);
|
||||
const socket = await createSocket(host, port);
|
||||
socket.on('data', data => {
|
||||
const payload: SocksSocketDataPayload = { uid, data };
|
||||
this.emit(SocksProxyHandler.Events.SocksData, payload);
|
||||
|
|
|
|||
|
|
@ -48,6 +48,23 @@ class HttpsHappyEyeballsAgent extends https.Agent {
|
|||
export const httpsHappyEyeballsAgent = new HttpsHappyEyeballsAgent();
|
||||
export const httpHappyEyeballsAgent = new HttpHappyEyeballsAgent();
|
||||
|
||||
export async function createSocket(host: string, port: number): Promise<net.Socket> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (net.isIP(host)) {
|
||||
const socket = net.createConnection({ host, port });
|
||||
socket.on('connect', () => resolve(socket));
|
||||
socket.on('error', error => reject(error));
|
||||
} else {
|
||||
createConnectionAsync({ host, port }, (err, socket) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
if (socket)
|
||||
resolve(socket);
|
||||
}, /* useTLS */ false).catch(err => reject(err));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function createConnectionAsync(options: http.ClientRequestArgs, oncreate: ((err: Error | null, socket?: net.Socket) => void) | undefined, useTLS: boolean) {
|
||||
const lookup = (options as any).__testHookLookup || lookupAddresses;
|
||||
const hostname = clientRequestArgsToHostName(options);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import net from 'net';
|
||||
import { getProxyForUrl } from '../utilsBundle';
|
||||
import { HttpsProxyAgent } from '../utilsBundle';
|
||||
import * as URL from 'url';
|
||||
|
|
@ -26,14 +25,6 @@ import { isString, isRegExp } from './rtti';
|
|||
import { globToRegex } from './glob';
|
||||
import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from './happy-eyeballs';
|
||||
|
||||
export async function createSocket(host: string, port: number): Promise<net.Socket> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = net.createConnection({ host, port });
|
||||
socket.on('connect', () => resolve(socket));
|
||||
socket.on('error', error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
export type HTTPRequestParams = {
|
||||
url: string,
|
||||
method?: string,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import type { Browser, ConnectOptions } from 'playwright-core';
|
|||
type ExtraFixtures = {
|
||||
connect: (wsEndpoint: string, options?: ConnectOptions, redirectPortForTest?: number) => Promise<Browser>,
|
||||
dummyServerPort: number,
|
||||
ipV6ServerUrl: string,
|
||||
ipV6ServerPort: number,
|
||||
};
|
||||
const test = playwrightTest.extend<ExtraFixtures>({
|
||||
connect: async ({ browserType }, use) => {
|
||||
|
|
@ -56,14 +56,14 @@ const test = playwrightTest.extend<ExtraFixtures>({
|
|||
await new Promise<Error>(resolve => server.close(resolve));
|
||||
},
|
||||
|
||||
ipV6ServerUrl: async ({}, use) => {
|
||||
ipV6ServerPort: async ({}, use) => {
|
||||
test.skip(!!process.env.INSIDE_DOCKER, 'docker does not support IPv6 by default');
|
||||
const server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
res.end('<html><body>from-ipv6-server</body></html>');
|
||||
});
|
||||
await new Promise<void>(resolve => server.listen(0, '::1', resolve));
|
||||
const address = server.address() as net.AddressInfo;
|
||||
await use('http://[::1]:' + address.port);
|
||||
await use(address.port);
|
||||
await new Promise<Error>(resolve => server.close(resolve));
|
||||
},
|
||||
});
|
||||
|
|
@ -145,12 +145,24 @@ for (const kind of ['launchServer', 'run-server'] as const) {
|
|||
}
|
||||
});
|
||||
|
||||
test('should be able to visit ipv6', async ({ connect, startRemoteServer, ipV6ServerUrl }) => {
|
||||
test('should be able to visit ipv6', async ({ connect, startRemoteServer, ipV6ServerPort }) => {
|
||||
test.fail(!!process.env.INSIDE_DOCKER, 'docker does not support IPv6 by default');
|
||||
const remoteServer = await startRemoteServer(kind);
|
||||
const browser = await connect(remoteServer.wsEndpoint());
|
||||
const page = await browser.newPage();
|
||||
await page.goto(ipV6ServerUrl);
|
||||
const ipV6Url = 'http://[::1]:' + ipV6ServerPort;
|
||||
await page.goto(ipV6Url);
|
||||
expect(await page.content()).toContain('from-ipv6-server');
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
test('should be able to visit ipv6 through localhost', async ({ connect, startRemoteServer, ipV6ServerPort }) => {
|
||||
test.fail(!!process.env.INSIDE_DOCKER, 'docker does not support IPv6 by default');
|
||||
const remoteServer = await startRemoteServer(kind);
|
||||
const browser = await connect(remoteServer.wsEndpoint());
|
||||
const page = await browser.newPage();
|
||||
const ipV6Url = 'http://localhost:' + ipV6ServerPort;
|
||||
await page.goto(ipV6Url);
|
||||
expect(await page.content()).toContain('from-ipv6-server');
|
||||
await browser.close();
|
||||
});
|
||||
|
|
@ -718,6 +730,26 @@ for (const kind of ['launchServer', 'run-server'] as const) {
|
|||
expect(reachedOriginalTarget).toBe(false);
|
||||
});
|
||||
|
||||
test('should proxy ipv6 localhost requests @smoke', async ({ startRemoteServer, server, browserName, connect, platform, ipV6ServerPort }, testInfo) => {
|
||||
test.skip(browserName === 'webkit' && platform === 'darwin', 'no localhost proxying');
|
||||
|
||||
let reachedOriginalTarget = false;
|
||||
server.setRoute('/foo.html', async (req, res) => {
|
||||
reachedOriginalTarget = true;
|
||||
res.end('<html><body></body></html>');
|
||||
});
|
||||
const examplePort = 20_000 + testInfo.workerIndex * 3;
|
||||
const remoteServer = await startRemoteServer(kind);
|
||||
const browser = await connect(remoteServer.wsEndpoint(), { _exposeNetwork: '*' } as any, ipV6ServerPort);
|
||||
const page = await browser.newPage();
|
||||
await page.goto(`http://[::1]:${examplePort}/foo.html`);
|
||||
expect(await page.content()).toContain('from-ipv6-server');
|
||||
const page2 = await browser.newPage();
|
||||
await page2.goto(`http://localhost:${examplePort}/foo.html`);
|
||||
expect(await page2.content()).toContain('from-ipv6-server');
|
||||
expect(reachedOriginalTarget).toBe(false);
|
||||
});
|
||||
|
||||
test('should proxy localhost requests from fetch api', async ({ startRemoteServer, server, browserName, connect, channel, platform, dummyServerPort }, workerInfo) => {
|
||||
test.skip(browserName === 'webkit' && platform === 'darwin', 'no localhost proxying');
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue