index.js 4.7 KB

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