AJ ONeal
6 years ago
3 changed files with 216 additions and 203 deletions
@ -0,0 +1,55 @@ |
|||
'use strict'; |
|||
|
|||
var Devices = module.exports; |
|||
Devices.add = function (store, servername, newDevice) { |
|||
var devices = store[servername] || []; |
|||
devices.push(newDevice); |
|||
store[servername] = devices; |
|||
}; |
|||
Devices.remove = function (store, servername, device) { |
|||
var devices = store[servername] || []; |
|||
var index = devices.indexOf(device); |
|||
|
|||
if (index < 0) { |
|||
console.warn('attempted to remove non-present device', device.deviceId, 'from', servername); |
|||
return null; |
|||
} |
|||
return devices.splice(index, 1)[0]; |
|||
}; |
|||
Devices.list = function (store, servername) { |
|||
if (store[servername] && store[servername].length) { |
|||
return store[servername]; |
|||
} |
|||
// There wasn't an exact match so check any of the wildcard domains, sorted longest
|
|||
// first so the one with the biggest natural match with be found first.
|
|||
var deviceList = []; |
|||
Object.keys(store).filter(function (pattern) { |
|||
return pattern[0] === '*' && store[pattern].length; |
|||
}).sort(function (a, b) { |
|||
return b.length - a.length; |
|||
}).some(function (pattern) { |
|||
var subPiece = pattern.slice(1); |
|||
if (subPiece === servername.slice(-subPiece.length)) { |
|||
console.log('"'+servername+'" matches "'+pattern+'"'); |
|||
deviceList = store[pattern]; |
|||
return true; |
|||
} |
|||
}); |
|||
|
|||
return deviceList; |
|||
}; |
|||
Devices.exist = function (store, servername) { |
|||
return !!(Devices.list(store, servername).length); |
|||
}; |
|||
Devices.next = function (store, servername) { |
|||
var devices = Devices.list(store, servername); |
|||
var device; |
|||
|
|||
if (devices._index >= devices.length) { |
|||
devices._index = 0; |
|||
} |
|||
device = devices[devices._index || 0]; |
|||
devices._index = (devices._index || 0) + 1; |
|||
|
|||
return device; |
|||
}; |
@ -0,0 +1,153 @@ |
|||
'use strict'; |
|||
|
|||
var packer = require('tunnel-packer'); |
|||
var sni = require('sni'); |
|||
|
|||
function pipeWs(servername, service, conn, remote) { |
|||
console.log('[pipeWs] servername:', servername, 'service:', service); |
|||
|
|||
var browserAddr = packer.socketToAddr(conn); |
|||
browserAddr.service = service; |
|||
var cid = packer.addrToId(browserAddr); |
|||
conn.tunnelCid = cid; |
|||
console.log('[pipeWs] browser is', cid, 'home-cloud is', packer.socketToId(remote.upgradeReq.socket)); |
|||
|
|||
function sendWs(data, serviceOverride) { |
|||
if (remote.ws && (!conn.tunnelClosing || serviceOverride)) { |
|||
try { |
|||
remote.ws.send(packer.pack(browserAddr, data, serviceOverride), { binary: true }); |
|||
// If we can't send data over the websocket as fast as this connection can send it to us
|
|||
// (or there are a lot of connections trying to send over the same websocket) then we
|
|||
// need to pause the connection for a little. We pause all connections if any are paused
|
|||
// to make things more fair so a connection doesn't get stuck waiting for everyone else
|
|||
// to finish because it got caught on the boundary. Also if serviceOverride is set it
|
|||
// means the connection is over, so no need to pause it.
|
|||
if (!serviceOverride && (remote.pausedConns.length || remote.ws.bufferedAmount > 1024*1024)) { |
|||
// console.log('pausing', cid, 'to allow web socket to catch up');
|
|||
conn.pause(); |
|||
remote.pausedConns.push(conn); |
|||
} |
|||
} catch (err) { |
|||
console.warn('[pipeWs] error sending websocket message', err); |
|||
} |
|||
} |
|||
} |
|||
|
|||
remote.clients[cid] = conn; |
|||
conn.on('data', function (chunk) { |
|||
console.log('[pipeWs] data from browser to tunneler', chunk.byteLength); |
|||
sendWs(chunk); |
|||
}); |
|||
conn.on('error', function (err) { |
|||
console.warn('[pipeWs] browser connection error', err); |
|||
}); |
|||
conn.on('close', function (hadErr) { |
|||
console.log('[pipeWs] browser connection closing'); |
|||
sendWs(null, hadErr ? 'error': 'end'); |
|||
delete remote.clients[cid]; |
|||
}); |
|||
} |
|||
|
|||
module.exports.createTcpConnectionHandler = function (copts) { |
|||
var Devices = copts.Devices; |
|||
|
|||
return function onTcpConnection(conn) { |
|||
// this works when I put it here, but I don't know if it's tls yet here
|
|||
// httpsServer.emit('connection', socket);
|
|||
//tls3000.emit('connection', socket);
|
|||
|
|||
//var tlsSocket = new tls.TLSSocket(socket, { secureContext: tls.createSecureContext(tlsOpts) });
|
|||
//tlsSocket.on('data', function (chunk) {
|
|||
// console.log('dummy', chunk.byteLength);
|
|||
//});
|
|||
|
|||
//return;
|
|||
conn.once('data', function (firstChunk) { |
|||
// BUG XXX: this assumes that the packet won't be chunked smaller
|
|||
// than the 'hello' or the point of the 'Host' header.
|
|||
// This is fairly reasonable, but there are edge cases where
|
|||
// it does not hold (such as manual debugging with telnet)
|
|||
// and so it should be fixed at some point in the future
|
|||
|
|||
// defer after return (instead of being in many places)
|
|||
process.nextTick(function () { |
|||
conn.unshift(firstChunk); |
|||
}); |
|||
|
|||
var service = 'tcp'; |
|||
var servername; |
|||
var str; |
|||
var m; |
|||
|
|||
function tryTls() { |
|||
if (-1 !== copts.servernames.indexOf(servername)) { |
|||
console.log("Lock and load, admin interface time!"); |
|||
copts.httpsTunnel(servername, conn); |
|||
return; |
|||
} |
|||
|
|||
if (!servername) { |
|||
console.log("No SNI was given, so there's nothing we can do here"); |
|||
copts.httpsInvalid(servername, conn); |
|||
return; |
|||
} |
|||
|
|||
var nextDevice = Devices.next(copts.deviceLists, servername); |
|||
if (!nextDevice) { |
|||
console.log("No devices match the given servername"); |
|||
copts.httpsInvalid(servername, conn); |
|||
return; |
|||
} |
|||
|
|||
console.log("pipeWs(servername, service, socket, deviceLists['" + servername + "'])"); |
|||
pipeWs(servername, service, conn, nextDevice); |
|||
} |
|||
|
|||
// https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155
|
|||
if (22 === firstChunk[0]) { |
|||
// TLS
|
|||
service = 'https'; |
|||
servername = (sni(firstChunk)||'').toLowerCase(); |
|||
console.log("tls hello servername:", servername); |
|||
tryTls(); |
|||
return; |
|||
} |
|||
|
|||
if (firstChunk[0] > 32 && firstChunk[0] < 127) { |
|||
str = firstChunk.toString(); |
|||
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im); |
|||
servername = (m && m[1].toLowerCase() || '').split(':')[0]; |
|||
console.log('servername', servername); |
|||
if (/HTTP\//i.test(str)) { |
|||
service = 'http'; |
|||
// TODO disallow http entirely
|
|||
// /^\/\.well-known\/acme-challenge\//.test(str)
|
|||
if (/well-known/.test(str)) { |
|||
// HTTP
|
|||
if (Devices.exist(copts.deviceLists, servername)) { |
|||
pipeWs(servername, service, conn, Devices.next(copts.deviceLists, servername)); |
|||
return; |
|||
} |
|||
copts.handleHttp(servername, conn); |
|||
} |
|||
else { |
|||
// redirect to https
|
|||
copts.handleInsecureHttp(servername, conn); |
|||
} |
|||
return; |
|||
} |
|||
} |
|||
|
|||
console.error("Got unexpected connection", str); |
|||
conn.write(JSON.stringify({ error: { |
|||
message: "not sure what you were trying to do there..." |
|||
, code: 'E_INVALID_PROTOCOL' } |
|||
})); |
|||
conn.end(); |
|||
}); |
|||
conn.on('error', function (err) { |
|||
console.error('[error] tcp socket raw TODO forward and close'); |
|||
console.error(err); |
|||
}); |
|||
}; |
|||
}; |
Loading…
Reference in new issue