homespun-discovery
Version:
discover devices and services on the local network.
202 lines (146 loc) • 5.6 kB
JavaScript
/* jshint asi: true, node: true, laxbreak: true, laxcomma: true, undef: true, unused: true */
var dgram = require('dgram')
, EventEmitter = require('events').EventEmitter
, snmpn = require('snmp-native')
, underscore = require('underscore')
, util = require('util')
var logger, observers, snmp
var init = function (log) {
logger = log || { error: console.error }
if (snmp) return
snmp = new SNMP()
observers = []
}
var done = function () {
if (!snmp) return
(observers || []).forEach(function (observer) { observer.destroy.bind(observer)() })
snmp.destroy()
snmp = null
}
var Observe = function (options) { /* { sysObjectIDs: ['1.3.6.1....' ... ] } */
if (!(this instanceof Observe)) return new Observe(options)
var self = this
EventEmitter.call(self)
var onUp = function(options, service) { self.emit('up', self._options, service) }
var onError = function(options, err) { self.emit('error', self._options, err) }
self._options = underscore.clone(options)
if (!self._options.sysObjectIDs) throw new Error('options.sysObjectIDs is missing')
if (!util.isArray(self._options.sysObjectIDs)) throw new Error('options.sysObjectIDs not an array')
self._options.sysObjectIDs.forEach(function (sysObjectID) {
sysObjectID.split('.').forEach(function (n) {
if (typeof n === 'string') n = parseInt(n, 10)
if ((n < 0) || (!Number.isFinite(n))) throw new Error('invalid sysObjectID: ' + sysObjectID)
})
})
self._browser = snmp.find(self._options, onUp).on('up', onUp).on('error', onError)
observers.push(self)
}
util.inherits(Observe, EventEmitter)
Observe.prototype.destroy = function () {
var self = this
var i = underscore.findIndex(observers, function(observer) { return (observer === self) })
if (i !== -1) observers.splice(i, 1)
self.emit('end', self._options)
self._browser.stop()
}
Observe.prototype.oidS = function (b) { return underscore.map(b, function (n) { return n.toString() }).join('.') }
Observe.prototype.oidI = function (s) { return underscore.map(s.split('.'), function (n) { return +n }) }
var SNMP_IPADDR = '255.255.255.255'
, SNMP_PORTNO = 161
, SNMP_TTL = 10
var SNMP = function () {
if (!(this instanceof SNMP)) return new SNMP()
var self = this
EventEmitter.call(self)
self._socket = dgram.createSocket('udp4').on('error', function (err) {
logger.error('SNMP socket', err)
self.emit('error', err)
}).on('message', function (buffer, rinfo) {
var bindings
, oidS = Observe.prototype.oidS
, packet = snmpn.parse(buffer)
, response = { host: rinfo.address, port: rinfo.port, packet: packet }
if ((!packet.pdu) || (packet.pdu.type !== 2) || (packet.pdu.error !== 0)
|| (!util.isArray(packet.pdu.varbinds)) || (packet.pdu.varbinds.length !== 3)) return
bindings = packet.pdu.varbinds
if (bindings[1].type !== 6) return
bindings.forEach(function (binding) {
binding.oid = oidS(binding.oid)
if (binding.type === 6) binding.value = oidS(binding.value)
delete binding.valueHex
delete binding.valueRaw
})
self.emit('response', response)
}).on('listening', function () {
self._socket.setBroadcast(true)
self._socket.setTTL(SNMP_TTL)
self.update.bind(self)()
self._timer = setInterval(self.update.bind(self), 30 * 1000)
})
self._socket.bind()
}
util.inherits(SNMP, EventEmitter)
SNMP.prototype.destroy = function () {
try { this._socket.close() } catch (ex) {
logger.error('SNMP close', ex)
}
if (this._timer) {
clearInterval(this._timer)
this._timer = null
}
}
SNMP.prototype.find = function (options, onUp) {
return new Browser(this, options, onUp)
}
SNMP.prototype.update = function () {
var data
, oidI = Observe.prototype.oidI
, packet = new snmpn.Packet()
// sysDescr.0, sysObjectID.0, and sysName.0
packet.pdu.varbinds[0].oid = oidI('1.3.6.1.2.1.1.1.0')
packet.pdu.varbinds[1] = underscore.extend({}, packet.pdu.varbinds[0], { oid : oidI('1.3.6.1.2.1.1.2.0') } )
packet.pdu.varbinds[2] = underscore.extend({}, packet.pdu.varbinds[0], { oid : oidI('1.3.6.1.2.1.1.5.0') } )
data = snmpn.encode(packet)
this._socket.send(data, 0, data.length, SNMP_PORTNO, SNMP_IPADDR, function (err, bytes) {/* jshint unused: false */
if (err) return logger.error('SNMP send', err)
})
}
var Browser = function (snmp, options, onUp) {
if (!(this instanceof Browser)) return new Browser(snmp, options, onUp)
EventEmitter.call(this)
if (typeof options === 'function') {
onUp = options
options = {}
}
this._options = underscore.clone(options)
this._snmp = snmp
if (onUp) this.on('up', onUp)
this.start()
}
util.inherits(Browser, EventEmitter)
Browser.prototype.start = function () {
var self = this
if (self._onResponse) return
self._onResponse = function (response) {
if (self._options.sysObjectIDs.indexOf(response.packet.pdu.varbinds[1].value) !== -1) {
self.emit('up', self._options, response)
}
}
self._onError = function (err) { self.emit('error', self._options, err) }
self._snmp.on('response', self._onResponse).on('error', self._onError)
self.update()
}
Browser.prototype.stop = function () {
if (!this._onResponse) return
this._snmp.removeListener('response', this._onResponse)
this._onResponse = null
this._snmp.removeListener('error', this._onError)
}
Browser.prototype.update = function () {
this._snmp.update.bind(this._snmp)()
}
module.exports =
{ init : init
, done : done
, Observe : Observe
}