-
Notifications
You must be signed in to change notification settings - Fork 144
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 0c70bfd
Showing
10 changed files
with
317 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
language: node_js | ||
node_js: | ||
- '4' | ||
- '0.12' | ||
- '0.10' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2015 Thomas Watson Steen | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
'use strict' | ||
|
||
exports.tcp = require('./tcp') | ||
exports.udp = require('./udp') | ||
exports.unpublishAll = require('./lib/registry').unpublishAll |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
'use strict' | ||
|
||
var registry = {} | ||
|
||
var mdns = require('./mdns') | ||
|
||
mdns.on('query', respondToQuery) | ||
|
||
exports.register = function (records) { | ||
if (Array.isArray(records)) records.forEach(register) | ||
else register(records) | ||
|
||
function register (record) { | ||
// TODO: Should the registry be able to hold two records with the | ||
// same name and type? Or should the new record replace the old? | ||
var subRegistry = registry[record.type] | ||
if (!subRegistry) subRegistry = registry[record.type] = [] | ||
subRegistry.push(record) | ||
} | ||
} | ||
|
||
exports.unregister = function (records) { | ||
if (Array.isArray(records)) records.forEach(unregister) | ||
else unregister(records) | ||
|
||
function unregister (record) { | ||
var type = record.type | ||
if (!(type in registry)) return | ||
registry[type] = registry[type].filter(function (r) { | ||
return r.name !== record.name | ||
}) | ||
} | ||
} | ||
|
||
function respondToQuery (query) { | ||
query.questions.forEach(function (question) { | ||
var type = question.type | ||
var name = question.name | ||
var records = type === 'ANY' | ||
? flatten(Object.keys(registry).map(recordsFor.bind(null, name))) // TODO: In case of ANY, should the PTR records be the primary records and everything else should be listed as additionals? | ||
: recordsFor(type, name) | ||
|
||
if (records.length === 0) return | ||
|
||
// TODO: When responding to PTR queries, the additionals array should be | ||
// populated with the related SRV and TXT records | ||
|
||
mdns.respond(records, function (err) { | ||
if (err) throw err // TODO: Handle this (if no callback is given, the error will be ignored) | ||
}) | ||
}) | ||
} | ||
|
||
function recordsFor (name, type) { | ||
if (!(type in registry)) return [] | ||
return registry[type].filter(function (record) { | ||
return record.name === name | ||
}) | ||
} | ||
|
||
function flatten (arr) { | ||
return [].concat.apply.bind([].concat, [])(arr) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
'use strict' | ||
|
||
module.exports = require('multicast-dns')() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
'use strict' | ||
|
||
var mdns = require('./mdns') | ||
var server = require('./mdns-server') | ||
var Service = require('./service') | ||
|
||
var services = [] | ||
|
||
var REANNOUNCE_MAX_MS = 60 * 60 * 1000 | ||
var REANNOUNCE_FACTOR = 3 | ||
|
||
exports.publish = function publish (protocol, opts) { | ||
if (typeof opts === 'string') return publish(protocol, { type: opts, port: arguments[2] }) | ||
if (!opts.type) throw new Error('Required type not given') | ||
if (!opts.port) throw new Error('Required port not given') | ||
|
||
var service = new Service(opts) | ||
service.unpublish = function (cb) { | ||
teardown(service, cb) | ||
var index = services.indexOf(service) | ||
if (index !== -1) services.splice(index, 1) | ||
} | ||
services.push(service) | ||
|
||
// TODO: The RFC allows that probing can be optional if it's know that | ||
// the name is unique or in some way is already owned by the service (but | ||
// maybe the class then need to change) | ||
probe(service, function (exists) { | ||
if (exists) throw new Error('Service name is already in use on the network') // TODO: Handle this. Maybe implement fallback option to auto-increment a number trailing the name | ||
announce(service) | ||
}) | ||
|
||
return service | ||
} | ||
|
||
exports.unpublishAll = function () { | ||
teardown(services) | ||
services = [] | ||
} | ||
|
||
/** | ||
* Check if a service name is already in use on the network. | ||
* | ||
* Used before announcing the new service. | ||
* | ||
* To guard against race conditions where multiple services are started | ||
* simultaneously on the network, wait a random amount of time (between | ||
* 0 and 250 ms) before probing. | ||
* | ||
* TODO: Add support for Simultaneous Probe Tiebreaking: | ||
* https://tools.ietf.org/html/rfc6762#section-8.2 | ||
*/ | ||
function probe (service, cb) { | ||
var sent = false | ||
var retries = 0 | ||
var retryTimer | ||
|
||
mdns.on('response', onresponse) | ||
setTimeout(send, Math.random() * 250) | ||
|
||
function send () { | ||
// abort if the service have been unpublished in the meantime | ||
if (!service.published) return | ||
|
||
mdns.query(service.name, 'ANY', function () { | ||
// This function will optionally be called with an error object. We'll | ||
// just silently ignore it and retry as we normally would | ||
// TODO: Maybe we should not just ignore it we have no successfull probes at all? | ||
sent = true | ||
if (++retries < 3) retryTimer = setTimeout(send, 250) | ||
else done() | ||
}) | ||
} | ||
|
||
function onresponse (packet) { | ||
// Apparently conflicting Multicast DNS responses received *before* | ||
// the first probe packet is sent MUST be silently ignored (see | ||
// discussion of stale probe packets in RFC 6762 Section 8.2, | ||
// "Simultaneous Probe Tiebreaking" at | ||
// https://tools.ietf.org/html/rfc6762#section-8.2 | ||
if (!sent) return | ||
|
||
if (packet.answers.some(exists) || packet.additionals.some(exists)) done(true) | ||
} | ||
|
||
function exists (answer) { | ||
return answer.name === service.name | ||
} | ||
|
||
function done (exists) { | ||
mdns.removeListener('response', onresponse) | ||
clearTimeout(retryTimer) | ||
cb(!!exists) | ||
} | ||
} | ||
|
||
/** | ||
* Initial service announcement | ||
* | ||
* Used to announce new services when they are first registered. | ||
* | ||
* Broadcasts right away, then after 3 seconds, 9 seconds, 27 seconds, | ||
* and so on, up to a maximum interval og one hour. | ||
*/ | ||
function announce (service) { | ||
var delay = 1000 | ||
var packet = service.records() | ||
|
||
server.register(packet) | ||
|
||
;(function broadcast () { | ||
// abort if the service have been unpublished in the meantime | ||
if (!service.published) return | ||
|
||
mdns.respond(packet, function () { | ||
// This function will optionally be called with an error object. We'll | ||
// just silently ignore it and retry as we normally would | ||
delay = delay * REANNOUNCE_FACTOR | ||
if (delay < REANNOUNCE_MAX_MS) setTimeout(broadcast, delay) | ||
}) | ||
})() | ||
} | ||
|
||
/** | ||
* Stop a given service | ||
* | ||
* Besides removing the service from the mDNS registry, it's recommended to | ||
* send a "goodbye" message to let the network know about the shutdown. | ||
*/ | ||
function teardown (services, cb) { | ||
if (!Array.isArray(service)) services = [services] | ||
|
||
var records = flatten(services | ||
.filter(function (service) { | ||
return service.published | ||
}) | ||
.map(function (service) { | ||
service.published = false | ||
var records = service.records() | ||
records.forEach(function (record) { | ||
record.ttl = 0 | ||
}) | ||
return records | ||
})) | ||
|
||
if (records.length === 0) return | ||
|
||
server.unregister(records) | ||
mdns.respond(records, cb) | ||
} | ||
|
||
function flatten (arr) { | ||
return [].concat.apply.bind([].concat, [])(arr) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
'use strict' | ||
|
||
var os = require('os') | ||
var serviceName = require('multicast-dns-service-types') | ||
var mdns = require('./mdns') | ||
|
||
var TLD = '.local' | ||
var hostname = os.hostname() | ||
|
||
var Service = module.exports = function (opts) { | ||
this.name = opts.name || hostname.replace(/\.local\.?$/, '') | ||
this.type = serviceName.stringify(opts.type, opts.protocol) | ||
this.port = opts.port | ||
this.fqdn = this.name + '.' + this.type + TLD, // TODO: Encode illegal chars in name | ||
this.txt = opts.txt | ||
this.published = true | ||
} | ||
|
||
Service.prototype.records = function () { | ||
return [rr_ptr(this), rr_srv(this), rr_txt(this)] | ||
} | ||
|
||
function rr_ptr (service) { | ||
return { | ||
name: service.type + TLD, | ||
type: 'PTR', | ||
ttl: 28800, | ||
data: service.fqdn | ||
} | ||
} | ||
|
||
function rr_srv (service) { | ||
return { | ||
name: service.fqdn, | ||
type: 'SRV', | ||
ttl: 120, | ||
data: { | ||
port: service.port, | ||
target: hostname | ||
} | ||
} | ||
} | ||
|
||
function rr_txt (service) { | ||
return { | ||
name: service.fqdn, | ||
type: 'TXT', | ||
ttl: 4500, | ||
data: service.txt | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
'use strict' | ||
|
||
var publish = require('./lib/registry').publish | ||
|
||
var PROTOCOL = 'tcp' | ||
|
||
exports.publish = publish.bind(null, PROTOCOL) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
'use strict' | ||
|
||
var publish = require('./lib/registry').publish | ||
|
||
var PROTOCOL = 'udp' | ||
|
||
exports.publish = publish.bind(null, PROTOCOL) |