This commit is contained in:
Zachary Boyd 2017-01-31 18:11:36 -05:00
parent 2962448bb1
commit e41c4d99a4
12 changed files with 268 additions and 60 deletions

View file

@ -4,6 +4,10 @@ EXPOSE 9050
EXPOSE 53 EXPOSE 53
ENV DNS_PORT 53
ENV SOCKS_PORT 9050
ADD https://deb.nodesource.com/setup_7.x /nodejs_install ADD https://deb.nodesource.com/setup_7.x /nodejs_install
RUN bash /nodejs_install RUN bash /nodejs_install

54
bin/tor-router Executable file
View file

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

View file

@ -7,4 +7,4 @@ services:
- "./test:/app/test" - "./test:/app/test"
ports: ports:
- "9050:9050" - "9050:9050"
- "53:53" - "53:53/udp"

View file

@ -16,11 +16,12 @@
}, },
"dependencies": { "dependencies": {
"async": "^2.1.4", "async": "^2.1.4",
"commander": "^2.9.0",
"eventemitter2": "^3.0.0", "eventemitter2": "^3.0.0",
"get-port": "^2.1.0", "get-port": "^2.1.0",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"native-dns": "https://github.com/znetstar/node-dns.git", "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", "temp": "^0.8.3",
"winston": "^2.3.1" "winston": "^2.3.1"
} }

View file

@ -2,23 +2,28 @@ const dns = require('native-dns');
const UDPServer = require('native-dns').UDPServer; const UDPServer = require('native-dns').UDPServer;
class DNSServer extends UDPServer { class DNSServer extends UDPServer {
constructor(tor_pool, options, timeout) { constructor(tor_pool, logger, options, timeout) {
super(options || {}); super(options || {});
this.logger = logger;
this.tor_pool = tor_pool; this.tor_pool = tor_pool;
this.on('request', (req, res) => { this.on('request', (req, res) => {
for (let question of req.question) { for (let question of req.question) {
let dns_port = (tor_pool.next().dns_port);
let outbound_req = dns.Request({ let outbound_req = dns.Request({
question, 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 timeout: this.timeout
}); });
outbound_req.on('message', (err, answer) => { outbound_req.on('message', (err, answer) => {
if (!err && answer) if (!err && answer) {
answer.answer.forEach((a) => res.answer.push(a)); 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) => { outbound_req.on('error', (err) => {

63
src/SOCKSServer.js Normal file
View file

@ -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;

View file

@ -26,7 +26,7 @@ const _ = require('lodash');
temp.track(); temp.track();
class TorPool extends EventEmitter { class TorPool extends EventEmitter {
constructor(tor_path, config) { constructor(tor_path, config, logger) {
super(); super();
config = config || {}; config = config || {};
@ -34,20 +34,21 @@ class TorPool extends EventEmitter {
this.tor_config = config; this.tor_config = config;
this.tor_path = tor_path || 'tor'; this.tor_path = tor_path || 'tor';
this._instances = []; this._instances = [];
this.logger = logger;
} }
get instances() { return this._instances.slice(0); } get instances() { return this._instances.slice(0); }
create_instance(callback) { create_instance(callback) {
let config = _.extend({}, this.tor_config) 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) => { instance.create((error) => {
if (error) return callback(error); if (error) return callback(error);
this._instances.push(instance); this._instances.push(instance);
instance.once('error', callback) instance.once('error', callback)
instance.once('ready', () => { 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) => { async.map(Array.from(Array(Number(instances))), (nothing, next) => {
this.create_instance(next); this.create_instance(next);
}, callback); }, (callback || (() => {})));
} }
next() { next() {
this._instances.rotate(); this._instances = this._instances.rotate(1);
return this.instances[0]; return this.instances[0];
} }

View file

@ -9,10 +9,11 @@ const EventEmitter = require('eventemitter2').EventEmitter2;
temp.track(); temp.track();
class TorProcess extends EventEmitter { class TorProcess extends EventEmitter {
constructor(tor_path, config) { constructor(tor_path, config, logger) {
super(); super();
this.tor_path = tor_path || 'tor'; this.tor_path = tor_path || 'tor';
this.logger = logger;
this.tor_config = _.extend({ this.tor_config = _.extend({
Log: 'notice stdout', Log: 'notice stdout',
@ -78,15 +79,28 @@ class TorProcess extends EventEmitter {
this.emit('error', new Error(error_message)); 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) => { tor.stdout.on('data', (data) => {
let text = new Buffer(data).toString('utf8'); let text = new Buffer(data).toString('utf8');
let msg = text.split('] ').pop();
if (text.indexOf('Bootstrapped 100%: Done') !== -1){ if (text.indexOf('Bootstrapped 100%: Done') !== -1){
this.emit('ready'); this.emit('ready');
} }
if (text.indexOf('[err]') !== -1) { 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}`);
} }
}); });

View file

@ -1,5 +1,6 @@
module.exports = { module.exports = {
TorProcess: require('./TorProcess'), TorProcess: require('./TorProcess'),
TorPool: require('./TorPool'), TorPool: require('./TorPool'),
DNSServer: require('./DNSServer') DNSServer: require('./DNSServer'),
SOCKSServer: require('./SOCKSServer')
}; };

View file

@ -7,6 +7,24 @@ const TorRouter = require('../');
const getPort = require('get-port'); const getPort = require('get-port');
const dns = require('native-dns'); 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 () { describe('TorProcess', function () {
const TOR_DATA_DIR = temp.mkdirSync(); const TOR_DATA_DIR = temp.mkdirSync();
const TorProcess = TorRouter.TorProcess; const TorProcess = TorRouter.TorProcess;
@ -35,22 +53,6 @@ describe('TorProcess', function () {
var old_ip = null; var old_ip = null;
this.timeout(Infinity); 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) { before('create a tor instance', function (done) {
tor.once('error', done); tor.once('error', done);
@ -59,7 +61,7 @@ describe('TorProcess', function () {
}); });
it('should have an ip address', function (done) { 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); if (err) return done(err);
old_ip = ip; old_ip = ip;
@ -70,7 +72,7 @@ describe('TorProcess', function () {
it('should have a new ip address after sending HUP', function (done) { it('should have a new ip address after sending HUP', function (done) {
tor.new_ip(); tor.new_ip();
setTimeout(() => { setTimeout(() => {
get_ip((err, ip) => { get_ip.call({ socks_port: tor.socks_port }, (err, ip) => {
if (err) return done(err); if (err) return done(err);
if (ip === old_ip) if (ip === old_ip)
@ -108,38 +110,46 @@ describe('TorPool', function () {
describe('DNSServer', function () { describe('DNSServer', function () {
var TorPool = TorRouter.TorPool; var TorPool = TorRouter.TorPool;
var DNSServer = TorRouter.DNSServer; var DNSServer = TorRouter.DNSServer;
var port;
var pool = new TorPool('tor'); var pool = new TorPool('tor');
var dns_server = new DNSServer(pool); var dns_server = new DNSServer(pool);
describe('#on("request")', function () { describe('#on("request")', function () {
this.timeout(Infinity); 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); pool.create(2, done);
}); })
it('should be able to resolve "google.com" ', function (done) { it('should be able to resolve "google.com" ', function (done) {
getPort().then((port) => {
dns_server.serve(port);
let req = dns.Request({
let req = dns.Request({ question: dns.Question({ name: 'google.com', type: 'A' }),
question: dns.Question({ name: 'google.com', type: 'A' }), server: { address: '127.0.0.1', port: port, type: 'udp' },
server: { address: '127.0.0.1', port: port, type: 'udp' }, timeout: 5000
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();
}); });
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 () { after(function () {
@ -147,4 +157,50 @@ describe('DNSServer', function () {
dns_server.close(); dns_server.close();
}); });
}); });
}); });
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();
});
});
});

9
x.js Normal file
View file

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

View file

@ -748,9 +748,9 @@ socks5-http-client@^1.0.2:
dependencies: dependencies:
socks5-client "~1.1.0" socks5-client "~1.1.0"
socksv5@^0.0.6: "socksv5@git+https://github.com/lee-elenbaas/socksv5.git":
version "0.0.6" 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: dependencies:
ipv6 "*" ipv6 "*"