Finishes proxy by name for groups.

This commit is contained in:
Zachary Boyd 2018-09-11 15:53:52 -04:00
parent be52091af1
commit f97272b5c0
7 changed files with 225 additions and 32 deletions

View file

@ -3,8 +3,9 @@
## [4.0.0] - 2018-09-09 ## [4.0.0] - 2018-09-09
### Added ### Added
- Instances can now added to one or multiple groups by setting the `Group` field in the instance definition to a single string or array. - 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. Setting `--proxyByName` or `-n` to false will disable this feature. For example to connect to an instance named `instance-1` via http use `http://instance-1:@localhost:9080`. - You can now proxy through a specific instance using the username field when connecting to a proxy. Setting `--proxyByName` or `-n` to false will disable this feature. 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.
- 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. - 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. - 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.

View file

@ -39,6 +39,8 @@ class HTTPServer extends Server {
if (!this.proxy_by_name) if (!this.proxy_by_name)
return true; return true;
this.logger.verbose(`[http]: connected attempted to instance "${username}"`);
let deny_un = this.proxy_by_name.deny_unidentified_users; let deny_un = this.proxy_by_name.deny_unidentified_users;
let header = req.headers['authorization'] || req.headers['proxy-authorization']; let header = req.headers['authorization'] || req.headers['proxy-authorization'];
@ -54,9 +56,17 @@ class HTTPServer extends Server {
if ( !username && deny_un ) return deny(); if ( !username && deny_un ) return deny();
else if (!username) return true; else if (!username) return true;
this.logger.verbose(`[http]: connected attempted to instance "${username}"`); let instance;
let instance = this.tor_pool.instance_by_name(username); 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(); if (!instance) return deny();
req.instance = instance; req.instance = instance;

View file

@ -15,21 +15,33 @@ class SOCKSServer extends Server{
}); });
} }
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}`);
}
authenticate_user(username, password, callback) { authenticate_user(username, password, callback) {
let deny_un = this.proxy_by_name.deny_unidentified_users; let deny_un = this.proxy_by_name.deny_unidentified_users;
this.logger.verbose(`[socks]: connected attempted to instance "${username}"`);
// No username and deny unindentifed then deny // No username and deny unindentifed then deny
if (!username && deny_un) callback(false); if (!username && deny_un) callback(false);
// Otherwise if there is no username allow // Otherwise if there is no username allow
else if (!username) callback(true); else if (!username) callback(true);
this.logger.verbose(`[socks]: connected attempted to instance "${username}"`);
let instance = this.tor_pool.instance_by_name(username); if (this.proxy_by_name.mode === 'individual'){
if (!this.tor_pool.instance_names.includes(username)) return callback(false);
// If a username is specified but no instances match that username deny }
if (!instance) else if (this.proxy_by_name.mode === 'group') {
return callback(false); 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 // Otherwise allow
callback(true, true); callback(true, true);
@ -41,7 +53,7 @@ class SOCKSServer extends Server{
let instance; let instance;
if (inbound_socket.user) if (inbound_socket.user)
instance = this.tor_pool.instance_by_name(inbound_socket.user); instance = this.get_instance_pbn(inbound_socket.user);
let outbound_socket; let outbound_socket;
let buffer = []; let buffer = [];

View file

@ -149,8 +149,8 @@ class TorPool extends EventEmitter {
return instances.length; return instances.length;
if (prop === 'rotate') { if (prop === 'rotate') {
return () => { return (num) => {
instances.rotate(1); instances.rotate(typeof(num) === 'undefined' ? 1 : num);
save_index(); save_index();
}; };
} }
@ -167,7 +167,6 @@ class TorPool extends EventEmitter {
instances_in_group = this.instances.filter((instance) => instance.instance_group.indexOf(prop) !== -1); 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 = _.sortBy(instances_in_group, ['_index', 'instance_name']);
instances_in_group.group_name = prop; instances_in_group.group_name = prop;
@ -294,7 +293,7 @@ class TorPool extends EventEmitter {
} }
next_by_group(group) { next_by_group(group) {
this.groups[group].rotate(); this.groups[group].rotate(1);
return this.groups[group][0]; return this.groups[group][0];
} }

View file

@ -2,6 +2,7 @@ const nconf = require('nconf');
const request = require('request-promise'); const request = require('request-promise');
const getPort = require('get-port'); const getPort = require('get-port');
const { assert } = require('chai'); const { assert } = require('chai');
const _ = require('lodash');
const { TorPool, HTTPServer } = require('../'); const { TorPool, HTTPServer } = require('../');
const { WAIT_FOR_CREATE, PAGE_LOAD_TIME } = require('./constants'); const { WAIT_FOR_CREATE, PAGE_LOAD_TIME } = require('./constants');
@ -89,12 +90,13 @@ describe('HTTPServer', function () {
let httpPort; let httpPort;
let instance_def = { let instance_def = {
Name: 'instance-3' Name: 'instance-3',
Group: 'foo'
}; };
before('start up server', async function (){ before('start up server', async function (){
httpServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null); httpServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
httpServer = new HTTPServer(httpServerTorPool, null, { deny_unidentified_users: true }); httpServer = new HTTPServer(httpServerTorPool, null, { deny_unidentified_users: true, mode: 'individual' });
this.timeout(WAIT_FOR_CREATE * 3); this.timeout(WAIT_FOR_CREATE * 3);
@ -105,17 +107,21 @@ describe('HTTPServer', function () {
await httpServer.listen(httpPort); await httpServer.listen(httpPort);
}); });
it(`should service a request for example.com through ${instance_def.Name}`, function (done) { it(`should service a request for example.com through the instance named ${instance_def.Name}`, function (done) {
this.timeout(PAGE_LOAD_TIME); this.timeout(PAGE_LOAD_TIME);
let req; let req;
httpServer.on('instance-connection', (instance, source) => { let connectionHandler = (instance, source) => {
assert.equal(instance.instance_name, instance_def.Name); assert.equal(instance.instance_name, instance_def.Name);
assert.isTrue(source.by_name); assert.isTrue(source.by_name);
req.cancel(); req.cancel();
httpServer.removeAllListeners('instance-connection');;
done(); done();
}); };
httpServer.on('instance-connection', connectionHandler);
req = request({ req = request({
url: 'http://example.com', url: 'http://example.com',
@ -124,6 +130,57 @@ describe('HTTPServer', function () {
.catch(done) .catch(done)
}); });
it(`four requests made to example.com through the group named "foo" should come from the instances in "foo"`, function (done) {
(async () => {
this.timeout(PAGE_LOAD_TIME + (WAIT_FOR_CREATE));
await httpServerTorPool.add([
{
Name: 'instance-4',
Group: 'foo'
}
]);
})()
.then(async () => {
httpServer.proxy_by_name.mode = "group";
let request = require('request-promise').defaults({ proxy: `http://foo:@127.0.0.1:${httpPort}` });
let names_requested = [];
let connectionHandler = (instance, source) => {
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'
});
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() { it(`shouldn't be able to send a request without a username`, async function() {
let f = () => {}; let f = () => {};
try { try {
@ -169,12 +226,13 @@ describe('HTTPServer', function () {
let httpPort; let httpPort;
let instance_def = { let instance_def = {
Name: 'instance-3' Name: 'instance-3',
Group: 'foo'
}; };
before('start up server', async function (){ before('start up server', async function (){
httpServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null); httpServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
httpServer = new HTTPServer(httpServerTorPool, null, { deny_unidentified_users: true }); httpServer = new HTTPServer(httpServerTorPool, null, { deny_unidentified_users: true, mode: "individual" });
this.timeout(WAIT_FOR_CREATE * 3); this.timeout(WAIT_FOR_CREATE * 3);
@ -188,12 +246,16 @@ describe('HTTPServer', function () {
it(`should service a request for example.com through ${instance_def.Name}`, function (done) { it(`should service a request for example.com through ${instance_def.Name}`, function (done) {
this.timeout(PAGE_LOAD_TIME); this.timeout(PAGE_LOAD_TIME);
let req; let req;
httpServer.on('instance-connection', (instance, source) => {
let connectionHandler = (instance, source) => {
assert.equal(instance.instance_name, instance_def.Name); assert.equal(instance.instance_name, instance_def.Name);
assert.isTrue(source.by_name); assert.isTrue(source.by_name);
req.cancel(); req.cancel();
httpServer.removeAllListeners('instance-connection');
done(); done();
}); };
httpServer.on('instance-connection', connectionHandler);
req = request({ req = request({
url: 'https://example.com', url: 'https://example.com',
@ -202,6 +264,55 @@ describe('HTTPServer', function () {
.catch(done); .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 + (WAIT_FOR_CREATE));
await httpServerTorPool.add([
{
Name: 'instance-4',
Group: 'foo'
}
]);
httpServer.proxy_by_name.mode = "group";
})()
.then(async () => {
let request = require('request-promise').defaults({ proxy: `http://foo:@127.0.0.1:${httpPort}` });
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'
});
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() { it(`shouldn't be able to send a request without a username`, async function() {
let f = () => {}; let f = () => {};
try { try {

View file

@ -3,6 +3,7 @@ const request = require('request-promise');
const getPort = require('get-port'); const getPort = require('get-port');
const { HttpAgent, auth } = require('socksv5'); const { HttpAgent, auth } = require('socksv5');
const { assert } = require('chai'); const { assert } = require('chai');
const _ = require('lodash');
const { TorPool, SOCKSServer } = require('../'); const { TorPool, SOCKSServer } = require('../');
const { WAIT_FOR_CREATE, PAGE_LOAD_TIME } = require('./constants'); const { WAIT_FOR_CREATE, PAGE_LOAD_TIME } = require('./constants');
@ -28,7 +29,6 @@ describe('SOCKSServer', function () {
await socksServer.listen(socksPort); await socksServer.listen(socksPort);
}); });
it('should service a request for example.com', async function () { it('should service a request for example.com', async function () {
this.timeout(PAGE_LOAD_TIME); this.timeout(PAGE_LOAD_TIME);
@ -53,17 +53,18 @@ describe('SOCKSServer', function () {
}); });
}); });
describe('#authenticate_user(username, password) - proxy by name', function () { describe('#authenticate_user(username, password)', function () {
let socksPort; let socksPort;
let socksServerTorPool; let socksServerTorPool;
let socksServer; let socksServer;
let instance_def = { let instance_def = {
Name: 'instance-3' Name: 'instance-3',
Group: "foo"
}; };
before('start up server', async function (){ before('start up server', async function (){
socksServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null); socksServerTorPool = new TorPool(nconf.get('torPath'), {}, nconf.get('parentDataDirectory'), 'round_robin', null);
socksServer = new SOCKSServer(socksServerTorPool, null, { deny_unidentified_users: true }); socksServer = new SOCKSServer(socksServerTorPool, null, { deny_unidentified_users: true, mode: "individual" });
this.timeout(WAIT_FOR_CREATE * 3); this.timeout(WAIT_FOR_CREATE * 3);
@ -79,13 +80,15 @@ describe('SOCKSServer', function () {
this.timeout(PAGE_LOAD_TIME); this.timeout(PAGE_LOAD_TIME);
let req; let req;
let connectionHandler = (instance, source) => {
socksServer.on('instance-connection', (instance, source) => {
assert.equal(instance.instance_name, instance_def.Name); assert.equal(instance.instance_name, instance_def.Name);
assert.isTrue(source.by_name); assert.isTrue(source.by_name);
req.cancel(); req.cancel();
socksServer.removeAllListeners('instance-connection');
done(); done();
}); };
socksServer.on('instance-connection', connectionHandler);
req = request({ req = request({
url: 'http://example.com', url: 'http://example.com',
@ -99,6 +102,63 @@ describe('SOCKSServer', function () {
.catch(done); .catch(done);
}); });
it(`four requests made to example.com through the group named "foo" should come from the instances in "foo"`, function (done) {
(async () => {
this.timeout(PAGE_LOAD_TIME + (WAIT_FOR_CREATE));
await socksServerTorPool.add([
{
Name: 'instance-4',
Group: 'foo'
}
]);
})()
.then(async () => {
socksServer.proxy_by_name.mode = "group";
let request = require('request-promise').defaults({
agent: new HttpAgent({
proxyHost: '127.0.0.1',
proxyPort: socksPort,
localDNS: false,
auths: [ auth.UserPassword('foo', "doesn't mater") ]
})
});
let names_requested = [];
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'
});
i++;
}
})
.then(async () => {
await socksServerTorPool.remove_by_name('instance-4');
socksServer.proxy_by_name.mode = "individual";
})
.catch(done);
});
// it(`shouldn't be able to send a request without a username`, async function() { // it(`shouldn't be able to send a request without a username`, async function() {
// let f = () => {}; // let f = () => {};
// try { // try {

View file

@ -1,4 +1,4 @@
module.exports = { module.exports = {
WAIT_FOR_CREATE: 240000, WAIT_FOR_CREATE: 240000,
PAGE_LOAD_TIME: 60000 PAGE_LOAD_TIME: 60000 * 5
}; };