socks: refactor errors.

This commit is contained in:
Christopher Jeffrey 2017-01-24 18:15:51 -08:00
parent 3a0b7d07d3
commit 5288e8a619
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD

View File

@ -52,6 +52,21 @@ SOCKS.states = {
RESOLVE_DONE: 7 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) { SOCKS.prototype.error = function error(msg) {
if (this.destroyed) if (this.destroyed)
return; return;
@ -66,6 +81,13 @@ SOCKS.prototype.error = function error(msg) {
this.destroy(); this.destroy();
}; };
SOCKS.prototype.getError = function getError(code) {
if (code >= SOCKS.errors.length)
return 'Unknown';
return SOCKS.errors[code];
};
SOCKS.prototype.destroy = function destroy() { SOCKS.prototype.destroy = function destroy() {
if (this.destroyed) if (this.destroyed)
return; return;
@ -84,9 +106,10 @@ SOCKS.prototype.destroy = function destroy() {
SOCKS.prototype.startTimeout = function startTimeout() { SOCKS.prototype.startTimeout = function startTimeout() {
var self = this; var self = this;
this.timeout = setTimeout(function() { this.timeout = setTimeout(function() {
var state = SOCKS.statesByVal[self.state];
self.timeout = null; self.timeout = null;
self.error('Request timed out (' + self.state + ').'); self.error('SOCKS request timed out (state=' + state + ').');
}, 5000); }, 8000);
}; };
SOCKS.prototype.stopTimeout = function stopTimeout() { SOCKS.prototype.stopTimeout = function stopTimeout() {
@ -138,6 +161,8 @@ SOCKS.prototype.open = function open(options) {
if (options.username != null) { if (options.username != null) {
assert(typeof options.username === 'string'); assert(typeof options.username === 'string');
this.username = options.username; this.username = options.username;
assert(typeof options.password === 'string',
'Username must have a password.');
} }
if (options.password != null) { if (options.password != null) {
@ -181,8 +206,11 @@ SOCKS.prototype.handleError = function handleError(err) {
}; };
SOCKS.prototype.handleClose = function handleClose() { SOCKS.prototype.handleClose = function handleClose() {
var state;
if (this.state !== this.target) { 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; return;
} }
@ -192,10 +220,10 @@ SOCKS.prototype.handleClose = function handleClose() {
SOCKS.prototype.handleData = function handleData(data) { SOCKS.prototype.handleData = function handleData(data) {
switch (this.state) { switch (this.state) {
case SOCKS.states.INIT: case SOCKS.states.INIT:
this.error('Data before connection.'); this.error('Data before SOCKS connection.');
break; break;
case SOCKS.states.CONNECT: case SOCKS.states.CONNECT:
this.error('Data before handshake.'); this.error('Data before SOCKS handshake.');
break; break;
case SOCKS.states.HANDSHAKE: case SOCKS.states.HANDSHAKE:
this.handleHandshake(data); this.handleHandshake(data);
@ -240,12 +268,12 @@ SOCKS.prototype.sendHandshake = function sendHandshake() {
SOCKS.prototype.handleHandshake = function handleHandshake(data) { SOCKS.prototype.handleHandshake = function handleHandshake(data) {
if (data.length !== 2) { if (data.length !== 2) {
this.error('Bad handshake response.'); this.error('Bad SOCKS handshake response (size).');
return; return;
} }
if (data[0] !== 0x05) { if (data[0] !== 0x05) {
this.error('Bad SOCKS version.'); this.error('Bad SOCKS version for handshake.');
return; return;
} }
@ -253,7 +281,7 @@ SOCKS.prototype.handleHandshake = function handleHandshake(data) {
switch (data[1]) { switch (data[1]) {
case 0xff: case 0xff:
this.error('No acceptable auth methods.'); this.error('No acceptable SOCKS auth methods.');
break; break;
case 0x02: case 0x02:
this.sendAuth(); this.sendAuth();
@ -263,7 +291,7 @@ SOCKS.prototype.handleHandshake = function handleHandshake(data) {
this.auth(); this.auth();
break; break;
default: default:
this.error('Handshake error: ' + data[1]); this.error('SOCKS handshake error: ' + data[1]);
break; break;
} }
}; };
@ -274,12 +302,12 @@ SOCKS.prototype.sendAuth = function sendAuth() {
var ulen, plen, size, packet; var ulen, plen, size, packet;
if (!user) { if (!user) {
this.error('No username passed for auth.'); this.error('No username passed for SOCKS auth.');
return; return;
} }
if (!pass) { if (!pass) {
this.error('No password passed for auth.'); this.error('No password passed for SOCKS auth.');
return; return;
} }
@ -301,17 +329,17 @@ SOCKS.prototype.sendAuth = function sendAuth() {
SOCKS.prototype.handleAuth = function handleAuth(data) { SOCKS.prototype.handleAuth = function handleAuth(data) {
if (data.length !== 2) { if (data.length !== 2) {
this.error('Bad auth response.'); this.error('Bad packet size for SOCKS auth.');
return; return;
} }
if (data[0] !== 0x01) { if (data[0] !== 0x01) {
this.error('Bad version number.'); this.error('Bad SOCKS auth version number.');
return; return;
} }
if (data[1] !== 0x00) { if (data[1] !== 0x00) {
this.error('Auth failure: ' + data[0]); this.error('SOCKS auth failure: ' + data[0]);
return; return;
} }
@ -379,52 +407,26 @@ SOCKS.prototype.sendProxy = function sendProxy() {
}; };
SOCKS.prototype.handleProxy = function handleProxy(data) { SOCKS.prototype.handleProxy = function handleProxy(data) {
var br, len, host, port; var br, len, host, port, err;
if (data.length < 6) { if (data.length < 6) {
this.error('Setup failed.'); this.error('Bad packet size for SOCKS connect.');
return; return;
} }
if (data[0] !== 0x05) { if (data[0] !== 0x05) {
this.error('Bad SOCKS version.'); this.error('Bad SOCKS version for connect.');
return; return;
} }
switch (data[1]) { if (data[1] !== 0x00) {
case 0x00: err = this.getError(data[1]);
break; this.error('SOCKS connect error: ' + err);
case 0x01: return;
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[2] !== 0x00) { if (data[2] !== 0x00) {
this.error('Data corruption.'); this.error('SOCKS connect failed (padding).');
return; return;
} }
@ -434,7 +436,7 @@ SOCKS.prototype.handleProxy = function handleProxy(data) {
switch (br.readU8()) { switch (br.readU8()) {
case 0x01: case 0x01:
if (br.left() < 6) { if (br.left() < 6) {
this.error('Bad packet length.'); this.error('Bad packet length for SOCKS connect.');
return; return;
} }
host = IP.toString(br.readBytes(4)); host = IP.toString(br.readBytes(4));
@ -443,7 +445,7 @@ SOCKS.prototype.handleProxy = function handleProxy(data) {
case 0x03: case 0x03:
len = br.readU8(); len = br.readU8();
if (br.left() < len + 2) { if (br.left() < len + 2) {
this.error('Bad packet length.'); this.error('Bad packet length for SOCKS connect.');
return; return;
} }
host = br.readString(len, 'utf8'); host = br.readString(len, 'utf8');
@ -451,14 +453,14 @@ SOCKS.prototype.handleProxy = function handleProxy(data) {
break; break;
case 0x04: case 0x04:
if (br.left() < 18) { if (br.left() < 18) {
this.error('Bad packet length.'); this.error('Bad packet length for SOCKS connect.');
return; return;
} }
host = IP.toString(br.readBytes(16)); host = IP.toString(br.readBytes(16));
port = br.readU16BE(); port = br.readU16BE();
break; break;
default: default:
this.error('Unknown response.'); this.error('Unknown SOCKS connect response: ' + data[4]);
return; return;
} }
@ -466,6 +468,7 @@ SOCKS.prototype.handleProxy = function handleProxy(data) {
this.stopTimeout(); this.stopTimeout();
this.proxied = true; this.proxied = true;
this.emit('proxied', host, port);
this.emit('proxy', this.socket); this.emit('proxy', this.socket);
}; };
@ -488,30 +491,31 @@ SOCKS.prototype.sendResolve = function sendResolve() {
}; };
SOCKS.prototype.handleResolve = function handleResolve(data) { SOCKS.prototype.handleResolve = function handleResolve(data) {
var ip; var ip, err;
if (data.length !== 10) { if (data.length !== 10) {
this.error('Resolve failed.'); this.error('Bad packet size for tor resolve.');
return; return;
} }
if (data[0] !== 0x05) { if (data[0] !== 0x05) {
this.error('Bad SOCKS version.'); this.error('Bad SOCKS version for tor resolve.');
return; return;
} }
if (data[1] !== 0x00) { if (data[1] !== 0x00) {
this.error('Tor error: ' + data[1]); err = this.getError(data[1]);
this.emit('Tor resolve error: ' + err + ' (' + this.name + ')');
return; return;
} }
if (data[2] !== 0x00) { if (data[2] !== 0x00) {
this.error('Tor error: ' + data[2]); this.error('Unknown tor resolve error: ' + data[2]);
return; return;
} }
if (data[3] !== 0x01) { if (data[3] !== 0x01) {
this.error('Tor error.'); this.error('Tor resolve failed (padding).');
return; return;
} }
@ -550,21 +554,26 @@ SOCKS.proxy = function proxy(options) {
/** /**
* Proxy Socket * Proxy Socket
* @constructor * @constructor
* @param {String} proxy * @param {String} host
* @param {Number} port
* @param {String?} user * @param {String?} user
* @param {String?} pass * @param {String?} pass
*/ */
function Proxy(proxy, user, pass) { function Proxy(host, port, user, pass) {
if (!(this instanceof Proxy)) if (!(this instanceof Proxy))
return new Proxy(proxy, user, pass); return new Proxy(host, port, user, pass);
EventEmitter.call(this); EventEmitter.call(this);
assert(typeof host === 'string');
assert(typeof port === 'number');
this.socket = null; this.socket = null;
this.proxy = IP.fromHostname(proxy); this.host = host;
this.username = user; this.port = port;
this.password = pass; this.username = user || null;
this.password = pass || null;
this.bytesWritten = 0; this.bytesWritten = 0;
this.bytesRead = 0; this.bytesRead = 0;
this.remoteAddress = null; this.remoteAddress = null;
@ -577,9 +586,11 @@ Proxy.prototype.connect = co(function* connect(port, host) {
var self = this; var self = this;
var options, socket; var options, socket;
assert(!this.socket, 'Already connected.');
options = { options = {
host: this.proxy.host, host: this.host,
port: this.proxy.port, port: this.port,
username: this.username, username: this.username,
password: this.password, password: this.password,
destHost: host, destHost: host,
@ -618,43 +629,89 @@ Proxy.prototype.connect = co(function* connect(port, host) {
}); });
Proxy.prototype.write = function write(data, callback) { Proxy.prototype.write = function write(data, callback) {
assert(this.socket, 'Not connected.');
this.bytesWritten += data.length; this.bytesWritten += data.length;
return this.socket.write(data, callback); return this.socket.write(data, callback);
}; };
Proxy.prototype.end = function end() { Proxy.prototype.end = function end() {
assert(this.socket, 'Not connected.');
return this.socket.end(); return this.socket.end();
}; };
Proxy.prototype.pause = function pause() { Proxy.prototype.pause = function pause() {
assert(this.socket, 'Not connected.');
return this.socket.pause(); return this.socket.pause();
}; };
Proxy.prototype.resume = function resume() { Proxy.prototype.resume = function resume() {
assert(this.socket, 'Not connected.');
return this.socket.resume(); return this.socket.resume();
}; };
Proxy.prototype.destroy = function destroy() { Proxy.prototype.destroy = function destroy() {
if (!this.socket)
return;
return this.socket.destroy(); 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 * Expose
*/ */
exports.connect = function connect(proxy, port, host, user, pass) { exports.connect = function connect(proxy, destPort, destHost) {
var socket = new Proxy(proxy, user, pass); var addr = parseProxy(proxy);
socket.connect(port, host); 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; return socket;
}; };
exports.resolve = function resolve(proxy, name, user, pass) { exports.resolve = function resolve(proxy, name) {
var addr = IP.fromHostname(proxy); var addr = parseProxy(proxy);
return SOCKS.resolve({ return SOCKS.resolve({
host: addr.host, host: addr.host,
port: addr.port, port: addr.port,
username: user, username: addr.username,
password: pass, password: addr.password,
name: name name: name
}); });
}; };