@libp2p/mdns 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +4 -0
- package/README.md +98 -0
- package/dist/src/compat/constants.d.ts +5 -0
- package/dist/src/compat/constants.d.ts.map +1 -0
- package/dist/src/compat/constants.js +5 -0
- package/dist/src/compat/constants.js.map +1 -0
- package/dist/src/compat/index.d.ts +20 -0
- package/dist/src/compat/index.d.ts.map +1 -0
- package/dist/src/compat/index.js +47 -0
- package/dist/src/compat/index.js.map +1 -0
- package/dist/src/compat/querier.d.ts +25 -0
- package/dist/src/compat/querier.d.ts.map +1 -0
- package/dist/src/compat/querier.js +163 -0
- package/dist/src/compat/querier.js.map +1 -0
- package/dist/src/compat/responder.d.ts +19 -0
- package/dist/src/compat/responder.d.ts.map +1 -0
- package/dist/src/compat/responder.js +99 -0
- package/dist/src/compat/responder.js.map +1 -0
- package/dist/src/index.d.ts +48 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +145 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/query.d.ts +9 -0
- package/dist/src/query.d.ts.map +1 -0
- package/dist/src/query.js +141 -0
- package/dist/src/query.js.map +1 -0
- package/package.json +149 -0
- package/src/compat/constants.ts +4 -0
- package/src/compat/index.ts +64 -0
- package/src/compat/querier.ts +209 -0
- package/src/compat/responder.ts +127 -0
- package/src/index.ts +191 -0
- package/src/query.ts +166 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { EventEmitter } from 'events'
|
|
2
|
+
import MDNS from 'multicast-dns'
|
|
3
|
+
import { Multiaddr } from '@multiformats/multiaddr'
|
|
4
|
+
import { PeerId } from '@libp2p/peer-id'
|
|
5
|
+
import debug from 'debug'
|
|
6
|
+
import { SERVICE_TAG_LOCAL, MULTICAST_IP, MULTICAST_PORT } from './constants.js'
|
|
7
|
+
import { base58btc } from 'multiformats/bases/base58'
|
|
8
|
+
import type { PeerDiscovery } from '@libp2p/interfaces/peer-discovery'
|
|
9
|
+
import type { ResponsePacket } from 'multicast-dns'
|
|
10
|
+
import type { RemoteInfo } from 'dgram'
|
|
11
|
+
|
|
12
|
+
const log = Object.assign(debug('libp2p:mdns:compat:querier'), {
|
|
13
|
+
error: debug('libp2p:mdns:compat:querier:error')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export interface QuerierOptions {
|
|
17
|
+
peerId: PeerId
|
|
18
|
+
queryInterval?: number
|
|
19
|
+
queryPeriod?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Handle {
|
|
23
|
+
stop: () => Promise<void>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class Querier extends EventEmitter implements PeerDiscovery {
|
|
27
|
+
private readonly _peerIdStr: string
|
|
28
|
+
private readonly _options: Required<QuerierOptions>
|
|
29
|
+
private _handle?: Handle
|
|
30
|
+
|
|
31
|
+
constructor (options: QuerierOptions) {
|
|
32
|
+
super()
|
|
33
|
+
|
|
34
|
+
const { peerId, queryInterval, queryPeriod } = options
|
|
35
|
+
|
|
36
|
+
if (peerId == null) {
|
|
37
|
+
throw new Error('missing peerId parameter')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this._peerIdStr = peerId.toString(base58btc)
|
|
41
|
+
this._options = {
|
|
42
|
+
peerId,
|
|
43
|
+
|
|
44
|
+
// Re-query in leu of network change detection (every 60s by default)
|
|
45
|
+
queryInterval: queryInterval ?? 60000,
|
|
46
|
+
// Time for which the MDNS server will stay alive waiting for responses
|
|
47
|
+
// Must be less than options.queryInterval!
|
|
48
|
+
queryPeriod: Math.min(
|
|
49
|
+
queryInterval ?? 60000,
|
|
50
|
+
queryPeriod ?? 5000
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
this._onResponse = this._onResponse.bind(this)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
isStarted () {
|
|
57
|
+
return Boolean(this._handle)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
start () {
|
|
61
|
+
this._handle = periodically(() => {
|
|
62
|
+
// Create a querier that queries multicast but gets responses unicast
|
|
63
|
+
const mdns = MDNS({ multicast: false, interface: '0.0.0.0', port: 0 })
|
|
64
|
+
|
|
65
|
+
mdns.on('response', this._onResponse)
|
|
66
|
+
|
|
67
|
+
// @ts-expect-error @types/multicast-dns are wrong
|
|
68
|
+
mdns.query({
|
|
69
|
+
id: nextId(), // id > 0 for unicast response
|
|
70
|
+
questions: [{
|
|
71
|
+
name: SERVICE_TAG_LOCAL,
|
|
72
|
+
type: 'PTR',
|
|
73
|
+
class: 'IN'
|
|
74
|
+
}]
|
|
75
|
+
}, null, {
|
|
76
|
+
address: MULTICAST_IP,
|
|
77
|
+
port: MULTICAST_PORT
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
stop: async () => {
|
|
82
|
+
mdns.removeListener('response', this._onResponse)
|
|
83
|
+
return await new Promise(resolve => mdns.destroy(resolve))
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}, {
|
|
87
|
+
period: this._options.queryPeriod,
|
|
88
|
+
interval: this._options.queryInterval
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
_onResponse (event: ResponsePacket, info: RemoteInfo) {
|
|
93
|
+
const answers = event.answers ?? []
|
|
94
|
+
const ptrRecord = answers.find(a => a.type === 'PTR' && a.name === SERVICE_TAG_LOCAL)
|
|
95
|
+
|
|
96
|
+
// Only deal with responses for our service tag
|
|
97
|
+
if (ptrRecord == null) return
|
|
98
|
+
|
|
99
|
+
log('got response', event, info)
|
|
100
|
+
|
|
101
|
+
const txtRecord = answers.find(a => a.type === 'TXT')
|
|
102
|
+
if (txtRecord == null || txtRecord.type !== 'TXT') {
|
|
103
|
+
return log('missing TXT record in response')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let peerIdStr
|
|
107
|
+
try {
|
|
108
|
+
peerIdStr = txtRecord.data[0].toString()
|
|
109
|
+
} catch (err) {
|
|
110
|
+
return log('failed to extract peer ID from TXT record data', txtRecord, err)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (this._peerIdStr === peerIdStr) {
|
|
114
|
+
return log('ignoring reply to myself')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let peerId
|
|
118
|
+
try {
|
|
119
|
+
peerId = PeerId.fromString(peerIdStr)
|
|
120
|
+
} catch (err) {
|
|
121
|
+
return log('failed to create peer ID from TXT record data', peerIdStr, err)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const srvRecord = answers.find(a => a.type === 'SRV')
|
|
125
|
+
if (srvRecord == null || srvRecord.type !== 'SRV') {
|
|
126
|
+
return log('missing SRV record in response')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
log('peer found', peerIdStr)
|
|
130
|
+
|
|
131
|
+
const { port } = srvRecord.data ?? {}
|
|
132
|
+
const protos = { A: 'ip4', AAAA: 'ip6' }
|
|
133
|
+
|
|
134
|
+
const multiaddrs = answers
|
|
135
|
+
.filter(a => ['A', 'AAAA'].includes(a.type))
|
|
136
|
+
.reduce<Multiaddr[]>((addrs, a) => {
|
|
137
|
+
if (a.type !== 'A' && a.type !== 'AAAA') {
|
|
138
|
+
return addrs
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const maStr = `/${protos[a.type]}/${a.data}/tcp/${port}`
|
|
142
|
+
try {
|
|
143
|
+
addrs.push(new Multiaddr(maStr))
|
|
144
|
+
log(maStr)
|
|
145
|
+
} catch (err) {
|
|
146
|
+
log(`failed to create multiaddr from ${a.type} record data`, maStr, port, err)
|
|
147
|
+
}
|
|
148
|
+
return addrs
|
|
149
|
+
}, [])
|
|
150
|
+
|
|
151
|
+
this.emit('peer', {
|
|
152
|
+
id: peerId,
|
|
153
|
+
multiaddrs
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async stop () {
|
|
158
|
+
if (this._handle != null) {
|
|
159
|
+
await this._handle.stop()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Run `fn` for a certain period of time, and then wait for an interval before
|
|
166
|
+
* running it again. `fn` must return an object with a stop function, which is
|
|
167
|
+
* called when the period expires.
|
|
168
|
+
*/
|
|
169
|
+
function periodically (fn: () => Handle, options: { period: number, interval: number }) {
|
|
170
|
+
let handle: Handle | null
|
|
171
|
+
let timeoutId: NodeJS.Timer
|
|
172
|
+
let stopped = false
|
|
173
|
+
|
|
174
|
+
const reRun = () => {
|
|
175
|
+
handle = fn()
|
|
176
|
+
timeoutId = setTimeout(() => {
|
|
177
|
+
if (handle != null) {
|
|
178
|
+
handle.stop().catch(log)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!stopped) {
|
|
182
|
+
timeoutId = setTimeout(reRun, options.interval)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
handle = null
|
|
186
|
+
}, options.period)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
reRun()
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
async stop () {
|
|
193
|
+
stopped = true
|
|
194
|
+
clearTimeout(timeoutId)
|
|
195
|
+
if (handle != null) {
|
|
196
|
+
await handle.stop()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const nextId = (() => {
|
|
203
|
+
let id = 0
|
|
204
|
+
return () => {
|
|
205
|
+
id++
|
|
206
|
+
if (id === Number.MAX_SAFE_INTEGER) id = 1
|
|
207
|
+
return id
|
|
208
|
+
}
|
|
209
|
+
})()
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import OS from 'os'
|
|
2
|
+
import MDNS, { QueryPacket } from 'multicast-dns'
|
|
3
|
+
import debug from 'debug'
|
|
4
|
+
import { SERVICE_TAG_LOCAL } from './constants.js'
|
|
5
|
+
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
|
6
|
+
import type { Multiaddr, MultiaddrObject } from '@multiformats/multiaddr'
|
|
7
|
+
import { base58btc } from 'multiformats/bases/base58'
|
|
8
|
+
import type { RemoteInfo } from 'dgram'
|
|
9
|
+
import type { Answer } from 'dns-packet'
|
|
10
|
+
|
|
11
|
+
const log = Object.assign(debug('libp2p:mdns:compat:responder'), {
|
|
12
|
+
error: debug('libp2p:mdns:compat:responder:error')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export interface ResponderOptions {
|
|
16
|
+
peerId: PeerId
|
|
17
|
+
multiaddrs: Multiaddr[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class Responder {
|
|
21
|
+
private readonly _peerIdStr: string
|
|
22
|
+
private readonly _multiaddrs: Multiaddr[]
|
|
23
|
+
private _mdns?: MDNS.MulticastDNS
|
|
24
|
+
|
|
25
|
+
constructor (options: ResponderOptions) {
|
|
26
|
+
const { peerId, multiaddrs } = options
|
|
27
|
+
|
|
28
|
+
if (peerId == null) {
|
|
29
|
+
throw new Error('missing peerId parameter')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this._peerIdStr = peerId.toString(base58btc)
|
|
33
|
+
this._multiaddrs = multiaddrs
|
|
34
|
+
this._onQuery = this._onQuery.bind(this)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
start () {
|
|
38
|
+
this._mdns = MDNS()
|
|
39
|
+
this._mdns.on('query', this._onQuery)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_onQuery (event: QueryPacket, info: RemoteInfo) {
|
|
43
|
+
const addresses = this._multiaddrs.reduce<MultiaddrObject[]>((acc, addr) => {
|
|
44
|
+
if (addr.isThinWaistAddress()) {
|
|
45
|
+
acc.push(addr.toOptions())
|
|
46
|
+
}
|
|
47
|
+
return acc
|
|
48
|
+
}, [])
|
|
49
|
+
|
|
50
|
+
// Only announce TCP for now
|
|
51
|
+
if (addresses.length === 0) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const questions = event.questions ?? []
|
|
56
|
+
|
|
57
|
+
// Only respond to queries for our service tag
|
|
58
|
+
if (!questions.some(q => q.name === SERVICE_TAG_LOCAL)) return
|
|
59
|
+
|
|
60
|
+
log('got query', event, info)
|
|
61
|
+
|
|
62
|
+
const answers: Answer[] = []
|
|
63
|
+
const peerServiceTagLocal = `${this._peerIdStr}.${SERVICE_TAG_LOCAL}`
|
|
64
|
+
|
|
65
|
+
answers.push({
|
|
66
|
+
name: SERVICE_TAG_LOCAL,
|
|
67
|
+
type: 'PTR',
|
|
68
|
+
class: 'IN',
|
|
69
|
+
ttl: 120,
|
|
70
|
+
data: peerServiceTagLocal
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Only announce TCP multiaddrs for now
|
|
74
|
+
const port = addresses[0].port
|
|
75
|
+
|
|
76
|
+
answers.push({
|
|
77
|
+
name: peerServiceTagLocal,
|
|
78
|
+
type: 'SRV',
|
|
79
|
+
class: 'IN',
|
|
80
|
+
ttl: 120,
|
|
81
|
+
data: {
|
|
82
|
+
priority: 10,
|
|
83
|
+
weight: 1,
|
|
84
|
+
port,
|
|
85
|
+
target: OS.hostname()
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
answers.push({
|
|
90
|
+
name: peerServiceTagLocal,
|
|
91
|
+
type: 'TXT',
|
|
92
|
+
class: 'IN',
|
|
93
|
+
ttl: 120,
|
|
94
|
+
data: [Buffer.from(this._peerIdStr)]
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
addresses.forEach((ma) => {
|
|
98
|
+
if ([4, 6].includes(ma.family)) {
|
|
99
|
+
answers.push({
|
|
100
|
+
name: OS.hostname(),
|
|
101
|
+
type: ma.family === 4 ? 'A' : 'AAAA',
|
|
102
|
+
class: 'IN',
|
|
103
|
+
ttl: 120,
|
|
104
|
+
data: ma.host
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
if (this._mdns != null) {
|
|
110
|
+
log('responding to query', answers)
|
|
111
|
+
this._mdns.respond(answers, info)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
stop () {
|
|
116
|
+
if (this._mdns != null) {
|
|
117
|
+
this._mdns.removeListener('query', this._onQuery)
|
|
118
|
+
return new Promise<void>(resolve => {
|
|
119
|
+
if (this._mdns != null) {
|
|
120
|
+
this._mdns.destroy(resolve)
|
|
121
|
+
} else {
|
|
122
|
+
resolve()
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import multicastDNS from 'multicast-dns'
|
|
2
|
+
import { EventEmitter } from 'events'
|
|
3
|
+
import debug from 'debug'
|
|
4
|
+
import * as query from './query.js'
|
|
5
|
+
import { GoMulticastDNS } from './compat/index.js'
|
|
6
|
+
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
|
7
|
+
import type PeerDiscovery from '@libp2p/interfaces/peer-discovery'
|
|
8
|
+
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
9
|
+
import type { PeerData } from '@libp2p/interfaces/peer-data'
|
|
10
|
+
|
|
11
|
+
const log = Object.assign(debug('libp2p:mdns'), {
|
|
12
|
+
error: debug('libp2p:mdns:error')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export interface MulticastDNSOptions {
|
|
16
|
+
peerId: PeerId
|
|
17
|
+
broadcast?: boolean
|
|
18
|
+
interval?: number
|
|
19
|
+
serviceTag?: string
|
|
20
|
+
port?: number
|
|
21
|
+
multiaddrs?: Multiaddr[]
|
|
22
|
+
compat?: boolean
|
|
23
|
+
compatQueryPeriod?: number
|
|
24
|
+
compatQueryInterval?: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class MulticastDNS extends EventEmitter implements PeerDiscovery {
|
|
28
|
+
static tag = 'mdns'
|
|
29
|
+
|
|
30
|
+
public mdns?: multicastDNS.MulticastDNS
|
|
31
|
+
|
|
32
|
+
private readonly broadcast: boolean
|
|
33
|
+
private readonly interval: number
|
|
34
|
+
private readonly serviceTag: string
|
|
35
|
+
private readonly port: number
|
|
36
|
+
private readonly peerId: PeerId
|
|
37
|
+
private readonly peerMultiaddrs: Multiaddr[] // TODO: update this when multiaddrs change?
|
|
38
|
+
private _queryInterval: NodeJS.Timer | null
|
|
39
|
+
private readonly _goMdns?: GoMulticastDNS
|
|
40
|
+
|
|
41
|
+
constructor (options: MulticastDNSOptions) {
|
|
42
|
+
super()
|
|
43
|
+
|
|
44
|
+
if (options.peerId == null) {
|
|
45
|
+
throw new Error('needs own PeerId to work')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.broadcast = options.broadcast !== false
|
|
49
|
+
this.interval = options.interval ?? (1e3 * 10)
|
|
50
|
+
this.serviceTag = options.serviceTag ?? 'ipfs.local'
|
|
51
|
+
this.port = options.port ?? 5353
|
|
52
|
+
this.peerId = options.peerId
|
|
53
|
+
this.peerMultiaddrs = options.multiaddrs ?? []
|
|
54
|
+
this._queryInterval = null
|
|
55
|
+
this._onPeer = this._onPeer.bind(this)
|
|
56
|
+
this._onMdnsQuery = this._onMdnsQuery.bind(this)
|
|
57
|
+
this._onMdnsResponse = this._onMdnsResponse.bind(this)
|
|
58
|
+
|
|
59
|
+
if (options.compat !== false) {
|
|
60
|
+
this._goMdns = new GoMulticastDNS({
|
|
61
|
+
multiaddrs: this.peerMultiaddrs,
|
|
62
|
+
peerId: options.peerId,
|
|
63
|
+
queryPeriod: options.compatQueryPeriod,
|
|
64
|
+
queryInterval: options.compatQueryInterval
|
|
65
|
+
})
|
|
66
|
+
this._goMdns.on('peer', this._onPeer)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
isStarted () {
|
|
71
|
+
return Boolean(this.mdns)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Start sending queries to the LAN.
|
|
76
|
+
*
|
|
77
|
+
* @returns {void}
|
|
78
|
+
*/
|
|
79
|
+
async start () {
|
|
80
|
+
if (this.mdns != null) {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.mdns = multicastDNS({ port: this.port })
|
|
85
|
+
this.mdns.on('query', this._onMdnsQuery)
|
|
86
|
+
this.mdns.on('response', this._onMdnsResponse)
|
|
87
|
+
|
|
88
|
+
this._queryInterval = query.queryLAN(this.mdns, this.serviceTag, this.interval)
|
|
89
|
+
|
|
90
|
+
if (this._goMdns != null) {
|
|
91
|
+
await this._goMdns.start()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
_onMdnsQuery (event: multicastDNS.QueryPacket) {
|
|
96
|
+
if (this.mdns == null) {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
query.gotQuery(event, this.mdns, this.peerId, this.peerMultiaddrs, this.serviceTag, this.broadcast)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
_onMdnsResponse (event: multicastDNS.ResponsePacket) {
|
|
104
|
+
try {
|
|
105
|
+
const foundPeer = query.gotResponse(event, this.peerId, this.serviceTag)
|
|
106
|
+
|
|
107
|
+
if (foundPeer != null) {
|
|
108
|
+
this.emit('peer', foundPeer)
|
|
109
|
+
}
|
|
110
|
+
} catch (err) {
|
|
111
|
+
log('Error processing peer response', err)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_onPeer (peerData: PeerData) {
|
|
116
|
+
(this.mdns != null) && this.emit('peer', peerData)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Stop sending queries to the LAN.
|
|
121
|
+
*
|
|
122
|
+
* @returns {Promise}
|
|
123
|
+
*/
|
|
124
|
+
async stop () {
|
|
125
|
+
if (this.mdns == null) {
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.mdns.removeListener('query', this._onMdnsQuery)
|
|
130
|
+
this.mdns.removeListener('response', this._onMdnsResponse)
|
|
131
|
+
this._goMdns?.removeListener('peer', this._onPeer)
|
|
132
|
+
|
|
133
|
+
if (this._queryInterval != null) {
|
|
134
|
+
clearInterval(this._queryInterval)
|
|
135
|
+
this._queryInterval = null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await Promise.all([
|
|
139
|
+
this._goMdns?.stop(),
|
|
140
|
+
new Promise<void>((resolve) => {
|
|
141
|
+
if (this.mdns != null) {
|
|
142
|
+
this.mdns.destroy(resolve)
|
|
143
|
+
} else {
|
|
144
|
+
resolve()
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
])
|
|
148
|
+
|
|
149
|
+
this.mdns = undefined
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* for reference
|
|
154
|
+
|
|
155
|
+
[ { name: 'discovery.ipfs.io.local',
|
|
156
|
+
type: 'PTR',
|
|
157
|
+
class: 1,
|
|
158
|
+
ttl: 120,
|
|
159
|
+
data: 'QmbBHw1Xx9pUpAbrVZUKTPL5Rsph5Q9GQhRvcWVBPFgGtC.discovery.ipfs.io.local' },
|
|
160
|
+
|
|
161
|
+
{ name: 'QmbBHw1Xx9pUpAbrVZUKTPL5Rsph5Q9GQhRvcWVBPFgGtC.discovery.ipfs.io.local',
|
|
162
|
+
type: 'SRV',
|
|
163
|
+
class: 1,
|
|
164
|
+
ttl: 120,
|
|
165
|
+
data: { priority: 10, weight: 1, port: 4001, target: 'lorien.local' } },
|
|
166
|
+
|
|
167
|
+
{ name: 'lorien.local',
|
|
168
|
+
type: 'A',
|
|
169
|
+
class: 1,
|
|
170
|
+
ttl: 120,
|
|
171
|
+
data: '127.0.0.1' },
|
|
172
|
+
|
|
173
|
+
{ name: 'lorien.local',
|
|
174
|
+
type: 'A',
|
|
175
|
+
class: 1,
|
|
176
|
+
ttl: 120,
|
|
177
|
+
data: '127.94.0.1' },
|
|
178
|
+
|
|
179
|
+
{ name: 'lorien.local',
|
|
180
|
+
type: 'A',
|
|
181
|
+
class: 1,
|
|
182
|
+
ttl: 120,
|
|
183
|
+
data: '172.16.38.224' },
|
|
184
|
+
|
|
185
|
+
{ name: 'QmbBHw1Xx9pUpAbrVZUKTPL5Rsph5Q9GQhRvcWVBPFgGtC.discovery.ipfs.io.local',
|
|
186
|
+
type: 'TXT',
|
|
187
|
+
class: 1,
|
|
188
|
+
ttl: 120,
|
|
189
|
+
data: 'QmbBHw1Xx9pUpAbrVZUKTPL5Rsph5Q9GQhRvcWVBPFgGtC' } ],
|
|
190
|
+
|
|
191
|
+
*/
|
package/src/query.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import os from 'os'
|
|
2
|
+
import debug from 'debug'
|
|
3
|
+
import { Multiaddr, MultiaddrObject } from '@multiformats/multiaddr'
|
|
4
|
+
import { base58btc } from 'multiformats/bases/base58'
|
|
5
|
+
import { PeerId } from '@libp2p/peer-id'
|
|
6
|
+
import type { PeerData } from '@libp2p/interfaces/peer-data'
|
|
7
|
+
import type { MulticastDNS, ResponsePacket, QueryPacket } from 'multicast-dns'
|
|
8
|
+
import type { SrvAnswer, StringAnswer, TxtAnswer, Answer } from 'dns-packet'
|
|
9
|
+
|
|
10
|
+
const log = Object.assign(debug('libp2p:mdns'), {
|
|
11
|
+
error: debug('libp2p:mdns:error')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export function queryLAN (mdns: MulticastDNS, serviceTag: string, interval: number) {
|
|
15
|
+
const query = () => {
|
|
16
|
+
log('query', serviceTag)
|
|
17
|
+
mdns.query({
|
|
18
|
+
questions: [{
|
|
19
|
+
name: serviceTag,
|
|
20
|
+
type: 'PTR'
|
|
21
|
+
}]
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Immediately start a query, then do it every interval.
|
|
26
|
+
query()
|
|
27
|
+
return setInterval(query, interval)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface Answers {
|
|
31
|
+
ptr?: StringAnswer
|
|
32
|
+
srv?: SrvAnswer
|
|
33
|
+
txt?: TxtAnswer
|
|
34
|
+
a: StringAnswer[]
|
|
35
|
+
aaaa: StringAnswer[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function gotResponse (rsp: ResponsePacket, localPeerId: PeerId, serviceTag: string): PeerData | undefined {
|
|
39
|
+
if (rsp.answers == null) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const answers: Answers = {
|
|
44
|
+
a: [],
|
|
45
|
+
aaaa: []
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
rsp.answers.forEach((answer) => {
|
|
49
|
+
switch (answer.type) {
|
|
50
|
+
case 'PTR': answers.ptr = answer; break
|
|
51
|
+
case 'SRV': answers.srv = answer; break
|
|
52
|
+
case 'TXT': answers.txt = answer; break
|
|
53
|
+
case 'A': answers.a.push(answer); break
|
|
54
|
+
case 'AAAA': answers.aaaa.push(answer); break
|
|
55
|
+
default: break
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
if (answers.ptr == null ||
|
|
60
|
+
answers.ptr.name !== serviceTag ||
|
|
61
|
+
answers.txt == null ||
|
|
62
|
+
answers.srv == null) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const b58Id = answers.txt.data[0].toString()
|
|
67
|
+
const port = answers.srv.data.port
|
|
68
|
+
const multiaddrs: Multiaddr[] = []
|
|
69
|
+
|
|
70
|
+
answers.a.forEach((a) => {
|
|
71
|
+
const ma = new Multiaddr(`/ip4/${a.data}/tcp/${port}`)
|
|
72
|
+
|
|
73
|
+
if (!multiaddrs.some((m) => m.equals(ma))) {
|
|
74
|
+
multiaddrs.push(ma)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
answers.aaaa.forEach((a) => {
|
|
79
|
+
const ma = new Multiaddr(`/ip6/${a.data}/tcp/${port}`)
|
|
80
|
+
|
|
81
|
+
if (!multiaddrs.some((m) => m.equals(ma))) {
|
|
82
|
+
multiaddrs.push(ma)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
if (localPeerId.toString(base58btc) === b58Id) {
|
|
87
|
+
return // replied to myself, ignore
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
log('peer found -', b58Id)
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
id: PeerId.fromString(b58Id),
|
|
94
|
+
multiaddrs,
|
|
95
|
+
protocols: []
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function gotQuery (qry: QueryPacket, mdns: MulticastDNS, peerId: PeerId, multiaddrs: Multiaddr[], serviceTag: string, broadcast: boolean) {
|
|
100
|
+
if (!broadcast) {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const addresses: MultiaddrObject[] = multiaddrs.reduce<MultiaddrObject[]>((acc, addr) => {
|
|
105
|
+
if (addr.isThinWaistAddress()) {
|
|
106
|
+
acc.push(addr.toOptions())
|
|
107
|
+
}
|
|
108
|
+
return acc
|
|
109
|
+
}, [])
|
|
110
|
+
|
|
111
|
+
// Only announce TCP for now
|
|
112
|
+
if (addresses.length === 0) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (qry.questions[0] != null && qry.questions[0].name === serviceTag) {
|
|
117
|
+
const answers: Answer[] = []
|
|
118
|
+
|
|
119
|
+
answers.push({
|
|
120
|
+
name: serviceTag,
|
|
121
|
+
type: 'PTR',
|
|
122
|
+
class: 'IN',
|
|
123
|
+
ttl: 120,
|
|
124
|
+
data: peerId.toString(base58btc) + '.' + serviceTag
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Only announce TCP multiaddrs for now
|
|
128
|
+
const port = addresses[0].port
|
|
129
|
+
|
|
130
|
+
answers.push({
|
|
131
|
+
name: peerId.toString(base58btc) + '.' + serviceTag,
|
|
132
|
+
type: 'SRV',
|
|
133
|
+
class: 'IN',
|
|
134
|
+
ttl: 120,
|
|
135
|
+
data: {
|
|
136
|
+
priority: 10,
|
|
137
|
+
weight: 1,
|
|
138
|
+
port: port,
|
|
139
|
+
target: os.hostname()
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
answers.push({
|
|
144
|
+
name: peerId.toString(base58btc) + '.' + serviceTag,
|
|
145
|
+
type: 'TXT',
|
|
146
|
+
class: 'IN',
|
|
147
|
+
ttl: 120,
|
|
148
|
+
data: peerId.toString(base58btc)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
addresses.forEach((addr) => {
|
|
152
|
+
if ([4, 6].includes(addr.family)) {
|
|
153
|
+
answers.push({
|
|
154
|
+
name: os.hostname(),
|
|
155
|
+
type: addr.family === 4 ? 'A' : 'AAAA',
|
|
156
|
+
class: 'IN',
|
|
157
|
+
ttl: 120,
|
|
158
|
+
data: addr.host
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
log('responding to query')
|
|
164
|
+
mdns.respond(answers)
|
|
165
|
+
}
|
|
166
|
+
}
|