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
};
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
});
};