The control server can now accept websocket connections. All servers can be bound to a specific hostname

This commit is contained in:
Zachary Boyd 2018-09-09 22:12:31 -04:00
parent a571d2caf9
commit c379350de7
8 changed files with 133 additions and 68 deletions

View file

@ -3,11 +3,13 @@
## [4.0.0] - 2018-09-09 ## [4.0.0] - 2018-09-09
### Added ### Added
- All servers (DNS, HTTP, SOCKS and Control) all have a `listen` method which returns a Promise that will resolve when the server is listening. - 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 returns a Promise that will resolve when the server is listening.
### Changes ### 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. Methods now take advantage of async/await to increase readability. - All methods now return promises instead of accepting callbacks. Methods now take advantage of async/await to increase readability.
- The `logger` argument to the constructor's of all classes is now optional - The `logger` argument to the constructor of all classes is now optional
- The `Config` property of instance definitions will now inherit from `TorPool.default_tor_config`. - The `Config` property of instance definitions will now inherit from `TorPool.default_tor_config`.
- The mocha test has been split into individual files all under `test/` - The mocha test has been split into individual files all under `test/`

19
package-lock.json generated
View file

@ -85,6 +85,11 @@
"lodash": "^4.14.0" "lodash": "^4.14.0"
} }
}, },
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"asynckit": { "asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -1079,11 +1084,11 @@
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
}, },
"jrpc2": { "jrpc2": {
"version": "1.0.5", "version": "git+https://github.com/znetstar/jrpc2.git#f1521bd3f2fa73d716e74bf8f746d08d5e03e7d7",
"resolved": "https://registry.npmjs.org/jrpc2/-/jrpc2-1.0.5.tgz", "from": "git+https://github.com/znetstar/jrpc2.git",
"integrity": "sha1-dyGptrP6+qz8QE0N0RkOI4ZCCN4=",
"requires": { "requires": {
"async": "^1.2", "async": "^1.2",
"ws": "^6.0.0",
"xtend": "^4.0.0" "xtend": "^4.0.0"
}, },
"dependencies": { "dependencies": {
@ -2211,6 +2216,14 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}, },
"ws": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz",
"integrity": "sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w==",
"requires": {
"async-limiter": "~1.0.0"
}
},
"xregexp": { "xregexp": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz",

View file

@ -27,7 +27,7 @@
"eventemitter3": "^3.1.0", "eventemitter3": "^3.1.0",
"get-port": "^2.1.0", "get-port": "^2.1.0",
"granax": "^3.1.3", "granax": "^3.1.3",
"jrpc2": "^1.0.5", "jrpc2": "git+https://github.com/znetstar/jrpc2.git",
"js-weighted-list": "^0.1.1", "js-weighted-list": "^0.1.1",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"nanoid": "^1.0.2", "nanoid": "^1.0.2",

View file

@ -1,9 +1,11 @@
const TorPool = require('./TorPool');
const SOCKSServer = require('./SOCKSServer');
const DNSServer = require('./DNSServer');
const HTTPServer = require('./HTTPServer');
const rpc = require('jrpc2'); const rpc = require('jrpc2');
const SOCKSServer = require('./SOCKSServer');
const HTTPServer = require('./HTTPServer');
const DNSServer = require('./DNSServer');
const TorPool = require('./TorPool');
const default_ports = require('./default_ports');
class ControlServer { class ControlServer {
constructor(logger, nconf) { constructor(logger, nconf) {
this.torPool = new TorPool(nconf.get('torPath'), (() => nconf.get('torConfig')), nconf.get('parentDataDirectory'), nconf.get('loadBalanceMethod'), nconf.get('granaxOptions'), logger); this.torPool = new TorPool(nconf.get('torPath'), (() => nconf.get('torConfig')), nconf.get('parentDataDirectory'), nconf.get('loadBalanceMethod'), nconf.get('granaxOptions'), logger);
@ -118,11 +120,20 @@ class ControlServer {
server.expose('signalInstanceByName', this.torPool.signal_by_name.bind(this.torPool)); server.expose('signalInstanceByName', this.torPool.signal_by_name.bind(this.torPool));
} }
async listen(port) { async listenTcp(port, hostname) {
this.tcpTransport = new rpc.tcpTransport({ port }); this.tcpTransport = new rpc.tcpTransport({ port, hostname });
this.tcpTransport.listen(this.server); this.tcpTransport.listen(this.server);
this.logger.info(`[control]: control server listening on tcp://${hostname}:${port}`);
} }
async listenWs(port, hostname) {
this.wsTransport = new rpc.wsTransport({ port, hostname });
this.wsTransport.listen(this.server);
this.logger.info(`[control]: control server listening on ws://${hostname}:${port}`);
}
async listen(port) { return await this.listenTcp(port); }
close() { close() {
return this.tcpTransport.tcpServer.close(); return this.tcpTransport.tcpServer.close();
} }
@ -132,24 +143,24 @@ class ControlServer {
return this.torPool; return this.torPool;
} }
async createSOCKSServer(port) { async createSOCKSServer(port, hostname) {
this.socksServer = new SOCKSServer(this.torPool, this.logger); this.socksServer = new SOCKSServer(this.torPool, this.logger);
await this.socksServer.listen(port || 9050); await this.socksServer.listen(port || default_ports.socks, hostname);
this.logger.info(`[socks]: Listening on ${port}`); this.logger.info(`[socks]: listening on socks5://${hostname}:${port}`);
this.socksServer; this.socksServer;
} }
async createHTTPServer(port) { async createHTTPServer(port, hostname) {
this.httpServer = new HTTPServer(this.torPool, this.logger); this.httpServer = new HTTPServer(this.torPool, this.logger);
await this.httpServer.listen(port || 9080); await this.httpServer.listen(port || default_ports.http, hostname);
this.logger.info(`[http]: Listening on ${port}`); this.logger.info(`[http]: listening on http://${hostname}:${port}`);
this.httpServer; this.httpServer;
} }
async createDNSServer(port) { async createDNSServer(port, hostname) {
this.dnsServer = new DNSServer(this.torPool, this.nconf.get('dns:options'), this.nconf.get('dns:timeout'), this.logger); this.dnsServer = new DNSServer(this.torPool, this.nconf.get('dns:options'), this.nconf.get('dns:timeout'), this.logger);
await this.dnsServer.serve(port || 9053); await this.dnsServer.serve(port || default_ports.dns, hostname);
this.logger.info(`[dns]: Listening on ${port}`); this.logger.info(`[dns]: listening on dns://${hostname}:${port}`);
this.dnsServer; this.dnsServer;
} }
}; };

View file

@ -3,11 +3,12 @@ const temp = require('temp');
const path = require('path'); const path = require('path');
temp.track(); temp.track();
module.exports = { module.exports = {
"controlPort": 9077, "controlHost": 9077,
"websocketControlHost": null,
"parentDataDirectory": temp.mkdirSync(), "parentDataDirectory": temp.mkdirSync(),
"socksPort": null, "socksHost": null,
"dnsPort": null, "dnsHost": null,
"httpPort": null, "httpHost": null,
"logLevel": "info", "logLevel": "info",
"loadBalanceMethod": "round_robin", "loadBalanceMethod": "round_robin",
"torConfig": { "torConfig": {

8
src/default_ports.js Normal file
View file

@ -0,0 +1,8 @@
module.exports = Object.freeze({
socks: 9050,
http: 9080,
dns: 9053,
control: 9077,
controlWs: 9078,
default_host: null
});

View file

@ -6,48 +6,72 @@ const winston = require('winston')
const Promise = require('bluebird'); const Promise = require('bluebird');
const { ControlServer } = require('./'); const { ControlServer } = require('./');
const default_ports = require('./default_ports');
const package_json = JSON.parse(fs.readFileSync(`${__dirname}/../package.json`, 'utf8')); const package_json = JSON.parse(fs.readFileSync(`${__dirname}/../package.json`, 'utf8'));
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;
}
function assembleHost(host) {
return `${typeof(host.hostname) === 'string' ? host.hostname : '' }:${host.port}`;
}
async function main(nconf, logger) { async function main(nconf, logger) {
let instances = nconf.get('instances'); let instances = nconf.get('instances');
let socks_port = nconf.get('socksPort'); let socks_host = typeof(nconf.get('socksHost')) !== 'boolean' ? extractHost(nconf.get('socksHost')) : nconf.get('socksHost');
let dns_port = nconf.get('dnsPort'); let dns_host = typeof(nconf.get('dnsHost')) !== 'boolean' ? extractHost(nconf.get('dnsHost')) : nconf.get('dnsHost');
let http_port = nconf.get('httpPort'); let http_host = typeof(nconf.get('httpHost')) !== 'boolean' ? extractHost(nconf.get('httpHost')) : nconf.get('httpHost');
let control_port = nconf.get('controlPort'); 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 (typeof(control_port) === 'boolean') { if (typeof(control_host) === 'boolean') {
control_port = 9077; control_host = extractHost(9077);
nconf.set('controlPort', 9077); nconf.set('controlHost', assembleHost(control_port));
}
if (typeof(control_host_ws) === 'boolean') {
control_host_ws = extractHost(9078);
nconf.set('websocketControlPort', assembleHost(control_host_ws));
} }
let control = new ControlServer(logger, nconf); let control = new ControlServer(logger, nconf);
try { try {
await control.listen(control_port); await control.listenTcp(control_host.port, control_host.hostname);
if (socks_port) { if (control_host_ws) {
if (typeof(socks_port) === 'boolean') { control.listenWs(control_host_ws.port, control_host_ws.hostname);
socks_port = 9050;
nconf.set('socksPort', socks_port);
}
control.createSOCKSServer(socks_port);
} }
if (http_port) { if (socks_host) {
if (typeof(http_port) === 'boolean') { if (typeof(socks_host) === 'boolean') {
http_port = 9080; socks_host = extractHost(default_ports.socks);
nconf.set('httpPort', http_port); nconf.set('socksHost', assembleHost(socks_host));
} }
control.createHTTPServer(http_port); control.createSOCKSServer(socks_host.port, socks_host.hostname);
} }
if (dns_port) { if (http_host) {
if (typeof(dns_port) === 'boolean') { if (typeof(http_host) === 'boolean') {
dns_port = 9053; http_host = extractHost(default_ports.http);
nconf.set('dnsPort', dns_port); nconf.set('httpHost', assembleHost(http_host));
} }
control.createDNSServer(dns_port); 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) { if (instances) {
@ -56,7 +80,6 @@ async function main(nconf, logger) {
logger.info('[tor]: tor started'); logger.info('[tor]: tor started');
} }
logger.info(`[control]: control Server listening on ${control_port}`);
} catch (error) { } catch (error) {
logger.error(`[global]: error starting application: ${error.stack}`); logger.error(`[global]: error starting application: ${error.stack}`);
process.exit(1); process.exit(1);
@ -102,11 +125,16 @@ let argv_config =
demand: false demand: false
}, },
c: { c: {
alias: 'controlPort', alias: 'controlHost',
describe: 'Port the control server will bind to [default: 9077]', describe: `Host the control server will bind to, handling TCP connections [default: ${default_ports.default_host}:9077]`,
demand: false demand: false
// ,default: 9077 // ,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: { j: {
alias: 'instances', alias: 'instances',
describe: 'Number of instances using the default config', describe: 'Number of instances using the default config',
@ -114,19 +142,19 @@ let argv_config =
// ,default: 1 // ,default: 1
}, },
s: { s: {
alias: 'socksPort', alias: 'socksHost',
describe: 'Port the SOCKS5 Proxy server will bind to', describe: 'Host the SOCKS5 Proxy server will bind to. If no hostname is specified will bind to localhost',
demand: false, demand: false,
// ,default: 9050 // ,default: default_ports.socks
}, },
d: { d: {
alias: 'dnsPort', alias: 'dnsHost',
describe: 'Port the DNS Proxy server will bind to', describe: 'Host the DNS Proxy server will bind to. If no hostname is specified will bind to localhost',
demand: false demand: false
}, },
h: { h: {
alias: 'httpPort', alias: 'httpHost',
describe: 'Port the HTTP Proxy server will bind to', describe: 'Host the HTTP Proxy server will bind to. If no hostname is specified will bind to localhost',
demand: false demand: false
}, },
l: { l: {

View file

@ -1,21 +1,23 @@
const env_whitelist = [ "CONTROL_PORT", const env_whitelist = [ "CONTROL_HOST",
"TOR_PATH", "TOR_PATH",
"INSTANCES", "INSTANCES",
"SOCKS_PORT", "SOCKS_HOST",
"DNS_PORT", "DNS_HOST",
"HTTP_PORT", "HTTP_HOST",
"LOG_LEVEL", "LOG_LEVEL",
'PARENT_DATA_DIRECTORIES', 'PARENT_DATA_DIRECTORIES',
'LOAD_BALANCE_METHOD', 'LOAD_BALANCE_METHOD',
"controlPort", "WEBSOCKET_CONTROL_PORT",
"controlHost",
"torPath", "torPath",
"instances", "instances",
"socksPort", "socksHost",
"dnsPort", "dnsHost",
"httpPort", "httpHost",
"logLevel", "logLevel",
'parentDataDirectories', 'parentDataDirectories',
'loadBalanceMethod' 'loadBalanceMethod',
"websocketControlPort"
]; ];
module.exports = (nconf) => { module.exports = (nconf) => {