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 || opts.bind === false) return cb(null)
|
64 | socket.once('error', cb)
|
65 | socket.bind(port, opts.bind || 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 | else if (!rinfo.host && !rinfo.address) rinfo.address = me.address
|
81 |
|
82 | bind(onbind)
|
83 |
|
84 | function onbind (err) {
|
85 | if (destroyed) return cb()
|
86 | if (err) return cb(err)
|
87 | var message = packet.encode(value)
|
88 | socket.send(message, 0, message.length, rinfo.port, rinfo.address || rinfo.host, cb)
|
89 | }
|
90 | }
|
91 |
|
92 | that.response =
|
93 | that.respond = function (res, rinfo, cb) {
|
94 | if (Array.isArray(res)) res = {answers: res}
|
95 |
|
96 | res.type = 'response'
|
97 | res.flags = (res.flags || 0) | packet.AUTHORITATIVE_ANSWER
|
98 | that.send(res, rinfo, cb)
|
99 | }
|
100 |
|
101 | that.query = function (q, type, rinfo, cb) {
|
102 | if (typeof type === 'function') return that.query(q, null, null, type)
|
103 | if (typeof type === 'object' && type && type.port) return that.query(q, null, type, rinfo)
|
104 | if (typeof rinfo === 'function') return that.query(q, type, null, rinfo)
|
105 | if (!cb) cb = noop
|
106 |
|
107 | if (typeof q === 'string') q = [{name: q, type: type || 'ANY'}]
|
108 | if (Array.isArray(q)) q = {type: 'query', questions: q}
|
109 |
|
110 | q.type = 'query'
|
111 | that.send(q, rinfo, cb)
|
112 | }
|
113 |
|
114 | that.destroy = function (cb) {
|
115 | if (!cb) cb = noop
|
116 | if (destroyed) return process.nextTick(cb)
|
117 | destroyed = true
|
118 | clearInterval(interval)
|
119 |
|
120 |
|
121 |
|
122 | for (var iface in memberships) {
|
123 | try {
|
124 | socket.dropMembership(ip, iface)
|
125 | } catch (e) {
|
126 |
|
127 | }
|
128 | }
|
129 | memberships = {}
|
130 | socket.close(cb)
|
131 | }
|
132 |
|
133 | that.update = function () {
|
134 | var ifaces = opts.interface ? [].concat(opts.interface) : allInterfaces()
|
135 | var updated = false
|
136 |
|
137 | for (var i = 0; i < ifaces.length; i++) {
|
138 | var addr = ifaces[i]
|
139 | if (memberships[addr]) continue
|
140 |
|
141 | try {
|
142 | socket.addMembership(ip, addr)
|
143 | memberships[addr] = true
|
144 | updated = true
|
145 | } catch (err) {
|
146 | that.emit('warning', err)
|
147 | }
|
148 | }
|
149 |
|
150 | if (updated) {
|
151 | if (socket.setMulticastInterface) {
|
152 | try {
|
153 | socket.setMulticastInterface(opts.interface || defaultInterface())
|
154 | } catch (err) {
|
155 | that.emit('warning', err)
|
156 | }
|
157 | }
|
158 | that.emit('networkInterface')
|
159 | }
|
160 | }
|
161 |
|
162 | return that
|
163 | }
|
164 |
|
165 | function defaultInterface () {
|
166 | var networks = os.networkInterfaces()
|
167 | var names = Object.keys(networks)
|
168 |
|
169 | for (var i = 0; i < names.length; i++) {
|
170 | var net = networks[names[i]]
|
171 | for (var j = 0; j < net.length; j++) {
|
172 | var iface = net[j]
|
173 | if (isIPv4(iface.family) && !iface.internal) {
|
174 | if (os.platform() === 'darwin' && names[i] === 'en0') return iface.address
|
175 | return '0.0.0.0'
|
176 | }
|
177 | }
|
178 | }
|
179 |
|
180 | return '127.0.0.1'
|
181 | }
|
182 |
|
183 | function allInterfaces () {
|
184 | var networks = os.networkInterfaces()
|
185 | var names = Object.keys(networks)
|
186 | var res = []
|
187 |
|
188 | for (var i = 0; i < names.length; i++) {
|
189 | var net = networks[names[i]]
|
190 | for (var j = 0; j < net.length; j++) {
|
191 | var iface = net[j]
|
192 | if (isIPv4(iface.family)) {
|
193 | res.push(iface.address)
|
194 |
|
195 | break
|
196 | }
|
197 | }
|
198 | }
|
199 |
|
200 | return res
|
201 | }
|
202 |
|
203 | function isIPv4 (family) {
|
204 | return family === 4 || family === 'IPv4'
|
205 | }
|