From 8280e8d83b8c2414ff6d74f8fdfefcbb48b75010 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 8 Apr 2019 16:13:20 +0100 Subject: [PATCH 01/17] feat: compatibility with go-libp2p-mdns This PR adds a compatibility class that allows a js-libp2p node to find a go-libp2p node (and vice versa) over MDNS. It's implemented as a separate class so the two differing implementations do not get confused. I've verified this is working correctly by running a go-ipfs and js-ipfs node with no boostrap nodes (and no other discovery methods) and verifying they find each other. TODO: * [ ] Add tests! Some tips if you want to try this out: * After you've run `ipfs init`, remember to remove the bootstrap nodes from the config file (`~/.ipfs/config`) of each node before you start up * Use `ipfs log level mdns debug` for some go-ipfs mdns logs * You can use the following script (after `npm link`ing this branch) to start a js-ipfs node with no bootstrap nodes and no discovery modules other than MDNS: ```js const IPFS = require('ipfs') const MDNS = require('libp2p-mdns') const TCP = require('libp2p-tcp') const ipfs = new IPFS({ repo: '/tmp/ipfs-mdns', config: { Bootstrap: [] }, libp2p: { modules: { peerDiscovery: [MDNS], transport: [TCP] } } }) ipfs.on('ready', async () => { console.log('ipfs is ready') console.log('My Peer ID:', (await ipfs.id()).id) setInterval(async () => { const peers = await ipfs.swarm.peers() console.log(peers.length, 'peers:') peers.forEach(p => console.log(p.peer.toB58String())) }, 10000) }) ``` License: MIT Signed-off-by: Alan Shaw --- src/compat/constants.js | 4 ++ src/compat/index.js | 46 +++++++++++++ src/compat/querier.js | 144 ++++++++++++++++++++++++++++++++++++++++ src/compat/responder.js | 94 ++++++++++++++++++++++++++ src/index.js | 27 ++++++-- 5 files changed, 311 insertions(+), 4 deletions(-) create mode 100644 src/compat/constants.js create mode 100644 src/compat/index.js create mode 100644 src/compat/querier.js create mode 100644 src/compat/responder.js diff --git a/src/compat/constants.js b/src/compat/constants.js new file mode 100644 index 0000000..f54f350 --- /dev/null +++ b/src/compat/constants.js @@ -0,0 +1,4 @@ +exports.SERVICE_TAG = '_ipfs-discovery._udp' +exports.SERVICE_TAG_LOCAL = `${exports.SERVICE_TAG}.local` +exports.MULTICAST_IP = '224.0.0.251' +exports.MULTICAST_PORT = 5353 diff --git a/src/compat/index.js b/src/compat/index.js new file mode 100644 index 0000000..6c27a5b --- /dev/null +++ b/src/compat/index.js @@ -0,0 +1,46 @@ +'use strict' + +// Compatibility with Go libp2p MDNS + +const EE = require('events') +const parallel = require('async/parallel') +const Responder = require('./responder') +const Querier = require('./querier') + +class GoMulticastDNS extends EE { + constructor (peerInfo) { + super() + this._started = false + this._responder = new Responder(peerInfo) + this._querier = new Querier(peerInfo.id) + this._querier.on('peer', peerInfo => this.emit('peer', peerInfo)) + } + + start (callback) { + if (this._started) { + return callback(new Error('MulticastDNS service is already started')) + } + + this._started = true + + parallel([ + cb => this._responder.start(cb), + cb => this._querier.start(cb) + ], callback) + } + + stop (callback) { + if (!this._started) { + return callback(new Error('MulticastDNS service is not started')) + } + + this._started = false + + parallel([ + cb => this._responder.stop(cb), + cb => this._querier.stop(cb) + ], callback) + } +} + +module.exports = GoMulticastDNS diff --git a/src/compat/querier.js b/src/compat/querier.js new file mode 100644 index 0000000..f0e32ec --- /dev/null +++ b/src/compat/querier.js @@ -0,0 +1,144 @@ +'use strict' + +const assert = require('assert') +const EE = require('events') +const MDNS = require('multicast-dns') +const Multiaddr = require('multiaddr') +const PeerInfo = require('peer-info') +const PeerId = require('peer-id') +const log = require('debug')('libp2p:mdns:compat:querier') +const { SERVICE_TAG_LOCAL, MULTICAST_IP, MULTICAST_PORT } = require('./constants') + +class Querier extends EE { + constructor (peerId, options) { + super() + assert(peerId, 'missing peerId parameter') + this._peerIdStr = peerId.toB58String() + this._options = options || {} + this._options.queryPeriod = this._options.queryPeriod || 5000 + this._onResponse = this._onResponse.bind(this) + } + + start (callback) { + this._handle = periodically(() => { + // Create a querier that queries multicast but gets responses unicast + const querier = MDNS({ multicast: false, interface: '0.0.0.0', port: 0 }) + + querier.on('response', this._onResponse) + + querier.query({ + id: nextId(), // id > 0 for unicast response + questions: [{ name: SERVICE_TAG_LOCAL, type: 'PTR', class: 'IN' }] + }, null, { + address: MULTICAST_IP, + port: MULTICAST_PORT + }) + + return { stop: callback => querier.destroy(callback) } + }, this._options.queryPeriod) + + setImmediate(() => callback()) + } + + _onResponse (event, info) { + const answers = event.answers || [] + const ptrRecord = answers.find(a => a.type === 'PTR' && a.name === SERVICE_TAG_LOCAL) + + // Only deal with responses for our service tag + if (!ptrRecord) return + + log('got response', event, info) + + const txtRecord = answers.find(a => a.type === 'TXT') + if (!txtRecord) return log('missing TXT record in response') + + let peerIdStr + try { + peerIdStr = txtRecord.data[0].toString() + } catch (err) { + return log('failed to extract peer ID from TXT record data', txtRecord, err) + } + + if (this._peerIdStr === peerIdStr) return // replied to myself, ignore + + let peerId + try { + peerId = PeerId.createFromB58String(peerIdStr) + } catch (err) { + return log('failed to create peer ID from TXT record data', peerIdStr, err) + } + + PeerInfo.create(peerId, (err, info) => { + if (err) return log('failed to create peer info from peer ID', peerId, err) + + const srvRecord = answers.find(a => a.type === 'SRV') + if (!srvRecord) return log('missing SRV record in response') + + log('peer found', peerIdStr) + + const { port } = srvRecord.data || {} + const protos = { A: 'ip4', AAAA: 'ip6' } + + const multiaddrs = answers + .filter(a => ['A', 'AAAA'].includes(a.type)) + .reduce((addrs, a) => { + const maStr = `/${protos[a.type]}/${a.data}/tcp/${port}` + try { + addrs.push(new Multiaddr(maStr)) + log(maStr) + } catch (err) { + log(`failed to create multiaddr from ${a.type} record data`, maStr, port, err) + } + return addrs + }, []) + + multiaddrs.forEach(addr => info.multiaddrs.add(addr)) + this.emit('peer', info) + }) + } + + stop (callback) { + this._handle.stop(callback) + } +} + +module.exports = Querier + +function periodically (run, period) { + let handle, timeoutId + let stopped = false + + const reRun = () => { + handle = run() + timeoutId = setTimeout(() => { + handle.stop(err => { + if (err) log(err) + if (!stopped) reRun() + }) + handle = null + }, period) + } + + reRun() + + return { + stop (callback) { + stopped = true + clearTimeout(timeoutId) + if (handle) { + handle.stop(callback) + } else { + callback() + } + } + } +} + +const nextId = (() => { + let id = 1 + return () => { + id++ + if (id === Number.MAX_SAFE_INTEGER) id = 1 + return id + } +})() diff --git a/src/compat/responder.js b/src/compat/responder.js new file mode 100644 index 0000000..782fdfd --- /dev/null +++ b/src/compat/responder.js @@ -0,0 +1,94 @@ +'use strict' + +const OS = require('os') +const assert = require('assert') +const MDNS = require('multicast-dns') +const log = require('debug')('libp2p:mdns:compat:responder') +const TCP = require('libp2p-tcp') +const tcp = new TCP() +const { SERVICE_TAG_LOCAL } = require('./constants') + +class Responder { + constructor (peerInfo) { + assert(peerInfo, 'missing peerInfo parameter') + this._peerInfo = peerInfo + this._peerIdStr = peerInfo.id.toB58String() + this._onQuery = this._onQuery.bind(this) + } + + start (callback) { + this._mdns = MDNS() + this._mdns.on('query', this._onQuery) + setImmediate(() => callback()) + } + + _onQuery (event, info) { + const multiaddrs = tcp.filter(this._peerInfo.multiaddrs.toArray()) + // Only announce TCP for now + if (!multiaddrs.length) return + + const questions = event.questions || [] + + // Only respond to queires for our service tag + if (!questions.some(q => q.name === SERVICE_TAG_LOCAL)) return + + log('got query', event, info) + + const answers = [] + const peerServiceTagLocal = `${this._peerIdStr}.${SERVICE_TAG_LOCAL}` + + answers.push({ + name: SERVICE_TAG_LOCAL, + type: 'PTR', + class: 'IN', + ttl: 120, + data: peerServiceTagLocal + }) + + // Only announce TCP multiaddrs for now + const port = multiaddrs[0].toString().split('/')[4] + + answers.push({ + name: peerServiceTagLocal, + type: 'SRV', + class: 'IN', + ttl: 120, + data: { + priority: 10, + weight: 1, + port, + target: OS.hostname() + } + }) + + answers.push({ + name: peerServiceTagLocal, + type: 'TXT', + class: 'IN', + ttl: 120, + data: this._peerIdStr + }) + + multiaddrs.forEach((ma) => { + const proto = ma.protoNames()[0] + if (proto === 'ip4' || proto === 'ip6') { + answers.push({ + name: OS.hostname(), + type: proto === 'ip4' ? 'A' : 'AAAA', + class: 'IN', + ttl: 120, + data: ma.toString().split('/')[2] + }) + } + }) + + log('responding to query', answers) + this._mdns.respond(answers) + } + + stop (callback) { + this._mdns.destroy(callback) + } +} + +module.exports = Responder diff --git a/src/index.js b/src/index.js index d2c3ef3..b0d0cff 100644 --- a/src/index.js +++ b/src/index.js @@ -3,9 +3,12 @@ const multicastDNS = require('multicast-dns') const EventEmitter = require('events').EventEmitter const assert = require('assert') +const setImmediate = require('async/setImmediate') +const parallel = require('async/parallel') const debug = require('debug') const log = debug('libp2p:mdns') const query = require('./query') +const GoMulticastDNS = require('./compat') class MulticastDNS extends EventEmitter { constructor (options) { @@ -18,6 +21,10 @@ class MulticastDNS extends EventEmitter { this.port = options.port || 5353 this.peerInfo = options.peerInfo this._queryInterval = null + + if (options.compat !== false) { + this._goMdns = new GoMulticastDNS(options.peerInfo) + } } start (callback) { @@ -42,15 +49,27 @@ class MulticastDNS extends EventEmitter { query.gotQuery(event, this.mdns, this.peerInfo, this.serviceTag, this.broadcast) }) - setImmediate(() => callback()) + if (this._goMdns) { + this._goMdns.start(callback) + } else { + setImmediate(() => callback()) + } } stop (callback) { if (!this.mdns) { - callback(new Error('MulticastDNS service had not started yet')) + return callback(new Error('MulticastDNS service had not started yet')) + } + + clearInterval(this._queryInterval) + this._queryInterval = null + + if (this._goMdns) { + parallel([ + cb => this._goMdns.stop(cb), + cb => this.mdns.destroy(cb) + ], callback) } else { - clearInterval(this._queryInterval) - this._queryInterval = null this.mdns.destroy(callback) this.mdns = undefined } From 0320312afbe37fb3e7335828401da6eabd0b3f9e Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 8 Apr 2019 16:16:50 +0100 Subject: [PATCH 02/17] chore: appease linter License: MIT Signed-off-by: Alan Shaw --- src/compat/constants.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compat/constants.js b/src/compat/constants.js index f54f350..df83a3f 100644 --- a/src/compat/constants.js +++ b/src/compat/constants.js @@ -1,3 +1,5 @@ +'use strict' + exports.SERVICE_TAG = '_ipfs-discovery._udp' exports.SERVICE_TAG_LOCAL = `${exports.SERVICE_TAG}.local` exports.MULTICAST_IP = '224.0.0.251' From 62562b88027b87054de7b47f99840b624cc1cc0d Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 8 Apr 2019 16:17:01 +0100 Subject: [PATCH 03/17] fix: move async to dependencies License: MIT Signed-off-by: Alan Shaw --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f58ed99..e748e67 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,11 @@ "homepage": "https://github.com/libp2p/js-libp2p-mdns", "devDependencies": { "aegir": "^18.2.2", - "async": "^2.6.2", "chai": "^4.2.0", "dirty-chai": "^2.0.1" }, "dependencies": { + "async": "^2.6.2", "debug": "^4.1.1", "libp2p-tcp": "~0.13.0", "multiaddr": "^6.0.6", From 2483aba1520a9c7ab748d7ea16db4aab758dd3c5 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Tue, 9 Apr 2019 11:05:48 +0100 Subject: [PATCH 04/17] fix: typo in comment Co-Authored-By: alanshaw --- src/compat/responder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compat/responder.js b/src/compat/responder.js index 782fdfd..8d05eb3 100644 --- a/src/compat/responder.js +++ b/src/compat/responder.js @@ -29,7 +29,7 @@ class Responder { const questions = event.questions || [] - // Only respond to queires for our service tag + // Only respond to queries for our service tag if (!questions.some(q => q.name === SERVICE_TAG_LOCAL)) return log('got query', event, info) From 655b4d76368a0e6e512a9ac58c03ed2b7a54091a Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 9 Apr 2019 11:52:13 +0100 Subject: [PATCH 05/17] refactor: pr feedback License: MIT Signed-off-by: Alan Shaw --- src/compat/index.js | 24 +++++++++++++++++++----- src/compat/querier.js | 20 ++++++++++++++------ src/compat/responder.js | 9 ++++++--- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/compat/index.js b/src/compat/index.js index 6c27a5b..495f3cb 100644 --- a/src/compat/index.js +++ b/src/compat/index.js @@ -11,9 +11,8 @@ class GoMulticastDNS extends EE { constructor (peerInfo) { super() this._started = false - this._responder = new Responder(peerInfo) - this._querier = new Querier(peerInfo.id) - this._querier.on('peer', peerInfo => this.emit('peer', peerInfo)) + this._peerInfo = peerInfo + this._onPeer = this._onPeer.bind(this) } start (callback) { @@ -22,6 +21,10 @@ class GoMulticastDNS extends EE { } this._started = true + this._responder = new Responder(this._peerInfo) + this._querier = new Querier(this._peerInfo.id) + + this._querier.on('peer', this._onPeer) parallel([ cb => this._responder.start(cb), @@ -29,16 +32,27 @@ class GoMulticastDNS extends EE { ], callback) } + _onPeer (peerInfo) { + this.emit('peer', peerInfo) + } + stop (callback) { if (!this._started) { return callback(new Error('MulticastDNS service is not started')) } + const responder = this._responder + const querier = this._querier + this._started = false + this._responder = null + this._querier = null + + querier.removeListener('peer', this._onPeer) parallel([ - cb => this._responder.stop(cb), - cb => this._querier.stop(cb) + cb => responder.stop(cb), + cb => querier.stop(cb) ], callback) } } diff --git a/src/compat/querier.js b/src/compat/querier.js index f0e32ec..238e23f 100644 --- a/src/compat/querier.js +++ b/src/compat/querier.js @@ -6,6 +6,7 @@ const MDNS = require('multicast-dns') const Multiaddr = require('multiaddr') const PeerInfo = require('peer-info') const PeerId = require('peer-id') +const nextTick = require('async/nextTick') const log = require('debug')('libp2p:mdns:compat:querier') const { SERVICE_TAG_LOCAL, MULTICAST_IP, MULTICAST_PORT } = require('./constants') @@ -22,11 +23,11 @@ class Querier extends EE { start (callback) { this._handle = periodically(() => { // Create a querier that queries multicast but gets responses unicast - const querier = MDNS({ multicast: false, interface: '0.0.0.0', port: 0 }) + const mdns = MDNS({ multicast: false, interface: '0.0.0.0', port: 0 }) - querier.on('response', this._onResponse) + mdns.on('response', this._onResponse) - querier.query({ + mdns.query({ id: nextId(), // id > 0 for unicast response questions: [{ name: SERVICE_TAG_LOCAL, type: 'PTR', class: 'IN' }] }, null, { @@ -34,10 +35,15 @@ class Querier extends EE { port: MULTICAST_PORT }) - return { stop: callback => querier.destroy(callback) } + return { + stop: callback => { + mdns.removeListener('response', this._onResponse) + mdns.destroy(callback) + } + } }, this._options.queryPeriod) - setImmediate(() => callback()) + nextTick(() => callback()) } _onResponse (event, info) { @@ -59,7 +65,9 @@ class Querier extends EE { return log('failed to extract peer ID from TXT record data', txtRecord, err) } - if (this._peerIdStr === peerIdStr) return // replied to myself, ignore + if (this._peerIdStr === peerIdStr) { + return log('ignoring reply to myself') + } let peerId try { diff --git a/src/compat/responder.js b/src/compat/responder.js index 8d05eb3..9bb532f 100644 --- a/src/compat/responder.js +++ b/src/compat/responder.js @@ -5,9 +5,11 @@ const assert = require('assert') const MDNS = require('multicast-dns') const log = require('debug')('libp2p:mdns:compat:responder') const TCP = require('libp2p-tcp') -const tcp = new TCP() +const nextTick = require('async/nextTick') const { SERVICE_TAG_LOCAL } = require('./constants') +const tcp = new TCP() + class Responder { constructor (peerInfo) { assert(peerInfo, 'missing peerInfo parameter') @@ -19,7 +21,7 @@ class Responder { start (callback) { this._mdns = MDNS() this._mdns.on('query', this._onQuery) - setImmediate(() => callback()) + nextTick(() => callback()) } _onQuery (event, info) { @@ -66,7 +68,7 @@ class Responder { type: 'TXT', class: 'IN', ttl: 120, - data: this._peerIdStr + data: [Buffer.from(this._peerIdStr)] }) multiaddrs.forEach((ma) => { @@ -87,6 +89,7 @@ class Responder { } stop (callback) { + this._mdns.removeListener('query', this._onQuery) this._mdns.destroy(callback) } } From 7f6ac76388e7eac6450c673e4eeb84af016015eb Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 9 Apr 2019 12:27:53 +0100 Subject: [PATCH 06/17] fix: respond directly to querier License: MIT Signed-off-by: Alan Shaw --- src/compat/responder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compat/responder.js b/src/compat/responder.js index 9bb532f..da3ec4c 100644 --- a/src/compat/responder.js +++ b/src/compat/responder.js @@ -85,7 +85,7 @@ class Responder { }) log('responding to query', answers) - this._mdns.respond(answers) + this._mdns.respond(answers, info) } stop (callback) { From 324bbf70f95f33381449bb958ab286d663712b00 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 9 Apr 2019 12:41:14 +0100 Subject: [PATCH 07/17] fix: reemit the peer event from GoMulticastDNS License: MIT Signed-off-by: Alan Shaw --- src/index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index b0d0cff..e600703 100644 --- a/src/index.js +++ b/src/index.js @@ -21,14 +21,15 @@ class MulticastDNS extends EventEmitter { this.port = options.port || 5353 this.peerInfo = options.peerInfo this._queryInterval = null + this._onPeer = this._onPeer.bind(this) if (options.compat !== false) { this._goMdns = new GoMulticastDNS(options.peerInfo) + this._goMdns.on('peer', this._onPeer) } } start (callback) { - const self = this const mdns = multicastDNS({ port: this.port }) this.mdns = mdns @@ -41,7 +42,7 @@ class MulticastDNS extends EventEmitter { return log('Error processing peer response', err) } - self.emit('peer', foundPeer) + this._onPeer(foundPeer) }) }) @@ -56,6 +57,10 @@ class MulticastDNS extends EventEmitter { } } + _onPeer (peerInfo) { + this.emit('peer', peerInfo) + } + stop (callback) { if (!this.mdns) { return callback(new Error('MulticastDNS service had not started yet')) @@ -65,6 +70,7 @@ class MulticastDNS extends EventEmitter { this._queryInterval = null if (this._goMdns) { + this._goMdns.removeListener('peer', this._onPeer) parallel([ cb => this._goMdns.stop(cb), cb => this.mdns.destroy(cb) From 05ec9f08b64a61239bb151fff105553529b66d38 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Wed, 10 Apr 2019 15:37:35 +0100 Subject: [PATCH 08/17] refactor: add interval between queries License: MIT Signed-off-by: Alan Shaw --- src/compat/querier.js | 28 +++++++++++++++++++++------- src/index.js | 5 ++++- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/compat/querier.js b/src/compat/querier.js index 238e23f..9cc6600 100644 --- a/src/compat/querier.js +++ b/src/compat/querier.js @@ -14,9 +14,13 @@ class Querier extends EE { constructor (peerId, options) { super() assert(peerId, 'missing peerId parameter') + options = options || {} this._peerIdStr = peerId.toB58String() - this._options = options || {} - this._options.queryPeriod = this._options.queryPeriod || 5000 + // Time for which the MDNS server will stay alive waiting for responses + options.queryPeriod = options.queryPeriod || 5000 + // Re-query every 60s, in leu of network change detection + options.queryInterval = options.queryInterval || 60000 + this._options = options this._onResponse = this._onResponse.bind(this) } @@ -41,7 +45,10 @@ class Querier extends EE { mdns.destroy(callback) } } - }, this._options.queryPeriod) + }, { + period: this._options.queryPeriod, + interval: this._options.queryInterval + }) nextTick(() => callback()) } @@ -112,16 +119,23 @@ class Querier extends EE { module.exports = Querier -function periodically (run, period) { +/** + * Run `fn` for a certain period of time, and then wait for an interval before + * running it again. `fn` must return an object with a stop function, which is + * called when the period expires. + */ +function periodically (fn, { period, interval }) { let handle, timeoutId let stopped = false const reRun = () => { - handle = run() + handle = fn() timeoutId = setTimeout(() => { handle.stop(err => { if (err) log(err) - if (!stopped) reRun() + if (!stopped) { + timeoutId = setTimeout(reRun, interval) + } }) handle = null }, period) @@ -143,7 +157,7 @@ function periodically (run, period) { } const nextId = (() => { - let id = 1 + let id = 0 return () => { id++ if (id === Number.MAX_SAFE_INTEGER) id = 1 diff --git a/src/index.js b/src/index.js index e600703..21c3a12 100644 --- a/src/index.js +++ b/src/index.js @@ -24,7 +24,10 @@ class MulticastDNS extends EventEmitter { this._onPeer = this._onPeer.bind(this) if (options.compat !== false) { - this._goMdns = new GoMulticastDNS(options.peerInfo) + this._goMdns = new GoMulticastDNS(options.peerInfo, { + queryPeriod: options.compatQueryPeriod, + queryInterval: options.compatQueryInerval + }) this._goMdns.on('peer', this._onPeer) } } From 31369eb58ad4936183e1c8f888d41707beb38bbb Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Wed, 10 Apr 2019 15:40:23 +0100 Subject: [PATCH 09/17] chore: appease linter License: MIT Signed-off-by: Alan Shaw --- src/compat/querier.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/compat/querier.js b/src/compat/querier.js index 9cc6600..371aeb4 100644 --- a/src/compat/querier.js +++ b/src/compat/querier.js @@ -123,8 +123,14 @@ module.exports = Querier * Run `fn` for a certain period of time, and then wait for an interval before * running it again. `fn` must return an object with a stop function, which is * called when the period expires. + * + * @param {Function} fn function to run + * @param {Object} [options] + * @param {Object} [options.period] Period in ms to run the function for + * @param {Object} [options.interval] Interval in ms between runs + * @returns {Object} handle that can be used to stop execution */ -function periodically (fn, { period, interval }) { +function periodically (fn, options) { let handle, timeoutId let stopped = false @@ -134,11 +140,11 @@ function periodically (fn, { period, interval }) { handle.stop(err => { if (err) log(err) if (!stopped) { - timeoutId = setTimeout(reRun, interval) + timeoutId = setTimeout(reRun, options.interval) } }) handle = null - }, period) + }, options.period) } reRun() From 3ff725021b66dcc65fc2a0f9a4b6c5eff0a0948a Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 15 Apr 2019 12:03:51 +0100 Subject: [PATCH 10/17] fix: existing tests License: MIT Signed-off-by: Alan Shaw --- .gitignore | 1 + package.json | 2 +- test/multicast-dns.spec.js | 27 ++++++++++++++++++--------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 1c73b37..2640756 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ docs **/*.log test/repo-tests* **/bundle.js +.nyc_output # Logs logs diff --git a/package.json b/package.json index e748e67..b3da7cd 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ ], "scripts": { "lint": "aegir lint", - "coverage": "aegir coverage", + "coverage": "nyc npm run test:node", "test": "aegir test -t node", "test:node": "aegir test -t node", "release": "aegir release -t node --no-build", diff --git a/test/multicast-dns.spec.js b/test/multicast-dns.spec.js index 8958477..5aad46b 100644 --- a/test/multicast-dns.spec.js +++ b/test/multicast-dns.spec.js @@ -67,12 +67,14 @@ describe('MulticastDNS', () => { const mdnsA = new MulticastDNS({ peerInfo: pA, broadcast: false, // do not talk to ourself - port: 50001 + port: 50001, + compat: false }) const mdnsB = new MulticastDNS({ peerInfo: pB, - port: 50001 // port must be the same + port: 50001, // port must be the same + compat: false }) parallel([ @@ -97,15 +99,18 @@ describe('MulticastDNS', () => { const mdnsA = new MulticastDNS({ peerInfo: pA, broadcast: false, // do not talk to ourself - port: 50003 + port: 50003, + compat: false }) const mdnsC = new MulticastDNS({ peerInfo: pC, - port: 50003 // port must be the same + port: 50003, // port must be the same + compat: false }) const mdnsD = new MulticastDNS({ peerInfo: pD, - port: 50003 // port must be the same + port: 50003, // port must be the same + compat: false }) parallel([ @@ -134,12 +139,14 @@ describe('MulticastDNS', () => { const mdnsA = new MulticastDNS({ peerInfo: pA, broadcast: false, // do not talk to ourself - port: 50001 + port: 50001, + compat: false }) const mdnsB = new MulticastDNS({ peerInfo: pB, - port: 50001 + port: 50001, + compat: false }) series([ @@ -164,12 +171,14 @@ describe('MulticastDNS', () => { const mdnsA = new MulticastDNS({ peerInfo: pA, - port: 50004 // port must be the same + port: 50004, // port must be the same + compat: false }) const mdnsC = new MulticastDNS({ peerInfo: pC, - port: 50004 + port: 50004, + compat: false }) series([ From 0b398140684f6e65d8959f53db861921ad455694 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Wed, 10 Apr 2019 15:42:40 +0100 Subject: [PATCH 11/17] fix: option name License: MIT Signed-off-by: Alan Shaw --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 21c3a12..6ec3a8c 100644 --- a/src/index.js +++ b/src/index.js @@ -26,7 +26,7 @@ class MulticastDNS extends EventEmitter { if (options.compat !== false) { this._goMdns = new GoMulticastDNS(options.peerInfo, { queryPeriod: options.compatQueryPeriod, - queryInterval: options.compatQueryInerval + queryInterval: options.compatQueryInterval }) this._goMdns.on('peer', this._onPeer) } From 2b01368e2f90a77978f97be3c94845af50be7b2b Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Wed, 10 Apr 2019 16:15:04 +0100 Subject: [PATCH 12/17] fix: use async/nextTick License: MIT Signed-off-by: Alan Shaw --- src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 6ec3a8c..f2c3657 100644 --- a/src/index.js +++ b/src/index.js @@ -3,7 +3,7 @@ const multicastDNS = require('multicast-dns') const EventEmitter = require('events').EventEmitter const assert = require('assert') -const setImmediate = require('async/setImmediate') +const nextTick = require('async/nextTick') const parallel = require('async/parallel') const debug = require('debug') const log = debug('libp2p:mdns') @@ -56,7 +56,7 @@ class MulticastDNS extends EventEmitter { if (this._goMdns) { this._goMdns.start(callback) } else { - setImmediate(() => callback()) + nextTick(() => callback()) } } From 3aca133a102607bea0041df97be6a6237c9d436f Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 15 Apr 2019 15:39:07 +0100 Subject: [PATCH 13/17] test: add tests for querier and main compat class License: MIT Signed-off-by: Alan Shaw --- package.json | 2 +- src/compat/querier.js | 8 +- test/compat/go-multicast-dns.spec.js | 84 +++++++ test/compat/querier.spec.js | 322 +++++++++++++++++++++++++++ 4 files changed, 413 insertions(+), 3 deletions(-) create mode 100644 test/compat/go-multicast-dns.spec.js create mode 100644 test/compat/querier.spec.js diff --git a/package.json b/package.json index b3da7cd..41a79f5 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ ], "scripts": { "lint": "aegir lint", - "coverage": "nyc npm run test:node", + "coverage": "nyc --reporter=lcov --reporter=text npm run test:node", "test": "aegir test -t node", "test:node": "aegir test -t node", "release": "aegir release -t node --no-build", diff --git a/src/compat/querier.js b/src/compat/querier.js index 371aeb4..eaa9878 100644 --- a/src/compat/querier.js +++ b/src/compat/querier.js @@ -16,10 +16,14 @@ class Querier extends EE { assert(peerId, 'missing peerId parameter') options = options || {} this._peerIdStr = peerId.toB58String() - // Time for which the MDNS server will stay alive waiting for responses - options.queryPeriod = options.queryPeriod || 5000 // Re-query every 60s, in leu of network change detection options.queryInterval = options.queryInterval || 60000 + // Time for which the MDNS server will stay alive waiting for responses + // Must be less than options.queryInterval! + options.queryPeriod = Math.min( + options.queryInterval, + options.queryPeriod == null ? 5000 : options.queryPeriod + ) this._options = options this._onResponse = this._onResponse.bind(this) } diff --git a/test/compat/go-multicast-dns.spec.js b/test/compat/go-multicast-dns.spec.js new file mode 100644 index 0000000..699f2d7 --- /dev/null +++ b/test/compat/go-multicast-dns.spec.js @@ -0,0 +1,84 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) +const PeerInfo = require('peer-info') +const map = require('async/map') +const series = require('async/series') + +const GoMulticastDNS = require('../../src/compat') + +describe('GoMulticastDNS', () => { + const peerAddrs = [ + '/ip4/127.0.0.1/tcp/20001', + '/ip4/127.0.0.1/tcp/20002' + ] + let peerInfos + + before(done => { + map(peerAddrs, (addr, cb) => { + PeerInfo.create((err, info) => { + expect(err).to.not.exist() + info.multiaddrs.add(addr) + cb(null, info) + }) + }, (err, infos) => { + expect(err).to.not.exist() + peerInfos = infos + done() + }) + }) + + it('should start and stop', done => { + const mdns = new GoMulticastDNS(peerInfos[0]) + + mdns.start(err => { + expect(err).to.not.exist() + mdns.stop(err => { + expect(err).to.not.exist() + done() + }) + }) + }) + + it('should not start when started', done => { + const mdns = new GoMulticastDNS(peerInfos[0]) + + mdns.start(err => { + expect(err).to.not.exist() + + mdns.start(err => { + expect(err.message).to.equal('MulticastDNS service is already started') + mdns.stop(done) + }) + }) + }) + + it('should not stop when not started', done => { + const mdns = new GoMulticastDNS(peerInfos[0]) + + mdns.stop(err => { + expect(err.message).to.equal('MulticastDNS service is not started') + done() + }) + }) + + it('should emit peer info when peer is discovered', done => { + const mdnsA = new GoMulticastDNS(peerInfos[0]) + const mdnsB = new GoMulticastDNS(peerInfos[1]) + + mdnsA.on('peer', info => { + if (!info.id.isEqual(peerInfos[1].id)) return + expect(info.multiaddrs.has(peerAddrs[1])).to.be.true() + done() + }) + + series([ + cb => mdnsA.start(cb), + cb => mdnsB.start(cb) + ], err => expect(err).to.not.exist()) + }) +}) diff --git a/test/compat/querier.spec.js b/test/compat/querier.spec.js new file mode 100644 index 0000000..7f2bf31 --- /dev/null +++ b/test/compat/querier.spec.js @@ -0,0 +1,322 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) +const PeerInfo = require('peer-info') +const parallel = require('async/parallel') +const map = require('async/map') +const MDNS = require('multicast-dns') +const OS = require('os') + +const Querier = require('../../src/compat/querier') +const { SERVICE_TAG_LOCAL } = require('../../src/compat/constants') + +describe.only('Querier', () => { + let querier, mdns + const peerAddrs = [ + '/ip4/127.0.0.1/tcp/20001', + '/ip4/127.0.0.1/tcp/20002' + ] + let peerInfos + + before(done => { + map(peerAddrs, (addr, cb) => { + PeerInfo.create((err, info) => { + expect(err).to.not.exist() + info.multiaddrs.add(addr) + cb(null, info) + }) + }, (err, infos) => { + expect(err).to.not.exist() + peerInfos = infos + done() + }) + }) + + afterEach(done => { + parallel([ + cb => querier ? querier.stop(cb) : cb(), + cb => mdns ? mdns.destroy(cb) : cb() + ], err => { + querier = mdns = null + done(err) + }) + }) + + it('should start and stop', done => { + const querier = new Querier(peerInfos[0].id) + + querier.start(err => { + expect(err).to.not.exist() + querier.stop(err => { + expect(err).to.not.exist() + done() + }) + }) + }) + + it('should query on interval', done => { + querier = new Querier(peerInfos[0].id, { queryPeriod: 0, queryInterval: 10 }) + mdns = MDNS() + + let queryCount = 0 + + mdns.on('query', event => { + const questions = event.questions || [] + if (!questions.some(q => q.name === SERVICE_TAG_LOCAL)) return + queryCount++ + }) + + querier.start(err => expect(err).to.not.exist()) + + setTimeout(() => { + // Should have queried at least twice by now! + expect(queryCount >= 2).to.be.true() + done() + }, 20) + }) + + it('should not emit peer for responses with non matching service tags', done => { + ensureNoPeer(event => { + const peerServiceTagLocal = `${peerInfos[1].id.toB58String()}.${SERVICE_TAG_LOCAL}` + const bogusServiceTagLocal = '_ifps-discovery._udp' + + return [{ + name: bogusServiceTagLocal, + type: 'PTR', + class: 'IN', + ttl: 120, + data: peerServiceTagLocal + }] + }, done) + }) + + it('should not emit peer for responses with missing TXT record', done => { + ensureNoPeer(event => { + const peerServiceTagLocal = `${peerInfos[1].id.toB58String()}.${SERVICE_TAG_LOCAL}` + + return [{ + name: SERVICE_TAG_LOCAL, + type: 'PTR', + class: 'IN', + ttl: 120, + data: peerServiceTagLocal + }] + }, done) + }) + + it('should not emit peer for responses with missing peer ID in TXT record', done => { + ensureNoPeer(event => { + const peerServiceTagLocal = `${peerInfos[1].id.toB58String()}.${SERVICE_TAG_LOCAL}` + + return [{ + name: SERVICE_TAG_LOCAL, + type: 'PTR', + class: 'IN', + ttl: 120, + data: peerServiceTagLocal + }, { + name: peerServiceTagLocal, + type: 'TXT', + class: 'IN', + ttl: 120, + data: [] // undefined peer ID + }] + }, done) + }) + + it('should not emit peer for responses to self', done => { + ensureNoPeer(event => { + const peerServiceTagLocal = `${peerInfos[1].id.toB58String()}.${SERVICE_TAG_LOCAL}` + + return [{ + name: SERVICE_TAG_LOCAL, + type: 'PTR', + class: 'IN', + ttl: 120, + data: peerServiceTagLocal + }, { + name: peerServiceTagLocal, + type: 'TXT', + class: 'IN', + ttl: 120, + data: peerInfos[0].id.toB58String() + }] + }, done) + }) + + // TODO: unskip when https://github.com/libp2p/js-peer-id/issues/83 is resolved + it.skip('should not emit peer for responses with invalid peer ID in TXT record', done => { + ensureNoPeer(event => { + const peerServiceTagLocal = `${peerInfos[1].id.toB58String()}.${SERVICE_TAG_LOCAL}` + + return [{ + name: SERVICE_TAG_LOCAL, + type: 'PTR', + class: 'IN', + ttl: 120, + data: peerServiceTagLocal + }, { + name: peerServiceTagLocal, + type: 'TXT', + class: 'IN', + ttl: 120, + data: '🤪' + }] + }, done) + }) + + it('should not emit peer for responses with missing SRV record', done => { + ensureNoPeer(event => { + const peerServiceTagLocal = `${peerInfos[1].id.toB58String()}.${SERVICE_TAG_LOCAL}` + + return [{ + name: SERVICE_TAG_LOCAL, + type: 'PTR', + class: 'IN', + ttl: 120, + data: peerServiceTagLocal + }, { + name: peerServiceTagLocal, + type: 'TXT', + class: 'IN', + ttl: 120, + data: peerInfos[1].id.toB58String() + }] + }, done) + }) + + it('should emit peer for responses even if no multiaddrs', done => { + ensurePeer(event => { + const peerServiceTagLocal = `${peerInfos[1].id.toB58String()}.${SERVICE_TAG_LOCAL}` + + return [{ + name: SERVICE_TAG_LOCAL, + type: 'PTR', + class: 'IN', + ttl: 120, + data: peerServiceTagLocal + }, { + name: peerServiceTagLocal, + type: 'TXT', + class: 'IN', + ttl: 120, + data: peerInfos[1].id.toB58String() + }, { + name: peerServiceTagLocal, + type: 'SRV', + class: 'IN', + ttl: 120, + data: { + priority: 10, + weight: 1, + port: parseInt(peerAddrs[1].split().pop()), + target: OS.hostname() + } + }] + }, done) + }) + + it('should emit peer for responses with valid multiaddrs', done => { + ensurePeer(event => { + const peerServiceTagLocal = `${peerInfos[1].id.toB58String()}.${SERVICE_TAG_LOCAL}` + + return [{ + name: SERVICE_TAG_LOCAL, + type: 'PTR', + class: 'IN', + ttl: 120, + data: peerServiceTagLocal + }, { + name: peerServiceTagLocal, + type: 'TXT', + class: 'IN', + ttl: 120, + data: peerInfos[1].id.toB58String() + }, { + name: peerServiceTagLocal, + type: 'SRV', + class: 'IN', + ttl: 120, + data: { + priority: 10, + weight: 1, + port: parseInt(peerAddrs[1].split().pop()), + target: OS.hostname() + } + }, { + name: OS.hostname(), + type: peerAddrs[1].startsWith('/ip4') ? 'A' : 'AAAA', + class: 'IN', + ttl: 120, + data: peerAddrs[1].split('/')[2] + }] + }, done) + }) + + /** + * Ensure peerInfos[1] are emitted from `querier` + * @param {Function} getResponse Given a query, construct a response to test the querier + * @param {Function} callback Callback called when test finishes + */ + function ensurePeer (getResponse, callback) { + querier = new Querier(peerInfos[0].id) + mdns = MDNS() + + mdns.on('query', (event, info) => { + const questions = event.questions || [] + if (!questions.some(q => q.name === SERVICE_TAG_LOCAL)) return + mdns.respond(getResponse(event, info), info) + }) + + let peerInfo + + querier.on('peer', info => { + // Ignore non-test peers + if (!info.id.isEqual(peerInfos[1].id)) return + peerInfo = info + }) + + querier.start(err => { + if (err) return callback(err) + setTimeout(() => { + callback(peerInfo ? null : new Error('Missing peer')) + }, 100) + }) + } + + /** + * Ensure none of peerInfos are emitted from `querier` + * @param {Function} getResponse Given a query, construct a response to test the querier + * @param {Function} callback Callback called when test finishes + */ + function ensureNoPeer (getResponse, callback) { + querier = new Querier(peerInfos[0].id) + mdns = MDNS() + + mdns.on('query', (event, info) => { + const questions = event.questions || [] + if (!questions.some(q => q.name === SERVICE_TAG_LOCAL)) return + mdns.respond(getResponse(event, info), info) + }) + + let peerInfo + + querier.on('peer', info => { + // Ignore non-test peers + if (!info.id.isEqual(peerInfos[0].id) && !info.id.isEqual(peerInfos[1].id)) return + peerInfo = info + }) + + querier.start(err => { + if (err) return callback(err) + setTimeout(() => { + if (!peerInfo) return callback() + callback(Object.assign(new Error('Unexpected peer'), { peerInfo })) + }, 100) + }) + } +}) From 15416c3f1058bd0e0d4f0a8400f49697f155d966 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 15 Apr 2019 15:40:08 +0100 Subject: [PATCH 14/17] chore: remove .only License: MIT Signed-off-by: Alan Shaw --- test/compat/querier.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/compat/querier.spec.js b/test/compat/querier.spec.js index 7f2bf31..0aa091b 100644 --- a/test/compat/querier.spec.js +++ b/test/compat/querier.spec.js @@ -14,7 +14,7 @@ const OS = require('os') const Querier = require('../../src/compat/querier') const { SERVICE_TAG_LOCAL } = require('../../src/compat/constants') -describe.only('Querier', () => { +describe('Querier', () => { let querier, mdns const peerAddrs = [ '/ip4/127.0.0.1/tcp/20001', From 37fbfdd68c7925efae4d500734cf7ba494f8d424 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 15 Apr 2019 16:31:36 +0100 Subject: [PATCH 15/17] test: add responder tests License: MIT Signed-off-by: Alan Shaw --- test/compat/responder.spec.js | 181 ++++++++++++++++++++++++++++++++++ test/multicast-dns.spec.js | 12 +++ 2 files changed, 193 insertions(+) create mode 100644 test/compat/responder.spec.js diff --git a/test/compat/responder.spec.js b/test/compat/responder.spec.js new file mode 100644 index 0000000..67eaefe --- /dev/null +++ b/test/compat/responder.spec.js @@ -0,0 +1,181 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) +const PeerInfo = require('peer-info') +const parallel = require('async/parallel') +const map = require('async/map') +const MDNS = require('multicast-dns') + +const Responder = require('../../src/compat/responder') +const { SERVICE_TAG_LOCAL, MULTICAST_IP, MULTICAST_PORT } = require('../../src/compat/constants') + +describe('Responder', () => { + let responder, mdns + const peerAddrs = [ + '/ip4/127.0.0.1/tcp/20001', + '/ip4/127.0.0.1/tcp/20002' + ] + let peerInfos + + before(done => { + map(peerAddrs, (addr, cb) => { + PeerInfo.create((err, info) => { + expect(err).to.not.exist() + info.multiaddrs.add(addr) + cb(null, info) + }) + }, (err, infos) => { + expect(err).to.not.exist() + peerInfos = infos + done() + }) + }) + + afterEach(done => { + parallel([ + cb => responder ? responder.stop(cb) : cb(), + cb => mdns ? mdns.destroy(cb) : cb() + ], err => { + responder = mdns = null + done(err) + }) + }) + + it('should start and stop', done => { + const responder = new Responder(peerInfos[0]) + + responder.start(err => { + expect(err).to.not.exist() + responder.stop(err => { + expect(err).to.not.exist() + done() + }) + }) + }) + + it('should not respond to a query if no TCP addresses', done => { + PeerInfo.create((err, peerInfo) => { + expect(err).to.not.exist() + + responder = new Responder(peerInfo) + mdns = MDNS({ multicast: false, interface: '0.0.0.0', port: 0 }) + + responder.start(err => { + expect(err).to.not.exist() + + let response + + mdns.on('response', event => { + if (isResponseFrom(event, peerInfo)) { + response = event + } + }) + + mdns.query({ + id: 1, // id > 0 for unicast response + questions: [{ name: SERVICE_TAG_LOCAL, type: 'PTR', class: 'IN' }] + }, null, { + address: MULTICAST_IP, + port: MULTICAST_PORT + }) + + setTimeout(() => { + done(response ? new Error('Unexpected repsonse') : null) + }, 100) + }) + }) + }) + + it('should not respond to a query with non matching service tag', done => { + responder = new Responder(peerInfos[0]) + mdns = MDNS({ multicast: false, interface: '0.0.0.0', port: 0 }) + + responder.start(err => { + expect(err).to.not.exist() + + let response + + mdns.on('response', event => { + if (isResponseFrom(event, peerInfos[0])) { + response = event + } + }) + + const bogusServiceTagLocal = '_ifps-discovery._udp' + + mdns.query({ + id: 1, // id > 0 for unicast response + questions: [{ name: bogusServiceTagLocal, type: 'PTR', class: 'IN' }] + }, null, { + address: MULTICAST_IP, + port: MULTICAST_PORT + }) + + setTimeout(() => { + done(response ? new Error('Unexpected repsonse') : null) + }, 100) + }) + }) + + it('should respond correctly', done => { + responder = new Responder(peerInfos[0]) + mdns = MDNS({ multicast: false, interface: '0.0.0.0', port: 0 }) + + responder.start(err => { + expect(err).to.not.exist() + + mdns.on('response', event => { + if (!isResponseFrom(event, peerInfos[0])) return + + const srvRecord = event.answers.find(a => a.type === 'SRV') + if (!srvRecord) return done(new Error('Missing SRV record')) + + const { port } = srvRecord.data || {} + const protos = { A: 'ip4', AAAA: 'ip6' } + + const addrs = event.answers + .filter(a => ['A', 'AAAA'].includes(a.type)) + .map(a => `/${protos[a.type]}/${a.data}/tcp/${port}`) + + if (!addrs.includes(peerAddrs[0])) { + return done(new Error('Missing peer address in response: ' + peerAddrs[0])) + } + + done() + }) + + mdns.query({ + id: 1, // id > 0 for unicast response + questions: [{ name: SERVICE_TAG_LOCAL, type: 'PTR', class: 'IN' }] + }, null, { + address: MULTICAST_IP, + port: MULTICAST_PORT + }) + }) + }) +}) + +function isResponseFrom (res, fromPeerInfo) { + const answers = res.answers || [] + const ptrRecord = answers.find(a => a.type === 'PTR' && a.name === SERVICE_TAG_LOCAL) + if (!ptrRecord) return false // Ignore irrelevant + + const txtRecord = answers.find(a => a.type === 'TXT') + if (!txtRecord) return false // Ignore missing TXT record + + let peerIdStr + try { + peerIdStr = txtRecord.data[0].toString() + } catch (err) { + return false // Ignore invalid peer ID data + } + + // Ignore response from someone else + if (fromPeerInfo.id.toB58String() !== peerIdStr) return false + + return true +} diff --git a/test/multicast-dns.spec.js b/test/multicast-dns.spec.js index 5aad46b..b762bf2 100644 --- a/test/multicast-dns.spec.js +++ b/test/multicast-dns.spec.js @@ -193,4 +193,16 @@ describe('MulticastDNS', () => { }) }) }) + + it('should start and stop with go-libp2p-mdns compat', done => { + const mdns = new MulticastDNS({ peerInfo: pA, port: 50004 }) + + mdns.start(err => { + expect(err).to.not.exist() + mdns.stop(err => { + expect(err).to.not.exist() + done() + }) + }) + }) }) From a3683e76d3e8ce8be56bedaec0be6dcaf06fe8cc Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 15 Apr 2019 16:42:33 +0100 Subject: [PATCH 16/17] fix: increase timeout for query on interval test License: MIT Signed-off-by: Alan Shaw --- test/compat/querier.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/compat/querier.spec.js b/test/compat/querier.spec.js index 0aa091b..5921a0c 100644 --- a/test/compat/querier.spec.js +++ b/test/compat/querier.spec.js @@ -76,7 +76,7 @@ describe('Querier', () => { // Should have queried at least twice by now! expect(queryCount >= 2).to.be.true() done() - }, 20) + }, 100) }) it('should not emit peer for responses with non matching service tags', done => { From f8f98300f93eb3e008229324245dd5a0cc28be12 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 9 May 2019 09:11:29 +0100 Subject: [PATCH 17/17] docs: document new compat option License: MIT Signed-off-by: Alan Shaw --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 27720d9..3b43987 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,10 @@ mdns.start(() => setTimeout(() => mdns.stop(() => {}), 20 * 1000)) - options - `peerInfo` - PeerInfo to announce - - `broadcast` - (true/false) announce our presence through mDNS, default false + - `broadcast` - (true/false) announce our presence through mDNS, default `false` - `interval` - query interval, default 10 * 1000 (10 seconds) - `serviceTag` - name of the service announce , default 'ipfs.local` + - `compat` - enable/disable compatibility with go-libp2p-mdns, default `true` ## MDNS messages