UNPKG

electron-dns-sd

Version:

The electron-dns-sd is a Node.js module which is a pure javascript implementation of mDNS/DNS-SD (Apple Bonjour) browser and packet parser. It allows you to discover IPv4 addresses in the local network specifying a service name such as `_http._tcp.local`.

344 lines (325 loc) 8.81 kB
/* ------------------------------------------------------------------ * node-dns-sd - dns-sd-parser.js * * Copyright (c) 2018-2019, Futomi Hatano, All rights reserved. * Released under the MIT license * Date: 2018-02-27 * Related specifications: * - RFC 1035 (DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION) * https://tools.ietf.org/html/rfc1035 * - RFC 6762 (Multicast DNS) * https://tools.ietf.org/html/rfc6762 * - RFC 6763 (DNS-Based Service Discovery) * https://tools.ietf.org/html/rfc6763 * - RFC 2782 (A DNS RR for specifying the location of services (DNS SRV)) * https://tools.ietf.org/html/rfc2782 * ---------------------------------------------------------------- */ 'use strict'; /* ------------------------------------------------------------------ * Constructor: DnsSdParser() * ---------------------------------------------------------------- */ const DnsSdParser = function() { this._CLASSES = require('./dns-sd-classes.json'); this._CLASS_MAP = {}; Object.keys(this._CLASSES).forEach((k) => { this._CLASS_MAP[this._CLASSES[k]] = k; }); this._TYPES = require('./dns-sd-types.json'); this._TYPE_MAP = {}; Object.keys(this._TYPES).forEach((k) => { this._TYPE_MAP[this._TYPES[k]] = k; }); }; /* ------------------------------------------------------------------ * Method: parse(buf) * ---------------------------------------------------------------- */ DnsSdParser.prototype.parse = function(buf) { if(buf.length <= 12) { return null; } let header = { id: buf.readUInt16BE(0), qr: buf.readUInt8(2) >> 7, // 0: Query, 1: Response op: (buf.readUInt8(2) & 0b01111000) >> 3, // 0: Normal query, 4: Notify, 5: Update aa: (buf.readUInt8(2) & 0b00000100) >> 2, tc: (buf.readUInt8(2) & 0b00000010) >> 1, rd: (buf.readUInt8(2) & 0b00000001), ra: (buf.readUInt8(3) & 0b10000000) >> 7, z : (buf.readUInt8(3) & 0b01000000) >> 6, ad: (buf.readUInt8(3) & 0b00100000) >> 6, cd: (buf.readUInt8(3) & 0b00010000) >> 5, rc: (buf.readUInt8(3) & 0b00001111), questions : buf.readUInt16BE(4), answers : buf.readUInt16BE(6), authorities: buf.readUInt16BE(8), additionals: buf.readUInt16BE(10) }; if(header['tc'] !== 0 || header['rd'] !== 0 || header['ra'] !== 0 || header['z'] !== 0 || header['ad'] !== 0 || header['cd'] !== 0 || header['rc'] !== 0) { return null; } if(header['questions'] + header['answers'] + header['authorities'] + header['additionals'] === 0) { return null; } let offset = 12; let records = { header: header, questions: [], answers: [], authorities: [], additionals: [] }; let record_count_list = []; ['questions', 'answers', 'authorities', 'additionals'].forEach((k) => { let cnt = header[k]; if(cnt > 0) { record_count_list.push({name: k, count: cnt}); } }); let invalid = false; while(true) { let domain_name_parts = []; let parsed = this._parseLabel(buf, offset); let domain_name = ''; if(parsed) { offset += parsed['length']; domain_name = parsed['name']; } else { invalid = true; break; } let record_key = record_count_list[0]['name']; if(record_key === 'questions') { if(offset + 4 > buf.length) { invalid = true; break; } let type_value = buf.readUInt16BE(offset); let type = this._TYPE_MAP[type_value]; offset += 2; let cls_value = buf.readUInt16BE(offset); let cls = this._CLASS_MAP[cls_value]; offset += 2; records[record_key].push({ name : domain_name, type : type || '', class : cls || '' }); } else { if(offset + 10 > buf.length) { invalid = true; break; } let type_value = buf.readUInt16BE(offset); let type = this._TYPE_MAP[type_value] || ''; offset += 2; let cls_value = buf.readUInt16BE(offset); let cls = this._CLASS_MAP[cls_value & 0b0111111111111111]; let flash = (cls_value & 0b1000000000000000) ? true : false; offset += 2; let ttl = buf.readUInt32BE(offset); offset += 4; let rdlen = buf.readUInt16BE(offset); offset += 2; if(offset + rdlen > buf.length) { invalid = true; break; } let rdata = ''; let rdbuf = buf.slice(offset, offset + rdlen); let rdata_txt_buffer = null; if(type === 'A') { rdata = this._parseRdataA(buf, offset, rdlen); } else if(type === 'AAAA') { rdata = this._parseRdataAAAA(buf, offset, rdlen); } else if(type === 'PTR') { rdata = this._parseRdataPtr(buf, offset, rdlen); } else if(type === 'TXT') { rdata = this._parseRdataTxt(buf, offset, rdlen); rdata_txt_buffer = this._parseRdataTxt(buf, offset, rdlen, true); } else if(type === 'SRV') { rdata = this._parseRdataSrv(buf, offset, rdlen); } else if(type === 'HINFO') { rdata = this._parseRdataHinfo(buf, offset, rdlen); } else { rdata = this._parseRdataOther(buf, offset, rdlen); } offset += rdlen; if(!rdata) { invalid = true; break; } let d = { name : domain_name, type : type || '', class : cls || '', flash : flash, ttl : ttl, rdata : rdata }; if(rdata_txt_buffer) { d['rdata_buffer'] = rdata_txt_buffer; } records[record_key].push(d); } if(offset >= buf.length) { break; } record_count_list[0]['count'] --; if(record_count_list[0]['count'] <= 0) { record_count_list.shift(); } if(record_count_list.length === 0) { break; } } if(invalid === true) { return null; } else { return records; }; }; DnsSdParser.prototype._parseLabel = function(buf, offset) { let labels = []; let length = 0; let invalid = false; while(true) { let label_len = buf.readUInt8(offset + length); if((label_len & 0b11000000) === 0b11000000) { let i = buf.readUInt16BE(offset + length) & 0b0011111111111111; let parsed = this._parseLabel(buf, i); if(parsed) { labels.push(parsed['name']); length += 2; break; } else { invalid = true; break; } } else if((label_len & 0b11000000) === 0b00000000) { length += 1; if(label_len === 0x00) { break; } else if(offset + length + label_len <= buf.length) { let label = buf.slice(offset + length, offset + length + label_len).toString('utf8'); labels.push(label); length += label_len; } else { invalid = true; break; } } else { invalid = true; break; } } if(invalid === true) { return null; } else { return { name : labels.join('.'), length : length }; } }; DnsSdParser.prototype._parseRdataA = function(buf, offset, len) { let addr_parts = []; for(let i=0; i<len; i++) { addr_parts.push(buf.readUInt8(offset + i)); } let addr = addr_parts.join('.'); return addr; }; DnsSdParser.prototype._parseRdataAAAA = function(buf, offset, len) { let addr_parts = []; for(let i=0; i<len; i+=2) { addr_parts.push(buf.slice(offset + i, offset + i + 2).toString('hex')); } let addr = addr_parts.join(':'); return addr; }; DnsSdParser.prototype._parseRdataPtr = function(buf, offset, len) { let parsed = this._parseLabel(buf, offset); if(parsed) { return parsed['name']; } else { return null; } }; DnsSdParser.prototype._parseRdataTxt = function(buf, offset, len, buf_flag) { let labels = {}; let i = 0; while(true) { if(i >= len) { break; } let blen = buf.readUInt8(offset + i); i += 1; if(i + blen <= len) { let pair = buf.slice(offset + i, offset + i + blen).toString('utf8'); let m = pair.match(/^([^\=]+)\=(.*)/); if(m) { labels[m[1]] = m[2]; if(buf_flag) { let s = offset + i + m[1].length + 1; let e = offset + i + blen; labels[m[1]] = buf.slice(s, e); } } i += blen; } else { break; } } return labels; }; DnsSdParser.prototype._parseRdataSrv = function(buf, offset, len) { if(len <= 6) { return null; } let target = null; let parsed = this._parseLabel(buf, offset + 6); if(parsed) { target = parsed['name']; } return { priority : buf.readUInt16BE(offset), weight : buf.readUInt16BE(offset + 2), port : buf.readUInt16BE(offset + 4), target : target }; }; DnsSdParser.prototype._parseRdataHinfo = function(buf, offset, len) { let txt_list = []; let i = 0; while(true) { if(i >= len) { break; } let blen = buf.readUInt8(offset + i); i += 1; if(i + blen <= len) { let v = buf.slice(offset + i, offset + i + blen).toString('utf8'); txt_list.push(v); i += blen; } else { break; } } let info = {}; if(txt_list[0]) { info['cpu'] = txt_list[0]; } if(txt_list[1]) { info['os'] = txt_list[1]; } return info; }; DnsSdParser.prototype._parseRdataOther = function(buf, offset, len) { let rdata_parts = []; for(let i=0; i<len; i++) { rdata_parts.push(buf.slice(offset + i, offset + i + 1).toString('hex')); } let rdata = rdata_parts.join(' '); return rdata; }; module.exports = new DnsSdParser();