From 5288e8a6191ea78857041bd3fbd19215acf4a562 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 24 Jan 2017 18:15:51 -0800 Subject: [PATCH] socks: refactor errors. --- lib/net/socks.js | 203 ++++++++++++++++++++++++++++++----------------- 1 file changed, 130 insertions(+), 73 deletions(-) diff --git a/lib/net/socks.js b/lib/net/socks.js index c7e9553f..a4eb1deb 100644 --- a/lib/net/socks.js +++ b/lib/net/socks.js @@ -52,6 +52,21 @@ SOCKS.states = { RESOLVE_DONE: 7 }; +SOCKS.statesByVal = util.revMap(SOCKS.states); + +SOCKS.errors = [ + '', + 'General failure', + 'Connection not allowed', + 'Network is unreachable', + 'Host is unreachable', + 'Connection refused', + 'TTL expired', + 'Command not supported', + 'Address type not supported', + 'Unknown proxy error' +]; + SOCKS.prototype.error = function error(msg) { if (this.destroyed) return; @@ -66,6 +81,13 @@ SOCKS.prototype.error = function error(msg) { this.destroy(); }; +SOCKS.prototype.getError = function getError(code) { + if (code >= SOCKS.errors.length) + return 'Unknown'; + + return SOCKS.errors[code]; +}; + SOCKS.prototype.destroy = function destroy() { if (this.destroyed) return; @@ -84,9 +106,10 @@ SOCKS.prototype.destroy = function destroy() { SOCKS.prototype.startTimeout = function startTimeout() { var self = this; this.timeout = setTimeout(function() { + var state = SOCKS.statesByVal[self.state]; self.timeout = null; - self.error('Request timed out (' + self.state + ').'); - }, 5000); + self.error('SOCKS request timed out (state=' + state + ').'); + }, 8000); }; SOCKS.prototype.stopTimeout = function stopTimeout() { @@ -138,6 +161,8 @@ SOCKS.prototype.open = function open(options) { if (options.username != null) { assert(typeof options.username === 'string'); this.username = options.username; + assert(typeof options.password === 'string', + 'Username must have a password.'); } if (options.password != null) { @@ -181,8 +206,11 @@ SOCKS.prototype.handleError = function handleError(err) { }; SOCKS.prototype.handleClose = function handleClose() { + var state; + if (this.state !== this.target) { - this.error('State did not reach target state of: ' + this.target); + state = SOCKS.statesByVal[this.target]; + this.error('SOCKS request destroyed (target=' + state + ').'); return; } @@ -192,10 +220,10 @@ SOCKS.prototype.handleClose = function handleClose() { SOCKS.prototype.handleData = function handleData(data) { switch (this.state) { case SOCKS.states.INIT: - this.error('Data before connection.'); + this.error('Data before SOCKS connection.'); break; case SOCKS.states.CONNECT: - this.error('Data before handshake.'); + this.error('Data before SOCKS handshake.'); break; case SOCKS.states.HANDSHAKE: this.handleHandshake(data); @@ -240,12 +268,12 @@ SOCKS.prototype.sendHandshake = function sendHandshake() { SOCKS.prototype.handleHandshake = function handleHandshake(data) { if (data.length !== 2) { - this.error('Bad handshake response.'); + this.error('Bad SOCKS handshake response (size).'); return; } if (data[0] !== 0x05) { - this.error('Bad SOCKS version.'); + this.error('Bad SOCKS version for handshake.'); return; } @@ -253,7 +281,7 @@ SOCKS.prototype.handleHandshake = function handleHandshake(data) { switch (data[1]) { case 0xff: - this.error('No acceptable auth methods.'); + this.error('No acceptable SOCKS auth methods.'); break; case 0x02: this.sendAuth(); @@ -263,7 +291,7 @@ SOCKS.prototype.handleHandshake = function handleHandshake(data) { this.auth(); break; default: - this.error('Handshake error: ' + data[1]); + this.error('SOCKS handshake error: ' + data[1]); break; } }; @@ -274,12 +302,12 @@ SOCKS.prototype.sendAuth = function sendAuth() { var ulen, plen, size, packet; if (!user) { - this.error('No username passed for auth.'); + this.error('No username passed for SOCKS auth.'); return; } if (!pass) { - this.error('No password passed for auth.'); + this.error('No password passed for SOCKS auth.'); return; } @@ -301,17 +329,17 @@ SOCKS.prototype.sendAuth = function sendAuth() { SOCKS.prototype.handleAuth = function handleAuth(data) { if (data.length !== 2) { - this.error('Bad auth response.'); + this.error('Bad packet size for SOCKS auth.'); return; } if (data[0] !== 0x01) { - this.error('Bad version number.'); + this.error('Bad SOCKS auth version number.'); return; } if (data[1] !== 0x00) { - this.error('Auth failure: ' + data[0]); + this.error('SOCKS auth failure: ' + data[0]); return; } @@ -379,52 +407,26 @@ SOCKS.prototype.sendProxy = function sendProxy() { }; SOCKS.prototype.handleProxy = function handleProxy(data) { - var br, len, host, port; + var br, len, host, port, err; if (data.length < 6) { - this.error('Setup failed.'); + this.error('Bad packet size for SOCKS connect.'); return; } if (data[0] !== 0x05) { - this.error('Bad SOCKS version.'); + this.error('Bad SOCKS version for connect.'); return; } - switch (data[1]) { - case 0x00: - break; - case 0x01: - this.error('General failure.'); - return; - case 0x02: - this.error('Connection not allowed.'); - return; - case 0x03: - this.error('Network is unreachable.'); - return; - case 0x04: - this.error('Host is unreachable.'); - return; - case 0x05: - this.error('Connection refused.'); - return; - case 0x06: - this.error('TTL expired.'); - return; - case 0x07: - this.error('Command not supported.'); - return; - case 0x08: - this.error('Address type not supported.'); - return; - default: - this.error('Unknown proxy error: ' + data[1]); - return; + if (data[1] !== 0x00) { + err = this.getError(data[1]); + this.error('SOCKS connect error: ' + err); + return; } if (data[2] !== 0x00) { - this.error('Data corruption.'); + this.error('SOCKS connect failed (padding).'); return; } @@ -434,7 +436,7 @@ SOCKS.prototype.handleProxy = function handleProxy(data) { switch (br.readU8()) { case 0x01: if (br.left() < 6) { - this.error('Bad packet length.'); + this.error('Bad packet length for SOCKS connect.'); return; } host = IP.toString(br.readBytes(4)); @@ -443,7 +445,7 @@ SOCKS.prototype.handleProxy = function handleProxy(data) { case 0x03: len = br.readU8(); if (br.left() < len + 2) { - this.error('Bad packet length.'); + this.error('Bad packet length for SOCKS connect.'); return; } host = br.readString(len, 'utf8'); @@ -451,14 +453,14 @@ SOCKS.prototype.handleProxy = function handleProxy(data) { break; case 0x04: if (br.left() < 18) { - this.error('Bad packet length.'); + this.error('Bad packet length for SOCKS connect.'); return; } host = IP.toString(br.readBytes(16)); port = br.readU16BE(); break; default: - this.error('Unknown response.'); + this.error('Unknown SOCKS connect response: ' + data[4]); return; } @@ -466,6 +468,7 @@ SOCKS.prototype.handleProxy = function handleProxy(data) { this.stopTimeout(); this.proxied = true; + this.emit('proxied', host, port); this.emit('proxy', this.socket); }; @@ -488,30 +491,31 @@ SOCKS.prototype.sendResolve = function sendResolve() { }; SOCKS.prototype.handleResolve = function handleResolve(data) { - var ip; + var ip, err; if (data.length !== 10) { - this.error('Resolve failed.'); + this.error('Bad packet size for tor resolve.'); return; } if (data[0] !== 0x05) { - this.error('Bad SOCKS version.'); + this.error('Bad SOCKS version for tor resolve.'); return; } if (data[1] !== 0x00) { - this.error('Tor error: ' + data[1]); + err = this.getError(data[1]); + this.emit('Tor resolve error: ' + err + ' (' + this.name + ')'); return; } if (data[2] !== 0x00) { - this.error('Tor error: ' + data[2]); + this.error('Unknown tor resolve error: ' + data[2]); return; } if (data[3] !== 0x01) { - this.error('Tor error.'); + this.error('Tor resolve failed (padding).'); return; } @@ -550,21 +554,26 @@ SOCKS.proxy = function proxy(options) { /** * Proxy Socket * @constructor - * @param {String} proxy + * @param {String} host + * @param {Number} port * @param {String?} user * @param {String?} pass */ -function Proxy(proxy, user, pass) { +function Proxy(host, port, user, pass) { if (!(this instanceof Proxy)) - return new Proxy(proxy, user, pass); + return new Proxy(host, port, user, pass); EventEmitter.call(this); + assert(typeof host === 'string'); + assert(typeof port === 'number'); + this.socket = null; - this.proxy = IP.fromHostname(proxy); - this.username = user; - this.password = pass; + this.host = host; + this.port = port; + this.username = user || null; + this.password = pass || null; this.bytesWritten = 0; this.bytesRead = 0; this.remoteAddress = null; @@ -577,9 +586,11 @@ Proxy.prototype.connect = co(function* connect(port, host) { var self = this; var options, socket; + assert(!this.socket, 'Already connected.'); + options = { - host: this.proxy.host, - port: this.proxy.port, + host: this.host, + port: this.port, username: this.username, password: this.password, destHost: host, @@ -618,43 +629,89 @@ Proxy.prototype.connect = co(function* connect(port, host) { }); Proxy.prototype.write = function write(data, callback) { + assert(this.socket, 'Not connected.'); this.bytesWritten += data.length; return this.socket.write(data, callback); }; Proxy.prototype.end = function end() { + assert(this.socket, 'Not connected.'); return this.socket.end(); }; Proxy.prototype.pause = function pause() { + assert(this.socket, 'Not connected.'); return this.socket.pause(); }; Proxy.prototype.resume = function resume() { + assert(this.socket, 'Not connected.'); return this.socket.resume(); }; Proxy.prototype.destroy = function destroy() { + if (!this.socket) + return; return this.socket.destroy(); }; +/* + * Helpers + */ + +function parseProxy(host) { + var index = host.indexOf('@'); + var addr, left, right, parts; + + if (index === -1) { + addr = IP.fromHostname(host, 1080); + return { + host: addr.host, + port: addr.port + }; + } + + left = host.substring(0, index); + right = host.substring(index + 1); + + parts = left.split(':'); + assert(parts.length > 1, 'Bad username and password.'); + + addr = IP.fromHostname(right, 1080); + + return { + host: addr.host, + port: addr.port, + username: parts[0], + password: parts[1] + }; +} + /* * Expose */ -exports.connect = function connect(proxy, port, host, user, pass) { - var socket = new Proxy(proxy, user, pass); - socket.connect(port, host); +exports.connect = function connect(proxy, destPort, destHost) { + var addr = parseProxy(proxy); + var host = addr.host; + var port = addr.port; + var user = addr.username; + var pass = addr.password; + var socket; + + socket = new Proxy(host, port, user, pass); + socket.connect(destPort, destHost); + return socket; }; -exports.resolve = function resolve(proxy, name, user, pass) { - var addr = IP.fromHostname(proxy); +exports.resolve = function resolve(proxy, name) { + var addr = parseProxy(proxy); return SOCKS.resolve({ host: addr.host, port: addr.port, - username: user, - password: pass, + username: addr.username, + password: addr.password, name: name }); };