Thu, 14 Aug 2014 19:15:12 +0200
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();