finishes documentation. minor bug fixes and some code clean-up

This commit is contained in:
Zachary Boyd 2018-09-14 19:54:14 -04:00
parent 5b598f612a
commit 0b82ad9cb5
30 changed files with 1227 additions and 430 deletions

View File

@ -7,4 +7,4 @@ docker-compose.yml
README.md
.vscode
.DS_Store
doc
docs

2
.gitignore vendored
View File

@ -3,4 +3,4 @@ npm-debug.log
.env
.vscode
.DS_Store
doc
docs

View File

@ -2,4 +2,5 @@ node_modules
npm-debug.log
.env
.vscode
.DS_Store
.DS_Store
docs

View File

@ -3,7 +3,7 @@
## [4.0.2] - 2018-09-13
### Added
- Adds API documentation. To generate run `npm run doc` and open under `doc/index.html`
- Adds API documentation. To generate run `npm run docs` and open under `docs/index.html`
### Changed
- Much of the README has been moved to [the wiki](https://github.com/znetstar/tor-router/wiki)

View File

@ -26,11 +26,7 @@ ADD . /app
ENV HOME /home/tor_router
EXPOSE 9050
EXPOSE 9053
EXPOSE 9077
EXPOSE 9050 9053 9077
ENTRYPOINT [ "tor-router" ]

View File

@ -5,9 +5,8 @@ module.exports = function(grunt) {
dist : {
src: ['src/*.js', 'test/*.js', 'README.md'],
options: {
destination : 'doc',
template : "node_modules/ink-docstrap/template",
configure : "node_modules/ink-docstrap/template/jsdoc.conf.json"
destination : 'docs',
template : "node_modules/docdash"
}
}
}
@ -17,5 +16,5 @@ module.exports = function(grunt) {
grunt.registerTask('default', []);
grunt.registerTask('doc', ['jsdoc']);
grunt.registerTask('docs', ['jsdoc']);
};

View File

@ -49,7 +49,7 @@ For example: `tor-router -j 3 -s 127.0.0.1:9050` would start the proxy with 3 to
For detailed examples and insturctions on using Tor Router [see the wiki](https://github.com/znetstar/tor-router/wiki).
To generate API documentation run `npm run doc`. The documentation will be available in `doc/`.
To generate API documentation run `npm run docs`. The documentation will be available in `docs/`.
## Testing

178
package-lock.json generated
View File

@ -21,6 +21,15 @@
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true
},
"agent-base": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
"integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
"dev": true,
"requires": {
"es6-promisify": "^5.0.0"
}
},
"ajv": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
@ -571,6 +580,12 @@
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
"dev": true
},
"docdash": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/docdash/-/docdash-1.0.0.tgz",
"integrity": "sha512-HhK72PT4z55og8FDqskO/tTYXxU+LovRz+9pCDHLnUoPchkxjdIJidS+96LqW3CLrRdBmnkDRrcVrDFGLIluTw==",
"dev": true
},
"dom-serializer": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
@ -646,6 +661,21 @@
"is-arrayish": "^0.2.1"
}
},
"es6-promise": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz",
"integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==",
"dev": true
},
"es6-promisify": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
"dev": true,
"requires": {
"es6-promise": "^4.0.3"
}
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@ -1195,16 +1225,6 @@
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
"ink-docstrap": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/ink-docstrap/-/ink-docstrap-1.3.2.tgz",
"integrity": "sha512-STx5orGQU1gfrkoI/fMU7lX6CSP7LBGO10gXNgOZhwKhUqbtNjCkYSewJtNnLmWP1tAGN6oyEpG1HFPw5vpa5Q==",
"dev": true,
"requires": {
"moment": "^2.14.1",
"sanitize-html": "^1.13.0"
}
},
"interpret": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz",
@ -1215,6 +1235,12 @@
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
},
"ip": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
"dev": true
},
"ipaddr.js": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-0.1.9.tgz",
@ -1475,36 +1501,6 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
"dev": true
},
"lodash.escaperegexp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
"integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=",
"dev": true
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=",
"dev": true
},
"lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=",
"dev": true
},
"lodash.mergewith": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
"integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==",
"dev": true
},
"logform": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-1.6.0.tgz",
@ -1657,12 +1653,6 @@
"supports-color": "5.4.0"
}
},
"moment": {
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=",
"dev": true
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -2005,17 +1995,6 @@
"resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz",
"integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE="
},
"postcss": {
"version": "6.0.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz",
"integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==",
"dev": true,
"requires": {
"chalk": "^2.4.1",
"source-map": "^0.6.1",
"supports-color": "^5.4.0"
}
},
"process-nextick-args": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
@ -2172,6 +2151,25 @@
"lodash": "^4.13.1"
}
},
"requestretry": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/requestretry/-/requestretry-2.0.2.tgz",
"integrity": "sha512-wBIylIEvvGHnFAYRXIKCARGzWxChn+mo7X3KjXPgtofB+c0ejcZFdZ5k6RFhBV+IOf80fkemcVuVdUKqovnj8A==",
"dev": true,
"requires": {
"extend": "^3.0.2",
"lodash": "^4.17.10",
"when": "^3.7.7"
},
"dependencies": {
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
}
}
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -2221,24 +2219,6 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"sanitize-html": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.19.0.tgz",
"integrity": "sha512-Qt2imq49f2qP4537a7R2Xgx9sjTvw18jIT7zKurhu5kpYNQfMo8EZaW3OcpoXCvg3GTN4C4R3mN8ao7STUtKtA==",
"dev": true,
"requires": {
"chalk": "^2.3.0",
"htmlparser2": "^3.9.0",
"lodash.clonedeep": "^4.5.0",
"lodash.escaperegexp": "^4.1.2",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.mergewith": "^4.6.0",
"postcss": "^6.0.14",
"srcset": "^1.0.0",
"xtend": "^4.0.0"
}
},
"secure-keys": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/secure-keys/-/secure-keys-1.0.0.tgz",
@ -2282,6 +2262,12 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"smart-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.1.tgz",
"integrity": "sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg==",
"dev": true
},
"sntp": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz",
@ -2291,6 +2277,26 @@
"hoek": "4.x.x"
}
},
"socks": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.2.1.tgz",
"integrity": "sha512-0GabKw7n9mI46vcNrVfs0o6XzWzjVa3h6GaSo2UPxtWAROXUWavfJWh1M4PR5tnE0dcnQXZIDFP4yrAysLze/w==",
"dev": true,
"requires": {
"ip": "^1.1.5",
"smart-buffer": "^4.0.1"
}
},
"socks-proxy-agent": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz",
"integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==",
"dev": true,
"requires": {
"agent-base": "~4.2.0",
"socks": "~2.2.0"
}
},
"socksv5": {
"version": "git+https://github.com/znetstar/socksv5.git#431e541390314adbbe765650d8d810ec1df38d8a",
"from": "git+https://github.com/znetstar/socksv5.git#431e541390314adbbe765650d8d810ec1df38d8a",
@ -2315,12 +2321,6 @@
}
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"spdx-correct": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz",
@ -2359,16 +2359,6 @@
"integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw=",
"dev": true
},
"srcset": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz",
"integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=",
"dev": true,
"requires": {
"array-uniq": "^1.0.2",
"number-is-nan": "^1.0.0"
}
},
"sshpk": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz",
@ -2586,6 +2576,12 @@
"extsprintf": "^1.2.0"
}
},
"when": {
"version": "3.7.8",
"resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz",
"integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=",
"dev": true
},
"which": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",

View File

@ -13,17 +13,18 @@
"start": "bin/tor-router -s -d -j 1",
"test": "mocha -u tdd --exit test/index.js",
"debug": "node --inspect-brk bin/tor-router",
"build": "grunt",
"doc": "grunt doc"
"docs": "grunt docs"
},
"devDependencies": {
"chai": "^4.1.2",
"docdash": "^1.0.0",
"grunt": "^1.0.3",
"grunt-jsdoc": "^2.3.0",
"ink-docstrap": "^1.3.2",
"mocha": "^5.2.0",
"request": "^2.79.0",
"request-promise": "^4.2.2"
"request-promise": "^4.2.2",
"requestretry": "^2.0.2",
"socks-proxy-agent": "^4.0.1"
},
"dependencies": {
"bluebird": "^3.5.2",

View File

@ -7,96 +7,266 @@ const DNSServer = require('./DNSServer');
const TorPool = require('./TorPool');
const default_ports = require('./default_ports');
/**
* @typedef ControlServer~InstanceInfo
*
* @property {string} name - Name of the instance.
* @property {string[]} group - Groups the instance belongs to.
* @property {number} dns_port - Port Tor is listening on for DNS Traffic.
* @property {number} socks_port - Port Tor is listening on for SOCKS Traffic.
* @property {number} process_id - Process ID for the Tor process.
* @property {Object} config - Configuration (torrc) set when starting the process.
* @property {number} [weight] - Weight of the instance for weighted load balancing.
*/
/**
* A server which exposes an RPC interface that can control the application.
*/
class ControlServer {
constructor(logger, nconf) {
this.torPool = new TorPool(nconf.get('torPath'), (() => nconf.get('torConfig')), nconf.get('parentDataDirectory'), nconf.get('loadBalanceMethod'), nconf.get('granaxOptions'), logger);
this.logger = logger || require('./winston-silent-logger');
/**
*
* @param {Provider} nconf - Instance of nconf.Provider that will be used to obtain configuration values.
* @param {Logger} [logger] - Winston logger that will be used for logging. If not provided will disable logging.
*/
constructor(nconf, logger) {
/**
* Pool of Tor instances
*
* @type {TorPool}
*/
this.tor_pool = new TorPool(nconf.get('torPath'), (() => nconf.get('torConfig')), nconf.get('parentDataDirectory'), nconf.get('loadBalanceMethod'), nconf.get('granaxOptions'), logger);
/**
* Winston Logger for logging
*
* @type {Logger}
*/
this.logger = logger || require('./winston_silent_logger');
/**
* Nconf Provider for configuration
*
* @type {Provider}
*/
this.nconf = nconf;
/**
* RPC Server instance
*
* @type {rpc.Server}
*/
let server = this.server = new rpc.Server();
/**
* @borrows ControlServer#createTorPool as ControlServer~createTorPool
* @function ControlServer~createTorPool
*/
server.expose('createTorPool', this.createTorPool.bind(this));
/**
* @borrows ControlServer#createSOCKSServer as ControlServer~createSOCKSServer
* @function ControlServer~createSOCKSServer
*/
server.expose('createSOCKSServer', this.createSOCKSServer.bind(this));
/**
* @borrows ControlServer#createDNSServer as ControlServer~createDNSServer
* @function ControlServer~createDNSServer
*/
server.expose('createDNSServer', this.createDNSServer.bind(this));
/**
* @borrows ControlServer#createHTTPServer as ControlServer~createHTTPServer
* @function ControlServer~createHTTPServer
*/
server.expose('createHTTPServer', this.createHTTPServer.bind(this));
const instance_info = (i) => {
return { group: i.instance_group, 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 };
};
server.expose('queryInstances', (async () => {
return this.torPool.instances.map(instance_info);
/**
* Returns a list of all instances currently in the pool.
* @function ControlServer~queryInstances
* @returns {ControlServer~InstanceInfo[]}
*/
server.expose('queryInstances', (() => {
return this.tor_pool.instances.map(ControlServer.instance_info);
}).bind(this));
server.expose('queryInstanceByName', (async (instance_name) => {
let instance = this.torPool.instance_by_name(instance_name);
/**
* Returns information on an instance identified by the {@link TorProcess#instance_name} field.
* @function ControlServer~queryInstanceByName
* @param {string} instance_name - Name of the instance.
* @returns {ControlServer~InstanceInfo}
*/
server.expose('queryInstanceByName', ((instance_name) => {
let instance = this.tor_pool.instance_by_name(instance_name);
if (!instance)
throw new Error(`Instance "${instance_name}"" does not exist`);
return instance_info(instance);
return ControlServer.instance_info(instance);
}).bind(this));
server.expose('queryInstanceAt', (async (index) => {
if (!this.torPool)
/**
* Returns information on an instance identified by its index in the pool.
* @function ControlServer~queryInstanceAt
* @param {number} instance_index - Index of the instance in the pool.
* @returns {ControlServer~InstanceInfo}
*/
server.expose('queryInstanceAt', ((index) => {
if (!this.tor_pool)
throw new Error('No pool created');
let instance = this.torPool.instance_at(index);
let instance = this.tor_pool.instance_at(index);
if (!instance)
throw new Error(`Instance at "${i}"" does not exist`);
return instance_info(this.torPool.instance_at(index));
return ControlServer.instance_info(this.tor_pool.instance_at(index));
}).bind(this));
server.expose('queryInstanceNames', (() => this.torPool.instance_names).bind(this));
/**
* Returns a list of the names of all of the instances in the pool.
* @function ControlServer~queryInstanceNames
* @returns {string[]}
*/
server.expose('queryInstanceNames', (() => this.tor_pool.instance_names).bind(this));
/**
* Returns a list of the names of all of the current groups.
* @function ControlServer~queryGroupNames
* @returns {string[]}
*/
server.expose('queryGroupNames', (() => Array.from(this.tor_pool.group_names)).bind(this));
server.expose('queryGroupNames', (() => Array.from(this.torPool.group_names)).bind(this));
/**
* Returns a list of the instances that exist in a given group.
* @function ControlServer~queryGroupNames
* @param {string} group - Group the search in.
* @returns {InstanceInfo[]}
*/
server.expose('queryInstancesByGroup', ((group) => this.tor_pool.instances_by_group(group).map(ControlServer.instance_info)).bind(this));
server.expose('queryInstancesByGroup', ((group) => this.torPool.instances_by_group(group).map(instance_info)).bind(this));
/**
* Creates instances from a number, an array of instance definitions or a single instance definition.
* If a number is provided, creates n many instances.
* @function ControlServer~createInstances
* @param {InstanceDefinition[]|InstanceDefinition|number} instances_to_create - Array of definitions, single definition, or number of instances,
* @async
* @returns {Promise<InstanceInfo[]>} - The instances that were created
*/
server.expose('createInstances', (async (instances_to_create) => {
let instances = await this.tor_pool.create(instances_to_create);
server.expose('createInstances', (async (num) => {
let instances = await this.torPool.create(num);
return instances.map(instance_info);
return instances.map(ControlServer.instance_info);
}).bind(this));
/**
* Creates instances from an array of instance definitions or a single instance definition.
* @function ControlServer~addInstances
* @param {InstanceDefinition[]|InstanceDefinition} instances_to_create - Array of definitions or single definition.
* @async
* @returns {Promise<InstanceInfo[]>} - The instances that were created
*/
server.expose('addInstances', (async (defs) => {
let instances = await this.torPool.create(defs);
let instances = await this.tor_pool.add(defs);
return instances.map(instance_info);
return instances.map(ControlServer.instance_info);
}).bind(this));
server.expose('removeInstances', this.torPool.remove.bind(this.torPool));
/**
* Removes a number of instances from the pool.
* @function ControlServer~removeInstances
* @borrows TorPool#remove as ControlServer~removeInstances
*/
server.expose('removeInstances', this.tor_pool.remove.bind(this.tor_pool));
server.expose('removeInstanceAt', this.torPool.remove_at.bind(this.torPool));
/**
* Remove an instance at the index provided from the pool.
* @function ControlServer~removeInstanceAt
* @borrows remove_at#remove as ControlServer~removeInstanceAt
*/
server.expose('removeInstanceAt', this.tor_pool.remove_at.bind(this.tor_pool));
server.expose('removeInstanceByName', this.torPool.remove_by_name.bind(this.torPool));
/**
* Remove an instance from the pool by the {@link TorProcess#instance_name} field.
* @function ControlServer~removeInstanceByName
* @borrows TorPool#remove_by_name as ControlServer~removeInstanceByName
*/
server.expose('removeInstanceByName', this.tor_pool.remove_by_name.bind(this.tor_pool));
server.expose('newIdentites', this.torPool.new_identites.bind(this.torPool));
/**
* Gets new identities for all instances in the pool.
* @function ControlServer~newIdentites
* @borrows TorPool#new_identites as ControlServer~newIdentites
*/
server.expose('newIdentites', this.tor_pool.new_identites.bind(this.tor_pool));
server.expose('newIdentityAt', this.torPool.new_identity_at.bind(this.torPool));
/**
* Get a new identity for the instance at the index provided in the pool.
* @function ControlServer~newIdentityAt
* @borrows TorPool#new_identity_at as ControlServer~newIdentityAt
*/
server.expose('newIdentityAt', this.tor_pool.new_identity_at.bind(this.tor_pool));
server.expose('newIdentityByName', this.torPool.new_identity_by_name.bind(this.torPool));
/**
* Get a new identity for the instance by the {@link TorProcess#instance_name} field.
* @function ControlServer~newIdentityByName
* @borrows TorPool#new_identity_by_name as ControlServer~newIdentityByName
*/
server.expose('newIdentityByName', this.tor_pool.new_identity_by_name.bind(this.tor_pool));
server.expose('newIdentitiesByGroup', (async (group) => await this.torPool.new_identites_by_group(group)).bind(this));
/**
* Gets new identities for all instances in the group.
* @function ControlServer~newIdentitiesByGroup
* @borrows TorPool#new_identites_by_group as ControlServer~newIdentitiesByGroup
*/
server.expose('newIdentitiesByGroup', this.tor_pool.new_identites_by_group.bind(this.tor_pool));
server.expose('nextInstance', (async () => instance_info( await this.torPool.next() )).bind(this));
/**
* Gets the next instance in the pool using the load balance method.
* @function ControlServer~nextInstance
* @returns {InstanceInfo} - The next instance in the pool.
*/
server.expose('nextInstance', (() => ControlServer.instance_info(this.tor_pool.next())).bind(this));
/**
* Gets the next instance in the group using the load balance method.
* @function ControlServer~nextInstanceByGroup
* @param {string} group - The group in question.
* @returns {InstanceInfo} - The next instance in the group.
*/
server.expose('nextInstanceByGroup', ((group) => {
return instance_info(this.torPool.next_by_group(group));
return ControlServer.instance_info(this.tor_pool.next_by_group(group));
}).bind(this));
server.expose('closeInstances', (async () => this.torPool.exit()).bind(this));
/**
* Kills the processes of all instances in the pool.
* @function ControlServer~closeInstances
* @borrows TorPool#exit as ControlServer~closeInstances
*/
server.expose('closeInstances', this.tor_pool.exit.bind(this.tor_pool));
/**
* Sets a property in the application configuration.
* @function ControlServer~setConfig
* @param {string} key - Name of the property to set.
* @param {string} value - Value to set the property to.
*/
server.expose('setConfig', ((key, value) => {
return this.nconf.set(key, value);
this.nconf.set(key, value);
}).bind(this));
/**
* Gets a property in the application configuration.
* @function ControlServer~getConfig
* @param {string} key - Name of the property to set.
* @returns {*} - Value of the property
*/
server.expose('getConfig', ((key) => {
return this.nconf.get(key);
}).bind(this));
/**
* Saves the application configuration to the underlying store (usually a JSON file).
* @function ControlServer~saveConfig
* @async
* @throws If the save operation fails.
* @returns {Promise}
*/
server.expose('saveConfig', (async () => {
await new Promise((resolve, reject) => {
this.nconf.save((err) => {
@ -106,6 +276,13 @@ class ControlServer {
});
}).bind(this));
/**
* Loads the application configuration from the underlying store (usually a JSON file).
* @function ControlServer~loadConfig
* @async
* @throws If the load operation fails.
* @returns {Promise}
*/
server.expose('loadConfig', (async () => {
await new Promise((resolve, reject) => {
this.nconf.load((err) => {
@ -115,107 +292,259 @@ class ControlServer {
});
}).bind(this));
server.expose('getDefaultTorConfig', (async () => {
return this.nconf.get('torConfig');
}).bind(this));
server.expose('setDefaultTorConfig', (async (config) => {
this.nconf.set('torConfig', config);
}).bind(this));
/**
* Sets a configuration property on all instances in the pool.
* @function ControlServer~setTorConfig
* @async
* @param {Object} config - An object containing properties to be set.
* @returns {Promise}
*/
server.expose('setTorConfig', (async (config) => {
await Promise.all(Object.keys(config).map((key) => {
let value = config[key];
return this.torPool.set_config_all(key, value);
return this.tor_pool.set_config_all(key, value);
}));
}).bind(this));
/**
* Sets a configuration property on all instances in a group.
* @function ControlServer~setTorConfigByGroup
* @async
* @param {string} group - Group to set properties on.
* @param {Object} config - An object containing properties to be set.
* @returns {Promise}
*/
server.expose('setTorConfigByGroup', (async (group, config) => {
await Promise.all(Object.keys(config).map((key) => {
let value = config[key];
return this.torPool.set_config_by_group(group, key, value);
return this.tor_pool.set_config_by_group(group, key, value);
}));
}).bind(this));
server.expose('getLoadBalanceMethod', (async () => {
return this.torPool.load_balance_method;
/**
* Retrieves the current load balance method for the pool
* @function ControlServer~getLoadBalanceMethod
* @returns {string} - The load balance method
*/
server.expose('getLoadBalanceMethod', (() => {
return this.tor_pool.load_balance_method;
}).bind(this));
/**
* Sets the current load balance method for the pool
* @function ControlServer~getLoadBalanceMethod
* @param {string} load_balance_method - The load balance method to set
*/
server.expose('setLoadBalanceMethod', ((loadBalanceMethod) => {
this.torPool.load_balance_method = loadBalanceMethod;
this.tor_pool.load_balance_method = loadBalanceMethod;
this.nconf.set('loadBalanceMethod', loadBalanceMethod);
}).bind(this));
server.expose('getInstanceConfigByName', this.torPool.get_config_by_name.bind(this.torPool));
/**
* Retrieve a configuration property for an instance identified by the {@link TorProcess#instance_name} field.
* @function ControlServer~getInstanceConfigByName
* @borrows TorPool#getInstanceConfigByName as ControlServer~get_config_by_name
*/
server.expose('getInstanceConfigByName', this.tor_pool.get_config_by_name.bind(this.tor_pool));
server.expose('getInstanceConfigAt', this.torPool.get_config_at.bind(this.torPool));
/**
* Retrieves a configuration property for an instance by its index in the pool.
* @function ControlServer~get_config_at
* @borrows TorPool#get_config_at as ControlServer~get_config_at
*/
server.expose('getInstanceConfigAt', this.tor_pool.get_config_at.bind(this.tor_pool));
server.expose('setInstanceConfigByName', this.torPool.set_config_by_name.bind(this.torPool));
/**
* Sets a configuration property for an instance identified by the {@link TorProcess#instance_name} field.
* @function ControlServer~setInstanceConfigByName
* @borrows TorPool#set_config_by_name as ControlServer~setInstanceConfigByName
*/
server.expose('setInstanceConfigByName', this.tor_pool.set_config_by_name.bind(this.tor_pool));
server.expose('setInstanceConfigAt', this.torPool.set_config_at.bind(this.torPool));
/**
* Sets a configuration property for an instance identified by its index in the pool.
* @function ControlServer~setInstanceConfigAt
* @borrows TorPool#set_config_at as ControlServer~setInstanceConfigAt
*/
server.expose('setInstanceConfigAt', this.tor_pool.set_config_at.bind(this.tor_pool));
server.expose('signalAllInstances', this.torPool.signal_all.bind(this.torPool));
/**
* Sends a signal to all instances in the pool
* @function ControlServer~signalAllInstances
* @borrows TorPool#signal_all as ControlServer~signalAllInstances
*/
server.expose('signalAllInstances', this.tor_pool.signal_all.bind(this.tor_pool));
/**
* Sends a signal to an instance identified by its index in the pool.
* @function ControlServer~signalInstanceAt
* @borrows TorPool#signal_at as ControlServer~signalInstanceAt
*/
server.expose('signalInstanceAt', this.tor_pool.signal_at.bind(this.tor_pool));
server.expose('signalInstanceAt', this.torPool.signal_at.bind(this.torPool));
/**
* Sends a signal to an instance identified by the {@link TorProcess#instance_name} field.
* @function ControlServer~signalInstanceByName
* @borrows TorPool#signal_by_name as ControlServer~signalInstanceByName
*/
server.expose('signalInstanceByName', this.tor_pool.signal_by_name.bind(this.tor_pool));
server.expose('signalInstanceByName', this.torPool.signal_by_name.bind(this.torPool));
/**
* Sends a singal to all instances in a group.
* @function ControlServer~signalInstancesByGroup
* @borrows TorPool#signal_by_group as ControlServer~signalInstancesByGroup
*/
server.expose('signalInstancesByGroup', this.tor_pool.signal_by_group.bind(this.tor_pool));
server.expose('signalInstancesByGroup', (async (group, signal) => await this.torPool.signal_by_group(group, signal)).bind(this));
server.expose('addInstanceToGroupByName', ((group, instance_name) => this.torPool.add_instance_to_group_by_name(group, instance_name)).bind(this));
/**
* Adds an instance to a group identified by its {@link TorProcess#instance_name} field.
* @function ControlServer~addInstanceToGroupByName
* @borrows TorPool#add_instance_to_group_by_name as ControlServer~addInstanceToGroupByName
*/
server.expose('addInstanceToGroupByName', this.tor_pool.add_instance_to_group_by_name.bind(this.tor_pool));
server.expose('addInstanceToGroupAt', ((group, instance_index) => this.torPool.add_instance_to_group_at(group, instance_index)).bind(this));
/**
* Adds an instance to a group identified by its index in the pool.
* @function ControlServer~addInstanceToGroupAt
* @borrows TorPool#add_instance_to_group_at as ControlServer~addInstanceToGroupAt
*/
server.expose('addInstanceToGroupAt', this.tor_pool.add_instance_to_group_at.bind(this.tor_pool));
server.expose('removeInstanceFromGroupByName', ((group, instance_name) => this.torPool.remove_instance_from_group_by_name(group, instance_name)).bind(this));
/**
* Removes an instance from a group identified by its {@link TorProcess#instance_name} field.
* @function ControlServer~removeInstanceFromGroupByName
* @borrows TorPool#remove_instance_from_group_by_name as ControlServer~removeInstanceFromGroupByName
*/
server.expose('removeInstanceFromGroupByName', this.tor_pool.remove_instance_from_group_by_name.bind(this.tor_pool));
server.expose('removeInstanceFromGroupAt', ((group, instance_index) => this.torPool.remove_instance_from_group_at(group, instance_index)).bind(this));
/**
* Remove an instance from a group identified by its index in the pool.
* @function ControlServer~removeInstanceFromGroupAt
* @borrows TorPool#remove_instance_from_group_at as ControlServer~removeInstanceFromGroupAt
*/
server.expose('removeInstanceFromGroupAt', this.tor_pool.remove_instance_from_group_at .bind(this.tor_pool));
}
/**
* Returns a summary of information on the running instance
* @param {TorProcess} instance
* @static
* @returns {ControlServer~InstanceInfo}
*/
static instance_info(instance) {
return {
name: instance.instance_name,
group: instance.instance_group,
dns_port: instance.dns_port,
socks_port: instance.socks_port,
process_id: instance.process.pid,
config: instance.definition.Config,
weight: instance.definition.weight
};
}
/**
* Binds the server to a host and port and begins listening for TCP traffic
* @param {number} port - Port the server should bind to
* @param {string} [hostname] - Host the server should bind to
* @async
* @returns {Promise}
*/
async listenTcp(port, hostname) {
this.tcpTransport = new rpc.tcpTransport({ port, hostname });
this.tcpTransport.listen(this.server);
this.logger.info(`[control]: control server listening on tcp://${hostname}:${port}`);
}
/**
* Binds the server to a host and port and begins listening for WebSocket traffic
* @param {number} port - Port the server should bind to
* @param {string} [hostname] - Host the server should bind to
* @async
* @returns {Promise}
*/
async listenWs(port, hostname) {
this.wsTransport = new rpc.wsTransport({ port, hostname });
this.wsTransport.listen(this.server);
this.logger.info(`[control]: control server listening on ws://${hostname}:${port}`);
}
async listen(port) { return await this.listenTcp(port); }
/**
* Calls {@link ControlServer#listenTcp} with the same arguments
* @param {number} port - Port the server should bind to
* @param {string} [hostname] - Host the server should bind to
* @async
* @returns {Promise}
*/
async listen(port, hostname) { return await this.listenTcp(port, hostname); }
/**
* Closes the TCP and/or WebSocket servers
*/
close() {
return this.tcpTransport.tcpServer.close();
if (this.tcpTransport && this.tcpTransport.tcpServer)
this.tcpTransport.tcpServer.close();
if (this.wsTransport && this.wsTransport.httpServer)
this.wsTransport.httpServer.close();
}
createTorPool(options) {
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;
/**
* Creates a new {@link TorPool} instance
* @param {Object} [tor_config] - Default Tor config to be used for the pool of instances.
* @param {string} [load_balance_method] - Load balance method to be used for the pool. Will default to the global configuration if not provided.
*
* @returns {TorPool} - The {@link TorPool} that was created.
*/
createTorPool(tor_config, load_balance_method) {
this.tor_pool = new TorPool(this.nconf.get('torPath'), tor_config, this.nconf.get('parentDataDirectory'), (load_balance_method || this.nconf.get('loadBalanceMethod')), this.nconf.get('granaxOptions'), this.logger);
return this.tor_pool;
}
/**
* Creates an instance of {@link SOCKSServer} and begins listening for traffic
* @param {number} port - Port the server should bind to
* @param {string} [hostname] - Host the server should bind to
* @async
* @returns {Promise}
*/
async createSOCKSServer(port, hostname) {
this.socksServer = new SOCKSServer(this.torPool, this.logger, (this.nconf.get('proxyByName') ? { mode: this.nconf.get('proxyByName'), deny_unidentified_users: this.nconf.get('denyUnidentifiedUsers') } : ""));
this.socksServer = new SOCKSServer(this.tor_pool, this.logger, (this.nconf.get('proxyByName') ? { mode: this.nconf.get('proxyByName'), deny_unidentified_users: this.nconf.get('denyUnidentifiedUsers') } : ""));
await this.socksServer.listen(port || default_ports.socks, hostname);
this.logger.info(`[socks]: listening on socks5://${hostname}:${port}`);
this.socksServer;
}
/**
* Creates an instance of {@link HTTPServer} and begins listening for traffic
* @param {number} port - Port the server should bind to
* @param {string} [hostname] - Host the server should bind to
* @async
* @returns {Promise}
*/
async createHTTPServer(port, hostname) {
this.httpServer = new HTTPServer(this.torPool, this.logger, (this.nconf.get('proxyByName') ? { mode: this.nconf.get('proxyByName'), deny_unidentified_users: this.nconf.get('denyUnidentifiedUsers') } : ""));
this.httpServer = new HTTPServer(this.tor_pool, this.logger, (this.nconf.get('proxyByName') ? { mode: this.nconf.get('proxyByName'), deny_unidentified_users: this.nconf.get('denyUnidentifiedUsers') } : ""));
await this.httpServer.listen(port || default_ports.http, hostname);
this.logger.info(`[http]: listening on http://${hostname}:${port}`);
this.httpServer;
}
/**
* Creates an instance of {@link DNSServer} and begins listening for traffic
* @param {number} port - Port the server should bind to
* @param {string} [hostname] - Host the server should bind to
* @async
* @returns {Promise}
*/
async createDNSServer(port, hostname) {
this.dnsServer = new DNSServer(this.torPool, this.nconf.get('dns:options'), this.nconf.get('dns:timeout'), this.logger);
this.dnsServer = new DNSServer(this.tor_pool, this.nconf.get('dns:options'), this.nconf.get('dns:timeout'), this.logger);
await this.dnsServer.serve(port || default_ports.dns, hostname);
this.logger.info(`[dns]: listening on dns://${hostname}:${port}`);
this.dnsServer;
}
};
/**
* Module that contains the {@link ControlServer} class.
* @module tor-router/ControlServer
* @see ControlServer
*/
module.exports = ControlServer;

View File

@ -1,8 +1,21 @@
const dns = require('native-dns');
const { UDPServer } = require('native-dns');
const { UDPServer, Request } = dns;
const Promise = require('bluebird');
/**
* A DNS proxy server that will route requests to instances in the TorPool provided.
* @extends UDPServer
*/
class DNSServer extends UDPServer {
/**
* Binds the server to a port and IP Address.
*
* @async
* @param {number} port - The port to bind to.
* @param {string} [host="::"] - Address to bind to. Will default to :: or 0.0.0.0 if not specified.
* @returns {Promise}
*
*/
async listen() {
let args = Array.from(arguments);
let inner_func = super.serve;
@ -24,25 +37,32 @@ class DNSServer extends UDPServer {
});
}
/**
* Creates an instance of `DNSServer`.
* @param {TorPool} tor_pool - The pool of instances that will be used for requests.
* @param {Object} [dns_options] - Options that will be passed to the parent constructor.
* @param {number} dns_timeout - How long to wait before each outbound DNS request before timing out.
* @param {Logger} [logger] - Winston logger that will be used for logging. If not specified will disable logging.
*/
constructor(tor_pool, dns_options, dns_timeout, logger) {
super(dns_options);
this.logger = logger || require('./winston-silent-logger');
this.tor_pool = tor_pool;
const handle_dns_request = (req, res) => {
/**
* Handles an incoming DNS request.
*
* @function handle_request
* @param {Request} req - Incoming DNS request.
* @param {Response} res - Outgoing DNS response.
* @private
*/
const handle_request = (req, res) => {
let connect = (tor_instance) => {
let source = { hostname: req.address.address, port: req.address.port, proto: 'dns' };
this.emit('instance-connection', tor_instance, source);
for (let question of req.question) {
let dns_port = (tor_instance.dns_port);
let outbound_req = dns.Request({
let outbound_req = Request({
question,
server: { address: '127.0.0.1', port: dns_port, type: 'udp' },
timeout: dns_timeout
timeout: this.dns_timeout
});
this.logger.verbose(`[dns]: ${source.hostname}:${source.port} → 127.0.0.1:${dns_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' }`);
outbound_req.on('message', (err, answer) => {
if (!err && answer) {
for (let a of answer.answer){
@ -52,11 +72,21 @@ class DNSServer extends UDPServer {
});
outbound_req.on('error', (err) => {
this.logger.error(`[dns]: an error occured while handling ar request: ${err.message}`);
this.logger.error(`[dns]: an error occured while handling the request: ${err.message}`);
});
outbound_req.on('end', () => {
let source = { hostname: req.address.address, port: req.address.port, proto: 'dns' };
/**
* Fires when the proxy has made a connection through an instance.
*
* @event DNSServer#instance-connection
* @param {TorProcess} instance - Instance that has been connected to.
* @param {InstanceConnectionSource} source - Details on the source of the connection.
*/
this.emit('instance_connection', tor_instance, source);
this.logger.verbose(`[dns]: ${source.hostname}:${source.port} → 127.0.0.1:${dns_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' }`);
res.send();
});
@ -71,8 +101,36 @@ class DNSServer extends UDPServer {
this.tor_pool.once('instance_created', connect);
}
};
this.on('request', handle_dns_request);
super(dns_options);
/**
* Winston logger
* @type {Logger}
* @public
*/
this.logger = logger || require('./winston_silent_logger');
/**
* The pool of instances that will be used for requests.
* @type {TorPool}
* @public
*/
this.tor_pool = tor_pool;
/**
* Timeout for each outbound DNS request
* @type {number}
* @public
*/
this.dns_timeout = dns_timeout;
this.on('request', handle_request);
}
};
/**
* Module that contains the {@link DNSSErver} class.
* @module tor-router/DNSServer
* @see DNSServer
*/
module.exports = DNSServer;

View File

@ -5,9 +5,36 @@ const { Server } = http;
const Promise = require('bluebird');
const socks = require('socksv5');
/**
* Value of the "Proxy-Agent" header that will be sent with each http-connect (https) request
* @constant
* @type {string}
* @default
*/
const TOR_ROUTER_PROXY_AGENT = 'tor-router';
/**
* What will show up when an unauthenticated user attempts to connect when an invalid username
* @constant
* @type {string}
* @default
*/
const REALM = 'Name of instance to route to';
/**
* A HTTP(S) proxy server that will route requests to instances in the TorPool provided.
* @extends Server
*/
class HTTPServer extends Server {
/**
* Binds the server to a port and IP Address.
*
* @async
* @param {number} port - The port to bind to
* @param {string} [host="::"] - Address to bind to. Will default to :: or 0.0.0.0 if not specified.
* @returns {Promise}
*
*/
async listen() {
return await new Promise((resolve, reject) => {
let args = Array.from(arguments);
@ -20,21 +47,41 @@ class HTTPServer extends Server {
});
}
/**
* Handles username authentication for HTTP requests
* @param {ClientRequest} req - Incoming HTTP request
* @param {ClientResponse} res - Outgoing HTTP response
* @private
*/
authenticate_user_http(req, res) {
return this.authenticate_user(req, () => {
res.writeHead(407, { 'Proxy-Authenticate': `Basic realm="Name of instance to route to"` });
res.writeHead(407, { 'Proxy-Authenticate': `Basic realm="${REALM}"` });
res.end();
return false;
})
}
/**
* Handles username authentication for HTTP-Connect requests
* @param {ClientRequest} req - Incoming HTTP request
* @param {Socket} socket - Inbound HTTP-Connect socket
* @private
*/
authenticate_user_connect(req, socket) {
return this.authenticate_user(req, () => {
socket.write(`HTTP/1.1 407 Proxy Authentication Required\r\n'+'Proxy-Authenticate: Basic realm="${REALM}"\r\n` +'\r\n');
socket.end();
return false;
})
}
/**
* Checks the username provided against all groups (for "group" mode) or all instances (for "individual" mode).
* @param {ClientRequest} req - Incoming HTTP request
* @param {Function} deny - Function that when called will deny the connection to the proxy server and prompt the user for credentials (HTTP 407).
* @private
* @throws If the {@link HTTPServer#proxy_by_name} mode is invalid
*/
authenticate_user(req, deny) {
if (!this.proxy_by_name)
return true;
@ -75,8 +122,21 @@ class HTTPServer extends Server {
return true;
}
/**
* Creates an instance of `HTTPServer`.
* @param {TorPool} tor_pool - The pool of instances that will be used for requests.
* @param {Logger} [logger] - Winston logger that will be used for logging. If not specified will disable logging.
* @param {ProxyByNameConfig} [proxy_by_name] - Enable routing to specific instances or groups of instances using the username field (http://instance-1:@my-server:9050) when connecting.
*/
constructor(tor_pool, logger, proxy_by_name) {
let handle_http_connections = (req, res) => {
/**
* Handles incoming HTTP Connections.
* @function handle_http_connections
* @param {ClientRequest} - Incoming HTTP request.
* @param {ClientResponse} - Outgoing HTTP response.
* @private
*/
const handle_http_connections = (req, res) => {
if (!this.authenticate_user_http(req, res))
return;
@ -103,10 +163,8 @@ class HTTPServer extends Server {
let connect = (tor_instance) => {
let source = { hostname: req.connection.remoteAddress, port: req.connection.remotePort, proto: 'http', by_name: Boolean(instance) };
this.emit('instance-connection', tor_instance, source);
let socks_port = tor_instance.socks_port;
this.logger.verbose(`[http-proxy]: ${source.hostname}:${source.port} → 127.0.0.1:${socks_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' }${url.hostname}:${url.port}`);
let proxy_req = http.request({
method: req.method,
hostname: url.hostname,
@ -120,6 +178,16 @@ class HTTPServer extends Server {
localDNS: false
})
}, (proxy_res) => {
/**
* Fires when the proxy has made a connection through an instance using HTTP or HTTP-Connect.
*
* @event HTTPServer#instance-connection
* @param {TorProcess} instance - Instance that has been connected to.
* @param {InstanceConnectionSource} source - Details on the source of the connection.
*/
this.emit('instance_connection', tor_instance, source);
this.logger.verbose(`[http-proxy]: ${source.hostname}:${source.port} → 127.0.0.1:${socks_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' }${url.hostname}:${url.port}`);
proxy_res.on('data', (chunk) => {
res.write(chunk);
});
@ -165,9 +233,17 @@ class HTTPServer extends Server {
this.logger.debug(`[http-proxy]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`);
tor_pool.once('instance_created', connect);
}
};
}
let handle_connect_connections = (req, inbound_socket, head) => {
/**
* Handles incoming HTTP-Connect connections.
* @function handle_connect_connections
* @param {ClientRequest} req - Incoming HTTP Request.
* @param {Socket} inbound_socket - Incoming socket.
* @param {Buffer|string} head - HTTP Request head.
* @private
*/
const handle_connect_connections = (req, inbound_socket, head) => {
if (!this.authenticate_user_connect(req, inbound_socket))
return;
@ -178,9 +254,8 @@ class HTTPServer extends Server {
let connect = (tor_instance) => {
let source = { hostname: req.connection.remoteAddress, port: req.connection.remotePort, proto: 'http-connect', by_name: Boolean(instance) };
this.emit('instance-connection', tor_instance, source);
let socks_port = tor_instance.socks_port;
this.logger && this.logger.verbose(`[http-connect]: ${source.hostname}:${source.port} → 127.0.0.1:${socks_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' }${hostname}:${port}`)
var outbound_socket;
let onClose = (error) => {
@ -204,6 +279,9 @@ class HTTPServer extends Server {
localDNS: false,
auths: [ socks.auth.None() ]
}, ($outbound_socket) => {
this.emit('instance_connection', tor_instance, source);
this.logger && this.logger.verbose(`[http-connect]: ${source.hostname}:${source.port} → 127.0.0.1:${socks_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' }${hostname}:${port}`)
outbound_socket = $outbound_socket;
outbound_socket.on('close', onClose);
outbound_socket.on('error', onClose);
@ -231,15 +309,34 @@ class HTTPServer extends Server {
this.logger.debug(`[http-connect]: 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);
}
};
}
super(handle_http_connections);
this.on('connect', handle_connect_connections);
this.logger = logger || require('./winston-silent-logger');
/**
* Winston logger.
* @type {Logger}
* @public
*/
this.logger = logger || require('./winston_silent_logger');
/**
* The pool of instances that will be used for requests.
* @type {TorPool}
* @public
*/
this.tor_pool = tor_pool;
/**
* Configuration for "proxy by name" feature.
* @type {ProxyByNameConfig}
* @public
*/
this.proxy_by_name = proxy_by_name;
}
};
/**
* Module that contains the {@link HTTPServer} class.
* @module tor-router/HTTPServer
* @see HTTPServer
*/
module.exports = HTTPServer;

View File

@ -2,7 +2,44 @@ const socks = require('socksv5');
const Promise = require('bluebird');
const { Server } = socks;
/**
* Configuration for the "proxy by name" feature (connecting to specific instances or groups of instances using the username field when connecting).
* @typedef ProxyByNameConfig
*
* @property {boolean} [deny_unidentified_users=false] - Deny unauthenticated (e.g. no username - socks://my-server:9050) users access to the proxy server.
* @property {string} mode - Either "group" for routing to a group of instances or "individual" for routing to individual instances.
*/
/**
* Details on the source of a connection the proxy server.
* @typedef InstanceConnectionSource
* @property {string} hostname - Hostname where the connection was made from.
* @property {number} port - Port where the connection was made from.
* @property {boolean} by_name - Indicates whether the connection was made using a username (made to a specific instance or group of instances).
* @property {string} proto - The protocol of the connection "socks", "http", "http-connect" or "dns"
*/
/**
* A SOCKS5 proxy server that will route requests to instances in the TorPool provided.
* @extends Server
*/
class SOCKSServer extends Server{
/**
* Callback for `authenticate_user`.
* @callback SOCKSServer~authenticate_user_callback
* @param {boolean} allow - Indicates if the connection should be allowed.
* @param {boolean} user - Indicates if the connection should have a session (authentication was successful).
*/
/**
* Binds the server to a port and IP Address.
*
* @async
* @param {number} port - The port to bind to.
* @param {string} [host="::"] - Address to bind to. Will default to :: or 0.0.0.0 if not specified.
* @returns {Promise}
*
*/
async listen() {
return await new Promise((resolve, reject) => {
let args = Array.from(arguments);
@ -15,6 +52,14 @@ class SOCKSServer extends Server{
});
}
/**
* Retrieves an instance from the pool or an instance from a group by the name provided.
* @param {string} username - Name of the group or instance to route to.
* @returns {TorProcess}
* @throws If {@link SOCKSServer#proxy_by_name} is set to an invalid value.
* @throws If the name of the instance or group provided is invalid.
* @private
*/
get_instance_pbn(username) {
if (this.proxy_by_name.mode === 'individual')
return this.tor_pool.instance_by_name(username);
@ -24,6 +69,14 @@ class SOCKSServer extends Server{
throw Error(`Unknown "proxy_by_name" mode ${this.proxy_by_name.mode}`);
}
/**
* Checks the username provided against all groups (for "group" mode) or all instances (for "individual" mode).
* @param {string} username
* @param {string} password
* @param {SOCKSServer~authenticate_user_callback} callback - Callback for `authenticate_user`.
* @throws If {@link SOCKSServer#proxy_by_name} is invalid.
* @private
*/
authenticate_user(username, password, callback) {
let deny_un = this.proxy_by_name.deny_unidentified_users;
@ -45,8 +98,23 @@ class SOCKSServer extends Server{
callback(true, true);
}
/**
* Creates an instance of `SOCKSServer`.
* @param {TorPool} tor_pool - The pool of instances that will be used for requests
* @param {Logger} [logger] - Winston logger that will be used for logging. If not specified will disable logging.
* @param {ProxyByNameConfig} [proxy_by_name] - Enable routing to specific instances or groups of instances using the username field (socks://instance-1:@my-server:9050) when connecting.
*/
constructor(tor_pool, logger, proxy_by_name) {
let handleConnection = (info, accept, deny) => {
/**
* Handles SOCKS5 inbound connections.
*
* @function handle_connections
* @param {object} info - Information about the inbound connection.
* @param {Function} accept - Callback that allows the connection.
* @param {Function} deny - Callback that denies the connection.
* @private
*/
const handle_connections = (info, accept, deny) => {
let inbound_socket = accept(true);
let instance;
@ -77,8 +145,6 @@ class SOCKSServer extends Server{
let connect = (tor_instance) => {
let source = { hostname: info.srcAddr, port: info.srcPort, proto: 'socks', by_name: Boolean(instance) };
let socks_port = tor_instance.socks_port;
this.emit('instance-connection', tor_instance, source);
this.logger.verbose(`[socks]: ${source.hostname}:${source.port} → 127.0.0.1:${socks_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' }${info.dstAddr}:${info.dstPort}`)
socks.connect({
host: info.dstAddr,
@ -88,6 +154,16 @@ class SOCKSServer extends Server{
localDNS: false,
auths: [ socks.auth.None() ]
}, ($outbound_socket) => {
/**
* Fires when the proxy has made a connection through an instance.
*
* @event SOCKSServer#instance-connection
* @param {TorProcess} instance - Instance that has been connected to.
* @param {InstanceConnectionSource} source - Details on the source of the connection.
*/
this.emit('instance_connection', tor_instance, source);
this.logger.verbose(`[socks]: ${source.hostname}:${source.port} → 127.0.0.1:${socks_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' }${info.dstAddr}:${info.dstPort}`)
outbound_socket = $outbound_socket;
outbound_socket && outbound_socket.on('close', onClose);
@ -123,9 +199,8 @@ class SOCKSServer extends Server{
this.logger.debug(`[socks]: 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);
}
};
super(handleConnection);
}
super(handle_connections);
let auth = socks.auth.None();
@ -135,11 +210,34 @@ class SOCKSServer extends Server{
this.useAuth(auth);
this.logger = logger || require('./winston-silent-logger');
/**
* Winston logger to use.
*
* @type {Logger}
* @public
*/
this.logger = logger || require('./winston_silent_logger');
/**
* Pool of instances use to service requests.
*
* @type {TorPool}
* @public
*/
this.tor_pool = tor_pool;
/**
* Configuration for the "proxy by name" feature.
*
* @type {ProxyByNameConfig}
* @public
*/
this.proxy_by_name = proxy_by_name;
this.logger.debug(`[socks]: connecting to a specific instance by name has ben turned ${proxy_by_name ? 'on' : 'off'}`);
}
};
/**
* Module that contains the {@link SOCKSServer} class.
* @module tor-router/SOCKSServer
* @see SOCKSServer
*/
module.exports = SOCKSServer;

View File

@ -1,4 +1,6 @@
/* From http://stackoverflow.com/questions/1985260/javascript-array-rotate */
/**
* @author Christoph and Marco Bonelli on Stackoverflow <https://bit.ly/2p6gedO>
*/
Array.prototype.rotate = (function() {
// save references to array functions to make lookup faster
var push = Array.prototype.push,
@ -29,21 +31,19 @@ const TorProcess = require('./TorProcess');
Promise.promisifyAll(fs);
/**
* Class that represents a pool of Tor processes.
* @extends EventEmitter
*/
class TorPool extends EventEmitter {
/**
* Creates an instance of TorPool.
* Creates an instance of `TorPool`.
*
* @param {string} tor_path - Path to the Tor executable.
* @param {Object|Function} [default_config] - Default configuration that will be passed to all Tor instances created. Can be a function. See {@link https://bit.ly/2QrmI3o|Tor Documentation} for all possible options
* @param {string} data_directory - Parent directory for the data directory of each proccess.
* @param {string} load_balance_method - Name of the load balance method to use. See {@link TorPool#load_balance_methods}.
* @param {string} [granax_options] - Object containing options that will be passed to granax.
* @param {string} [granax_options] - Object containing options that will be passed to granax for each instance.
* @param {string} [logger] - A winston logger. If not provided no logging will occur.
*
* @throws If "data_directory" is not provided.
@ -54,10 +54,40 @@ class TorPool extends EventEmitter {
super();
this._instances = [];
this._default_tor_config = default_config;
/**
* Parent directory for the data directory of each proccess.
*
* @type {string}
* @public
*/
this.data_directory = data_directory;
/**
* Name of the load balance method to use.
*
* @type {string}
* @public
*/
this.load_balance_method = load_balance_method;
/**
* Path to the Tor executable.
*
* @type {string}
* @public
*/
this.tor_path = tor_path;
this.logger = logger || require('./winston-silent-logger');
/**
* The winston logger.
*
* @type {Logger}
* @public
*/
this.logger = logger || require('./winston_silent_logger');
/**
* Object containing options that will be passed to granax for each instance.
*
* @type {Logger}
* @public
*/
this.granax_options = granax_options;
}
@ -182,7 +212,7 @@ class TorPool extends EventEmitter {
* Represents a group of instances. Group is a Proxy with an array as its object. The array is generated by calling {@link TorPool#instances_in_group}.
* When called with an index (e.g. `Group[0]`) will return the instance at that index.
* Helper functions are available as properties.
* @typedef {TorProcess[]} Group
* @typedef {TorProcess[]} InstanceGroup
*
* @property {Function} add - Adds an instance to the group.
* @property {Function} remove - Removes an instance from the group.
@ -196,14 +226,14 @@ class TorPool extends EventEmitter {
/**
* Represents a collection of groups as an associative array. GroupCollection is a Proxy with a Set as its object. The Set is {@link TorPool#group_names}.
* If a non-existant group is referenced (e.g. `Groups["doesn't exist"]`) it will be created. So `Groups["doesn't exist"].add(my_instance)` will create the group and add the instance to it.
* @typedef {Group[]} GroupCollection
* @typedef {InstanceGroup[]} InstanceGroupCollection
*/
/**
* Represents all groups currently in the pool.
*
* @readonly
* @type {GroupCollection}
* @type {InstanceGroupCollection}
*/
get groups() {
let groupHandler = {
@ -745,4 +775,9 @@ class TorPool extends EventEmitter {
}
};
/**
* Module that contains the {@link TorPool} class.
* @module tor-router/TorPool
* @see TorPool
*/
module.exports = TorPool;

View File

@ -5,7 +5,7 @@ const crypto = require('crypto');
const { connect } = require('net');
const os = require('os');
const Promise = require('bluebird');
const Promise = require('bluebird');
const _ = require('lodash');
const { EventEmitter } = require('eventemitter3');
const shell = require('shelljs');
@ -36,7 +36,7 @@ temp.track();
*/
class TorProcess extends EventEmitter {
/**
* Creates an instance of TorProcess
* Creates an instance of `TorProcess`
*
* @param {string} tor_path - Path to the Tor executable.
* @param {InstanceDefinition} [definition] - Object containing various options for the instance. See {@link InstanceDefinition} for more info.
@ -45,7 +45,7 @@ class TorProcess extends EventEmitter {
*/
constructor(tor_path, definition, granax_options, logger) {
super();
this.logger = logger || require('./winston-silent-logger');
this.logger = logger || require('./winston_silent_logger');
definition = definition || {};
definition.Group = definition.Group ? [].concat(definition.Group) : [];
@ -305,7 +305,6 @@ class TorProcess extends EventEmitter {
tor.stderr.on('data', (data) => {
let error_message = Buffer.from(data).toString('utf8');
this.emit('error', new Error(error_message));
});
@ -315,29 +314,42 @@ class TorProcess extends EventEmitter {
this.logger.info(`[tor-${this.instance_name}]: tor is ready`);
});
this.on('control_listen', () => {
this._controller = new TorController(connect(this._control_port), _.extend({ authOnConnect: false }, this.granax_options));
Promise.promisifyAll(this._controller);
this.controller.on('ready', () => {
this.logger.debug(`[tor-${this.instance_name}]: connected to tor control port`);
this.controller.authenticate(`"${this.control_password}"`, (err) => {
if (err) {
this.logger.error(`[tor-${this.instance_name}]: ${err.stack}`);
this.emit('error', err);
} else {
this.logger.debug(`[tor-${this.instance_name}]: authenticated with tor instance via the control port`);
this.control_port_connected = true;
/**
* An event that fires when a connection has been established to the control protocol.
*
* @event TorProcess#controller_ready
*/
this.emit('controller_ready');
}
this.on('control_listen', (async () => {
try {
let socket = connect(this.control_port);
socket.on('error', ((error) => {
this.logger.error(`[tor-${this.instance_name}]: ${error.stack}`);
this.emit('error', error);
}));
this._controller = new TorController(socket, _.extend({ authOnConnect: false }, this.granax_options));
Promise.promisifyAll(this._controller);
this.controller.on('ready', () => {
this.logger.debug(`[tor-${this.instance_name}]: connected via the tor control protocol`);
this.controller.authenticate(`"${this.control_password}"`, (err) => {
if (err) {
this.logger.error(`[tor-${this.instance_name}]: ${err.stack}`);
this.emit('error', err);
} else {
this.logger.debug(`[tor-${this.instance_name}]: authenticated with tor instance via the control protocol`);
/**
* Is true when the connected via the control protcol
* @type {boolean}
*/
this.control_port_connected = true;
/**
* An event that fires when a connection has been established to the control protocol.
*
* @event TorProcess#controller_ready
*/
this.emit('controller_ready');
}
});
});
});
});
} catch (error) {
this.logger.error(`[tor-${this.instance_name}]: ${err.stack}`);
this.emit('error', err);
}
}));
tor.stdout.on('data', (data) => {
let text = Buffer.from(data).toString('utf8');
@ -373,18 +385,18 @@ class TorProcess extends EventEmitter {
}
if (text.indexOf('Opening DNS listener on') !== -1) {
this.dns_port_listening = true;
/**
* An event that fires when the Tor process has started listening for DNS traffic.
*
* @event TorProcess#dns_listen
*/
this.dns_port_listening = true;
this.emit('dns_listen');
}
if (text.indexOf('[err]') !== -1) {
/**
* An event that fires the Tor process has written an error to stdout or stderr or an error occured connecting to the control protocol.
* An event that fires when the Tor process has written an error to stdout, stderr or when an error occurs connecting via the control protocol.
*
* @event TorProcess#error
* @type {Error}
@ -409,4 +421,9 @@ class TorProcess extends EventEmitter {
}
};
/**
* Module that contains the {@link TorProcess} class.
* @module tor-router/TorProcess
* @see TorProcess
*/
module.exports = TorProcess;

View File

@ -1,7 +1,11 @@
// Default configuration for Tor Router
const temp = require('temp');
const path = require('path');
temp.track();
/**
* This module cotains the default configuration for the application.
* @module tor-router/default_config
*/
module.exports = {
"controlHost": 9077,
"websocketControlHost": null,
@ -20,7 +24,9 @@ module.exports = {
"torPath": (() => {
let platform = require('os').platform();
let BIN_PATH = path.join(__dirname, '..', 'node_modules', 'granax', 'bin');
/* Taken from https://github.com/bookchin/granax/blob/master/index.js */
/**
* @author gordonhall on GitLab <https://bit.ly/2xcahjY>
*/
switch (platform) {
case 'win32':
return path.join(BIN_PATH, 'Browser', 'TorBrowser', 'Tor', 'tor.exe');
@ -39,8 +45,8 @@ module.exports = {
})(),
"instances": null,
"dns": {
"options": {},
"timeout": null
"timeout": 10000,
"options": {}
},
"granaxOptions": null
};

View File

@ -1,3 +1,7 @@
/**
* This module cotains the default ports the application will bind to.
* @module tor-router/default_ports
*/
module.exports = Object.freeze({
socks: 9050,
http: 9080,

View File

@ -1,8 +1,12 @@
/**
* Index module for the project
* @module tor-router
*/
module.exports = {
TorProcess: require('./TorProcess'),
TorPool: require('./TorPool'),
DNSServer: require('./DNSServer'),
SOCKSServer: require('./SOCKSServer'),
HTTPServer: require('./HTTPServer'),
DNSServer: require('./DNSServer'),
ControlServer: require('./ControlServer')
};

View File

@ -1,6 +1,6 @@
const fs = require('fs');
const nconf = require('nconf');
const { Provider } = require('nconf');
const yargs = require('yargs');
const winston = require('winston')
const Promise = require('bluebird');
@ -10,20 +10,46 @@ const default_ports = require('./default_ports');
const package_json = JSON.parse(fs.readFileSync(`${__dirname}/../package.json`, 'utf8'));
/**
* @typedef Host
* @property {string} hostname - The hostname
* @property {number} port - The port
* @private
*/
/**
* Extracts the host and port components from a string
* @param {string} host
* @returns {Host}
* @private
*/
function extractHost (host) {
if (typeof(host) === 'number')
return { hostname: (typeof(default_ports.default_host) === 'string' ? default_ports.default_host : '0.0.0.0'), port: host };
return { hostname: (typeof(default_ports.default_host) === 'string' ? default_ports.default_host : ''), port: host };
else if (typeof(host) === 'string' && host.indexOf(':') !== -1)
return { hostname: host.split(':').shift(), port: Number(host.split(':').pop()) };
else
return null;
}
/**
* Takes an object with a hostname and port returns a formatted string
* @param {Host} host
* @returns {string} - Formatted host (e.g. "0.0.0.0:1234")
*/
function assembleHost(host) {
return `${typeof(host.hostname) === 'string' ? host.hostname : '' }:${host.port}`;
}
/**
* Main function for the application
* @param {Provider} nconf - Instance of `nconf.Provider` used for configuration.
* @param {Logger} [logger] - Winston logger to be used for logging. If not provided will disable logging.
* @async
* @returns {Promise}
*/
async function main(nconf, logger) {
Promise.promisifyAll(nconf);
let instances = nconf.get('instances');
let socks_host = typeof(nconf.get('socksHost')) !== 'boolean' ? extractHost(nconf.get('socksHost')) : nconf.get('socksHost');
let dns_host = typeof(nconf.get('dnsHost')) !== 'boolean' ? extractHost(nconf.get('dnsHost')) : nconf.get('dnsHost');
@ -44,7 +70,7 @@ async function main(nconf, logger) {
nconf.set('websocketControlPort', assembleHost(control_host_ws));
}
let control = new ControlServer(logger, nconf);
let control = new ControlServer(nconf, logger);
try {
await control.listenTcp(control_host.port, control_host.hostname);
@ -80,7 +106,7 @@ async function main(nconf, logger) {
if (instances) {
logger.info(`[tor]: starting ${Array.isArray(instances) ? instances.length : instances} tor instance(s)...`);
await control.torPool.create(instances);
await control.tor_pool.create(instances);
logger.info('[tor]: tor started');
}
@ -89,11 +115,17 @@ async function main(nconf, logger) {
process.exit(1);
}
/**
* Kills all tor processes and exits, logging an error if one occurs.
* @function cleanUp
*
* @param {Error} error - Error or exit code
*/
const cleanUp = (async (error) => {
let thereWasAnExitError = false;
let { handleError } = this;
try {
await control.torPool.exit();
await control.tor_pool.exit();
} catch (exitError) {
logger.error(`[global]: error closing tor instances: ${exitError.message}`);
thereWasAnExitError = true;
@ -111,7 +143,7 @@ async function main(nconf, logger) {
process.title = 'tor-router';
process.on('SIGHUP', () => {
control.torPool.new_identites();
control.tor_pool.new_identites();
});
process.on('exit', cleanUp);
@ -119,6 +151,12 @@ async function main(nconf, logger) {
process.on('uncaughtException', cleanUp.bind({ handleError: true }));
}
/**
* Instance of `nconf.Provider`
* @type {Provider}
*/
let nconf = new Provider();
let argv_config =
yargs
.version(package_json.version)
@ -190,8 +228,6 @@ let argv_config =
}
});
Promise.promisifyAll(nconf);
require(`${__dirname}/../src/nconf_load_env.js`)(nconf);
nconf
.argv(argv_config);
@ -211,6 +247,10 @@ nconf.defaults(require(`${__dirname}/../src/default_config.js`));
let logLevel = nconf.get('logLevel');
/**
* Instnace of `winston.Logger`
* @type {Logger}
*/
let logger = winston.createLogger({
level: logLevel,
format: winston.format.simple(),
@ -218,4 +258,8 @@ let logger = winston.createLogger({
transports: [ new (winston.transports.Console)({ level: (logLevel !== 'null' ? logLevel : void(0)), silent: (logLevel === 'null') }) ]
});
/**
* Exports the main function for the application, a configured `nconf.Provider` instance and a winston logger
* @module tor-router/launch
*/
module.exports = { main, nconf, logger };

View File

@ -1,47 +1,63 @@
const env_whitelist = [ "CONTROL_HOST",
"TOR_PATH",
"INSTANCES",
"SOCKS_HOST",
"DNS_HOST",
"HTTP_HOST",
"LOG_LEVEL",
'PARENT_DATA_DIRECTORIES',
'LOAD_BALANCE_METHOD',
"WEBSOCKET_CONTROL_PORT",
"PROXY_BY_NAME",
"DENY_UNIDENTIFIED_USERS",
"controlHost",
"torPath",
"instances",
"socksHost",
"dnsHost",
"httpHost",
"logLevel",
'parentDataDirectories',
'loadBalanceMethod',
"websocketControlPort",
"proxyByName",
"denyUnidentifedUsers"
];
/**
* An array of all valid environment variables
* @constant
* @type {string[]}
*/
const env_whitelist = [
"CONTROL_HOST",
"TOR_PATH",
"INSTANCES",
"SOCKS_HOST",
"DNS_HOST",
"HTTP_HOST",
"LOG_LEVEL",
'PARENT_DATA_DIRECTORIES',
'LOAD_BALANCE_METHOD',
"WEBSOCKET_CONTROL_PORT",
"PROXY_BY_NAME",
"DENY_UNIDENTIFIED_USERS"
];
module.exports = (nconf) => {
/**
* Converts a configuration property's name from env variable format to application config format
* `"CONTROL_HOST"` -> `"controlHost"`
* @param {string} env - Environment variable
* @returns {string}
* @private
*/
function env_to_config(env) {
let a = env.toLowerCase().split('_');
i = 1;
while (i < a.length) {
a[i] = a[i][0].toUpperCase() + a[i].substr(1);
i++;
}
return a.join('');
}
/**
* Sets up nconf with the `env` store.
* @param {Provider} nconf - Instance of `nconf.Provider`.
* @returns {Provider} - Same instance of `nconf.Provider`.
*/
function setup_nconf_env(nconf) {
return nconf
.env({
whitelist: env_whitelist,
whitelist: env_whitelist.concat(env_whitelist.map(env_to_config)),
parseValues: true,
transform: (obj) => {
if (env_whitelist.includes(obj.key)) {
if (obj.key.indexOf('_') !== -1) {
let a = obj.key.toLowerCase().split('_');
i = 1;
while (i < a.length) {
a[i] = a[i][0].toUpperCase() + a[i].substr(1);
i++;
}
obj.key = a.join('');
obj.key = env_to_config(obj.key);
}
}
return obj;
}
});
};
};
/**
* This module returns a function
* @module tor-router/nconf_load_env
*/
module.exports = setup_nconf_env;

View File

@ -1,5 +1,9 @@
const winston = require('winston');
/**
* Contains a winston `Logger` that is silent (doesn't log anything).
* @module tor-router/winston_silent_logger
*/
module.exports = winston.createLogger({
level: 'info',
format: winston.format.simple(),

View File

@ -1,7 +1,8 @@
const _ = require('lodash');
const { assert } = require('chai');
const nconf = require('nconf');
const { Provider } = require('nconf');
const nconf = new Provider();
const getPort = require('get-port');
nconf.use('memory');
@ -9,7 +10,7 @@ require(`${__dirname}/../src/nconf_load_env.js`)(nconf);
nconf.defaults(require(`${__dirname}/../src/default_config.js`));
const { ControlServer, TorPool, HTTPServer, SOCKSServer, DNSServer } = require('../');
let controlServer = new ControlServer(null, nconf);
let controlServer = new ControlServer(nconf);
let controlPort;
describe('ControlServer', function () {
@ -23,7 +24,7 @@ describe('ControlServer', function () {
it('should create a TorPool with a given configuration', function () {
let torPool = controlServer.createTorPool({ ProtocolWarnings: 1 });
assert.instanceOf(controlServer.torPool, TorPool);
assert.instanceOf(controlServer.tor_pool, TorPool);
assert.equal(1, torPool.default_tor_config.ProtocolWarnings);
});
});
@ -56,6 +57,6 @@ describe('ControlServer', function () {
});
after('shutdown tor pool', async function () {
await controlServer.torPool.exit();
await controlServer.tor_pool.exit();
});
});

View File

@ -1,9 +1,11 @@
const nconf = require('nconf');
const { Provider } = require('nconf');
const nconf = new Provider();
const getPort = require('get-port');
const dns = require('native-dns');
const { assert } = require('chai');
const { TorPool, DNSServer } = require('../');
const { TorPool, DNSServer, TorProcess } = require('../');
const { WAIT_FOR_CREATE } = require('./constants');
nconf.use('memory');
@ -14,7 +16,7 @@ let dnsServerTorPool;
let dnsServer;
describe('DNSServer', function () {
dnsServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
dnsServer = new DNSServer(dnsServerTorPool, {}, 10000);
dnsServer = new DNSServer(dnsServerTorPool, {}, nconf.get('dns:timeout'));
let dnsPort;
before('start up server', async function (){
this.timeout(WAIT_FOR_CREATE);
@ -48,6 +50,31 @@ describe('DNSServer', function () {
req.send();
});
it('should emit the "instance_connection" event', function (done) {
this.timeout(10000);
dnsServer.on('instance_connection', (instance, source) => {
assert.instanceOf(instance, TorProcess);
assert.isObject(source);
done();
});
let req = dns.Request({
question: dns.Question({
name: 'example.com',
type: 'A'
}),
server: { address: '127.0.0.1', port: dnsPort, type: 'udp' },
timeout: 10000,
});
req.on('timeout', function () {
done(new Error('Connection timed out'));
});
req.send();
});
});
after('shutdown server', function () {

View File

@ -1,11 +1,19 @@
const nconf = require('nconf');
const request = require('request-promise');
const { Provider } = require('nconf');
const nconf = new Provider();
const getPort = require('get-port');
const { assert } = require('chai');
const _ = require('lodash');
const Promise = require('bluebird');
const { TorPool, HTTPServer } = require('../');
const { WAIT_FOR_CREATE, PAGE_LOAD_TIME } = require('./constants');
const { TorPool, HTTPServer, TorProcess } = require('../');
const { WAIT_FOR_CREATE, PAGE_LOAD_TIME, RETRY_DELAY, RETRY_STRATEGY, MAX_ATTEMPTS } = require('./constants');
const request = require('requestretry').defaults({
promiseFactory: ((resolver) => new Promise(resolver)),
maxAttempts: MAX_ATTEMPTS,
retryStrategy: RETRY_STRATEGY,
retryDelay: RETRY_DELAY
});
nconf.use('memory');
require(`${__dirname}/../src/nconf_load_env.js`)(nconf);
@ -39,6 +47,29 @@ describe('HTTPServer', function () {
});
});
it('should emit the "instance_connection" event', function (done) {
this.timeout(PAGE_LOAD_TIME);
let connectionHandler = (instance, source) => {
assert.instanceOf(instance, TorProcess);
assert.isObject(source);
httpServer.removeAllListeners('instance_connection');;
done();
};
httpServer.on('instance_connection', connectionHandler);
request({
url: 'http://example.com',
proxy: `http://127.0.0.1:${httpPort}`
})
.catch((err) => {
})
});
after('shutdown server', function () {
httpServer.close();
});
@ -48,7 +79,7 @@ describe('HTTPServer', function () {
});
});
return
describe('#handle_connect_connections(req, inbound_socket, head)', function () {
let httpServerTorPool;
let httpServer;
@ -70,11 +101,33 @@ describe('HTTPServer', function () {
this.timeout(PAGE_LOAD_TIME);
await request({
url: 'http://example.com',
url: 'https://example.com',
proxy: `http://127.0.0.1:${httpPort}`
});
});
it('should emit the "instance_connection" event', function (done) {
this.timeout(PAGE_LOAD_TIME);
let connectionHandler = (instance, source) => {
assert.instanceOf(instance, TorProcess);
assert.isObject(source);
httpServer.removeAllListeners('instance_connection');;
done();
};
httpServer.on('instance_connection', connectionHandler);
request({
url: 'https://example.com',
proxy: `http://127.0.0.1:${httpPort}`
})
.catch((error) => {
});
});
after('shutdown server', function () {
httpServer.close();
});
@ -117,11 +170,11 @@ describe('HTTPServer', function () {
assert.isTrue(source.by_name);
req.cancel();
httpServer.removeAllListeners('instance-connection');;
httpServer.removeAllListeners('instance_connection');;
done();
};
httpServer.on('instance-connection', connectionHandler);
httpServer.on('instance_connection', connectionHandler);
req = request({
url: 'http://example.com',
@ -129,8 +182,7 @@ describe('HTTPServer', function () {
})
.catch(done)
});
it(`four requests made to example.com through the group named "foo" should come from the instances in "foo"`, function (done) {
(async () => {
this.timeout(PAGE_LOAD_TIME + (WAIT_FOR_CREATE));
@ -145,8 +197,6 @@ describe('HTTPServer', function () {
.then(async () => {
httpServer.proxy_by_name.mode = "group";
let request = require('request-promise').defaults({ proxy: `http://foo:@127.0.0.1:${httpPort}` });
let names_requested = [];
let connectionHandler = (instance, source) => {
@ -158,17 +208,18 @@ describe('HTTPServer', function () {
let names_in_group = httpServerTorPool.instances_by_group('foo').map((i) => i.instance_name).sort()
assert.deepEqual(names_requested, names_in_group);
httpServer.removeAllListeners('instance-connection');
httpServer.removeAllListeners('instance_connection');
done();
}
};
httpServer.on('instance-connection', connectionHandler);
httpServer.on('instance_connection', connectionHandler);
let i = 0;
while (i < httpServerTorPool.instances.length) {
await request({
url: 'http://example.com'
url: 'http://example.com',
proxy: `http://foo:@127.0.0.1:${httpPort}`
});
i++;
}
@ -183,11 +234,13 @@ describe('HTTPServer', function () {
it(`shouldn't be able to send a request without a username`, async function() {
let f = () => {};
try {
await request({
let res = await request({
url: 'http://example.com',
proxy: `https://127.0.0.1:${httpPort}`,
proxy: `http://127.0.0.1:${httpPort}`,
timeout: PAGE_LOAD_TIME
});
if (res.statusCode === 407)
throw new Error('407');
} catch (error) {
f = () => { throw error };
} finally {
@ -198,11 +251,14 @@ describe('HTTPServer', function () {
it(`shouldn't be able to send a request with an incorrect username`, async function() {
let f = () => {};
try {
await request({
let res = await request({
url: 'http://example.com',
proxy: `http://blah-blah-blah:@127.0.0.1:${httpPort}`,
timeout: PAGE_LOAD_TIME
timeout: PAGE_LOAD_TIME,
fullResponse: true
});
if (res.statusCode === 407)
throw new Error('407');
} catch (error) {
f = () => { throw error };
} finally {
@ -218,7 +274,7 @@ describe('HTTPServer', function () {
await httpServerTorPool.exit();
});
});
describe('#authenticate_user_connect(req, socket)', function () {
let httpServerTorPool;
let httpServer;
@ -250,11 +306,11 @@ describe('HTTPServer', function () {
assert.equal(instance.instance_name, instance_def.Name);
assert.isTrue(source.by_name);
req.cancel();
httpServer.removeAllListeners('instance-connection');
httpServer.removeAllListeners('instance_connection');
done();
};
httpServer.on('instance-connection', connectionHandler);
httpServer.on('instance_connection', connectionHandler);
req = request({
url: 'https://example.com',
@ -290,12 +346,12 @@ describe('HTTPServer', function () {
let names_in_group = httpServerTorPool.instances_by_group('foo').map((i) => i.instance_name).sort()
assert.deepEqual(names_requested, names_in_group);
httpServer.removeAllListeners('instance-connection');
httpServer.removeAllListeners('instance_connection');
done();
}
};
httpServer.on('instance-connection', connectionHandler);
httpServer.on('instance_connection', connectionHandler);
let i = 0;
while (i < httpServerTorPool.instances.length) {
@ -323,7 +379,7 @@ describe('HTTPServer', function () {
} catch (error) {
f = () => { throw error };
} finally {
assert.throws(f, "socket hang up", "Did not hang up");
assert.throws(f, "statusCode=407", "Did not return 407 status code");
}
});
@ -338,7 +394,7 @@ describe('HTTPServer', function () {
} catch (error) {
f = () => { throw error };
} finally {
assert.throws(f, "socket hang up", "Did not hang up");
assert.throws(f, "statusCode=407", "Did not hang up");
}
});

View File

@ -2,7 +2,8 @@
const _ = require('lodash');
const assert = require('chai').assert;
const Promise = require('bluebird');
const nconf = require('nconf');
const { Provider } = require('nconf');
const nconf = new Provider();
const rpc = require('jrpc2');
const getPort = require('get-port');
const temp = require('temp');
@ -16,7 +17,7 @@ const { WAIT_FOR_CREATE } = require('./constants');
Promise.promisifyAll(fs);
Promise.promisifyAll(temp);
let rpcControlServer = new ControlServer(null, nconf);
let rpcControlServer = new ControlServer(nconf);
let rpcControlPort;
let rpcClient;
@ -91,7 +92,7 @@ describe('ControlServer - RPC Interface', function () {
});
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-2'));
assert.ok(rpcControlServer.tor_pool.instance_by_name('instance-2'));
});
});
@ -123,11 +124,11 @@ describe('ControlServer - RPC Interface', function () {
});
it('"instance-1" should be added to "baz"', function () {
assert.include(rpcControlServer.torPool.instances_by_group('baz').map((i) => i.instance_name), "instance-1");
assert.include(rpcControlServer.tor_pool.instances_by_group('baz').map((i) => i.instance_name), "instance-1");
});
after('remove from group', function () {
rpcControlServer.torPool.groups['baz'].remove_by_name('instance-1');
rpcControlServer.tor_pool.groups['baz'].remove_by_name('instance-1');
});
});
@ -137,17 +138,17 @@ describe('ControlServer - RPC Interface', function () {
});
it('"instance-1" should be added to "baz"', function () {
assert.include(rpcControlServer.torPool.instances_by_group('baz').map((i) => i.instance_name), "instance-1");
assert.include(rpcControlServer.tor_pool.instances_by_group('baz').map((i) => i.instance_name), "instance-1");
});
after('remove from group', function () {
rpcControlServer.torPool.groups['baz'].remove_by_name('instance-1');
rpcControlServer.tor_pool.groups['baz'].remove_by_name('instance-1');
});
});
describe('#removeInstanceFromGroupByName()', function () {
before('add to group', function () {
rpcControlServer.torPool.groups['baz'].add_by_name('instance-1');
rpcControlServer.tor_pool.groups['baz'].add_by_name('instance-1');
});
it(`should remove "instance-1" from baz`, async function () {
@ -155,13 +156,13 @@ describe('ControlServer - RPC Interface', function () {
});
it('"instance-1" should be remove from to "baz"', function () {
assert.notInclude(rpcControlServer.torPool.instances_by_group('baz').map((i) => i.instance_name), "instance-1");
assert.notInclude(rpcControlServer.tor_pool.group_names, "baz");
});
});
describe('#removeInstanceFromGroupAt()', function () {
before('add to group', function () {
rpcControlServer.torPool.groups['baz'].add_by_name('instance-1');
rpcControlServer.tor_pool.groups['baz'].add_by_name('instance-1');
});
it(`should remove "instance-1" from baz`, async function () {
@ -169,10 +170,9 @@ describe('ControlServer - RPC Interface', function () {
});
it('"instance-1" should be remove from to "baz"', function () {
assert.notInclude(rpcControlServer.torPool.instances_by_group('baz').map((i) => i.instance_name), "instance-1");
assert.notInclude(rpcControlServer.tor_pool.group_names, "baz");
});
});
describe('#newIdentites()', function () {
this.timeout(3000);
@ -209,7 +209,7 @@ describe('ControlServer - RPC Interface', function () {
});
it('all instances should have the modified variables', async function() {
await Promise.all(rpcControlServer.torPool.instances.map(async (instance) => {
await Promise.all(rpcControlServer.tor_pool.instances.map(async (instance) => {
let var1 = await instance.get_config('TestSocks');
let var2 = await instance.get_config('ProtocolWarnings');
@ -219,8 +219,8 @@ describe('ControlServer - RPC Interface', function () {
});
after('unset config variables', async function () {
await rpcControlServer.torPool.set_config_all('TestSocks', 0);
await rpcControlServer.torPool.set_config_all('ProtocolWarnings', 0);
await rpcControlServer.tor_pool.set_config_all('TestSocks', 0);
await rpcControlServer.tor_pool.set_config_all('ProtocolWarnings', 0);
});
});
@ -245,13 +245,13 @@ describe('ControlServer - RPC Interface', function () {
});
it('all instances should have the config value set', async function () {
let values = _.flatten(await Promise.all(rpcControlServer.torPool.instances_by_group('foo').map((i) => i.get_config('ProtocolWarnings'))));
let values = _.flatten(await Promise.all(rpcControlServer.tor_pool.instances_by_group('foo').map((i) => i.get_config('ProtocolWarnings'))));
assert.isTrue(values.every((v) => v === "1"));
});
after('unset config values', async function () {
rpcControlServer.torPool.set_config_by_group('foo', 'ProtocolWarnings', 0);
rpcControlServer.tor_pool.set_config_by_group('foo', 'ProtocolWarnings', 0);
});
});
@ -337,7 +337,7 @@ describe('ControlServer - RPC Interface', function () {
describe('#getLoadBalanceMethod()', function () {
this.timeout(3000);
before(function () {
rpcControlServer.torPool.load_balance_method = 'round_robin';
rpcControlServer.tor_pool.load_balance_method = 'round_robin';
});
it('should return the current load balance method', async function () {
@ -355,11 +355,11 @@ describe('ControlServer - RPC Interface', function () {
});
it('the load balance method should be changed', function () {
assert.equal(rpcControlServer.torPool.load_balance_method, 'weighted');
assert.equal(rpcControlServer.tor_pool.load_balance_method, 'weighted');
});
after(function () {
rpcControlServer.torPool.load_balance_method = 'round_robin';
rpcControlServer.tor_pool.load_balance_method = 'round_robin';
});
});
@ -367,7 +367,7 @@ describe('ControlServer - RPC Interface', function () {
this.timeout(3000);
before('set config property', async function () {
await rpcControlServer.torPool.instance_by_name('instance-1').set_config('TestSocks', 1);
await rpcControlServer.tor_pool.instance_by_name('instance-1').set_config('TestSocks', 1);
});
it('should retrieve the property from the tor instance', async function () {
@ -379,7 +379,7 @@ describe('ControlServer - RPC Interface', function () {
});
after('unset config property', async function () {
await rpcControlServer.torPool.instance_by_name('instance-1').set_config('TestSocks', 0);
await rpcControlServer.tor_pool.instance_by_name('instance-1').set_config('TestSocks', 0);
});
});
@ -387,7 +387,7 @@ describe('ControlServer - RPC Interface', function () {
this.timeout(3000);
before('set config property', async function () {
await rpcControlServer.torPool.instance_at(0).set_config('TestSocks', 1);
await rpcControlServer.tor_pool.instance_at(0).set_config('TestSocks', 1);
});
it('should retrieve the property from the tor instance', async function () {
@ -399,7 +399,7 @@ describe('ControlServer - RPC Interface', function () {
});
after('unset config property', async function () {
await rpcControlServer.torPool.instance_at(0).set_config('TestSocks', 0);
await rpcControlServer.tor_pool.instance_at(0).set_config('TestSocks', 0);
});
});
@ -407,7 +407,7 @@ describe('ControlServer - RPC Interface', function () {
this.timeout(3000);
before('set config property', async function () {
await rpcControlServer.torPool.instance_by_name('instance-1').set_config('TestSocks', 0);
await rpcControlServer.tor_pool.instance_by_name('instance-1').set_config('TestSocks', 0);
});
it('should set the property for the tor instance', async function () {
@ -415,12 +415,12 @@ describe('ControlServer - RPC Interface', function () {
});
it('tor instance should have the modified property', async function () {
let value = await rpcControlServer.torPool.instance_by_name('instance-1').get_config('TestSocks');
let value = await rpcControlServer.tor_pool.instance_by_name('instance-1').get_config('TestSocks');
assert.equal(value, 1);
});
after('unset config property', async function () {
await rpcControlServer.torPool.instance_by_name('instance-1').set_config('TestSocks', 0);
await rpcControlServer.tor_pool.instance_by_name('instance-1').set_config('TestSocks', 0);
});
});
@ -428,7 +428,7 @@ describe('ControlServer - RPC Interface', function () {
this.timeout(3000);
before('set config property', async function () {
await rpcControlServer.torPool.instance_at(0).set_config('TestSocks', 0);
await rpcControlServer.tor_pool.instance_at(0).set_config('TestSocks', 0);
});
it('should set the property for the tor instance', async function () {
@ -436,12 +436,12 @@ describe('ControlServer - RPC Interface', function () {
});
it('tor instance should have the modified property', async function () {
let value = await rpcControlServer.torPool.instance_at(0).get_config('TestSocks');
let value = await rpcControlServer.tor_pool.instance_at(0).get_config('TestSocks');
assert.equal(value, 1);
});
after('unset config property', async function () {
await rpcControlServer.torPool.instance_at(0).set_config('TestSocks', 0);
await rpcControlServer.tor_pool.instance_at(0).set_config('TestSocks', 0);
});
});
@ -476,31 +476,31 @@ describe('ControlServer - RPC Interface', function () {
this.timeout(3000);
let instance_name;
it('should rotate the 0th item in the instances array', async function () {
instance_name = rpcControlServer.torPool.instances[0].instance_name;
instance_name = rpcControlServer.tor_pool.instances[0].instance_name;
await rpcClient.invokeAsync('nextInstance', []);
});
it('0th item in the instances array should be different after nextInstance is called', function () {
assert.notEqual(rpcControlServer.torPool.instances[0].instance_name, instance_name);
assert.notEqual(rpcControlServer.tor_pool.instances[0].instance_name, instance_name);
});
});
describe('#nextInstanceByGroup(group)', function () {
before('add "instance-1" to "foo"', function () {
rpcControlServer.torPool.add_instance_to_group_by_name('foo', 'instance-2');
rpcControlServer.tor_pool.add_instance_to_group_by_name('foo', 'instance-2');
});
it('should rotate the instances in group "foo"', async function () {
this.timeout(5000);
let first_instance_name_before = rpcControlServer.torPool.groups['foo'][0].instance_name;
let first_instance_name_before = rpcControlServer.tor_pool.groups['foo'][0].instance_name;
await rpcClient.invokeAsync('nextInstanceByGroup', [ 'foo' ]);
let first_instance_name_after = rpcControlServer.torPool.groups['foo'][0].instance_name;
let first_instance_name_after = rpcControlServer.tor_pool.groups['foo'][0].instance_name;
assert.notEqual(first_instance_name_after, first_instance_name_before);
});
after('remove "instance-1" from "foo"', function () {
rpcControlServer.torPool.remove_instance_from_group_by_name('foo', 'instance-2');
rpcControlServer.tor_pool.remove_instance_from_group_by_name('foo', 'instance-2');
})
});
@ -508,41 +508,41 @@ describe('ControlServer - RPC Interface', function () {
describe('#removeInstanceAt(index)', function () {
this.timeout(10000);
it("should remove an instance at the position specified", async function () {
instance_num1 = rpcControlServer.torPool.instances.length;
instance_num1 = rpcControlServer.tor_pool.instances.length;
await rpcClient.invokeAsync('removeInstanceAt', [0]);
});
it('the tor pool should contain one instance fewer', function () {
assert.equal(rpcControlServer.torPool.instances.length, (instance_num1 - 1));
assert.equal(rpcControlServer.tor_pool.instances.length, (instance_num1 - 1));
});
});
describe('#removeInstanceByName(instance_name)', function () {
this.timeout(10000);
it("should remove an instance at the position specified", async function () {
instance_num2 = rpcControlServer.torPool.instances.length;
instance_num2 = rpcControlServer.tor_pool.instances.length;
await rpcClient.invokeAsync('removeInstanceByName', [ "instance-1" ]);
});
it('the tor pool should contain one instance fewer', function () {
assert.equal(rpcControlServer.torPool.instances.length, (instance_num2 - 1));
assert.equal(rpcControlServer.tor_pool.instances.length, (instance_num2 - 1));
});
});
describe('#closeInstances()', function () {
this.timeout(10000);
it('should shutdown all instances', async function () {
instance_num = rpcControlServer.torPool.instances.length;
instance_num = rpcControlServer.tor_pool.instances.length;
await rpcClient.invokeAsync('closeInstances', [ ]);
});
it('no instances should be present in the pool', function () {
assert.equal(rpcControlServer.torPool.instances.length, 0);
assert.equal(rpcControlServer.tor_pool.instances.length, 0);
});
});
after('shutdown tor pool', async function () {
this.timeout(10000);
await rpcControlServer.torPool.exit();
await rpcControlServer.tor_pool.exit();
});
});

View File

@ -1,12 +1,20 @@
const nconf = require('nconf');
const request = require('request-promise');
const { Provider } = require('nconf');
const nconf = new Provider();
const getPort = require('get-port');
const { HttpAgent, auth } = require('socksv5');
const { assert } = require('chai');
const SocksProxyAgent = require('socks-proxy-agent');
const _ = require('lodash');
const { TorPool, SOCKSServer } = require('../');
const { WAIT_FOR_CREATE, PAGE_LOAD_TIME } = require('./constants');
const { TorPool, SOCKSServer, TorProcess } = require('../');
const { WAIT_FOR_CREATE, PAGE_LOAD_TIME, RETRY_DELAY, RETRY_STRATEGY, MAX_ATTEMPTS } = require('./constants');
const request = require('requestretry').defaults({
promiseFactory: ((resolver) => new Promise(resolver)),
maxAttempts: MAX_ATTEMPTS,
retryStrategy: RETRY_STRATEGY,
retryDelay: RETRY_DELAY
});
nconf.use('memory');
require(`${__dirname}/../src/nconf_load_env.js`)(nconf);
@ -21,7 +29,7 @@ describe('SOCKSServer', function () {
before('start up server', async function (){
socksServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
socksServer = new SOCKSServer(socksServerTorPool, null, false);
this.timeout(WAIT_FOR_CREATE);
await socksServerTorPool.create(1);
@ -29,25 +37,37 @@ describe('SOCKSServer', function () {
await socksServer.listen(socksPort);
});
it('should service a request for example.com', async function () {
this.timeout(PAGE_LOAD_TIME);
await request({
url: 'http://example.com',
agent: new HttpAgent({
proxyHost: '127.0.0.1',
proxyPort: socksPort,
localDNS: false,
auths: [ auth.None() ]
})
agent: new SocksProxyAgent(`socks5h://127.0.0.1:${socksPort}`)
});
});
after('shutdown server', function () {
socksServer.close();
it('should emit the "instance_connection" event', function (done) {
this.timeout(PAGE_LOAD_TIME);
let connectionHandler = (instance, source) => {
assert.instanceOf(instance, TorProcess);
assert.isObject(source);
socksServer.removeAllListeners('instance_connection');;
done();
};
socksServer.on('instance_connection', connectionHandler);
request({
url: 'http://example.com',
agent: new SocksProxyAgent(`socks5h://127.0.0.1:${socksPort}`)
})
.catch(done)
});
after('shutdown tor pool', async function () {
after('shutdown server and shutdown tor pool', async function () {
socksServer.close();
await socksServerTorPool.exit();
});
@ -79,29 +99,23 @@ describe('SOCKSServer', function () {
it(`should service a request for example.com through ${instance_def.Name}`, function (done) {
this.timeout(PAGE_LOAD_TIME);
let req;
let connectionHandler = (instance, source) => {
assert.equal(instance.instance_name, instance_def.Name);
assert.isTrue(source.by_name);
req.cancel();
socksServer.removeAllListeners('instance-connection');
socksServer.removeAllListeners('instance_connection');
done();
};
socksServer.on('instance-connection', connectionHandler);
req = request({
socksServer.on('instance_connection', connectionHandler);
request({
url: 'http://example.com',
agent: new HttpAgent({
proxyHost: '127.0.0.1',
proxyPort: socksPort,
localDNS: false,
auths: [ auth.UserPassword(instance_def.Name, "doesn't mater") ]
})
agent: new SocksProxyAgent(`socks5h://${instance_def.Name}:doesnotmatter@127.0.0.1:${socksPort}`)
})
.catch(done);
});
return
it(`four requests made to example.com through the group named "foo" should come from the instances in "foo"`, function (done) {
(async () => {
this.timeout(PAGE_LOAD_TIME + (WAIT_FOR_CREATE));
@ -116,15 +130,6 @@ describe('SOCKSServer', function () {
.then(async () => {
socksServer.proxy_by_name.mode = "group";
let request = require('request-promise').defaults({
agent: new HttpAgent({
proxyHost: '127.0.0.1',
proxyPort: socksPort,
localDNS: false,
auths: [ auth.UserPassword('foo', "doesn't mater") ]
})
});
let names_requested = [];
@ -137,17 +142,18 @@ describe('SOCKSServer', function () {
let names_in_group = socksServerTorPool.instances_by_group('foo').map((i) => i.instance_name).sort()
assert.deepEqual(names_requested, names_in_group);
socksServer.removeAllListeners('instance-connection');
socksServer.removeAllListeners('instance_connection');
done();
}
};
socksServer.on('instance-connection', connectionHandler);
socksServer.on('instance_connection', connectionHandler);
let i = 0;
while (i < socksServerTorPool.instances.length) {
await request({
url: 'http://example.com'
url: 'http://example.com',
agent: new SocksProxyAgent(`socks5h://foo:doesnotmatter@127.0.0.1:${socksPort}`)
});
i++;
}
@ -199,11 +205,8 @@ describe('SOCKSServer', function () {
// }
// });
after('shutdown server', function () {
after('shutdown server and shutdown tor pool', async function () {
socksServer.close();
});
after('shutdown tor pool', async function () {
await socksServerTorPool.exit();
});
});

View File

@ -1,4 +1,5 @@
const nconf = require('nconf');
const { Provider } = require('nconf');
const nconf = new Provider();
const { assert } = require('chai');
const Promise = require('bluebird');
const _ = require('lodash');
@ -605,10 +606,10 @@ describe('TorPool', function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.create(1);
await torPool.create([ { "Name": "instance-1", "Group": ["foo"] }, { "Name": "instance-2", "Group": ["foo"] } ]);
});
it('should send a signal to an instance identified by name', async function () {
it('should send a signal to a group of instances', async function () {
this.timeout(5000);
await torPool.signal_by_group('foo', 'DEBUG');
});

View File

@ -1,4 +1,5 @@
const nconf = require('nconf');
const { Provider } = require('nconf');
const nconf = new Provider();
const { assert } = require('chai');
const _ = require('lodash');

View File

@ -1,4 +1,7 @@
module.exports = {
WAIT_FOR_CREATE: 240000,
PAGE_LOAD_TIME: 60000
WAIT_FOR_CREATE: 600000,
PAGE_LOAD_TIME: 60000,
RETRY_DELAY: 500,
MAX_ATTEMPTS: 10,
RETRY_STRATEGY: require('requestretry').RetryStrategies.HTTPOrNetworkError
};

View File

@ -1,9 +1,9 @@
describe("TorRouter", function () {
require('./TorProcess');
require('./TorPool');
// require('./TorProcess');
// require('./TorPool');
require('./SOCKSServer');
require('./HTTPServer');
require('./DNSServer');
require('./ControlServer');
require('./RPCInterface');
// require('./DNSServer');
// require('./ControlServer');
// require('./RPCInterface');
});