Skip to content

Commit

Permalink
Merge pull request #7 from gapitio/feature/snmpv3
Browse files Browse the repository at this point in the history
Feature/snmpv3
  • Loading branch information
sverre authored Aug 1, 2022
2 parents 6a713d0 + 1dd749c commit 3e38ccc
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 22 deletions.
85 changes: 81 additions & 4 deletions gapit-snmp.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,60 @@
<label for="node-input-host"><i class="fa fa-globe"></i> Host</label>
<input type="text" id="node-input-host" placeholder="ip address(:optional port)">
</div>
<div class="form-row">
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
<input type="text" id="node-input-community" placeholder="public">
</div>
<div class="form-row">
<label for="node-input-version"><i class="fa fa-bookmark"></i> Version</label>
<select type="text" id="node-input-version" style="width:150px;">
<option value="1">v1</option>
<option value="2c">v2c</option>
<option value="3">v3</option>
</select>
<span style="margin-left:50px;">Timeout</span>
<input type="text" id="node-input-timeout" placeholder="secs" style="width:50px; direction:rtl; vertical-align:baseline;">&nbsp;S
</div>
<div class="form-row snmpoptions snmpoptions_v1 snmpoptions_v2c">
<label for="node-input-community"><i class="fa fa-user"></i> Community</label>
<input type="text" id="node-input-community" placeholder="public">
</div>
<div class="form-row snmpoptions snmpoptions_v3">
<label for="node-input-context"><i class="fa fa-filter"></i> Context</label>
<input type="text" id="node-input-context" placeholder="SNMPv3 context - can be empty">
</div>
<div class="form-row snmpoptions snmpoptions_v3">
<label for="node-input-username"><i class="fa fa-user"></i> Username</label>
<input type="text" id="node-input-username" placeholder="SNMPv3 username">
</div>
<div class="form-row snmpoptions snmpoptions_v3">
<label for="node-input-security_level"><i class="fa fa-shield"></i> Security level</label>
<select type="text" id="node-input-security_level">
<option value="authPriv">authPriv</option>
<option value="authNoPriv">authNoPriv</option>
<option value="noAuthNoPriv">noAuthNoPriv</option>
</select>
</div>
<div class="form-row snmpoptions snmpoptions_v3 snmpoptions_v3_sec snmpoptions_v3_sec_authNoPriv snmpoptions_v3_sec_authPriv">
<label for="node-input-auth_protocol"><i class="fa fa-shield"></i> Auth Procol</label>
<select type="text" id="node-input-auth_protocol">
<option value="md5">MD5</option>
<option value="sha">SHA</option>
</select>
</div>
<div class="form-row snmpoptions snmpoptions_v3 snmpoptions_v3_sec snmpoptions_v3_sec_authNoPriv snmpoptions_v3_sec_authPriv">
<label for="node-input-auth_key"><i class="fa fa-user-secret"></i> Auth Key</label>
<input type="password" id="node-input-auth_key" placeholder="SNMPv3 auth key">
</div>
<div class="form-row snmpoptions snmpoptions_v3 snmpoptions_v3_sec snmpoptions_v3_sec_authPriv">
<label for="node-input-priv_protocol"><i class="fa fa-shield"></i> Privacy Protocol</label>
<select type="text" id="node-input-priv_protocol">
<option value="des">DES</option>
<option value="aes">AES-128</option>
<option value="aes256r">AES-256 ("Reeder" key localization)</option>
<option value="aes256b">AES-256 ("Blumenthal" key localization)</option>
</select>
</div>
<div class="form-row snmpoptions snmpoptions_v3 snmpoptions_v3_sec snmpoptions_v3_sec_authPriv">
<label for="node-input-priv_key"><i class="fa fa-user-secret"></i> Privacy Key</label>
<input type="password" id="node-input-priv_key" placeholder="SNMPv3 priv key">
</div>
<div class="form-row">
<label for="node-input-tagvalue_device_name"><i class="fa fa-file-code-o"></i> Device name</label>
<input type="text" id="node-input-tagvalue_device_name" />
Expand Down Expand Up @@ -99,10 +140,19 @@
RED.nodes.registerType('gapit-snmp', {
category: 'network-input',
color: "YellowGreen",
credentials: {
username: {type:"text"},
auth_key: {type:"password"},
priv_key: {type:"password"}
},
defaults: {
host: { value: "127.0.0.1" },
community: { value: "public" },
version: { value: "1", required: true },
context: { value: "" },
security_level: { value: "authPriv" },
auth_protocol: { value: "sha" },
priv_protocol: { value: "des" },
tagname_device_name: { value: "device_name", required: true },
tagvalue_device_name: { value: "default_name", required: true },
minion_ids: { value: "" },
Expand All @@ -125,6 +175,7 @@
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
// field customization
$("#node-input-custom_tags").typedInput({
type:"json",
types:["json"]
Expand All @@ -144,6 +195,32 @@
}
]
});

///// dynamic UI
//
// hide fields based on SNMP version
$("#node-input-version").on("change", function() {
let version = $("#node-input-version").val();
let optionsClass = `.snmpoptions_v${version}`;
$(".snmpoptions").hide();
$(optionsClass).show();
});

// hide fields based on SNMP security level
$("#node-input-security_level").on("change", function() {
let securityLevel = $("#node-input-security_level").val();
let levelClass = `.snmpoptions_v3_sec_${securityLevel}`;
$(".snmpoptions_v3_sec").hide();
$(levelClass).show();
});

// Trigger change events for initial form setup.
// Doesn't seem to work when called immediately,
// so use a timer (even 1 msec seems to work).
setTimeout(function() {
$("#node-input-version").triggerHandler("change");
$("#node-input-security_level").triggerHandler("change");
}, 1);
},
});
</script>
110 changes: 92 additions & 18 deletions gapit-snmp.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,10 @@
module.exports = function (RED) {
"use strict";
var snmp = require("net-snmp");
var crypto = require("crypto");

var sessions = {};

function getSession(host, community, version, timeout) {
var sessionKey = host + ":" + community + ":" + version;
var port = 161;
if (host.indexOf(":") !== -1) {
port = host.split(":")[1];
host = host.split(":")[0];
}
if (!(sessionKey in sessions)) {
sessions[sessionKey] = snmp.createSession(host, community, { port:port, version:version, timeout:(timeout || 5000) });
}
return sessions[sessionKey];
}


function varbindsParseCounter64Buffers(varbinds, convertToNumbers=true) {
// Counter64 is not directly supported by net-snmp,
// but returned as a buffer (array). This function
Expand Down Expand Up @@ -259,7 +246,7 @@ module.exports = function (RED) {
this.blockTuningSteps = 10;
this.config = config;
this.multi_device_separator = ";";
this.version = (config.version === "2c") ? snmp.Version2c : snmp.Version1;
this.version = snmp.Version[config.version];
this.tagname_device_name = config.tagname_device_name.trim();
this.tagvalue_device_name = config.tagvalue_device_name.trim();
// split device_name from config into an array
Expand Down Expand Up @@ -345,6 +332,83 @@ module.exports = function (RED) {
console.info("initializing nonexistent_oids in context (set to empty Array)")
nodeContext.set("nonexistent_oids", Array());

this.getSession = function () {
// check if session already exists, return if it does
if (node.sessionKey !== undefined && node.sessionKey in sessions) {
console.debug(`Found existing session for ${node.sessionKey}`);
return sessions[node.sessionKey];
}

let host = node.config.host;
let sessionKey;
if (node.version === snmp.Version3) {
// include (a hash of) all v3 options to create a unique session key
let v3OptionsHash = crypto.createHash('sha256').update(
node.config.security_level +
node.config.auth_protocol +
node.credentials.auth_key +
node.config.priv_protocol +
node.credentials.priv_key).digest('hex').slice(-8)
sessionKey = host + ":" + node.credentials.username + ":" + node.version + ":" + v3OptionsHash;
}
else { // not SNMPv3
sessionKey = host + ":" + node.config.community + ":" + node.version;
}
var port = 161;
if (host.indexOf(":") !== -1) {
port = host.split(":")[1];
host = host.split(":")[0];
}

console.info(`Creating session for ${sessionKey}`);
let options = { port:port, version:node.version, timeout:(node.timeout || 5000) }
if (node.version === snmp.Version3) {
if (node.config.context.trim() != "") {
options.context = node.config.context.trim();
}
let user = node.createV3UserObject();
sessions[sessionKey] = snmp.createV3Session(host, user, options);
}
else {
sessions[sessionKey] = snmp.createSession(host, node.config.community, options);
}

sessions[sessionKey].on("error", function (error) {
console.log ("Session error: " + error.toString());
node.closeSession();
})

node.sessionKey = sessionKey;
return sessions[sessionKey];
}

this.createV3UserObject = function() {
// set up user object for SNMPv3
// empty object if other version
let user = {}
if (node.version == snmp.Version3) {
user.name = node.credentials.username;
user.level = snmp.SecurityLevel[node.config.security_level];
if (user.level == snmp.SecurityLevel.authNoPriv || user.level == snmp.SecurityLevel.authPriv) {
user.authProtocol = snmp.AuthProtocols[node.config.auth_protocol];
user.authKey = node.credentials.auth_key;
}
if (user.level == snmp.SecurityLevel.authPriv) {
user.privProtocol = snmp.PrivProtocols[node.config.priv_protocol];
user.privKey = node.credentials.priv_key;
}
}
return user;
}

this.closeSession = function () {
if (node.sessionKey !== undefined && node.sessionKey in sessions) {
console.debug(`Closing session ${node.sessionKey}`);
sessions[node.sessionKey].close();
delete sessions[node.sessionKey];
}
}

this.processVarbinds = function (msg, varbinds) {
// parse Counter64 values in varbinds
varbindsParseCounter64Buffers(varbinds, node.config.convert_counter64_bigint_to_number);
Expand Down Expand Up @@ -589,7 +653,7 @@ module.exports = function (RED) {
}

this.tuneSnmpBlockSize = function (host, community, oids, msg, blockSize) {
getSession(host, community, node.version, node.timeout).get(oids.slice(0, blockSize), function (error, varbinds) {
node.getSession().get(oids.slice(0, blockSize), function (error, varbinds) {
if (error) {
// error object has .name, .message and, optionally, .status
// error.status is only set for RequestFailed, so check
Expand Down Expand Up @@ -644,7 +708,7 @@ module.exports = function (RED) {

for (let blockStart = 0; blockStart < oids.length; blockStart=blockStart+blockSize) {
let oidBlock = oids.slice(blockStart, blockStart+blockSize);
getSession(host, community, node.version, node.timeout).get(oidBlock, function (error, blockVarbinds) {
node.getSession().get(oidBlock, function (error, blockVarbinds) {
msg.totalBlockResponseCount += oidBlock.length;
if (error) {
// error object has .name, .message and, optionally, .status
Expand Down Expand Up @@ -723,6 +787,16 @@ module.exports = function (RED) {
node.warn("No oid(s) to search for");
}
});

this.on("close", function() {
node.closeSession();
});
}
RED.nodes.registerType("gapit-snmp", GapitSnmpNode);
RED.nodes.registerType("gapit-snmp", GapitSnmpNode, {
credentials: {
username: {type:"text"},
auth_key: {type:"password"},
priv_key: {type:"password"}
}
});
};

0 comments on commit 3e38ccc

Please sign in to comment.