Compare commits

..

No commits in common. "master" and "v3.4.2" have entirely different histories.

80 changed files with 2302 additions and 30811 deletions

View file

@ -1,9 +1,8 @@
.git
node_modules
Dockerfile
yarn.lock
npm-debug.log
docker-compose.yml
.env
.vscode
.DS_Store
docs
README.md

8
.gitignore vendored
View file

@ -1,6 +1,4 @@
node_modules
*.log
.env
.vscode
.DS_Store
yarn.lock
npm-debug.log
.env

View file

@ -1,8 +1,4 @@
node_modules
yarn.lock
npm-debug.log
.env
.vscode
.DS_Store
docs
test
Gruntfile.js
.env

View file

@ -1,197 +0,0 @@
# Changelog
## [4.0.13] - 2019-11-23
### Added
- Added a mechanism to handle dynamically allocating numerous ports at the same time without collisions per [jogli5er's](https://github.com/jogli5er) suggestion in [PR 15](https://zb.gy/3a).
### Changed
- Switched 'granax' package to '@deadcanaries/granax' as suggested in [issue 12](https://zb.gy/2z).
## [4.0.12] - 2019-11-21
### Added
- Added a entrypoint file so the node.js process is not the root process, so it responds to SIGINT calls (Ctrl+C).
## [4.0.11] - 2019-01-15
### Changed
- Updates `multi-rpc` to version 1.5.5.
## [4.0.10] - 2018-12-14
### Changed
- Updates `multi-rpc` to version 1.4.1.
## [4.0.9] - 2018-12-14
### Changed
- Updates `multi-rpc` to version 1.4.0.
## [4.0.7] - 2018-12-14
### Changed
- Updates `multi-rpc` to version 1.1.9.
## [4.0.6] - 2018-12-14
### Changed
- Updates `multi-rpc` to version 1.1.1.
## [4.0.5] - 2018-10-15
### Changed
- Prevents errors that occur when connecting to the destination in the SOCKS Proxy and HTTP Proxy from crashing the applications
## [4.0.4] - 2018-09-24
### Changed
- Replaces `jrpc2` with `multi-rpc` for providing the RPC Interface. No changes to the application or API
## [4.0.3] - 2018-09-15
### Changed
- References granax in `default_config.js` to comply with licensing requirements
## [4.0.2] - 2018-09-15
### Added
- 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)
- Updates granax to 3.1.4 which fixes a bug on MacOS
- The constructor on `ControlServer` now takes an nconf instance as the first argument and a logger as the second
## [4.0.1] - 2018-09-11
## [4.0.0] - 2018-09-09
### Added
- Instances can now added to one or more groups by setting the `Group` field in the instance definition to a single string or array
- You can now proxy through a specific instance using the username field when connecting to a proxy by setting `--proxyByName` or `-n` to "individual" or true. For example: to connect to an instance named `instance-1` via http use `http://instance-1:@localhost:9080`
- You can also connect to a specific group of instances by setting `--proxyByName` or `-n` to "group". If enabled, requests made to `://foo:@localhost:9080` would be routed to instances in the `foo` group in round-robin fashion
- The control server will accept WebSocket connections if the `--websocketControlHost` or `-w` argument is set. If the argument is used without a hostname it will default to 9078 on all interfaces
- All servers (DNS, HTTP, SOCKS and Control) all have a `listen` method which takes a port and optionally a host. It will return a Promise that will resolve when the server is listening
- Application configuration can be changed at runtime using the `getConfig` and `setConfig` RPC methods
- Application configuration can be saved and loaded from disk using the `saveConfig` and `loadConfig` RPC methods
### Changes
- All "Port" config options (e.g. socksPort) have been replaced with "Host", and can take a full host (e.g. 127.0.0.1:9050) for its value. This allows you to bind Tor Router to a specific hostname. If just a port is given it will bind to all interfaces
- All methods now return promises instead of accepting callbacks
- The `logger` argument to the constructor of all classes is now optional
- The `Config` property of instance definitions will now inherit all properties from `TorPool.default_tor_config`
- The mocha test has been split into individual files all under `test/`
- DNS shows the source/destination hostname/port in logs instead of what the query was resolved to
- `TorProcess` takes an instance definition as the second argument in its constructor
### Removes
- The `new_ips` and `new_ip_at` TorPool and `new_ip` TorProcess have been removed. Use `new_identites`, `new_identity_at` and `new_identity` instead.
- The `getDefaultTorConfig` and `setDefaultTorConfig` RPC methods have removed. Use `getConfig('torConfig')` and `setConfig('torConfig', value)` instead.
## [3.4.3] - 2018-08-10
### Added
- Adds a changelog
- Adds `queryInstanceByName` and `queryInstanceAt` RPC methods to retrieve individual instances
### Changes
- Makes changes to ensure compatibility on Windows
## [3.4.2] - 2018-08-09
### Added
- Test suites for `DNSServer`, `HTTPServer`, `SOCKSServer` and `ControlServer`
- Added `TorPool.set_config_all` method to change configuration of all active instances
### Changed
- Cleans up test suites for `TorPool` and `TorProcess`
- Removes potential security vulnerability
- `setTorConfig` rpc method will now change the configuration of active instances, `setDefaultTorConfig` and `getDefaultTorConfig` will set or get the configuration of future instances
- The default Tor Configuration will be applied to instances when the `Config` property on the instance definition is not set
## [3.4.1] - 2018-07-19
### Changed
- Fixes bug with the application not binding to port numbers specified on the command line
## [3.4.0] - 2018-05-11
### Added
- Bundles the Tor executable with the application. Tor will be downloaded during `npm install`
- Signals and Configuration changes can be sent to live Tor instances via the Tor Control Protocol. Serveral RPC and `TorPool` methods have been added.
### Changed
- By default Tor Router will use the Tor executable bundled with the application, to override use the `TOR_PATH` environment variable
- Deprecates the `TorPool.new_ips` and `TorProcess.new_ip` functions use `TorPool.new_identites`cand `TorProcess.new_identity` function respectively.
## [3.3.0] - 2018-05-10
### Added
- Adds documentation on all available RPC Methods
- Allows different load-balance methods to be defined, and changed at runtime and via RPC
- Each instance can have started with a specific configuration (torrc) by setting the `Config` property in the definition
### Changed
- If the `Name` property in the definition was not set the data directory will be deleted when the Tor Process exits
- Switches from "commander" to "nconf"/"yargs" for command line processing, switches however will remain the same
## [3.2.2] - 2018-05-08
### Changed
- Tor Router and child Tor processes are run as an unprivilieged user in the Docker container
- Fixes [Issue #4](https://github.com/znetstar/tor-router/issues/4) which affects dynamically created Tor Instances
- Network traffic is only logged when `logLevel` is set to "verbose"
## [3.2.1] - 2017-12-08
### Changed
- Fixes typo bug in Control Server
## [3.2.0] - 2017-12-07
### Changed
- Replaced socket.io with JSON-RPC 2 as the RPC protocol
- The Dockerfile includes an `ENTRYPOINT` for Tor Router
## [3.1.0] - 2017-12-07
### Added
- Adds an HTTP Proxy Server `HTTPServer` which can HTTP-Connect requests (HTTPS traffic).
- Adds a `queryInstances` RPC function as requested in [Issue #3](https://github.com/znetstar/tor-router/issues/3)
### Removed
- Removes the "docker-compose.yml" file
## [3.0.7] - 2017-11-12
### Changes
- The Dockerfile will now use Node.js version 8
## [3.0.6] - 2017-07-25
### Changes
- Fixes a bug that occures on OS X
## [3.0.5] - 2017-04-05
### Changes
- Fixes a bug with an RPC Method
## [3.0.4] - 2017-04-05
### Changes
- Changes the arguments tor-router will be launched with when run using npm start
## [3.0.3] - 2017-03-25
### Added
- Adds the Apache Licence Version 2 as the project's licence
## [3.0.2] - 2017-03-25
### Changed
- Data sent in a request before a Tor instance comes online is held in a buffer.
## [3.0.1] - 2017-03-25
### Added
- In `SOCKSServer` waits for a Tor instance in the pool to come online before making a connection.
## [3.0.0] - 2017-03-24
### Changed
- Rewrites the application as a proxy server written as a node.js app.

View file

@ -1,16 +1,33 @@
FROM node:10-jessie
FROM ubuntu:18.04
WORKDIR /app
EXPOSE 9050
EXPOSE 53
EXPOSE 9077
ENV PARENT_DATA_DIRECTORTY /var/lib/tor-router
ENV TOR_PATH /usr/bin/tor
ENV NODE_ENV production
ENV PATH $PATH:/app/bin
RUN apt-get update && apt-get install -y tor && rm -rf /var/lib/apt/lists/*
ADD https://deb.nodesource.com/setup_8.x /tmp/nodejs_install
RUN apt-get update && apt-get -y install dirmngr
RUN gpg --keyserver keys.gnupg.net --recv A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89 && gpg --export A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89 | apt-key add -
RUN echo 'deb http://deb.torproject.org/torproject.org artful main\n\
\n\
deb-src http://deb.torproject.org/torproject.org artful main'\
>> /etc/apt/sources.list.d/tor.list
RUN bash /tmp/nodejs_install
RUN apt-get install -y nodejs tor git
RUN useradd -ms /bin/bash tor_router
@ -20,16 +37,12 @@ USER tor_router
ADD package.json /app/package.json
ADD package-lock.json /app/package-lock.json
RUN npm ci
RUN npm install
ADD . /app
ENV HOME /home/tor_router
EXPOSE 9050 9053 9077
ENTRYPOINT [ "/bin/bash", "/app/docker-entrypoint.sh" ]
ENTRYPOINT [ "tor-router" ]
CMD [ "-s", "-d", "-j", "1" ]

View file

@ -1,20 +0,0 @@
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
jsdoc : {
dist : {
src: ['src/*.js', 'test/*.js', 'README.md'],
options: {
destination : 'docs',
template : "node_modules/docdash"
}
}
}
});
grunt.loadNpmTasks('grunt-jsdoc');
grunt.registerTask('default', []);
grunt.registerTask('docs', ['jsdoc']);
};

View file

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2018 Zachary Boyd
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

154
README.md
View file

@ -1,12 +1,8 @@
# Tor Router
[![NPM](https://nodei.co/npm/tor-router.png)](https://nodei.co/npm/tor-router/)
*Tor Router* is a simple SOCKS5 proxy server for distributing traffic across multiple instances of Tor. At startup Tor Router will run an arbitrary number of instances Tor an each request will be sent to a different instance in round-robin fashion. This can be used to increase anonymity, because each request will be sent on a different circuit and will most likely use a different exit-node, and also to increase performance since outbound traffic is now split across several instances of Tor.
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fznetstar%2Ftor-router.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fznetstar%2Ftor-router?ref=badge_shield)
*Tor Router* is a SOCKS5, DNS and HTTP proxy server for distributing traffic across multiple instances of Tor. At startup Tor Router will run an arbitrary number of instances Tor and each request will be sent to a different instance in round-robin fashion. This can be used to increase anonymity, because each request will be sent on a different circuit and will most likely use a different exit-node, and also to increase performance since outbound traffic is now split across several instances of Tor.
A list of changes can be [found here](https://github.com/znetstar/tor-router/blob/master/CHANGELOG.md).
Tor Router also includes a DNS proxy server and a HTTP proxy as well, which like the SOCKS proxy will distribute traffic across multiple instances of Tor in round-robin fashion. The HTTP proxy server can be used to access Tor via an HTTP Proxy.
## Building and Running
@ -15,7 +11,7 @@ The only installation requirement is node.js. Tor is bundled with the applicatio
To install run: `npm install`
To start run: `bin/tor-router`
To install globally run: `npm install -g tor-router`
To install globally run: `npm install -g`
Alternatively docker can be used. The build will retrieve the latest version of Tor from the offical Tor Project repository.
@ -26,33 +22,133 @@ To start run: `docker run --rm -it -p 9050:9050 znetstar/tor-router`
The following command line switches and their environment variable equivalents are available for use:
If just a port number is passed in place of a host, it will bind to all interfaces.
|Command line switch|Environment Variable|Description|
|---------------------------|--------------------|-----------|
|-f, --config | |Path to a JSON configuration file to use|
|-c, --controlHost |CONTROL_HOST |Host the control server will bind to and listen for TCP traffic (see below)|
|-w, --websocketControlHost |WEBSOCKET_CONTROL_HOST |Host the control server will bind to and listen for WebSocket traffic|
|-j, --instances |INSTANCES |Number of Tor instances to spawn|
|-s, --socksHost |SOCKS_HOST |Host the SOCKS proxy will bind to|
|-d, --dnsHost |DNS_HOST |Host the DNS proxy will bind to|
|-h, --httpHost |HTTP_HOST |Host the HTTP proxy will bind to|
|-l, --logLevel |LOG_LEVEL |Log level (defaults to "info") set to "null" to disable logging. To see a log of all network traffic set logLevel to "verbose"|
|-p, --parentDataDirectory |PARENT_DATA_DIRECTORY|Parent directory that will contain the data directories for the instances|
|-b, --loadBalanceMethod |LOAD_BALANCE_METHOD |Method that will be used to sort the instances between each request. Currently supports "round_robin" and "weighted". |
|-t, --torPath |TOR_PATH |Provide the path for the Tor executable that will be used|
|-n, --proxyByName |PROXY_BY_NAME |Controls how authenticated requests will be handled. Can be set to "individual", "group" or false to disable|
|-------------------|--------------------|-----------|
|-f, --config | |Path to a JSON configuration file to use|
|-c, --controlPort |CONTROL_PORT |Port the control server will bind to (see below)|
|-j, --instances |INSTANCES |Number of Tor instances to spawn|
|-s, --socksPort |SOCKS_PORT |Port the SOCKS proxy will bind to|
|-d, --dnsPort |DNS_PORT |Port the DNS proxy will bind to|
|-h, --httpPort |HTTP_PORT |Port the HTTP proxy will bind to|
|-l, --logLevel |LOG_LEVEL |Log level (defaults to "info") set to "null" to disable logging. To see a log of all network traffic set logLevel to "verbose"|
|-p, --parentDataDirectory|PARENT_DATA_DIRECTORY |Parent directory that will contain the data directories for the instances|
|-b, --loadBalanceMethod|LOAD_BALANCE_METHOD |Method that will be used to sort the instances between each request. Currently supports "round_robin" and "weighted".|
|-t, --torPath|TOR_PATH|Provide the path for the Tor executable that will be used|
A full list of all available configuration options and their defaults can be found in [default_config.js](https://github.com/znetstar/tor-router/blob/master/src/default_config.js)
For example: `tor-router -j 3 -s 9050` would start the proxy with 3 tor instances and listen for SOCKS connections on 9050.
For example: `tor-router -j 3 -s 127.0.0.1:9050` would start the proxy with 3 tor instances and listen for SOCKS connections on localhost:9050.
## Configuration
## Documentation
Using the `--config` or `-f` command line switch you can set the path to a JSON file which can be used to load configuration on startup
For detailed examples and insturctions on using Tor Router [see the wiki](https://github.com/znetstar/tor-router/wiki).
The same variable names from the command line switches are used to name the keys in the JSON file.
Documentation is available in `docs/`. An online version of the documentation is also [available here](https://tor-router.docs.zacharyboyd.nyc/).
Example:
## Testing
```
{
"controlPort": 9077,
"logLevel": "debug"
}
```
Tests are written in mocha and can be found under `test/` and can be run with `npm test`
Using the configuration file you can set a default configuration for all Tor instances
```
{
"torConfig": {
"MaxCircuitDirtiness": "10"
}
}
```
You can also specify a configuration for individual instances by setting the "instances" field to an array instead of an integer.
Instances can optionally be assigned name and a weight. If the `loadBalanceMethod` config variable is set to "weighted" the weight field will determine how frequently the instance is used. If the instance is assigned a name the data directory will be preserved when the process is killed saving time when Tor is restarted.
```
{
"loadBalanceMethod": "weighted",
"instances": [
{
"Name": "instance-1"
"Weight": 10,
"Config": {
}
},
{
"Name": "instance-2",
"Weight": 5,
"Config": {
}
}
]
}
```
## Control Server
A JSON-RPC 2 TCP Server will listen on port 9077 by default. Using the rpc server the client can add/remove Tor instances and get a new identity (which includes a new ip address) while Tor Router is running.
Example (in node):
```
const net = require('net');
let client = net.createConnection({ port: 9077 }, () => {
let rpcRequest = {
"method": "createInstances",
"params": [3],
"jsonrpc":"2.0",
"id": 1
};
client.write(JSON.stringify(rpcRequest));
});
client.on('data', (chunk) => {
let rawResponse = chunk.toString('utf8');
let rpcResponse = JSON.parse(rawResponse);
console.log(rpcResponse)
if (rpcResponse.id === 1) {
console.log('Three instances have been created!')
}
})
```
A full list of available RPC Methods can be [found here](https://github.com/znetstar/tor-router/blob/master/docs/rpc-methods.md)
## Tor Control Protocol
You can retrieve or set the configuration of instances while they're running via the Tor Control Protocol.
The example below will change the "MaxCircuitDirtiness" value for the first instance in the pool
Example:
```
let rpcRequest = {
"method": "setInstanceConfigAt",
"params": [0, "MaxCircuitDirtiness", "20"],
"jsonrpc":"2.0",
"id": 1
};
client.write(JSON.stringify(rpcRequest));
```
You can also send signals directly to instances or to all instances in the pool via the control protocol. A list of all signals can be [found here](https://gitweb.torproject.org/torspec.git/tree/control-spec.txt)
The example below will set the log level of all instances to "debug".
Example:
```
let rpcRequest = {
"method": "signalAllInstances",
"params": ["DEBUG"],
"jsonrpc":"2.0",
"id": 1
};
client.write(JSON.stringify(rpcRequest));
```
## Test
Tests are written in mocha, just run `npm test`

View file

@ -1,5 +1,168 @@
#!/usr/bin/env node
let { nconf, logger, main } = require('../src/launch');
var nconf = require('nconf');
var yargs = require('yargs');
var fs = require('fs');
var TorRouter = require('../');
var SOCKSServer = TorRouter.SOCKSServer;
var DNSServer = TorRouter.DNSServer;
var TorPool = TorRouter.TorPool;
var ControlServer = TorRouter.ControlServer;
var winston = require('winston');
var async = require('async');
main(nconf, logger);
process.title = 'tor-router';
let package_json = JSON.parse(fs.readFileSync(`${__dirname}/../package.json`, 'utf8'));
let argv_config = require('yargs')
.version(package_json.version)
.usage('Usage: tor-router [arguments]')
.options({
f: {
alias: 'config',
describe: 'Path to a config file to use',
demand: false
},
c: {
alias: 'controlPort',
describe: 'Port the control server will bind to [default: 9077]',
demand: false
// ,default: 9077
},
j: {
alias: 'instances',
describe: 'Number of instances using the default config',
demand: false
// ,default: 1
},
s: {
alias: 'socksPort',
describe: 'Port the SOCKS5 Proxy server will bind to',
demand: false,
// ,default: 9050
},
d: {
alias: 'dnsPort',
describe: 'Port the DNS Proxy server will bind to',
demand: false
},
h: {
alias: 'httpPort',
describe: 'Port the HTTP Proxy server will bind to',
demand: false
},
l: {
alias: 'logLevel',
describe: 'Controls the verbosity of console log output. Default level is "info". Set to "verbose" to see all network traffic logged or "null" to disable logging completely [default: info]',
demand: false
// ,default: "info"
},
p: {
alias: 'parentDataDirectory',
describe: 'Parent directory that will contain the data directories for the instances',
demand: false
},
b: {
alias: "loadBalanceMethod",
describe: 'Method that will be used to sort the instances between each request. Currently supports "round_robin" and "weighted". [default: round_robin]',
demand: false
},
t: {
alias: "torPath",
describe: "Provide the path for the Tor executable that will be used",
demand: false
}
});
nconf = require(`${__dirname}/../src/nconf_load_env.js`)(nconf);
nconf
.argv(argv_config);
let nconf_config = nconf.get('config');
if (nconf_config) {
nconf.file(nconf_config);
}
nconf.defaults(require(`${__dirname}/../src/default_config.js`))
let logLevel = nconf.get('logLevel');
var logger = null;
logger = winston.createLogger({
level: logLevel,
format: winston.format.simple(),
transports: (logLevel == 'null') ? [ new (require('winston-null-transport'))() ] : [ new (winston.transports.Console)({ level: logLevel }) ]
});
let instances = nconf.get('instances');
let log_level = logLevel;
let socks_port = nconf.get('socksPort');
let dns_port = nconf.get('dnsPort');
let http_port = nconf.get('httpPort');
let control_port = nconf.get('controlPort');
if (typeof(control_port) === 'boolean') {
control_port = 9077;
nconf.set('controlPort', 9077);
}
let control = new ControlServer(logger, nconf);
process.on('SIGHUP', () => {
control.torPool.new_ips();
});
if (socks_port) {
if (typeof(socks_port) === 'boolean') {
socks_port = 9050;
nconf.set('socksPort', socks_port);
}
let socks = control.createSOCKSServer(socks_port);
}
if (http_port) {
if (typeof(http_port) === 'boolean') {
http_port = 9080;
nconf.set('httpPort', http_port);
}
let http = control.createHTTPServer(http_port);
}
if (dns_port) {
if (typeof(dns_port) === 'boolean') {
dns_port = 9053;
nconf.set('dnsPort', dns_port);
}
let dns = control.createDNSServer(dns_port);
}
if (instances) {
logger.info(`[tor]: starting ${Array.isArray(instances) ? instances.length : instances} tor instances...`)
control.torPool.create(instances, (err) => {
logger.info('[tor]: tor started');
});
}
control.listen(control_port, () => {
logger.info(`[control]: Control Server listening on ${control_port}`);
})
function cleanUp(error) {
async.each(control.torPool.instances, (instance, next) => {
instance.exit(next);
}, (exitError) => {
if (error) {
console.error(error.stack);
}
if (exitError){
console.error(exitError);
}
process.exit(Number(Boolean(error || exitError)));
});
}
process.on('exit', cleanUp);
process.on('SIGINT', cleanUp);
process.on('uncaughtException', cleanUp);

9
bitbucket-pipelines.yml Normal file
View file

@ -0,0 +1,9 @@
image: znetstar/tor-router:3.4.1
pipelines:
default:
- step:
name: Build and test
script:
- npm install
- npm test

View file

@ -1,3 +0,0 @@
#!/bin/bash
/app/bin/tor-router $@

View file

@ -1 +0,0 @@
tor-router.gh.zb.gy

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

130
docs/rpc-methods.md Normal file
View file

@ -0,0 +1,130 @@
## RPC Functions
The following functions are available via the RPC
# queryInstances()
Returns an array containing information on the instances currently running under the router.
# createInstances(Array or Integrer)
If passed an integrer, creates thats many Tor instances. An array can also be passed describing the names, weights and configurations of prospective instances. :
```
var rpcRequest = {
"method": "createInstances",
"params": [
{
"Config": {
},
"Name": "instance-1",
"Weight": 10
},
...
],
"jsonrpc":"2.0",
"id": 1
};
```
Will wait until the Tor Instance has fully connected to the network before returning
# addInstances(Array)
Serves the same purpose as "createInstances" but only takes an Array
# removeInstances(Integrer)
Removes a number of instances
# removeInstanceAt(Integrer)
Remove a specific instance from the pool by its index
# removeInstanceByName(String)
Remove a specific instance from the pool by its name
# newIdentites()
Get new identites for all instances
# newIdentityAt(Integrer)
Get a new identity for a specific instance by its index
# newIdentityByName(String)
Get a new identity for a specific instance by its name
# nextInstance()
Cycle to the next instance using the load balancing method
# closeInstances()
Shutdown all Tor instances
# setTorConfig(Object)
Applies the configuration to all active instances
# getDefaultTorConfig()
Retrieve the default Tor Config for all future instances
# setDefaultTorConfig(Object)
Set the default Tor Config for all future instances
# getLoadBalanceMethod()
Get the current load balance method
# setLoadBalanceMethod(String)
Set the current load balance method
# getInstanceConfigAt(Integrer: index, String: keyword)
Retrieves the current value of an option set in the configuration by the index of the instance using the control protocol.
Example:
The following would retrieve the path to the data directory of the instance
```
var rpcRequest = {
"method": "getInstanceConfigAt",
"params": [0, "DataDirectory"],
"jsonrpc":"2.0",
"id": 1
};
```
# getInstanceConfigByName(String: name, String: keyword)
Works the same way as `getInstanceConfigAt` except takes an instance name instead of an index
# setInstanceConfigAt(Integrer: index, String: keyword, String: value)
Sets the value in the configuration of an instance using the control protocol. Changes will be applied immediately.
# setInstanceConfigByName(Integrer: index, String: keyword, String: value)
Works the same way as `setInstanceConfigAt` except takes an instance name instead of an index
# signalAllInstances(String)
Sends a signal using the control protocol to all instances
A list of all signals can be [found here](https://gitweb.torproject.org/torspec.git/tree/control-spec.txt)
# signalInstanceAt(Integrer: index, String: signal)
Sends a signal using the control protocol to an instance identified by its index
# signalInstanceByName(String: name, String: signal)
Sends a signal using the control protocol to an instance identified by its name

File diff suppressed because one or more lines are too long

View file

@ -1,11 +0,0 @@
function hideAllButCurrent(){
//by default all submenut items are hidden
$("nav > ul > li > ul li").hide();
//only current page (if it exists) should be opened
var file = window.location.pathname.split("/").pop();
$("nav > ul > li > a[href^='"+file+"']").parent().find("> ul li").show();
}
$( document ).ready(function() {
hideAllButCurrent();
});

File diff suppressed because one or more lines are too long

View file

@ -1,25 +0,0 @@
/*global document */
(function() {
var source = document.getElementsByClassName('prettyprint source linenums');
var i = 0;
var lineNumber = 0;
var lineId;
var lines;
var totalLines;
var anchorHash;
if (source && source[0]) {
anchorHash = document.location.hash.substring(1);
lines = source[0].getElementsByTagName('li');
totalLines = lines.length;
for (; i < totalLines; i++) {
lineNumber++;
lineId = 'line' + lineNumber;
lines[i].id = lineId;
if (lineId === anchorHash) {
lines[i].className += ' selected';
}
}
}
})();

View file

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,2 +0,0 @@
PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);

View file

@ -1,28 +0,0 @@
var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();

View file

@ -1,42 +0,0 @@
$( document ).ready(function() {
jQuery.expr[':'].Contains = function(a,i,m){
return (a.textContent || a.innerText || "").toUpperCase().indexOf(m[3].toUpperCase())>=0;
};
//on search
$("#nav-search").on("keyup", function(event) {
var search = $(this).val();
if (!search) {
//no search, show all results
$("nav > ul > li").show();
if(typeof hideAllButCurrent === "function"){
//let's do what ever collapse wants to do
hideAllButCurrent();
}
else{
//menu by default should be opened
$("nav > ul > li > ul li").show();
}
}
else{
//we are searching
//show all parents
$("nav > ul > li").show();
//hide all results
$("nav > ul > li > ul li").hide();
//show results matching filter
$("nav > ul > li > ul").find("a:Contains("+search+")").parent().show();
//hide parents without children
$("nav > ul > li").each(function(){
if($(this).find("a:Contains("+search+")").length == 0 && $(this).children("ul").length === 0){
//has no child at all and does not contain text
$(this).hide();
}
else if($(this).find("a:Contains("+search+")").length == 0 && $(this).find("ul").children(':visible').length == 0){
//has no visible child and does not contain text
$(this).hide();
}
});
}
});
});

View file

@ -1,654 +0,0 @@
@import url(https://fonts.googleapis.com/css?family=Montserrat:400,700);
* {
box-sizing: border-box
}
html, body {
height: 100%;
width: 100%;
}
body {
color: #4d4e53;
background-color: white;
margin: 0 auto;
padding: 0 20px;
font-family: 'Helvetica Neue', Helvetica, sans-serif;
font-size: 16px;
line-height: 160%;
}
img {
max-width: 100%;
}
a,
a:active {
color: #606;
text-decoration: none;
}
a:hover {
text-decoration: none;
}
article a {
border-bottom: 1px solid #ddd;
}
article a:hover, article a:active {
border-bottom-color: #222;
}
p, ul, ol, blockquote {
margin-bottom: 1em;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Montserrat', sans-serif;
}
h1, h2, h3, h4, h5, h6 {
color: #000;
font-weight: 400;
margin: 0;
}
h1 {
font-weight: 300;
font-size: 48px;
margin: 1em 0 .5em;
}
h1.page-title {
font-size: 48px;
margin: 1em 30px;
line-height: 100%;
}
h2 {
font-size: 24px;
margin: 1.5em 0 .3em;
}
h3 {
font-size: 24px;
margin: 1.2em 0 .3em;
}
h4 {
font-size: 18px;
margin: 1em 0 .2em;
color: #4d4e53;
}
h4.name {
color: #fff;
background: #6d426d;
box-shadow: 0 .25em .5em #d3d3d3;
border-top: 1px solid #d3d3d3;
border-bottom: 1px solid #d3d3d3;
margin: 1.5em 0 0.5em;
padding: .75em 0 .75em 10px;
}
h4.name a {
color: #fc83ff;
}
h4.name a:hover {
border-bottom-color: #fc83ff;
}
h5, .container-overview .subsection-title {
font-size: 120%;
letter-spacing: -0.01em;
margin: 8px 0 3px 0;
}
h6 {
font-size: 100%;
letter-spacing: -0.01em;
margin: 6px 0 3px 0;
font-style: italic;
}
tt, code, kbd, samp {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
background: #f4f4f4;
padding: 1px 5px;
}
.class-description {
font-size: 130%;
line-height: 140%;
margin-bottom: 1em;
margin-top: 1em;
}
.class-description:empty {
margin: 0
}
#main {
float: right;
min-width: 360px;
width: calc(100% - 240px);
}
header {
display: block
}
section {
display: block;
background-color: #fff;
padding: 0 0 0 30px;
}
.variation {
display: none
}
.signature-attributes {
font-size: 60%;
color: #eee;
font-style: italic;
font-weight: lighter;
}
nav {
float: left;
display: block;
width: 250px;
background: #fff;
overflow: auto;
position: fixed;
height: 100%;
}
nav #nav-search{
width: 210px;
height: 30px;
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
margin-right: 20px;
margin-top: 20px;
}
nav h3 {
margin-top: 12px;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 700;
line-height: 24px;
margin: 15px 0 10px;
padding: 0;
color: #000;
}
nav ul {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif;
font-size: 100%;
line-height: 17px;
padding: 0;
margin: 0;
list-style-type: none;
}
nav ul a,
nav ul a:active {
font-family: 'Montserrat', sans-serif;
line-height: 18px;
padding: 0;
display: block;
font-size: 12px;
}
nav a:hover,
nav a:active {
color: #606;
}
nav > ul {
padding: 0 10px;
}
nav > ul > li > a {
color: #606;
margin-top: 10px;
}
nav ul ul a {
color: hsl(207, 1%, 60%);
border-left: 1px solid hsl(207, 10%, 86%);
}
nav ul ul a,
nav ul ul a:active {
padding-left: 20px
}
nav h2 {
font-size: 12px;
margin: 0;
padding: 0;
}
nav > h2 > a {
display: block;
margin: 10px 0 -10px;
color: #606 !important;
}
footer {
color: hsl(0, 0%, 28%);
margin-left: 250px;
display: block;
padding: 15px;
font-style: italic;
font-size: 90%;
}
.ancestors {
color: #999
}
.ancestors a {
color: #999 !important;
}
.clear {
clear: both
}
.important {
font-weight: bold;
color: #950B02;
}
.yes-def {
text-indent: -1000px
}
.type-signature {
color: #CA79CA
}
.type-signature:last-child {
color: #eee;
}
.name, .signature {
font-family: Consolas, Monaco, 'Andale Mono', monospace
}
.signature {
color: #fc83ff;
}
.details {
margin-top: 6px;
border-left: 2px solid #DDD;
line-height: 20px;
font-size: 14px;
}
.details dt {
width: auto;
float: left;
padding-left: 10px;
}
.details dd {
margin-left: 70px;
margin-top: 6px;
margin-bottom: 6px;
}
.details ul {
margin: 0
}
.details ul {
list-style-type: none
}
.details pre.prettyprint {
margin: 0
}
.details .object-value {
padding-top: 0
}
.description {
margin-bottom: 1em;
margin-top: 1em;
}
.code-caption {
font-style: italic;
font-size: 107%;
margin: 0;
}
.prettyprint {
font-size: 14px;
overflow: auto;
}
.prettyprint.source {
width: inherit;
line-height: 18px;
display: block;
background-color: #0d152a;
color: #aeaeae;
}
.prettyprint code {
line-height: 18px;
display: block;
background-color: #0d152a;
color: #4D4E53;
}
.prettyprint > code {
padding: 15px;
}
.prettyprint .linenums code {
padding: 0 15px
}
.prettyprint .linenums li:first-of-type code {
padding-top: 15px
}
.prettyprint code span.line {
display: inline-block
}
.prettyprint.linenums {
padding-left: 70px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.prettyprint.linenums ol {
padding-left: 0
}
.prettyprint.linenums li {
border-left: 3px #34446B solid;
}
.prettyprint.linenums li.selected, .prettyprint.linenums li.selected * {
background-color: #34446B;
}
.prettyprint.linenums li * {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
table {
border-spacing: 0;
border: 1px solid #ddd;
border-collapse: collapse;
border-radius: 3px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
width: 100%;
font-size: 14px;
margin: 1em 0;
}
td, th {
margin: 0px;
text-align: left;
vertical-align: top;
padding: 10px;
display: table-cell;
}
thead tr, thead tr {
background-color: #fff;
font-weight: bold;
border-bottom: 1px solid #ddd;
}
.params .type {
white-space: nowrap;
}
.params code {
white-space: pre;
}
.params td, .params .name, .props .name, .name code {
color: #4D4E53;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
font-size: 100%;
}
.params td {
border-top: 1px solid #eee
}
.params td.description > p:first-child, .props td.description > p:first-child {
margin-top: 0;
padding-top: 0;
}
.params td.description > p:last-child, .props td.description > p:last-child {
margin-bottom: 0;
padding-bottom: 0;
}
span.param-type, .params td .param-type, .param-type dd {
color: #606;
font-family: Consolas, Monaco, 'Andale Mono', monospace
}
.param-type dt, .param-type dd {
display: inline-block
}
.param-type {
margin: 14px 0;
}
.disabled {
color: #454545
}
/* navicon button */
.navicon-button {
display: none;
position: relative;
padding: 2.0625rem 1.5rem;
transition: 0.25s;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
opacity: .8;
}
.navicon-button .navicon:before, .navicon-button .navicon:after {
transition: 0.25s;
}
.navicon-button:hover {
transition: 0.5s;
opacity: 1;
}
.navicon-button:hover .navicon:before, .navicon-button:hover .navicon:after {
transition: 0.25s;
}
.navicon-button:hover .navicon:before {
top: .825rem;
}
.navicon-button:hover .navicon:after {
top: -.825rem;
}
/* navicon */
.navicon {
position: relative;
width: 2.5em;
height: .3125rem;
background: #000;
transition: 0.3s;
border-radius: 2.5rem;
}
.navicon:before, .navicon:after {
display: block;
content: "";
height: .3125rem;
width: 2.5rem;
background: #000;
position: absolute;
z-index: -1;
transition: 0.3s 0.25s;
border-radius: 1rem;
}
.navicon:before {
top: .625rem;
}
.navicon:after {
top: -.625rem;
}
/* open */
.nav-trigger:checked + label:not(.steps) .navicon:before,
.nav-trigger:checked + label:not(.steps) .navicon:after {
top: 0 !important;
}
.nav-trigger:checked + label .navicon:before,
.nav-trigger:checked + label .navicon:after {
transition: 0.5s;
}
/* Minus */
.nav-trigger:checked + label {
-webkit-transform: scale(0.75);
transform: scale(0.75);
}
/* × and + */
.nav-trigger:checked + label.plus .navicon,
.nav-trigger:checked + label.x .navicon {
background: transparent;
}
.nav-trigger:checked + label.plus .navicon:before,
.nav-trigger:checked + label.x .navicon:before {
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
background: #FFF;
}
.nav-trigger:checked + label.plus .navicon:after,
.nav-trigger:checked + label.x .navicon:after {
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
background: #FFF;
}
.nav-trigger:checked + label.plus {
-webkit-transform: scale(0.75) rotate(45deg);
transform: scale(0.75) rotate(45deg);
}
.nav-trigger:checked ~ nav {
left: 0 !important;
}
.nav-trigger:checked ~ .overlay {
display: block;
}
.nav-trigger {
position: fixed;
top: 0;
clip: rect(0, 0, 0, 0);
}
.overlay {
display: none;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
background: hsla(0, 0%, 0%, 0.5);
z-index: 1;
}
@media only screen and (min-width: 320px) and (max-width: 680px) {
body {
overflow-x: hidden;
}
nav {
background: #FFF;
width: 250px;
height: 100%;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: -250px;
z-index: 3;
padding: 0 10px;
transition: left 0.2s;
}
.navicon-button {
display: inline-block;
position: fixed;
top: 1.5em;
right: 0;
z-index: 2;
}
#main {
width: 100%;
min-width: 360px;
}
#main h1.page-title {
margin: 1em 0;
}
#main section {
padding: 0;
}
footer {
margin-left: 0;
}
}
/** Add a '#' to static members */
[data-type="member"] a::before {
content: '#';
display: inline-block;
margin-left: -14px;
margin-right: 5px;
}
#disqus_thread{
margin-left: 30px;
}

View file

@ -1,79 +0,0 @@
.pln {
color: #ddd;
}
/* string content */
.str {
color: #61ce3c;
}
/* a keyword */
.kwd {
color: #fbde2d;
}
/* a comment */
.com {
color: #aeaeae;
}
/* a type name */
.typ {
color: #8da6ce;
}
/* a literal value */
.lit {
color: #fbde2d;
}
/* punctuation */
.pun {
color: #ddd;
}
/* lisp open bracket */
.opn {
color: #000000;
}
/* lisp close bracket */
.clo {
color: #000000;
}
/* a markup tag name */
.tag {
color: #8da6ce;
}
/* a markup attribute name */
.atn {
color: #fbde2d;
}
/* a markup attribute value */
.atv {
color: #ddd;
}
/* a declaration */
.dec {
color: #EF5050;
}
/* a variable name */
.var {
color: #c82829;
}
/* a function name */
.fun {
color: #4271ae;
}
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0;
}

File diff suppressed because one or more lines are too long

1732
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,53 +1,41 @@
{
"name": "tor-router",
"version": "4.0.13",
"version": "3.4.2",
"main": "src/index.js",
"repository": "git@github.com:znetstar/tor-router.git",
"author": "Zachary Boyd <zachary@zacharyboyd.nyc>",
"license": "Apache-2.0",
"description": "A SOCKS, HTTP and DNS proxy for distributing traffic across multiple instances of Tor",
"description": "A SOCKS proxy for distributing traffic across multiple instances of Tor",
"bin": {
"tor-router": "bin/tor-router"
},
"keywords": [
"tor"
],
"scripts": {
"start": "bin/tor-router -s -d -j 1",
"test": "mocha -u tdd --exit test/index.js",
"debug": "node --inspect-brk bin/tor-router",
"docs": "grunt docs"
"test": "mocha test/test.js",
"debug": "node --inspect-brk bin/tor-router"
},
"devDependencies": {
"chai": "^4.1.2",
"docdash": "^1.0.0",
"grunt": "^1.0.3",
"grunt-jsdoc": "^2.3.0",
"jsdoc": "^3.5.5",
"mocha": "^5.2.0",
"request": "^2.87.0",
"request-promise": "^4.2.2",
"requestretry": "^2.0.2",
"socks-proxy-agent": "^4.0.1"
"request": "^2.79.0"
},
"dependencies": {
"@deadcanaries/granax": "^3.2.4",
"arrify": "^2.0.1",
"async-mutex": "^0.1.4",
"bluebird": "^3.5.2",
"async": "^2.1.4",
"del": "^3.0.0",
"eventemitter3": "^3.1.0",
"eventemitter2": "^3.0.0",
"get-port": "^2.1.0",
"granax": "^3.1.3",
"jrpc2": "^1.0.5",
"js-weighted-list": "^0.1.1",
"lodash": "^4.17.4",
"multi-rpc": "^1.5.5",
"nanoid": "^1.2.3",
"native-dns": "git+https://github.com/znetstar/node-dns.git#336f1d3027b2a3da719b5cd65380219267901aeb",
"nanoid": "^1.0.2",
"native-dns": "git+https://github.com/znetstar/node-dns.git",
"nconf": "^0.10.0",
"shelljs": "^0.8.2",
"socksv5": "git+https://github.com/znetstar/socksv5.git#1480422215cf1464fa06f5aec4a3e7f2117e3403",
"socks-proxy-agent": "^3.0.1",
"socksv5": "git+https://github.com/lee-elenbaas/socksv5.git",
"temp": "^0.8.3",
"winston": "^3.0.0-rc5",
"winston-null-transport": "git+https://github.com/NCIOCPL/winston-null-transport.git#1.0.0",
"yargs": "^11.0.0"
}
}

View file

@ -1,440 +1,271 @@
const { Server, WebSocketTransport, TCPTransport, JSONSerializer } = require("multi-rpc");
const Promise = require('bluebird');
const SOCKSServer = require('./SOCKSServer');
const HTTPServer = require('./HTTPServer');
const DNSServer = require('./DNSServer');
const TorPool = require('./TorPool');
const default_ports = require('./default_ports');
const SOCKSServer = require('./SOCKSServer');
const DNSServer = require('./DNSServer');
const HTTPServer = require('./HTTPServer');
const rpc = require('jrpc2');
const async = require('async');
/**
* @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 {
/**
*
* @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}
*/
constructor(logger, nconf) {
this.torPool = new TorPool(nconf.get('torPath'), null, nconf.get('parentDataDirectory'), nconf.get('loadBalanceMethod'), nconf.get('granaxOptions'),logger);
this.logger = logger;
this.nconf = nconf;
/**
* RPC Server instance
*
* @type {Server}
*/
let server = this.server = new Server();
let server = this.server = new rpc.Server();
server.expose('createTorPool', this.createTorPool.bind(this));
server.expose('createSOCKSServer', this.createSOCKSServer.bind(this));
server.expose('createDNSServer', this.createDNSServer.bind(this));
server.expose('createHTTPServer', this.createHTTPServer.bind(this));
this.serializer = new JSONSerializer();
// queryInstanceAt, queryInstanceByName
this.server.methods.createTorPool = this.createTorPool.bind(this);
this.server.methods.createSocksServer = this.createSOCKSServer.bind(this);
this.server.methods.createDNSServer = this.createDNSServer.bind(this);
this.server.methods.createHTTPServer = this.createHTTPServer.bind(this);
/**
* Returns a list of all instances currently in the pool.
*/
this.server.methods.queryInstances = (() => {
return this.tor_pool.instances.map(ControlServer.instance_info);
}).bind(this);
server.expose('queryInstances', (function () {
return new Promise((resolve, reject) => {
if (!this.torPool)
return reject({ message: 'No pool created' });
/**
* Returns information on an instance identified by the {@link TorProcess#instance_name} field.
*/
this.server.methods.queryInstanceByName = ((instance_name) => {
let instance = this.tor_pool.instance_by_name(instance_name);
resolve(this.torPool.instances.map((i) => ( { name: i.instance_name, dns_port: i.dns_port, socks_port: i.socks_port, process_id: i.process.pid, config: i.definition.Config, weight: i.definition.weight } )) );
});
}).bind(this));
if (!instance)
throw new Error(`Instance "${instance_name}"" does not exist`);
server.expose('createInstances', (function (instances) {
return new Promise((resolve, reject) => {
this.torPool.create(instances, (error, instances) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this) );
return ControlServer.instance_info(instance);
}).bind(this);
server.expose('addInstances', (function (instances) {
return new Promise((resolve, reject) => {
this.torPool.add(instances, (error, instances) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this) );
/**
* Returns information on an instance identified by its index in the pool.
*/
this.server.methods.queryInstanceAt = ((index) => {
if (!this.tor_pool)
throw new Error('No pool created');
server.expose('removeInstances', (function (instances) {
return new Promise((resolve, reject) => {
this.torPool.remove(instances, (error) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this) );
let instance = this.tor_pool.instance_at(index);
server.expose('removeInstanceAt', (function (instance_index) {
return new Promise((resolve, reject) => {
this.torPool.remove_at(instance_index, (error) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this) );
if (!instance)
throw new Error(`Instance at "${i}"" does not exist`);
server.expose('removeInstanceByName', (function (instance_name) {
return new Promise((resolve, reject) => {
this.torPool.remove_by_name(instance_name, (error) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this) );
return ControlServer.instance_info(this.tor_pool.instance_at(index));
}).bind(this);
server.expose('newIdentites', (function() {
return new Promise((resolve, reject) => {
this.torPool.new_identites((error) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this));
/**
* Returns a list of the names of all of the instances in the pool.
*/
this.server.methods.queryInstanceNames = (() => this.tor_pool.instance_names).bind(this);
/**
* Returns a list of the names of all of the current groups.
*/
this.server.methods.queryGroupNames = (() => Array.from(this.tor_pool.group_names)).bind(this);
server.expose('newIdentityAt', (function(index) {
return new Promise((resolve, reject) => {
this.torPool.new_identity_at(index, (error) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this));
/**
* Returns a list of the instances that exist in a given group.
*/
this.server.methods.queryInstancesByGroup = ((group) => this.tor_pool.instances_by_group(group).map(ControlServer.instance_info)).bind(this);
server.expose('newIdentityByName', (function(name) {
return new Promise((resolve, reject) => {
this.torPool.new_identity_by_name(name, (error) => {
if (error) reject(error);
else resolve();
});
});
}).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.
*/
this.server.methods.createInstances = (async (instances_to_create) => {
let instances = await this.tor_pool.create(instances_to_create);
/* Begin Deprecated */
return instances.map(ControlServer.instance_info);
}).bind(this);
server.expose('newIps', (function() {
return new Promise((resolve, reject) => {
this.torPool.new_ips((error) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this));
/**
* Creates instances from an array of instance definitions or a single instance definition.
*/
this.server.methods.addInstances = (async (defs) => {
let instances = await this.tor_pool.add(defs);
server.expose('newIpAt', (function(index) {
return new Promise((resolve, reject) => {
this.torPool.new_ip_at(index, (error) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this));
return instances.map(ControlServer.instance_info);
}).bind(this);
/* End Deprecated */
/**
* Removes a number of instances from the pool.
*/
this.server.methods.removeInstances = this.tor_pool.remove.bind(this.tor_pool);
server.expose('nextInstance', (function () {
this.torPool.next();
return Promise.resolve();
}).bind(this) );
/**
* Remove an instance at the index provided from the pool.
*/
this.server.methods.removeInstanceAt = this.tor_pool.remove_at.bind(this.tor_pool);
server.expose('closeInstances', (function () {
this.torPool.exit();
return Promise.resolve();
}).bind(this) );
/**
* Remove an instance from the pool by the {@link TorProcess#instance_name} field.
*/
this.server.methods.removeInstanceByName = this.tor_pool.remove_by_name.bind(this.tor_pool);
server.expose('getDefaultTorConfig', (function () {
return Promise.resolve(this.nconf.get('torConfig'));
}).bind(this));
/**
* Gets new identities for all instances in the pool.
*/
this.server.methods.newIdentites = this.tor_pool.new_identites.bind(this.tor_pool);
server.expose('setDefaultTorConfig', (function (config) {
this.nconf.set('torConfig', config);
return Promise.resolve();
}).bind(this));
/**
* Get a new identity for the instance at the index provided in the pool.
*/
this.server.methods.newIdentityAt = this.tor_pool.new_identity_at.bind(this.tor_pool);
server.expose('setTorConfig', (function (config) {
return new Promise((resolve, reject) => {
async.each(Object.keys(config), function (key, next) {
var value = config[key];
/**
* Get a new identity for the instance by the {@link TorProcess#instance_name} field.
*/
this.server.methods.newIdentityByName = this.tor_pool.new_identity_by_name.bind(this.tor_pool);
/**
* Gets new identities for all instances in the group.
*/
this.server.methods.newIdentitiesByGroup = this.tor_pool.new_identites_by_group.bind(this.tor_pool);
/**
* Gets the next instance in the pool using the load balance method.
*/
this.server.methods.nextInstance = (() => ControlServer.instance_info(this.tor_pool.next())).bind(this);
/**
* Gets the next instance in the group using the load balance method.
*/
this.server.methods.nextInstanceByGroup = ((group) => {
return ControlServer.instance_info(this.tor_pool.next_by_group(group));
}).bind(this);
/**
* Kills the processes of all instances in the pool.
*/
this.server.methods.closeInstances = this.tor_pool.exit.bind(this.tor_pool);
/**
* Sets a property in the application configuration.
*/
this.server.methods.setConfig = ((key, value) => {
this.nconf.set(key, value);
}).bind(this);
/**
* Gets a property in the application configuration.
*/
this.server.methods.getConfig = ((key) => {
return this.nconf.get(key);
}).bind(this);
/**
* Saves the application configuration to the underlying store (usually a JSON file).
*/
this.server.methods.saveConfig = (async () => {
await new Promise((resolve, reject) => {
this.nconf.save((err) => {
if (err) return reject(err);
this.torPool.set_config_all(key, value, next);
}, function (error){
if (error) reject(error);
resolve();
});
});
}).bind(this);
}).bind(this));
/**
* Loads the application configuration from the underlying store (usually a JSON file).
*/
this.server.methods.loadConfig = (async () => {
await new Promise((resolve, reject) => {
this.nconf.load((err) => {
if (err) return reject(err);
resolve();
server.expose('getLoadBalanceMethod', (function () {
return Promise.resolve(this.torPool.load_balance_method);
}).bind(this));
server.expose('setLoadBalanceMethod', (function (loadBalanceMethod) {
this.torPool.load_balance_method = loadBalanceMethod;
return Promise.resolve();
}).bind(this));
server.expose('getInstanceConfigByName', (function (name, keyword) {
return new Promise((resolve, reject) => {
this.torPool.get_config_by_name(name, keyword, (error, value) => {
if (error) reject(error);
else resolve(value);
});
});
}).bind(this);
}).bind(this));
/**
* Sets a configuration property on all instances in the pool.
*/
this.server.methods.setTorConfig = (async (config) => {
await Promise.all(Object.keys(config).map((key) => {
let value = config[key];
server.expose('getInstanceConfigAt', (function (index, keyword) {
return new Promise((resolve, reject) => {
this.torPool.get_config_at(index, keyword, (error, value) => {
if (error) reject(error);
else resolve(value);
});
});
}).bind(this));
return this.tor_pool.set_config_all(key, value);
}));
}).bind(this);
server.expose('setInstanceConfigByName', (function (name, keyword, value) {
return new Promise((resolve, reject) => {
this.torPool.set_config_by_name(name, keyword, value, (error) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this));
/**
* Sets a configuration property on all instances in a group.
*/
this.server.methods.setTorConfigByGroup = (async (group, config) => {
await Promise.all(Object.keys(config).map((key) => {
let value = config[key];
server.expose('setInstanceConfigAt', (function (index, keyword, value) {
return new Promise((resolve, reject) => {
this.torPool.set_config_at(index, keyword, value, (error) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this));
return this.tor_pool.set_config_by_group(group, key, value);
}));
}).bind(this);
/**
* Retrieves the current load balance method for the pool
*/
this.server.methods.getLoadBalanceMethod = (() => {
return this.tor_pool.load_balance_method;
}).bind(this);
server.expose('signalAllInstances', (function (signal) {
return new Promise((resolve, reject) => {
this.torPool.signal_all(signal, (error) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this));
/**
* Sets the current load balance method for the pool
*/
this.server.methods.setLoadBalanceMethod = ((loadBalanceMethod) => {
this.tor_pool.load_balance_method = loadBalanceMethod;
this.nconf.set('loadBalanceMethod', loadBalanceMethod);
}).bind(this);
server.expose('signalInstanceAt', (function (index, signal, callback) {
return new Promise((resolve, reject) => {
this.torPool.signal_at(index, signal, (error) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this));
/**
* Retrieve a configuration property for an instance identified by the {@link TorProcess#instance_name} field.
*/
this.server.methods.getInstanceConfigByName = this.tor_pool.get_config_by_name.bind(this.tor_pool);
/**
* Retrieves a configuration property for an instance by its index in the pool.
*/
this.server.methods.getInstanceConfigAt = this.tor_pool.get_config_at.bind(this.tor_pool);
/**
* Sets a configuration property for an instance identified by the {@link TorProcess#instance_name} field.
*/
this.server.methods.setInstanceConfigByName = this.tor_pool.set_config_by_name.bind(this.tor_pool);
/**
* Sets a configuration property for an instance identified by its index in the pool.
*/
this.server.methods.setInstanceConfigAt = this.tor_pool.set_config_at.bind(this.tor_pool);
/**
* Sends a signal to all instances in the pool
*/
this.server.methods.signalAllInstances = this.tor_pool.signal_all.bind(this.tor_pool);
/**
* Sends a signal to an instance identified by its index in the pool.
*/
this.server.methods.signalInstanceAt = this.tor_pool.signal_at.bind(this.tor_pool);
/**
* Sends a signal to an instance identified by the {@link TorProcess#instance_name} field.
*/
this.server.methods.signalInstanceByName = this.tor_pool.signal_by_name.bind(this.tor_pool);
/**
* Sends a singal to all instances in a group.
*/
this.server.methods.signalInstancesByGroup = this.tor_pool.signal_by_group.bind(this.tor_pool);
/**
* Adds an instance to a group identified by its {@link TorProcess#instance_name} field.
*/
this.server.methods.addInstanceToGroupByName = this.tor_pool.add_instance_to_group_by_name.bind(this.tor_pool);
/**
* Adds an instance to a group identified by its index in the pool.
*/
this.server.methods.addInstanceToGroupAt = this.tor_pool.add_instance_to_group_at.bind(this.tor_pool);
/**
* Removes an instance from a group identified by its {@link TorProcess#instance_name} field.
*/
this.server.methods.removeInstanceFromGroupByName = this.tor_pool.remove_instance_from_group_by_name.bind(this.tor_pool);
/**
* Remove an instance from a group identified by its index in the pool.
*/
this.server.methods.removeInstanceFromGroupAt = this.tor_pool.remove_instance_from_group_at .bind(this.tor_pool);
server.expose('signalInstanceByName', (function (name, signal, callback) {
return new Promise((resolve, reject) => {
this.torPool.signal_by_name(name, signal, (error) => {
if (error) reject(error);
else resolve();
});
});
}).bind(this));
}
/**
* 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
};
listen(port, callback) {
this.tcpTransport = new rpc.tcpTransport({ port });
this.tcpTransport.listen(this.server);
callback && callback();
}
/**
* 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 TCPTransport(this.serializer, port, hostname);
this.server.addTransport(this.tcpTransport);
this.logger.info(`[control]: control server listening on tcp://${hostname}:${port}`);
await this.tcpTransport.listen();
}
/**
* 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 WebSocketTransport(this.serializer, port, hostname);
this.server.addTransport(this.wsTransport);
this.logger.info(`[control]: control server listening on ws://${hostname}:${port}`);
await this.wsTransport.listen();
}
/**
* 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() {
this.server.close();
return this.tcpTransport.tcpServer.close();
}
/**
* 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;
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 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.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}`);
createSOCKSServer(port, callback) {
this.socksServer = new SOCKSServer(this.torPool, this.logger);
this.socksServer.listen(port || 9050);
this.logger.info(`[socks]: Listening on ${port}`);
this.socksServer;
callback && callback();
}
/**
* 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.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}`);
createHTTPServer(port, callback) {
this.httpServer = new HTTPServer(this.torPool, this.logger);
this.httpServer.listen(port || 9080);
this.logger.info(`[http]: Listening on ${port}`);
this.httpServer;
callback && callback();
}
/**
* 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.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}`);
createDNSServer(port, callback) {
this.dnsServer = new DNSServer(this.torPool, this.nconf.get('dns:options'), this.nconf.get('dns:timeout'), this.logger);
this.dnsServer.serve(port || 9053);
this.logger.info(`[dns]: Listening on ${port}`);
this.dnsServer;
callback && callback();
}
};
/**
* Module that contains the {@link ControlServer} class.
* @module tor-router/ControlServer
* @see ControlServer
*/
module.exports = ControlServer;

View file

@ -1,92 +1,37 @@
const dns = require('native-dns');
const { UDPServer, Request } = dns;
const Promise = require('bluebird');
const UDPServer = require('native-dns').UDPServer;
/**
* 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;
if (!args[1])
args[1] = null;
return await new Promise((resolve, reject) => {
args.push(() => {
let args = Array.from(arguments);
resolve.apply(args);
});
try {
inner_func.apply(this, args);
} catch (error) {
reject(error);
}
});
}
/**
* 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) {
/**
* 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) => {
super(dns_options);
this.logger = logger;
this.tor_pool = tor_pool;
var handle_dns_request = (req, res) => {
let connect = (tor_instance) => {
for (let question of req.question) {
let dns_port = (tor_instance.dns_port);
let outbound_req = Request({
let outbound_req = dns.Request({
question,
server: { address: '127.0.0.1', port: dns_port, type: 'udp' },
timeout: this.dns_timeout
timeout: dns_timeout
});
outbound_req.on('message', (err, answer) => {
if (!err && answer) {
for (let a of answer.answer){
res.answer.push(a);
this.logger.verbose(`[dns]: ${question.name} type ${dns.consts.QTYPE_TO_NAME[question.type]} → 127.0.0.1:${dns_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' }${a.address}`)
}
}
});
outbound_req.on('error', (err) => {
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();
});
@ -101,36 +46,8 @@ class DNSServer extends UDPServer {
this.tor_pool.once('instance_created', connect);
}
};
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);
this.on('request', handle_dns_request);
}
};
/**
* Module that contains the {@link DNSSErver} class.
* @module tor-router/DNSServer
* @see DNSServer
*/
module.exports = DNSServer;

View file

@ -1,157 +1,16 @@
const http = require('http');
const URL = require('url');
const { Server } = http;
const Promise = require('bluebird');
const Server = http.Server;
const socks = require('socksv5');
const URL = require('url');
const SocksProxyAgent = require('socks-proxy-agent');
/**
* 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);
let inner_func = super.listen;
args.push(() => {
let args = Array.from(arguments);
resolve.apply(args);
});
inner_func.apply(this, args);
});
}
/**
* 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="${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;
let deny_un = this.proxy_by_name.deny_unidentified_users;
let header = req.headers['authorization'] || req.headers['proxy-authorization'];
if (!header && deny_un) return deny();
else if (!header) return true;
let token = header.split(/\s+/).pop();
if (!token && deny_un) return deny();
else if (!token) return true;
let buf = new Buffer.from(token, 'base64').toString();
if ( !buf && deny_un ) return deny();
else if (!buf) return true;
let username = buf.split(/:/).shift();
if ( !username && deny_un ) return deny();
else if (!username) return true;
let instance;
if (this.proxy_by_name.mode === 'individual')
instance = this.tor_pool.instance_by_name(username);
else if (this.proxy_by_name.mode === 'group') {
if (!this.tor_pool.group_names.has(username)) return deny();
instance = this.tor_pool.next_by_group(username);
}
else
throw Error(`Unknown "proxy_by_name" mode ${this.proxy_by_name.mode}`);
if (!instance) return deny();
req.instance = instance;
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) {
/**
* 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;
let { instance } = req;
constructor(tor_pool, logger) {
let handle_http_connections = (req, res) => {
let url = URL.parse(req.url);
url.port = url.port || 80;
let buffer = [];
const onError = (err) => {
this.logger.error("[http-proxy]: an error occured: "+err.message);
res.writeHead(500);
res.end();
}
var buffer = [];
function onIncomingData(chunk) {
buffer.push(chunk);
@ -163,18 +22,13 @@ class HTTPServer extends Server {
req.on('data', onIncomingData);
req.on('end', preConnectClosed);
req.on('error', onError);
req.on('error', function (err) {
logger.error("[http-proxy]: an error occured\n"+err.stack);
});
let connect = (tor_instance) => {
let source = { hostname: req.connection.remoteAddress, port: req.connection.remotePort, proto: 'http', by_name: Boolean(instance) };
let socks_port = tor_instance.socks_port;
const agent = socks.HttpAgent({
proxyHost: '127.0.0.1',
proxyPort: socks_port,
auths: [ socks.auth.None() ],
localDNS: false
});
logger.verbose(`[http-proxy]: ${req.connection.remoteAddress}:${req.connection.remotePort} → 127.0.0.1:${socks_port}${url.hostname}:${url.port}`);
let proxy_req = http.request({
method: req.method,
@ -182,18 +36,8 @@ class HTTPServer extends Server {
port: url.port,
path: url.path,
headers: req.headers,
agent
agent: new SocksProxyAgent(`socks://127.0.0.1:${socks_port}`)
}, (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);
});
@ -205,8 +49,6 @@ class HTTPServer extends Server {
res.writeHead(proxy_res.statusCode, proxy_res.headers);
});
proxy_req.on("error", onError);
req.removeListener('data', onIncomingData);
req.on('data', (chunk) => {
@ -225,61 +67,40 @@ class HTTPServer extends Server {
proxy_req.end();
};
if (instance) {
if (instance.ready) {
connect(instance);
}
else {
this.logger.debug(`[http-proxy]: a connection has been attempted to "${instance.instance_name}", but it is not live... waiting for the instance to come online`);
instance.once('ready', (() => connect(instance)));
}
}
else if (this.tor_pool.instances.length) {
if (tor_pool.instances.length) {
connect(tor_pool.next());
} else {
this.logger.debug(`[http-proxy]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`);
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);
}
}
/**
* 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;
let { instance } = req;
};
let handle_connect_connections = (req, inbound_socket, head) => {
let hostname = req.url.split(':').shift();
let port = Number(req.url.split(':').pop());
let connect = (tor_instance) => {
let source = { hostname: req.connection.remoteAddress, port: req.connection.remotePort, proto: 'http-connect', by_name: Boolean(instance) };
let socks_port = tor_instance.socks_port;
logger && logger.verbose(`[http-connect]: ${req.connection.remoteAddress}:${req.connection.remotePort} → 127.0.0.1:${socks_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' }${hostname}:${port}`)
var outbound_socket;
let onClose = (error) => {
inbound_socket && inbound_socket.end();
outbound_socket && outbound_socket.end();
inbound_socket = outbound_socket = void(0);
inbound_socket = outbound_socket = buffer = void(0);
if (error instanceof Error)
if (error)
this.logger.error(`[http-connect]: an error occured: ${error.message}`)
};
inbound_socket.on('error', onClose);
inbound_socket.on('close', onClose);
var buffer = [head];
let onInboundData = function (data) {
buffer.push(data);
};
const client = socks.connect({
socks.connect({
host: hostname,
port: port,
proxyHost: '127.0.0.1',
@ -287,66 +108,31 @@ 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);
outbound_socket && outbound_socket.on('close', onClose);
outbound_socket && outbound_socket.on('error', onClose);
inbound_socket.write(`HTTP/1.1 200 Connection Established\r\n'+'Proxy-agent: ${TOR_ROUTER_PROXY_AGENT}\r\n` +'\r\n');
inbound_socket.write('HTTP/1.1 200 Connection Established\r\n'+'Proxy-agent: tor-router\r\n' +'\r\n');
outbound_socket.write(head);
outbound_socket.pipe(inbound_socket);
inbound_socket.pipe(outbound_socket);
});
client.on('error', onClose);
};
if (instance) {
if (instance.ready) {
connect(instance);
}
else {
this.logger.debug(`[http-connect]: a connection has been attempted to "${instance.instance_name}", but it is not live... waiting for the instance to come online`);
instance.once('ready', (() => connect(instance)));
}
}
else if (this.tor_pool.instances.length) {
connect(this.tor_pool.next());
if (tor_pool.instances.length) {
connect(tor_pool.next());
} else {
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);
logger.debug(`[http-connect]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`);
tor_pool.once('instance_created', connect);
}
}
};
super(handle_http_connections);
this.on('connect', handle_connect_connections);
/**
* 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.logger = logger;
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

@ -1,127 +1,11 @@
const socks = require('socksv5');
const Promise = require('bluebird');
const { Server } = socks;
const SOCKS5Server = socks.Server;
/**
* 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);
let inner_func = super.listen;
args.push(() => {
let args = Array.from(arguments);
resolve.apply(args);
});
inner_func.apply(this, args);
});
}
/**
* 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);
else if (this.proxy_by_name.mode === 'group') {
return this.tor_pool.next_by_group(username);
} else
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;
// No username and deny unindentifed then deny
if (!username && deny_un) callback(false);
// Otherwise if there is no username allow
else if (!username) callback(true);
if (this.proxy_by_name.mode === 'individual'){
if (!this.tor_pool.instance_names.includes(username)) return callback(false);
}
else if (this.proxy_by_name.mode === 'group') {
if (!this.tor_pool.group_names.has(username)) return callback(false);
}
else
throw Error(`Unknown "proxy_by_name" mode "${this.proxy_by_name.mode}"`);
// Otherwise allow
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) {
/**
* 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) => {
class SOCKSServer extends SOCKS5Server{
constructor(tor_pool, logger) {
let handleConnection = (info, accept, deny) => {
let inbound_socket = accept(true);
let instance;
if (inbound_socket.user)
instance = this.get_instance_pbn(inbound_socket.user);
let outbound_socket;
var outbound_socket;
let buffer = [];
let onInboundData = (data) => buffer.push(data)
@ -143,10 +27,10 @@ class SOCKSServer extends Server{
inbound_socket.on('error', onClose);
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;
logger.verbose(`[socks]: ${info.srcAddr}:${info.srcPort} → 127.0.0.1:${socks_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' }${info.dstAddr}:${info.dstPort}`)
let client = socks.connect({
socks.connect({
host: info.dstAddr,
port: info.dstPort,
proxyHost: '127.0.0.1',
@ -154,16 +38,6 @@ 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);
@ -182,64 +56,21 @@ class SOCKSServer extends Server{
outbound_socket.write(buffer.shift());
}
});
client.on('error', onClose);
};
if (instance) {
if (instance.ready) {
connect(instance);
}
else {
this.logger.debug(`[socks]: a connection has been attempted to "${instance.instance_name}", but it is not live... waiting for the instance to come online`);
instance.once('ready', (() => connect(instance)));
}
}
else if (this.tor_pool.instances.length) {
connect(this.tor_pool.next());
if (tor_pool.instances.length) {
connect(tor_pool.next());
} else {
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);
logger.debug(`[socks]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`);
tor_pool.once('instance_created', connect);
}
}
super(handle_connections);
};
let auth = socks.auth.None();
if (proxy_by_name) {
auth = socks.auth.UserPassword(this.authenticate_user.bind(this));
}
super(handleConnection);
this.useAuth(auth);
/**
* 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'}`);
this.logger = logger;
this.useAuth(socks.auth.None());
}
};
/**
* Module that contains the {@link SOCKSServer} class.
* @module tor-router/SOCKSServer
* @see SOCKSServer
*/
module.exports = SOCKSServer;

View file

@ -1,6 +1,4 @@
/**
* @author Christoph and Marco Bonelli on Stackoverflow <https://bit.ly/2p6gedO>
*/
/* From http://stackoverflow.com/questions/1985260/javascript-array-rotate */
Array.prototype.rotate = (function() {
// save references to array functions to make lookup faster
var push = Array.prototype.push,
@ -19,780 +17,223 @@ Array.prototype.rotate = (function() {
};
})();
const path = require('path');
const fs = require('fs');
const { EventEmitter } = require('eventemitter3');
const Promise = require("bluebird");
const EventEmitter = require('eventemitter2').EventEmitter2;
const async = require('async');
const TorProcess = require('./TorProcess');
const _ = require('lodash');
const path = require('path');
const nanoid = require('nanoid');
const fs = require('fs');
const WeightedList = require('js-weighted-list');
const getPort = require('get-port');
const TorProcess = require('./TorProcess');
const { Mutex } = require('async-mutex');
const load_balance_methods = {
round_robin: function (instances) {
return instances.rotate(1);
},
weighted: function (instances) {
if (!instances._weighted_list) {
instances._weighted_list = new WeightedList(
instances.map((instance) => {
return [ instance.id, instance.definition.Weight, instance ]
})
);
};
let i = instances._weighted_list.peek(instances.length).map((element) => element.data);
i._weighted_list = instances._weighted_list;
return i;
}
};
Promise.promisifyAll(fs);
/**
* Class that represents a pool of Tor processes.
* @extends EventEmitter
*/
class TorPool extends EventEmitter {
/**
* 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 for each instance.
* @param {string} [logger] - A winston logger. If not provided no logging will occur.
*
* @throws If "data_directory" is not provided.
*/
constructor(tor_path, default_config, data_directory, load_balance_method, granax_options, logger) {
if (!data_directory)
throw new Error('Invalid "data_directory"');
super();
this._instances = [];
this._default_tor_config = default_config;
/**
* Parent directory for the data directory of each proccess.
*
* @type {string}
* @public
*/
default_config = _.extend({}, (default_config || {}));
this.default_tor_config = default_config;
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
*/
!fs.existsSync(this.data_directory) && fs.mkdirSync(this.data_directory);
this.tor_path = tor_path;
/**
* 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.logger = logger;
this.granax_options = granax_options;
}
/**
* Returns a Set containing the names of all of the groups.
*
* @readonly
* @type {Set<string>}
*/
get group_names() {
return new Set(_.flatten(this.instances.map((instance) => instance.instance_group).filter(Boolean)));
}
/**
* Returns an array containing all of the instances in a group.
*
* @param {string} group_name - The group to query.
* @returns {string[]}
*
* @throws If the provided group does not exist
*/
instances_by_group(group_name) {
if (!this.group_names.has(group_name))
throw new Error(`Group "${group_name}" doesn't exist`);
let group = this.groups[group_name];
let arr = [];
for (let i = 0; i < group.length; i++) {
arr.push(group[i]);
}
return arr;
}
/**
* Adds an instance to a group. If the group doesn't exist it will be created.
*
* @param {string} group - The group to add the instance to.
* @param {TorProcess} instance - The instance in question.
*/
add_instance_to_group(group, instance) {
instance.definition.Group = _.union(instance.instance_group, [group]);
}
/**
* Adds an instance to a group by the {@link TorProcess#instance_name} property on the instance. If the group doesn't exist it will be created.
*
* @param {string} group - The group to add the instance to.
* @param {string} instance_name - The name of the instance in question.
*
* @throws If an instance with the name provided does not exist
*/
add_instance_to_group_by_name(group, instance_name) {
let instance = this.instance_by_name(instance_name);
if (!instance) throw new Error(`Instance "${instance_name}" not found`);
return this.add_instance_to_group(group, instance);
}
/**
* Adds an instance to a group by the index of the instance in the pool. If the group doesn't exist it will be created.
*
* @param {string} group - The group to add the instance to.
* @param {number} instance_index - The index of the instance in question.
*
* @throws If an instance with the index provided does not exist.
*/
add_instance_to_group_at(group, instance_index) {
let instance = this.instance_at(instance_index);
if (!instance) throw new Error(`Instance at "${instance_index}" not found`);
return this.add_instance_to_group(group, instance);
}
/**
* Removes an instance from a group.
*
* @param {string} group - The group to remove the instance from.
* @param {TorProcess} instance - The instance in question.
*/
remove_instance_from_group(group, instance) {
_.remove(instance.definition.Group, (g) => g === group);
}
/**
* Removes an instance from a group by the {@link TorProcess#instance_name} property on the instance.
*
* @param {string} group - The group to remove the instance from.
* @param {string} instance_name - The name of the instance in question.
*
* @throws If an instance with the name provided does not exist.
*/
remove_instance_from_group_by_name(group, instance_name) {
let instance = this.instance_by_name(instance_name);
if (!instance) throw new Error(`Instance "${instance_name}" not found`);
return this.remove_instance_from_group(group, instance);
}
/**
* Removes an instance from a group by the index of the instance in the pool.
*
* @param {string} group - The group to remove the instance from.
* @param {number} instance_index - The index of the instance in question.
*
* @throws If an instance with the index provided does not exist.
*/
remove_instance_from_group_at(group, instance_index) {
let instance = this.instance_at(instance_index);
if (!instance) throw new Error(`Instance at "${instance_index}" not found`);
return this.remove_instance_from_group(group, instance);
}
/**
* 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[]} InstanceGroup
*
* @property {Function} add - Adds an instance to the group.
* @property {Function} remove - Removes an instance from the group.
* @property {Function} add_by_name - Adds an instance to the group by the {@link TorProcess#instance_name} property on the instance.
* @property {Function} remove_by_name - Removes an instance from the group by the {@link TorProcess#instance_name} property on the instance.
* @property {Function} remove_at - Removes an instance from the group by the index of the instance in the group.
* @property {number} length - The size of the group of instances
* @property {Function} rotate - Rotates the array of instances
*/
/**
* 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 {InstanceGroup[]} InstanceGroupCollection
*/
/**
* Represents all groups currently in the pool.
*
* @readonly
* @type {InstanceGroupCollection}
*/
get groups() {
let groupHandler = {
get: (instances, prop) => {
if (!Number.isNaN(Number(prop)))
return instances[prop];
let save_index = () => {
instances = instances.map((instance, index) => {
instance._index = index;
return instance;
});
};
let { group_name } = instances;
if (prop === 'add')
return (instance) => {
this.add_instance_to_group(group_name, instance);
save_index();
};
if (prop === 'add_by_name') {
return (instance_name) => {
this.add_instance_to_group_by_name(group_name, instance_name);
save_index();
};
}
if (prop === 'remove')
return (instance) => {
this.remove_instance_from_group(group_name, instance);
save_index();
};
if (prop === 'remove_by_name') {
return (instance_name) => {
this.remove_instance_from_group_by_name(group_name, instance_name);
save_index();
};
}
if (prop === 'remove_at')
return (instance_index) => {
this.remove_instance_from_group(group_name, instances[instance_index]);
save_index();
};
if (prop === 'length')
return instances.length;
if (prop === 'rotate') {
return (num) => {
instances.rotate(typeof(num) === 'undefined' ? 1 : num);
save_index();
};
}
return void(0);
}
};
let groupsHandler = {
get: (group_names, prop) => {
let instances_in_group = [];
if (group_names.has(prop)) {
instances_in_group = this.instances.filter((instance) => instance.instance_group.indexOf(prop) !== -1);
}
instances_in_group = _.sortBy(instances_in_group, ['_index', 'instance_name']);
instances_in_group.group_name = prop;
return new Proxy(instances_in_group, groupHandler);
}
};
return new Proxy(this.group_names, groupsHandler);
}
/**
* The default configuration that will be passed to each instance. Values from "definition.Config" on each instance will override the default config
*
*/
/** Getter
*
* @type {Object|Function}
*/
get default_tor_config() {
if (typeof(this._default_tor_config) === 'function')
return this._default_tor_config();
else if (this._default_tor_config)
return _.cloneDeep(this._default_tor_config);
else
return {};
}
/**
* Setter
*
* @param {Object|Function} value
*/
set default_tor_config(value) { this._default_tor_config = value; }
/**
* Returns an enumeration of load balance methods as functions
*
* @readonly
* @enum {Function}
* @static
*/
static get load_balance_methods() {
return Object.freeze({
round_robin: function (instances) {
return instances.rotate(1);
},
weighted: function (instances) {
if (!instances._weighted_list) {
instances._weighted_list = new WeightedList(
instances.map((instance) => {
return [ instance.id, instance.definition.Weight, instance ]
})
);
};
let i = instances._weighted_list.peek(instances.length).map((element) => element.data);
i._weighted_list = instances._weighted_list;
return i;
}
});
}
/**
* An array containing all instances in the pool.
*
* @readonly
* @type {TorProcess[]}
*/
get instances() {
return this._instances.slice(0);
return this._instances.slice(0).filter((tor) => tor.ready);
}
/**
* An array containing the names of the instances in the pool.
*
* @readonly
* @type {string[]}
*/
get instance_names() {
return this.instances.map((i) => i.instance_name);
}
/**
* Creates an instance then adds it to the pool from the provided definiton.
* Instance will be added (and Promise will resolve) after the instance is fully bootstrapped.
*
* @async
* @param {InstanceDefinition} [instance_definition={}] - Instance definition that will be used to create the instance.
* @returns {Promise<TorProcess>} - The instance that was created.
*
* @throws If an instance with the same {@link InstanceDefinition#Name} already exists.
*/
async create_instance(instance_definition) {
if (!(fs.existsSync(this.data_directory)))
await fs.mkdirAsync(this.data_directory);
instance_definition = instance_definition || {};
if (instance_definition.Name && this.instance_names.indexOf(instance_definition.Name) !== -1)
throw new Error(`Instance named ${instance_definition.Name} already exists`);
create_instance(instance_definition, callback) {
this._instances._weighted_list = void(0);
instance_definition.Config = _.extend(_.cloneDeep(this.default_tor_config), (instance_definition.Config || {}));
let instance = new TorProcess(this.tor_path, instance_definition, this.granax_options, this.logger);
instance.definition.Config.DataDirectory = instance.definition.Config.DataDirectory || path.join(this.data_directory, instance.instance_name);
await instance.create();
this._instances.push(instance);
return await new Promise((resolve, reject) => {
instance.once('error', reject);
if (!instance_definition.Config)
instance_definition.Config = _.extend({}, this.default_tor_config);
let instance_id = nanoid();
instance_definition.Config.DataDirectory = instance_definition.Config.DataDirectory || path.join(this.data_directory, (instance_definition.Name || instance_id));
let instance = new TorProcess(this.tor_path, instance_definition.Config, this.granax_options, this.logger);
instance.id = instance_id;
instance.definition = instance_definition;
instance.create((error) => {
if (error) return callback(error);
this._instances.push(instance);
instance.once('error', callback)
instance.once('ready', () => {
/**
* Fires when an instance has been created.
*
* @event TorPool#instance_created
* @type {TorProcess}
* @param {TorProcess} instance - The instance that was created.
*/
this.emit('instance_created', instance);
resolve(instance);
callback && callback(null, instance);
});
});
}
/**
* Adds one or more instances to the pool from an array of definitions or single definition.
* @param {InstanceDefinition[]|InstanceDefinition} instance_definitions
*
* @async
* @return {Promise<TorProcess[]>}
* @throws If `instance_definitions` is falsy.
*/
async add(instance_definitions) {
if (!instance_definitions)
throw new Error('Invalid "instance_definitions"');
return await Promise.all([].concat(instance_definitions).map((instance_definition) => this.create_instance(instance_definition)));
add(instance_definitions, callback) {
async.each(instance_definitions, (instance_definition, next) => {
this.create_instance(instance_definition, next);
}, (callback || (() => {})));
}
/**
* Creates one or more instances to the pool from either an array of definitions, a single definition or a number.
* If a number is provided it will create n instances with empty definitions (e.g. `TorPool.create(5)` will create 5 instances).
* @param {InstanceDefinition[]|InstanceDefinition|number} instance_definitions
*
* @async
* @return {Promise<TorProcess[]>}
* @throws If `instances` is falsy.
*/
async create(instances) {
if (!instances)
throw new Error('Invalid "instances"');
create(instances, callback) {
if (typeof(instances) === 'number') {
instances = Array.from(Array(instances)).map(() => ({}));
const lock = new Mutex();
instances = await Promise.all(instances.map(async(instance) => {
instance.ports = {};
let release = await lock.acquire();
try {
instance.ports.dns_port = await getPort();
instance.ports.socks_port = await getPort();
instance.ports.control_port = await getPort();
} finally {
release();
}
return instance;
}));
instances = Array.from(Array(instances)).map(() => {
return {
Config: {}
};
});
}
return await this.add(instances);
return this.add(instances, callback);
}
/**
* Searches for an instance with the matching {@link TorProcess#instance_name} property.
* @param {string} name - Name of the instance to search for
* @returns {TorProcess} - Matching instance
*/
instance_by_name(name) {
return this._instances.filter((i) => i.instance_name === name)[0];
return this.instances.filter((i) => i.definition.Name === name)[0];
}
/**
* Returns the instance located at the provided index in the pool.
* Is equivalent to `{@link TorPool#instances}[index]`
* @param {number} index - Index of the instance in the pool
* @returns {TorProcess} - Matching instance
*/
instance_at(index) {
return this._instances[index];
return this.instances[index];
}
/**
* Removes a number of instances from the pool and kills their Tor processes.
* @param {number} instances - Number of instances to remove
* @param {number} [start_at=0] - Index to start removing from
* @async
* @returns {Promise} - Promise will resolve when the processes are dead
*/
async remove(instances, start_at) {
remove(instances, callback) {
this._instances._weighted_list = void(0);
let instances_to_remove = this._instances.splice((start_at || 0), instances);
await Promise.all(instances_to_remove.map((instance) => instance.exit()));
let instances_to_remove = this._instances.splice(0, instances);
async.each(instances_to_remove, (instance, next) => {
instance.exit(next);
}, callback);
}
/**
* Removes an instance at the provided index and kills its Tor process.
* @param {number} instance_index - Index of the instance to remove
* @async
* @returns {Promise} - Promise will resolve when the process is dead
*/
async remove_at(instance_index) {
remove_at(instance_index, callback) {
this._instances._weighted_list = void(0);
let instance = this._instances.splice(instance_index, 1)[0];
if (!instance)
throw new Error(`No instance at "${instance_index}"`);
await instance.exit();
instance.exit((error) => {
callback(error);
});
}
/**
* Removes an instance whose {@link TorProcess#instance_name} property matches the provided name and kills its Tor process.
* @param {string} instance_name - Name of the instance to remove
* @async
* @returns {Promise} - Promise will resolve when the process is dead
*/
async remove_by_name(instance_name) {
remove_by_name(instance_name, callback) {
let instance = this.instance_by_name(instance_name);
if (!instance)
throw new Error(`Instance "${name}" not found`);
if (!instance) return callback && callback(new Error(`Instance "${name}" not found`));
let instance_index = (this.instances.indexOf(instance));
await this.remove_at(instance_index);
return this.remove_at(instance_index, callback);
}
/**
* Runs the load balance function ({@link TorPool#load_balance_method}) on the array of instances in the pool and returns the first instance in the array.
*
* @returns {TorProcess} - The first instance in the modified array.
*/
next() {
this._instances = TorPool.load_balance_methods[this.load_balance_method](this._instances);
this._instances = load_balance_methods[this.load_balance_method](this._instances);
return this.instances[0];
}
/**
* Rotates the array containing instances in the group provided so that the second element becomes the first element and the first element becomes the last element.
* [1,2,3] -> [2,3,1]
* @todo Load balance methods other than "round_robin" to be used
* @param {string} group - Name of the group
* @returns {TorProcess} - The first element in the modified array
*/
next_by_group(group) {
this.groups[group].rotate(1);
return this.groups[group][0];
exit(callback) {
async.until(() => { return this._instances.length === 0; }, (next) => {
var instance = this._instances.shift();
instance.exit(next);
}, (error) => {
callback && callback(error);
});
}
/**
* Kills the Tor processes of all instances in the pool.
*
* @async
* @returns {Promise} - Resolves when all instances have been killed.
*/
async exit() {
await Promise.all(this._instances.map((instance) => instance.exit()));
this._instances = [];
new_identites(callback) {
async.each(this.instances, (tor, next) => {
tor.new_identity(next);
}, (error) => {
callback && callback(error);
});
}
/**
* Gets new identities for all instances in the pool.
*
* @async
* @returns {Promise} - Resolves when all instances have new identities.
*/
async new_identites() {
await Promise.all(this.instances.map((instance) => instance.new_identity()));
new_identity_at(index, callback) {
this.instances[index].new_identity(callback);
}
/**
* Gets new identities for all instances in a group.
*
* @async
* @param {string} - Name of the group.
* @returns {Promise} - Resolves when all instances in the group have new identities.
*/
async new_identites_by_group(group) {
await Promise.all(this.instances_by_group(group).map((instance) => instance.new_identity()));
}
/**
* Gets a new identity for the instance at the provided index in the pool.
*
* @async
* @param {number} - Index of the instance in the pool.
* @returns {Promise} - Resolves when the instance has a new identity.
*/
async new_identity_at(index) {
await this.instances[index].new_identity();
}
/**
* Gets a new identity for the instance whose {@link TorProcess.instance_name} matches the provided name.
*
* @async
* @param {string} - Name of the instance.
* @returns {Promise} - Resolves when the instance has a new identity.
*
* @throws When no instance matched the provided name.
*/
async new_identity_by_name(name) {
new_identity_by_name(name, callback) {
let instance = this.instance_by_name(name);
if (!instance)
throw new Error(`Instance "${name}" not found`);
await instance.new_identity();
if (!instance) return callback && callback(new Error(`Instance "${name}" not found`));
instance.new_identity(callback);
}
/**
* Get a configuration value from the instance whose {@link TorProcess.instance_name} matches the provided name via the control protocol.
*
* @async
* @param {string} name - Name of the instance.
* @param {string} keyword - Name of the configuration property.
*
* @returns {Promise<string[]>} - The configuration property's value.
* @throws When no instance matched the provided name.
*/
async get_config_by_name(name, keyword) {
/* Begin Deprecated */
new_ips(callback) {
this.logger.warn(`TorPool.new_ips is deprecated, use TorPool.new_identites`);
return this.new_identites(callback);
}
new_ip_at(index, callback) {
this.logger.warn(`TorPool.new_ip_at is deprecated, use TorPool.new_identity_at`);
return this.new_identity_at(index, callback);
}
/* End Deprecated */
get_config_by_name(name, keyword, callback) {
let instance = this.instance_by_name(name);
if (!instance)
throw new Error(`Instance "${name}" not found`);
return await instance.get_config(keyword);
if (!instance) return callback && callback(new Error(`Instance "${name}" not found`));
instance.get_config(keyword, callback);
}
/**
* Set a configuration value for the instance whose {@link TorProcess.instance_name} matches the provided name via the control protocol.
*
* @async
* @param {string} name - Name of the instance.
* @param {string} keyword - Name of the configuration property.
* @param {any} value - Value to set the configuration property to.
*
* @returns {Promise}
* @throws When no instance matched the provided name.
*/
async set_config_by_name(name, keyword, value) {
set_config_by_name(name, keyword, value, callback) {
let instance = this.instance_by_name(name);
if (!instance)
throw new Error(`Instance "${name}" not found`);
return await instance.set_config(keyword, value);
if (!instance) return callback && callback(new Error(`Instance "${name}" not found`));
instance.set_config(keyword, value, callback);
}
/**
* Get a configuration value from the instance at the index in the pool via the control protocol.
*
* @async
* @param {number} index - Index of the instance in the pool.
* @param {string} keyword - Name of the configuration property.
*
* @returns {Promise<string[]>} - The configuration property's value.
* @throws When no instance exists at the provided index.
*/
async get_config_at(index, keyword) {
get_config_at(index, keyword, callback) {
let instance = this.instances[index];
if (!instance)
throw new Error(`Instance at ${index} not found`);
return await instance.get_config(keyword);
if (!instance) return callback && callback(new Error(`Instance at ${index} not found`));
instance.get_config(keyword, callback);
}
/**
* Set a configuration value for the instance at the index in the pool via the control protocol.
*
* @async
* @param {number} index - Index of the instance in the pool.
* @param {string} keyword - Name of the configuration property.
* @param {any} value - Value to set the configuration property to.
*
* @returns {Promise}
* @throws When no instance exists at the provided index.
*/
async set_config_at(index, keyword, value) {
set_config_at(index, keyword, value, callback) {
let instance = this.instances[index];
if (!instance)
throw new Error(`Instance at ${index} not found`);
return await instance.set_config(keyword, value);
if (!instance) return callback && callback(new Error(`Instance at ${index} not found`));
instance.set_config(keyword, value, callback);
}
/**
* Set a configuration value for all instances in the provided group via the control protocol.
*
* @async
* @param {string} group - Name of the group.
* @param {string} keyword - Name of the configuration property.
* @param {any} value - Value to set the configuration property to.
*
* @returns {Promise}
* @throws When the provided group does not exist.
*/
async set_config_by_group(group, keyword, value) {
return await Promise.all(this.instances_by_group(group).map((instance) => instance.set_config(keyword, value)));
set_config_all(keyword, value, callback) {
var i = 0;
async.until(() => { return i === this.instances.length; }, (next) => {
let instance = this.instances[i++];
instance.set_config(keyword, value, (error) => {
next(error);
});
}, (error) => {
callback(error);
});
}
/**
* Set a configuration value for all instances in the pool via the control protocol.
*
* @async
* @param {string} keyword - Name of the configuration property.
* @param {any} value - Value to set the configuration property to.
*
* @returns {Promise}
*/
async set_config_all(keyword, value) {
return await Promise.all(this.instances.map((instance) => instance.set_config(keyword, value)));
signal_all(signal, callback) {
async.each(this.instances, (instance, next) => {
instance.signal(signal, next);
}, callback);
}
/**
* Send a signal via the control protocol to all instances in the pool.
*
* @async
* @param {string} signal - The signal to send.
*
* @returns {Promise}
*/
async signal_all(signal) {
await Promise.all(this.instances.map((instance) => instance.signal(signal)));
}
/**
* Send a signal via the control protocol to an instance whose {@link TorProcess#instance_name} property matches the provided name.
*
* @async
* @param {string} name - Name of the instance.
* @param {string} signal - The signal to send.
*
* @returns {Promise}
*/
async signal_by_name(name, signal) {
signal_by_name(name, signal, callback) {
let instance = this.instance_by_name(name);
if (!instance)
throw new Error(`Instance "${name}" not found`);
await instance.signal(signal);
if (!instance) return callback && callback(new Error(`Instance "${name}" not found`));
instance.signal(signal, callback);
}
/**
* Send a signal via the control protocol to an instance at the provided index in the pool.
*
* @async
* @param {number} index - Index of the instance in the pool.
* @param {string} signal - The signal to send.
*
* @returns {Promise}
*/
async signal_at(index, signal) {
signal_at(index, signal, callback) {
let instance = this.instances[index];
if (!instance)
throw new Error(`Instance at ${index} not found`);
await instance.signal(signal);
}
/**
* Send a signal via the control protocol to all instances in the provided group.
*
* @async
* @param {string} group - Name of the group.
* @param {string} signal - The signal to send.
*
* @returns {Promise}
* @throws When the provided group does not exist.
*/
async signal_by_group(group, signal) {
await Promise.all(this.instances_by_group(group).map((instance) => instance.signal(signal)));
if (!instance) return callback && callback(new Error(`Instance at ${index} not found`));
instance.signal(signal, callback);
}
};
/**
* Module that contains the {@link TorPool} class.
* @module tor-router/TorPool
* @see TorPool
*/
TorPool.LoadBalanceMethods = load_balance_methods;
module.exports = TorPool;

View file

@ -1,432 +1,211 @@
const spawn = require('child_process').spawn;
const fs = require('fs');
const crypto = require('crypto');
const { connect } = require('net');
const os = require('os');
const Promise = require('bluebird');
const _ = require('lodash');
const { EventEmitter } = require('eventemitter3');
const shell = require('shelljs');
const async = require('async');
const fs = require('fs');
const getPort = require('get-port');
const del = require('del');
const EventEmitter = require('eventemitter2').EventEmitter2;
const temp = require('temp');
const { TorController } = require('@deadcanaries/granax');
const nanoid = require("nanoid");
const winston = require('winston');
Promise.promisifyAll(temp);
Promise.promisifyAll(fs);
const { TorController } = require('granax');
const { connect } = require('net');
const shell = require('shelljs');
const crypto = require('crypto');
temp.track();
/**
* @typedef {Object} InstanceDefinition
* @property {string} [Name] - Name of the instance.
* @property {string[]|string} [Group=[]] - Groups the instance belongs to.
* @property {Object} [Config={}] - Configuration that will be passed to Tor. See {@link https://bit.ly/2QrmI3o|Tor Documentation} for all possible options.
* @property {Number} [Weight] - Weight of the instance for "weighted" load balancing.
*/
/**
* Class that represents an individual Tor process.
*
* @extends EventEmitter
*/
class TorProcess extends EventEmitter {
/**
* 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.
* @param {Object} [granax_options] - Object containing options that will be passed to granax.
* @param {Logger} [logger] - A winston logger. If not provided no logging will occur.
*/
constructor(tor_path, definition, granax_options, logger) {
constructor(tor_path, config, granax_options, logger) {
super();
this.logger = logger || require('./winston_silent_logger');
definition = definition || {};
definition.Group = definition.Group ? [].concat(definition.Group) : [];
definition.Config = definition.Config || {};
this._definition = definition;
this._ports = definition.ports || {};
/**
* Path to the Tor executable.
*
* @type {string}
* @public
*/
this.logger = logger;
this.tor_path = tor_path;
/**
* Object containing options that will be passed to granax.
*
* @type {Object}
* @public
*/
this.granax_options = granax_options;
/**
* The password that will be set for the control protocol.
*
* @type {string}
* @public
*/
this.control_password = crypto.randomBytes(128).toString('base64');
this._id = nanoid(12);
this.tor_config.DataDirectory = this.tor_config.DataDirectory || temp.mkdirSync();
config.DataDirectory = config.DataDirectory || temp.mkdirSync();
this.tor_config = config;
}
/**
* Kills the Tor process.
*
* @async
* @returns {Promise}
*/
async exit() {
let p = new Promise((resolve, reject) => {
this.once('process_exit', (code) => {
resolve();
});
exit(callback) {
this.once('process_exit', (code) => {
callback && callback(null, code);
});
this.process.kill('SIGINT');
await p;
}
/**
* The unique identifier assigned to each instance.
*
* @readonly
* @type {string}
*/
get id() { return this._id; }
/**
* Groups the instance are currently in.
*
* @readonly
* @type {string[]}
*/
get instance_group() {
return (this.definition && this.definition.Group);
}
/**
* Either the "Name" property of the definition or the {@link TorProcess#id} property.
*
* @readonly
* @type {string}
*/
get instance_name() {
return (this.definition && this.definition.Name) || this.id;
return (this.definition && this.definition.Name) || this.process.pid;
}
/**
* The definition used to create the instance.
*
* @readonly
* @type {string}
*/
get definition() { return this._definition; }
/**
* The configuration passed to Tor. The same value as `definition.Config`.
*
* @readonly
* @type {Object}
*/
get tor_config() { return this.definition.Config; }
/**
* Port Tor is bound to for DNS traffic.
*
* @readonly
* @type {number}
*/
get dns_port() {
return this._ports.dns_port;
return this._dns_port || null;
}
/**
* Port Tor is bound to for SOCKS5 traffic.
*
* @readonly
* @type {number}
*/
get socks_port() {
return this._ports.socks_port;
return this._socks_port || null;
}
/**
* Port Tor is bound to for API access.
*
* @readonly
* @type {number}
*/
get control_port() {
return this._ports.control_port;
return this._control_port || null;
}
/**
* Instance of granax.TorController connected to the Tor process.
*
* @readonly
* @type {TorController}
*/
get controller() {
return this._controller;
return this._controller || null;
}
/**
* Property identifiyng whether Tor has started.
*
* @readonly
* @type {boolean}
*/
get ready() { return this._ready; }
/* Passthrough to granax */
/**
* Requests a new identity via the control protocol.
*
* @async
*/
async new_identity() {
new_identity(callback) {
this.logger.info(`[tor-${this.instance_name}]: requested a new identity`);
await this.controller.cleanCircuitsAsync();
this.controller.cleanCircuits(callback || (() => {}));
}
/**
* Retrieves a configuration value from the instance via the control protocol.
*
* @async
* @throws Will throw an error if not connected to the control protocol.
* @param {string} keyword - The name of the configuration property to retrieve.
*
* @returns {Promise<string[]>}
*/
async get_config(keyword) {
if (!this.controller)
throw new Error(`Controller is not connected`);
return await this.controller.getConfigAsync(keyword);
}
/**
* Sets a configuration value for the instance via the control protocol.
*
* @async
* @throws Will throw an error if not connected to the control protocol.
* @param {string} keyword - The name of the configuration property to retrieve.
* @param {any} value - Value to set the property to.
*
* @returns {Promise}
*/
async set_config(keyword, value) {
get_config(keyword, callback) {
if (!this.controller) {
return new Error(`Controller is not connected`);
return callback(new Error(`Controller is not connected`));
}
return await this.controller.setConfigAsync(keyword, value);
return this.controller.getConfig(keyword, callback);
}
/**
* Sends a signal via the control tnterface.
*
* @async
* @throws Will throw an error if not connected to the control protocol.
* @param {string} signal - The signal to send.
*
* @returns {Promise}
*/
async signal(signal) {
set_config(keyword, value, callback) {
if (!this.controller) {
throw new Error(`Controller is not connected`);
return callback(new Error(`Controller is not connected`));
}
return await this.controller.signal(signal);
return this.controller.setConfig(keyword, value, callback);
}
/**
* Creates the Tor process based on the configuration provided. Promise is resolved when the process has been started.
*
* @async
*
* @returns {Promise<ChildProcess>} - The process that has been created.
*/
async create() {
let dnsPort = this._ports.dns_port || await getPort();
let socksPort = this._ports.socks_port || await getPort();
let controlPort = this._ports.control_port || await getPort();
this.logger.info(`[tor-${this.instance_name}]: DNS PORT = ${dnsPort}`);
this.logger.info(`[tor-${this.instance_name}]: SOCKS PORT = ${socksPort}`);
this.logger.info(`[tor-${this.instance_name}]: CONTROL PORT = ${controlPort}`);
Object.freeze(this._ports);
signal(signal, callback) {
if (!this.controller) {
return callback(new Error(`Controller is not connected`));
}
let options = {
DNSPort: `127.0.0.1:${dnsPort}`,
SocksPort: `127.0.0.1:${socksPort}`,
ControlPort: `127.0.0.1:${controlPort}`,
HashedControlPassword: shell.exec(`${this.tor_path} --quiet --hash-password "${this.control_password}"`, { async: false, silent: true }).stdout.trim()
};
return this.controller.signal(signal, callback);
}
let config = _.extend(_.cloneDeep(this.tor_config), options);
let text = Object.keys(config).map((key) => `${key} ${config[key]}`).join(os.EOL);
/* Begin Deprecated */
let configFile = await temp.openAsync('tor-router');
let configPath = configFile.path;
await fs.writeFileAsync(configPath, text);
new_ip(callback) {
this.logger.warn(`TorProcess.new_ip is deprecated, use TorProcess.new_identity`);
return this.new_identity(callback);
}
let tor = spawn(this.tor_path, ['-f', configPath], {
stdio: ['ignore', 'pipe', 'pipe'],
detached: false
});
/* End Deprecated */
tor.on('close', (async (code) => {
if (this.definition && !this.definition.Name) {
await del(this.tor_config.DataDirectory, { force: true });
}
/**
* An event that fires when the process has closed.
*
* @event TorProcess#process_exit
* @type {number}
* @param {number} code - The exit code from the process.
*/
this.emit('process_exit', code);
}));
create(callback) {
async.auto({
dnsPort: (callback) => getPort().then(port => callback(null, port)),
socksPort: (callback) => getPort().then(port => callback(null, port)),
controlPort: (callback) => getPort().then(port => callback(null, port)),
configPath: ['dnsPort', 'socksPort', 'controlPort', (context, callback) => {
let options = {
DNSPort: `127.0.0.1:${context.dnsPort}`,
SocksPort: `127.0.0.1:${context.socksPort}`,
ControlPort: `127.0.0.1:${context.controlPort}`,
HashedControlPassword: shell.exec(`${this.tor_path} --hash-password "${this.control_password}"`, { async: false, silent: true }).stdout.trim()
};
let config = _.extend(_.extend({}, this.tor_config), options);
let text = Object.keys(config).map((key) => `${key} ${config[key]}`).join("\n");
temp.open('tor-router', (err, info) => {
if (err) return callback(err);
tor.stderr.on('data', (data) => {
let error_message = Buffer.from(data).toString('utf8');
this.emit('error', new Error(error_message));
});
fs.writeFile(info.path, text, (err) => {
callback(err, info.path);
});
});
}]
}, (error, context) => {
if (error)
return callback && callback(error);
this.once('ready', () => {
this._ready = true;
this._dns_port = context.dnsPort;
this._socks_port = context.socksPort;
this._control_port = context.controlPort;
this.logger.info(`[tor-${this.instance_name}]: tor is ready`);
});
let tor = spawn(this.tor_path, ['-f', context.configPath], {
stdio: ['ignore', 'pipe', 'pipe'],
detached: false
});
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);
tor.on('close', (code) => {
this.emit('process_exit', code);
if (this.definition && !this.definition.Name) {
del.sync(this.tor_config.DataDirectory, { force: true });
}
});
tor.stderr.on('data', (data) => {
let error_message = Buffer.from(data).toString('utf8');
this.emit('error', new Error(error_message));
});
this.once('ready', () => {
this.ready = true;
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));
this.controller.on('ready', () => {
this.logger.debug(`[tor-${this.instance_name}]: connected via the tor control protocol`);
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 protocol`);
/**
* Is true when the connected via the control protcol
* @type {boolean}
*/
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');
}
});
});
} 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');
let msg = text.indexOf('] ') !== -1 ? text.split('] ').pop() : null;
if (text.indexOf('Bootstrapped 100%: Done') !== -1){
this.bootstrapped = true;
/**
* An event that fires when the Tor process is fully bootstrapped (and ready for traffic).
*
* @event TorProcess#ready
*/
this.emit('ready');
}
tor.stdout.on('data', (data) => {
let text = Buffer.from(data).toString('utf8');
let msg = text.split('] ').pop();
if (text.indexOf('Bootstrapped 100%: Done') !== -1){
this.bootstrapped = true;
this.emit('ready');
}
if (text.indexOf('Opening Control listener on') !== -1) {
this.control_port_listening = true;
/**
* An event that fires when the Tor process has started listening for control interface traffic.
*
* @event TorProcess#control_listen
*/
this.emit('control_listen');
}
if (text.indexOf('Opening Control listener on') !== -1) {
this.control_port_listening = true;
this.emit('control_listen');
}
if (text.indexOf('Opening Socks listener on') !== -1) {
this.socks_port_listening = true;
/**
* An event that fires when the Tor process has started listening for SOCKS5 traffic.
*
* @event TorProcess#socks_listen
*/
this.emit('socks_listen');
}
if (text.indexOf('Opening Socks listener on') !== -1) {
this.socks_port_listening = true;
this.emit('socks_listen');
}
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.emit('dns_listen');
}
if (text.indexOf('Opening DNS listener on') !== -1) {
this.dns_port_listening = true;
this.emit('dns_listen');
}
if (text.indexOf('[err]') !== -1) {
/**
* 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}
* @returns {Error}
*/
this.emit('error', new Error(msg));
this.logger.error(`[tor-${this.instance_name}]: ${text}`);
}
if (text.indexOf('[err]') !== -1) {
this.emit('error', new Error(msg));
this.logger.error(`[tor-${this.instance_name}]: ${msg}`);
}
else if (text.indexOf('[notice]') !== -1) {
this.logger.debug(`[tor-${this.instance_name}]: ${msg}`);
}
else if (text.indexOf('[notice]') !== -1) {
this.logger.debug(`[tor-${this.instance_name}]: ${msg}`);
}
else if (text.indexOf('[warn]') !== -1) {
this.logger.warn(`[tor-${this.instance_name}]: ${msg}`);
}
else if (text.indexOf('[warn]') !== -1) {
this.logger.warn(`[tor-${this.instance_name}]: ${msg}`);
}
});
this.process = tor;
callback && callback(null, tor);
});
this.process = tor;
return tor;
}
};
/**
* Module that contains the {@link TorProcess} class.
* @module tor-router/TorProcess
* @see TorProcess
*/
module.exports = TorProcess;

View file

@ -1,31 +1,44 @@
// 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,
"controlPort": 9077,
"parentDataDirectory": temp.mkdirSync(),
"socksHost": null,
"dnsHost": null,
"httpHost": null,
"proxyByName": false,
"denyUnidentifedUsers": false,
"socksPort": null,
"dnsPort": null,
"httpPort": null,
"logLevel": "info",
"loadBalanceMethod": "round_robin",
"torConfig": {
"Log": "notice stdout",
"NewCircuitPeriod": "10"
},
"torPath": require('@deadcanaries/granax').tor(require('os').platform()),
"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 */
switch (platform) {
case 'win32':
torpath = path.join(BIN_PATH, 'Browser', 'TorBrowser', 'Tor', 'tor.exe');
break;
case 'darwin':
torpath = path.join(BIN_PATH, '.tbb.app', 'Contents', 'Resources',
'TorBrowser', 'Tor', 'tor');
break;
case 'android':
case 'linux':
torpath = path.join(BIN_PATH, 'tor-browser_en-US', 'Browser', 'TorBrowser', 'Tor', 'tor');
break;
default:
throw new Error(`Unsupported platform "${platform}"`);
}
return torpath;
})(),
"instances": null,
"dns": {
"timeout": 10000,
"options": {}
"options": {},
"timeout": null
},
"granaxOptions": null
};

View file

@ -1,12 +0,0 @@
/**
* This module cotains the default ports the application will bind to.
* @module tor-router/default_ports
*/
module.exports = Object.freeze({
socks: 9050,
http: 9080,
dns: 9053,
control: 9077,
controlWs: 9078,
default_host: null
});

View file

@ -1,12 +1,8 @@
/**
* 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,266 +0,0 @@
const fs = require('fs');
const { Provider } = require('nconf');
const yargs = require('yargs');
const winston = require('winston')
const Promise = require('bluebird');
const { ControlServer } = require('./');
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 : ''), 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');
let http_host = typeof(nconf.get('httpHost')) !== 'boolean' ? extractHost(nconf.get('httpHost')) : nconf.get('httpHost');
let control_host = typeof(nconf.get('controlHost')) !== 'boolean' ? extractHost(nconf.get('controlHost')) : nconf.get('controlHost');
let control_host_ws = typeof(nconf.get('websocketControlHost')) !== 'boolean' ? extractHost(nconf.get('websocketControlHost')) : nconf.get('websocketControlHost');
if (nconf.get('proxyByName') && nconf.get('proxyByName') === true)
nconf.set('proxyByName', 'individual');
if (typeof(control_host) === 'boolean') {
control_host = extractHost(9077);
nconf.set('controlHost', assembleHost(control_host));
}
if (typeof(control_host_ws) === 'boolean') {
control_host_ws = extractHost(9078);
nconf.set('websocketControlPort', assembleHost(control_host_ws));
}
let control = new ControlServer(nconf, logger);
try {
await control.listenTcp(control_host.port, control_host.hostname);
if (control_host_ws) {
control.listenWs(control_host_ws.port, control_host_ws.hostname);
}
if (socks_host) {
if (typeof(socks_host) === 'boolean') {
socks_host = extractHost(default_ports.socks);
nconf.set('socksHost', assembleHost(socks_host));
}
control.createSOCKSServer(socks_host.port, socks_host.hostname);
}
if (http_host) {
if (typeof(http_host) === 'boolean') {
http_host = extractHost(default_ports.http);
nconf.set('httpHost', assembleHost(http_host));
}
control.createHTTPServer(http_host.port, http_host.hostname);
}
if (dns_host) {
if (typeof(dns_host) === 'boolean') {
dns_host = extractHost(default_ports.dns);
nconf.set('dnsPort', assembleHost(dns_host));
}
control.createDNSServer(dns_host.port, dns_host.hostname);
}
if (instances) {
logger.info(`[tor]: starting ${Array.isArray(instances) ? instances.length : instances} tor instance(s)...`);
await control.tor_pool.create(instances);
logger.info('[tor]: tor started');
}
} catch (error) {
logger.error(`[global]: error starting application: ${error.stack}`);
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.tor_pool.exit();
} catch (exitError) {
logger.error(`[global]: error closing tor instances: ${exitError.message}`);
thereWasAnExitError = true;
}
if (error instanceof Error) {
logger.error(`[global]: ${error.stack || 'An unknown error occured'}`);
} else {
error = 0;
}
process.exit(Number(Boolean(error || thereWasAnExitError)));
});
process.title = 'tor-router';
process.on('SIGHUP', () => {
control.tor_pool.new_identites();
});
process.on('exit', cleanUp);
process.on('SIGINT', cleanUp);
process.on('SIGTERM', cleanUp);
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)
.usage('Usage: tor-router [arguments]')
.options({
f: {
alias: 'config',
describe: 'Path to a config file to use',
demand: false
},
c: {
alias: 'controlHost',
describe: `Host the control server will bind to, handling TCP connections [default: ${default_ports.default_host}:9077]`,
demand: false
// ,default: 9077
},
w: {
alias: 'websocketControlHost',
describe: 'Host the control server will bind to, handling WebSocket connections. If no hostname is specified will bind to localhost',
demand: false
},
j: {
alias: 'instances',
describe: 'Number of instances using the default config',
demand: false
// ,default: 1
},
s: {
alias: 'socksHost',
describe: 'Host the SOCKS5 Proxy server will bind to. If no hostname is specified will bind to localhost',
demand: false,
// ,default: default_ports.socks
},
d: {
alias: 'dnsHost',
describe: 'Host the DNS Proxy server will bind to. If no hostname is specified will bind to localhost',
demand: false
},
h: {
alias: 'httpHost',
describe: 'Host the HTTP Proxy server will bind to. If no hostname is specified will bind to localhost',
demand: false
},
l: {
alias: 'logLevel',
describe: 'Controls the verbosity of console log output. Default level is "info". Set to "verbose" to see all network traffic logged or "null" to disable logging completely [default: info]',
demand: false
// ,default: "info"
},
p: {
alias: 'parentDataDirectory',
describe: 'Parent directory that will contain the data directories for the instances',
demand: false
},
b: {
alias: "loadBalanceMethod",
describe: 'Method that will be used to sort the instances between each request. Currently supports "round_robin" and "weighted". [default: round_robin]',
demand: false
},
t: {
alias: "torPath",
describe: "Provide the path for the Tor executable that will be used",
demand: false
},
n: {
alias: 'proxyByName',
describe: 'Allow connecting to a specific instance identified by the username field when connecting to a proxy',
demand: false
}
});
require(`${__dirname}/../src/nconf_load_env.js`)(nconf);
nconf
.argv(argv_config);
let nconf_config = nconf.get('config');
if (nconf_config) {
if (!require('fs').existsSync(nconf_config)) {
console.error(`[global]: config file "${nconf_config}" does not exist. exiting.`);
process.exit(1);
}
nconf.file(nconf_config);
} else {
nconf.use('memory');
}
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(),
silent: (logLevel === 'null'),
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,63 +1,41 @@
/**
* 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"
];
const env_whitelist = [ "CONTROL_PORT",
"TOR_PATH",
"INSTANCES",
"SOCKS_PORT",
"DNS_PORT",
"HTTP_PORT",
"LOG_LEVEL",
'PARENT_DATA_DIRECTORIES',
'LOAD_BALANCE_METHOD',
"controlPort",
"torPath",
"instances",
"socksPort",
"dnsPort",
"httpPort",
"logLevel",
'parentDataDirectories',
'loadBalanceMethod'
];
/**
* 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) {
module.exports = (nconf) => {
return nconf
.env({
whitelist: env_whitelist.concat(env_whitelist.map(env_to_config)),
whitelist: env_whitelist,
parseValues: true,
transform: (obj) => {
if (env_whitelist.includes(obj.key)) {
if (obj.key.indexOf('_') !== -1) {
obj.key = env_to_config(obj.key);
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('');
}
}
return obj;
}
});
};
/**
* This module returns a function
* @module tor-router/nconf_load_env
*/
module.exports = setup_nconf_env;
};

View file

@ -1,12 +0,0 @@
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(),
silent: true,
transports: [ new (winston.transports.Console)({ silent: true }) ]
});

View file

@ -1,62 +0,0 @@
const _ = require('lodash');
const { assert } = require('chai');
const { Provider } = require('nconf');
const nconf = new Provider();
const getPort = require('get-port');
nconf.use('memory');
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(nconf);
let controlPort;
describe('ControlServer', function () {
describe('#listen(port)', function () {
it('should bind to a given port', async function () {
controlPort = await getPort();
await controlServer.listen(controlPort);
});
});
describe('#createTorPool(options)', function () {
it('should create a TorPool with a given configuration', function () {
let torPool = controlServer.createTorPool({ ProtocolWarnings: 1 });
assert.instanceOf(controlServer.tor_pool, TorPool);
assert.equal(1, torPool.default_tor_config.ProtocolWarnings);
});
});
describe('#createSOCKSServer(port)', function () {
it('should create a SOCKS Server', async function () {
let port = await getPort();
controlServer.createSOCKSServer(port);
assert.instanceOf(controlServer.socksServer, SOCKSServer);
});
});
describe('#createDNSServer(port)', function () {
it('should create a DNS Server', async function () {
let port = await getPort();
controlServer.createDNSServer(port);
assert.instanceOf(controlServer.dnsServer, DNSServer);
});
});
describe('#createHTTPServer(port)', function () {
it('should create a HTTP Server', async function () {
let port = await getPort();
controlServer.createHTTPServer(port);
assert.instanceOf(controlServer.httpServer, HTTPServer);
});
});
describe('#close()', function () {
it('should close the RPC Server', function () {
controlServer.close();
});
});
after('shutdown tor pool', async function () {
await controlServer.tor_pool.exit();
});
});

View file

@ -1,87 +0,0 @@
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, TorProcess } = require('../');
const { WAIT_FOR_CREATE } = require('./constants');
nconf.use('memory');
require(`${__dirname}/../src/nconf_load_env.js`)(nconf);
nconf.defaults(require(`${__dirname}/../src/default_config.js`));
let dnsServerTorPool;
let dnsServer;
describe('DNSServer', function () {
dnsServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
dnsServer = new DNSServer(dnsServerTorPool, {}, nconf.get('dns:timeout'));
let dnsPort;
before('start up server', async function (){
this.timeout(WAIT_FOR_CREATE);
await dnsServerTorPool.create(1);
dnsPort = await getPort();
await dnsServer.listen(dnsPort);
});
describe('#handle_dns_request(req, res)', function () {
it('should service a request for example.com', function (done) {
this.timeout(10000);
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.on('message', function () {
done();
});
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 () {
dnsServer.close();
});
after('shutdown tor pool', async function () {
await dnsServerTorPool.exit();
});
});

View file

@ -1,412 +0,0 @@
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, TorProcess } = require('../');
const { WAIT_FOR_CREATE, PAGE_LOAD_TIME, RETRY_DELAY, RETRY_STRATEGY, MAX_ATTEMPTS, SHUTDOWN_DELAY, SHUTDOWN_TIMEOUT } = require('./constants');
const request = require('requestretry').defaults({
promiseFactory: ((resolver) => new Promise(resolver)),
maxAttempts: MAX_ATTEMPTS,
retryStrategy: RETRY_STRATEGY,
retryDelay: RETRY_DELAY,
timeout: PAGE_LOAD_TIME
});
nconf.use('memory');
require(`${__dirname}/../src/nconf_load_env.js`)(nconf);
nconf.defaults(require(`${__dirname}/../src/default_config.js`));
describe('HTTPServer', function () {
describe('#handle_http_connections(req, res)', function () {
let httpServerTorPool;
let httpServer;
let httpPort;
before('start up server', async function (){
httpServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
httpServer = new HTTPServer(httpServerTorPool, null, false);
this.timeout(WAIT_FOR_CREATE);
await httpServerTorPool.create(1);
httpPort = await getPort();
await httpServer.listen(httpPort);
});
it('should service a request for example.com', async function () {
this.timeout(PAGE_LOAD_TIME);
await request({
url: 'http://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: 'http://example.com',
proxy: `http://127.0.0.1:${httpPort}`
})
.catch((err) => {
})
});
after('shutdown server and shutdown tor pool', function (done) {
this.timeout(SHUTDOWN_TIMEOUT);
setTimeout(async () => {
httpServer.close();
await httpServerTorPool.exit();
done();
}, SHUTDOWN_DELAY);
});
});
describe('#handle_connect_connections(req, inbound_socket, head)', function () {
let httpServerTorPool;
let httpServer;
let httpPort;
before('start up server', async function (){
httpServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
httpServer = new HTTPServer(httpServerTorPool, null, false);
this.timeout(WAIT_FOR_CREATE);
await httpServerTorPool.create(1);
httpPort = await getPort();
await httpServer.listen(httpPort);
});
it('should service a request for example.com', async function () {
this.timeout(PAGE_LOAD_TIME);
await request({
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 and shutdown tor pool', function (done) {
this.timeout(SHUTDOWN_TIMEOUT);
setTimeout(async () => {
httpServer.close();
await httpServerTorPool.exit();
done();
}, SHUTDOWN_DELAY);
});
});
describe('#authenticate_user_http(req, res)', function () {
let httpServerTorPool;
let httpServer;
let httpPort;
let instance_def = {
Name: 'instance-3',
Group: 'foo'
};
before('start up server', async function (){
httpServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
httpServer = new HTTPServer(httpServerTorPool, null, { deny_unidentified_users: true, mode: 'individual' });
this.timeout(WAIT_FOR_CREATE * 3);
await httpServerTorPool.create(2);
await httpServerTorPool.add(instance_def);
httpPort = await getPort();
await httpServer.listen(httpPort);
});
it(`should service a request for example.com through the instance named ${instance_def.Name}`, function (done) {
this.timeout(PAGE_LOAD_TIME);
let connectionHandler = (instance, source) => {
assert.equal(instance.instance_name, instance_def.Name);
assert.isTrue(source.by_name);
httpServer.removeAllListeners('instance_connection');;
done();
};
httpServer.on('instance_connection', connectionHandler);
request({
url: 'http://example.com',
proxy: `http://${instance_def.Name}:@127.0.0.1:${httpPort}`,
timeout: PAGE_LOAD_TIME
})
.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 * 4) + (WAIT_FOR_CREATE));
await httpServerTorPool.add([
{
Name: 'instance-4',
Group: 'foo'
}
]);
})()
.then(async () => {
httpServer.proxy_by_name.mode = "group";
let names_requested = [];
let connectionHandler = (instance, source) => {
names_requested.push(instance.instance_name);
if (names_requested.length === httpServerTorPool.instances.length) {
names_requested = _.uniq(names_requested).sort();
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');
done();
}
};
httpServer.on('instance_connection', connectionHandler);
let i = 0;
while (i < httpServerTorPool.instances.length) {
await request({
url: 'http://example.com',
proxy: `http://foo:@127.0.0.1:${httpPort}`
});
i++;
}
})
.then(async () => {
await httpServerTorPool.remove_by_name('instance-4');
httpServer.proxy_by_name.mode = "individual";
})
.catch(done);
});
it(`shouldn't be able to send a request without a username`, async function() {
let f = () => {};
try {
let res = await request({
url: 'http://example.com',
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 {
assert.throws(f, "407", "Did not return 407 status code");
}
});
it(`shouldn't be able to send a request with an incorrect username`, async function() {
let f = () => {};
try {
let res = await request({
url: 'http://example.com',
proxy: `http://blah-blah-blah:@127.0.0.1:${httpPort}`,
timeout: PAGE_LOAD_TIME,
fullResponse: true
});
if (res.statusCode === 407)
throw new Error('407');
} catch (error) {
f = () => { throw error };
} finally {
assert.throws(f, "407", "Did not return 407 status code");
}
});
after('shutdown server and shutdown tor pool', function (done) {
this.timeout(SHUTDOWN_TIMEOUT);
setTimeout(async () => {
httpServer.close();
await httpServerTorPool.exit();
done();
}, SHUTDOWN_DELAY);
});
});
describe('#authenticate_user_connect(req, socket)', function () {
let httpServerTorPool;
let httpServer;
let httpPort;
let instance_def = {
Name: 'instance-3',
Group: 'foo'
};
before('start up server', async function (){
httpServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
httpServer = new HTTPServer(httpServerTorPool, null, { deny_unidentified_users: true, mode: "individual" });
this.timeout(WAIT_FOR_CREATE * 3);
await httpServerTorPool.create(2);
await httpServerTorPool.add(instance_def);
httpPort = await getPort();
await httpServer.listen(httpPort);
});
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();
httpServer.removeAllListeners('instance_connection');
done();
};
httpServer.on('instance_connection', connectionHandler);
req = request({
url: 'https://example.com',
proxy: `http://${instance_def.Name}:@127.0.0.1:${httpPort}`
})
.catch(done);
});
it(`four requests made to example.com through the group named "foo" should come from instances in the "foo" group`, function (done) {
(async () => {
this.timeout((PAGE_LOAD_TIME * 4) + (WAIT_FOR_CREATE));
await httpServerTorPool.add([
{
Name: 'instance-4',
Group: 'foo'
}
]);
httpServer.proxy_by_name.mode = "group";
})()
.then(async () => {
let names_requested = [];
let connectionHandler = (instance, source) => {
names_requested.push(instance.instance_name);
if (names_requested.length === httpServerTorPool.instances.length) {
names_requested = _.uniq(names_requested).sort();
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');
done();
}
};
httpServer.on('instance_connection', connectionHandler);
let i = 0;
while (i < httpServerTorPool.instances.length) {
await request({
url: 'https://example.com',
proxy: `http://foo:@127.0.0.1:${httpPort}`
});
i++;
}
})
.then(async () => {
await httpServerTorPool.remove_by_name('instance-4');
httpServer.proxy_by_name.mode = "individual";
})
.catch(done);
});
const regular_request = require('request-promise');
it(`shouldn't be able to send a request without a username`, async function() {
let f = () => {};
try {
await regular_request({
url: 'https://example.com',
proxy: `http://127.0.0.1:${httpPort}`,
timeout: PAGE_LOAD_TIME
});
} catch (error) {
f = () => { throw error };
} finally {
assert.throws(f, "statusCode=407", "Did not return 407 status code");
}
});
it(`shouldn't be able to send a request with an incorrect username`, async function() {
let f = () => {};
try {
await regular_request({
url: 'https://example.com',
proxy: `http://blah-blah-blah:@127.0.0.1:${httpPort}`,
timeout: PAGE_LOAD_TIME,
retryStrategy: request.RetryStrategies.NetworkError
});
} catch (error) {
f = () => { throw error };
} finally {
assert.throws(f, "statusCode=407", "Did not hang up");
}
});
after('shutdown server and shutdown tor pool', function (done) {
this.timeout(SHUTDOWN_TIMEOUT);
setTimeout(async () => {
httpServer.close();
await httpServerTorPool.exit();
done();
}, SHUTDOWN_DELAY);
});
});
});

View file

@ -1,531 +0,0 @@
const _ = require('lodash');
const assert = require('chai').assert;
const Promise = require('bluebird');
const { Provider } = require('nconf');
const nconf = new Provider();
const { Client, JSONSerializer, TCPTransport } = require('multi-rpc');
const getPort = require('get-port');
const temp = require('temp');
const fs = require('fs');
nconf.use('memory');
require(`${__dirname}/../src/nconf_load_env.js`)(nconf);
nconf.defaults(require(`${__dirname}/../src/default_config.js`));
const { ControlServer } = require('../');
const { WAIT_FOR_CREATE } = require('./constants');
Promise.promisifyAll(fs);
Promise.promisifyAll(temp);
let rpcControlServer = new ControlServer(nconf);
let rpcControlPort;
let rpcClient;
describe('ControlServer - RPC Interface', function () {
before('setup control server', async function () {
rpcControlPort = await getPort();
await rpcControlServer.listen(rpcControlPort);
rpcClient = new Client(new TCPTransport(new JSONSerializer(), rpcControlPort));
});
describe('#createInstances(number_of_instances)', function () {
this.timeout(WAIT_FOR_CREATE*2);
it('should create an instance', async function () {
await rpcClient.invoke('createInstances', [{ Name: 'instance-1', Group: "foo" }]);
});
});
describe('#queryInstanceNames()', function () {
it("should have an instance named \"instance-1\"", async function () {
let instances = await rpcClient.invoke('queryInstanceNames', [ ]);
assert.deepEqual(instances, [ 'instance-1' ]);
});
});
describe('#queryGroupNames()', function () {
it("should have a group named \"foo\"", async function () {
let groups = await rpcClient.invoke('queryGroupNames', [ ]);
assert.deepEqual(groups, [ 'foo' ]);
});
});
describe('#queryInstancesByGroup()', function () {
it("should return an instance named \"instance-1\"", async function () {
let instances = await rpcClient.invoke('queryInstancesByGroup', [ 'foo' ]);
assert.equal(instances.length, 1);
assert.ok(instances[0]);
assert.equal(instances[0].name, 'instance-1');
});
});
describe('#queryInstances()', function () {
this.timeout(3000);
it('should return a list of instances', async function () {
let instances = await rpcClient.invoke('queryInstances', []);
assert.isArray(instances, 'Did not return an array');
assert.isNotEmpty(instances, 'Returned an empty array');
assert.isTrue(instances.every((instance) => ( typeof(instance.name) !== 'undefined' ) && ( instance.name !== null )), 'Objects were not valid');
});
});
describe('#addInstances(definitions)', function () {
this.timeout(WAIT_FOR_CREATE);
it("should add an instance based on a defintion", async function () {
let def = {
Name: 'instance-2',
Group: 'bar'
};
await rpcClient.invoke('addInstances', [ def ]);
});
it("tor pool should now contain and instance that has the same name as the name specified in the defintion", function () {
assert.ok(rpcControlServer.tor_pool.instance_by_name('instance-2'));
});
});
describe('#queryInstanceByName(instance_name)', function () {
this.timeout(3000);
it('should return a single instance by name', async function () {
let instance = await rpcClient.invoke('queryInstanceByName', ['instance-1']);
assert.isOk(instance);
});
});
describe('#queryInstanceAt(index)', function () {
this.timeout(3000);
it('should return a single instance by index', async function () {
let instance = await rpcClient.invoke('queryInstanceAt', [0]);
assert.isOk(instance);
});
});
describe('#addInstanceToGroupByName()', function () {
it(`should add "instance-1" to baz`, async function () {
await rpcClient.invoke('addInstanceToGroupByName', [ 'baz', "instance-1" ]);
});
it('"instance-1" should be added to "baz"', function () {
assert.include(rpcControlServer.tor_pool.instances_by_group('baz').map((i) => i.instance_name), "instance-1");
});
after('remove from group', function () {
rpcControlServer.tor_pool.groups['baz'].remove_by_name('instance-1');
});
});
describe('#addInstanceToGroupAt()', function () {
it(`should add "instance-1" to baz`, async function () {
await rpcClient.invoke('addInstanceToGroupAt', [ 'baz', 0 ]);
});
it('"instance-1" should be added to "baz"', function () {
assert.include(rpcControlServer.tor_pool.instances_by_group('baz').map((i) => i.instance_name), "instance-1");
});
after('remove from group', function () {
rpcControlServer.tor_pool.groups['baz'].remove_by_name('instance-1');
});
});
describe('#removeInstanceFromGroupByName()', function () {
before('add to group', function () {
rpcControlServer.tor_pool.groups['baz'].add_by_name('instance-1');
});
it(`should remove "instance-1" from baz`, async function () {
await rpcClient.invoke('removeInstanceFromGroupByName', [ 'baz', "instance-1" ]);
});
it('"instance-1" should be remove from to "baz"', function () {
assert.notInclude(rpcControlServer.tor_pool.group_names, "baz");
});
});
describe('#removeInstanceFromGroupAt()', function () {
before('add to group', function () {
rpcControlServer.tor_pool.groups['baz'].add_by_name('instance-1');
});
it(`should remove "instance-1" from baz`, async function () {
await rpcClient.invoke('removeInstanceFromGroupAt', [ 'baz', 0 ]);
});
it('"instance-1" should be remove from to "baz"', function () {
assert.notInclude(rpcControlServer.tor_pool.group_names, "baz");
});
});
describe('#newIdentites()', function () {
this.timeout(3000);
it('should request new identities for all instances', async function () {
await rpcClient.invoke('newIdentites', []);
});
});
describe('#newIdentityByName(instance_name)', function () {
this.timeout(3000);
it('should request new identities for all instances', async function () {
await rpcClient.invoke('newIdentityByName', ['instance-1']);
});
});
describe('#newIdentityAt(index)', function () {
this.timeout(3000);
it('should request new identities for all instances', async function () {
await rpcClient.invoke('newIdentityAt', [0]);
});
});
describe('#newIdentitiesByGroup()', function () {
it(`should get new identites for all instances in group`, async function () {
await rpcClient.invoke('newIdentitiesByGroup', [ 'foo' ]);
});
});
describe("#setTorConfig(config_object)", function () {
this.timeout(3000);
it('should set several config variables on all instances', async function () {
await rpcClient.invoke('setTorConfig', [ { TestSocks: 1, ProtocolWarnings: 1 } ]);
});
it('all instances should have the modified variables', async function() {
await Promise.all(rpcControlServer.tor_pool.instances.map(async (instance) => {
let var1 = await instance.get_config('TestSocks');
let var2 = await instance.get_config('ProtocolWarnings');
assert.equal(var1, 1);
assert.equal(var2, 1);
}));
});
after('unset config variables', async function () {
await rpcControlServer.tor_pool.set_config_all('TestSocks', 0);
await rpcControlServer.tor_pool.set_config_all('ProtocolWarnings', 0);
});
});
describe('#setConfig(key, value)', function () {
it('should set the default config of new instances', async function () {
this.timeout(3000);
await rpcClient.invoke('setConfig', [ 'foo', 'bar' ]);
});
it('a new instance should be created with the modified property', function () {
assert.equal(nconf.get('foo'), 'bar');;
});
after('remove instance', function () {
nconf.reset();
});
});
describe('#setTorConfigByGroup()', function () {
it(`should set the config value on all instances`, async function () {
await rpcClient.invoke('setTorConfigByGroup', [ 'foo', { 'ProtocolWarnings': 1 } ]);
});
it('all instances should have the config value set', async function () {
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.tor_pool.set_config_by_group('foo', 'ProtocolWarnings', 0);
});
});
describe('#getConfig()', function () {
before('set a property', function () {
nconf.set('foo', 'bar');
});
it('should return the property that was set', async function () {
this.timeout(6000);
let value = await rpcClient.invoke('getConfig', [ 'foo' ]);
assert.equal(value, 'bar');
});
after('unset property', function () {
nconf.reset();
});
});
describe('#saveConfig()', function () {
let file_path;
before('set a property and create temp file', async function () {
file_path = temp.path({ suffix: '.json' });
nconf.remove('memory');
nconf.file({ file: file_path });
nconf.set('foo', 'bar');
});
it('should save the config to the the temp file', async function () {
this.timeout(6000);
await rpcClient.invoke('saveConfig', []);
});
it('the temp file should contain the property', async function () {
let tmp_file = await fs.readFileAsync(file_path, 'utf8');
let tmp_json = JSON.parse(tmp_file);
assert.equal(tmp_json.foo, 'bar');
});
after('unset property and delete file', async function () {
nconf.remove('file');
nconf.use('memory');
nconf.reset();
await fs.unlinkAsync(file_path);
});
});
describe('#loadConfig()', function () {
let file;
before('create temp file with property', async function () {
file = await temp.openAsync({ suffix: '.json' });
await fs.writeFileAsync(file.fd, JSON.stringify({ foo: 'bar' }));
nconf.remove('memory');
nconf.file({ file: file.path });
});
it('should load the config from the the temp file', async function () {
this.timeout(6000);
await rpcClient.invoke('loadConfig', []);
});
it("the application's config should contain the property", async function () {
assert.equal(nconf.get('foo'), 'bar');
});
after('unset property and delete file', async function () {
nconf.remove('file');
nconf.use('memory');
nconf.reset();
await fs.unlinkAsync(file.path);
});
});
describe('#getLoadBalanceMethod()', function () {
this.timeout(3000);
before(function () {
rpcControlServer.tor_pool.load_balance_method = 'round_robin';
});
it('should return the current load balance method', async function () {
let lb_method = await rpcClient.invoke('getLoadBalanceMethod', []);
assert.equal(lb_method, 'round_robin');
});
});
describe('#setLoadBalanceMethod(load_balance_method)', function () {
this.timeout(3000);
it('should set the load balance method', async function () {
await rpcClient.invoke('setLoadBalanceMethod', ['weighted']);
});
it('the load balance method should be changed', function () {
assert.equal(rpcControlServer.tor_pool.load_balance_method, 'weighted');
});
after(function () {
rpcControlServer.tor_pool.load_balance_method = 'round_robin';
});
});
describe("#getInstanceConfigByName(instance_name)", function () {
this.timeout(3000);
before('set config property', async function () {
await rpcControlServer.tor_pool.instance_by_name('instance-1').set_config('TestSocks', 1);
});
it('should retrieve the property from the tor instance', async function () {
let values = await rpcClient.invoke('getInstanceConfigByName', ['instance-1', "TestSocks"]);
assert.isNotEmpty(values);
assert.equal(values[0], "1");
});
after('unset config property', async function () {
await rpcControlServer.tor_pool.instance_by_name('instance-1').set_config('TestSocks', 0);
});
});
describe("#getInstanceConfigAt(index)", function () {
this.timeout(3000);
before('set config property', async function () {
await rpcControlServer.tor_pool.instance_at(0).set_config('TestSocks', 1);
});
it('should retrieve the property from the tor instance', async function () {
let values = await rpcClient.invoke('getInstanceConfigAt', [0, "TestSocks"]);
assert.isNotEmpty(values);
assert.equal(values[0], "1");
});
after('unset config property', async function () {
await rpcControlServer.tor_pool.instance_at(0).set_config('TestSocks', 0);
});
});
describe("#setInstanceConfigByName(instance_name)", function () {
this.timeout(3000);
before('set config property', async function () {
await rpcControlServer.tor_pool.instance_by_name('instance-1').set_config('TestSocks', 0);
});
it('should set the property for the tor instance', async function () {
await rpcClient.invoke('setInstanceConfigByName', ['instance-1', 'TestSocks', 1]);
});
it('tor instance should have the modified property', async function () {
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.tor_pool.instance_by_name('instance-1').set_config('TestSocks', 0);
});
});
describe("#setInstanceConfigAt(index)", function () {
this.timeout(3000);
before('set config property', async function () {
await rpcControlServer.tor_pool.instance_at(0).set_config('TestSocks', 0);
});
it('should set the property for the tor instance', async function () {
await rpcClient.invoke('setInstanceConfigAt', [0, 'TestSocks', 1]);
});
it('tor instance should have the modified property', async function () {
let value = await rpcControlServer.tor_pool.instance_at(0).get_config('TestSocks');
assert.equal(value, 1);
});
after('unset config property', async function () {
await rpcControlServer.tor_pool.instance_at(0).set_config('TestSocks', 0);
});
});
describe('#signalAllInstances(signal)', function () {
this.timeout(3000);
it('should signal to all interfaces', async function () {
await rpcClient.invoke('signalAllInstances', [ 'DEBUG' ]);
});
});
describe('#signalInstanceAt(signal)', function () {
this.timeout(3000);
it('should signal to all interfaces', async function () {
await rpcClient.invoke('signalInstanceAt', [ 0, 'DEBUG' ]);
});
});
describe('#signalAllInstances(signal)', function () {
this.timeout(3000);
it('should signal to all interfaces', async function () {
await rpcClient.invoke('signalInstanceByName', [ 'instance-1', 'DEBUG' ]);
});
});
describe('#signalInstancesByGroup()', function () {
it(`should get new identites for all instances in group`, async function () {
await rpcClient.invoke('signalInstancesByGroup', [ 'foo', 'DEBUG' ]);
});
});
describe("#nextInstance()", function () {
this.timeout(3000);
let instance_name;
it('should rotate the 0th item in the instances array', async function () {
instance_name = rpcControlServer.tor_pool.instances[0].instance_name;
await rpcClient.invoke('nextInstance', []);
});
it('0th item in the instances array should be different after nextInstance is called', function () {
assert.notEqual(rpcControlServer.tor_pool.instances[0].instance_name, instance_name);
});
});
describe('#nextInstanceByGroup(group)', function () {
before('add "instance-1" to "foo"', function () {
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.tor_pool.groups['foo'][0].instance_name;
await rpcClient.invoke('nextInstanceByGroup', [ 'foo' ]);
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.tor_pool.remove_instance_from_group_by_name('foo', 'instance-2');
})
});
var instance_num1, instance_num2, i_num;
describe('#removeInstanceAt(index)', function () {
this.timeout(10000);
it("should remove an instance at the position specified", async function () {
instance_num1 = rpcControlServer.tor_pool.instances.length;
await rpcClient.invoke('removeInstanceAt', [0]);
});
it('the tor pool should contain one instance fewer', function () {
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.tor_pool.instances.length;
await rpcClient.invoke('removeInstanceByName', [ "instance-1" ]);
});
it('the tor pool should contain one instance fewer', function () {
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.tor_pool.instances.length;
await rpcClient.invoke('closeInstances', [ ]);
});
it('no instances should be present in the pool', function () {
assert.equal(rpcControlServer.tor_pool.instances.length, 0);
});
});
after('shutdown tor pool', async function () {
this.timeout(10000);
await rpcControlServer.tor_pool.exit();
});
});

View file

@ -1,234 +0,0 @@
const { Provider } = require('nconf');
const nconf = new Provider();
const getPort = require('get-port');
const { HttpAgent, auth } = require('socksv5');
const { assert } = require('chai');
const _ = require('lodash');
const SocksProxyAgent = require('socks-proxy-agent');
const { TorPool, SOCKSServer, TorProcess } = require('../');
const { WAIT_FOR_CREATE, PAGE_LOAD_TIME, RETRY_DELAY, RETRY_STRATEGY, MAX_ATTEMPTS, SHUTDOWN_DELAY, SHUTDOWN_TIMEOUT } = require('./constants');
const request = require('requestretry').defaults({
promiseFactory: ((resolver) => new Promise(resolver)),
maxAttempts: MAX_ATTEMPTS,
retryStrategy: RETRY_STRATEGY,
retryDelay: RETRY_DELAY,
timeout: PAGE_LOAD_TIME
});
nconf.use('memory');
require(`${__dirname}/../src/nconf_load_env.js`)(nconf);
nconf.defaults(require(`${__dirname}/../src/default_config.js`));
describe('SOCKSServer', function () {
describe('#handleConnection(socket)', function () {
let socksPort;
let socksServerTorPool;
let socksServer;
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);
socksPort = await getPort();
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() ]
})
});
});
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 HttpAgent({
proxyHost: '127.0.0.1',
proxyPort: socksPort,
localDNS: false,
auths: [ auth.None() ]
})
})
.catch(done)
});
after('shutdown server and shutdown tor pool', function (done) {
this.timeout(SHUTDOWN_TIMEOUT);
setTimeout(async () => {
socksServer.close();
await socksServerTorPool.exit();
done();
}, SHUTDOWN_DELAY);
});
});
describe('#authenticate_user(username, password)', function () {
let socksPort;
let socksServerTorPool;
let socksServer;
let instance_def = {
Name: 'instance-3',
Group: "foo"
};
before('start up server', async function (){
socksServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
socksServer = new SOCKSServer(socksServerTorPool, null, { deny_unidentified_users: true, mode: "individual" });
this.timeout(WAIT_FOR_CREATE * 3);
await socksServerTorPool.create(2);
await socksServerTorPool.add(instance_def);
socksPort = await getPort();
await socksServer.listen(socksPort);
});
it(`should service a request for example.com through ${instance_def.Name}`, function (done) {
this.timeout(PAGE_LOAD_TIME);
let connectionHandler = (instance, source) => {
assert.equal(instance.instance_name, instance_def.Name);
assert.isTrue(source.by_name);
socksServer.removeAllListeners('instance_connection');
done();
};
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") ]
})
})
.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 * 4) + (WAIT_FOR_CREATE));
await socksServerTorPool.add([
{
Name: 'instance-4',
Group: 'foo'
}
]);
})()
.then(async () => {
socksServer.proxy_by_name.mode = "group";
let names_requested = [];
let connectionHandler = (instance, source) => {
names_requested.push(instance.instance_name);
if (names_requested.length === socksServerTorPool.instances.length) {
names_requested = _.uniq(names_requested).sort();
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');
done();
}
};
socksServer.on('instance_connection', connectionHandler);
let i = 0;
while (i < socksServerTorPool.instances.length) {
await request({
url: 'http://example.com',
agent: new HttpAgent({
proxyHost: '127.0.0.1',
proxyPort: socksPort,
localDNS: false,
auths: [ auth.UserPassword('foo', "doesn't mater") ]
})
});
i++;
}
})
.then(async () => {
await socksServerTorPool.remove_by_name('instance-4');
socksServer.proxy_by_name.mode = "individual";
})
.catch(done);
});
const regular_request = require('request-promise');
it(`shouldn't be able to send a request without a username`, async function() {
let f = () => {};
try {
await regular_request({
url: 'http://example.com',
agent: new SocksProxyAgent(`socks5h://127.0.0.1:${socksPort}`),
timeout: PAGE_LOAD_TIME
});
} catch (error) {
f = () => { throw error };
} finally {
assert.throws(f);
}
});
it(`shouldn't be able to send a request with an incorrect username`, async function() {
let f = () => {};
try {
await regular_request({
url: 'http://example.com',
agent: new SocksProxyAgent(`socks5h://blah-blah-blah:@127.0.0.1:${socksPort}`),
timeout: PAGE_LOAD_TIME
});
} catch (error) {
f = () => { throw error };
} finally {
assert.throws(f);
}
});
after('shutdown server and shutdown tor pool', function (done) {
this.timeout(SHUTDOWN_TIMEOUT);
setTimeout(async () => {
socksServer.close();
await socksServerTorPool.exit();
done();
}, SHUTDOWN_DELAY);
});
});
});

View file

@ -1,859 +0,0 @@
const { Provider } = require('nconf');
const nconf = new Provider();
const { assert } = require('chai');
const Promise = require('bluebird');
const _ = require('lodash');
const { TorPool } = require('../');
const { WAIT_FOR_CREATE } = require('./constants');
nconf.use('memory');
require(`${__dirname}/../src/nconf_load_env.js`)(nconf);
nconf.defaults(require(`${__dirname}/../src/default_config.js`));
describe('TorPool', function () {
const torPoolFactory = () => new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
describe('#default_tor_config', function () {
let tor_pool;
let cfg = { "TestSocks": 1, "Log": "notice stdout", "NewCircuitPeriod": "10" };
before('create tor config based on nconf', function () {
nconf.set('torConfig', cfg);
tor_pool = new TorPool(nconf.get('torPath'), (() => nconf.get('torConfig')), nconf.get('parentDataDirectory'), 'round_robin', null);
});
it('the tor config should have the same value as set in nconf', function () {
assert.deepEqual(nconf.get('torConfig'), tor_pool.default_tor_config);
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
before('create tor config based on nconf', function () {
tor_pool = new TorPool(nconf.get('torPath'), cfg, nconf.get('parentDataDirectory'), 'round_robin', null);
});
it('the tor config should have the same value as set', function () {
assert.deepEqual(cfg, tor_pool.default_tor_config);
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
});
describe('#create_instance(instance_defintion)', function () {
let instance_defintion = {
Name: 'instance-1',
Config: {
ProtocolWarnings: 1
}
};
let torPool;
before('create tor pool', () => { torPool = torPoolFactory(); })
it('should create one tor instance based on the provided definition', async function () {
this.timeout(WAIT_FOR_CREATE);
await torPool.create_instance(instance_defintion);
});
it('one instance should exist in the instances collection', function () {
assert.equal(1, torPool.instances.length);
});
it('the created instance should have the defintion properties as the input definition', function () {
assert.deepEqual(instance_defintion, torPool.instances[0].definition);
});
it('the created instance should have the same config properties specified in the definiton', async function () {
let value = await torPool.instances[0].get_config('ProtocolWarnings');
assert.equal(value, instance_defintion.Config.ProtocolWarnings);
});
it('should not be able to create an instance with an existing name', async function () {
this.timeout(WAIT_FOR_CREATE * 2);
let fn = () => {}
try {
await torPool.create_instance({ Name: 'foo' });
await torPool.create_instance({ Name: 'foo' });
}
catch (error) {
fn = () => { throw error };
} finally {
assert.throws(fn);
}
});
after('shutdown tor pool', async function () {
await torPool.exit();
});
});
describe('#add(instance_defintions)', function () {
let instance_defintions = [
{ Name: 'instance-1', Group: [], Config: { ProtocolWarnings: 1} },
{ Name: 'instance-2', Group: [], Config: { ProtocolWarnings: 1 } }
];
let torPool;
before('create tor pool', () => { torPool = torPoolFactory(); })
it('should throw if the "instance_defintions" field is falsy', async function () {
let fn = () => {};
try {
await torPool.add();
} catch (error) {
fn = () => { throw error };
}
finally {
assert.throws(fn);
}
});
it('should create instances from several instance definitions', async function () {
this.timeout(WAIT_FOR_CREATE*2);
await torPool.add(_.cloneDeep(instance_defintions))
});
it('2 instances should exist in the pool', function () {
assert.equal(2, torPool.instances.length);
});
it('the created instances should have the same defintion properties as the input definitions', function () {
let live_instance_definitions = torPool.instances.map((instance) => {
let def_clone = _.cloneDeep(instance.definition);
delete def_clone.Config.DataDirectory;
return def_clone;
}).sort((a,b) => (a.Name > b.Name) ? 1 : ((b.Name > a.Name) ? -1 : 0));
assert.deepEqual(instance_defintions, live_instance_definitions);
});
it('the created instances should have the same config properties specified in the definiton', async function () {
this.timeout(10000);
let values = await Promise.all(torPool.instances.map((instance) => instance.get_config('ProtocolWarnings')));
values = _.flatten(values);
assert.isTrue( values.every((value) => value === "1") );
});
after('shutdown tor pool', async function () {
await torPool.exit();
});
});
describe('#create(instances)', function () {
let torPool;
before('create tor pool', () => {
torPool = torPoolFactory();
torPool.default_tor_config = { TestSocks: 1 };
});
it('should throw if the "number_of_instances" field is falsy', async function () {
let fn = () => {};
try {
await torPool.create();
} catch (error) {
fn = () => { throw error; }
}
finally {
assert.throws(fn);
}
});
it('should create 2 instances with the default config', async function () {
this.timeout(WAIT_FOR_CREATE*2);
await torPool.create(2);
});
it('2 instances should exist in the pool', function () {
assert.equal(2, torPool.instances.length);
});
it('the created instances should have the same config properties specified in the default config', async function () {
this.timeout(10000);
let values = await Promise.all(torPool.instances.map((instance) => instance.get_config('TestSocks')));
values = _.flatten(values);
assert.isTrue( values.every((value) => value === "1") );
});
after('shutdown tor pool', async function () {
torPool.default_tor_config = {};
await torPool.exit();
});
});
describe('#instances_by_group(group)', function () {
let tor_pool;
let instances;
before('create tor pool', async function () {
tor_pool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
instances = (await tor_pool.add([ { Name: 'instance-1', Group: [ "bar", "foo" ] }, { Name: 'instance-2', Group: ["foo", "bar"] } ]));
});
it('should throw if the provided group does not exist', function () {
assert.throws(() => {
tor_pool.instances_by_group('baz');
});
});
it('should return both instances', function () {
assert.deepEqual(tor_pool.instances_by_group('bar'), instances);
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
});
describe('#next()', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE * 3);
await torPool.add([
{
Name: 'instance-1',
Weight: 50,
Group: 'foo'
},
{
Name: 'instance-2',
Weight: 25,
Group: 'bar'
},
{
Name: 'instance-3',
Weight: 2,
Group: 'bar'
}
]);
});
it('result of next should be different if run twice', function () {
let t1 = torPool.next().instance_name;
let t2 = torPool.next().instance_name;
assert.notEqual(t1, t2);
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#next_by_group(group)', function () {
let tor_pool;
before('create tor pool', async function () {
tor_pool = new TorPool(nconf.get('torPath'), (() => nconf.get('torConfig')), nconf.get('parentDataDirectory'), 'round_robin', null);
this.timeout(WAIT_FOR_CREATE * 3);
await tor_pool.add([
{ Group: 'foo' },
{ Group: 'bar' },
{ Group: 'bar' }
]);
});
it('after rotating the first instance in group should be different', function () {
let first_instance_name_before = tor_pool.groups['bar'][0].instance_name;
tor_pool.next_by_group('bar');
let first_instance_name_after = tor_pool.groups['bar'][0].instance_name;
assert.notEqual(first_instance_name_after, first_instance_name_before);
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
});
describe('#instance_by_name(instance_name)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.add([
{
Name: 'instance-1'
}
]);
});
it('should retrieve instance by name', function () {
assert.ok(torPool.instance_by_name('instance-1'));
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#remove_by_name(instance_name)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.add([
{
Name: 'instance-3'
}
]);
});
it('should remove instance by name', async function () {
this.timeout(5000);
await torPool.remove_by_name('instance-3');
assert.notInclude(torPool.instance_names, "instance-3");
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#instance_at(index)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.create(1);
});
it('should retrieve an instance by id', function () {
this.timeout(5000);
assert.ok(torPool.instance_at(0));
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#remove_at(index)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE );
await torPool.create(1);
});
it('should remove an instance by id', async function () {
this.timeout(5000);
await torPool.remove_at(0);
assert.notOk(torPool.instances[0]);
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#new_identites()', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE * 2);
await torPool.create(2);
});
it('should signal to retrieve a new identity to all instances', async function () {
this.timeout(5000);
await torPool.new_identites();
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#new_identites_by_group(group)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE * 3);
await torPool.add([
{ Name: 'instance-1', Group: 'bar' },
{ Name: 'instance-2', Group: 'foo' }
]);
});
it('should signal to retrieve a new identity to all instances', async function () {
this.timeout(5000);
await torPool.new_identites_by_group('bar');
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#new_identity_at(index)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.create(1);
});
it('should signal to retrieve a new identity identified by index', async function () {
this.timeout(5000);
await torPool.new_identity_at(0);
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#new_identity_by_name(instance_name)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.add({ Name: 'instance-1' });
});
it('should signal to retrieve a new identity identified by name', async function () {
this.timeout(5000);
await torPool.new_identity_by_name('instance-1');
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#set_config_all(keyword, value)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE * 2);
await torPool.create(2);
});
it('should set configuration on all active instances', async function () {
this.timeout(5000);
await torPool.set_config_all('TestSocks', 1);
});
it('all instances should contain the same changed configuration', async function () {
this.timeout(5000);
let values = await Promise.all(torPool.instances.map((instance) => instance.get_config('TestSocks')));
values = _.flatten(values);
assert.isTrue( values.every((value) => value === "1") );
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#set_config_by_group(group, keyword, value)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.add([ { Name: 'instance-1', Group: 'foo' }, { Name: 'instance-2', Group: 'bar' } ]);
});
it('should set configuration on all active instances', async function () {
this.timeout(5000);
await torPool.set_config_by_group('bar', 'TestSocks', 1);
});
it('all instances should contain the same changed configuration', async function () {
this.timeout(5000);
let values_from_bar = await Promise.all(torPool.instances_by_group('bar').map((instance) => instance.get_config('TestSocks')));
values_from_bar = _.flatten(values_from_bar);
assert.isTrue( values_from_bar.every((value) => value === "1") );
let values_from_foo = await Promise.all(torPool.instances_by_group('foo').map((instance) => instance.get_config('TestSocks')));
values_from_foo = _.flatten(values_from_foo);
assert.isTrue( values_from_foo.every((value) => value !== "1") );
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#set_config_by_name(name, keyword, value)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.add({ Name: 'instance-1' });
});
it('should set a configuration property of an instance identified by name', async function () {
this.timeout(5000);
await torPool.set_config_by_name('instance-1', 'ProtocolWarnings', 1);
});
it('should have the set value', async function () {
this.timeout(5000);
let value = _.flatten(await torPool.get_config_by_name('instance-1', 'ProtocolWarnings'))[0];
assert.equal(value, '1');
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#get_config_by_name(name, keyword)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.add({ Name: 'instance-1', Config: { ProtocolWarnings: 1 } });
});
it('should get retrieve the configuration of an instance identified by name', async function () {
this.timeout(5000);
let value = _.flatten(await torPool.get_config_by_name('instance-1', 'ProtocolWarnings'))[0];
assert.equal(value, '1');
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#set_config_at(index, keyword, value)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.create(1);
});
it('should set a configuration property of an instance identified by index', async function () {
this.timeout(5000);
await torPool.set_config_at(0, 'ProtocolWarnings', 1);
});
it('should have the set value', async function () {
this.timeout(5000);
let value = _.flatten(await torPool.get_config_at(0, 'ProtocolWarnings'))[0];
assert.equal(value, '1');
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#get_config_at(index, keyword)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.add({ Config: { ProtocolWarnings: 1 } });
});
it('should get retrieve the configuration of an instance identified by name', async function () {
this.timeout(5000);
let value = _.flatten(await torPool.get_config_at(0, 'ProtocolWarnings'))[0];
assert.equal(value, '1');
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#signal_all(signal)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.create(1);
});
it('should send a signal to all instances', async function () {
this.timeout(5000);
await torPool.signal_all('DEBUG');
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#signal_by_name(name, signal)', async function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.create({ Name: 'instance-1' });
});
it('should send a signal to an instance identified by name', async function () {
this.timeout(5000);
await torPool.signal_by_name('instance-1', 'DEBUG');
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#signal_by_group(group, name, signal)', async function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.create([ { "Name": "instance-1", "Group": ["foo"] }, { "Name": "instance-2", "Group": ["foo"] } ]);
});
it('should send a signal to a group of instances', async function () {
this.timeout(5000);
await torPool.signal_by_group('foo', 'DEBUG');
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#signal_at(index, signal)', function () {
let torPool;
before('create tor pool', async function () {
torPool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
await torPool.create(1);
});
it('should send a signal to an instance identified by index', async function () {
this.timeout(5000);
await torPool.signal_at(0, 'DEBUG');
});
after('shutdown tor pool', async function () { await torPool.exit(); });
});
describe('#group_names', function () {
let tor_pool;
before('create tor pool', async function () {
tor_pool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE * 2);
await tor_pool.add([
{ Name: 'instance-1', Group: [ "foo", "bar" ] },
{ Name: 'instance-2', Group: "baz" }
]);
});
it('the pool should contain three groups, bar, baz and foo', function () {
assert.deepEqual(tor_pool.group_names, (new Set([ "bar", "baz", "foo" ])));
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
});
describe('#add_instance_to_group(group, instance)', function () {
let tor_pool;
let instance;
before('create tor pool', async function () {
tor_pool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
instance = (await tor_pool.create(1))[0];
});
it('should add the instance to the group successfully', function () {
tor_pool.add_instance_to_group("foo", instance);
});
it('the instance should be added to the group', function () {
assert.deepEqual(instance.instance_group, ["foo"]);
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
});
describe('#add_instance_to_group_by_name(group, instance_name)', function () {
let tor_pool;
let instance;
before('create tor pool', async function () {
tor_pool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
instance = (await tor_pool.add({ Name: 'instance-1' }))[0];
});
it('should add the instance to the group successfully', function () {
tor_pool.add_instance_to_group_by_name("foo", instance.definition.Name);
});
it('the instance should be added to the group', function () {
assert.deepEqual(instance.instance_group, ["foo"]);
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
});
describe('#add_instance_to_group_at(group, instance_index)', function () {
let tor_pool;
let instance;
before('create tor pool', async function () {
tor_pool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
instance = (await tor_pool.create(1))[0];
});
it('should add the instance to the group successfully', function () {
tor_pool.add_instance_to_group_at("foo", 0);
});
it('the instance should be added to the group', function () {
assert.deepEqual(instance.instance_group, ["foo"]);
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
});
describe('#remove_instance_from_group(group, instance)', function () {
let tor_pool;
let instance;
before('create tor pool', async function () {
tor_pool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
instance = (await tor_pool.add({ Group: "foo" }))[0];
});
it('should remove the instance from the group successfully', function () {
tor_pool.remove_instance_from_group("foo", instance);
});
it('the instance should be no longer be in the group', function () {
assert.notInclude(instance.instance_group, "foo");
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
});
describe('#remove_instance_from_group_by_name(group, instance_name)', function () {
let tor_pool;
let instance;
before('create tor pool', async function () {
tor_pool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
instance = (await tor_pool.add({ Name: 'instance-1', Group: "foo" }))[0];
});
it('should remove the instance from the group successfully', function () {
tor_pool.remove_instance_from_group_by_name("foo", instance.definition.Name);
});
it('the instance should no longer be in the group', function () {
assert.notInclude(instance.instance_group, "foo");
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
});
describe('#remove_instance_from_group_at(group, instance_index)', function () {
let tor_pool;
let instance;
before('create tor pool', async function () {
tor_pool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE);
instance = (await tor_pool.add({ Group: "foo" }))[0];
});
it('should remove the instance from the group successfully', function () {
tor_pool.remove_instance_from_group_at("foo", 0);
});
it('the instance should no longer be in the group', function () {
assert.notInclude(instance.instance_group, "foo");
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
});
describe('#groups', function () {
let tor_pool;
let instances;
let get_instance_names = (group_name) => {
let instances = [];
let group = tor_pool.groups[group_name];
for (let i = 0; i < group.length; i++)
instances.push(group[i]);
return instances.map((i) => i.instance_name).sort();
};
before('create tor pool', async function () {
tor_pool = torPoolFactory();
this.timeout(WAIT_FOR_CREATE * 3);
instances = (await tor_pool.add([
{ Group: ["foo", "flob"], Name: 'instance-1' },
{ Group: ["bar", "baz"], Name: 'instance-2' },
{ Group: ["flob"], Name: 'instance-3' }
]));
});
it('should contain three groups, bar, baz and foo', function () {
assert.deepEqual(Array.from(tor_pool.group_names).sort(), [ "bar", "baz", "flob", "foo" ]);
});
it('#[Number] - the 1st element should be "instance-1"', function () {
assert.equal(tor_pool.groups["foo"][0], instances[0]);
});
it('#length() - group "foo" should contain 1 instance', function () {
assert.equal(tor_pool.groups["foo"].length, 1);
});
it('#add() - adding "instance-1" to "baz" should result in "baz" having "instance-1" and "instance-2"', function () {
tor_pool.groups["baz"].add(instances[0]);
assert.deepEqual(get_instance_names("baz"), [ "instance-1", "instance-2" ] );
});
it('#remove() - removing "instance-1" firom "baz" should result in "baz" having just "instance-2"', function () {
tor_pool.groups["baz"].remove(instances[0]);
assert.deepEqual(get_instance_names("baz"), [ "instance-2" ] );
});
it('#add_by_name() - adding "instance-1" to "baz" should result in "baz" having "instance-1" and "instance-2"', function () {
tor_pool.groups["baz"].add_by_name('instance-1');
assert.deepEqual(get_instance_names("baz"), [ "instance-1", "instance-2" ] );
});
it('#remove_by_name() - removing "instance-1" from "baz" should result in "baz" having just "instance-2"', function () {
tor_pool.groups["baz"].remove_by_name('instance-1');
assert.deepEqual(get_instance_names("baz"), [ "instance-2" ] );
});
it('#remove_at() - removing "instance-1" from "baz" should result in "baz" having just "instance-2"', function () {
tor_pool.groups["baz"].add_by_name('instance-1');
tor_pool.groups["baz"].remove_at(0);
assert.deepEqual(get_instance_names("baz"), [ "instance-1" ] );
});
it('#rotate() - the name of the first instance should change', function () {
let first_instance_name_before = tor_pool.groups["flob"][0].instance_name;
tor_pool.groups["flob"].rotate();
let first_instance_name_after = tor_pool.groups["flob"][0].instance_name;
assert.notEqual(first_instance_name_after, first_instance_name_before);
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
});
});

View file

@ -1,200 +0,0 @@
const { Provider } = require('nconf');
const nconf = new Provider();
const { assert } = require('chai');
const _ = require('lodash');
const { TorProcess } = require('../');
const { WAIT_FOR_CREATE } = require('./constants');
nconf.use('memory');
require(`${__dirname}/../src/nconf_load_env.js`)(nconf);
nconf.defaults(require(`${__dirname}/../src/default_config.js`));
describe('TorProcess', function () {
const tor_factory = (d) => {
let defaults = { Config: { DataDirectory: nconf.get('parentDataDirectory') } };
d = _.extend(_.cloneDeep(defaults), (d || {}));
return new TorProcess(nconf.get('torPath'), d, null);
}
describe('#instance_name', function () {
let def = { Name: 'foo' };
before('create tor process', function () {
tor = tor_factory(def);
});
it('should have the same name as the definition', function () {
assert.equal(tor.instance_name, def.Name);
});
});
describe('#instance_group', function () {
let def = { Group: ['foo'] };
before('create tor process', function () {
tor = tor_factory(def);
});
it('should have the same group as the definition', function () {
assert.deepEqual(tor.instance_group, def.Group);
});
});
describe('#definition', function () {
let def = { Group: ['foo'], Config: {} };
before('create tor process', function () {
tor = tor_factory(def);
});
it('should be the same as the definition', function () {
let tor_def = _.cloneDeep(tor.definition);
assert.deepEqual(tor.definition, def);
});
});
describe('#exit()', function () {
let tor
before('create tor process', async function () {
tor = tor_factory();
this.timeout(WAIT_FOR_CREATE);
await tor.create();
});
it('should exit cleanly', async function () {
await tor.exit();
});
it('the process should be dead', function () {
assert.isTrue(tor.process.killed);
});
});
describe('#create()', function () {
let tor
before('create tor process', function () {
tor = tor_factory();
});
it('should create the child process', async function () {
this.timeout(5000);
await tor.create();
});
it('should signal when it is listening on the control port', function (done) {
this.timeout(5000);
if (tor.control_port_listening)
return done();
tor.once('control_listen', done);
});
it('should signal when connected to the control port', function (done) {
this.timeout(5000);
if (tor.control_port_connected)
return done();
tor.once('controller_ready', done);
});
it('should signal when it is listening on the socks port', function (done) {
this.timeout(5000);
if (tor.socks_port_listening)
return done();
tor.once('socks_listen', done);
});
it('should signal when it is listening on the dns port', function (done) {
this.timeout(5000);
if (tor.dns_port_listening)
return done();
tor.once('dns_listen', done);
});
it('should signal when bootstrapped', function (done) {
this.timeout(WAIT_FOR_CREATE);
tor.once('error', done);
if (tor.bootstrapped)
return done();
tor.once('ready', done);
});
after('exit tor', async function () {
this.timeout(5000);
await tor.exit();
});
});
describe('#set_config(keyword, value)', function () {
let tor
before('create tor process', function (done) {
tor = tor_factory();
this.timeout(WAIT_FOR_CREATE);
tor.once('controller_ready', done);
tor.create().catch(done);
});
it('should set sample configuration option via the control protocol', async function () {
await tor.set_config('ProtocolWarnings', 1);
});
it('should have the value set', async function () {
let value = (await tor.get_config('ProtocolWarnings'))[0];
assert.equal(value, "1");
});
after('exit tor', async function () {
await tor.exit();
});
});
describe('#get_config(keyword, value)', function () {
let tor
before('create tor process', function (done) {
tor = tor_factory({ Config: { ProtocolWarnings: 1 } });
this.timeout(WAIT_FOR_CREATE);
tor.once('controller_ready', done);
tor.create().catch(done);
});
it('should retrieve sample configuration option via the control protocol', async function () {
let value = await tor.get_config('ProtocolWarnings');
assert.equal(value, 1);
});
after('exit tor', async function () {
await tor.exit();
});
});
describe('#new_identity()', function () {
before('create tor process', function (done) {
tor = tor_factory();
this.timeout(WAIT_FOR_CREATE);
tor.once('controller_ready', done);
tor.create().catch(done);
});
it('should use a new identity', async function () {
await tor.new_identity();
});
after('exit tor', async function () {
await tor.exit();
});
});
describe('#signal()', function () {
let tor;
before('create tor process', function (done) {
tor = tor_factory();
this.timeout(WAIT_FOR_CREATE);
tor.once('controller_ready', done);
tor.create().catch(done);
});
it('should send a signal via the control protocol', async function () {
await tor.signal('DEBUG');
});
after('exit tor', async function () {
await tor.exit();
});
});
});

View file

@ -1,9 +0,0 @@
module.exports = {
WAIT_FOR_CREATE: 600000,
PAGE_LOAD_TIME: 75000,
RETRY_DELAY: 5000,
MAX_ATTEMPTS: 15,
RETRY_STRATEGY: require('requestretry').RetryStrategies.HTTPOrNetworkError,
SHUTDOWN_DELAY: 5000,
SHUTDOWN_TIMEOUT: (this.SHUTDOWN_DELAY * 2)
};

View file

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

965
test/test.js Normal file
View file

@ -0,0 +1,965 @@
const SocksAgent = require('socks-proxy-agent');
const request = require('request');
const async = require('async');
const TorRouter = require('../');
const getPort = require('get-port');
const dns = require('native-dns');
const _ = require('lodash');
const assert = require('assert');
const winston = require('winston');
const del = require('del');
const rpc = require('jrpc2');
const fs = require('fs');
var colors = require('mocha/lib/reporters/base').colors;
var nconf = require('nconf');
nconf = require(`${__dirname}/../src/nconf_load_env.js`)(nconf);
nconf.defaults(require(`${__dirname}/../src/default_config.js`));
var logger = winston.createLogger({
level: 'info',
format: winston.format.simple(),
transports: [new (require('winston-null-transport'))() ]
});
const WAIT_FOR_CREATE = 120000;
const PAGE_LOAD_TIME = 30000;
describe('TorProcess', function () {
var tor = new (TorRouter.TorProcess)(nconf.get('torPath'), { DataDirectory: nconf.get('parentDataDirectory'), ProtocolWarnings: 0 }, null, logger);
describe('#create()', function () {
this.timeout(WAIT_FOR_CREATE);
it('should create the child process', function (done) {
tor.create(done);
});
it('should signal when it is listening on the control port', function (done) {
if (tor.control_port_listening)
return done();
tor.once('control_listen', done);
});
it('should signal when connected to the control port', function (done) {
if (tor.control_port_connected)
return done();
tor.once('controller_ready', done);
});
it('should signal when it is listening on the socks port', function (done) {
if (tor.socks_port_listening)
return done();
tor.once('socks_listen', done);
});
it('should signal when it is listening on the dns port', function (done) {
if (tor.dns_port_listening)
return done();
tor.once('dns_listen', done);
});
it('should signal when bootstrapped', function (done) {
tor.once('error', done);
if (tor.bootstrapped)
return done();
tor.once('ready', done);
});
});
describe('#set_config(keyword, value)', function () {
it('should set sample configuration option via the control protocol', function (done) {
tor.set_config('ProtocolWarnings', 1, done);
});
});
describe('#get_config(keyword, value)', function () {
it('should retrieve sample configuration option via the control protocol', function (done) {
tor.get_config('ProtocolWarnings', function (error, value) {
done(error, (value == 1));
});
});
});
describe('#new_identity()', function () {
it('should use a new identity', function (done) {
tor.new_identity(done);
});
});
describe('#signal()', function () {
it('should send a signal via the control protocol', function (done) {
tor.signal('DEBUG', done);
});
});
after('shutdown tor', function (done) {
tor.exit(done);
});
});
var torPool;
describe('TorPool', function () {
torPool = new (TorRouter.TorPool)(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null, logger);
describe('#create_instance(instance_defintion)', function () {
var instance_defintion = {
Name: 'instance-1',
Config: {
ProtocolWarnings: 1
}
};
it('should create one tor instance based on the provided definition', function (done) {
this.timeout(WAIT_FOR_CREATE);
torPool.create_instance(instance_defintion, (err, _instance) => {
done(err);
});
});
it('one instance should exist in the instances collection', function () {
assert.equal(1, torPool.instances.length);
});
it('the created instance should have the defintion properties as the input definition', function () {
assert.deepEqual(instance_defintion, torPool.instances[0].definition);
});
it('the created instance should have the same config properties specified in the definiton', function (done) {
torPool.instances[0].get_config('ProtocolWarnings', (err, v) => {
if (err)
return done(err);
done(null, (v === instance_defintion.Config.ProtocolWarnings));
});
});
after('shutdown tor pool', function (done) {
torPool.exit(done);
});
});
describe('#add(instance_defintions)', function () {
var instance_defintions = [
{ Name: 'instance-1', Config: { ProtocolWarnings: 1} },
{ Name: 'instance-2', Config: { ProtocolWarnings: 1 } }
];
it('should create instances from several instance definitions', function (done) {
this.timeout(WAIT_FOR_CREATE*2);
torPool.add(instance_defintions, function (error) {
done(error)
});
});
it('2 instances should exist in the pool', function () {
assert.equal(2, torPool.instances.length);
});
it('the created instances should have the same defintion properties as the input definitions', function () {
assert.deepEqual(instance_defintions, torPool.instances.map((i) => i.definition));
});
it('the created instances should have the same config properties specified in the definiton', function (done) {
this.timeout(10000);
async.map(torPool.instances, function grabConfig(instance, next) {
instance.get_config('ProtocolWarnings', next);
}, function (err, values) {
if (err) return done(err);
done(null, values.every((v) => v === 1));
});
});
after('shutdown tor pool', function (done) {
torPool.exit(done);
});
});
describe('#create(number_of_instances)', function () {
torPool.default_tor_config = { TestSocks: 1 };
it('should create 2 instances with the default config', function (done) {
this.timeout(WAIT_FOR_CREATE*2);
torPool.create(2, done);
});
it('2 instances should exist in the pool', function () {
assert.equal(2, torPool.instances.length);
});
it('the created instances should have the same config properties specified in the default config', function (done) {
this.timeout(10000);
async.map(torPool.instances, function grabConfig(instance, next) {
instance.get_config('ProtocolWarnings', next);
}, function (err, values) {
if (err) return done(err);
done(null, values.every((v) => v === 1));
});
});
after('shutdown tor pool', function (done) {
torPool.default_tor_config = {};
torPool.exit(done);
});
});
describe('#next()', function () {
before('create tor instances', function (done) {
this.timeout(WAIT_FOR_CREATE * 3);
torPool.add([
{
Name: 'instance-1',
Weight: 50
},
{
Name: 'instance-2',
Weight: 25
},
{
Name: 'instance-3',
Weight: 2
}
], done);
});
it('result of next should be different if run twice', function () {
var t1 = torPool.next().instance_name;
var t2 = torPool.next().instance_name;
assert.notEqual(t1, t2);
});
});
describe('#instance_by_name(instance_name)', function () {
it('should retrieve instance by name', function () {
assert.ok(torPool.instance_by_name('instance-1'));
});
});
describe('#remove_by_name(instance_name)', function () {
this.timeout(5000);
it('should remove instance by name', function (done) {
torPool.remove_by_name('instance-3', done);
});
});
describe('#instance_at(index)', function () {
this.timeout(5000);
it('should retrieve an instance by id', function () {
assert.ok(torPool.instance_at(0));
});
});
describe('#remove_at(index)', function () {
this.timeout(5000);
it('should remove an instance by id', function (done) {
torPool.remove_at(1, done);
});
});
describe('#new_identites()', function () {
this.timeout(5000);
it('should signal to retrieve a new identity to all instances', function (done) {
torPool.new_identites(done);
});
});
describe('#new_identity_at(index)', function () {
this.timeout(5000);
it('should signal to retrieve a new identity identified by index', function (done) {
torPool.new_identity_at(0, done);
});
});
describe('#new_identity_by_name(instance_name)', function () {
this.timeout(5000);
it('should signal to retrieve a new identity identified by name', function (done) {
torPool.new_identity_by_name('instance-1', done);
});
});
describe('#new_ips(index)', function () {
this.timeout(5000);
it('should signal to retrieve a new identity to all instances', function (done) {
torPool.new_ips(done);
});
});
describe('#new_ip_at(instance_name)', function () {
this.timeout(5000);
it('should signal to retrieve a new identity identified by index', function (done) {
torPool.new_ip_at(0, done);
});
});
describe('#set_config_all(keyword, value)', function () {
it('should set configuration on all active instances', function (done) {
this.timeout(5000);
torPool.set_config_all('TestSocks', 1, done);
});
it('all instances should contain the same changed configuration', function (done) {
this.timeout(5000);
async.map(torPool.instances, (instance, next) => {
instance.get_config('TestSocks', next);
}, function (error, results) {
if (error) return done(error);
done(null, results.every((r) => r === 1));
});
});
after('unset config options', function (done) {
torPool.set_config_all('TestSocks', 0, done);
});
});
describe('#set_config_by_name(name, keyword, value)', function () {
this.timeout(5000);
it('should set a configuration property of an instance identified by name', function (done) {
torPool.set_config_by_name('instance-1', 'ProtocolWarnings', 1, done);
});
});
describe('#get_config_by_name(name, keyword)', function () {
this.timeout(5000);
it('should get retrieve the configuration of an instance identified by name', function (done) {
torPool.get_config_by_name('instance-1', 'ProtocolWarnings', (error, value) => {
if (error) return done(error);
done(null, (value === 1));
});
});
});
describe('#set_config_at(index, keyword, value)', function () {
this.timeout(5000);
it('should set a configuration property of an instance identified by index', function (done) {
torPool.set_config_at(0, 'ProtocolWarnings', 0, done);
});
});
describe('#get_config_at(index, keyword)', function () {
this.timeout(5000);
it('should get retrieve the configuration of an instance identified by name', function (done) {
torPool.get_config_at(0, 'ProtocolWarnings', (error, value) => {
if (error) return done(error);
done(null, (value === 0));
});
});
});
describe('#signal_all(signal)', function () {
this.timeout(5000);
it('should send a signal to all instances', function (done) {
torPool.signal_all('DEBUG', done);
});
});
describe('#signal_by_name(name, signal)', function () {
this.timeout(5000);
it('should send a signal to an instance identified by name', function (done) {
torPool.signal_by_name('instance-1', 'DEBUG', done);
});
});
describe('#signal_at(index, signal)', function () {
this.timeout(5000);
it('should send a signal to an instance identified by index', function (done) {
torPool.signal_at(0, 'DEBUG', done);
});
});
after('shutdown tor pool', function (done) {
torPool.exit(done);
});
});
var socksServerTorPool;
var socksServer;
describe('SOCKSServer', function () {
socksServerTorPool = new (TorRouter.TorPool)(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null, logger);
socksServer = new (TorRouter.SOCKSServer)(socksServerTorPool, logger);
var socksPort;
before('start up server', function (done){
this.timeout(WAIT_FOR_CREATE);
async.waterfall([
(callback) => { socksServerTorPool.create(1, callback); },
(callback) => { getPort().then((port) => callback(null, port)); },
(port, callback) => {
socksPort = port;
socksServer.listen(port);
callback();
}
], done);
});
describe('#handleConnection(socket)', function () {
it('should service a request for example.com', function (done) {
this.timeout(PAGE_LOAD_TIME);
var req = request({
url: 'http://example.com',
agent: new SocksAgent(`socks://localhost:${socksPort}`)
});
req.on('error', function (error) {
done(error);
});
req.on('response', function (res) {
done();
})
});
});
after('shutdown tor pool', function (done) {
socksServerTorPool.exit(done);
});
});
var httpServerTorPool;
var httpServer;
describe('HTTPServer', function () {
httpServerTorPool = new (TorRouter.TorPool)(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null, logger);
httpServer = new (TorRouter.HTTPServer)(httpServerTorPool, logger);
var httpPort;
before('start up server', function (done){
this.timeout(WAIT_FOR_CREATE);
async.waterfall([
(callback) => { httpServerTorPool.create(1, callback); },
(callback) => { getPort().then((port) => callback(null, port)); },
(port, callback) => {
httpPort = port;
httpServer.listen(port);
callback();
}
], done);
});
describe('#handle_http_connections(req, res)', function () {
it('should service a request for example.com', function (done) {
this.timeout(PAGE_LOAD_TIME);
var req = request({
url: 'http://example.com',
proxy: `http://localhost:${httpPort}`
});
req.on('error', function (error) {
done(error);
});
req.on('response', function (res) {
done();
})
});
});
describe('#handle_connect_connections(req, inbound_socket, head)', function () {
it('should service a request for example.com', function (done) {
this.timeout(PAGE_LOAD_TIME);
var req = request({
url: 'https://example.com',
proxy: `http://localhost:${httpPort}`
});
req.on('error', function (error) {
done(error);
});
req.on('response', function (res) {
done();
})
});
});
after('shutdown tor pool', function (done) {
httpServerTorPool.exit(done);
});
});
var dnsServerTorPool;
var dnsServer;
describe('DNSServer', function () {
dnsServerTorPool = new (TorRouter.TorPool)(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null, logger);
dnsServer = new (TorRouter.DNSServer)(dnsServerTorPool, {}, 10000, logger);
var dnsPort;
before('start up server', function (done){
this.timeout(WAIT_FOR_CREATE);
async.waterfall([
(callback) => { dnsServerTorPool.create(1, callback); },
(callback) => { getPort().then((port) => callback(null, port)); },
(port, callback) => {
dnsPort = port;
dnsServer.serve(port);
callback();
}
], done);
});
describe('#handle_dns_request(req, res)', function () {
it('should service a request for example.com', function (done) {
this.timeout(10000);
var req = dns.Request({
question: dns.Question({
name: 'example.com',
type: 'A'
}),
server: { address: '127.0.0.1', port: dnsPort, type: 'udp' },
timeout: 1000,
});
req.on('timeout', function () {
done(new Error('Connection timed out'));
});
req.on('message', function () {
done();
});
req.send();
});
});
after('shutdown tor pool', function (done) {
dnsServerTorPool.exit(done);
});
});
var controlServer = new (TorRouter.ControlServer)(logger, nconf);
var controlPort;
describe('ControlServer', function () {
describe('#listen(port)', function () {
it('should bind to a given port', function (done) {
getPort().then((port) => {
controlPort = port;
controlServer.listen(port, done);
});
});
});
describe('#createTorPool(options)', function () {
it('should create a TorPool with a given configuration', function () {
let torPool = controlServer.createTorPool({ ProtocolWarnings: 1 });
assert.ok((controlServer.torPool instanceof (TorRouter.TorPool)));
assert.equal(1, torPool.default_tor_config.ProtocolWarnings);
});
});
describe('#createSOCKSServer(port)', function () {
it('should create a SOCKS Server', function (done) {
getPort().then((port) => {
controlServer.createSOCKSServer(port);
done(null, (
(controlServer.socksServer instanceof (TorRouter.SOCKSServer))
));
});
});
});
describe('#createDNSServer(port)', function () {
it('should create a DNS Server', function (done) {
getPort().then((port) => {
controlServer.createDNSServer(port);
done(null, (
(controlServer.dnsServer instanceof (TorRouter.DNSServer))
));
});
});
});
describe('#createHTTPServer(port)', function () {
it('should create a HTTP Server', function (done) {
getPort().then((port) => {
controlServer.createHTTPServer(port);
done(null, (
(controlServer.httpServer instanceof (TorRouter.HTTPServer))
));
});
});
});
describe('#close()', function () {
it('should close the RPC Server', function () {
controlServer.close();
});
});
after('shutdown tor pool', function (done) {
controlServer.torPool.exit(done);
});
});
var rpcControlServer = new (TorRouter.ControlServer)(logger, nconf);
var rpcControlPort;
var rpcClient;
describe('ControlServer - RPC', function () {
before('setup control server', function (done) {
async.waterfall([
(callback) => { getPort().then((port) => callback(null, port)); },
(port, callback) => { rpcControlPort = port; rpcControlServer.listen(port, callback); },
(callback) => {
rpcClient = new rpc.Client(new rpc.tcpTransport({ port: rpcControlPort, hostname: 'localhost' }));
return callback();
}
], done);
});
describe('#createInstances(number_of_instances)', function () {
this.timeout(WAIT_FOR_CREATE*2);
it('should create an instance', function (done) {
rpcClient.invoke('createInstances', [2], function (error) {
if (error)
return done(error);
done();
});
});
});
describe('#queryInstances()', function () {
this.timeout(3000);
it('should return a list of instances', function (done) {
rpcClient.invoke('queryInstances', [], function (error, raw) {
if (error)
return done(error);
var instances = JSON.parse(raw).result;
if (!Array.isArray(instances))
done(new Error('Did not return an array'));
done(null, (instances.every((i) => (typeof(i.name) !== 'undefined') && (i.name !== null)) && instances.length));
});
});
});
describe('#addInstances(definitions)', function () {
this.timeout(WAIT_FOR_CREATE);
it("should add an instance based on a defintion", function (done) {
var def = {
Name: 'instance-1'
};
rpcClient.invoke('addInstances', [ [ def ] ], done);
});
it("tor pool should now contain and instance that has the same name as the name specified in the defintion", function () {
assert.ok(rpcControlServer.torPool.instance_by_name('instance-1'));
});
});
describe('#newIdentites()', function () {
this.timeout(3000);
it('should request new identities for all instances', function (done) {
rpcClient.invoke('newIdentites', [], done);
});
});
describe('#newIdentityByName(instance_name)', function () {
this.timeout(3000);
it('should request new identities for all instances', function (done) {
rpcClient.invoke('newIdentityByName', ['instance-1'], done);
});
});
describe('#newIdentityAt(index)', function () {
this.timeout(3000);
it('should request new identities for all instances', function (done) {
rpcClient.invoke('newIdentityAt', [0], done);
});
});
describe("#setTorConfig(config_object)", function () {
this.timeout(3000);
it('should set several config variables on all instances', function (done) {
rpcClient.invoke('setTorConfig', [ { TestSocks: 1, ProtocolWarnings: 1 } ], done);
});
it('all instances should have the modified variables', function(done) {
async.map(rpcControlServer.torPool.instances, (instance, next) => {
async.series([
(cb) => { instance.get_config('TestSocks', next); },
(cb) => { instance.get_config('ProtocolWarnings', next); }
], next);
}, (error, results) => {
if (error) return done(error);
done(null, results.every((i) => i[0] === 1 && i[1] === 1));
});
});
after('unset config variables', function (done) {
async.series([
(cb) => { rpcControlServer.torPool.set_config_all('TestSocks', 0, cb); },
(cb) => { rpcControlServer.torPool.set_config_all('ProtocolWarnings', 0, cb); }
], done);
});
});
describe('#setDefaultTorConfig(object)', function () {
it('should set the default config of new instances', function (done) {
this.timeout(3000);
rpcClient.invoke('setDefaultTorConfig', [ { TestSocks: 1 } ], done);
});
it('a new instance should be created with the modified property', function (done) {
this.timeout(WAIT_FOR_CREATE);
rpcControlServer.torPool.create_instance({ Name: 'config-test' }, (err) => {
if (err) return done(err);
rpcControlServer.torPool.instance_by_name('config-test').get_config('TestSocks', (err, val) => {
if (err) return done(err);
done(null, val === 1);
});
});
});
after('remove instance', function (done) {
this.timeout(10000);
nconf.set('torConfig', {});
rpcControlServer.torPool.remove_by_name('config-test', done);
});
});
describe('#getDefaultTorConfig()', function () {
before('set tor config', function () {
nconf.set('torConfig', { TestSocks: 1 });
});
it('should return a tor config with a modified property', function (done) {
this.timeout(3000);
rpcClient.invoke('getDefaultTorConfig', [ { } ], function (error, raw) {
if (error) return done(error);
var config = JSON.parse(raw).result;
done(null, (config.TestSocks === 1))
});
});
after('unset property', function () {
nconf.set('torConfig', {});
});
});
describe('#getLoadBalanceMethod()', function () {
this.timeout(3000);
before(function () {
rpcControlServer.torPool.load_balance_method = 'round_robin';
});
it('should return the current load balance method', function (done) {
rpcClient.invoke('getLoadBalanceMethod', [], function (error, raw) {
if (error) return done(error);
var lb_method = JSON.parse(raw).result;
done(null, (lb_method === 'round_robin'));
});
});
});
describe('#setLoadBalanceMethod(load_balance_method)', function () {
this.timeout(3000);
it('should set the load balance method', function (done) {
rpcClient.invoke('setLoadBalanceMethod', ['weighted'], function (error) {
return done(error);
});
});
it('the load balance method should be changed', function () {
assert.equal(rpcControlServer.torPool.load_balance_method, 'weighted');
});
after(function () {
rpcControlServer.torPool.load_balance_method = 'round_robin';
});
});
describe("#getInstanceConfigByName(instance_name)", function () {
this.timeout(3000);
before('set config property', function (done) {
rpcControlServer.torPool.instance_by_name('instance-1').set_config('TestSocks', 1, done);
});
it('should retrieve the property from the tor instance', function (done) {
rpcClient.invoke('getInstanceConfigByName', ['instance-1'], function (error, raw) {
if (error) return done(error);
var value = JSON.parse(raw).result;
done(null, value === 1);
});
});
after('unset config property', function (done) {
rpcControlServer.torPool.instance_by_name('instance-1').set_config('TestSocks', 0, done);
});
});
describe("#getInstanceConfigAt(index)", function () {
this.timeout(3000);
before('set config property', function (done) {
rpcControlServer.torPool.instance_at(0).set_config('TestSocks', 1, done);
});
it('should retrieve the property from the tor instance', function (done) {
rpcClient.invoke('getInstanceConfigByName', [0], function (error, raw) {
if (error) return done(error);
var value = JSON.parse(raw).result;
done(null, value === 1);
});
});
after('unset config property', function (done) {
rpcControlServer.torPool.instance_at(0).set_config('TestSocks', 0, done);
});
});
describe("#setInstanceConfigByName(instance_name)", function () {
this.timeout(3000);
before('set config property', function (done) {
rpcControlServer.torPool.instance_by_name('instance-1').set_config('TestSocks', 0, done);
});
it('should set the property for the tor instance', function (done) {
rpcClient.invoke('setInstanceConfigByName', ['instance-1', 'TestSocks', 1], function (error, value) {
done(error);
});
});
it('tor instance should have the modified property', function (done) {
rpcControlServer.torPool.instance_by_name('instance-1').get_config('TestSocks', function (error, value) {
if (error) return done(error);
done(null, (value === 1));
});
});
after('unset config property', function (done) {
rpcControlServer.torPool.instance_by_name('instance-1').set_config('TestSocks', 0, done);
});
});
describe("#setInstanceConfigAt(index)", function () {
this.timeout(3000);
before('set config property', function (done) {
rpcControlServer.torPool.instance_at(0).set_config('TestSocks', 0, done);
});
it('should set the property for the tor instance', function (done) {
rpcClient.invoke('setInstanceConfigAt', [0, 'TestSocks', 1], function (error, value) {
done(error);
});
});
it('tor instance should have the modified property', function (done) {
rpcControlServer.torPool.instance_at(0).get_config('TestSocks', function (error, value) {
if (error) return done(error);
done(null, (value === 1));
});
});
after('unset config property', function (done) {
rpcControlServer.torPool.instance_at(0).set_config('TestSocks', 0, (err) => {
done(err);
});
});
});
describe('#signalAllInstances(signal)', function () {
this.timeout(3000);
it('should signal to all interfaces', function (done) {
rpcClient.invoke('signalAllInstances', [ 'DEBUG' ], function (error) {
done(error);
});
});
});
describe('#signalInstanceAt(signal)', function () {
this.timeout(3000);
it('should signal to all interfaces', function (done) {
rpcClient.invoke('signalInstanceAt', [ 0, 'DEBUG' ], function (error) {
done(error);
});
});
});
describe('#signalAllInstances(signal)', function () {
this.timeout(3000);
it('should signal to all interfaces', function (done) {
rpcClient.invoke('signalInstanceByName', [ 'instance-1', 'DEBUG' ], function (error) {
done(error);
});
});
});
describe("#nextInstance()", function () {
this.timeout(3000);
var i_name;
it('should rotate the 0th item in the instances array', function (done) {
i_name = rpcControlServer.torPool.instances[0].instance_name;
rpcClient.invoke('nextInstance', [], function (error) {
done(error);
});
});
it('0th item in the instances array should be different after nextInstance is called', function () {
assert.notEqual(rpcControlServer.torPool.instances[0].instance_name, i_name);
});
});
var instance_num1, instance_num2, i_num;
describe('#removeInstanceAt(index)', function () {
this.timeout(10000);
it("should remove an instance at the position specified", function (done) {
instance_num1 = rpcControlServer.torPool.instances.length;
rpcClient.invoke('removeInstanceAt', [0], function (error) {
done(error);
});
});
it('the tor pool should contain one instance fewer', function () {
assert.equal(rpcControlServer.torPool.instances.length, (instance_num1 - 1));
});
});
describe('#removeInstanceByName(instance_name)', function () {
this.timeout(10000);
it("should remove an instance at the position specified", function (done) {
instance_num2 = rpcControlServer.torPool.instances.length;
rpcClient.invoke('removeInstanceByName', [ "instance-1" ], function (error) {
done(error);
});
});
it('the tor pool should contain one instance fewer', function () {
assert.equal(rpcControlServer.torPool.instances.length, (instance_num2 - 1));
});
});
describe('#closeInstances()', function () {
this.timeout(10000);
it('should shutdown all instances', function (done) {
i_num = rpcControlServer.torPool.instances.length;
rpcClient.invoke('closeInstances', [ ], function (error) {
done(error);
});
});
it('no instances should be present in the pool', function () {
assert.equal(rpcControlServer.torPool.instances.length, 0);
assert.notEqual(rpcControlServer.torPool.instances.length, i_num);
});
});
after('shutdown tor pool', function (done) {
this.timeout(10000);
rpcControlServer.torPool.exit(done);
});
});