Comparing version 0.1.3 to 0.1.4
var debug = require('debug')('mdns:advertisement'); | ||
var dgram = require('dgram'); | ||
var os = require('os'); | ||
var DNSPacket = require('./dnspacket'); | ||
var DNSRecord = require('./dnsrecord'); | ||
var ServiceType = require('./service_type').ServiceType; | ||
var internal = {}; | ||
internal.buildDNSPacket = function (ttl) { | ||
internal.buildQDPacket = function () { | ||
var packet = new DNSPacket(); | ||
var name = this.options.name + this.nameSuffix; | ||
var domain = this.options.domain || 'local'; | ||
var serviceType = this.serviceType.toString() + '.' + domain; | ||
this.alias = name + '.' + serviceType; | ||
packet.push('qd', new DNSRecord(this.alias, DNSRecord.Type.ANY, 1)); | ||
return packet; | ||
}; | ||
internal.buildANPacket = function (ttl) { | ||
var packet = | ||
new DNSPacket(DNSPacket.Flag.RESPONSE | DNSPacket.Flag.AUTHORATIVE); | ||
var name = this.options.name; | ||
var address = name + '.local'; | ||
if ('host' in this.options) { | ||
address = this.options.host; | ||
} | ||
var serviceType = this.serviceType.toString() + '.local'; | ||
var alias = name + '.' + serviceType; | ||
var name = this.options.name + this.nameSuffix; | ||
var domain = this.options.domain || 'local'; | ||
var target = (this.options.host || name) + '.' + domain; | ||
var serviceType = this.serviceType.toString() + '.' + domain; | ||
var cl = DNSRecord.Class.IN | DNSRecord.Class.FLUSH; | ||
debug('serviceType:', serviceType); | ||
debug('alias:', this.alias); | ||
packet.push('an', new DNSRecord( | ||
serviceType, DNSRecord.Type.PTR, 1, ttl, | ||
DNSRecord.toName(alias))); | ||
serviceType, DNSRecord.Type.PTR, cl, ttl, DNSRecord.toName(this.alias))); | ||
packet.push('an', new DNSRecord( | ||
alias, DNSRecord.Type.SRV, 1, ttl, | ||
DNSRecord.toSrv(0, 0, this.port, address))); | ||
this.alias, DNSRecord.Type.SRV, cl, ttl, | ||
DNSRecord.toSrv(0, 0, this.port, target))); | ||
// TODO: https://github.com/agnat/node_mdns/blob/master/lib/advertisement.js | ||
// has 'txtRecord' | ||
if ('txt' in this.options) { | ||
packet.push('an', new DNSRecord( | ||
alias, DNSRecord.Type.TXT, 1, ttl, | ||
this.alias, DNSRecord.Type.TXT, cl, ttl, | ||
DNSRecord.toTxt(this.options.txt))); | ||
} | ||
var interfaces = os.networkInterfaces(); | ||
var ifaceFilter = this.options.networkInterface; | ||
for (var key in interfaces) { | ||
if (typeof ifaceFilter === 'undefined' || key === ifaceFilter) { | ||
debug('add A record for interface:' , key); | ||
for (var i = 0; i < interfaces[key].length; i++) { | ||
var address = interfaces[key][i].address; | ||
if (address.indexOf(':') === -1) { | ||
packet.push('an', new DNSRecord( | ||
target, DNSRecord.Type.A, cl, ttl, DNSRecord.toA(address))); | ||
} else { | ||
// TODO: also publish the ip6_address in an AAAA record | ||
} | ||
} | ||
} | ||
} | ||
return packet; | ||
}; | ||
internal.sendDNSPacket = function (packet) { | ||
internal.sendDNSPacket = function (packet, cb) { | ||
var buf = packet.toBuffer(); | ||
@@ -49,5 +78,7 @@ | ||
} | ||
sock.setMulticastTTL(255); | ||
sock.send(buf, 0, buf.length, 5353, '224.0.0.251', function (err, bytes) { | ||
debug('sent %d bytes with err:%s', bytes, err); | ||
sock.close(); | ||
typeof cb === 'function' && cb(); | ||
}); | ||
@@ -57,2 +88,184 @@ }); | ||
// Array of published services. | ||
internal.services = []; | ||
// Array of pending probes. | ||
internal.probes = []; | ||
// Array of open sockets | ||
internal.connections = []; | ||
internal.haveResponder = function () { | ||
return (internal.services.length !== 0 || internal.probes.length !== 0); | ||
}; | ||
internal.startResponder = function () { | ||
var interfaces = os.networkInterfaces(); | ||
var index = 0; | ||
for (var key in interfaces) { | ||
if (interfaces.hasOwnProperty(key)) { | ||
for (var i = 0; i < interfaces[key].length; i++) { | ||
var address = interfaces[key][i].address; | ||
debug('interface', key, interfaces[key]); | ||
//no IPv6 addresses | ||
if (address.indexOf(':') !== -1) { | ||
continue; | ||
} | ||
// these are for unicast queries ? | ||
createSocket(index++, key, address, 0, bindToAddress.bind(this)); | ||
} | ||
} | ||
} | ||
// this is for multicast queries ? | ||
createSocket(index++, key, '224.0.0.251', 5353, bindToAddress.bind(this)); | ||
function createSocket(interfaceIndex, networkInterface, address, port, cb) { | ||
var sock = dgram.createSocket('udp4'); | ||
debug('creating socket for interface %s', address); | ||
sock.bind(port, address, function (err) { | ||
cb(err, interfaceIndex, networkInterface, sock); | ||
}); | ||
} | ||
function bindToAddress (err, interfaceIndex, networkInterface, sock) { | ||
if (err) { | ||
debug('there was an error binding %s', err); | ||
return; | ||
} | ||
debug('bindToAddress'); | ||
internal.connections.push(sock); | ||
sock.on('message', function (message, remote) { | ||
debug('got packet from remote', remote); | ||
var packet; | ||
try { | ||
packet = DNSPacket.parse(message); | ||
} catch (err) { | ||
debug('got packet truncated package, ignoring'); | ||
return; | ||
} | ||
// check if it is a query where we are the authority for | ||
packet.each('qd', handleQuery.bind(this)); | ||
packet.each('an', handleAnswer.bind(this)); | ||
}.bind(this)); | ||
sock.on('error', function (err) { | ||
debug('socket error', err); | ||
}); | ||
} | ||
function handleQuery(rec) { | ||
if (rec.type !== DNSRecord.Type.PTR && | ||
rec.type !== DNSRecord.Type.ANY) { | ||
debug('skipping query: type not PTR/ANY'); | ||
return; | ||
} | ||
// check if we should reply via multi or unicast | ||
// TODO: handle the is_qu === true case and reply directly to remote | ||
// var is_qu = (rec.cl & DNSRecord.Class.IS_QM) === DNSRecord.Class.IS_QM; | ||
rec.cl &= ~DNSRecord.Class.IS_OM; | ||
if (rec.cl !== DNSRecord.Class.IN && rec.type !== DNSRecord.Class.ANY) { | ||
debug('skipping query: class not IN/ANY'); | ||
return; | ||
} | ||
try { | ||
var type = new ServiceType(rec.name); | ||
internal.services.forEach(function (service) { | ||
if (type.isWildcard() || type.matches(service.serviceType)) { | ||
debug('answering query'); | ||
// TODO: should we only send PTR records if the query was for PTR | ||
// records? | ||
internal.sendDNSPacket( | ||
internal.buildANPacket.apply(service, [DNSRecord.TTL])); | ||
} else { | ||
debug('skipping query; type %s not * or %s', type, | ||
service.serviceType); | ||
} | ||
}); | ||
} catch (err) { | ||
// invalid service type | ||
} | ||
} | ||
function handleAnswer(rec) { | ||
try { | ||
internal.probes.forEach(function (service) { | ||
if (service.status < 3) { | ||
var conflict = false; | ||
// parse answers and check if they match a probe | ||
debug('check names: %s and %s', rec.name, service.alias); | ||
switch (rec.type) { | ||
case DNSRecord.Type.PTR: | ||
if (rec.asName() === service.alias) { | ||
conflict = true; | ||
debug('name conflict in PTR'); | ||
} | ||
break; | ||
case DNSRecord.Type.SRV: | ||
case DNSRecord.Type.TXT: | ||
if (rec.name === service.alias) { | ||
conflict = true; | ||
debug('name conflict in SRV/TXT'); | ||
} | ||
break; | ||
} | ||
if (conflict) { | ||
// no more probes | ||
service.status = 4; | ||
} | ||
} | ||
}); | ||
} catch (err) { | ||
// invalid service type | ||
} | ||
} | ||
}; | ||
internal.stopResponder = function () { | ||
debug('stopping %d sockets', internal.connections.length); | ||
for (var i = 0; i < internal.connections.length; i++) { | ||
var sock = internal.connections[i]; | ||
sock.close(); | ||
sock.unref(); | ||
} | ||
internal.connections = []; | ||
}; | ||
internal.probeAndAdvertise = function () { | ||
switch (this.status) { | ||
case 0: | ||
case 1: | ||
case 2: | ||
debug('probing service %d', this.status + 1); | ||
internal.sendDNSPacket(internal.buildQDPacket.apply(this, [])); | ||
break; | ||
case 3: | ||
debug('publishing service, suffix=%s', this.nameSuffix); | ||
internal.sendDNSPacket( | ||
internal.buildANPacket.apply(this, [DNSRecord.TTL])); | ||
// Repost announcement after 1sec (see rfc6762: 8.3) | ||
setTimeout(function onTimeout() { | ||
internal.sendDNSPacket( | ||
internal.buildANPacket.apply(this, [DNSRecord.TTL])); | ||
}.bind(this), 1000); | ||
// Service has been registered, repond to matching queries | ||
internal.services.push(this); | ||
internal.probes = | ||
internal.probes.filter(function (service) { return service === this; }); | ||
break; | ||
case 4: | ||
// we had a conflict | ||
if (this.nameSuffix === '') { | ||
this.nameSuffix = '1'; | ||
} else { | ||
this.nameSuffix = (parseInt(this.nameSuffix) + 1) + ''; | ||
} | ||
this.status = -1; | ||
break; | ||
} | ||
if (this.status < 3) { | ||
this.status++; | ||
setTimeout(internal.probeAndAdvertise.bind(this), 250); | ||
} | ||
}; | ||
/** | ||
@@ -77,2 +290,5 @@ * mDNS Advertisement class | ||
this.options = options; | ||
this.nameSuffix = ''; | ||
this.alias = ''; | ||
this.status = 0; // inactive | ||
debug('created new service'); | ||
@@ -82,7 +298,7 @@ }; //--Advertisement constructor | ||
Advertisement.prototype.start = function () { | ||
debug('publishing service'); | ||
internal.sendDNSPacket(internal.buildDNSPacket.apply(this, [DNSRecord.TTL])); | ||
setTimeout(function onTimeout() { | ||
this.start(); | ||
}.bind(this), DNSRecord.TTL * 950); // TTL is seconds, need ms | ||
if (!internal.haveResponder()) { | ||
internal.startResponder.apply(this, []); | ||
} | ||
internal.probes.push(this); | ||
internal.probeAndAdvertise.apply(this, []); | ||
}; | ||
@@ -92,3 +308,11 @@ | ||
debug('unpublishing service'); | ||
internal.sendDNSPacket(internal.buildDNSPacket.apply(this, [0])); | ||
internal.services = | ||
internal.services.filter(function (service) { return service === this; }); | ||
if (!internal.haveResponder()) { | ||
internal.stopResponder.apply(this, []); | ||
} | ||
internal.sendDNSPacket(internal.buildANPacket.apply(this, [0])); | ||
this.nameSuffix = ''; | ||
this.alias = ''; | ||
this.status = 0; // inactive | ||
}; |
@@ -114,2 +114,6 @@ | ||
var info = sock.address(); | ||
if (info.address === '224.0.0.251') { | ||
sock.addMembership('224.0.0.251'); | ||
} | ||
var connection = { | ||
@@ -116,0 +120,0 @@ socket:sock, |
@@ -53,3 +53,3 @@ var debug = require('debug')('mdns:lib:bufferconsumer'); | ||
var len; | ||
this.ref = undefined; | ||
var savedOffset; | ||
for (;;) { | ||
@@ -63,9 +63,28 @@ try { | ||
break; | ||
} else if (len === 0xc0) { | ||
// TODO: This indicates a pointer to another valid name inside the | ||
// DNSPacket, and is always a suffix: we're at the end of the name. | ||
// We should probably hold onto this value instead of discarding it. | ||
this.ref = this.byte(); | ||
} else if ((len & 0xc0) === 0xc0) { | ||
debug('compressed label: bits=%d', ((len & 0xc0) >> 6)); | ||
// This indicates a pointer to another valid name inside the DNSPacket, | ||
// and is always a suffix: we're at the end of the name. | ||
// Store the current parse position and read the name from the offset. | ||
var offset = this.byte(); | ||
offset |= ((len & ~0xc0) << 8); | ||
if (offset > this._view.length) { | ||
debug('!! offset=%d beyond buffer-size=%d', offset, this._view.length); | ||
break; | ||
} | ||
if (typeof savedOffset === 'undefined') { | ||
savedOffset = this._offset; | ||
debug('saved offset=%d', savedOffset); | ||
} | ||
this._offset = offset; | ||
debug('continue at %d', offset); | ||
continue; | ||
} | ||
if ((this._offset + len) > this._view.length) { | ||
debug('!! offset + len=%d+%d=%d beyond buffer-size=%d', this._offset, | ||
len, (this._offset + len), this._view.length); | ||
break; | ||
} | ||
debug('reading string with len=%d at %d, remaining size=%d', len, | ||
this._offset, (this._view.length - this._offset)); | ||
@@ -77,4 +96,11 @@ // Otherwise, consume a string! | ||
} | ||
debug('single \'%s\', remaining size=%d', v, | ||
(this._view.length - this._offset)); | ||
parts.push(v); | ||
} | ||
if (typeof savedOffset !== 'undefined') { | ||
this._offset = savedOffset; | ||
savedOffset = undefined; | ||
} | ||
debug('all labels \'%s\'', parts.join('.')); | ||
if (join) { | ||
@@ -81,0 +107,0 @@ return parts.join('.'); |
@@ -39,6 +39,19 @@ var debug = require('debug')('mdns:lib:dnsrecord'); | ||
TXT: 0x10, // 16 | ||
AAAA: 28, // 0x16 | ||
SRV: 0x21 // 33 | ||
AAAA: 0x16, // 28 | ||
SRV: 0x21, // 33 | ||
ANY: 0xff // 255 | ||
}; | ||
/** | ||
* Enum for record class values | ||
* @readonly | ||
* @enum {number} | ||
*/ | ||
DNSRecord.Class = { | ||
IN: 0x01, | ||
ANY: 0xff, | ||
FLUSH: 0x8000, | ||
IS_QM: 0x8000 | ||
}; | ||
DNSRecord.TTL = 60 * 60; // one hour default TTL | ||
@@ -55,2 +68,3 @@ | ||
DNSRecord.prototype.asName = function () { | ||
debug('parse PTR'); | ||
return new DataConsumer(this.data_).name(); | ||
@@ -68,2 +82,3 @@ }; | ||
DNSRecord.prototype.asSrv = function () { | ||
debug('parse SRV'); | ||
var consumer = new DataConsumer(this.data_); | ||
@@ -89,2 +104,3 @@ return { | ||
DNSRecord.prototype.asTxt = function () { | ||
debug('parse TXT'); | ||
var consumer = new DataConsumer(this.data_); | ||
@@ -101,3 +117,14 @@ var data = {}; | ||
DNSRecord.toA = function (ip) { | ||
var out = new BufferWriter(); | ||
var parts = ip.split('.'); | ||
for (var i = 0; i < 4; i++) { | ||
out.byte(parts[i]); | ||
} | ||
return out.buf.slice(0, out.offset); | ||
}; | ||
DNSRecord.prototype.asA = function () { | ||
debug('parse A'); | ||
var consumer = new DataConsumer(this.data_); | ||
@@ -118,2 +145,3 @@ var data = ''; | ||
DNSRecord.prototype.asAAAA = function () { | ||
debug('parse AAAA'); | ||
var consumer = new DataConsumer(this.data_); | ||
@@ -120,0 +148,0 @@ var data = ''; |
@@ -65,2 +65,3 @@ var debug = require('debug')('mdns:lib:ServiceType'); | ||
debug('fromString', text); | ||
text = text.replace(/.local$/, ''); | ||
var isWildcard = text === ServiceType.wildcard; | ||
@@ -73,5 +74,12 @@ var subtypes = text.split(','); | ||
debug('primary: %s, servicetype: %s, serviceTokens: %s, subtypes: %s', | ||
primaryString, serviceType, serviceTokens.join('.'), subtypes.join(',')); | ||
if (isWildcard) { | ||
serviceType += '.' + serviceTokens.shift(); | ||
} | ||
if (primaryString[0] !== '_' || primaryString[0] === '_services') { | ||
serviceType = serviceTokens.shift(); | ||
} | ||
protocol = serviceTokens.shift(); | ||
@@ -98,2 +106,5 @@ //make tcp default if not already defined | ||
this.subtypes = subtypes.map(function (t) { return t.substr(1); }); | ||
debug('name: %s, protocol: %s, subtypes: %s', this.name, this.protocol, | ||
this.subtypes.join(',')); | ||
}; | ||
@@ -100,0 +111,0 @@ |
{ | ||
"name": "mdns-js", | ||
"version": "0.1.3", | ||
"version": "0.1.4", | ||
"repository": { | ||
@@ -5,0 +5,0 @@ "type": "git", |
@@ -44,2 +44,3 @@ | ||
var ptrCount = 0; | ||
var aCount = 0; | ||
var aaaaCount = 0; | ||
@@ -57,5 +58,9 @@ var srvCount = 0; | ||
packet.each('an', DNSRecord.Type.A, function (rec) { | ||
rec.asA(); | ||
aCount++; | ||
}); | ||
packet.each('an', DNSRecord.Type.AAAA, function (rec) { | ||
var aaaa = rec.asAAAA(); | ||
aaaa.should.equal('fe80:0:0:0:2ac6:8eff:fe34:b8c3'); | ||
rec.asAAAA(); | ||
aaaaCount++; | ||
@@ -65,8 +70,3 @@ }); | ||
packet.each('an', DNSRecord.Type.SRV, function (rec) { | ||
var value = rec.asSrv(); | ||
value.should.be.type('object'); | ||
value.should.have.property('priority', 0), | ||
value.should.have.property('weight', 0), | ||
value.should.have.property('port', 9), | ||
value.should.have.property('target', 'vestri'); | ||
rec.asSrv(); | ||
srvCount++; | ||
@@ -76,6 +76,3 @@ }); | ||
packet.each('an', DNSRecord.Type.TXT, function (rec) { | ||
var value = rec.asTxt(); | ||
value.should.be.type('object'); | ||
value.should.be.empty; | ||
rec.asTxt(); | ||
txtCount++; | ||
@@ -85,5 +82,6 @@ }); | ||
ptrCount.should.equal(4); | ||
aCount.should.equal(0, 'bad A count'); | ||
aaaaCount.should.equal(0, 'bad AAAA count'); | ||
srvCount.should.equal(0, 'bad SRV count'); | ||
txtCount.should.equal(0, 'bad TXT count'); | ||
srvCount.should.equal(0, 'bad SRV count'); | ||
done(); | ||
@@ -97,2 +95,3 @@ }); | ||
var ptrCount = 0; | ||
var aCount = 0; | ||
var aaaaCount = 0; | ||
@@ -109,5 +108,10 @@ var srvCount = 0; | ||
packet.each('an', DNSRecord.Type.A, function (rec) { | ||
var a = rec.asA(); | ||
a.should.equal('10.100.0.99'); | ||
aCount++; | ||
}); | ||
packet.each('an', DNSRecord.Type.AAAA, function (rec) { | ||
var aaaa = rec.asAAAA(); | ||
aaaa.should.equal('fe80:0:0:0:2ac6:8eff:fe34:b8c3'); | ||
rec.asAAAA(); | ||
aaaaCount++; | ||
@@ -133,5 +137,8 @@ }); | ||
if (ptrCount === 1 && aaaaCount === 1 && txtCount === 1 && srvCount === 1) { | ||
done(); | ||
} | ||
ptrCount.should.equal(1); | ||
aCount.should.equal(1, 'bad A count'); | ||
aaaaCount.should.equal(0, 'bad AAAA count'); | ||
srvCount.should.equal(1, 'bad SRV count'); | ||
txtCount.should.equal(1, 'bad TXT count'); | ||
done(); | ||
}); | ||
@@ -138,0 +145,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
69165
22
1590