1
|
var packet = require('dns-packet')
|
2
|
var dgram = require('dgram')
|
3
|
var thunky = require('thunky')
|
4
|
var events = require('events')
|
5
|
var os = require('os')
|
6
|
|
7
|
var noop = function () {}
|
8
|
|
9
|
module.exports = function (opts) {
|
10
|
if (!opts) opts = {}
|
11
|
|
12
|
var that = new events.EventEmitter()
|
13
|
var port = typeof opts.port === 'number' ? opts.port : 5353
|
14
|
var type = opts.type || 'udp4'
|
15
|
var ip = opts.ip || opts.host || (type === 'udp4' ? '224.0.0.251' : null)
|
16
|
var me = {address: ip, port: port}
|
17
|
var memberships = {}
|
18
|
var destroyed = false
|
19
|
var interval = null
|
20
|
|
21
|
if (type === 'udp6' && (!ip || !opts.interface)) {
|
22
|
throw new Error('For IPv6 multicast you must specify `ip` and `interface`')
|
23
|
}
|
24
|
|
25
|
var socket = opts.socket || dgram.createSocket({
|
26
|
type: type,
|
27
|
reuseAddr: opts.reuseAddr !== false,
|
28
|
toString: function () {
|
29
|
return type
|
30
|
}
|
31
|
})
|
32
|
|
33
|
socket.on('error', function (err) {
|
34
|
if (err.code === 'EACCES' || err.code === 'EADDRINUSE') that.emit('error', err)
|
35
|
else that.emit('warning', err)
|
36
|
})
|
37
|
|
38
|
socket.on('message', function (message, rinfo) {
|
39
|
try {
|
40
|
message = packet.decode(message)
|
41
|
} catch (err) {
|
42
|
that.emit('warning', err)
|
43
|
return
|
44
|
}
|
45
|
|
46
|
that.emit('packet', message, rinfo)
|
47
|
|
48
|
if (message.type === 'query') that.emit('query', message, rinfo)
|
49
|
if (message.type === 'response') that.emit('response', message, rinfo)
|
50
|
})
|
51
|
|
52
|
socket.on('listening', function () {
|
53
|
if (!port) port = me.port = socket.address().port
|
54
|
if (opts.multicast !== false) {
|
55
|
that.update()
|
56
|
interval = setInterval(that.update, 5000)
|
57
|
socket.setMulticastTTL(opts.ttl || 255)
|
58
|
socket.setMulticastLoopback(opts.loopback !== false)
|
59
|
}
|
60
|
})
|
61
|
|
62
|
var bind = thunky(function (cb) {
|
63
|
if (!port) return cb(null)
|
64
|
socket.once('error', cb)
|
65
|
socket.bind(port, opts.interface, function () {
|
66
|
socket.removeListener('error', cb)
|
67
|
cb(null)
|
68
|
})
|
69
|
})
|
70
|
|
71
|
bind(function (err) {
|
72
|
if (err) return that.emit('error', err)
|
73
|
that.emit('ready')
|
74
|
})
|
75
|
|
76
|
that.send = function (value, rinfo, cb) {
|
77
|
if (typeof rinfo === 'function') return that.send(value, null, rinfo)
|
78
|
if (!cb) cb = noop
|
79
|
if (!rinfo) rinfo = me
|
80
|
|
81
|
bind(onbind)
|
82
|
|
83
|
function onbind (err) {
|
84
|
if (destroyed) return cb()
|
85
|
if (err) return cb(err)
|
86
|
var message = packet.encode(value)
|
87
|
socket.send(message, 0, message.length, rinfo.port, rinfo.address || rinfo.host, cb)
|
88
|
}
|
89
|
}
|
90
|
|
91
|
that.response =
|
92
|
that.respond = function (res, rinfo, cb) {
|
93
|
if (Array.isArray(res)) res = {answers: res}
|
94
|
|
95
|
res.type = 'response'
|
96
|
res.flags = (res.flags || 0) | packet.AUTHORITATIVE_ANSWER
|
97
|
that.send(res, rinfo, cb)
|
98
|
}
|
99
|
|
100
|
that.query = function (q, type, rinfo, cb) {
|
101
|
if (typeof type === 'function') return that.query(q, null, null, type)
|
102
|
if (typeof type === 'object' && type && type.port) return that.query(q, null, type, rinfo)
|
103
|
if (typeof rinfo === 'function') return that.query(q, type, null, rinfo)
|
104
|
if (!cb) cb = noop
|
105
|
|
106
|
if (typeof q === 'string') q = [{name: q, type: type || 'ANY'}]
|
107
|
if (Array.isArray(q)) q = {type: 'query', questions: q}
|
108
|
|
109
|
q.type = 'query'
|
110
|
that.send(q, rinfo, cb)
|
111
|
}
|
112
|
|
113
|
that.destroy = function (cb) {
|
114
|
if (!cb) cb = noop
|
115
|
if (destroyed) return process.nextTick(cb)
|
116
|
destroyed = true
|
117
|
clearInterval(interval)
|
118
|
socket.once('close', cb)
|
119
|
socket.close()
|
120
|
}
|
121
|
|
122
|
that.update = function () {
|
123
|
var ifaces = opts.interface ? [].concat(opts.interface) : allInterfaces()
|
124
|
var updated = false
|
125
|
|
126
|
for (var i = 0; i < ifaces.length; i++) {
|
127
|
var addr = ifaces[i]
|
128
|
|
129
|
if (memberships[addr]) continue
|
130
|
memberships[addr] = true
|
131
|
updated = true
|
132
|
|
133
|
try {
|
134
|
socket.addMembership(ip, addr)
|
135
|
} catch (err) {
|
136
|
that.emit('warning', err)
|
137
|
}
|
138
|
}
|
139
|
|
140
|
if (!updated || !socket.setMulticastInterface) return
|
141
|
socket.setMulticastInterface(opts.interface || defaultInterface())
|
142
|
}
|
143
|
|
144
|
return that
|
145
|
}
|
146
|
|
147
|
function defaultInterface () {
|
148
|
var networks = os.networkInterfaces()
|
149
|
var names = Object.keys(networks)
|
150
|
|
151
|
for (var i = 0; i < names.length; i++) {
|
152
|
var net = networks[names[i]]
|
153
|
for (var j = 0; j < net.length; j++) {
|
154
|
var iface = net[j]
|
155
|
if (iface.family === 'IPv4' && !iface.internal) return iface.address
|
156
|
}
|
157
|
}
|
158
|
|
159
|
return '127.0.0.1'
|
160
|
}
|
161
|
|
162
|
function allInterfaces () {
|
163
|
var networks = os.networkInterfaces()
|
164
|
var names = Object.keys(networks)
|
165
|
var res = []
|
166
|
|
167
|
for (var i = 0; i < names.length; i++) {
|
168
|
var net = networks[names[i]]
|
169
|
for (var j = 0; j < net.length; j++) {
|
170
|
var iface = net[j]
|
171
|
if (iface.family === 'IPv4') {
|
172
|
res.push(iface.address)
|
173
|
// could only addMembership once per interface (https://nodejs.org/api/dgram.html#dgram_socket_addmembership_multicastaddress_multicastinterface)
|
174
|
break
|
175
|
}
|
176
|
}
|
177
|
}
|
178
|
|
179
|
return res
|
180
|
}
|