From 04f185f85bb549702cdae93f59f69a6963d7cb5b Mon Sep 17 00:00:00 2001 From: Zachary Boyd Date: Fri, 10 Aug 2018 00:44:07 -0400 Subject: [PATCH] Cleans up existing test suites and adds suites for new methods --- docs/rpc-methods.md | 14 +- package.json | 3 +- src/ControlServer.js | 48 ++- src/DNSServer.js | 5 +- src/TorPool.js | 46 ++- src/TorProcess.js | 5 +- test/test.js | 894 ++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 964 insertions(+), 51 deletions(-) diff --git a/docs/rpc-methods.md b/docs/rpc-methods.md index 56d7638..910dce9 100644 --- a/docs/rpc-methods.md +++ b/docs/rpc-methods.md @@ -66,13 +66,17 @@ Cycle to the next instance using the load balancing method Shutdown all Tor instances -# getTorConfig() - -Retrieve the default Tor Config - # setTorConfig(Object) -Set the default Tor Config +Applies the configuration to all active instances + +# getDefaultTorConfig() + +Retrieve the default Tor Config for all future instances + +# setDefaultTorConfig(Object) + +Set the default Tor Config for all future instances # getLoadBalanceMethod() diff --git a/package.json b/package.json index 7fe13ea..6267e99 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,7 @@ }, "devDependencies": { "mocha": "^3.2.0", - "request": "^2.79.0", - "socks5-http-client": "^1.0.2" + "request": "^2.79.0" }, "dependencies": { "async": "^2.1.4", diff --git a/src/ControlServer.js b/src/ControlServer.js index 73ebfc2..63d6b49 100644 --- a/src/ControlServer.js +++ b/src/ControlServer.js @@ -3,6 +3,7 @@ const SOCKSServer = require('./SOCKSServer'); const DNSServer = require('./DNSServer'); const HTTPServer = require('./HTTPServer'); const rpc = require('jrpc2'); +const async = require('async'); class ControlServer { constructor(logger, nconf) { @@ -16,12 +17,14 @@ class ControlServer { server.expose('createDNSServer', this.createDNSServer.bind(this)); server.expose('createHTTPServer', this.createHTTPServer.bind(this)); + // queryInstanceAt, queryInstanceByName + server.expose('queryInstances', (function () { return new Promise((resolve, reject) => { if (!this.torPool) return reject({ message: 'No pool created' }); - resolve(this.torPool.instances.map((i) => ( { dns_port: i.dns_port, socks_port: i.socks_port, process_id: i.process.pid, config: i.definition.Config, weight: i.definition.weight } )) ); + resolve(this.torPool.instances.map((i) => ( { name: i.instance_name, dns_port: i.dns_port, socks_port: i.socks_port, process_id: i.process.pid, config: i.definition.Config, weight: i.definition.weight } )) ); }); }).bind(this)); @@ -129,21 +132,34 @@ class ControlServer { return Promise.resolve(); }).bind(this) ); - server.expose('setTorConfig', (function (config) { + server.expose('getDefaultTorConfig', (function () { + return Promise.resolve(this.nconf.get('torConfig')); + }).bind(this)); + + server.expose('setDefaultTorConfig', (function (config) { this.nconf.set('torConfig', config); return Promise.resolve(); }).bind(this)); - server.expose('getTorConfig', (function () { - return Promise.resolve(this.nconf.get('torConfig')); + server.expose('setTorConfig', (function (config) { + return new Promise((resolve, reject) => { + async.each(Object.keys(config), function (key, next) { + var value = config[key]; + + this.torPool.set_config_all(key, value, next); + }, function (error){ + if (error) reject(error); + resolve(); + }); + }); }).bind(this)); server.expose('getLoadBalanceMethod', (function () { - return Promise.resolve(this.nconf.get('loadBalanceMethod')); + return Promise.resolve(this.torPool.load_balance_method); }).bind(this)); server.expose('setLoadBalanceMethod', (function (loadBalanceMethod) { - this.nconf.set('loadBalanceMethod', loadBalanceMethod); + this.torPool.load_balance_method = loadBalanceMethod; return Promise.resolve(); }).bind(this)); @@ -215,7 +231,7 @@ class ControlServer { listen(port, callback) { this.tcpTransport = new rpc.tcpTransport({ port }); this.tcpTransport.listen(this.server); - callback(); + callback && callback(); } close() { @@ -223,32 +239,32 @@ class ControlServer { } createTorPool(options) { - this.torPool = new TorPool(nconf.get('torPath'), options, nconf.get('parentDataDirectory'), nconf.get('loadBalanceMethod'), nconf.get('granaxOptions'), this.logger); - return Promise.resolve(); + this.torPool = new TorPool(this.nconf.get('torPath'), options, this.nconf.get('parentDataDirectory'), this.nconf.get('loadBalanceMethod'), this.nconf.get('granaxOptions'), this.logger); + return this.torPool; } - createSOCKSServer(port) { + createSOCKSServer(port, callback) { this.socksServer = new SOCKSServer(this.torPool, this.logger); this.socksServer.listen(port || 9050); this.logger.info(`[socks]: Listening on ${port}`); this.socksServer; - return Promise.resolve(); + callback && callback(); } - createHTTPServer(port) { + createHTTPServer(port, callback) { this.httpServer = new HTTPServer(this.torPool, this.logger); this.httpServer.listen(port || 9080); this.logger.info(`[http]: Listening on ${port}`); this.httpServer; - return Promise.resolve(); + callback && callback(); } - createDNSServer(port) { - this.dnsServer = new DNSServer(this.torPool, nconf.get('dns:options'), this.nconf.get('dns:timeout'), this.logger); + createDNSServer(port, callback) { + this.dnsServer = new DNSServer(this.torPool, this.nconf.get('dns:options'), this.nconf.get('dns:timeout'), this.logger); this.dnsServer.serve(port || 9053); this.logger.info(`[dns]: Listening on ${port}`); this.dnsServer; - return Promise.resolve(); + callback && callback(); } }; diff --git a/src/DNSServer.js b/src/DNSServer.js index ea7491d..cac996b 100644 --- a/src/DNSServer.js +++ b/src/DNSServer.js @@ -7,7 +7,7 @@ class DNSServer extends UDPServer { this.logger = logger; this.tor_pool = tor_pool; - this.on('request', (req, res) => { + var handle_dns_request = (req, res) => { let connect = (tor_instance) => { for (let question of req.question) { let dns_port = (tor_instance.dns_port); @@ -45,7 +45,8 @@ class DNSServer extends UDPServer { this.logger.debug(`[dns]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`); this.tor_pool.once('instance_created', connect); } - }); + }; + this.on('request', handle_dns_request); } }; diff --git a/src/TorPool.js b/src/TorPool.js index fd562cb..ac37c70 100644 --- a/src/TorPool.js +++ b/src/TorPool.js @@ -37,8 +37,10 @@ const load_balance_methods = { return [ instance.id, instance.definition.Weight, instance ] }) ); - } - return instances._weighted_list.peek(instances.length).map((element) => element.data); + }; + let i = instances._weighted_list.peek(instances.length).map((element) => element.data); + i._weighted_list = instances._weighted_list; + return i; } }; @@ -57,12 +59,13 @@ class TorPool extends EventEmitter { } get instances() { - return this._instances.filter((tor) => tor.ready).slice(0); + return this._instances.slice(0).filter((tor) => tor.ready); } create_instance(instance_definition, callback) { - instance_definition.Config = instance_definition.Config || {}; - instance_definition.Config = _.extend(instance_definition.Config, this.default_tor_config); + this._instances._weighted_list = void(0); + if (!instance_definition.Config) + instance_definition.Config = _.extend({}, this.default_tor_config); let instance_id = nanoid(); instance_definition.Config.DataDirectory = instance_definition.Config.DataDirectory || path.join(this.data_directory, (instance_definition.Name || instance_id)); let instance = new TorProcess(this.tor_path, instance_definition.Config, this.granax_options, this.logger); @@ -101,7 +104,12 @@ class TorPool extends EventEmitter { return this.instances.filter((i) => i.definition.Name === name)[0]; } + instance_at(index) { + return this.instances[index]; + } + remove(instances, callback) { + this._instances._weighted_list = void(0); let instances_to_remove = this._instances.splice(0, instances); async.each(instances_to_remove, (instance, next) => { instance.exit(next); @@ -109,14 +117,18 @@ class TorPool extends EventEmitter { } remove_at(instance_index, callback) { - let instance = this._instances.splice(instance_index, 1); - instance.exit(callback); + this._instances._weighted_list = void(0); + + let instance = this._instances.splice(instance_index, 1)[0]; + instance.exit((error) => { + callback(error); + }); } remove_by_name(instance_name, callback) { let instance = this.instance_by_name(instance_name); if (!instance) return callback && callback(new Error(`Instance "${name}" not found`)); - let instance_index = (this.instances.indexOf(instance));; + let instance_index = (this.instances.indexOf(instance)); return this.remove_at(instance_index, callback); } @@ -126,7 +138,8 @@ class TorPool extends EventEmitter { } exit(callback) { - async.each(this.instances, (instance,next) => { + async.until(() => { return this._instances.length === 0; }, (next) => { + var instance = this._instances.shift(); instance.exit(next); }, (error) => { callback && callback(error); @@ -189,9 +202,22 @@ class TorPool extends EventEmitter { instance.set_config(keyword, value, callback); } + set_config_all(keyword, value, callback) { + var i = 0; + async.until(() => { return i === this.instances.length; }, (next) => { + let instance = this.instances[i++]; + + instance.set_config(keyword, value, (error) => { + next(error); + }); + }, (error) => { + callback(error); + }); + } + signal_all(signal, callback) { async.each(this.instances, (instance, next) => { - instance.signal(signal, callback); + instance.signal(signal, next); }, callback); } diff --git a/src/TorProcess.js b/src/TorProcess.js index a779502..2e10084 100644 --- a/src/TorProcess.js +++ b/src/TorProcess.js @@ -117,7 +117,8 @@ class TorProcess extends EventEmitter { }); }] }, (error, context) => { - if (error) callback(error); + if (error) + return callback && callback(error); this._dns_port = context.dnsPort; this._socks_port = context.socksPort; @@ -132,7 +133,7 @@ class TorProcess extends EventEmitter { tor.on('close', (code) => { this.emit('process_exit', code); if (this.definition && !this.definition.Name) { - del.sync(this.tor_config.DataDirectory); + del.sync(this.tor_config.DataDirectory, { force: true }); } }); diff --git a/test/test.js b/test/test.js index a1deca6..6046f83 100644 --- a/test/test.js +++ b/test/test.js @@ -1,19 +1,18 @@ -const SocksAgent = require('socks5-http-client/lib/Agent'); +const SocksAgent = require('socks-proxy-agent'); const request = require('request'); const async = require('async'); -const temp = require('temp'); -temp.track(); const TorRouter = require('../'); const getPort = require('get-port'); const dns = require('native-dns'); const _ = require('lodash'); +const assert = require('assert'); const winston = require('winston'); +const del = require('del'); +const rpc = require('jrpc2'); +const fs = require('fs'); var colors = require('mocha/lib/reporters/base').colors; -colors['diff added'] = 32; -colors['diff removed'] = 31; - -var nconf = require('nconf') +var nconf = require('nconf'); nconf = require(`${__dirname}/../src/nconf_load_env.js`)(nconf); nconf.defaults(require(`${__dirname}/../src/default_config.js`)); @@ -23,12 +22,13 @@ var logger = winston.createLogger({ transports: [new (require('winston-null-transport'))() ] }); +const WAIT_FOR_CREATE = 120000; +const PAGE_LOAD_TIME = 30000; + describe('TorProcess', function () { - const torDataDir = temp.mkdirSync(); - - var tor = new (TorRouter.TorProcess)(nconf.get('torPath'), { DataDirectory: torDataDir, ProtocolWarnings: 0 }, null, logger); + var tor = new (TorRouter.TorProcess)(nconf.get('torPath'), { DataDirectory: nconf.get('parentDataDirectory'), ProtocolWarnings: 0 }, null, logger); describe('#create()', function () { - this.timeout(60000); + this.timeout(WAIT_FOR_CREATE); it('should create the child process', function (done) { tor.create(done); @@ -92,8 +92,874 @@ describe('TorProcess', function () { }); }); - after('shutdown tor', function () { - tor.exit(); - require('fs').unlinkSync(torDataDir); + after('shutdown tor', function (done) { + tor.exit(done); }); +}); + +var torPool; + +describe('TorPool', function () { + torPool = new (TorRouter.TorPool)(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null, logger); + + describe('#create_instance(instance_defintion)', function () { + var instance_defintion = { + Name: 'instance-1', + Config: { + ProtocolWarnings: 1 + } + }; + + it('should create one tor instance based on the provided definition', function (done) { + this.timeout(WAIT_FOR_CREATE); + torPool.create_instance(instance_defintion, (err, _instance) => { + done(err); + }); + }); + + it('one instance should exist in the instances collection', function () { + assert.equal(1, torPool.instances.length); + }); + + it('the created instance should have the defintion properties as the input definition', function () { + assert.deepEqual(instance_defintion, torPool.instances[0].definition); + }); + + it('the created instance should have the same config properties specified in the definiton', function (done) { + torPool.instances[0].get_config('ProtocolWarnings', (err, v) => { + if (err) + return done(err); + done(null, (v === instance_defintion.Config.ProtocolWarnings)); + }); + }); + + after('shutdown tor pool', function (done) { + torPool.exit(done); + }); + }); + + describe('#add(instance_defintions)', function () { + var instance_defintions = [ + { Name: 'instance-1', Config: { ProtocolWarnings: 1} }, + { Name: 'instance-2', Config: { ProtocolWarnings: 1 } } + ]; + + it('should create instances from several instance definitions', function (done) { + this.timeout(WAIT_FOR_CREATE*2); + torPool.add(instance_defintions, function (error) { + done(error) + }); + }); + + it('2 instances should exist in the pool', function () { + assert.equal(2, torPool.instances.length); + }); + + it('the created instances should have the same defintion properties as the input definitions', function () { + assert.deepEqual(instance_defintions, torPool.instances.map((i) => i.definition)); + }); + + it('the created instances should have the same config properties specified in the definiton', function (done) { + this.timeout(10000); + async.map(torPool.instances, function grabConfig(instance, next) { + instance.get_config('ProtocolWarnings', next); + }, function (err, values) { + if (err) return done(err); + done(null, values.every((v) => v === 1)); + }); + }); + + after('shutdown tor pool', function (done) { + torPool.exit(done); + }); + }); + + describe('#create(number_of_instances)', function () { + torPool.default_tor_config = { TestSocks: 1 }; + it('should create 2 instances with the default config', function (done) { + this.timeout(WAIT_FOR_CREATE*2); + torPool.create(2, done); + }); + + it('2 instances should exist in the pool', function () { + assert.equal(2, torPool.instances.length); + }); + + it('the created instances should have the same config properties specified in the default config', function (done) { + this.timeout(10000); + async.map(torPool.instances, function grabConfig(instance, next) { + instance.get_config('ProtocolWarnings', next); + }, function (err, values) { + if (err) return done(err); + done(null, values.every((v) => v === 1)); + }); + }); + + after('shutdown tor pool', function (done) { + torPool.default_tor_config = {}; + torPool.exit(done); + }); + }); + + + describe('#next()', function () { + before('create tor instances', function (done) { + this.timeout(WAIT_FOR_CREATE * 3); + torPool.add([ + { + Name: 'instance-1', + Weight: 50 + }, + { + Name: 'instance-2', + Weight: 25 + }, + { + Name: 'instance-3', + Weight: 2 + } + ], done); + }); + + it('result of next should be different if run twice', function () { + var t1 = torPool.next().instance_name; + var t2 = torPool.next().instance_name; + assert.notEqual(t1, t2); + }); + }); + + describe('#instance_by_name(instance_name)', function () { + it('should retrieve instance by name', function () { + assert.ok(torPool.instance_by_name('instance-1')); + }); + }); + + describe('#remove_by_name(instance_name)', function () { + this.timeout(5000); + it('should remove instance by name', function (done) { + torPool.remove_by_name('instance-3', done); + }); + }); + + describe('#instance_at(index)', function () { + this.timeout(5000); + it('should retrieve an instance by id', function () { + assert.ok(torPool.instance_at(0)); + }); + }); + + describe('#remove_at(index)', function () { + this.timeout(5000); + it('should remove an instance by id', function (done) { + torPool.remove_at(1, done); + }); + }); + + describe('#new_identites()', function () { + this.timeout(5000); + it('should signal to retrieve a new identity to all instances', function (done) { + torPool.new_identites(done); + }); + }); + + describe('#new_identity_at(index)', function () { + this.timeout(5000); + it('should signal to retrieve a new identity identified by index', function (done) { + torPool.new_identity_at(0, done); + }); + }); + + describe('#new_identity_by_name(instance_name)', function () { + this.timeout(5000); + it('should signal to retrieve a new identity identified by name', function (done) { + torPool.new_identity_by_name('instance-1', done); + }); + }); + + + describe('#new_ips(index)', function () { + this.timeout(5000); + it('should signal to retrieve a new identity to all instances', function (done) { + torPool.new_ips(done); + }); + }); + + describe('#new_ip_at(instance_name)', function () { + this.timeout(5000); + it('should signal to retrieve a new identity identified by index', function (done) { + torPool.new_ip_at(0, done); + }); + }); + + describe('#set_config_all(keyword, value)', function () { + it('should set configuration on all active instances', function (done) { + this.timeout(5000); + torPool.set_config_all('TestSocks', 1, done); + }); + + it('all instances should contain the same changed configuration', function (done) { + this.timeout(5000); + + async.map(torPool.instances, (instance, next) => { + instance.get_config('TestSocks', next); + }, function (error, results) { + if (error) return done(error); + done(null, results.every((r) => r === 1)); + }); + }); + + after('unset config options', function (done) { + torPool.set_config_all('TestSocks', 0, done); + }); + }); + + describe('#set_config_by_name(name, keyword, value)', function () { + this.timeout(5000); + it('should set a configuration property of an instance identified by name', function (done) { + torPool.set_config_by_name('instance-1', 'ProtocolWarnings', 1, done); + }); + }); + + describe('#get_config_by_name(name, keyword)', function () { + this.timeout(5000); + it('should get retrieve the configuration of an instance identified by name', function (done) { + torPool.get_config_by_name('instance-1', 'ProtocolWarnings', (error, value) => { + if (error) return done(error); + + done(null, (value === 1)); + }); + }); + }); + + describe('#set_config_at(index, keyword, value)', function () { + this.timeout(5000); + it('should set a configuration property of an instance identified by index', function (done) { + torPool.set_config_at(0, 'ProtocolWarnings', 0, done); + }); + }); + + describe('#get_config_at(index, keyword)', function () { + this.timeout(5000); + it('should get retrieve the configuration of an instance identified by name', function (done) { + torPool.get_config_at(0, 'ProtocolWarnings', (error, value) => { + if (error) return done(error); + + done(null, (value === 0)); + }); + }); + }); + + describe('#signal_all(signal)', function () { + this.timeout(5000); + it('should send a signal to all instances', function (done) { + torPool.signal_all('DEBUG', done); + }); + }); + + describe('#signal_by_name(name, signal)', function () { + this.timeout(5000); + it('should send a signal to an instance identified by name', function (done) { + torPool.signal_by_name('instance-1', 'DEBUG', done); + }); + }); + + describe('#signal_at(index, signal)', function () { + this.timeout(5000); + it('should send a signal to an instance identified by index', function (done) { + torPool.signal_at(0, 'DEBUG', done); + }); + }); + + after('shutdown tor pool', function (done) { + torPool.exit(done); + }); +}); + +var socksServerTorPool; +var socksServer; +describe('SOCKSServer', function () { + socksServerTorPool = new (TorRouter.TorPool)(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null, logger); + socksServer = new (TorRouter.SOCKSServer)(socksServerTorPool, logger); + var socksPort; + before('start up server', function (done){ + this.timeout(WAIT_FOR_CREATE); + async.waterfall([ + (callback) => { socksServerTorPool.create(1, callback); }, + (callback) => { getPort().then((port) => callback(null, port)); }, + (port, callback) => { + socksPort = port; + socksServer.listen(port); + callback(); + } + ], done); + }); + + describe('#handleConnection(socket)', function () { + it('should service a request for example.com', function (done) { + this.timeout(PAGE_LOAD_TIME); + + var req = request({ + url: 'http://example.com', + agent: new SocksAgent(`socks://localhost:${socksPort}`) + }); + + req.on('error', function (error) { + done(error); + }); + + req.on('response', function (res) { + done(); + }) + }); + }); + + after('shutdown tor pool', function (done) { + socksServerTorPool.exit(done); + }); +}); + + +var httpServerTorPool; +var httpServer; +describe('HTTPServer', function () { + httpServerTorPool = new (TorRouter.TorPool)(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null, logger); + httpServer = new (TorRouter.HTTPServer)(httpServerTorPool, logger); + var httpPort; + before('start up server', function (done){ + this.timeout(WAIT_FOR_CREATE); + async.waterfall([ + (callback) => { httpServerTorPool.create(1, callback); }, + (callback) => { getPort().then((port) => callback(null, port)); }, + (port, callback) => { + httpPort = port; + httpServer.listen(port); + callback(); + } + ], done); + }); + + describe('#handle_http_connections(req, res)', function () { + it('should service a request for example.com', function (done) { + this.timeout(PAGE_LOAD_TIME); + + var req = request({ + url: 'http://example.com', + proxy: `http://localhost:${httpPort}` + }); + + req.on('error', function (error) { + done(error); + }); + + req.on('response', function (res) { + done(); + }) + }); + }); + + describe('#handle_connect_connections(req, inbound_socket, head)', function () { + it('should service a request for example.com', function (done) { + this.timeout(PAGE_LOAD_TIME); + + var req = request({ + url: 'https://example.com', + proxy: `http://localhost:${httpPort}` + }); + + req.on('error', function (error) { + done(error); + }); + + req.on('response', function (res) { + done(); + }) + }); + }); + + after('shutdown tor pool', function (done) { + httpServerTorPool.exit(done); + }); +}); + +var dnsServerTorPool; +var dnsServer; +describe('DNSServer', function () { + dnsServerTorPool = new (TorRouter.TorPool)(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null, logger); + dnsServer = new (TorRouter.DNSServer)(dnsServerTorPool, {}, 10000, logger); + var dnsPort; + before('start up server', function (done){ + this.timeout(WAIT_FOR_CREATE); + async.waterfall([ + (callback) => { dnsServerTorPool.create(1, callback); }, + (callback) => { getPort().then((port) => callback(null, port)); }, + (port, callback) => { + dnsPort = port; + dnsServer.serve(port); + callback(); + } + ], done); + }); + + describe('#handle_dns_request(req, res)', function () { + it('should service a request for example.com', function (done) { + this.timeout(10000); + + var req = dns.Request({ + question: dns.Question({ + name: 'example.com', + type: 'A' + }), + server: { address: '127.0.0.1', port: dnsPort, type: 'udp' }, + timeout: 1000, + }); + + req.on('timeout', function () { + done(new Error('Connection timed out')); + }); + + req.on('message', function () { + done(); + }); + + req.send(); + }); + }); + + after('shutdown tor pool', function (done) { + dnsServerTorPool.exit(done); + }); +}); + +var controlServer = new (TorRouter.ControlServer)(logger, nconf); +var controlPort; +describe('ControlServer', function () { + describe('#listen(port)', function () { + it('should bind to a given port', function (done) { + getPort().then((port) => { + controlPort = port; + controlServer.listen(port, done); + }); + }); + }); + describe('#createTorPool(options)', function () { + it('should create a TorPool with a given configuration', function () { + let torPool = controlServer.createTorPool({ ProtocolWarnings: 1 }); + + assert.ok((controlServer.torPool instanceof (TorRouter.TorPool))); + assert.equal(1, torPool.default_tor_config.ProtocolWarnings); + }); + }); + describe('#createSOCKSServer(port)', function () { + it('should create a SOCKS Server', function (done) { + getPort().then((port) => { + controlServer.createSOCKSServer(port); + done(null, ( + (controlServer.socksServer instanceof (TorRouter.SOCKSServer)) + )); + }); + }); + }); + describe('#createDNSServer(port)', function () { + it('should create a DNS Server', function (done) { + getPort().then((port) => { + controlServer.createDNSServer(port); + done(null, ( + (controlServer.dnsServer instanceof (TorRouter.DNSServer)) + )); + }); + }); + }); + describe('#createHTTPServer(port)', function () { + it('should create a HTTP Server', function (done) { + getPort().then((port) => { + controlServer.createHTTPServer(port); + done(null, ( + (controlServer.httpServer instanceof (TorRouter.HTTPServer)) + )); + }); + }); + }); + + describe('#close()', function () { + it('should close the RPC Server', function () { + controlServer.close(); + }); + }); + + after('shutdown tor pool', function (done) { + controlServer.torPool.exit(done); + }); +}); + +var rpcControlServer = new (TorRouter.ControlServer)(logger, nconf); +var rpcControlPort; +var rpcClient; +describe('ControlServer - RPC', function () { + before('setup control server', function (done) { + async.waterfall([ + (callback) => { getPort().then((port) => callback(null, port)); }, + (port, callback) => { rpcControlPort = port; rpcControlServer.listen(port, callback); }, + (callback) => { + rpcClient = new rpc.Client(new rpc.tcpTransport({ port: rpcControlPort, hostname: 'localhost' })); + return callback(); + } + ], done); + }); + + describe('#createInstances(number_of_instances)', function () { + this.timeout(WAIT_FOR_CREATE*2); + it('should create an instance', function (done) { + rpcClient.invoke('createInstances', [2], function (error) { + if (error) + return done(error); + done(); + }); + }); + }); + + describe('#queryInstances()', function () { + this.timeout(3000); + it('should return a list of instances', function (done) { + rpcClient.invoke('queryInstances', [], function (error, raw) { + if (error) + return done(error); + + var instances = JSON.parse(raw).result; + + if (!Array.isArray(instances)) + done(new Error('Did not return an array')); + + done(null, (instances.every((i) => (typeof(i.name) !== 'undefined') && (i.name !== null)) && instances.length)); + }); + }); + }); + + describe('#addInstances(definitions)', function () { + this.timeout(WAIT_FOR_CREATE); + it("should add an instance based on a defintion", function (done) { + var def = { + Name: 'instance-1' + }; + rpcClient.invoke('addInstances', [ [ def ] ], done); + }); + + it("tor pool should now contain and instance that has the same name as the name specified in the defintion", function () { + assert.ok(rpcControlServer.torPool.instance_by_name('instance-1')); + }); + }); + + describe('#newIdentites()', function () { + this.timeout(3000); + it('should request new identities for all instances', function (done) { + rpcClient.invoke('newIdentites', [], done); + }); + }); + + describe('#newIdentityByName(instance_name)', function () { + this.timeout(3000); + it('should request new identities for all instances', function (done) { + rpcClient.invoke('newIdentityByName', ['instance-1'], done); + }); + }); + + describe('#newIdentityAt(index)', function () { + this.timeout(3000); + it('should request new identities for all instances', function (done) { + rpcClient.invoke('newIdentityAt', [0], done); + }); + }); + + describe("#setTorConfig(config_object)", function () { + this.timeout(3000); + it('should set several config variables on all instances', function (done) { + rpcClient.invoke('setTorConfig', [ { TestSocks: 1, ProtocolWarnings: 1 } ], done); + }); + + it('all instances should have the modified variables', function(done) { + async.map(rpcControlServer.torPool.instances, (instance, next) => { + async.series([ + (cb) => { instance.get_config('TestSocks', next); }, + (cb) => { instance.get_config('ProtocolWarnings', next); } + ], next); + }, (error, results) => { + if (error) return done(error); + + done(null, results.every((i) => i[0] === 1 && i[1] === 1)); + }); + }); + + after('unset config variables', function (done) { + async.series([ + (cb) => { rpcControlServer.torPool.set_config_all('TestSocks', 0, cb); }, + (cb) => { rpcControlServer.torPool.set_config_all('ProtocolWarnings', 0, cb); } + ], done); + }); + }); + + describe('#setDefaultTorConfig(object)', function () { + it('should set the default config of new instances', function (done) { + this.timeout(3000); + rpcClient.invoke('setDefaultTorConfig', [ { TestSocks: 1 } ], done); + }); + + it('a new instance should be created with the modified property', function (done) { + this.timeout(WAIT_FOR_CREATE); + + rpcControlServer.torPool.create_instance({ Name: 'config-test' }, (err) => { + if (err) return done(err); + + rpcControlServer.torPool.instance_by_name('config-test').get_config('TestSocks', (err, val) => { + if (err) return done(err); + + done(null, val === 1); + }); + }); + }); + + after('remove instance', function (done) { + this.timeout(10000); + nconf.set('torConfig', {}); + rpcControlServer.torPool.remove_by_name('config-test', done); + }); + }); + + describe('#getDefaultTorConfig()', function () { + before('set tor config', function () { + nconf.set('torConfig', { TestSocks: 1 }); + }); + + it('should return a tor config with a modified property', function (done) { + this.timeout(3000); + rpcClient.invoke('getDefaultTorConfig', [ { } ], function (error, raw) { + if (error) return done(error); + + var config = JSON.parse(raw).result; + + done(null, (config.TestSocks === 1)) + }); + }); + + after('unset property', function () { + nconf.set('torConfig', {}); + }); + }); + + describe('#getLoadBalanceMethod()', function () { + this.timeout(3000); + before(function () { + rpcControlServer.torPool.load_balance_method = 'round_robin'; + }); + + it('should return the current load balance method', function (done) { + rpcClient.invoke('getLoadBalanceMethod', [], function (error, raw) { + if (error) return done(error); + var lb_method = JSON.parse(raw).result; + done(null, (lb_method === 'round_robin')); + }); + }); + }); + + describe('#setLoadBalanceMethod(load_balance_method)', function () { + this.timeout(3000); + + it('should set the load balance method', function (done) { + rpcClient.invoke('setLoadBalanceMethod', ['weighted'], function (error) { + return done(error); + }); + }); + + it('the load balance method should be changed', function () { + assert.equal(rpcControlServer.torPool.load_balance_method, 'weighted'); + }); + + after(function () { + rpcControlServer.torPool.load_balance_method = 'round_robin'; + }); + }); + + describe("#getInstanceConfigByName(instance_name)", function () { + this.timeout(3000); + + before('set config property', function (done) { + rpcControlServer.torPool.instance_by_name('instance-1').set_config('TestSocks', 1, done); + }); + + it('should retrieve the property from the tor instance', function (done) { + rpcClient.invoke('getInstanceConfigByName', ['instance-1'], function (error, raw) { + if (error) return done(error); + + var value = JSON.parse(raw).result; + + done(null, value === 1); + }); + }); + + after('unset config property', function (done) { + rpcControlServer.torPool.instance_by_name('instance-1').set_config('TestSocks', 0, done); + }); + }); + + describe("#getInstanceConfigAt(index)", function () { + this.timeout(3000); + + before('set config property', function (done) { + rpcControlServer.torPool.instance_at(0).set_config('TestSocks', 1, done); + }); + + it('should retrieve the property from the tor instance', function (done) { + rpcClient.invoke('getInstanceConfigByName', [0], function (error, raw) { + if (error) return done(error); + + var value = JSON.parse(raw).result; + + done(null, value === 1); + }); + }); + + after('unset config property', function (done) { + rpcControlServer.torPool.instance_at(0).set_config('TestSocks', 0, done); + }); + }); + + describe("#setInstanceConfigByName(instance_name)", function () { + this.timeout(3000); + + before('set config property', function (done) { + rpcControlServer.torPool.instance_by_name('instance-1').set_config('TestSocks', 0, done); + }); + + it('should set the property for the tor instance', function (done) { + rpcClient.invoke('setInstanceConfigByName', ['instance-1', 'TestSocks', 1], function (error, value) { + done(error); + }); + }); + + it('tor instance should have the modified property', function (done) { + rpcControlServer.torPool.instance_by_name('instance-1').get_config('TestSocks', function (error, value) { + if (error) return done(error); + done(null, (value === 1)); + }); + }); + + after('unset config property', function (done) { + rpcControlServer.torPool.instance_by_name('instance-1').set_config('TestSocks', 0, done); + }); + }); + + describe("#setInstanceConfigAt(index)", function () { + this.timeout(3000); + + before('set config property', function (done) { + rpcControlServer.torPool.instance_at(0).set_config('TestSocks', 0, done); + }); + + it('should set the property for the tor instance', function (done) { + rpcClient.invoke('setInstanceConfigAt', [0, 'TestSocks', 1], function (error, value) { + done(error); + }); + }); + + it('tor instance should have the modified property', function (done) { + rpcControlServer.torPool.instance_at(0).get_config('TestSocks', function (error, value) { + if (error) return done(error); + done(null, (value === 1)); + }); + }); + + after('unset config property', function (done) { + rpcControlServer.torPool.instance_at(0).set_config('TestSocks', 0, (err) => { + done(err); + }); + }); + }); + + describe('#signalAllInstances(signal)', function () { + this.timeout(3000); + it('should signal to all interfaces', function (done) { + rpcClient.invoke('signalAllInstances', [ 'DEBUG' ], function (error) { + done(error); + }); + }); + }); + + describe('#signalInstanceAt(signal)', function () { + this.timeout(3000); + it('should signal to all interfaces', function (done) { + rpcClient.invoke('signalInstanceAt', [ 0, 'DEBUG' ], function (error) { + done(error); + }); + }); + }); + + describe('#signalAllInstances(signal)', function () { + this.timeout(3000); + it('should signal to all interfaces', function (done) { + rpcClient.invoke('signalInstanceByName', [ 'instance-1', 'DEBUG' ], function (error) { + done(error); + }); + }); + }); + + describe("#nextInstance()", function () { + this.timeout(3000); + var i_name; + it('should rotate the 0th item in the instances array', function (done) { + i_name = rpcControlServer.torPool.instances[0].instance_name; + rpcClient.invoke('nextInstance', [], function (error) { + done(error); + }); + }); + + it('0th item in the instances array should be different after nextInstance is called', function () { + assert.notEqual(rpcControlServer.torPool.instances[0].instance_name, i_name); + }); + }); + var instance_num1, instance_num2, i_num; + describe('#removeInstanceAt(index)', function () { + this.timeout(10000); + it("should remove an instance at the position specified", function (done) { + instance_num1 = rpcControlServer.torPool.instances.length; + rpcClient.invoke('removeInstanceAt', [0], function (error) { + done(error); + }); + }); + + it('the tor pool should contain one instance fewer', function () { + assert.equal(rpcControlServer.torPool.instances.length, (instance_num1 - 1)); + }); + }); + + describe('#removeInstanceByName(instance_name)', function () { + this.timeout(10000); + it("should remove an instance at the position specified", function (done) { + instance_num2 = rpcControlServer.torPool.instances.length; + rpcClient.invoke('removeInstanceByName', [ "instance-1" ], function (error) { + done(error); + }); + }); + + it('the tor pool should contain one instance fewer', function () { + assert.equal(rpcControlServer.torPool.instances.length, (instance_num2 - 1)); + }); + }); + + describe('#closeInstances()', function () { + this.timeout(10000); + it('should shutdown all instances', function (done) { + i_num = rpcControlServer.torPool.instances.length; + rpcClient.invoke('closeInstances', [ ], function (error) { + done(error); + }); + }); + + it('no instances should be present in the pool', function () { + assert.equal(rpcControlServer.torPool.instances.length, 0); + assert.notEqual(rpcControlServer.torPool.instances.length, i_num); + }); + }); + + after('shutdown tor pool', function (done) { + this.timeout(10000); + rpcControlServer.torPool.exit(done); + }); }); \ No newline at end of file