src/lib/app.js

Thu, 14 Aug 2014 19:15:12 +0200

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 14 Aug 2014 19:15:12 +0200
changeset 10
f48fa3532729
parent 5
ee8de27ff264
permissions
-rwxr-xr-x

Improve logging date format and integrate transactions in database ops.

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@10 40 | Test: dig @nodeapp.host.tld A realhost.local |
michael@10 41 | |
michael@0 42 ***********************************************************/
michael@0 43
michael@0 44 // import module dependencies
michael@0 45 var mdnsinst = require('mdns');
michael@0 46 var redisdat = require('redis');
michael@0 47 var nameinst = require('native-dns');
michael@0 48
michael@0 49
michael@3 50 // install POSIX signal handlers
michael@3 51 process.on('SIGUSR2', function() {
michael@3 52 console.log('SIGUSR2: Dumping mDNSGw entries at', Date());
michael@3 53 rediscli.hgetall('hostnames', function (error, object) {console.dir(object);});
michael@3 54 });
michael@3 55 process.on('SIGHUP', function() {
michael@3 56 console.log('SIGHUP: Cleared all database entries at', Date());
michael@3 57 cleardb();
michael@3 58 });
michael@3 59
michael@10 60 // format a date and time
michael@10 61 function getDateTime() {
michael@10 62 var date = new Date();
michael@10 63 var hour = date.getHours();
michael@10 64 hour = (hour < 10 ? '0' : '') + hour;
michael@10 65 var min = date.getMinutes();
michael@10 66 min = (min < 10 ? '0' : '') + min;
michael@10 67 var sec = date.getSeconds();
michael@10 68 sec = (sec < 10 ? '0' : '') + sec;
michael@10 69 var year = date.getFullYear();
michael@10 70 var month = date.getMonth() + 1;
michael@10 71 month = (month < 10 ? '0' : '') + month;
michael@10 72 var day = date.getDate();
michael@10 73 day = (day < 10 ? '0' : '') + day;
michael@10 74
michael@10 75 return year + '.' + month + '.' + day + '-' + hour + ':' + min + ':' + sec;
michael@10 76 }
michael@10 77
michael@0 78 // instantiate a new redis client
michael@0 79 // http://www.rediscookbook.org/
michael@0 80 var rediscli = redisdat.createClient();
michael@0 81 rediscli.on('error', function (error) {
michael@0 82 console.log('Error ' + error);
michael@0 83 });
michael@0 84
michael@0 85 // clear mDNS service keys
michael@3 86 function cleardb () {
michael@3 87 rediscli.del('hostnames');
michael@3 88 // this is not working unfortunately for the loop
michael@3 89 rediscli.keys('*', function (error, replies) {
michael@3 90 replies.forEach(function (reply, ident) {
michael@3 91 rediscli.del(reply, function (error, value) {
michael@3 92 if (error) throw(error);
michael@3 93 });
michael@0 94 });
michael@0 95 });
michael@3 96 }
michael@0 97
michael@0 98 // scan all advertised mDNS service types
michael@3 99 cleardb(); // clear old data first
michael@0 100 var browsall = mdnsinst.browseThemAll();
michael@0 101 browsall.on('serviceUp', function(service) {
michael@0 102 // iterate through hosts and watch accordingly
michael@0 103 if (service.type.name.match(/^[a-zA-Z0-9\-]+$/)) { // mdns module hack
michael@0 104 if (service.type.protocol == 'tcp') {
michael@0 105 var browserv = mdnsinst.createBrowser(mdnsinst.tcp(service.type.name));
michael@0 106 }
michael@0 107 else if (service.type.protocol == 'udp') {
michael@0 108 var browserv = mdnsinst.createBrowser(mdnsinst.udp(service.type.name));
michael@0 109 }
michael@0 110 else if (service.type.protocol == 'sctp') {
michael@0 111 var browserv = mdnsinst.createBrowser(mdnsinst.sctp(service.type.name));
michael@0 112 }
michael@0 113 else throw(error);
michael@0 114
michael@0 115 // common logic for all transports (TCP, UDP, SCTP, etcetera)
michael@0 116 browserv.on('serviceUp', function(service) {
michael@0 117 //console.log('service up: ', service);
michael@0 118 //{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@10 119 rediscli.hget('hostnames', service.host.replace(/\.$/, ''), function (error, value) {
michael@10 120 // handle unexpected errors
michael@10 121 if (error) throw(error);
michael@0 122
michael@10 123 // insert one or more IP addresses for each hostname.local.
michael@10 124 if (!value) { // only visit new (or with changed IPs) hosts
michael@10 125 var rcmulti = rediscli.multi(); // start a new transaction
michael@10 126 rcmulti.hsetnx('hostnames', service.host.replace(/\.$/, ''), service.addresses, function (error, nret) {
michael@10 127 if (nret === 1) // database wrote a new entry
michael@10 128 console.log(' ' + getDateTime() + ' Detected host: ' + service.host.replace(/\.$/, '') + ' ' + service.addresses);
michael@10 129 }); //rcmulti.hsetnx();
michael@10 130 rcmulti.exec(); // flush transaction queue
michael@10 131 }
michael@10 132 });
michael@0 133 });
michael@0 134 browserv.on('serviceDown', function(service) {
michael@10 135 //console.log('service down: ', service);
michael@0 136 //FIXME: still need to selectively remove hosts
michael@0 137 });
michael@3 138 browserv.on('serviceChanged', function(service) {
michael@10 139 //console.log('service changed: ', service);
michael@3 140 //FIXME: still need to selectively update hosts
michael@3 141 });
michael@0 142 browserv.start();
michael@0 143 }
michael@0 144 });
michael@0 145 browsall.start();
michael@0 146
michael@0 147 // instantiate a new DNS server
michael@0 148 var nameserv = nameinst.createServer();
michael@0 149
michael@0 150 nameserv.on('request', function (request, response) {
michael@0 151 //console.log(request)
michael@0 152
michael@0 153 // ensure that requested hostname is present
michael@0 154 rediscli.hget('hostnames', request.question[0].name, function (error, value) {
michael@0 155 // handle unexpected errors
michael@0 156 if (error) throw(error);
michael@0 157
michael@0 158 if (value) { // the db succeeded in finding a match
michael@10 159 // FIXME: need to test incoming questions stripping trailing '.'
michael@10 160 // FIXME: and adding '.local' to handle cases of non FQDNs.
michael@10 161 // FIXME: var found; // = {}; doesnt work unfortunately
michael@10 162 // FIXME: if (request.question[0].name == host)
michael@10 163 // FIXME: found = host;
michael@10 164 // FIXME: else if (request.question[0].name + '.local' == host)
michael@10 165 // FIXME: found = host.replace(/\.local$/, '');
michael@10 166 //
michael@10 167 // FIXME: replace silly new block with simple 'push(nameinst.A)
michael@10 168 // FIXME: since we already know that the host in question exists
michael@10 169 //
michael@0 170 // populate the DNS response with the chosen hostname
michael@0 171 rediscli.hkeys('hostnames', function (error, replies) {
michael@0 172 replies.forEach(function (host, index) {
michael@0 173 if (request.question[0].name == host) {
michael@0 174 rediscli.hget('hostnames', host, function (error, value) {
michael@0 175 // handle unexpected errors
michael@0 176 if (error) throw(error);
michael@0 177
michael@0 178 // FIXME: still must handle multihomed hosts
michael@0 179 //// a host might have more than one address
michael@0 180 //value.forEach(function (addr, iter)
michael@0 181 // set the nameserver address
michael@0 182 response.answer.push(nameinst.A({
michael@0 183 name: host,
michael@0 184 address: value,
michael@0 185 ttl: 600,
michael@0 186 }));
michael@0 187 response.send();
michael@0 188 });
michael@0 189 }
michael@0 190 });
michael@0 191 });
michael@0 192 }
michael@0 193 else {
michael@0 194 response.answer.push(nameinst.A({
michael@0 195 name: request.question[0].name,
michael@0 196 address: '127.0.0.1',
michael@0 197 ttl: 600,
michael@0 198 }));
michael@0 199 response.send();
michael@0 200 }
michael@0 201 });
michael@0 202 });
michael@0 203
michael@0 204 // DNS error handler logic
michael@0 205 nameserv.on('error', function (err, buff, req, res) {
michael@0 206 console.log('DNS problem: ', err.stack);
michael@0 207 });
michael@0 208
michael@3 209 //// debug process user
michael@3 210 //console.log('Start.');
michael@3 211 //console.log(process.env.USER);
michael@3 212 //console.log(process.env.SUDO_USER);
michael@3 213 //console.log('Done.');
michael@3 214 //
michael@3 215 // <1024 must run privileged
michael@3 216 var nudpport = 53; // default DNS
michael@3 217 if (nudpport < 1024 && process.getuid() !== 0) {
michael@3 218 //console.log('Serving on port <1024 from an unprivileged user.\nChange to root if using a privileged port number.')
michael@3 219 throw new Error('Serving on port <1024 from an unprivileged user.')
michael@3 220 }
michael@3 221
michael@3 222 // start the DNS process
michael@3 223 nameserv.serve(nudpport, function () {
michael@3 224 try {
michael@3 225 console.log('Starting mDNSGw on', Date());
michael@3 226 process.stdout.write('Old UID: ' + process.getuid() + ', Old GID: ' + process.getgid() + '... ');
michael@3 227 process.umask('0644');
michael@3 228 process.setgid('daemon');
michael@3 229 if (process.env.SUDO_USER)
michael@3 230 process.setuid(process.env.SUDO_USER);
michael@3 231 else
michael@3 232 process.setuid('daemon');
michael@3 233 console.log('New UID: ' + process.getuid() + ', New GID: ' + process.getgid());
michael@3 234 } catch (err) {
michael@3 235 console.log('Cowardly refusing to keep the process alive as root.');
michael@3 236 process.exit(1);
michael@3 237 }
michael@3 238 });
michael@0 239
michael@0 240 //// debug print all key and value database entries
michael@0 241 //rediscli.hgetall('hostnames', function (error, object) {console.dir(object);});
michael@0 242
michael@0 243 //// display stored mDNS service data entries
michael@0 244 //rediscli.hkeys('hostnames', function (error, replies) {
michael@0 245 // console.log(replies.length + ' replies:');
michael@0 246 // replies.forEach(function (reply, ident) {
michael@0 247 // console.log(' ' + ident + ': ' + reply);
michael@0 248 // });
michael@0 249 //});
michael@0 250
michael@0 251 //// block executes on program termination
michael@0 252 //rediscli.quit(); // cleanup db connection
michael@0 253 //browserv.stop(); // zombie scope too bad
michael@0 254 //browsall.stop();

mercurial