fix(socks): support ipv6 (#19727)

Signed-off-by: Dmitry Gozman <dgozman@gmail.com>
Co-authored-by: Yury Semikhatsky <yurys@chromium.org>
This commit is contained in:
Dmitry Gozman 2022-12-27 14:45:35 -08:00 committed by GitHub
parent ba393f51a8
commit 3334d89ad7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 42 additions and 13 deletions

View File

@ -177,9 +177,9 @@ class SocksConnection {
break; break;
case SocksAddressType.IPv6: case SocksAddressType.IPv6:
const bytes = await this._readBytes(16); const bytes = await this._readBytes(16);
const tokens = []; const tokens: string[] = [];
for (let i = 0; i < 8; ++i) for (let i = 0; i < 8; ++i)
tokens.push(bytes.readUInt16BE(i * 2)); tokens.push(bytes.readUInt16BE(i * 2).toString(16));
host = tokens.join(':'); host = tokens.join(':');
break; break;
} }
@ -232,9 +232,8 @@ class SocksConnection {
0x05, 0x05,
SocksReply.Succeeded, SocksReply.Succeeded,
0x00, // RSV 0x00, // RSV
0x01, // IPv4 ...ipToSocksAddress(host), // ATYP, Address
...parseIP(host), // Address port >> 8, port & 0xFF // Port
port << 8, port & 0xFF // Port
])); ]));
this._socket.on('data', data => this._client.onSocketData({ uid: this._uid, data })); this._socket.on('data', data => this._client.onSocketData({ uid: this._uid, data }));
} }
@ -244,8 +243,7 @@ class SocksConnection {
0x05, 0x05,
0, 0,
0x00, // RSV 0x00, // RSV
0x01, // IPv4 ...ipToSocksAddress('0.0.0.0'), // ATYP, Address
...parseIP('0.0.0.0'), // Address
0, 0 // Port 0, 0 // Port
]); ]);
switch (errorCode) { switch (errorCode) {
@ -261,6 +259,9 @@ class SocksConnection {
case 'ECONNREFUSED': case 'ECONNREFUSED':
buffer[1] = SocksReply.ConnectionRefused; buffer[1] = SocksReply.ConnectionRefused;
break; break;
case 'ERULESET':
buffer[1] = SocksReply.NotAllowedByRuleSet;
break;
} }
this._writeBytes(buffer); this._writeBytes(buffer);
this._socket.end(); this._socket.end();
@ -279,10 +280,39 @@ class SocksConnection {
} }
} }
function parseIP(address: string): number[] { function hexToNumber(hex: string): number {
if (!net.isIPv4(address)) // Note: parseInt has a few issues including ignoring trailing characters and allowing leading 0x.
throw new Error('IPv6 is not supported'); return [...hex].reduce((value, digit) => {
return address.split('.', 4).map(t => +t); const code = digit.charCodeAt(0);
if (code >= 48 && code <= 57) // 0..9
return value + code;
if (code >= 97 && code <= 102) // a..f
return value + (code - 97) + 10;
if (code >= 65 && code <= 70) // A..F
return value + (code - 65) + 10;
throw new Error('Invalid IPv6 token ' + hex);
}, 0);
}
function ipToSocksAddress(address: string): number[] {
if (net.isIPv4(address)) {
return [
0x01, // IPv4
...address.split('.', 4).map(t => (+t) & 0xFF), // Address
];
}
if (net.isIPv6(address)) {
const result = [0x04]; // IPv6
const tokens = address.split(':', 8);
while (tokens.length < 8)
tokens.unshift('');
for (const token of tokens) {
const value = hexToNumber(token);
result.push((value >> 8) & 0xFF, value & 0xFF); // Big-endian
}
return result;
}
throw new Error('Only IPv4 and IPv6 addresses are supported');
} }
type PatternMatcher = (host: string, port: number) => boolean; type PatternMatcher = (host: string, port: number) => boolean;
@ -502,7 +532,7 @@ export class SocksProxyHandler extends EventEmitter {
async socketRequested({ uid, host, port }: SocksSocketRequestedPayload): Promise<void> { async socketRequested({ uid, host, port }: SocksSocketRequestedPayload): Promise<void> {
if (!this._patternMatcher(host, port)) { if (!this._patternMatcher(host, port)) {
const payload: SocksSocketFailedPayload = { uid, errorCode: 'ECONNREFUSED' }; const payload: SocksSocketFailedPayload = { uid, errorCode: 'ERULESET' };
this.emit(SocksProxyHandler.Events.SocksFailed, payload); this.emit(SocksProxyHandler.Events.SocksFailed, payload);
return; return;
} }

View File

@ -138,7 +138,6 @@ 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, ipV6ServerUrl }) => {
test.fixme(kind === 'run-server', 'socks proxy does not support ipv6 yet');
const remoteServer = await startRemoteServer(kind); const remoteServer = await startRemoteServer(kind);
const browser = await connect(remoteServer.wsEndpoint()); const browser = await connect(remoteServer.wsEndpoint());
const page = await browser.newPage(); const page = await browser.newPage();