refactor and upgrade to proxy-packer
This commit is contained in:
parent
0c3f78147e
commit
eada75a83e
|
@ -92,6 +92,20 @@ function connectTunnel() {
|
||||||
// what udp ports to forward
|
// what udp ports to forward
|
||||||
// redirect http to https automatically
|
// redirect http to https automatically
|
||||||
// redirect www to nowww automatically
|
// redirect www to nowww automatically
|
||||||
|
if (state.config.http) {
|
||||||
|
Object.keys(state.config.http).forEach(function (hostname) {
|
||||||
|
if ('*' === hostname) {
|
||||||
|
state.config.servernames.forEach(function (servername) {
|
||||||
|
services.https[servername] = state.config.http[hostname];
|
||||||
|
services.http[servername] = 'redirect-https';
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
services.https[hostname] = state.config.http[hostname];
|
||||||
|
services.http[hostname] = 'redirect-https';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/*
|
||||||
Object.keys(state.config.localPorts).forEach(function (port) {
|
Object.keys(state.config.localPorts).forEach(function (port) {
|
||||||
var proto = state.config.localPorts[port];
|
var proto = state.config.localPorts[port];
|
||||||
if (!proto) { return; }
|
if (!proto) { return; }
|
||||||
|
@ -113,6 +127,7 @@ function connectTunnel() {
|
||||||
//services[proxy.protocol][proxy.hostname] = proxy.port;
|
//services[proxy.protocol][proxy.hostname] = proxy.port;
|
||||||
services[proto]['*'] = port;
|
services[proto]['*'] = port;
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
state.services = services;
|
state.services = services;
|
||||||
|
|
||||||
Object.keys(services).forEach(function (protocol) {
|
Object.keys(services).forEach(function (protocol) {
|
||||||
|
@ -127,6 +142,8 @@ function connectTunnel() {
|
||||||
// TODO Check undefined vs false for greenlock config
|
// TODO Check undefined vs false for greenlock config
|
||||||
var tun = remote.connect({
|
var tun = remote.connect({
|
||||||
relay: state.config.relay
|
relay: state.config.relay
|
||||||
|
, config: state.config
|
||||||
|
, sortingHat: state.config.sortingHat || './lib/sorting-hat.js'
|
||||||
, locals: state.config.servernames
|
, locals: state.config.servernames
|
||||||
, services: state.services
|
, services: state.services
|
||||||
, net: state.net
|
, net: state.net
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
module.exports.assign = function (state, tun, cb) {
|
||||||
|
var net = state.net || require('net');
|
||||||
|
var service = tun.service.toLowerCase();
|
||||||
|
var portList = state.services[service];
|
||||||
|
var port;
|
||||||
|
|
||||||
|
if (!tun.name && !tun.serviceport) {
|
||||||
|
console.log('tun:\n',tun);
|
||||||
|
//console.warn(tun.data.toString());
|
||||||
|
cb(new Error("missing routing information for ':tun_id'"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
port = portList[tun.name];
|
||||||
|
if (!port) {
|
||||||
|
// Check for any wildcard domains, sorted longest to shortest so the one with the
|
||||||
|
// biggest natural match will be found first.
|
||||||
|
Object.keys(portList).filter(function (pattern) {
|
||||||
|
return pattern[0] === '*' && pattern.length > 1;
|
||||||
|
}).sort(function (a, b) {
|
||||||
|
return b.length - a.length;
|
||||||
|
}).some(function (pattern) {
|
||||||
|
var subPiece = pattern.slice(1);
|
||||||
|
if (subPiece === tun.name.slice(-subPiece.length)) {
|
||||||
|
port = portList[pattern];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!port) {
|
||||||
|
port = portList['*'];
|
||||||
|
}
|
||||||
|
|
||||||
|
var createOpts = {
|
||||||
|
port: port
|
||||||
|
, host: '127.0.0.1'
|
||||||
|
|
||||||
|
, servername: tun.name
|
||||||
|
, name: tun.name
|
||||||
|
, serviceport: tun.serviceport
|
||||||
|
, data: tun.data
|
||||||
|
, remoteFamily: tun.family
|
||||||
|
, remoteAddress: tun.address
|
||||||
|
, remotePort: tun.port
|
||||||
|
};
|
||||||
|
var conn;
|
||||||
|
|
||||||
|
function handleNow(socket) {
|
||||||
|
var httpServer;
|
||||||
|
var tlsServer;
|
||||||
|
if ('https' === service) {
|
||||||
|
if (!state.greenlock) {
|
||||||
|
state.greenlock = require('greenlock').create(state.greenlockConfig);
|
||||||
|
}
|
||||||
|
httpServer = require('http').createServer(function (req, res) {
|
||||||
|
console.log('[hit http/s server]');
|
||||||
|
res.end('Hello, Encrypted Tunnel World!');
|
||||||
|
});
|
||||||
|
tlsServer = require('tls').createServer(state.greenlock.tlsOptions, function (tlsSocket) {
|
||||||
|
console.log('[hit tls server]');
|
||||||
|
httpServer.emit('connection', tlsSocket);
|
||||||
|
});
|
||||||
|
tlsServer.emit('connection', socket);
|
||||||
|
} else {
|
||||||
|
httpServer = require('http').createServer(state.greenlock.middleware(function (req, res) {
|
||||||
|
console.log('[hit pure http server]');
|
||||||
|
res.end('Hello, Encrypted Tunnel World!');
|
||||||
|
}));
|
||||||
|
// http://aj.telebit.cloud/.well-known/acme-challenge/blah
|
||||||
|
httpServer.emit('connection', socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ('aj.telebit.cloud' === tun.name) {
|
||||||
|
console.log('NEW CONNECTION to AJ\'s telebit could');
|
||||||
|
// For performance it may be better to use socket-pair, needs testing
|
||||||
|
var socketPair = require('socket-pair');
|
||||||
|
conn = socketPair.create(function (err, other) {
|
||||||
|
if (err) { console.error('[Error] ' + err.message); }
|
||||||
|
handleNow(other);
|
||||||
|
if (createOpts.data) {
|
||||||
|
conn.write(createOpts.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
var streamPair = require('stream-pair');
|
||||||
|
var pair = streamPair.create();
|
||||||
|
conn = pair.other;
|
||||||
|
process.nextTick(function () {
|
||||||
|
if (createOpts.data) {
|
||||||
|
conn.write(createOpts.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
conn = net.createConnection(createOpts, function () {
|
||||||
|
// this will happen before 'data' or 'readable' is triggered
|
||||||
|
// We use the data from the createOpts object so that the createConnection function has
|
||||||
|
// the oppurtunity of removing/changing it if it wants/needs to handle it differently.
|
||||||
|
if (createOpts.data) {
|
||||||
|
conn.write(createOpts.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cb(null, conn);
|
||||||
|
};
|
|
@ -53,9 +53,10 @@
|
||||||
"js-yaml": "^3.11.0",
|
"js-yaml": "^3.11.0",
|
||||||
"jsonwebtoken": "^7.1.9",
|
"jsonwebtoken": "^7.1.9",
|
||||||
"recase": "^1.0.4",
|
"recase": "^1.0.4",
|
||||||
|
"redirect-https": "^1.1.5",
|
||||||
"sni": "^1.0.0",
|
"sni": "^1.0.0",
|
||||||
"socket-pair": "^1.0.3",
|
"socket-pair": "^1.0.3",
|
||||||
"tunnel-packer": "^1.2.0",
|
"proxy-packer": "^1.4.3",
|
||||||
"ws": "^2.2.3"
|
"ws": "^2.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
175
remote.js
175
remote.js
|
@ -4,7 +4,7 @@
|
||||||
var WebSocket = require('ws');
|
var WebSocket = require('ws');
|
||||||
var PromiseA = require('bluebird');
|
var PromiseA = require('bluebird');
|
||||||
var sni = require('sni');
|
var sni = require('sni');
|
||||||
var Packer = require('tunnel-packer');
|
var Packer = require('proxy-packer');
|
||||||
|
|
||||||
function timeoutPromise(duration) {
|
function timeoutPromise(duration) {
|
||||||
return new PromiseA(function (resolve) {
|
return new PromiseA(function (resolve) {
|
||||||
|
@ -12,16 +12,16 @@ function timeoutPromise(duration) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(copts) {
|
function run(state) {
|
||||||
// jshint latedef:false
|
// jshint latedef:false
|
||||||
var activityTimeout = copts.activityTimeout || (2*60 - 5)*1000;
|
var activityTimeout = state.activityTimeout || (2*60 - 5)*1000;
|
||||||
var pongTimeout = copts.pongTimeout || 10*1000;
|
var pongTimeout = state.pongTimeout || 10*1000;
|
||||||
// Allow the tunnel client to be created with no token. This will prevent the connection from
|
// Allow the tunnel client to be created with no token. This will prevent the connection from
|
||||||
// being established initialy and allows the caller to use `.append` for the first token so
|
// being established initialy and allows the caller to use `.append` for the first token so
|
||||||
// they can get a promise that will provide feedback about invalid tokens.
|
// they can get a promise that will provide feedback about invalid tokens.
|
||||||
var tokens = [];
|
var tokens = [];
|
||||||
if (copts.token) {
|
if (state.token) {
|
||||||
tokens.push(copts.token);
|
tokens.push(state.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
var wstunneler;
|
var wstunneler;
|
||||||
|
@ -30,12 +30,13 @@ function run(copts) {
|
||||||
var localclients = {};
|
var localclients = {};
|
||||||
var pausedClients = [];
|
var pausedClients = [];
|
||||||
var clientHandlers = {
|
var clientHandlers = {
|
||||||
add: function (conn, cid, opts, servername) {
|
add: function (conn, cid, tun) {
|
||||||
localclients[cid] = conn;
|
localclients[cid] = conn;
|
||||||
console.info("[connect] new client '" + cid + "' for '" + servername + "' (" + clientHandlers.count() + " clients)");
|
console.info("[connect] new client '" + cid + "' for '" + tun.name + ":" + tun.serviceport + "' "
|
||||||
|
+ "(" + clientHandlers.count() + " clients)");
|
||||||
|
|
||||||
conn.tunnelCid = cid;
|
conn.tunnelCid = cid;
|
||||||
conn.tunnelRead = opts.data.byteLength;
|
conn.tunnelRead = tun.data.byteLength;
|
||||||
conn.tunnelWritten = 0;
|
conn.tunnelWritten = 0;
|
||||||
|
|
||||||
conn.on('data', function onLocalData(chunk) {
|
conn.on('data', function onLocalData(chunk) {
|
||||||
|
@ -50,7 +51,7 @@ function run(copts) {
|
||||||
// down the data we are getting to send over. We also want to pause all active connections
|
// down the data we are getting to send over. We also want to pause all active connections
|
||||||
// if any connections are paused to make things more fair so one connection doesn't get
|
// if any connections are paused to make things more fair so one connection doesn't get
|
||||||
// stuff waiting for all other connections to finish because it tried writing near the border.
|
// stuff waiting for all other connections to finish because it tried writing near the border.
|
||||||
var bufSize = wsHandlers.sendMessage(Packer.pack(opts, chunk));
|
var bufSize = wsHandlers.sendMessage(Packer.pack(tun, chunk));
|
||||||
if (pausedClients.length || bufSize > 1024*1024) {
|
if (pausedClients.length || bufSize > 1024*1024) {
|
||||||
// console.log('[onLocalData] paused connection', cid, 'to allow websocket to catch up');
|
// console.log('[onLocalData] paused connection', cid, 'to allow websocket to catch up');
|
||||||
conn.pause();
|
conn.pause();
|
||||||
|
@ -63,14 +64,14 @@ function run(copts) {
|
||||||
console.info("[onLocalEnd] connection '" + cid + "' ended, will probably close soon");
|
console.info("[onLocalEnd] connection '" + cid + "' ended, will probably close soon");
|
||||||
conn.tunnelClosing = true;
|
conn.tunnelClosing = true;
|
||||||
if (!sentEnd) {
|
if (!sentEnd) {
|
||||||
wsHandlers.sendMessage(Packer.pack(opts, null, 'end'));
|
wsHandlers.sendMessage(Packer.pack(tun, null, 'end'));
|
||||||
sentEnd = true;
|
sentEnd = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
conn.on('error', function onLocalError(err) {
|
conn.on('error', function onLocalError(err) {
|
||||||
console.info("[onLocalError] connection '" + cid + "' errored:", err);
|
console.info("[onLocalError] connection '" + cid + "' errored:", err);
|
||||||
if (!sentEnd) {
|
if (!sentEnd) {
|
||||||
wsHandlers.sendMessage(Packer.pack(opts, {message: err.message, code: err.code}, 'error'));
|
wsHandlers.sendMessage(Packer.pack(tun, {message: err.message, code: err.code}, 'error'));
|
||||||
sentEnd = true;
|
sentEnd = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -78,7 +79,7 @@ function run(copts) {
|
||||||
delete localclients[cid];
|
delete localclients[cid];
|
||||||
console.log('[onLocalClose] closed "' + cid + '" read:'+conn.tunnelRead+', wrote:'+conn.tunnelWritten+' (' + clientHandlers.count() + ' clients)');
|
console.log('[onLocalClose] closed "' + cid + '" read:'+conn.tunnelRead+', wrote:'+conn.tunnelWritten+' (' + clientHandlers.count() + ' clients)');
|
||||||
if (!sentEnd) {
|
if (!sentEnd) {
|
||||||
wsHandlers.sendMessage(Packer.pack(opts, null, hadErr && 'error' || 'end'));
|
wsHandlers.sendMessage(Packer.pack(tun, null, hadErr && 'error' || 'end'));
|
||||||
sentEnd = true;
|
sentEnd = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -254,132 +255,34 @@ function run(copts) {
|
||||||
wsHandlers.sendMessage(Packer.pack(null, [-cmd[0], err], 'control'));
|
wsHandlers.sendMessage(Packer.pack(null, [-cmd[0], err], 'control'));
|
||||||
}
|
}
|
||||||
|
|
||||||
, onmessage: function (opts) {
|
, onmessage: function (tun) {
|
||||||
var net = copts.net || require('net');
|
var cid = tun._id = Packer.addrToId(tun);
|
||||||
var cid = Packer.addrToId(opts);
|
|
||||||
var service = opts.service.toLowerCase();
|
|
||||||
var portList = copts.services[service];
|
|
||||||
var servername;
|
|
||||||
var port;
|
|
||||||
var str;
|
var str;
|
||||||
var m;
|
var m;
|
||||||
|
|
||||||
if (clientHandlers.write(cid, opts)) {
|
if ('http' === tun.service) {
|
||||||
return;
|
str = tun.data.toString();
|
||||||
}
|
|
||||||
if (!portList) {
|
|
||||||
packerHandlers._onConnectError(cid, opts, new Error("unsupported service '" + service + "'"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('http' === service) {
|
|
||||||
str = opts.data.toString();
|
|
||||||
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im);
|
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im);
|
||||||
servername = (m && m[1].toLowerCase() || '').split(':')[0];
|
tun._name = tun._hostname = (m && m[1].toLowerCase() || '').split(':')[0];
|
||||||
}
|
}
|
||||||
else if ('https' === service) {
|
else if ('https' === tun.service || 'tls' === tun.service) {
|
||||||
servername = sni(opts.data);
|
tun._name = tun._servername = sni(tun.data);
|
||||||
}
|
|
||||||
else {
|
|
||||||
servername = '*';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!servername) {
|
|
||||||
//console.warn(opts.data.toString());
|
|
||||||
packerHandlers._onConnectError(cid, opts, new Error("missing servername for '" + cid + "' " + opts.data.byteLength));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
port = portList[servername];
|
|
||||||
if (!port) {
|
|
||||||
// Check for any wildcard domains, sorted longest to shortest so the one with the
|
|
||||||
// biggest natural match will be found first.
|
|
||||||
Object.keys(portList).filter(function (pattern) {
|
|
||||||
return pattern[0] === '*' && pattern.length > 1;
|
|
||||||
}).sort(function (a, b) {
|
|
||||||
return b.length - a.length;
|
|
||||||
}).some(function (pattern) {
|
|
||||||
var subPiece = pattern.slice(1);
|
|
||||||
if (subPiece === servername.slice(-subPiece.length)) {
|
|
||||||
port = portList[pattern];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!port) {
|
|
||||||
port = portList['*'];
|
|
||||||
}
|
|
||||||
|
|
||||||
var createOpts = {
|
|
||||||
port: port
|
|
||||||
, host: '127.0.0.1'
|
|
||||||
|
|
||||||
, servername: servername
|
|
||||||
, data: opts.data
|
|
||||||
, remoteFamily: opts.family
|
|
||||||
, remoteAddress: opts.address
|
|
||||||
, remotePort: opts.port
|
|
||||||
};
|
|
||||||
var conn;
|
|
||||||
|
|
||||||
function handleNow(socket) {
|
|
||||||
var httpServer;
|
|
||||||
var tlsServer;
|
|
||||||
if ('https' === service) {
|
|
||||||
if (!copts.greenlock) {
|
|
||||||
copts.greenlock = require('greenlock').create(copts.greenlockConfig);
|
|
||||||
}
|
|
||||||
httpServer = require('http').createServer(function (req, res) {
|
|
||||||
console.log('[hit http/s server]');
|
|
||||||
res.end('Hello, Encrypted Tunnel World!');
|
|
||||||
});
|
|
||||||
tlsServer = require('tls').createServer(copts.greenlock.tlsOptions, function (tlsSocket) {
|
|
||||||
console.log('[hit tls server]');
|
|
||||||
httpServer.emit('connection', tlsSocket);
|
|
||||||
});
|
|
||||||
tlsServer.emit('connection', socket);
|
|
||||||
} else {
|
|
||||||
httpServer = require('http').createServer(copts.greenlock.middleware(function (req, res) {
|
|
||||||
console.log('[hit pure http server]');
|
|
||||||
res.end('Hello, Encrypted Tunnel World!');
|
|
||||||
}));
|
|
||||||
// http://aj.telebit.cloud/.well-known/acme-challenge/blah
|
|
||||||
httpServer.emit('connection', socket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ('aj.telebit.cloud' === servername) {
|
|
||||||
console.log('NEW CONNECTION to AJ\'s telebit could');
|
|
||||||
// For performance it may be better to use socket-pair, needs testing
|
|
||||||
var socketPair = require('socket-pair');
|
|
||||||
conn = socketPair.create(function (err, other) {
|
|
||||||
if (err) { console.error('[Error] ' + err.message); }
|
|
||||||
handleNow(other);
|
|
||||||
if (createOpts.data) {
|
|
||||||
conn.write(createOpts.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
/*
|
|
||||||
var streamPair = require('stream-pair');
|
|
||||||
var pair = streamPair.create();
|
|
||||||
conn = pair.other;
|
|
||||||
process.nextTick(function () {
|
|
||||||
if (createOpts.data) {
|
|
||||||
conn.write(createOpts.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
} else {
|
} else {
|
||||||
conn = net.createConnection(createOpts, function () {
|
tun._name = '';
|
||||||
// this will happen before 'data' or 'readable' is triggered
|
|
||||||
// We use the data from the createOpts object so that the createConnection function has
|
|
||||||
// the oppurtunity of removing/changing it if it wants/needs to handle it differently.
|
|
||||||
if (createOpts.data) {
|
|
||||||
conn.write(createOpts.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clientHandlers.add(conn, cid, opts, servername);
|
if (clientHandlers.write(cid, tun)) { return; }
|
||||||
|
|
||||||
|
wstunneler.pause();
|
||||||
|
require(state.sortingHat).assign(state, tun, function (err, conn) {
|
||||||
|
if (err) {
|
||||||
|
err.message = err.message.replace(/:tun_id/, tun._id);
|
||||||
|
packerHandlers._onConnectError(cid, tun, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clientHandlers.add(conn, cid, tun);
|
||||||
|
wstunneler.resume();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
, onpause: function (opts) {
|
, onpause: function (opts) {
|
||||||
|
@ -465,7 +368,7 @@ function run(copts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
, onOpen: function () {
|
, onOpen: function () {
|
||||||
console.info("[open] connected to '" + copts.relay + "'");
|
console.info("[open] connected to '" + state.relay + "'");
|
||||||
wsHandlers.refreshTimeout();
|
wsHandlers.refreshTimeout();
|
||||||
timeoutId = setTimeout(wsHandlers.checkTimeout, activityTimeout);
|
timeoutId = setTimeout(wsHandlers.checkTimeout, activityTimeout);
|
||||||
|
|
||||||
|
@ -549,11 +452,11 @@ function run(copts) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
timeoutId = null;
|
timeoutId = null;
|
||||||
var machine = require('tunnel-packer').create(packerHandlers);
|
var machine = Packer.create(packerHandlers);
|
||||||
|
|
||||||
console.info("[connect] '" + copts.relay + "'");
|
console.info("[connect] '" + state.relay + "'");
|
||||||
var tunnelUrl = copts.relay.replace(/\/$/, '') + '/?access_token=' + tokens[0];
|
var tunnelUrl = state.relay.replace(/\/$/, '') + '/?access_token=' + tokens[0];
|
||||||
wstunneler = new WebSocket(tunnelUrl, { rejectUnauthorized: !copts.insecure });
|
wstunneler = new WebSocket(tunnelUrl, { rejectUnauthorized: !state.insecure });
|
||||||
wstunneler.on('open', wsHandlers.onOpen);
|
wstunneler.on('open', wsHandlers.onOpen);
|
||||||
wstunneler.on('close', wsHandlers.onClose);
|
wstunneler.on('close', wsHandlers.onClose);
|
||||||
wstunneler.on('error', wsHandlers.onError);
|
wstunneler.on('error', wsHandlers.onError);
|
||||||
|
|
Loading…
Reference in New Issue