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.

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

mercurial