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
ENV DNS_PORT 53
ENV SOCKS_PORT 9050
ADD https://deb.nodesource.com/setup_7.x /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"
ports:
- "9050:9050"
- "53:53"
- "53:53/udp"

View file

@ -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"
}

View file

@ -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) => {

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

View file

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

View file

@ -1,5 +1,6 @@
module.exports = {
TorProcess: require('./TorProcess'),
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 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();
});
});
});
});
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:
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 "*"