Wed, 13 Aug 2014 21:00:15 +0200
Include signal handler and complete most of service daemon logic.
michael@0 | 1 | #! /usr/bin/env nodejs |
michael@0 | 2 | // |
michael@0 | 3 | // mDNSGw - Zero Configuration DNS Gateway for Mesh Networks |
michael@0 | 4 | // Copyright © 2014 Michael Schloh von Bennewitz <michael@schloh.com> |
michael@0 | 5 | // |
michael@0 | 6 | // Permission to use, copy, modify, and/or distribute this software for |
michael@0 | 7 | // any purpose with or without fee is hereby granted, provided that the |
michael@0 | 8 | // above copyright notice and this permission notice appear in all copies. |
michael@0 | 9 | // |
michael@0 | 10 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL |
michael@0 | 11 | // WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED |
michael@0 | 12 | // WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE |
michael@0 | 13 | // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL |
michael@0 | 14 | // DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR |
michael@0 | 15 | // PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS |
michael@0 | 16 | // ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF |
michael@0 | 17 | // THIS SOFTWARE. |
michael@0 | 18 | // |
michael@0 | 19 | // This file is part of mDNSGw, a Zero configuration DNS gateway |
michael@0 | 20 | // which can be found at http://dev.europalab.com/mdnsgw/ |
michael@0 | 21 | // |
michael@0 | 22 | // app.js: ECMA JavaScript implementation |
michael@0 | 23 | // |
michael@0 | 24 | |
michael@0 | 25 | /*********************************************************** |
michael@0 | 26 | | ____ _ _ ____ ____ | |
michael@0 | 27 | | _ __ ___ | _ \| \ | / ___| / ___|_ __ | |
michael@0 | 28 | | | '_ ` _ \| | | | \| \___ \| | _\ \ /\ / / | |
michael@0 | 29 | | | | | | | | |_| | |\ |___) | |_| |\ V V / | |
michael@0 | 30 | | |_| |_| |_|____/|_| \_|____/ \____| \_/\_/ | |
michael@0 | 31 | | | |
michael@0 | 32 | | Requirements: Redis server with standard configuration | |
michael@0 | 33 | | NodeJS and NPM modules (see package.json) | |
michael@0 | 34 | | | |
michael@0 | 35 | | Execute: To start this application, launch it with the | |
michael@0 | 36 | | script named fork.js: $ ./fork.js | |
michael@0 | 37 | | | |
michael@0 | 38 | | Support: http://list.europalab.com/mailman/mdnsgs/ | |
michael@0 | 39 | | | |
michael@0 | 40 | ***********************************************************/ |
michael@0 | 41 | |
michael@0 | 42 | // import module dependencies |
michael@0 | 43 | var mdnsinst = require('mdns'); |
michael@0 | 44 | var redisdat = require('redis'); |
michael@0 | 45 | var nameinst = require('native-dns'); |
michael@0 | 46 | |
michael@0 | 47 | |
michael@3 | 48 | // install POSIX signal handlers |
michael@3 | 49 | process.on('SIGUSR2', function() { |
michael@3 | 50 | console.log('SIGUSR2: Dumping mDNSGw entries at', Date()); |
michael@3 | 51 | rediscli.hgetall('hostnames', function (error, object) {console.dir(object);}); |
michael@3 | 52 | }); |
michael@3 | 53 | process.on('SIGHUP', function() { |
michael@3 | 54 | console.log('SIGHUP: Cleared all database entries at', Date()); |
michael@3 | 55 | cleardb(); |
michael@3 | 56 | }); |
michael@3 | 57 | |
michael@0 | 58 | // instantiate a new redis client |
michael@0 | 59 | // http://www.rediscookbook.org/ |
michael@0 | 60 | var rediscli = redisdat.createClient(); |
michael@0 | 61 | rediscli.on('error', function (error) { |
michael@0 | 62 | console.log('Error ' + error); |
michael@0 | 63 | }); |
michael@0 | 64 | |
michael@0 | 65 | // clear mDNS service keys |
michael@3 | 66 | function cleardb () { |
michael@3 | 67 | rediscli.del('hostnames'); |
michael@3 | 68 | // this is not working unfortunately for the loop |
michael@3 | 69 | rediscli.keys('*', function (error, replies) { |
michael@3 | 70 | replies.forEach(function (reply, ident) { |
michael@3 | 71 | rediscli.del(reply, function (error, value) { |
michael@3 | 72 | if (error) throw(error); |
michael@3 | 73 | }); |
michael@0 | 74 | }); |
michael@0 | 75 | }); |
michael@3 | 76 | } |
michael@0 | 77 | |
michael@0 | 78 | // scan all advertised mDNS service types |
michael@3 | 79 | cleardb(); // clear old data first |
michael@0 | 80 | var browsall = mdnsinst.browseThemAll(); |
michael@0 | 81 | browsall.on('serviceUp', function(service) { |
michael@0 | 82 | // iterate through hosts and watch accordingly |
michael@0 | 83 | if (service.type.name.match(/^[a-zA-Z0-9\-]+$/)) { // mdns module hack |
michael@0 | 84 | if (service.type.protocol == 'tcp') { |
michael@0 | 85 | var browserv = mdnsinst.createBrowser(mdnsinst.tcp(service.type.name)); |
michael@0 | 86 | } |
michael@0 | 87 | else if (service.type.protocol == 'udp') { |
michael@0 | 88 | var browserv = mdnsinst.createBrowser(mdnsinst.udp(service.type.name)); |
michael@0 | 89 | } |
michael@0 | 90 | else if (service.type.protocol == 'sctp') { |
michael@0 | 91 | var browserv = mdnsinst.createBrowser(mdnsinst.sctp(service.type.name)); |
michael@0 | 92 | } |
michael@0 | 93 | else throw(error); |
michael@0 | 94 | |
michael@0 | 95 | // common logic for all transports (TCP, UDP, SCTP, etcetera) |
michael@0 | 96 | browserv.on('serviceUp', function(service) { |
michael@0 | 97 | //console.log('service up: ', service); |
michael@0 | 98 | //{interfaceIndex: 2, type: {name: 'ssh', protocol: 'tcp', subtypes: [], fullyQualified: true}, replyDomain: 'local.', flags: 2, name: 'hostname-mich', networkInterface: 'eth0', fullname: 'hostname-mich._ssh._tcp.local.', host: 'hostname-mich.local.', port: 22, addresses: ['192.168.1.50']} |
michael@0 | 99 | |
michael@0 | 100 | // insert one or more IP addresses for each hostname.local. |
michael@0 | 101 | rediscli.hset('hostnames', service.host.replace(/\.$/, ''), service.addresses); |
michael@0 | 102 | }); |
michael@0 | 103 | browserv.on('serviceDown', function(service) { |
michael@3 | 104 | console.log('service down: ', service); |
michael@0 | 105 | //FIXME: still need to selectively remove hosts |
michael@0 | 106 | }); |
michael@3 | 107 | browserv.on('serviceChanged', function(service) { |
michael@3 | 108 | console.log('service changed: ', service); |
michael@3 | 109 | //FIXME: still need to selectively update hosts |
michael@3 | 110 | }); |
michael@0 | 111 | browserv.start(); |
michael@0 | 112 | } |
michael@0 | 113 | }); |
michael@0 | 114 | browsall.start(); |
michael@0 | 115 | |
michael@0 | 116 | // instantiate a new DNS server |
michael@0 | 117 | var nameserv = nameinst.createServer(); |
michael@0 | 118 | |
michael@0 | 119 | nameserv.on('request', function (request, response) { |
michael@0 | 120 | //console.log(request) |
michael@0 | 121 | |
michael@0 | 122 | // ensure that requested hostname is present |
michael@0 | 123 | rediscli.hget('hostnames', request.question[0].name, function (error, value) { |
michael@0 | 124 | // handle unexpected errors |
michael@0 | 125 | if (error) throw(error); |
michael@0 | 126 | |
michael@0 | 127 | if (value) { // the db succeeded in finding a match |
michael@0 | 128 | // populate the DNS response with the chosen hostname |
michael@0 | 129 | rediscli.hkeys('hostnames', function (error, replies) { |
michael@0 | 130 | replies.forEach(function (host, index) { |
michael@0 | 131 | if (request.question[0].name == host) { |
michael@0 | 132 | rediscli.hget('hostnames', host, function (error, value) { |
michael@0 | 133 | // handle unexpected errors |
michael@0 | 134 | if (error) throw(error); |
michael@0 | 135 | |
michael@0 | 136 | // FIXME: still must handle multihomed hosts |
michael@0 | 137 | //// a host might have more than one address |
michael@0 | 138 | //value.forEach(function (addr, iter) |
michael@0 | 139 | // set the nameserver address |
michael@0 | 140 | response.answer.push(nameinst.A({ |
michael@0 | 141 | name: host, |
michael@0 | 142 | address: value, |
michael@0 | 143 | ttl: 600, |
michael@0 | 144 | })); |
michael@0 | 145 | response.send(); |
michael@0 | 146 | }); |
michael@0 | 147 | } |
michael@0 | 148 | }); |
michael@0 | 149 | }); |
michael@0 | 150 | } |
michael@0 | 151 | else { |
michael@0 | 152 | response.answer.push(nameinst.A({ |
michael@0 | 153 | name: request.question[0].name, |
michael@0 | 154 | address: '127.0.0.1', |
michael@0 | 155 | ttl: 600, |
michael@0 | 156 | })); |
michael@0 | 157 | response.send(); |
michael@0 | 158 | } |
michael@0 | 159 | }); |
michael@0 | 160 | }); |
michael@0 | 161 | |
michael@0 | 162 | // DNS error handler logic |
michael@0 | 163 | nameserv.on('error', function (err, buff, req, res) { |
michael@0 | 164 | console.log('DNS problem: ', err.stack); |
michael@0 | 165 | }); |
michael@0 | 166 | |
michael@3 | 167 | //// debug process user |
michael@3 | 168 | //console.log('Start.'); |
michael@3 | 169 | //console.log(process.env.USER); |
michael@3 | 170 | //console.log(process.env.SUDO_USER); |
michael@3 | 171 | //console.log('Done.'); |
michael@3 | 172 | // |
michael@3 | 173 | // <1024 must run privileged |
michael@3 | 174 | var nudpport = 53; // default DNS |
michael@3 | 175 | if (nudpport < 1024 && process.getuid() !== 0) { |
michael@3 | 176 | //console.log('Serving on port <1024 from an unprivileged user.\nChange to root if using a privileged port number.') |
michael@3 | 177 | throw new Error('Serving on port <1024 from an unprivileged user.') |
michael@3 | 178 | } |
michael@3 | 179 | |
michael@3 | 180 | // start the DNS process |
michael@3 | 181 | nameserv.serve(nudpport, function () { |
michael@3 | 182 | try { |
michael@3 | 183 | console.log('Starting mDNSGw on', Date()); |
michael@3 | 184 | process.stdout.write('Old UID: ' + process.getuid() + ', Old GID: ' + process.getgid() + '... '); |
michael@3 | 185 | process.umask('0644'); |
michael@3 | 186 | process.setgid('daemon'); |
michael@3 | 187 | if (process.env.SUDO_USER) |
michael@3 | 188 | process.setuid(process.env.SUDO_USER); |
michael@3 | 189 | else |
michael@3 | 190 | process.setuid('daemon'); |
michael@3 | 191 | console.log('New UID: ' + process.getuid() + ', New GID: ' + process.getgid()); |
michael@3 | 192 | } catch (err) { |
michael@3 | 193 | console.log('Cowardly refusing to keep the process alive as root.'); |
michael@3 | 194 | process.exit(1); |
michael@3 | 195 | } |
michael@3 | 196 | }); |
michael@0 | 197 | |
michael@0 | 198 | //// debug print all key and value database entries |
michael@0 | 199 | //rediscli.hgetall('hostnames', function (error, object) {console.dir(object);}); |
michael@0 | 200 | |
michael@0 | 201 | //// display stored mDNS service data entries |
michael@0 | 202 | //rediscli.hkeys('hostnames', function (error, replies) { |
michael@0 | 203 | // console.log(replies.length + ' replies:'); |
michael@0 | 204 | // replies.forEach(function (reply, ident) { |
michael@0 | 205 | // console.log(' ' + ident + ': ' + reply); |
michael@0 | 206 | // }); |
michael@0 | 207 | //}); |
michael@0 | 208 | |
michael@0 | 209 | //// block executes on program termination |
michael@0 | 210 | //rediscli.quit(); // cleanup db connection |
michael@0 | 211 | //browserv.stop(); // zombie scope too bad |
michael@0 | 212 | //browsall.stop(); |