'use strict'; var PromiseA = require('bluebird'); var queryName = '_cloud._tcp.local'; function createResponse(name, ownerIds, packet, ttl, mainPort) { 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 , port: mainPort , target: name + ".local" }); rpacket.additional.push({ name: name + '._device-info.' + queryName , typeName: 'TXT' , ttl: ttl , className: 'IN' , data: ["model=CloudHome1,1", "dappsvers=1"] }); ownerIds.forEach(function (id) { rpacket.additional.push({ name: name + '._owner-id.' + queryName , typeName: 'TXT' , ttl: ttl , className: 'IN' , data: [id] }); }); return require('dns-suite').DNSPacket.write(rpacket); } module.exports.start = function (deps, config, mainPort) { var socket = require('dgram').createSocket({ type: 'udp4', reuseAddr: true }); var dns = require('dns-suite'); var nextBroadcast = -1; socket.on('message', function (message, rinfo) { // console.log('Received %d bytes from %s:%d', message.length, rinfo.address, rinfo.port); var packet; try { packet = dns.DNSPacket.parse(message); } 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. if (packet.header.qr !== 0) { return; } // Only respond if they were asking for cloud devices. if (packet.question.length !== 1 || packet.question[0].name !== queryName) { return; } if (packet.question[0].typeName !== 'PTR' || packet.question[0].className !== 'IN' ) { return; } var proms = [ deps.storage.mdnsId.get() , 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) { var resp = createResponse(results[0], results[1], packet, config.mdns.ttl, mainPort); var now = Date.now(); if (now > nextBroadcast) { socket.send(resp, config.mdns.port, config.mdns.broadcast); nextBroadcast = now + config.mdns.ttl * 1000; } else { socket.send(resp, rinfo.port, rinfo.address); } }); }); 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); }); };