implemented proxying decrypted TLS streams in raw form
This commit is contained in:
parent
0ef845f2d5
commit
138f59bea3
|
@ -73,6 +73,14 @@ Object.keys(moduleSchemas).forEach(function (name) {
|
||||||
validator.addSchema(schema, schema.id);
|
validator.addSchema(schema, schema.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function addDomainRequirement(itemSchema) {
|
||||||
|
var result = Object.assign({}, itemSchema);
|
||||||
|
result.required = (result.required || []).concat('domains');
|
||||||
|
result.properties = Object.assign({}, result.properties);
|
||||||
|
result.properties.domains = { type: 'array', items: { type: 'string' }, minLength: 1};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function toSchemaRef(name) {
|
function toSchemaRef(name) {
|
||||||
return { '$ref': '/modules/'+name };
|
return { '$ref': '/modules/'+name };
|
||||||
}
|
}
|
||||||
|
@ -84,12 +92,11 @@ var moduleRefs = {
|
||||||
, ddns: [ 'dns@oauth3.org' ].map(toSchemaRef)
|
, ddns: [ 'dns@oauth3.org' ].map(toSchemaRef)
|
||||||
};
|
};
|
||||||
|
|
||||||
function addDomainRequirement(itemSchema) {
|
// TCP is a bit special in that it has a module that doesn't operate based on domain name
|
||||||
itemSchema.required = (itemSchema.required || []).concat('domains');
|
// (ie forward), and a modules that does (ie proxy). It therefore has different module
|
||||||
itemSchema.properties = itemSchema.properties || {};
|
// when part of the `domains` config, and when not part of the `domains` config the proxy
|
||||||
itemSchema.properties.domains = { type: 'array', items: { type: 'string' }, minLength: 1};
|
// modules must have the `domains` property while forward should not have it.
|
||||||
return itemSchema;
|
moduleRefs.tcp.push(addDomainRequirement(toSchemaRef('proxy')));
|
||||||
}
|
|
||||||
|
|
||||||
var domainSchema = {
|
var domainSchema = {
|
||||||
type: 'array'
|
type: 'array'
|
||||||
|
@ -104,6 +111,7 @@ var domainSchema = {
|
||||||
tls: { type: 'array', items: { oneOf: moduleRefs.tls }}
|
tls: { type: 'array', items: { oneOf: moduleRefs.tls }}
|
||||||
, http: { type: 'array', items: { oneOf: moduleRefs.http }}
|
, http: { type: 'array', items: { oneOf: moduleRefs.http }}
|
||||||
, ddns: { type: 'array', items: { oneOf: moduleRefs.ddns }}
|
, ddns: { type: 'array', items: { oneOf: moduleRefs.ddns }}
|
||||||
|
, tcp: { type: 'array', items: { oneOf: ['proxy'].map(toSchemaRef)}}
|
||||||
}
|
}
|
||||||
, additionalProperties: false
|
, additionalProperties: false
|
||||||
}
|
}
|
||||||
|
@ -185,7 +193,7 @@ var ddnsSchema = {
|
||||||
, token_id: { type: 'string'}
|
, token_id: { type: 'string'}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
, modules: { type: 'array', items: { oneOf: moduleRefs.ddns }}
|
, modules: { type: 'array', items: addDomainRequirement({ oneOf: moduleRefs.ddns })}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var socks5Schema = {
|
var socks5Schema = {
|
||||||
|
@ -293,6 +301,7 @@ class DomainList extends IdList {
|
||||||
http: new ModuleList((dom.modules || {}).http)
|
http: new ModuleList((dom.modules || {}).http)
|
||||||
, tls: new ModuleList((dom.modules || {}).tls)
|
, tls: new ModuleList((dom.modules || {}).tls)
|
||||||
, ddns: new ModuleList((dom.modules || {}).ddns)
|
, ddns: new ModuleList((dom.modules || {}).ddns)
|
||||||
|
, tcp: new ModuleList((dom.modules || {}).tcp)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -309,6 +318,7 @@ class DomainList extends IdList {
|
||||||
http: new ModuleList()
|
http: new ModuleList()
|
||||||
, tls: new ModuleList()
|
, tls: new ModuleList()
|
||||||
, ddns: new ModuleList()
|
, ddns: new ModuleList()
|
||||||
|
, tcp: new ModuleList()
|
||||||
};
|
};
|
||||||
// We add these after instead of in the constructor to run the validation and manipulation
|
// We add these after instead of in the constructor to run the validation and manipulation
|
||||||
// in the ModList add function since these are all new modules.
|
// in the ModList add function since these are all new modules.
|
||||||
|
|
|
@ -6,13 +6,75 @@ module.exports.create = function (deps, config) {
|
||||||
//var PromiseA = global.Promise;
|
//var PromiseA = global.Promise;
|
||||||
var PromiseA = require('bluebird');
|
var PromiseA = require('bluebird');
|
||||||
var listeners = require('./servers').listeners;
|
var listeners = require('./servers').listeners;
|
||||||
|
var domainUtils = require('./domain-utils');
|
||||||
var modules;
|
var modules;
|
||||||
|
|
||||||
|
var addrProperties = [
|
||||||
|
'remoteAddress'
|
||||||
|
, 'remotePort'
|
||||||
|
, 'remoteFamily'
|
||||||
|
, 'localAddress'
|
||||||
|
, 'localPort'
|
||||||
|
, 'localFamily'
|
||||||
|
];
|
||||||
|
|
||||||
|
function nameMatchesDomains(name, domainList) {
|
||||||
|
return domainList.some(function (pattern) {
|
||||||
|
return domainUtils.match(pattern, name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function loadModules() {
|
function loadModules() {
|
||||||
modules = {};
|
modules = {};
|
||||||
|
|
||||||
modules.tls = require('./modules/tls').create(deps, config, netHandler);
|
modules.tls = require('./modules/tls').create(deps, config, tcpHandler);
|
||||||
modules.http = require('./modules/http.js').create(deps, config, modules.tls.middleware);
|
modules.http = require('./modules/http').create(deps, config, modules.tls.middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkTcpProxy(conn, opts) {
|
||||||
|
var proxied = false;
|
||||||
|
|
||||||
|
// TCP Proxying (ie forwarding based on domain name not incoming port) only works for
|
||||||
|
// TLS wrapped connections, so if the opts don't give us a servername or don't tell us
|
||||||
|
// this is the decrypted side of a TLS connection we can't handle it here.
|
||||||
|
if (!opts.servername || !opts.encrypted) { return proxied; }
|
||||||
|
|
||||||
|
function proxy(mod) {
|
||||||
|
// First thing we need to add to the connection options is where to proxy the connection to
|
||||||
|
var newConnOpts = domainUtils.separatePort(mod.address || '');
|
||||||
|
newConnOpts.port = newConnOpts.port || mod.port;
|
||||||
|
newConnOpts.host = newConnOpts.host || mod.host || 'localhost';
|
||||||
|
|
||||||
|
// Then we add all of the connection address information. We need to prefix all of the
|
||||||
|
// properties with '_' so we can provide the information to any connection `createConnection`
|
||||||
|
// implementation but not have the default implementation try to bind the same local port.
|
||||||
|
addrProperties.forEach(function (name) {
|
||||||
|
newConnOpts['_' + name] = opts[name] || opts['_'+name] || conn[name] || conn['_'+name];
|
||||||
|
});
|
||||||
|
|
||||||
|
deps.proxy(conn, newConnOpts);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxied = config.domains.some(function (dom) {
|
||||||
|
if (!dom.modules || !Array.isArray(dom.modules.tcp)) { return false; }
|
||||||
|
if (!nameMatchesDomains(opts.servername, dom.names)) { return false; }
|
||||||
|
|
||||||
|
return dom.modules.tcp.some(function (mod) {
|
||||||
|
if (mod.type !== 'proxy') { return false; }
|
||||||
|
|
||||||
|
return proxy(mod);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
proxied = proxied || config.tcp.modules.some(function (mod) {
|
||||||
|
if (mod.type !== 'proxy') { return false; }
|
||||||
|
if (!nameMatchesDomains(opts.servername, mod.domains)) { return false; }
|
||||||
|
|
||||||
|
return proxy(mod);
|
||||||
|
});
|
||||||
|
|
||||||
|
return proxied;
|
||||||
}
|
}
|
||||||
|
|
||||||
// opts = { servername, encrypted, peek, data, remoteAddress, remotePort }
|
// opts = { servername, encrypted, peek, data, remoteAddress, remotePort }
|
||||||
|
@ -52,26 +114,28 @@ module.exports.create = function (deps, config) {
|
||||||
console.warn('failed to identify protocol from first chunk', firstChunk);
|
console.warn('failed to identify protocol from first chunk', firstChunk);
|
||||||
conn.destroy();
|
conn.destroy();
|
||||||
}
|
}
|
||||||
function netHandler(conn, opts) {
|
function tcpHandler(conn, opts) {
|
||||||
function getProp(name) {
|
function getProp(name) {
|
||||||
return opts[name] || opts['_'+name] || conn[name] || conn['_'+name];
|
return opts[name] || opts['_'+name] || conn[name] || conn['_'+name];
|
||||||
}
|
}
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
var logName = getProp('remoteAddress') + ':' + getProp('remotePort') + ' -> ' +
|
var logName = getProp('remoteAddress') + ':' + getProp('remotePort') + ' -> ' +
|
||||||
getProp('localAddress') + ':' + getProp('localPort');
|
getProp('localAddress') + ':' + getProp('localPort');
|
||||||
console.log('[netHandler]', logName, 'encrypted: '+opts.encrypted);
|
console.log('[tcpHandler]', logName, 'connection started - encrypted: ' + (opts.encrypted || false));
|
||||||
|
|
||||||
var start = Date.now();
|
var start = Date.now();
|
||||||
conn.on('timeout', function () {
|
conn.on('timeout', function () {
|
||||||
console.log('[netHandler]', logName, 'connection timed out', (Date.now()-start)/1000);
|
console.log('[tcpHandler]', logName, 'connection timed out', (Date.now()-start)/1000);
|
||||||
});
|
});
|
||||||
conn.on('end', function () {
|
conn.on('end', function () {
|
||||||
console.log('[netHandler]', logName, 'connection ended', (Date.now()-start)/1000);
|
console.log('[tcpHandler]', logName, 'connection ended', (Date.now()-start)/1000);
|
||||||
});
|
});
|
||||||
conn.on('close', function () {
|
conn.on('close', function () {
|
||||||
console.log('[netHandler]', logName, 'connection closed', (Date.now()-start)/1000);
|
console.log('[tcpHandler]', logName, 'connection closed', (Date.now()-start)/1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (checkTcpProxy(conn, opts)) { return; }
|
||||||
|
|
||||||
// XXX PEEK COMMENT XXX
|
// XXX PEEK COMMENT XXX
|
||||||
// TODO we can have our cake and eat it too
|
// TODO we can have our cake and eat it too
|
||||||
// we can skip the need to wrap the TLS connection twice
|
// we can skip the need to wrap the TLS connection twice
|
||||||
|
@ -95,7 +159,7 @@ module.exports.create = function (deps, config) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function dnsListener(port, msg) {
|
function udpHandler(port, msg) {
|
||||||
if (!Array.isArray(config.udp.modules)) {
|
if (!Array.isArray(config.udp.modules)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -123,10 +187,8 @@ module.exports.create = function (deps, config) {
|
||||||
|
|
||||||
return function (conn) {
|
return function (conn) {
|
||||||
var newConnOpts = {};
|
var newConnOpts = {};
|
||||||
['remote', 'local'].forEach(function (end) {
|
addrProperties.forEach(function (name) {
|
||||||
['Family', 'Address', 'Port'].forEach(function (name) {
|
newConnOpts['_'+name] = conn[name];
|
||||||
newConnOpts['_'+end+name] = conn[end+name];
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
deps.proxy(conn, Object.assign(newConnOpts, dest));
|
deps.proxy(conn, Object.assign(newConnOpts, dest));
|
||||||
|
@ -176,7 +238,7 @@ module.exports.create = function (deps, config) {
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
netHandler(reader, wrapOpts);
|
tcpHandler(reader, wrapOpts);
|
||||||
|
|
||||||
process.nextTick(function () {
|
process.nextTick(function () {
|
||||||
// this cb will cause the stream to emit its (actually) first data event
|
// this cb will cause the stream to emit its (actually) first data event
|
||||||
|
@ -217,19 +279,19 @@ module.exports.create = function (deps, config) {
|
||||||
listenPromises.push(listeners.tcp.add(port, forwarder));
|
listenPromises.push(listeners.tcp.add(port, forwarder));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else if (mod.type !== 'proxy') {
|
||||||
console.warn('unknown TCP module specified', mod);
|
console.warn('unknown TCP module specified', mod);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var portList = Object.keys(tcpPortMap).map(Number).sort();
|
var portList = Object.keys(tcpPortMap).map(Number).sort();
|
||||||
portList.forEach(function (port) {
|
portList.forEach(function (port) {
|
||||||
listenPromises.push(listeners.tcp.add(port, netHandler));
|
listenPromises.push(listeners.tcp.add(port, tcpHandler));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (config.udp.bind) {
|
if (config.udp.bind) {
|
||||||
config.udp.bind.forEach(function (port) {
|
config.udp.bind.forEach(function (port) {
|
||||||
listenPromises.push(listeners.udp.add(port, dnsListener.bind(port)));
|
listenPromises.push(listeners.udp.add(port, udpHandler.bind(port)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue