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 // |
|
24 |
|
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 ***********************************************************/ |
|
41 |
|
42 // import module dependencies |
|
43 var mdnsinst = require('mdns'); |
|
44 var redisdat = require('redis'); |
|
45 var nameinst = require('native-dns'); |
|
46 |
|
47 |
|
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 }); |
|
57 |
|
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 }); |
|
64 |
|
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 } |
|
77 |
|
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); |
|
94 |
|
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']} |
|
99 |
|
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(); |
|
115 |
|
116 // instantiate a new DNS server |
|
117 var nameserv = nameinst.createServer(); |
|
118 |
|
119 nameserv.on('request', function (request, response) { |
|
120 //console.log(request) |
|
121 |
|
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); |
|
126 |
|
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); |
|
135 |
|
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 }); |
|
161 |
|
162 // DNS error handler logic |
|
163 nameserv.on('error', function (err, buff, req, res) { |
|
164 console.log('DNS problem: ', err.stack); |
|
165 }); |
|
166 |
|
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 } |
|
179 |
|
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 }); |
|
197 |
|
198 //// debug print all key and value database entries |
|
199 //rediscli.hgetall('hostnames', function (error, object) {console.dir(object);}); |
|
200 |
|
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 //}); |
|
208 |
|
209 //// block executes on program termination |
|
210 //rediscli.quit(); // cleanup db connection |
|
211 //browserv.stop(); // zombie scope too bad |
|
212 //browsall.stop(); |
|