Finishes proxy by name for groups.
This commit is contained in:
parent
be52091af1
commit
f97272b5c0
|
@ -3,8 +3,9 @@
|
|||
## [4.0.0] - 2018-09-09
|
||||
|
||||
### 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 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.
|
||||
- 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.
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ class HTTPServer extends Server {
|
|||
if (!this.proxy_by_name)
|
||||
return true;
|
||||
|
||||
this.logger.verbose(`[http]: connected attempted to instance "${username}"`);
|
||||
|
||||
let deny_un = this.proxy_by_name.deny_unidentified_users;
|
||||
|
||||
let header = req.headers['authorization'] || req.headers['proxy-authorization'];
|
||||
|
@ -54,9 +56,17 @@ class HTTPServer extends Server {
|
|||
if ( !username && deny_un ) return deny();
|
||||
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();
|
||||
req.instance = instance;
|
||||
|
|
|
@ -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) {
|
||||
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
|
||||
if (!username && deny_un) callback(false);
|
||||
// Otherwise if there is no username allow
|
||||
else if (!username) callback(true);
|
||||
|
||||
this.logger.verbose(`[socks]: connected attempted to instance "${username}"`);
|
||||
|
||||
let instance = this.tor_pool.instance_by_name(username);
|
||||
|
||||
// If a username is specified but no instances match that username deny
|
||||
if (!instance)
|
||||
return callback(false);
|
||||
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);
|
||||
|
@ -41,7 +53,7 @@ class SOCKSServer extends Server{
|
|||
let instance;
|
||||
|
||||
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 buffer = [];
|
||||
|
|
|
@ -149,8 +149,8 @@ class TorPool extends EventEmitter {
|
|||
return instances.length;
|
||||
|
||||
if (prop === 'rotate') {
|
||||
return () => {
|
||||
instances.rotate(1);
|
||||
return (num) => {
|
||||
instances.rotate(typeof(num) === 'undefined' ? 1 : num);
|
||||
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 = _.sortBy(instances_in_group, ['_index', 'instance_name']);
|
||||
|
||||
instances_in_group.group_name = prop;
|
||||
|
@ -294,7 +293,7 @@ class TorPool extends EventEmitter {
|
|||
}
|
||||
|
||||
next_by_group(group) {
|
||||
this.groups[group].rotate();
|
||||
this.groups[group].rotate(1);
|
||||
return this.groups[group][0];
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ const nconf = require('nconf');
|
|||
const request = require('request-promise');
|
||||
const getPort = require('get-port');
|
||||
const { assert } = require('chai');
|
||||
const _ = require('lodash');
|
||||
|
||||
const { TorPool, HTTPServer } = require('../');
|
||||
const { WAIT_FOR_CREATE, PAGE_LOAD_TIME } = require('./constants');
|
||||
|
@ -89,12 +90,13 @@ describe('HTTPServer', function () {
|
|||
let httpPort;
|
||||
|
||||
let instance_def = {
|
||||
Name: 'instance-3'
|
||||
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 });
|
||||
httpServer = new HTTPServer(httpServerTorPool, null, { deny_unidentified_users: true, mode: 'individual' });
|
||||
|
||||
this.timeout(WAIT_FOR_CREATE * 3);
|
||||
|
||||
|
@ -105,17 +107,21 @@ describe('HTTPServer', function () {
|
|||
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);
|
||||
|
||||
let req;
|
||||
|
||||
httpServer.on('instance-connection', (instance, source) => {
|
||||
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: 'http://example.com',
|
||||
|
@ -124,6 +130,57 @@ describe('HTTPServer', function () {
|
|||
.catch(done)
|
||||
});
|
||||
|
||||
|
||||
it(`four requests made to example.com through the group named "foo" should come from the instances in "foo"`, function (done) {
|
||||
(async () => {
|
||||
this.timeout(PAGE_LOAD_TIME + (WAIT_FOR_CREATE));
|
||||
|
||||
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() {
|
||||
let f = () => {};
|
||||
try {
|
||||
|
@ -169,12 +226,13 @@ describe('HTTPServer', function () {
|
|||
let httpPort;
|
||||
|
||||
let instance_def = {
|
||||
Name: 'instance-3'
|
||||
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 });
|
||||
httpServer = new HTTPServer(httpServerTorPool, null, { deny_unidentified_users: true, mode: "individual" });
|
||||
|
||||
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) {
|
||||
this.timeout(PAGE_LOAD_TIME);
|
||||
let req;
|
||||
httpServer.on('instance-connection', (instance, source) => {
|
||||
|
||||
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',
|
||||
|
@ -202,6 +264,55 @@ describe('HTTPServer', function () {
|
|||
.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() {
|
||||
let f = () => {};
|
||||
try {
|
||||
|
|
|
@ -3,6 +3,7 @@ const request = require('request-promise');
|
|||
const getPort = require('get-port');
|
||||
const { HttpAgent, auth } = require('socksv5');
|
||||
const { assert } = require('chai');
|
||||
const _ = require('lodash');
|
||||
|
||||
const { TorPool, SOCKSServer } = require('../');
|
||||
const { WAIT_FOR_CREATE, PAGE_LOAD_TIME } = require('./constants');
|
||||
|
@ -28,7 +29,6 @@ describe('SOCKSServer', function () {
|
|||
|
||||
await socksServer.listen(socksPort);
|
||||
});
|
||||
|
||||
it('should service a request for example.com', async function () {
|
||||
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 socksServerTorPool;
|
||||
let socksServer;
|
||||
let instance_def = {
|
||||
Name: 'instance-3'
|
||||
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 });
|
||||
socksServer = new SOCKSServer(socksServerTorPool, null, { deny_unidentified_users: true, mode: "individual" });
|
||||
|
||||
this.timeout(WAIT_FOR_CREATE * 3);
|
||||
|
||||
|
@ -79,13 +80,15 @@ describe('SOCKSServer', function () {
|
|||
this.timeout(PAGE_LOAD_TIME);
|
||||
|
||||
let req;
|
||||
|
||||
socksServer.on('instance-connection', (instance, source) => {
|
||||
let connectionHandler = (instance, source) => {
|
||||
assert.equal(instance.instance_name, instance_def.Name);
|
||||
assert.isTrue(source.by_name);
|
||||
req.cancel();
|
||||
socksServer.removeAllListeners('instance-connection');
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
socksServer.on('instance-connection', connectionHandler);
|
||||
|
||||
req = request({
|
||||
url: 'http://example.com',
|
||||
|
@ -99,6 +102,63 @@ describe('SOCKSServer', function () {
|
|||
.catch(done);
|
||||
});
|
||||
|
||||
it(`four requests made to example.com through the group named "foo" should come from the instances in "foo"`, function (done) {
|
||||
(async () => {
|
||||
this.timeout(PAGE_LOAD_TIME + (WAIT_FOR_CREATE));
|
||||
|
||||
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() {
|
||||
// let f = () => {};
|
||||
// try {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
module.exports = {
|
||||
WAIT_FOR_CREATE: 240000,
|
||||
PAGE_LOAD_TIME: 60000
|
||||
PAGE_LOAD_TIME: 60000 * 5
|
||||
};
|
Loading…
Reference in a new issue