1 |
3a515b92
|
cagy
|
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 |
|
|
}
|