Show Bad Gateway rather than disconnect when south-end has gone missing.
This commit is contained in:
parent
9e06faa581
commit
0a67728239
|
@ -47,7 +47,13 @@ function applyConfig(config) {
|
||||||
} else {
|
} else {
|
||||||
state.Promise = require('bluebird');
|
state.Promise = require('bluebird');
|
||||||
}
|
}
|
||||||
state.tlsOptions = {}; // TODO just close the sockets that would use this early? or use the admin servername
|
state.tlsOptions = {
|
||||||
|
// Handles disconnected devices
|
||||||
|
// TODO allow user to opt-in to wildcard hosting for a better error page?
|
||||||
|
SNICallback: function (servername, cb) {
|
||||||
|
return state.greenlock.tlsOptions.SNICallback(state.config.webminDomain || state.servernames[0], cb);
|
||||||
|
}
|
||||||
|
}; // TODO just close the sockets that would use this early? or use the admin servername
|
||||||
state.config = config;
|
state.config = config;
|
||||||
state.servernames = config.servernames || [];
|
state.servernames = config.servernames || [];
|
||||||
state.secret = state.config.secret;
|
state.secret = state.config.secret;
|
||||||
|
|
|
@ -10,7 +10,7 @@ function noSniCallback(tag) {
|
||||||
var err = new Error("[noSniCallback] no handler set for '" + tag + "':'" + servername + "'");
|
var err = new Error("[noSniCallback] no handler set for '" + tag + "':'" + servername + "'");
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
cb(new Error(err));
|
cb(new Error(err));
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.create = function (state) {
|
module.exports.create = function (state) {
|
||||||
|
@ -72,6 +72,28 @@ module.exports.create = function (state) {
|
||||||
state.tlsInvalidSniServer.on('tlsClientError', function () {
|
state.tlsInvalidSniServer.on('tlsClientError', function () {
|
||||||
console.error('tlsClientError InvalidSniServer');
|
console.error('tlsClientError InvalidSniServer');
|
||||||
});
|
});
|
||||||
|
state.createHttpInvalid = function (servername) {
|
||||||
|
return http.createServer(function (req, res) {
|
||||||
|
if (!servername) {
|
||||||
|
res.statusCode = 422;
|
||||||
|
res.end(
|
||||||
|
"3. An inexplicable temporal shift of the quantum realm... that makes me feel uncomfortable.\n\n"
|
||||||
|
+ "[ERROR] No SNI header was sent. I can only think of two possible explanations for this:\n"
|
||||||
|
+ "\t1. You really love Windows XP and you just won't let go of Internet Explorer 6\n"
|
||||||
|
+ "\t2. You're writing a bot and you forgot to set the servername parameter\n"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO use req.headers.host instead of servername (since domain fronting is disabled anyway)
|
||||||
|
res.statusCode = 502;
|
||||||
|
res.end(
|
||||||
|
"<h1>Oops!</h1>"
|
||||||
|
+ "<p>It looks like '" + encodeURIComponent(servername) + "' isn't connected right now.</p>"
|
||||||
|
+ "<small>Error: 502 Bad Gateway</small>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
state.httpsInvalid = function (servername, socket) {
|
state.httpsInvalid = function (servername, socket) {
|
||||||
// none of these methods work:
|
// none of these methods work:
|
||||||
// httpsServer.emit('connection', socket); // this didn't work
|
// httpsServer.emit('connection', socket); // this didn't work
|
||||||
|
@ -82,28 +104,9 @@ module.exports.create = function (state) {
|
||||||
//state.tlsInvalidSniServer.emit('connection', wrapSocket(socket));
|
//state.tlsInvalidSniServer.emit('connection', wrapSocket(socket));
|
||||||
var tlsInvalidSniServer = tls.createServer(state.tlsOptions, function (tlsSocket) {
|
var tlsInvalidSniServer = tls.createServer(state.tlsOptions, function (tlsSocket) {
|
||||||
console.log('[tlsInvalid] tls connection');
|
console.log('[tlsInvalid] tls connection');
|
||||||
// things get a little messed up here
|
// We create an entire http server object because it's difficult to figure out
|
||||||
var httpInvalidSniServer = http.createServer(function (req, res) {
|
// how to access the original tlsSocket to get the servername
|
||||||
if (!servername) {
|
state.createHttpInvalid(servername).emit('connection', tlsSocket);
|
||||||
res.statusCode = 422;
|
|
||||||
res.end(
|
|
||||||
"3. An inexplicable temporal shift of the quantum realm... that makes me feel uncomfortable.\n\n"
|
|
||||||
+ "[ERROR] No SNI header was sent. I can only think of two possible explanations for this:\n"
|
|
||||||
+ "\t1. You really love Windows XP and you just won't let go of Internet Explorer 6\n"
|
|
||||||
+ "\t2. You're writing a bot and you forgot to set the servername parameter\n"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.end(
|
|
||||||
"You came in hot looking for '" + servername + "' and, granted, the IP address for that domain"
|
|
||||||
+ " must be pointing here (or else how could you be here?), nevertheless either it's not registered"
|
|
||||||
+ " in the internal system at all (which Seth says isn't even a thing) or there is no device"
|
|
||||||
+ " connected on the south side of the network which has informed me that it's ready to have traffic"
|
|
||||||
+ " for that domain forwarded to it (sorry I didn't check that deeply to determine which).\n\n"
|
|
||||||
+ "Either way, you're doing strange things that make me feel uncomfortable... Please don't touch me there any more.");
|
|
||||||
});
|
|
||||||
httpInvalidSniServer.emit('connection', tlsSocket);
|
|
||||||
});
|
});
|
||||||
tlsInvalidSniServer.on('tlsClientError', function () {
|
tlsInvalidSniServer.on('tlsClientError', function () {
|
||||||
console.error('tlsClientError InvalidSniServer httpsInvalid');
|
console.error('tlsClientError InvalidSniServer httpsInvalid');
|
||||||
|
|
|
@ -27,6 +27,16 @@ module.exports.createTcpConnectionHandler = function (state) {
|
||||||
var str;
|
var str;
|
||||||
var m;
|
var m;
|
||||||
|
|
||||||
|
if (!firstChunk) {
|
||||||
|
try {
|
||||||
|
conn.end();
|
||||||
|
} catch(e) {
|
||||||
|
console.error("[lib/unwrap-tls.js] Error:", e);
|
||||||
|
conn.destroy();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//conn.pause();
|
//conn.pause();
|
||||||
conn.unshift(firstChunk);
|
conn.unshift(firstChunk);
|
||||||
|
|
||||||
|
@ -40,6 +50,8 @@ module.exports.createTcpConnectionHandler = function (state) {
|
||||||
function deferData(fn) {
|
function deferData(fn) {
|
||||||
if (fn) {
|
if (fn) {
|
||||||
state[fn](servername, conn);
|
state[fn](servername, conn);
|
||||||
|
} else {
|
||||||
|
console.error("[SANITY ERROR] '" + fn + "' doesn't have a state handler");
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
process.nextTick(function () {
|
process.nextTick(function () {
|
||||||
|
@ -48,33 +60,78 @@ module.exports.createTcpConnectionHandler = function (state) {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
function tryTls() {
|
var httpOutcomes = {
|
||||||
var vhost;
|
missingServername: function () {
|
||||||
|
console.log("[debug] [http] missing servername");
|
||||||
if (!state.servernames.length) {
|
// TODO use a more specific error page
|
||||||
console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)");
|
deferData('handleInsecureHttp');
|
||||||
deferData('httpsSetupServer');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
, requiresSetup: function () {
|
||||||
if (-1 !== state.servernames.indexOf(servername)) {
|
console.log("[debug] [http] requires setup");
|
||||||
if (state.debug) { console.log("[Admin]", servername); }
|
// TODO Insecure connections for setup will not work on secure domains (i.e. .app)
|
||||||
deferData('httpsTunnel');
|
state.httpSetupServer.emit('connection', conn);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
, isInternal: function () {
|
||||||
if (state.config.nowww && /^www\./i.test(servername)) {
|
console.log("[debug] [http] is known internally (admin)");
|
||||||
console.log("TODO: use www bare redirect");
|
if (/well-known/.test(str)) {
|
||||||
|
deferData('handleHttp');
|
||||||
|
} else {
|
||||||
|
deferData('handleInsecureHttp');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
, isVhost: function () {
|
||||||
|
console.log("[debug] [http] is vhost (normal server)");
|
||||||
|
if (/well-known/.test(str)) {
|
||||||
|
deferData('handleHttp');
|
||||||
|
} else {
|
||||||
|
deferData('handleInsecureHttp');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, assumeExternal: function () {
|
||||||
|
console.log("[debug] [http] assume external");
|
||||||
|
var service = 'http';
|
||||||
|
|
||||||
if (!servername) {
|
if (!Devices.exist(state.deviceLists, servername)) {
|
||||||
|
// It would be better to just re-read the host header rather
|
||||||
|
// than creating a whole server object, but this is a "rare"
|
||||||
|
// case and I'm feeling lazy right now.
|
||||||
|
console.log("[debug] [http] no device connected");
|
||||||
|
state.createHttpInvalid(servername).emit('connection', conn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO make https redirect configurable on a per-domain basis
|
||||||
|
// /^\/\.well-known\/acme-challenge\//.test(str)
|
||||||
|
if (/well-known/.test(str)) {
|
||||||
|
// HTTP
|
||||||
|
console.log("[debug] [http] passthru");
|
||||||
|
pipeWs(servername, service, Devices.next(state.deviceLists, servername), conn, serviceport);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
console.log("[debug] [http] redirect to https");
|
||||||
|
deferData('handleInsecureHttp');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var tlsOutcomes = {
|
||||||
|
missingServername: function () {
|
||||||
if (state.debug) { console.log("No SNI was given, so there's nothing we can do here"); }
|
if (state.debug) { console.log("No SNI was given, so there's nothing we can do here"); }
|
||||||
deferData('httpsInvalid');
|
deferData('httpsInvalid');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
, requiresSetup: function () {
|
||||||
function run() {
|
console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)");
|
||||||
var nextDevice = Devices.next(state.deviceLists, servername);
|
deferData('httpsSetupServer');
|
||||||
|
}
|
||||||
|
, isInternal: function () {
|
||||||
|
if (state.debug) { console.log("[Admin]", servername); }
|
||||||
|
deferData('httpsTunnel');
|
||||||
|
}
|
||||||
|
, isVhost: function (vhost) {
|
||||||
|
if (state.debug) { console.log("[tcp] [vhost]", state.config.vhost, "=>", vhost); }
|
||||||
|
deferData('httpsVhost');
|
||||||
|
}
|
||||||
|
, assumeExternal: function () {
|
||||||
|
var nextDevice = Devices.next(state.deviceLists, servername);
|
||||||
if (!nextDevice) {
|
if (!nextDevice) {
|
||||||
if (state.debug) { console.log("No devices match the given servername"); }
|
if (state.debug) { console.log("No devices match the given servername"); }
|
||||||
deferData('httpsInvalid');
|
deferData('httpsInvalid');
|
||||||
|
@ -82,27 +139,33 @@ module.exports.createTcpConnectionHandler = function (state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.debug) { console.log("pipeWs(servername, service, deviceLists['" + servername + "'], socket)"); }
|
if (state.debug) { console.log("pipeWs(servername, service, deviceLists['" + servername + "'], socket)"); }
|
||||||
deferData();
|
|
||||||
pipeWs(servername, service, nextDevice, conn, serviceport);
|
pipeWs(servername, service, nextDevice, conn, serviceport);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleConnection(outcomes) {
|
||||||
|
var vhost;
|
||||||
|
|
||||||
|
// No routing information available
|
||||||
|
if (!servername) { outcomes.missingServername(); return; }
|
||||||
|
// Server needs to be set up
|
||||||
|
if (!state.servernames.length) { outcomes.requiresSetup(); return; }
|
||||||
|
// This is one of the admin domains
|
||||||
|
if (-1 !== state.servernames.indexOf(servername)) { outcomes.isInternal(); return; }
|
||||||
|
|
||||||
// TODO don't run an fs check if we already know this is working elsewhere
|
// TODO don't run an fs check if we already know this is working elsewhere
|
||||||
//if (!state.validHosts) { state.validHosts = {}; }
|
//if (!state.validHosts) { state.validHosts = {}; }
|
||||||
if (state.config.vhost) {
|
if (state.config.vhost) {
|
||||||
vhost = state.config.vhost.replace(/:hostname/, (servername||'reallydoesntexist'));
|
vhost = state.config.vhost.replace(/:hostname/, servername);
|
||||||
if (state.debug) { console.log("[tcp] [vhost]", state.config.vhost, "=>", vhost); }
|
|
||||||
//state.httpsVhost(servername, conn);
|
|
||||||
//return;
|
|
||||||
require('fs').readdir(vhost, function (err, nodes) {
|
require('fs').readdir(vhost, function (err, nodes) {
|
||||||
if (state.debug && err) { console.log("VHOST error", err); }
|
if (state.debug && err) { console.log("VHOST error", err); }
|
||||||
if (err || !nodes) { run(); return; }
|
if (err || !nodes) { outcomes.assumeExternal(); return; }
|
||||||
//if (nodes) { deferData('httpsVhost'); return; }
|
outcomes.isVhost(vhost);
|
||||||
deferData('httpsVhost');
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
run();
|
outcomes.assumeExternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155
|
// https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155
|
||||||
|
@ -111,40 +174,19 @@ module.exports.createTcpConnectionHandler = function (state) {
|
||||||
service = 'https';
|
service = 'https';
|
||||||
servername = (sni(firstChunk)||'').toLowerCase().trim();
|
servername = (sni(firstChunk)||'').toLowerCase().trim();
|
||||||
if (state.debug) { console.log("[tcp] tls hello from '" + servername + "'"); }
|
if (state.debug) { console.log("[tcp] tls hello from '" + servername + "'"); }
|
||||||
tryTls();
|
handleConnection(tlsOutcomes);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstChunk[0] > 32 && firstChunk[0] < 127) {
|
if (firstChunk[0] > 32 && firstChunk[0] < 127) {
|
||||||
|
// (probably) HTTP
|
||||||
str = firstChunk.toString();
|
str = firstChunk.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];
|
servername = (m && m[1].toLowerCase() || '').split(':')[0];
|
||||||
if (state.debug) { console.log("[tcp] http hostname '" + servername + "'"); }
|
if (state.debug) { console.log("[tcp] http hostname '" + servername + "'"); }
|
||||||
|
|
||||||
if (/HTTP\//i.test(str)) {
|
if (/HTTP\//i.test(str)) {
|
||||||
if (!state.servernames.length) {
|
handleConnection(httpOutcomes);
|
||||||
console.info("[tcp] No admin servername. Entering setup mode.");
|
|
||||||
deferData();
|
|
||||||
state.httpSetupServer.emit('connection', conn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
service = 'http';
|
|
||||||
// TODO make https redirect configurable
|
|
||||||
// /^\/\.well-known\/acme-challenge\//.test(str)
|
|
||||||
if (/well-known/.test(str)) {
|
|
||||||
// HTTP
|
|
||||||
if (Devices.exist(state.deviceLists, servername)) {
|
|
||||||
deferData();
|
|
||||||
pipeWs(servername, service, Devices.next(state.deviceLists, servername), conn, serviceport);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
deferData('handleHttp');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// redirect to https
|
|
||||||
deferData('handleInsecureHttp');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue