2015-03-04 20:55:53 +01:00
2017-06-27 00:02:34 +02:00
module . exports = function ( RED ) {
2015-03-26 19:55:03 +01:00
"use strict" ;
2022-04-21 17:49:03 +02:00
const SNMP = require ( "net-snmp" ) ;
const sessions = { } ;
function generateUUID ( ) {
let d = Date . now ( ) ;
let d2 = ( ( typeof performance !== 'undefined' ) && performance . now && ( performance . now ( ) * 1000 ) ) || ( Date . now ( ) * Math . random ( ) * 100000 ) ; //Time in microseconds since load
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' . replace ( /[xy]/g , function ( c ) {
let r = Math . random ( ) * 16 ; //random number between 0 and 16
if ( d > 0 ) { //Use timestamp until depleted
r = ( d + r ) % 16 | 0 ;
d = Math . floor ( d / 16 ) ;
} else { //Use microseconds since page-load if supported
r = ( d2 + r ) % 16 | 0 ;
d2 = Math . floor ( d2 / 16 ) ;
2020-10-24 03:07:16 +02:00
}
2022-04-21 17:49:03 +02:00
return ( c === 'x' ? r : ( r & 0x3 | 0x8 ) ) . toString ( 16 ) ;
} ) ;
}
function openSession ( sessionid , host , user , options ) {
// SNMPv3 call
if ( options . version === SNMP . Version3 ) {
sessions [ sessionid ] = SNMP . createV3Session ( host , user , options ) ;
2017-05-30 18:15:36 +02:00
}
2020-10-24 03:07:16 +02:00
// SNMPv1 or SNMPv2c call
2022-04-21 17:49:03 +02:00
else {
sessions [ sessionid ] = SNMP . createSession ( host , user . community , options ) ;
2020-10-24 03:07:16 +02:00
}
return sessions [ sessionid ] ;
}
// Any session needs to be closed after completion
function closeSession ( sessionid ) {
2022-04-21 17:49:03 +02:00
try {
sessions [ sessionid ] . removeAllListeners ( ) ;
} catch ( e ) { }
try {
sessions [ sessionid ] . close ( ) ;
} catch ( e ) { }
delete sessions [ sessionid ] ;
}
function initSnmpNode ( node , config ) {
node . community = config . community ;
node . host = config . host ;
node . version = config . version ;
node . auth = config . auth ;
node . authprot = config . authprot ;
node . privprot = config . privprot ;
if ( node . credentials ) {
node . username = node . credentials . username ;
node . authkey = node . credentials . authkey ;
node . privkey = node . credentials . privkey ;
}
node . timeout = Number ( config . timeout || 5 ) * 1000 ;
}
function prepareSnmpOptions ( node , msg ) {
let host = node . host || msg . host ;
const sessionid = generateUUID ( ) ;
const user = { }
const options = { } ;
2022-05-07 17:40:57 +02:00
const compat = { "v1" : "1" , "v2" : "2c" , "v2c" : "2c" , "v3" : "3" } ;
if ( compat [ node . version ] ) {
node . version = compat [ node . version ] ;
} else if ( [ "1" , "2c" , "3" ] . indexOf ( node . version ) < 0 ) {
node . version = "1" ;
}
2022-04-21 17:49:03 +02:00
options . version = node . version ;
2022-05-07 17:40:57 +02:00
if ( node . version === "1" ) {
2022-04-21 17:49:03 +02:00
options . version = SNMP . Version1 ;
user . community = node . community || msg . community ;
2022-05-07 17:40:57 +02:00
} else if ( node . version === "2c" ) {
2022-04-21 17:49:03 +02:00
options . version = SNMP . Version2c ;
user . community = node . community || msg . community ;
2022-05-07 17:40:57 +02:00
} else if ( node . version === "3" ) {
2022-04-21 17:49:03 +02:00
user . name = node . username || msg . username || "" ;
user . level = SNMP . SecurityLevel . noAuthNoPriv ;
user . authProtocol = SNMP . AuthProtocols . none ;
user . authKey = "" ;
user . privProtocol = SNMP . PrivProtocols . none ;
user . privKey = "" ;
options . version = SNMP . Version3 ;
if ( node . auth === "authNoPriv" || node . auth === "authPriv" ) {
user . level = SNMP . SecurityLevel . authNoPriv ;
user . authProtocol = ( node . authprot === "SHA" ) ? SNMP . AuthProtocols . sha : SNMP . AuthProtocols . md5 ;
user . authKey = node . authkey || msg . authkey || "" ;
if ( node . auth === "authPriv" ) {
user . level = SNMP . SecurityLevel . authPriv ;
if ( node . privprot === "DES" || node . privprot === "AES" ) {
user . privProtocol = ( node . privprot === "AES" ) ? SNMP . PrivProtocols . aes : SNMP . PrivProtocols . des ;
user . privKey = node . privkey || msg . privkey || "" ;
}
}
}
}
options . timeout = node . timeout ;
options . debug = msg . debug || undefined ;
options . port = options . port || 161 ;
options . retries = options . retries || 1 ;
if ( msg . engineID ) {
options . engineID = msg . engineID ; //The engineID used for SNMPv3 communications, given as a hex string - defaults to a system-generated engineID containing elements of random
}
if ( msg . backoff ) {
options . backoff = msg . backoff ; //The factor by which to increase the timeout for every retry, defaults to 1 for no increase
}
if ( msg . backwardsGetNexts ) {
options . backwardsGetNexts = msg . backwardsGetNexts ; //boolean to allow GetNext operations to retrieve lexicographically preceding OIDs
}
if ( msg . idBitsSize === 16 || msg . idBitsSize === 32 ) {
options . idBitsSize = msg . idBitsSize ; //Either 16 or 32, defaults to 32. Used to reduce the size of the generated id for compatibility with some older devices.
}
const ipv = parseIP ( host ) ;
if ( ipv . version === 4 ) {
host = ipv . ip ;
options . port = ipv . port || options . port ;
options . transport = 'udp4' ;
} else if ( ipv . version === 6 ) {
host = ipv . ip ;
options . port = ipv . port || options . port ;
options . transport = 'udp6' ;
} else {
//probably a host name
if ( host . indexOf ( ":" ) > 0 ) {
host = host . split ( ":" ) [ 0 ] ;
options . port = host . split ( ":" ) [ 1 ] ;
}
}
return {
host : host ,
sessionid : sessionid ,
user : user ,
options : options ,
}
2017-05-30 18:15:36 +02:00
}
2022-04-21 17:49:03 +02:00
function parseIP ( ip ) {
const IPV4 _PAT = /^(\d+)\.(\d+)\.(\d+)\.(\d+)(?::(\d+)){0,1}$/g ;
const IPV6 _DOUBLE _COL _PAT = /^\[{0,1}([0-9a-f:]*)::([0-9a-f:]*)(?:\]:(\d+)){0,1}$/g ;
const ipv4Matcher = IPV4 _PAT . exec ( ip ) ;
let hex = "" ;
let port = undefined ;
let ipOnly = [ ] ;
try {
if ( ipv4Matcher && ipv4Matcher . length ) {
for ( let i = 1 ; i <= 4 ; i ++ ) {
ipOnly . push ( ipv4Matcher [ i ] ) ;
hex += toHex4 ( ipv4Matcher [ i ] ) ;
}
if ( ipv4Matcher [ 5 ] ) {
port = parseInt ( ipv4Matcher [ 5 ] ) ;
}
return { ip : ipOnly . join ( "." ) , hex , port , version : 4 } ;
}
// IPV6 Must be colons format (a:b:c:d:e:A.B.C.D not currently supported)
let ipv6Pattern = "^\\[{0,1}" ;
for ( let i = 1 ; i <= 7 ; i ++ ) {
ipv6Pattern += "([0-9a-f]+):" ;
}
ipv6Pattern += "([0-9a-f]+)(?:\\]:(\\d+)){0,1}$" ;
const IPV6 _PAT = new RegExp ( ipv6Pattern ) ;
// IPV6, double colon
const ipv6DoubleColonMatcher = IPV6 _DOUBLE _COL _PAT . exec ( ip ) ;
if ( ipv6DoubleColonMatcher && ipv6DoubleColonMatcher . length ) {
let p1 = ipv6DoubleColonMatcher [ 1 ] ;
if ( ! p1 ) {
p1 = "0" ;
}
let p2 = ipv6DoubleColonMatcher [ 2 ] ;
if ( ! p2 ) {
p2 = "0" ;
}
p1 = p1 . padStart ( 4 , "0" ) ;
p2 = p2 . padStart ( 4 , "0" ) ;
ip = p1 + getZeros ( 8 - numCount ( p1 ) - numCount ( p2 ) ) + p2 ;
if ( ipv6DoubleColonMatcher [ 3 ] ) {
ip = "[" + ip + "]:" + ipv6DoubleColonMatcher [ 3 ] ;
}
}
// IPV6
const ipv6Matcher = IPV6 _PAT . exec ( ip ) ;
if ( ipv6Matcher && ipv6Matcher . length ) {
for ( let i = 1 ; i <= 8 ; i ++ ) {
const p = toHex6 ( ipv6Matcher [ i ] ) . padStart ( 4 , "0" ) ;
ipOnly . push ( p ) ;
hex += p ;
}
if ( ipv6Matcher [ 9 ] ) {
port = parseInt ( ipv6Matcher [ 9 ] ) ;
}
return { ip : ipOnly . join ( ":" ) , hex , port , version : 6 } ;
}
2017-05-30 18:15:36 +02:00
2022-04-21 17:49:03 +02:00
throw new Error ( "Unknown address: " + ip ) ;
} catch ( error ) {
return { ip , hex , port , version : null , error : error } ;
}
function numCount ( /** @type {string} */ s ) {
return s . split ( ":" ) . length ;
}
function getZeros ( /** @type {number} */ count ) {
const sb = [ ":" ] ;
while ( count > 0 ) {
sb . push ( "0000:" ) ;
count -- ;
}
return sb . join ( "" ) ;
}
function toHex4 ( /** @type {string} */ s ) {
const val = parseInt ( s ) ;
if ( val < 0 || val > 255 ) {
throw new Error ( "Invalid value : " + s ) ;
}
return val . toString ( 16 ) . padStart ( 2 , "0" ) ;
}
function toHex6 ( /** @type {string} */ s ) {
const val = parseInt ( s , 16 ) ;
if ( val < 0 || val > 65536 ) {
throw new Error ( "Invalid hex value : " + s ) ;
}
return s ;
}
}
2015-03-04 20:55:53 +01:00
function SnmpNode ( n ) {
2022-04-21 17:49:03 +02:00
const node = this ;
RED . nodes . createNode ( node , n ) ;
initSnmpNode ( node , n ) ;
node . oids = n . oids ? n . oids . replace ( /\s/g , "" ) : "" ;
2015-03-04 20:55:53 +01:00
2022-04-21 17:49:03 +02:00
node . on ( "input" , function ( msg ) {
const oids = node . oids || msg . oid ;
const { host , sessionid , user , options } = prepareSnmpOptions ( node , msg ) ;
2015-03-04 20:55:53 +01:00
if ( oids ) {
2022-04-21 17:49:03 +02:00
let sess = openSession ( sessionid , host , user , options ) ;
sess . on ( "error" , function ( err ) {
node . error ( err , msg ) ;
} )
sess . get ( oids . split ( "," ) , function ( error , varbinds ) {
2015-03-04 20:55:53 +01:00
if ( error ) {
2017-06-27 00:02:34 +02:00
node . error ( error . toString ( ) , msg ) ;
2022-04-21 17:49:03 +02:00
} else {
for ( let i = 0 ; i < varbinds . length ; i ++ ) {
let vb = varbinds [ i ] ;
if ( SNMP . isVarbindError ( vb ) ) {
node . error ( SNMP . varbindError ( vb ) , msg ) ;
vb . _error = SNMP . varbindError ( vb ) ; //add _error to msg so users can determine the varbind is not valid
2015-03-04 20:55:53 +01:00
}
else {
2022-04-21 17:49:03 +02:00
if ( vb . type == 4 ) { vb . value = vb . value . toString ( ) ; }
2015-03-04 20:55:53 +01:00
}
2022-04-21 17:49:03 +02:00
vb . tstr = SNMP . ObjectType [ vb . type ] ;
2015-03-04 20:55:53 +01:00
}
msg . payload = varbinds ;
2022-04-21 17:49:03 +02:00
msg . oid = oids ;
2015-03-04 20:55:53 +01:00
node . send ( msg ) ;
}
2020-10-24 03:07:16 +02:00
closeSession ( sessionid ) ; // Needed to close the session else a bad or good read could affect future readings
2015-03-04 20:55:53 +01:00
} ) ;
2022-04-21 17:49:03 +02:00
} else {
2015-03-04 20:55:53 +01:00
node . warn ( "No oid(s) to search for" ) ;
}
} ) ;
}
2022-04-21 17:49:03 +02:00
RED . nodes . registerType ( "snmp" , SnmpNode , {
credentials : {
username : { type : "text" } ,
authkey : { type : "password" } ,
privkey : { type : "password" }
}
} ) ;
2017-06-27 00:02:34 +02:00
function SnmpSNode ( n ) {
2022-04-21 17:49:03 +02:00
const node = this ;
RED . nodes . createNode ( node , n ) ;
initSnmpNode ( node , n ) ;
node . varbinds = n . varbinds ;
if ( node . varbinds && node . varbinds . trim ( ) . length === 0 ) { delete node . varbinds ; }
node . on ( "input" , function ( msg ) {
const { host , sessionid , user , options } = prepareSnmpOptions ( node , msg ) ;
const varbinds = ( node . varbinds ) ? JSON . parse ( node . varbinds ) : msg . varbinds ;
2017-06-27 00:02:34 +02:00
if ( varbinds ) {
2022-04-21 17:49:03 +02:00
for ( let i = 0 ; i < varbinds . length ; i ++ ) {
varbinds [ i ] . type = SNMP . ObjectType [ varbinds [ i ] . type ] ;
2017-06-27 00:02:34 +02:00
}
2022-04-21 17:49:03 +02:00
let sess = openSession ( sessionid , host , user , options ) ;
sess . on ( "error" , function ( err ) {
node . error ( err , msg ) ;
} )
sess . set ( varbinds , function ( error , varbinds ) {
2017-06-27 00:02:34 +02:00
if ( error ) {
node . error ( error . toString ( ) , msg ) ;
2022-04-21 17:49:03 +02:00
} else {
for ( let i = 0 ; i < varbinds . length ; i ++ ) {
2017-06-27 00:02:34 +02:00
// for version 2c we must check each OID for an error condition
2022-04-21 17:49:03 +02:00
if ( SNMP . isVarbindError ( varbinds [ i ] ) ) {
node . error ( SNMP . varbindError ( varbinds [ i ] ) , msg ) ;
2017-06-27 00:02:34 +02:00
}
}
}
2020-10-24 03:07:16 +02:00
closeSession ( sessionid ) ;
2017-06-27 00:02:34 +02:00
} ) ;
2022-04-21 17:49:03 +02:00
} else {
2017-06-27 00:02:34 +02:00
node . warn ( "No varbinds to set" ) ;
}
} ) ;
}
2022-04-21 17:49:03 +02:00
RED . nodes . registerType ( "snmp set" , SnmpSNode , {
credentials : {
username : { type : "text" } ,
authkey : { type : "password" } ,
privkey : { type : "password" }
}
} ) ;
2017-06-27 00:02:34 +02:00
2015-03-04 20:55:53 +01:00
function SnmpTNode ( n ) {
2022-04-21 17:49:03 +02:00
const node = this ;
RED . nodes . createNode ( node , n ) ;
initSnmpNode ( node , n ) ;
node . oids = n . oids ? n . oids . replace ( /\s/g , "" ) : ""
const maxRepetitions = 20 ;
2015-03-04 20:55:53 +01:00
2016-04-12 20:18:48 +02:00
function sortInt ( a , b ) {
2015-03-26 19:55:03 +01:00
if ( a > b ) { return 1 ; }
2022-04-21 17:49:03 +02:00
else if ( b > a ) { return - 1 ; } else { return 0 ; }
2015-03-04 20:55:53 +01:00
}
2022-04-21 17:49:03 +02:00
node . on ( "input" , function ( msg ) {
const oids = node . oids || msg . oid ;
const { host , sessionid , user , options } = prepareSnmpOptions ( node , msg ) ;
2015-03-04 20:55:53 +01:00
if ( oids ) {
2016-02-11 23:50:30 +01:00
msg . oid = oids ;
2022-04-21 17:49:03 +02:00
let sess = openSession ( sessionid , host , user , options ) ;
sess . on ( "error" , function ( err ) {
node . error ( err , msg ) ;
} )
sess . table ( oids , maxRepetitions , function ( error , table ) {
2017-04-15 13:04:47 +02:00
if ( error ) {
2017-04-22 16:53:04 +02:00
node . error ( error . toString ( ) , msg ) ;
2022-04-21 17:49:03 +02:00
} else {
const indexes = [ ] ;
for ( let index in table ) {
2017-04-15 13:04:47 +02:00
if ( table . hasOwnProperty ( index ) ) {
indexes . push ( parseInt ( index ) ) ;
}
}
indexes . sort ( sortInt ) ;
2022-04-21 17:49:03 +02:00
for ( let i = 0 ; i < indexes . length ; i ++ ) {
const columns = [ ] ;
for ( let column in table [ indexes [ i ] ] ) {
2017-04-15 13:04:47 +02:00
if ( table [ indexes [ i ] ] . hasOwnProperty ( column ) ) {
columns . push ( parseInt ( column ) ) ;
}
}
columns . sort ( sortInt ) ;
}
msg . payload = table ;
node . send ( msg ) ;
}
2020-10-24 03:07:16 +02:00
closeSession ( sessionid ) ;
2017-04-15 13:04:47 +02:00
} ) ;
2022-04-21 17:49:03 +02:00
} else {
2015-03-04 20:55:53 +01:00
node . warn ( "No oid to search for" ) ;
}
} ) ;
}
2022-04-21 17:49:03 +02:00
RED . nodes . registerType ( "snmp table" , SnmpTNode , {
credentials : {
username : { type : "text" } ,
authkey : { type : "password" } ,
privkey : { type : "password" }
}
} ) ;
2016-04-12 20:18:48 +02:00
2018-01-28 18:50:41 +01:00
2016-04-12 20:18:48 +02:00
function SnmpSubtreeNode ( n ) {
2022-04-21 17:49:03 +02:00
const node = this ;
RED . nodes . createNode ( node , n ) ;
initSnmpNode ( node , n ) ;
node . oids = n . oids ? n . oids . replace ( /\s/g , "" ) : ""
const maxRepetitions = 20 ;
2016-04-12 19:39:19 +02:00
2022-04-21 17:49:03 +02:00
node . on ( "input" , function ( msg ) {
const oids = node . oids || msg . oid ;
const { host , sessionid , user , options } = prepareSnmpOptions ( node , msg ) ;
2016-04-12 19:39:19 +02:00
if ( oids ) {
msg . oid = oids ;
2022-04-21 17:49:03 +02:00
let sess = openSession ( sessionid , host , user , options ) ;
sess . on ( "error" , function ( err ) {
node . error ( err , msg ) ;
} )
//move response array & feedCb to inside `node.on("input",` to avoid subsequent
// calls overwriting results from previous operations (each call gets own result/response)
const response = [ ] ;
function feedCb ( varbinds ) {
for ( let i = 0 ; i < varbinds . length ; i ++ ) {
if ( SNMP . isVarbindError ( varbinds [ i ] ) ) {
node . error ( SNMP . varbindError ( varbinds [ i ] ) , msg ) ;
} else {
response . push ( { oid : varbinds [ i ] . oid , value : varbinds [ i ] . value } ) ;
}
}
}
sess . subtree ( msg . oid , maxRepetitions , feedCb , function ( error ) {
2017-04-15 13:04:47 +02:00
if ( error ) {
2017-04-22 16:53:04 +02:00
node . error ( error . toString ( ) , msg ) ;
2022-04-21 17:49:03 +02:00
} else {
msg . payload = response ;
2017-04-15 13:04:47 +02:00
node . send ( msg ) ;
}
2020-10-24 03:07:16 +02:00
closeSession ( sessionid ) ;
2017-04-15 13:04:47 +02:00
} ) ;
2022-04-21 17:49:03 +02:00
} else {
2016-04-12 19:39:19 +02:00
node . warn ( "No oid to search for" ) ;
}
} ) ;
}
2022-04-21 17:49:03 +02:00
RED . nodes . registerType ( "snmp subtree" , SnmpSubtreeNode , {
credentials : {
username : { type : "text" } ,
authkey : { type : "password" } ,
privkey : { type : "password" }
}
} ) ;
2018-01-28 18:50:41 +01:00
2016-04-12 20:18:48 +02:00
function SnmpWalkerNode ( n ) {
2022-04-21 17:49:03 +02:00
const node = this ;
RED . nodes . createNode ( node , n ) ;
initSnmpNode ( node , n ) ;
node . oids = n . oids ? n . oids . replace ( /\s/g , "" ) : ""
const maxRepetitions = 20 ;
2016-04-12 20:18:48 +02:00
2022-04-21 17:49:03 +02:00
node . on ( "input" , function ( msg ) {
const oids = node . oids || msg . oid ;
const { host , sessionid , user , options } = prepareSnmpOptions ( node , msg ) ;
2016-04-12 19:39:19 +02:00
if ( oids ) {
msg . oid = oids ;
2022-04-21 17:49:03 +02:00
let sess = openSession ( sessionid , host , user , options ) ;
sess . on ( "error" , function ( err ) {
node . error ( err , msg ) ;
} )
//move response array & feedCb to inside `node.on("input",` to avoid subsequent
// calls overwriting results from previous operations (each call gets own result/response)
const response = [ ] ;
function feedCb ( varbinds ) {
for ( let i = 0 ; i < varbinds . length ; i ++ ) {
if ( SNMP . isVarbindError ( varbinds [ i ] ) ) {
node . error ( SNMP . varbindError ( varbinds [ i ] ) , msg ) ;
} else {
response . push ( { oid : varbinds [ i ] . oid , value : varbinds [ i ] . value } ) ;
}
}
}
sess . walk ( msg . oid , maxRepetitions , feedCb , function ( error ) {
2017-04-15 13:04:47 +02:00
if ( error ) {
2017-04-22 16:53:04 +02:00
node . error ( error . toString ( ) , msg ) ;
2022-04-21 17:49:03 +02:00
} else {
msg . payload = response ;
2017-04-15 13:04:47 +02:00
node . send ( msg ) ;
}
2020-10-24 03:07:16 +02:00
closeSession ( sessionid ) ;
2017-04-15 13:04:47 +02:00
} ) ;
2022-04-21 17:49:03 +02:00
} else {
2016-04-12 19:39:19 +02:00
node . warn ( "No oid to search for" ) ;
}
} ) ;
}
2022-04-21 17:49:03 +02:00
RED . nodes . registerType ( "snmp walker" , SnmpWalkerNode , {
credentials : {
username : { type : "text" } ,
authkey : { type : "password" } ,
privkey : { type : "password" }
}
} ) ;
2016-04-12 20:18:48 +02:00
} ;