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 | }
|
---|