Wed, 13 Aug 2014 21:00:15 +0200
Include signal handler and complete most of service daemon logic.
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 ***********************************************************/
42 // import module dependencies
43 var mdnsinst = require('mdns');
44 var redisdat = require('redis');
45 var nameinst = require('native-dns');
48 // install POSIX signal handlers
49 process.on('SIGUSR2', function() {
50 console.log('SIGUSR2: Dumping mDNSGw entries at', Date());
51 rediscli.hgetall('hostnames', function (error, object) {console.dir(object);});
52 });
53 process.on('SIGHUP', function() {
54 console.log('SIGHUP: Cleared all database entries at', Date());
55 cleardb();
56 });
58 // instantiate a new redis client
59 // http://www.rediscookbook.org/
60 var rediscli = redisdat.createClient();
61 rediscli.on('error', function (error) {
62 console.log('Error ' + error);
63 });
65 // clear mDNS service keys
66 function cleardb () {
67 rediscli.del('hostnames');
68 // this is not working unfortunately for the loop
69 rediscli.keys('*', function (error, replies) {
70 replies.forEach(function (reply, ident) {
71 rediscli.del(reply, function (error, value) {
72 if (error) throw(error);
73 });
74 });
75 });
76 }
78 // scan all advertised mDNS service types
79 cleardb(); // clear old data first
80 var browsall = mdnsinst.browseThemAll();
81 browsall.on('serviceUp', function(service) {
82 // iterate through hosts and watch accordingly
83 if (service.type.name.match(/^[a-zA-Z0-9\-]+$/)) { // mdns module hack
84 if (service.type.protocol == 'tcp') {
85 var browserv = mdnsinst.createBrowser(mdnsinst.tcp(service.type.name));
86 }
87 else if (service.type.protocol == 'udp') {
88 var browserv = mdnsinst.createBrowser(mdnsinst.udp(service.type.name));
89 }
90 else if (service.type.protocol == 'sctp') {
91 var browserv = mdnsinst.createBrowser(mdnsinst.sctp(service.type.name));
92 }
93 else throw(error);
95 // common logic for all transports (TCP, UDP, SCTP, etcetera)
96 browserv.on('serviceUp', function(service) {
97 //console.log('service up: ', service);
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']}
100 // insert one or more IP addresses for each hostname.local.
101 rediscli.hset('hostnames', service.host.replace(/\.$/, ''), service.addresses);
102 });
103 browserv.on('serviceDown', function(service) {
104 console.log('service down: ', service);
105 //FIXME: still need to selectively remove hosts
106 });
107 browserv.on('serviceChanged', function(service) {
108 console.log('service changed: ', service);
109 //FIXME: still need to selectively update hosts
110 });
111 browserv.start();
112 }
113 });
114 browsall.start();
116 // instantiate a new DNS server
117 var nameserv = nameinst.createServer();
119 nameserv.on('request', function (request, response) {
120 //console.log(request)
122 // ensure that requested hostname is present
123 rediscli.hget('hostnames', request.question[0].name, function (error, value) {
124 // handle unexpected errors
125 if (error) throw(error);
127 if (value) { // the db succeeded in finding a match
128 // populate the DNS response with the chosen hostname
129 rediscli.hkeys('hostnames', function (error, replies) {
130 replies.forEach(function (host, index) {
131 if (request.question[0].name == host) {
132 rediscli.hget('hostnames', host, function (error, value) {
133 // handle unexpected errors
134 if (error) throw(error);
136 // FIXME: still must handle multihomed hosts
137 //// a host might have more than one address
138 //value.forEach(function (addr, iter)
139 // set the nameserver address
140 response.answer.push(nameinst.A({
141 name: host,
142 address: value,
143 ttl: 600,
144 }));
145 response.send();
146 });
147 }
148 });
149 });
150 }
151 else {
152 response.answer.push(nameinst.A({
153 name: request.question[0].name,
154 address: '127.0.0.1',
155 ttl: 600,
156 }));
157 response.send();
158 }
159 });
160 });
162 // DNS error handler logic
163 nameserv.on('error', function (err, buff, req, res) {
164 console.log('DNS problem: ', err.stack);
165 });
167 //// debug process user
168 //console.log('Start.');
169 //console.log(process.env.USER);
170 //console.log(process.env.SUDO_USER);
171 //console.log('Done.');
172 //
173 // <1024 must run privileged
174 var nudpport = 53; // default DNS
175 if (nudpport < 1024 && process.getuid() !== 0) {
176 //console.log('Serving on port <1024 from an unprivileged user.\nChange to root if using a privileged port number.')
177 throw new Error('Serving on port <1024 from an unprivileged user.')
178 }
180 // start the DNS process
181 nameserv.serve(nudpport, function () {
182 try {
183 console.log('Starting mDNSGw on', Date());
184 process.stdout.write('Old UID: ' + process.getuid() + ', Old GID: ' + process.getgid() + '... ');
185 process.umask('0644');
186 process.setgid('daemon');
187 if (process.env.SUDO_USER)
188 process.setuid(process.env.SUDO_USER);
189 else
190 process.setuid('daemon');
191 console.log('New UID: ' + process.getuid() + ', New GID: ' + process.getgid());
192 } catch (err) {
193 console.log('Cowardly refusing to keep the process alive as root.');
194 process.exit(1);
195 }
196 });
198 //// debug print all key and value database entries
199 //rediscli.hgetall('hostnames', function (error, object) {console.dir(object);});
201 //// display stored mDNS service data entries
202 //rediscli.hkeys('hostnames', function (error, replies) {
203 // console.log(replies.length + ' replies:');
204 // replies.forEach(function (reply, ident) {
205 // console.log(' ' + ident + ': ' + reply);
206 // });
207 //});
209 //// block executes on program termination
210 //rediscli.quit(); // cleanup db connection
211 //browserv.stop(); // zombie scope too bad
212 //browsall.stop();