| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var PromiseA = require('bluebird'); | 
					
						
							|  |  |  | var queryName = '_cloud._tcp.local'; | 
					
						
							| 
									
										
										
										
											2017-10-27 12:56:09 -06:00
										 |  |  | var dnsSuite = require('dns-suite'); | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-06 11:25:30 -06:00
										 |  |  | function createResponse(name, ownerIds, packet, ttl, mainPort) { | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  |   var rpacket = { | 
					
						
							|  |  |  |     header: { | 
					
						
							|  |  |  |       id: packet.header.id | 
					
						
							|  |  |  |     , qr: 1 | 
					
						
							|  |  |  |     , opcode: 0 | 
					
						
							|  |  |  |     , aa: 1 | 
					
						
							|  |  |  |     , tc: 0 | 
					
						
							|  |  |  |     , rd: 0 | 
					
						
							|  |  |  |     , ra: 0 | 
					
						
							|  |  |  |     , res1:  0 | 
					
						
							|  |  |  |     , res2:  0 | 
					
						
							|  |  |  |     , res3:  0 | 
					
						
							|  |  |  |     , rcode: 0 | 
					
						
							|  |  |  |   , } | 
					
						
							|  |  |  |   , question: packet.question | 
					
						
							|  |  |  |   , answer: [] | 
					
						
							|  |  |  |   , authority: [] | 
					
						
							|  |  |  |   , additional: [] | 
					
						
							|  |  |  |   , edns_options: [] | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   rpacket.answer.push({ | 
					
						
							|  |  |  |     name: queryName | 
					
						
							|  |  |  |   , typeName: 'PTR' | 
					
						
							|  |  |  |   , ttl: ttl | 
					
						
							|  |  |  |   , className: 'IN' | 
					
						
							|  |  |  |   , data: name + '.' + queryName | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   var ifaces = require('./local-ip').find(); | 
					
						
							|  |  |  |   Object.keys(ifaces).forEach(function (iname) { | 
					
						
							|  |  |  |     var iface = ifaces[iname]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     iface.ipv4.forEach(function (addr) { | 
					
						
							|  |  |  |       rpacket.additional.push({ | 
					
						
							|  |  |  |         name: name + '.local' | 
					
						
							|  |  |  |       , typeName: 'A' | 
					
						
							|  |  |  |       , ttl: ttl | 
					
						
							|  |  |  |       , className: 'IN' | 
					
						
							|  |  |  |       , address: addr.address | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     iface.ipv6.forEach(function (addr) { | 
					
						
							|  |  |  |       rpacket.additional.push({ | 
					
						
							|  |  |  |         name: name + '.local' | 
					
						
							|  |  |  |       , typeName: 'AAAA' | 
					
						
							|  |  |  |       , ttl: ttl | 
					
						
							|  |  |  |       , className: 'IN' | 
					
						
							|  |  |  |       , address: addr.address | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   rpacket.additional.push({ | 
					
						
							|  |  |  |     name: name + '.' + queryName | 
					
						
							|  |  |  |   , typeName: 'SRV' | 
					
						
							|  |  |  |   , ttl: ttl | 
					
						
							|  |  |  |   , className: 'IN' | 
					
						
							|  |  |  |   , priority: 1 | 
					
						
							|  |  |  |   , weight: 0 | 
					
						
							| 
									
										
										
										
											2017-06-08 13:21:58 -06:00
										 |  |  |   , port: mainPort | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  |   , target: name + ".local" | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   rpacket.additional.push({ | 
					
						
							| 
									
										
										
										
											2017-07-06 11:25:30 -06:00
										 |  |  |     name: name + '._device-info.' + queryName | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  |   , typeName: 'TXT' | 
					
						
							|  |  |  |   , ttl: ttl | 
					
						
							|  |  |  |   , className: 'IN' | 
					
						
							|  |  |  |   , data: ["model=CloudHome1,1", "dappsvers=1"] | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-07-06 11:25:30 -06:00
										 |  |  |   ownerIds.forEach(function (id) { | 
					
						
							|  |  |  |     rpacket.additional.push({ | 
					
						
							|  |  |  |       name: name + '._owner-id.' + queryName | 
					
						
							|  |  |  |     , typeName: 'TXT' | 
					
						
							|  |  |  |     , ttl: ttl | 
					
						
							|  |  |  |     , className: 'IN' | 
					
						
							|  |  |  |     , data: [id] | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-27 12:56:09 -06:00
										 |  |  |   return dnsSuite.DNSPacket.write(rpacket); | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-27 12:56:09 -06:00
										 |  |  | module.exports.create = function (deps, config) { | 
					
						
							|  |  |  |   var socket; | 
					
						
							| 
									
										
										
										
											2017-05-30 12:35:29 -06:00
										 |  |  |   var nextBroadcast = -1; | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-27 12:56:09 -06:00
										 |  |  |   function handlePacket(message, rinfo) { | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  |     // console.log('Received %d bytes from %s:%d', message.length, rinfo.address, rinfo.port);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var packet; | 
					
						
							|  |  |  |     try { | 
					
						
							| 
									
										
										
										
											2017-10-27 12:56:09 -06:00
										 |  |  |       packet = dnsSuite.DNSPacket.parse(message); | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  |     } | 
					
						
							|  |  |  |     catch (er) { | 
					
						
							|  |  |  |       // `dns-suite` actually errors on a lot of the packets floating around in our network,
 | 
					
						
							|  |  |  |       // so don't bother logging any errors. (We still use `dns-suite` because unlike `dns-js`
 | 
					
						
							|  |  |  |       // it can successfully craft the one packet we want to send.)
 | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Only respond to queries.
 | 
					
						
							| 
									
										
										
										
											2017-10-27 12:56:09 -06:00
										 |  |  |     if (packet.header.qr !== 0) {  return; } | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  |     // Only respond if they were asking for cloud devices.
 | 
					
						
							| 
									
										
										
										
											2017-10-27 12:56:09 -06:00
										 |  |  |     if (packet.question.length !== 1)           { return; } | 
					
						
							|  |  |  |     if (packet.question[0].name !== queryName)  { return; } | 
					
						
							|  |  |  |     if (packet.question[0].typeName !== 'PTR')  { return; } | 
					
						
							|  |  |  |     if (packet.question[0].className !== 'IN' ) { return; } | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-06 11:25:30 -06:00
										 |  |  |     var proms = [ | 
					
						
							| 
									
										
										
										
											2017-07-07 17:53:12 -06:00
										 |  |  |       deps.storage.mdnsId.get() | 
					
						
							| 
									
										
										
										
											2017-07-06 11:25:30 -06:00
										 |  |  |     , deps.storage.owners.all().then(function (owners) { | 
					
						
							|  |  |  |         // The ID is the sha256 hash of the PPID, which shouldn't be reversible and therefore
 | 
					
						
							|  |  |  |         // should be safe to expose without needing authentication.
 | 
					
						
							|  |  |  |         return owners.map(function (owner) { | 
					
						
							|  |  |  |           return owner.id; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     PromiseA.all(proms).then(function (results) { | 
					
						
							| 
									
										
										
										
											2017-10-27 12:56:09 -06:00
										 |  |  |       var resp = createResponse(results[0], results[1], packet, config.mdns.ttl, deps.tcp.mainPort); | 
					
						
							| 
									
										
										
										
											2017-06-16 13:21:20 -06:00
										 |  |  |       var now = Date.now(); | 
					
						
							|  |  |  |       if (now > nextBroadcast) { | 
					
						
							| 
									
										
										
										
											2017-05-30 12:35:29 -06:00
										 |  |  |         socket.send(resp, config.mdns.port, config.mdns.broadcast); | 
					
						
							| 
									
										
										
										
											2017-06-16 13:21:20 -06:00
										 |  |  |         nextBroadcast = now + config.mdns.ttl * 1000; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         socket.send(resp, rinfo.port, rinfo.address); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2017-10-27 12:56:09 -06:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-27 12:56:09 -06:00
										 |  |  |   function start() { | 
					
						
							|  |  |  |     socket = require('dgram').createSocket({ type: 'udp4', reuseAddr: true }); | 
					
						
							|  |  |  |     socket.on('message', handlePacket); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return new Promise(function (resolve, reject) { | 
					
						
							|  |  |  |       socket.once('error', reject); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       socket.bind(config.mdns.port, function () { | 
					
						
							|  |  |  |         var addr = this.address(); | 
					
						
							|  |  |  |         console.log('bound on UDP %s:%d for mDNS', addr.address, addr.port); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         socket.setBroadcast(true); | 
					
						
							|  |  |  |         socket.addMembership(config.mdns.broadcast); | 
					
						
							|  |  |  |         // This is supposed to be a local device discovery mechanism, so we shouldn't
 | 
					
						
							|  |  |  |         // need to hop through any gateways. This helps with security by making it
 | 
					
						
							|  |  |  |         // much more difficult for someone to use us as part of a DDoS attack by
 | 
					
						
							|  |  |  |         // spoofing the UDP address a request came from.
 | 
					
						
							|  |  |  |         socket.setTTL(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         socket.removeListener('error', reject); | 
					
						
							|  |  |  |         resolve(); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   function stop() { | 
					
						
							|  |  |  |     return new Promise(function (resolve, reject) { | 
					
						
							|  |  |  |       socket.once('error', reject); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       socket.close(function () { | 
					
						
							|  |  |  |         socket.removeListener('error', reject); | 
					
						
							|  |  |  |         socket = null; | 
					
						
							|  |  |  |         resolve(); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function updateConf() { | 
					
						
							|  |  |  |     var promise; | 
					
						
							|  |  |  |     if (config.mdns.disabled) { | 
					
						
							|  |  |  |       if (socket) { | 
					
						
							|  |  |  |         promise = stop(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       if (!socket) { | 
					
						
							|  |  |  |         promise = start(); | 
					
						
							|  |  |  |       } else if (socket.address().port !== config.mdns.port) { | 
					
						
							|  |  |  |         promise = stop().then(start); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         // Can't check membership, so just add the current broadcast address to make sure
 | 
					
						
							| 
									
										
										
										
											2017-10-30 16:00:35 -06:00
										 |  |  |         // it's set. If it's already set it will throw an exception (at least on linux).
 | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |           socket.addMembership(config.mdns.broadcast); | 
					
						
							|  |  |  |         } catch (e) {} | 
					
						
							| 
									
										
										
										
											2017-10-27 12:56:09 -06:00
										 |  |  |         promise = Promise.resolve(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   updateConf(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     updateConf | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2017-05-23 16:23:43 -06:00
										 |  |  | }; |