final
This commit is contained in:
parent
2962448bb1
commit
e41c4d99a4
|
@ -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
54
bin/tor-router
Executable 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');
|
||||
});
|
|
@ -7,4 +7,4 @@ services:
|
|||
- "./test:/app/test"
|
||||
ports:
|
||||
- "9050:9050"
|
||||
- "53:53"
|
||||
- "53:53/udp"
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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
63
src/SOCKSServer.js
Normal 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;
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module.exports = {
|
||||
TorProcess: require('./TorProcess'),
|
||||
TorPool: require('./TorPool'),
|
||||
DNSServer: require('./DNSServer')
|
||||
DNSServer: require('./DNSServer'),
|
||||
SOCKSServer: require('./SOCKSServer')
|
||||
};
|
140
test/test.js
140
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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
9
x.js
Normal 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);
|
||||
});
|
|
@ -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 "*"
|
||||
|
||||
|
|
Loading…
Reference in a new issue