From 22908b7bba2f0ece197eaa305cb003686b4648ef Mon Sep 17 00:00:00 2001 From: Zachary Boyd Date: Mon, 10 Sep 2018 20:53:33 -0400 Subject: [PATCH] Adds group functionality to TorPool --- CHANGELOG.md | 1 + src/ControlServer.js | 4 +- src/TorPool.js | 99 +++++++++++++++++++++++++++++++++++++++++-- src/TorProcess.js | 20 ++++++--- src/default_config.js | 2 +- src/launch.js | 8 +++- test/TorProcess.js | 2 +- 7 files changed, 122 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d75556..a8ffafd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - The `Config` property of instance definitions will now inherit from `TorPool.default_tor_config`. - The mocha test has been split into individual files all under `test/` - DNS shows the source/destination hostname/port in logs instead of what the query was resolved to +- `TorProcess` takes an instance definition as the second argument in its constructor. ### Removes - The `new_ips` and `new_ip_at` TorPool and `new_ip` TorProcess have been removed. Use `new_identites`, `new_identity_at` and `new_identity` instead. diff --git a/src/ControlServer.js b/src/ControlServer.js index 8b85181..5582eec 100644 --- a/src/ControlServer.js +++ b/src/ControlServer.js @@ -144,14 +144,14 @@ class ControlServer { } async createSOCKSServer(port, hostname) { - this.socksServer = new SOCKSServer(this.torPool, this.logger, (this.nconf.get('proxyByName') ? { deny_unidentified_users: this.nconf.get('denyUnidentifedUsers') } : "")); + this.socksServer = new SOCKSServer(this.torPool, this.logger, (this.nconf.get('proxyByName') ? { mode: this.nconf.get('proxyByName'), deny_unidentified_users: this.nconf.get('denyUnidentifedUsers') } : "")); await this.socksServer.listen(port || default_ports.socks, hostname); this.logger.info(`[socks]: listening on socks5://${hostname}:${port}`); this.socksServer; } async createHTTPServer(port, hostname) { - this.httpServer = new HTTPServer(this.torPool, this.logger, (this.nconf.get('proxyByName') ? { deny_unidentified_users: this.nconf.get('denyUnidentifedUsers') } : "")); + this.httpServer = new HTTPServer(this.torPool, this.logger, (this.nconf.get('proxyByName') ? { mode: this.nconf.get('proxyByName'), deny_unidentified_users: this.nconf.get('denyUnidentifedUsers') } : "")); await this.httpServer.listen(port || default_ports.http, hostname); this.logger.info(`[http]: listening on http://${hostname}:${port}`); this.httpServer; diff --git a/src/TorPool.js b/src/TorPool.js index 0cecc92..51786cf 100644 --- a/src/TorPool.js +++ b/src/TorPool.js @@ -45,6 +45,100 @@ class TorPool extends EventEmitter { this.granax_options = granax_options; } + get group_names() { + return _.uniq(_.flatten(this.instances.map((instance) => instance.instance_group).filter(Boolean))); + } + + add_instance_to_group(group, instance) { + instance.definition.Group = _.union(instance.instance_group, [group]); + } + + add_instance_to_group_by_name(group, instance_name) { + let instance = this.instance_by_name(instance_name); + + if (!instance) throw new Error(`Instance "${instance_name}" not found`); + + return this.add_instance_to_group(group, instance); + } + + add_instance_to_group_at(group, instance_index) { + let instance = this.instance_at(instance_index); + + if (!instance) throw new Error(`Instance at "${instance_index}" not found`); + + return this.add_instance_to_group(group, instance); + } + + remove_instance_from_group(group, instance) { + _.remove(instance.definition.Group, (g) => g === group); + } + + remove_instance_from_group_by_name(group, instance_name) { + let instance = this.instance_by_name(instance_name); + + if (!instance) throw new Error(`Instance "${instance_name}" not found`); + + return this.remove_instance_from_group(group, instance); + } + + remove_instance_from_group_at(group, instance_index) { + let instance = this.instance_at(instance_index); + + if (!instance) throw new Error(`Instance at "${instance_index}" not found`); + + return this.remove_instance_from_group(group, instance); + } + + get groups() { + let groupHandler = { + get: (instances, prop) => { + if (!Number.isNaN(Number(prop))) + return instances[prop]; + + let { group_name } = instances; + + if (prop === 'add') + return (instance) => { + return this.add_instance_to_group(group_name, instance); + }; + + if (prop === 'add_by_name') + return this.add_instance_to_group_by_name.bind(this, group_name); + + if (prop === 'remove') + return (instance) => { + return this.remove_instance_from_group(group_name, instance); + }; + + if (prop === 'remove_by_name') + return this.remove_instance_from_group_by_name.bind(this, group_name); + + if (prop === 'remove_at') + return (instance_index) => { + return this.remove_instance_from_group(group_name, instances[instance_index]); + }; + + return void(0); + } + }; + + let groupsHandler = { + get: (group_names, prop) => { + let instances_in_group = []; + + if (group_names.indexOf(prop) !== -1) { + instances_in_group = this.instances.filter((instance) => instance.instance_group.indexOf(prop) !== -1); + } + + instances_in_group.group_name = prop; + + return new Proxy(instances_in_group, groupHandler); + } + }; + + return new Proxy(this.group_names, groupsHandler); + } + get default_tor_config() { if (typeof(this._default_tor_config) === 'function') return this._default_tor_config(); @@ -77,7 +171,7 @@ class TorPool extends EventEmitter { } get instances() { - return this._instances.slice(0).filter((tor) => tor.ready); + return this._instances.slice(0); } async create_instance(instance_definition) { @@ -89,10 +183,9 @@ class TorPool extends EventEmitter { 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); + let instance = new TorProcess(this.tor_path, instance_definition, this.granax_options, this.logger); instance.id = instance_id; - instance.definition = instance_definition; await instance.create(); diff --git a/src/TorProcess.js b/src/TorProcess.js index b0ab1b3..c0ff1a1 100644 --- a/src/TorProcess.js +++ b/src/TorProcess.js @@ -20,16 +20,19 @@ Promise.promisifyAll(fs); temp.track(); class TorProcess extends EventEmitter { - constructor(tor_path, config, granax_options, logger) { + constructor(tor_path, definition, granax_options, logger) { super(); this.logger = logger || require('./winston-silent-logger'); + + definition.Group = definition.Group ? [].concat(definition.Group) : []; + + this._definition = definition; + this.tor_path = tor_path; this.granax_options = granax_options; this.control_password = crypto.randomBytes(128).toString('base64'); - config.DataDirectory = config.DataDirectory || temp.mkdirSync(); - - this.tor_config = config; + this.tor_config.DataDirectory = this.tor_config.DataDirectory || temp.mkdirSync(); } async exit() { @@ -43,11 +46,18 @@ class TorProcess extends EventEmitter { await p; } + get instance_group() { + return (this.definition && this.definition.Group) || null; + } get instance_name() { return (this.definition && this.definition.Name) || this.process.pid; } + get definition() { return this._definition; } + + get tor_config() { return this.definition.Config; } + get dns_port() { return this._dns_port || null; } @@ -109,7 +119,7 @@ class TorProcess extends EventEmitter { HashedControlPassword: shell.exec(`${this.tor_path} --quiet --hash-password "${this.control_password}"`, { async: false, silent: true }).stdout.trim() }; - let config = _.extend(_.extend({}, this.tor_config), options); + let config = _.extend(_.cloneDeep(this.tor_config), options); let text = Object.keys(config).map((key) => `${key} ${config[key]}`).join(os.EOL); let configFile = await temp.openAsync('tor-router'); diff --git a/src/default_config.js b/src/default_config.js index 3d8a102..7c769f3 100644 --- a/src/default_config.js +++ b/src/default_config.js @@ -9,7 +9,7 @@ module.exports = { "socksHost": null, "dnsHost": null, "httpHost": null, - "proxyByName": true, + "proxyByName": 'individual', "denyUnidentifedUsers": false, "logLevel": "info", "loadBalanceMethod": "round_robin", diff --git a/src/launch.js b/src/launch.js index c874011..51a8331 100644 --- a/src/launch.js +++ b/src/launch.js @@ -31,6 +31,9 @@ async function main(nconf, logger) { let control_host = typeof(nconf.get('controlHost')) !== 'boolean' ? extractHost(nconf.get('controlHost')) : nconf.get('controlHost'); let control_host_ws = typeof(nconf.get('websocketControlHost')) !== 'boolean' ? extractHost(nconf.get('websocketControlHost')) : nconf.get('websocketControlHost'); + if (nconf.get('proxyByName') && nconf.get('proxyByName') === true) + nconf.set('proxyByName', 'individual'); + if (typeof(control_host) === 'boolean') { control_host = extractHost(9077); nconf.set('controlHost', assembleHost(control_port)); @@ -75,9 +78,10 @@ async function main(nconf, logger) { } if (instances) { - logger.info(`[tor]: starting ${Array.isArray(instances) ? instances.length : instances} tor instance(s)...`) + logger.info(`[tor]: starting ${Array.isArray(instances) ? instances.length : instances} tor instance(s)...`); + await control.torPool.create(instances); - + logger.info('[tor]: tor started'); } } catch (error) { diff --git a/test/TorProcess.js b/test/TorProcess.js index a5d2cb3..5a5f0e3 100644 --- a/test/TorProcess.js +++ b/test/TorProcess.js @@ -9,7 +9,7 @@ require(`${__dirname}/../src/nconf_load_env.js`)(nconf); nconf.defaults(require(`${__dirname}/../src/default_config.js`)); describe('TorProcess', function () { - let tor = new TorProcess(nconf.get('torPath'), { DataDirectory: nconf.get('parentDataDirectory'), ProtocolWarnings: 0 }, null); + let tor = new TorProcess(nconf.get('torPath'), { Config: { DataDirectory: nconf.get('parentDataDirectory'), ProtocolWarnings: 0 } }, null); describe('#create()', function () { this.timeout(WAIT_FOR_CREATE);