From e41c4d99a46b3a40a5610ed2193f701bdb196368 Mon Sep 17 00:00:00 2001 From: Zachary Boyd Date: Tue, 31 Jan 2017 18:11:36 -0500 Subject: [PATCH] final --- Dockerfile | 4 ++ bin/tor-router | 54 +++++++++++++++++ docker-compose.yaml | 2 +- package.json | 3 +- src/DNSServer.js | 15 +++-- src/SOCKSServer.js | 63 ++++++++++++++++++++ src/TorPool.js | 11 ++-- src/TorProcess.js | 20 ++++++- src/index.js | 3 +- test/test.js | 140 +++++++++++++++++++++++++++++++------------- x.js | 9 +++ yarn.lock | 4 +- 12 files changed, 268 insertions(+), 60 deletions(-) create mode 100755 bin/tor-router create mode 100644 src/SOCKSServer.js create mode 100644 x.js diff --git a/Dockerfile b/Dockerfile index affe74e..c7638e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,10 @@ EXPOSE 9050 EXPOSE 53 +ENV DNS_PORT 53 + +ENV SOCKS_PORT 9050 + ADD https://deb.nodesource.com/setup_7.x /nodejs_install RUN bash /nodejs_install diff --git a/bin/tor-router b/bin/tor-router new file mode 100755 index 0000000..967e3cf --- /dev/null +++ b/bin/tor-router @@ -0,0 +1,54 @@ +#!/usr/bin/env node + +var program = require('commander'); +var TorRouter = require('../'); +var SOCKSServer = TorRouter.SOCKSServer; +var DNSServer = TorRouter.DNSServer; +var TorPool = TorRouter.TorPool; +var winston = require('winston') + +program + .version('0.0.1') + .option('-j, --instances <1>', 'Number of tor instances', Number) + .option('-s, --socksPort [9050]', 'SOCKS Server port', Number) + .option('-d, --dnsPort [9053]', 'DNS Server port', Number) + .option('-l, --logLevel [info]', 'Log level (defaults to "info") set to "null" to disable logging', Number) + .parse(process.argv); + +var logger = new (winston.Logger)({ +transports: [ + new (winston.transports.Console)({ level: (program.logLevel || 'info') }) +] +}); + +let instances = program.instances || Number(process.env.INSTANCES); +let log_level = program.logLevel || process.env.LOG_LEVEL; +let socks_port = program.socksPort || Number(process.env.SOCKS_PORT); +let dns_port = program.dnsPort || Number(process.env.DNS_PORT); + +if (!instances) { + logger.error('Number of instances not specified'); + process.exit(1); +} + +let pool = new TorPool('tor', null, logger); + +if (log_level === 'null') + logger = void(0); + +if (socks_port) { + let socks = new SOCKSServer(pool, logger); + logger && logger.info(`[socks]: Listening on ${socks_port}`); + socks.listen((socks_port || 9050)); +} + +if (dns_port) { + let dns = new DNSServer(pool, logger); + logger && logger.info(`[dns]: Listening on ${socks_port}`); + dns.serve((dns_port || 9050)); +} + +logger && logger.info('[tor]: starting tor...') +pool.create(instances, (err) => { + logger && logger.info('[tor]: tor started'); +}); \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 8bad748..ee8f585 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,4 +7,4 @@ services: - "./test:/app/test" ports: - "9050:9050" - - "53:53" \ No newline at end of file + - "53:53/udp" \ No newline at end of file diff --git a/package.json b/package.json index 8efd605..2460104 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,12 @@ }, "dependencies": { "async": "^2.1.4", + "commander": "^2.9.0", "eventemitter2": "^3.0.0", "get-port": "^2.1.0", "lodash": "^4.17.4", "native-dns": "https://github.com/znetstar/node-dns.git", - "socksv5": "^0.0.6", + "socksv5": "git+https://github.com/lee-elenbaas/socksv5.git", "temp": "^0.8.3", "winston": "^2.3.1" } diff --git a/src/DNSServer.js b/src/DNSServer.js index 83d56af..e0fb24f 100644 --- a/src/DNSServer.js +++ b/src/DNSServer.js @@ -2,23 +2,28 @@ const dns = require('native-dns'); const UDPServer = require('native-dns').UDPServer; class DNSServer extends UDPServer { - constructor(tor_pool, options, timeout) { + constructor(tor_pool, logger, options, timeout) { super(options || {}); - + this.logger = logger; this.tor_pool = tor_pool; this.on('request', (req, res) => { for (let question of req.question) { + let dns_port = (tor_pool.next().dns_port); let outbound_req = dns.Request({ question, - server: { address: '127.0.0.1', port: (tor_pool.next().dns_port), type: 'udp' }, + server: { address: '127.0.0.1', port: dns_port, type: 'udp' }, timeout: this.timeout }); outbound_req.on('message', (err, answer) => { - if (!err && answer) - answer.answer.forEach((a) => res.answer.push(a)); + if (!err && answer) { + for (let a of answer.answer){ + res.answer.push(a); + this.logger && this.logger.info(`[dns]: ${question.name} type ${dns.consts.QTYPE_TO_NAME[question.type]} → 127.0.0.1:${dns_port} → ${JSON.stringify(a)}`) + } + } }); outbound_req.on('error', (err) => { diff --git a/src/SOCKSServer.js b/src/SOCKSServer.js new file mode 100644 index 0000000..ef19e2b --- /dev/null +++ b/src/SOCKSServer.js @@ -0,0 +1,63 @@ +const socks = require('socksv5'); +const SOCKS5Server = socks.Server; + +class SOCKSServer extends SOCKS5Server{ + constructor(tor_pool, logger) { + super((info, accept, deny) => { + let inbound_socket = accept(true); + var outbound_socket; + let buffer = []; + + let socks_port = (tor_pool.next().socks_port); + logger && logger.info(`[socks]: ${info.srcAddr}:${info.srcPort} → 127.0.0.1:${socks_port} → ${info.dstAddr}:${info.dstPort}`) + + let onClose = () => { + inbound_socket && inbound_socket.end(); + outbound_socket && outbound_socket.end(); + + inbound_socket = outbound_socket = buffer = void(0); + }; + + let onInboundData = (data) => buffer.push(data) + + if (!inbound_socket) return; + + inbound_socket.on('close', onClose); + inbound_socket.on('data', onInboundData); + inbound_socket.on('error', onClose); + + socks.connect({ + host: info.dstAddr, + port: info.dstPort, + proxyHost: '127.0.0.1', + proxyPort: socks_port, + localDNS: false, + auths: [ socks.auth.None() ] + }, ($outbound_socket) => { + outbound_socket = $outbound_socket; + outbound_socket && outbound_socket.on('close', onClose); + + inbound_socket && inbound_socket.removeListener('data', onInboundData); + inbound_socket && inbound_socket.on('data', (data) => { + outbound_socket && outbound_socket.write(data); + }); + + outbound_socket && outbound_socket.on('data', (data) => { + inbound_socket && inbound_socket.write(data); + }); + + outbound_socket && outbound_socket.on('error', onClose); + + while (buffer && buffer.length && outbound_socket) { + outbound_socket.write(buffer.shift()); + } + }) + }); + + this.logger = logger; + + this.useAuth(socks.auth.None()); + } +}; + +module.exports = SOCKSServer; \ No newline at end of file diff --git a/src/TorPool.js b/src/TorPool.js index c7ac421..629eca8 100644 --- a/src/TorPool.js +++ b/src/TorPool.js @@ -26,7 +26,7 @@ const _ = require('lodash'); temp.track(); class TorPool extends EventEmitter { - constructor(tor_path, config) { + constructor(tor_path, config, logger) { super(); config = config || {}; @@ -34,20 +34,21 @@ class TorPool extends EventEmitter { this.tor_config = config; this.tor_path = tor_path || 'tor'; this._instances = []; + this.logger = logger; } get instances() { return this._instances.slice(0); } create_instance(callback) { let config = _.extend({}, this.tor_config) - let instance = new TorProcess(this.tor_path, config); + let instance = new TorProcess(this.tor_path, config, this.logger); instance.create((error) => { if (error) return callback(error); this._instances.push(instance); instance.once('error', callback) instance.once('ready', () => { - callback(null, instance); + callback && callback(null, instance); }); }); } @@ -57,11 +58,11 @@ class TorPool extends EventEmitter { async.map(Array.from(Array(Number(instances))), (nothing, next) => { this.create_instance(next); - }, callback); + }, (callback || (() => {}))); } next() { - this._instances.rotate(); + this._instances = this._instances.rotate(1); return this.instances[0]; } diff --git a/src/TorProcess.js b/src/TorProcess.js index f253e02..ba587cf 100644 --- a/src/TorProcess.js +++ b/src/TorProcess.js @@ -9,10 +9,11 @@ const EventEmitter = require('eventemitter2').EventEmitter2; temp.track(); class TorProcess extends EventEmitter { - constructor(tor_path, config) { + constructor(tor_path, config, logger) { super(); this.tor_path = tor_path || 'tor'; + this.logger = logger; this.tor_config = _.extend({ Log: 'notice stdout', @@ -78,15 +79,28 @@ class TorProcess extends EventEmitter { this.emit('error', new Error(error_message)); }); + this.once('ready', () => { + this.logger && this.logger.info(`[tor-${tor.pid}]: tor is ready`); + }); + tor.stdout.on('data', (data) => { let text = new Buffer(data).toString('utf8'); - + let msg = text.split('] ').pop(); if (text.indexOf('Bootstrapped 100%: Done') !== -1){ this.emit('ready'); } if (text.indexOf('[err]') !== -1) { - this.emit('error', new Error(text.split('] ').pop())); + this.emit('error', new Error(msg)); + this.logger && this.logger.error(`[tor-${tor.pid}]: ${msg}`); + } + + else if (text.indexOf('[notice]') !== -1) { + this.logger && this.logger.debug(`[tor-${tor.pid}]: ${msg}`); + } + + else if (text.indexOf('[warn]') !== -1) { + this.logger && this.logger.warn(`[tor-${tor.pid}]: ${msg}`); } }); diff --git a/src/index.js b/src/index.js index 2b1f7ec..df2b590 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ module.exports = { TorProcess: require('./TorProcess'), TorPool: require('./TorPool'), - DNSServer: require('./DNSServer') + DNSServer: require('./DNSServer'), + SOCKSServer: require('./SOCKSServer') }; \ No newline at end of file diff --git a/test/test.js b/test/test.js index c1bdc28..4c8cb68 100644 --- a/test/test.js +++ b/test/test.js @@ -7,6 +7,24 @@ const TorRouter = require('../'); const getPort = require('get-port'); const dns = require('native-dns'); +const get_ip = function (callback) { + request({ + url: 'http://monip.org', + agentClass: SocksAgent, + agentOptions: { + socksHost: '127.0.0.1', + socksPort: this.socks_port + } + }, (error, res, body) => { + var ip; + if (body) + ip = body.split('IP : ').pop().split('<').shift(); + + callback(error || (!body && new Error("Couldn't grab IP")), ip) + }); +}; + + describe('TorProcess', function () { const TOR_DATA_DIR = temp.mkdirSync(); const TorProcess = TorRouter.TorProcess; @@ -35,22 +53,6 @@ describe('TorProcess', function () { var old_ip = null; this.timeout(Infinity); - var get_ip = (callback) => { - request({ - url: 'http://monip.org', - agentClass: SocksAgent, - agentOptions: { - socksHost: '127.0.0.1', - socksPort: tor.socks_port - } - }, (error, res, body) => { - var ip; - if (body) - ip = body.split('IP : ').pop().split('<').shift(); - - callback(error || (!body && new Error("Couldn't grab IP")), ip) - }); - }; before('create a tor instance', function (done) { tor.once('error', done); @@ -59,7 +61,7 @@ describe('TorProcess', function () { }); it('should have an ip address', function (done) { - get_ip((err, ip) => { + get_ip.call({ socks_port: tor.socks_port }, (err, ip) => { if (err) return done(err); old_ip = ip; @@ -70,7 +72,7 @@ describe('TorProcess', function () { it('should have a new ip address after sending HUP', function (done) { tor.new_ip(); setTimeout(() => { - get_ip((err, ip) => { + get_ip.call({ socks_port: tor.socks_port }, (err, ip) => { if (err) return done(err); if (ip === old_ip) @@ -108,38 +110,46 @@ describe('TorPool', function () { describe('DNSServer', function () { var TorPool = TorRouter.TorPool; var DNSServer = TorRouter.DNSServer; - + var port; var pool = new TorPool('tor'); var dns_server = new DNSServer(pool); describe('#on("request")', function () { this.timeout(Infinity); - it('should startup a tor pool with two instances', function (done) { + + before((done) => { + getPort().then(($port) => { + port = $port; + dns_server.serve(port); + done() + }); + }) + + it('should startup tor', (done) => { + pool.create(2, done); - }); + }) it('should be able to resolve "google.com" ', function (done) { - getPort().then((port) => { - dns_server.serve(port); - - let req = dns.Request({ - question: dns.Question({ name: 'google.com', type: 'A' }), - server: { address: '127.0.0.1', port: port, type: 'udp' }, - timeout: 5000 - }); - - req.on('timeout', () => { - done && done(new Error("Request timed out")); - done = null; - }); - - req.on('message', (e, m) => { - done && done(((!m) || (!m.answer.length)) && new Error('Unable to resolve host')); - done = null; - }); - - req.send(); + + + let req = dns.Request({ + question: dns.Question({ name: 'google.com', type: 'A' }), + server: { address: '127.0.0.1', port: port, type: 'udp' }, + timeout: 5000 }); + + req.on('timeout', () => { + done && done(new Error("Request timed out")); + done = null; + }); + + req.on('message', (e, m) => { + done && done(((!m) || (!m.answer.length)) && new Error('Unable to resolve host')); + done = null; + }); + + req.send(); }); after(function () { @@ -147,4 +157,50 @@ describe('DNSServer', function () { dns_server.close(); }); }); -}); \ No newline at end of file +}); + +describe('SOCKSServer', function () { + var TorPool = TorRouter.TorPool; + var SOCKSServer = TorRouter.SOCKSServer; + + var pool = new TorPool('tor'); + var socks = new SOCKSServer(pool); + + this.timeout(Infinity); + + var port; + + before((done) => { + getPort().then(($port) => { + port = $port; + socks.listen(port, (done)); + }); + }) + + describe('#on("connection")', function () { + var ip; + + it('should startup tor ', (done) => { + pool.create(2, done); + }) + + it('should get an ip address', function (done) { + get_ip.call({ socks_port: port }, (err, _ip) => { + ip = _ip; + done(err || (!ip && new Error('could not get an ip'))) + }); + }); + + it('should have a different ip', function (done) { + get_ip.call({ socks_port: port }, (err, _ip) => { + + done(err || (!ip && new Error('could not get an ip')) || ((_ip === ip) && new Error('IP has not changed'))); + }); + }); + + after(() => { + pool.exit(); + socks.close(); + }); + }); +}); diff --git a/x.js b/x.js new file mode 100644 index 0000000..7aece33 --- /dev/null +++ b/x.js @@ -0,0 +1,9 @@ +const S = require('./').SOCKSServer; +const P = require('./').TorPool; + +let p = new P('tor', null, require('winston')); +let s = new S(p, require('winston')); + +p.create(3, () => { + s.listen(9050); +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 6e216e1..923e2be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -748,9 +748,9 @@ socks5-http-client@^1.0.2: dependencies: socks5-client "~1.1.0" -socksv5@^0.0.6: +"socksv5@git+https://github.com/lee-elenbaas/socksv5.git": version "0.0.6" - resolved "https://registry.yarnpkg.com/socksv5/-/socksv5-0.0.6.tgz#1327235ff7e8de21ac434a0a579dc69c3f071061" + resolved "git+https://github.com/lee-elenbaas/socksv5.git#7039cb6aeb39fa3187b327bb2de5d1aedaae6a56" dependencies: ipv6 "*"