Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add SNMP Monitor #4717

Open
wants to merge 48 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
d92003e
SNMP Initial Commits
mattv8 Apr 27, 2024
a3cdd69
Use net-snmp instead of snmp-native
mattv8 Apr 29, 2024
ff5890a
Updated a comment
mattv8 Apr 29, 2024
4a882be
Further SNMP monitor development
mattv8 Apr 29, 2024
99dc4cf
Wrong variable used
mattv8 Apr 30, 2024
138075a
Update db migration: allow nulls
mattv8 Apr 30, 2024
9c8024c
Update db migration: down function
mattv8 Apr 30, 2024
9d28fcf
Update bean model backend
mattv8 Apr 30, 2024
4593afb
Frontend input validation
mattv8 Apr 30, 2024
9848ce4
Minor frontend styling
mattv8 Apr 30, 2024
704ffd3
Finalized SNMP monitor
mattv8 Apr 30, 2024
b4bd003
Merge branch 'master' into snmp-monitor
CommanderStorm Apr 30, 2024
ba47aca
Apply suggestions from code review
mattv8 Apr 30, 2024
7459654
ES Lint Compliant
mattv8 May 1, 2024
e944492
Corrected down function
mattv8 May 1, 2024
97a9094
ES Lint Compliant
mattv8 May 1, 2024
9ba0f68
Remove supurfluous log.debug
mattv8 May 1, 2024
ba84f01
Delete .EditMonitor.vue.swp
mattv8 May 1, 2024
4699a1c
ES Lint Compliant
mattv8 May 1, 2024
d83c2b9
Revert unintentional changes to EditMonitor.vue
mattv8 May 2, 2024
f059d54
Use frontend timeout
mattv8 May 2, 2024
8e56a81
Refactor how strings/numerics are parsed
mattv8 May 2, 2024
c87ac2f
Move getKey() to util.ts
mattv8 May 3, 2024
09fd816
Updated code comments
mattv8 May 3, 2024
407f729
New dependency for net-snmp
mattv8 May 3, 2024
9053b48
Merge branch 'louislam:master' into snmp-monitor
mattv8 May 3, 2024
4386d0a
Apply suggestions from code review
mattv8 May 5, 2024
0280b2a
A comment about varbinds[0] for clarification
mattv8 May 6, 2024
86b997c
Limit to <= SNMPv2c for now
mattv8 May 6, 2024
0384b34
Remove unnecessary func getKey
mattv8 May 6, 2024
997791b
Default: invalid condition error
mattv8 May 6, 2024
1fe1bb5
Given that above throws, the else case is not nessesary
mattv8 May 6, 2024
433e317
Simplify error catch
mattv8 May 6, 2024
6037912
Consistent placeholder text
mattv8 May 6, 2024
c68b1c6
Remove unnecessary func getKey
mattv8 May 6, 2024
e9b52eb
Separate error cases for SNMP varbind returns
mattv8 May 6, 2024
4ef66b3
SNMP version helptext
mattv8 May 6, 2024
19f21a9
SNMP OID helptext
mattv8 May 6, 2024
56e7fa8
Helptext ALL THE THINGS
mattv8 May 6, 2024
f4842ea
Translation key for OID
mattv8 May 6, 2024
2b5d100
Ensure SNMP session is closed properly
mattv8 May 6, 2024
e5fb726
Missed changes leftover from removal of getKey()
mattv8 May 7, 2024
2015142
Maybe don't helptext all the things...
mattv8 May 7, 2024
8b4b27f
Final cleanup of changes to EditMonitor.vue
mattv8 May 7, 2024
da8f0d1
Apply suggestions from code review
mattv8 May 8, 2024
1c47407
Re-use monitor.radiusPassword for community string
mattv8 May 8, 2024
c475994
Fix ES Lint
mattv8 May 8, 2024
d25ee8f
Using JSON Query Expressions
mattv8 May 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions db/knex_migrations/2024-04-26-0000-snmp-monitor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
exports.up = function (knex) {
return knex.schema
.alterTable("monitor", function (table) {
table.string("snmp_community_string", 255).defaultTo("public"); // Add snmp_community_string column
table.string("snmp_oid").defaultTo(null); // Add oid column
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
table.enum("snmp_version", ["1", "2c", "3"]).defaultTo("2c"); // Add snmp_version column with enum values

Check warning on line 6 in db/knex_migrations/2024-04-26-0000-snmp-monitor.js

View workflow job for this annotation

GitHub Actions / check-linters

A space is required after '['

Check warning on line 6 in db/knex_migrations/2024-04-26-0000-snmp-monitor.js

View workflow job for this annotation

GitHub Actions / check-linters

A space is required before ']'
table.float("snmp_control_value").defaultTo(null); // Add control_value column as float
table.string("snmp_condition").defaultTo(null); // Add oid column
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
});
};

exports.down = function (knex) {
// Nothing to do here
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
};

Check failure on line 14 in db/knex_migrations/2024-04-26-0000-snmp-monitor.js

View workflow job for this annotation

GitHub Actions / check-linters

Newline required at end of file but not found
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
"mssql": "~8.1.4",
"mysql2": "~3.9.6",
"nanoid": "~3.3.4",
"net-snmp": "^3.11.2",
"node-cloudflared-tunnel": "~1.0.9",
"node-radius-client": "~1.0.0",
"nodemailer": "~6.9.13",
Expand Down
5 changes: 5 additions & 0 deletions server/model/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ class Monitor extends BeanModel {
kafkaProducerMessage: this.kafkaProducerMessage,
screenshot,
remote_browser: this.remote_browser,
snmpOid: this.snmpOid,
snmpCondition: this.snmpCondition,
snmpControlValue: this.snmpControlValue,
snmpVersion: this.snmpVersion,
};

if (includeSensitiveData) {
Expand Down Expand Up @@ -190,6 +194,7 @@ class Monitor extends BeanModel {
tlsCert: this.tlsCert,
tlsKey: this.tlsKey,
kafkaProducerSaslOptions: JSON.parse(this.kafkaProducerSaslOptions),
snmpCommunityString: this.snmpCommunityString,
};
}

Expand Down
107 changes: 107 additions & 0 deletions server/monitor-types/snmp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
const { MonitorType } = require("./monitor-type");
const { UP, DOWN, log } = require("../../src/util");
const snmp = require("net-snmp");

class SNMPMonitorType extends MonitorType {
name = "snmp";

/**

Check warning on line 8 in server/monitor-types/snmp.js

View workflow job for this annotation

GitHub Actions / check-linters

Missing JSDoc @returns declaration
* Checks the SNMP value against the condition and control value.
* @param {object} monitor The monitor object associated with the check.
* @param {object} heartbeat The heartbeat object to update.
* @param {object} _server Unused server object.
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
*/
async check(monitor, heartbeat, _server) {

log.debug("monitor", `SNMP: Community String: ${monitor.snmpCommunityString}`);
log.debug("monitor", `SNMP: OID: ${monitor.snmpOid}`);
log.debug("monitor", `SNMP: Version: ${monitor.snmpVersion}`);
log.debug("monitor", `SNMP: Condition: ${monitor.snmpCondition}`);
log.debug("monitor", `SNMP: Control Value: ${monitor.snmpControlValue}`);
mattv8 marked this conversation as resolved.
Show resolved Hide resolved

const options = {
port: monitor.port || '161',

Check failure on line 23 in server/monitor-types/snmp.js

View workflow job for this annotation

GitHub Actions / check-linters

Strings must use doublequote
retries: monitor.maxretries,
timeout: 1000,
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
version: getKey(snmp.Version, monitor.snmpVersion) || snmp.Version2c,
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
};

function getKey(obj, value) {

Check failure on line 29 in server/monitor-types/snmp.js

View workflow job for this annotation

GitHub Actions / check-linters

Missing JSDoc comment
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
return Object.keys(obj).find(key => obj[key] === value) || null;
}

try {
const session = snmp.createSession(monitor.hostname, monitor.snmpCommunityString, options);

// Handle errors during session creation
session.on('error', (error) => {

Check failure on line 37 in server/monitor-types/snmp.js

View workflow job for this annotation

GitHub Actions / check-linters

Strings must use doublequote
heartbeat.status = DOWN;
heartbeat.msg = `SNMP: Error creating SNMP session: ${error.message}`;
log.debug("monitor", `SNMP: ${heartbeat.msg}`);
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
});

const varbinds = await new Promise((resolve, reject) => {
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
session.get([monitor.snmpOid], (error, varbinds) => {

Check warning on line 44 in server/monitor-types/snmp.js

View workflow job for this annotation

GitHub Actions / check-linters

A space is required after '['

Check warning on line 44 in server/monitor-types/snmp.js

View workflow job for this annotation

GitHub Actions / check-linters

A space is required before ']'
if (error) {
reject(error);
} else {
log.debug("monitor", `SNMP: Received varbinds: Type: ${getKey(snmp.ObjectType, varbinds[0].type)}, Value: ${varbinds[0].value}`); // Log the received varbinds for debugging
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
resolve(varbinds);
}
});
});

if (varbinds.length === 0 || getKey(snmp.ObjectType, varbinds[0].type) === 'NoSuchInstance') {

Check failure on line 54 in server/monitor-types/snmp.js

View workflow job for this annotation

GitHub Actions / check-linters

Strings must use doublequote
throw new Error(`No varbinds returned from SNMP session (OID: ${monitor.snmpOid})`);
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
} else {
const value = varbinds[0].value;
const numericValue = parseInt(value);
const stringValue = value.toString('ascii');

Check failure on line 59 in server/monitor-types/snmp.js

View workflow job for this annotation

GitHub Actions / check-linters

Strings must use doublequote

switch (monitor.snmpCondition) {
case '>':

Check failure on line 62 in server/monitor-types/snmp.js

View workflow job for this annotation

GitHub Actions / check-linters

Strings must use doublequote
heartbeat.status = numericValue > monitor.snmpControlValue ? UP : DOWN;
break;
case '>=':

Check failure on line 65 in server/monitor-types/snmp.js

View workflow job for this annotation

GitHub Actions / check-linters

Strings must use doublequote
heartbeat.status = numericValue >= monitor.snmpControlValue ? UP : DOWN;
break;
case '<':

Check failure on line 68 in server/monitor-types/snmp.js

View workflow job for this annotation

GitHub Actions / check-linters

Strings must use doublequote
heartbeat.status = numericValue < monitor.snmpControlValue ? UP : DOWN;
break;
case '<=':

Check failure on line 71 in server/monitor-types/snmp.js

View workflow job for this annotation

GitHub Actions / check-linters

Strings must use doublequote
heartbeat.status = numericValue <= monitor.snmpControlValue ? UP : DOWN;
break;
case '==':
if (!isNaN(value) && !isNaN(monitor.snmpControlValue)) {
// Both values are numeric, parse them as numbers
heartbeat.status = parseFloat(value) === parseFloat(monitor.snmpControlValue) ? UP : DOWN;
} else {
// At least one of the values is not numeric, compare them as strings
heartbeat.status = value.toString() === monitor.snmpControlValue.toString() ? UP : DOWN;
}
break;
case 'contains':
heartbeat.status = stringValue.includes(monitor.snmpControlValue) ? UP : DOWN;
break;
default:
heartbeat.status = DOWN;
heartbeat.msg = `Invalid condition: ${monitor.snmpCondition}`;
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
break;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About this part @chakflying has commented in #1675 (comment)

Sounds pretty good, but just in case you didn't know, you should take a look at how the json-query monitor works.

Ideally in the long term, we would want all value comparisons to work with the jsonata syntax, and reuse the database fields for better compatibility (see #3919). I don't think it's worth implementing custom value comparison functionality just for this monitor.

=> This needs to be compatible with #4617 and #3919

@chakflying what do you think is the best course forward?

Copy link
Author

@mattv8 mattv8 May 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chakflying & @CommanderStorm I propose waiting until after #4617 is committed and I will re-factor & maintain the SNMP monitor after the fact.

heartbeat.msg = `SNMP value ` + (heartbeat.status ? `passes` : `does not pass`) + ` comparison: ${value.toString('ascii')} ${monitor.snmpCondition} ${monitor.snmpControlValue}`;

}
session.close(); // Close the session after use
mattv8 marked this conversation as resolved.
Show resolved Hide resolved

} catch (err) {
heartbeat.status = DOWN;
heartbeat.msg = `SNMP Error: ${err.message}`;
log.debug("monitor", `SNMP: ${heartbeat.msg}`);
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
}
}

}

module.exports = {
SNMPMonitorType,
};
5 changes: 5 additions & 0 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,11 @@ let needSetup = false;
monitor.kafkaProducerAllowAutoTopicCreation;
bean.gamedigGivenPortOnly = monitor.gamedigGivenPortOnly;
bean.remote_browser = monitor.remote_browser;
bean.snmpVersion = monitor.snmpVersion;
bean.snmpCommunityString = monitor.snmpCommunityString;
bean.snmpOid = monitor.snmpOid;
bean.snmpCondition = monitor.snmpCondition;
bean.snmpControlValue = monitor.snmpControlValue;

bean.validate();

Expand Down
2 changes: 2 additions & 0 deletions server/uptime-kuma-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class UptimeKumaServer {
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType();

// Allow all CORS origins (polling) in development
let cors = undefined;
Expand Down Expand Up @@ -516,3 +517,4 @@ const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor
const { TailscalePing } = require("./monitor-types/tailscale-ping");
const { DnsMonitorType } = require("./monitor-types/dns");
const { MqttMonitorType } = require("./monitor-types/mqtt");
const { SNMPMonitorType } = require("./monitor-types/snmp");
Binary file added src/pages/.EditMonitor.vue.swp
mattv8 marked this conversation as resolved.
Show resolved Hide resolved
Binary file not shown.