diff --git a/bin/goldilocks.js b/bin/goldilocks.js index 59312c7..6886619 100755 --- a/bin/goldilocks.js +++ b/bin/goldilocks.js @@ -61,6 +61,9 @@ function readConfigAndRun(args) { ); } } + if (!config.dns) { + config.dns = { proxy: { port: 3053 } }; + } if (!config.tcp) { config.tcp = {}; } @@ -116,24 +119,63 @@ function readConfigAndRun(args) { config.addresses = addresses; config.device = { hostname: 'daplien-pod' }; + var PromiseA = require('bluebird'); + var tcpProm, dnsProm; + if (config.tcp.ports) { - run(config); - return; + tcpProm = PromiseA.resolve(); + } else { + tcpProm = new PromiseA(function (resolve, reject) { + require('../lib/check-ports').checkTcpPorts(function (failed, bound) { + config.tcp.ports = Object.keys(bound); + if (config.tcp.ports.length) { + resolve(); + } else { + reject(failed); + } + }); + }); } - require('../lib/check-ports.js').checkPorts(config, function (failed, bound) { - config.tcp.ports = Object.keys(bound); + if (config.dns.bind) { + dnsProm = PromiseA.resolve(); + } else { + dnsProm = new PromiseA(function (resolve) { + require('../lib/check-ports').checkUdpPorts(function (failed, bound) { + var ports = Object.keys(bound); - if (!config.tcp.ports.length) { + if (ports.length === 0) { + // I don't think we want to prevent the rest of the app from running in + // this case like we do for TCP, do don't call reject. + console.warn('could not bind to the desired ports for DNS'); + Object.keys(failed).forEach(function (key) { + console.log('[error bind]', key, failed[key].code); + }); + } + else if (ports.length === 1) { + config.dns.bind = parseInt(ports[0], 10); + } + else { + config.dns.bind = ports.map(function (numStr) { + return parseInt(numStr, 10); + }); + } + + resolve(); + }); + }); + } + + PromiseA.all([tcpProm, dnsProm]) + .then(function () { + run(config); + }) + .catch(function (failed) { console.warn("could not bind to the desired ports"); Object.keys(failed).forEach(function (key) { console.log('[error bind]', key, failed[key].code); }); - return; - } - - run(config); - }); + }); } function readEnv(args) { @@ -166,7 +208,7 @@ else if (process.argv.length > 2) { var program = require('commander'); program - .version(require('package.json').version) + .version(require('../package.json').version) .option('--agree-tos [url1,url2]', "agree to all Terms of Service for Daplie, Let's Encrypt, etc (or specific URLs only)") .option('--config', 'Path to config file (Goldilocks.json or Goldilocks.yml) example: --config /etc/goldilocks/Goldilocks.json') .option('--tunnel [token]', 'Turn tunnel on. This will enter interactive mode for login if no token is specified.') diff --git a/lib/check-ports.js b/lib/check-ports.js index fc098c0..1286d4c 100644 --- a/lib/check-ports.js +++ b/lib/check-ports.js @@ -11,7 +11,18 @@ function bindTcpAndRelease(port, cb) { }); } -function checkPorts(config, cb) { +function bindUdpAndRelease(port, cb) { + var socket = require('dgram').createSocket('udp4'); + socket.on('error', function (e) { + cb(e); + }); + socket.bind(port, function () { + socket.close(); + cb(); + }); +} + +function checkTcpPorts(cb) { var bound = {}; var failed = {}; @@ -32,7 +43,6 @@ function checkPorts(config, cb) { } if (bound['80'] && bound['443']) { - //config.tcp.ports = [ 80, 443 ]; cb(null, bound); return; } @@ -52,4 +62,34 @@ function checkPorts(config, cb) { }); } -module.exports.checkPorts = checkPorts; +function checkUdpPorts(cb) { + var bound = {}; + var failed = {}; + + bindUdpAndRelease(53, function (e) { + if (e) { + failed[53] = e; + } else { + bound[53] = true; + } + + if (bound[53]) { + cb(null, bound); + return; + } + + console.warn("default DNS port 53 not available, trying 8053"); + bindUdpAndRelease(8053, function (e) { + if (e) { + failed[8053] = e; + } else { + bound[8053] = true; + } + + cb(null, bound); + }); + }); +} + +module.exports.checkTcpPorts = checkTcpPorts; +module.exports.checkUdpPorts = checkUdpPorts; diff --git a/lib/goldilocks.js b/lib/goldilocks.js index 7a0d377..6264845 100644 --- a/lib/goldilocks.js +++ b/lib/goldilocks.js @@ -236,6 +236,12 @@ module.exports.create = function (deps, config) { }); } + function dnsListener(msg) { + var dgram = require('dgram'); + var socket = dgram.createSocket('udp4'); + socket.send(msg, config.dns.proxy.port, config.dns.proxy.address || '127.0.0.1'); + } + function approveDomains(opts, certs, cb) { // This is where you check your database and associated // email addresses with domains and agreements and such @@ -448,7 +454,19 @@ module.exports.create = function (deps, config) { }); }); - PromiseA.all(config.tcp.ports.map(function (port) { + var listenPromises = config.tcp.ports.map(function (port) { return listeners.tcp.add(port, netHandler); - })); + }); + + if (config.dns.bind) { + if (Array.isArray(config.dns.bind)) { + listenPromises = listenPromises.concat(config.dns.bind.map(function (port) { + return listeners.udp.add(port, dnsListener); + })); + } else { + listenPromises.push(listeners.udp.add(config.dns.bind, dnsListener)); + } + } + + return PromiseA.all(listenPromises); }; diff --git a/lib/servers.js b/lib/servers.js index a0afb0b..e7f4d1f 100644 --- a/lib/servers.js +++ b/lib/servers.js @@ -1,12 +1,12 @@ 'use strict'; var serversMap = module.exports._serversMap = {}; +var dgramMap = module.exports._dgramMap = {}; +var PromiseA = require('bluebird'); module.exports.addTcpListener = function (port, handler) { - var PromiseA = require('bluebird'); - return new PromiseA(function (resolve, reject) { - var stat = serversMap[port] || serversMap[port]; + var stat = serversMap[port]; if (stat) { if (stat._closing) { @@ -34,9 +34,13 @@ module.exports.addTcpListener = function (port, handler) { stat = serversMap[port] = { server: server , handler: handler - , _closing: false + , _closing: null }; + // Add .destroy so we can close all open connections. Better if added before listen + // to eliminate any possibility of it missing an early connection in it's records. + enableDestroy(server); + server.on('connection', function (conn) { conn.__port = port; conn.__proto = 'tcp'; @@ -62,16 +66,13 @@ module.exports.addTcpListener = function (port, handler) { resolved = true; resolve(); }); - - enableDestroy(server); // adds .destroy }); }; module.exports.closeTcpListener = function (port) { - var PromiseA = require('bluebird'); - return new PromiseA(function (resolve) { var stat = serversMap[port]; if (!stat) { + resolve(); return; } stat.server.on('close', function () { @@ -98,10 +99,79 @@ module.exports.destroyTcpListener = function (port) { stat = null; }; +module.exports.addUdpListener = function (port, handler) { + return new PromiseA(function (resolve, reject) { + var stat = dgramMap[port]; + + if (stat) { + // we'll replace the current listener + stat.handler = handler; + resolve(); + return; + } + + var dgram = require('dgram'); + var server = dgram.createSocket('udp4'); + var resolved = false; + + stat = dgramMap[port] = { + server: server + , handler: handler + }; + + server.on('message', function (msg, rinfo) { + msg._size = rinfo.size; + msg._remoteFamily = rinfo.family; + msg._remoteAddress = rinfo.address; + msg._remotePort = rinfo.port; + msg._port = port; + stat.handler(msg); + }); + + server.on('error', function (err) { + if (!resolved) { + delete dgramMap[port]; + reject(err); + } + else if (stat.handler.onError) { + stat.handler.onError(err); + } + else { + throw err; + } + }); + + server.on('close', function () { + delete dgramMap[port]; + }); + + server.bind(port, function () { + resolved = true; + resolve(); + }); + }); +}; +module.exports.closeUdpListener = function (port) { + var stat = dgramMap[port]; + if (!stat) { + return PromiseA.resolve(); + } + + return new PromiseA(function (resolve) { + stat.server.once('close', resolve); + stat.server.close(); + }); +}; + + module.exports.listeners = { tcp: { add: module.exports.addTcpListener , close: module.exports.closeTcpListener , destroy: module.exports.destroyTcpListener } +, udp: { + add: module.exports.addUdpListener + , close: module.exports.closeUdpListener + } }; diff --git a/update-packages.sh b/update-packages.sh index ee658c2..776aa3c 100644 --- a/update-packages.sh +++ b/update-packages.sh @@ -18,5 +18,5 @@ popd mkdir -p well-known pushd well-known -ln -sf ../org.oauth3/well-known/oauth3 ./oauth3 +ln -snf ../org.oauth3/well-known/oauth3 ./oauth3 popd