UNPKG

5.53 kBJavaScriptView Raw
1var packet = require('dns-packet')
2var dgram = require('dgram')
3var thunky = require('thunky')
4var events = require('events')
5var os = require('os')
6
7var noop = function () {}
8
9module.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 // Need to drop memberships by hand and ignore errors.
121 // socket.close() does not cope with errors.
122 for (var iface in memberships) {
123 try {
124 socket.dropMembership(ip, iface)
125 } catch (e) {
126 // eat it
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
165function 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
183function 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 // could only addMembership once per interface (https://nodejs.org/api/dgram.html#dgram_socket_addmembership_multicastaddress_multicastinterface)
195 break
196 }
197 }
198 }
199
200 return res
201}
202
203function isIPv4 (family) { // for backwards compat
204 return family === 4 || family === 'IPv4'
205}