123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- "use strict";
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
- exports.SocksProxyHandler = exports.SocksProxy = void 0;
- exports.parsePattern = parsePattern;
- var _events = _interopRequireDefault(require("events"));
- var _net = _interopRequireDefault(require("net"));
- var _debugLogger = require("./debugLogger");
- var _happyEyeballs = require("../utils/happy-eyeballs");
- var _utils = require("../utils");
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
- /**
- * Copyright (c) Microsoft Corporation.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- // https://tools.ietf.org/html/rfc1928
- var SocksAuth;
- (function (SocksAuth) {
- SocksAuth[SocksAuth["NO_AUTHENTICATION_REQUIRED"] = 0] = "NO_AUTHENTICATION_REQUIRED";
- SocksAuth[SocksAuth["GSSAPI"] = 1] = "GSSAPI";
- SocksAuth[SocksAuth["USERNAME_PASSWORD"] = 2] = "USERNAME_PASSWORD";
- SocksAuth[SocksAuth["NO_ACCEPTABLE_METHODS"] = 255] = "NO_ACCEPTABLE_METHODS";
- })(SocksAuth || (SocksAuth = {}));
- var SocksAddressType;
- (function (SocksAddressType) {
- SocksAddressType[SocksAddressType["IPv4"] = 1] = "IPv4";
- SocksAddressType[SocksAddressType["FqName"] = 3] = "FqName";
- SocksAddressType[SocksAddressType["IPv6"] = 4] = "IPv6";
- })(SocksAddressType || (SocksAddressType = {}));
- var SocksCommand;
- (function (SocksCommand) {
- SocksCommand[SocksCommand["CONNECT"] = 1] = "CONNECT";
- SocksCommand[SocksCommand["BIND"] = 2] = "BIND";
- SocksCommand[SocksCommand["UDP_ASSOCIATE"] = 3] = "UDP_ASSOCIATE";
- })(SocksCommand || (SocksCommand = {}));
- var SocksReply;
- (function (SocksReply) {
- SocksReply[SocksReply["Succeeded"] = 0] = "Succeeded";
- SocksReply[SocksReply["GeneralServerFailure"] = 1] = "GeneralServerFailure";
- SocksReply[SocksReply["NotAllowedByRuleSet"] = 2] = "NotAllowedByRuleSet";
- SocksReply[SocksReply["NetworkUnreachable"] = 3] = "NetworkUnreachable";
- SocksReply[SocksReply["HostUnreachable"] = 4] = "HostUnreachable";
- SocksReply[SocksReply["ConnectionRefused"] = 5] = "ConnectionRefused";
- SocksReply[SocksReply["TtlExpired"] = 6] = "TtlExpired";
- SocksReply[SocksReply["CommandNotSupported"] = 7] = "CommandNotSupported";
- SocksReply[SocksReply["AddressTypeNotSupported"] = 8] = "AddressTypeNotSupported";
- })(SocksReply || (SocksReply = {}));
- class SocksConnection {
- constructor(uid, socket, client) {
- this._buffer = Buffer.from([]);
- this._offset = 0;
- this._fence = 0;
- this._fenceCallback = void 0;
- this._socket = void 0;
- this._boundOnData = void 0;
- this._uid = void 0;
- this._client = void 0;
- this._uid = uid;
- this._socket = socket;
- this._client = client;
- this._boundOnData = this._onData.bind(this);
- socket.on('data', this._boundOnData);
- socket.on('close', () => this._onClose());
- socket.on('end', () => this._onClose());
- socket.on('error', () => this._onClose());
- this._run().catch(() => this._socket.end());
- }
- async _run() {
- (0, _utils.assert)(await this._authenticate());
- const {
- command,
- host,
- port
- } = await this._parseRequest();
- if (command !== SocksCommand.CONNECT) {
- this._writeBytes(Buffer.from([0x05, SocksReply.CommandNotSupported, 0x00,
- // RSV
- 0x01,
- // IPv4
- 0x00, 0x00, 0x00, 0x00,
- // Address
- 0x00, 0x00 // Port
- ]));
- return;
- }
- this._socket.off('data', this._boundOnData);
- this._client.onSocketRequested({
- uid: this._uid,
- host,
- port
- });
- }
- async _authenticate() {
- // Request:
- // +----+----------+----------+
- // |VER | NMETHODS | METHODS |
- // +----+----------+----------+
- // | 1 | 1 | 1 to 255 |
- // +----+----------+----------+
- // Response:
- // +----+--------+
- // |VER | METHOD |
- // +----+--------+
- // | 1 | 1 |
- // +----+--------+
- const version = await this._readByte();
- (0, _utils.assert)(version === 0x05, 'The VER field must be set to x05 for this version of the protocol, was ' + version);
- const nMethods = await this._readByte();
- (0, _utils.assert)(nMethods, 'No authentication methods specified');
- const methods = await this._readBytes(nMethods);
- for (const method of methods) {
- if (method === 0) {
- this._writeBytes(Buffer.from([version, method]));
- return true;
- }
- }
- this._writeBytes(Buffer.from([version, SocksAuth.NO_ACCEPTABLE_METHODS]));
- return false;
- }
- async _parseRequest() {
- // Request.
- // +----+-----+-------+------+----------+----------+
- // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
- // +----+-----+-------+------+----------+----------+
- // | 1 | 1 | X'00' | 1 | Variable | 2 |
- // +----+-----+-------+------+----------+----------+
- // Response.
- // +----+-----+-------+------+----------+----------+
- // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
- // +----+-----+-------+------+----------+----------+
- // | 1 | 1 | X'00' | 1 | Variable | 2 |
- // +----+-----+-------+------+----------+----------+
- const version = await this._readByte();
- (0, _utils.assert)(version === 0x05, 'The VER field must be set to x05 for this version of the protocol, was ' + version);
- const command = await this._readByte();
- await this._readByte(); // skip reserved.
- const addressType = await this._readByte();
- let host = '';
- switch (addressType) {
- case SocksAddressType.IPv4:
- host = (await this._readBytes(4)).join('.');
- break;
- case SocksAddressType.FqName:
- const length = await this._readByte();
- host = (await this._readBytes(length)).toString();
- break;
- case SocksAddressType.IPv6:
- const bytes = await this._readBytes(16);
- const tokens = [];
- for (let i = 0; i < 8; ++i) tokens.push(bytes.readUInt16BE(i * 2).toString(16));
- host = tokens.join(':');
- break;
- }
- const port = (await this._readBytes(2)).readUInt16BE(0);
- this._buffer = Buffer.from([]);
- this._offset = 0;
- this._fence = 0;
- return {
- command,
- host,
- port
- };
- }
- async _readByte() {
- const buffer = await this._readBytes(1);
- return buffer[0];
- }
- async _readBytes(length) {
- this._fence = this._offset + length;
- if (!this._buffer || this._buffer.length < this._fence) await new Promise(f => this._fenceCallback = f);
- this._offset += length;
- return this._buffer.slice(this._offset - length, this._offset);
- }
- _writeBytes(buffer) {
- if (this._socket.writable) this._socket.write(buffer);
- }
- _onClose() {
- this._client.onSocketClosed({
- uid: this._uid
- });
- }
- _onData(buffer) {
- this._buffer = Buffer.concat([this._buffer, buffer]);
- if (this._fenceCallback && this._buffer.length >= this._fence) {
- const callback = this._fenceCallback;
- this._fenceCallback = undefined;
- callback();
- }
- }
- socketConnected(host, port) {
- this._writeBytes(Buffer.from([0x05, SocksReply.Succeeded, 0x00,
- // RSV
- ...ipToSocksAddress(host),
- // ATYP, Address
- port >> 8, port & 0xFF // Port
- ]));
- this._socket.on('data', data => this._client.onSocketData({
- uid: this._uid,
- data
- }));
- }
- socketFailed(errorCode) {
- const buffer = Buffer.from([0x05, 0, 0x00,
- // RSV
- ...ipToSocksAddress('0.0.0.0'),
- // ATYP, Address
- 0, 0 // Port
- ]);
- switch (errorCode) {
- case 'ENOENT':
- case 'ENOTFOUND':
- case 'ETIMEDOUT':
- case 'EHOSTUNREACH':
- buffer[1] = SocksReply.HostUnreachable;
- break;
- case 'ENETUNREACH':
- buffer[1] = SocksReply.NetworkUnreachable;
- break;
- case 'ECONNREFUSED':
- buffer[1] = SocksReply.ConnectionRefused;
- break;
- case 'ERULESET':
- buffer[1] = SocksReply.NotAllowedByRuleSet;
- break;
- }
- this._writeBytes(buffer);
- this._socket.end();
- }
- sendData(data) {
- this._socket.write(data);
- }
- end() {
- this._socket.end();
- }
- error(error) {
- this._socket.destroy(new Error(error));
- }
- }
- function hexToNumber(hex) {
- // Note: parseInt has a few issues including ignoring trailing characters and allowing leading 0x.
- return [...hex].reduce((value, digit) => {
- 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) {
- if (_net.default.isIPv4(address)) {
- return [0x01,
- // IPv4
- ...address.split('.', 4).map(t => +t & 0xFF) // Address
- ];
- }
- if (_net.default.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');
- }
- function starMatchToRegex(pattern) {
- const source = pattern.split('*').map(s => {
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
- }).join('.*');
- return new RegExp('^' + source + '$');
- }
- // This follows "Proxy bypass rules" syntax without implicit and negative rules.
- // https://source.chromium.org/chromium/chromium/src/+/main:net/docs/proxy.md;l=331
- function parsePattern(pattern) {
- if (!pattern) return () => false;
- const matchers = pattern.split(',').map(token => {
- const match = token.match(/^(.*?)(?::(\d+))?$/);
- if (!match) throw new Error(`Unsupported token "${token}" in pattern "${pattern}"`);
- const tokenPort = match[2] ? +match[2] : undefined;
- const portMatches = port => tokenPort === undefined || tokenPort === port;
- let tokenHost = match[1];
- if (tokenHost === '<loopback>') {
- return (host, port) => {
- if (!portMatches(port)) return false;
- return host === 'localhost' || host.endsWith('.localhost') || host === '127.0.0.1' || host === '[::1]';
- };
- }
- if (tokenHost === '*') return (host, port) => portMatches(port);
- if (_net.default.isIPv4(tokenHost) || _net.default.isIPv6(tokenHost)) return (host, port) => host === tokenHost && portMatches(port);
- if (tokenHost[0] === '.') tokenHost = '*' + tokenHost;
- const tokenRegex = starMatchToRegex(tokenHost);
- return (host, port) => {
- if (!portMatches(port)) return false;
- if (_net.default.isIPv4(host) || _net.default.isIPv6(host)) return false;
- return !!host.match(tokenRegex);
- };
- });
- return (host, port) => matchers.some(matcher => matcher(host, port));
- }
- class SocksProxy extends _events.default {
- constructor() {
- super();
- this._server = void 0;
- this._connections = new Map();
- this._sockets = new Set();
- this._closed = false;
- this._port = void 0;
- this._patternMatcher = () => false;
- this._directSockets = new Map();
- this._server = new _net.default.Server(socket => {
- const uid = (0, _utils.createGuid)();
- const connection = new SocksConnection(uid, socket, this);
- this._connections.set(uid, connection);
- });
- this._server.on('connection', socket => {
- if (this._closed) {
- socket.destroy();
- return;
- }
- this._sockets.add(socket);
- socket.once('close', () => this._sockets.delete(socket));
- });
- }
- setPattern(pattern) {
- try {
- this._patternMatcher = parsePattern(pattern);
- } catch (e) {
- this._patternMatcher = () => false;
- }
- }
- async _handleDirect(request) {
- try {
- var _this$_connections$ge4;
- const socket = await (0, _happyEyeballs.createSocket)(request.host, request.port);
- socket.on('data', data => {
- var _this$_connections$ge;
- return (_this$_connections$ge = this._connections.get(request.uid)) === null || _this$_connections$ge === void 0 ? void 0 : _this$_connections$ge.sendData(data);
- });
- socket.on('error', error => {
- var _this$_connections$ge2;
- (_this$_connections$ge2 = this._connections.get(request.uid)) === null || _this$_connections$ge2 === void 0 ? void 0 : _this$_connections$ge2.error(error.message);
- this._directSockets.delete(request.uid);
- });
- socket.on('end', () => {
- var _this$_connections$ge3;
- (_this$_connections$ge3 = this._connections.get(request.uid)) === null || _this$_connections$ge3 === void 0 ? void 0 : _this$_connections$ge3.end();
- this._directSockets.delete(request.uid);
- });
- const localAddress = socket.localAddress;
- const localPort = socket.localPort;
- this._directSockets.set(request.uid, socket);
- (_this$_connections$ge4 = this._connections.get(request.uid)) === null || _this$_connections$ge4 === void 0 ? void 0 : _this$_connections$ge4.socketConnected(localAddress, localPort);
- } catch (error) {
- var _this$_connections$ge5;
- (_this$_connections$ge5 = this._connections.get(request.uid)) === null || _this$_connections$ge5 === void 0 ? void 0 : _this$_connections$ge5.socketFailed(error.code);
- }
- }
- port() {
- return this._port;
- }
- async listen(port) {
- return new Promise(f => {
- this._server.listen(port, () => {
- const port = this._server.address().port;
- this._port = port;
- f(port);
- });
- });
- }
- async close() {
- if (this._closed) return;
- this._closed = true;
- for (const socket of this._sockets) socket.destroy();
- this._sockets.clear();
- await new Promise(f => this._server.close(f));
- }
- onSocketRequested(payload) {
- if (!this._patternMatcher(payload.host, payload.port)) {
- this._handleDirect(payload);
- return;
- }
- this.emit(SocksProxy.Events.SocksRequested, payload);
- }
- onSocketData(payload) {
- const direct = this._directSockets.get(payload.uid);
- if (direct) {
- direct.write(payload.data);
- return;
- }
- this.emit(SocksProxy.Events.SocksData, payload);
- }
- onSocketClosed(payload) {
- const direct = this._directSockets.get(payload.uid);
- if (direct) {
- direct.destroy();
- this._directSockets.delete(payload.uid);
- return;
- }
- this.emit(SocksProxy.Events.SocksClosed, payload);
- }
- socketConnected({
- uid,
- host,
- port
- }) {
- var _this$_connections$ge6;
- (_this$_connections$ge6 = this._connections.get(uid)) === null || _this$_connections$ge6 === void 0 ? void 0 : _this$_connections$ge6.socketConnected(host, port);
- }
- socketFailed({
- uid,
- errorCode
- }) {
- var _this$_connections$ge7;
- (_this$_connections$ge7 = this._connections.get(uid)) === null || _this$_connections$ge7 === void 0 ? void 0 : _this$_connections$ge7.socketFailed(errorCode);
- }
- sendSocketData({
- uid,
- data
- }) {
- var _this$_connections$ge8;
- (_this$_connections$ge8 = this._connections.get(uid)) === null || _this$_connections$ge8 === void 0 ? void 0 : _this$_connections$ge8.sendData(data);
- }
- sendSocketEnd({
- uid
- }) {
- var _this$_connections$ge9;
- (_this$_connections$ge9 = this._connections.get(uid)) === null || _this$_connections$ge9 === void 0 ? void 0 : _this$_connections$ge9.end();
- }
- sendSocketError({
- uid,
- error
- }) {
- var _this$_connections$ge10;
- (_this$_connections$ge10 = this._connections.get(uid)) === null || _this$_connections$ge10 === void 0 ? void 0 : _this$_connections$ge10.error(error);
- }
- }
- exports.SocksProxy = SocksProxy;
- SocksProxy.Events = {
- SocksRequested: 'socksRequested',
- SocksData: 'socksData',
- SocksClosed: 'socksClosed'
- };
- class SocksProxyHandler extends _events.default {
- constructor(pattern, redirectPortForTest) {
- super();
- this._sockets = new Map();
- this._patternMatcher = () => false;
- this._redirectPortForTest = void 0;
- this._patternMatcher = parsePattern(pattern);
- this._redirectPortForTest = redirectPortForTest;
- }
- cleanup() {
- for (const uid of this._sockets.keys()) this.socketClosed({
- uid
- });
- }
- async socketRequested({
- uid,
- host,
- port
- }) {
- _debugLogger.debugLogger.log('socks', `[${uid}] => request ${host}:${port}`);
- if (!this._patternMatcher(host, port)) {
- const payload = {
- uid,
- errorCode: 'ERULESET'
- };
- _debugLogger.debugLogger.log('socks', `[${uid}] <= pattern error ${payload.errorCode}`);
- this.emit(SocksProxyHandler.Events.SocksFailed, payload);
- return;
- }
- if (host === 'local.playwright') host = 'localhost';
- try {
- if (this._redirectPortForTest) port = this._redirectPortForTest;
- const socket = await (0, _happyEyeballs.createSocket)(host, port);
- socket.on('data', data => {
- const payload = {
- uid,
- data
- };
- this.emit(SocksProxyHandler.Events.SocksData, payload);
- });
- socket.on('error', error => {
- const payload = {
- uid,
- error: error.message
- };
- _debugLogger.debugLogger.log('socks', `[${uid}] <= network socket error ${payload.error}`);
- this.emit(SocksProxyHandler.Events.SocksError, payload);
- this._sockets.delete(uid);
- });
- socket.on('end', () => {
- const payload = {
- uid
- };
- _debugLogger.debugLogger.log('socks', `[${uid}] <= network socket closed`);
- this.emit(SocksProxyHandler.Events.SocksEnd, payload);
- this._sockets.delete(uid);
- });
- const localAddress = socket.localAddress;
- const localPort = socket.localPort;
- this._sockets.set(uid, socket);
- const payload = {
- uid,
- host: localAddress,
- port: localPort
- };
- _debugLogger.debugLogger.log('socks', `[${uid}] <= connected to network ${payload.host}:${payload.port}`);
- this.emit(SocksProxyHandler.Events.SocksConnected, payload);
- } catch (error) {
- const payload = {
- uid,
- errorCode: error.code
- };
- _debugLogger.debugLogger.log('socks', `[${uid}] <= connect error ${payload.errorCode}`);
- this.emit(SocksProxyHandler.Events.SocksFailed, payload);
- }
- }
- sendSocketData({
- uid,
- data
- }) {
- var _this$_sockets$get;
- (_this$_sockets$get = this._sockets.get(uid)) === null || _this$_sockets$get === void 0 ? void 0 : _this$_sockets$get.write(data);
- }
- socketClosed({
- uid
- }) {
- var _this$_sockets$get2;
- _debugLogger.debugLogger.log('socks', `[${uid}] <= browser socket closed`);
- (_this$_sockets$get2 = this._sockets.get(uid)) === null || _this$_sockets$get2 === void 0 ? void 0 : _this$_sockets$get2.destroy();
- this._sockets.delete(uid);
- }
- }
- exports.SocksProxyHandler = SocksProxyHandler;
- SocksProxyHandler.Events = {
- SocksConnected: 'socksConnected',
- SocksData: 'socksData',
- SocksError: 'socksError',
- SocksFailed: 'socksFailed',
- SocksEnd: 'socksEnd'
- };
|