307 lines
11 KiB
JavaScript
307 lines
11 KiB
JavaScript
|
var net = require('net');
|
||
|
var ip = require('ip');
|
||
|
var SmartBuffer = require('smart-buffer');
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
var COMMAND = {
|
||
|
Connect: 0x01,
|
||
|
Bind: 0x02,
|
||
|
Associate: 0x03
|
||
|
};
|
||
|
|
||
|
var SOCKS4_RESPONSE = {
|
||
|
Granted: 0x5A,
|
||
|
Failed: 0x5B,
|
||
|
Rejected: 0x5C,
|
||
|
RejectedIdent: 0x5D
|
||
|
};
|
||
|
|
||
|
var SOCKS5_AUTH = {
|
||
|
NoAuth: 0x00,
|
||
|
GSSApi: 0x01,
|
||
|
UserPass: 0x02
|
||
|
};
|
||
|
|
||
|
var SOCKS5_RESPONSE = {
|
||
|
Granted: 0x00,
|
||
|
Failure: 0x01,
|
||
|
NotAllowed: 0x02,
|
||
|
NetworkUnreachable: 0x03,
|
||
|
HostUnreachable: 0x04,
|
||
|
ConnectionRefused: 0x05,
|
||
|
TTLExpired: 0x06,
|
||
|
CommandNotSupported: 0x07,
|
||
|
AddressNotSupported: 0x08
|
||
|
};
|
||
|
|
||
|
|
||
|
exports.createConnection = function (options, callback) {
|
||
|
var socket = new net.Socket(), finished = false, buff = new SmartBuffer();
|
||
|
|
||
|
// Defaults
|
||
|
options.timeout = options.timeout || 10000;
|
||
|
options.proxy.command = commandFromString(options.proxy.command);
|
||
|
options.proxy.userid = options.proxy.userid || "";
|
||
|
|
||
|
var auth = options.proxy.authentication || {};
|
||
|
auth.username = auth.username || "";
|
||
|
auth.password = auth.password || "";
|
||
|
|
||
|
options.proxy.authentication = auth;
|
||
|
|
||
|
// Connect & negotiation timeout
|
||
|
function onTimeout() {
|
||
|
finish(new Error("Connection Timed Out"), socket, null, callback);
|
||
|
}
|
||
|
socket.setTimeout(options.timeout, onTimeout);
|
||
|
|
||
|
// Socket events
|
||
|
socket.once('close', function () {
|
||
|
finish(new Error("Socket Closed"), socket, null, callback);
|
||
|
});
|
||
|
|
||
|
socket.once('error', function (err) {
|
||
|
});
|
||
|
|
||
|
socket.once('connect', function () {
|
||
|
if (options.proxy.type === 4) {
|
||
|
negotiateSocks4(options, socket, callback);
|
||
|
} else if (options.proxy.type === 5) {
|
||
|
negotiateSocks5(options, socket, callback);
|
||
|
} else {
|
||
|
throw new Error("Please specify a proxy type in options.proxy.type");
|
||
|
}
|
||
|
});
|
||
|
|
||
|
socket.connect(options.proxy.port, options.proxy.ipaddress);
|
||
|
|
||
|
|
||
|
// 4/4a (connect, bind) - Supports domains & ipaddress
|
||
|
function negotiateSocks4(options, socket, callback) {
|
||
|
buff.writeUInt8(0x04);
|
||
|
buff.writeUInt8(options.proxy.command);
|
||
|
buff.writeUInt16BE(options.target.port);
|
||
|
|
||
|
// ipv4 or domain?
|
||
|
if (net.isIPv4(options.target.host)) {
|
||
|
buff.writeBuffer(ip.toBuffer(options.target.host));
|
||
|
buff.writeStringNT(options.proxy.userid);
|
||
|
} else {
|
||
|
buff.writeUInt8(0x00);
|
||
|
buff.writeUInt8(0x00);
|
||
|
buff.writeUInt8(0x00);
|
||
|
buff.writeUInt8(0x01);
|
||
|
buff.writeStringNT(options.proxy.userid);
|
||
|
buff.writeStringNT(options.target.host);
|
||
|
}
|
||
|
|
||
|
socket.once('data', receivedResponse);
|
||
|
socket.write(buff.toBuffer());
|
||
|
|
||
|
function receivedResponse(data) {
|
||
|
socket.pause();
|
||
|
if (data.length === 8 && data[1] === SOCKS4_RESPONSE.Granted) {
|
||
|
|
||
|
if (options.proxy.command === COMMAND.Bind) {
|
||
|
buff.clear();
|
||
|
buff.writeBuffer(data);
|
||
|
buff.skip(2);
|
||
|
|
||
|
var info = {
|
||
|
port: buff.readUInt16BE(),
|
||
|
host: buff.readUInt32BE()
|
||
|
};
|
||
|
|
||
|
if (info.host === 0) {
|
||
|
info.host = options.proxy.ipaddress;
|
||
|
} else {
|
||
|
info.host = ip.fromLong(info.host);
|
||
|
}
|
||
|
|
||
|
finish(null, socket, info, callback);
|
||
|
} else {
|
||
|
finish(null, socket, null, callback);
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
finish(new Error("Rejected (" + data[1] + ")"), socket, null, callback);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Socks 5 (connect, bind, associate) - Supports domains and ipv4, ipv6.
|
||
|
function negotiateSocks5(options, socket, callback) {
|
||
|
buff.writeUInt8(0x05);
|
||
|
buff.writeUInt8(2);
|
||
|
buff.writeUInt8(SOCKS5_AUTH.NoAuth);
|
||
|
buff.writeUInt8(SOCKS5_AUTH.UserPass);
|
||
|
|
||
|
socket.once('data', handshake);
|
||
|
socket.write(buff.toBuffer());
|
||
|
|
||
|
function handshake(data) {
|
||
|
if (data.length !== 2) {
|
||
|
finish(new Error("Negotiation Error"), socket, null, callback);
|
||
|
} else if (data[0] !== 0x05) {
|
||
|
finish(new Error("Negotiation Error (invalid version)"), socket, null, callback);
|
||
|
} else if (data[1] === 0xFF) {
|
||
|
finish(new Error("Negotiation Error (unacceptable authentication)"), socket, null, callback);
|
||
|
} else {
|
||
|
if (data[1] === SOCKS5_AUTH.NoAuth) {
|
||
|
sendRequest();
|
||
|
} else if (data[1] === SOCKS5_AUTH.UserPass) {
|
||
|
sendAuthentication(options.proxy.authentication);
|
||
|
} else {
|
||
|
finish(new Error("Negotiation Error (unknown authentication type)"), socket, null, callback);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function sendAuthentication(authinfo) {
|
||
|
buff.clear();
|
||
|
buff.writeUInt8(0x01);
|
||
|
buff.writeUInt8(Buffer.byteLength(authinfo.username));
|
||
|
buff.writeString(authinfo.username);
|
||
|
buff.writeUInt8(Buffer.byteLength(authinfo.password));
|
||
|
buff.writeString(authinfo.password);
|
||
|
|
||
|
socket.once('data', authenticationResponse);
|
||
|
socket.write(buff.toBuffer());
|
||
|
|
||
|
function authenticationResponse(data) {
|
||
|
if (data.length === 2 && data[1] === 0x00) {
|
||
|
sendRequest();
|
||
|
} else {
|
||
|
finish(new Error("Negotiation Error (authentication failed)"), socket, null, callback);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function sendRequest() {
|
||
|
buff.clear();
|
||
|
buff.writeUInt8(0x05);
|
||
|
buff.writeUInt8(options.proxy.command);
|
||
|
buff.writeUInt8(0x00);
|
||
|
|
||
|
// ipv4, ipv6, domain?
|
||
|
if (net.isIPv4(options.target.host)) {
|
||
|
buff.writeUInt8(0x01);
|
||
|
buff.writeBuffer(ip.toBuffer(options.target.host));
|
||
|
} else if (net.isIPv6(options.target.host)) {
|
||
|
buff.writeUInt8(0x04);
|
||
|
buff.writeBuffer(ip.toBuffer(options.target.host));
|
||
|
} else {
|
||
|
buff.writeUInt8(0x03);
|
||
|
buff.writeUInt8(options.target.host.length);
|
||
|
buff.writeString(options.target.host);
|
||
|
}
|
||
|
buff.writeUInt16BE(options.target.port);
|
||
|
|
||
|
socket.once('data', receivedResponse);
|
||
|
socket.write(buff.toBuffer());
|
||
|
}
|
||
|
|
||
|
function receivedResponse(data) {
|
||
|
socket.pause();
|
||
|
if (data.length < 4) {
|
||
|
finish(new Error("Negotiation Error"), socket, null, callback);
|
||
|
} else if (data[0] === 0x05 && data[1] === SOCKS5_RESPONSE.Granted) {
|
||
|
if (options.proxy.command === COMMAND.Connect) {
|
||
|
finish(null, socket, null, callback);
|
||
|
} else if (options.proxy.command === COMMAND.Bind || options.proxy.command === COMMAND.Associate) {
|
||
|
buff.clear();
|
||
|
buff.writeBuffer(data);
|
||
|
buff.skip(3);
|
||
|
|
||
|
var info = {};
|
||
|
var addrtype = buff.readUInt8();
|
||
|
|
||
|
try {
|
||
|
|
||
|
if (addrtype === 0x01) {
|
||
|
info.host = buff.readUInt32BE();
|
||
|
if (info.host === 0)
|
||
|
info.host = options.proxy.ipaddress;
|
||
|
else
|
||
|
info.host = ip.fromLong(info.host);
|
||
|
} else if (addrtype === 0x03) {
|
||
|
var len = buff.readUInt8();
|
||
|
info.host = buff.readString(len);
|
||
|
} else if (addrtype === 0x04) {
|
||
|
info.host = buff.readBuffer(16);
|
||
|
} else {
|
||
|
finish(new Error("Negotiation Error (invalid host address)"), socket, null, callback);
|
||
|
}
|
||
|
info.port = buff.readUInt16BE();
|
||
|
|
||
|
finish(null, socket, info, callback);
|
||
|
} catch (ex) {
|
||
|
finish(new Error("Negotiation Error (missing data)"), socket, null, callback);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
finish(new Error("Negotiation Error (" + data[1] + ")"), socket, null, callback);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function finish(err, socket, info, callback) {
|
||
|
socket.setTimeout(0, onTimeout);
|
||
|
if (!finished) {
|
||
|
finished = true;
|
||
|
|
||
|
if (buff instanceof SmartBuffer)
|
||
|
buff.destroy();
|
||
|
|
||
|
if (err && socket instanceof net.Socket) {
|
||
|
socket.removeAllListeners('close');
|
||
|
socket.removeAllListeners('timeout');
|
||
|
socket.removeAllListeners('data');
|
||
|
socket.destroy();
|
||
|
socket = null;
|
||
|
}
|
||
|
|
||
|
callback(err, socket, info);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function commandFromString(str) {
|
||
|
var result = COMMAND.Connect;
|
||
|
|
||
|
if (str === "connect") {
|
||
|
result = COMMAND.Connect;
|
||
|
} else if (str === 'associate') {
|
||
|
result = COMMAND.Associate;
|
||
|
} else if (str === 'bind') {
|
||
|
result = COMMAND.Bind;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
exports.createUDPFrame = function (target, data, frame) {
|
||
|
var buff = new SmartBuffer();
|
||
|
buff.writeUInt16BE(0);
|
||
|
buff.writeUInt8(frame || 0x00);
|
||
|
|
||
|
if (net.isIPv4(target.host)) {
|
||
|
buff.writeUInt8(0x01);
|
||
|
buff.writeUInt32BE(ip.toLong(target.host));
|
||
|
} else if (net.isIPv6(target.host)) {
|
||
|
buff.writeUInt8(0x04);
|
||
|
buff.writeBuffer(ip.toBuffer(target.host));
|
||
|
} else {
|
||
|
buff.writeUInt8(0x03);
|
||
|
buff.writeUInt8(Buffer.byteLength(target.host));
|
||
|
buff.writeString(target.host);
|
||
|
}
|
||
|
|
||
|
buff.writeUInt16BE(target.port);
|
||
|
buff.writeBuffer(data);
|
||
|
return buff.toBuffer();
|
||
|
};
|
||
|
})();
|