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