mirror of
https://github.com/Cacti/spine.git
synced 2026-10-23 03:27:10 +09:00
**Problem Description**
When a device does not support one or more of the system OIDs queried by
get_system_information(), not only system information collection but
also resource-related SNMP polling may fail.
This issue is especially noticeable when the device's Poller Thread
setting is 1, where resource-related SNMP polling does not occur at all.
During analysis, I found that if one of the OIDs queried in
get_system_information() returns NoSuchObject, the host is marked as
ignored through the following call path:
snmp_get()
-> snmp_get_base(current_host, snmp_oid, true)
-> NoSuchObject returned
-> current_host->ignore_host = TRUE
The OIDs queried by get_system_information() are:
- sysDescr
- sysObjectID
- sysUpTime
- sysContact
- sysName
- sysLocation
- Existing Behavior
Issue #366 already introduced special handling for the following OIDs:
- sysDescr
- sysUpTime
Reference:
Issue #366: Failing to actually poll a device that returns
"NoSuchObject"
However, the same handling was not applied to:
- sysObjectID
- sysContact
- sysName
- sysLocation
As a result, devices that do not implement one or more of these OIDs can
still be marked as ignored, causing polling to stop unexpectedly.
**Observed Behavior**
The following log shows the issue occurring when the target device
returns NoSuchObject for sysContact (.1.3.6.1.2.1.1.4.0).
After the error is detected, ignore_host becomes active and subsequent
system OID requests are skipped. The device polling thread then
completes without polling any of the scheduled data sources.
```
2026-07-30 10:56:53 - SPINE: Poller[1] PID[1069197] PT[140502752032320] ERROR: No such Object for oid '.1.3.6.1.2.1.1.4.0' for Device[72] with Status[1]
2026-07-30 10:56:53 - SPINE: Poller[1] PID[1069197] PT[140502752032320] WARNING: Skipped oid '.1.3.6.1.2.1.1.5.0' for Device[72] as host ignore flag is active
2026-07-30 10:56:53 - SPINE: Poller[1] PID[1069197] PT[140502752032320] WARNING: Skipped oid '.1.3.6.1.2.1.1.6.0' for Device[72] as host ignore flag is active
2026-07-30 10:56:53 - SPINE: Poller[1] PID[1069197] PT[140502752032320] Device[72] HT[1] NOTE: There are '17' Polling Items for this Device
2026-07-30 10:56:53 - SPINE: Poller[1] PID[1069197] PT[140502752032320] DEBUG: Setting up writes to local database
2026-07-30 10:56:53 - SPINE: Poller[1] PID[1069197] PT[140502752032320] Device[72] HT[1] Total Time: 0.019 Seconds
2026-07-30 10:56:53 - SPINE: Poller[1] PID[1069197] PT[140502752032320] DEBUG: Freeing Local Pool ID 0
2026-07-30 10:56:53 - SPINE: Poller[1] PID[1069197] PT[140502752032320] DEBUG: Device[72] HT[1] DEBUG: HOST COMPLETE: About to Exit Device Polling Thread Function
2026-07-30 10:56:53 - SPINE: Poller[1] PID[1069197] PT[140502985176896] The final count of Threads is 0
2026-07-30 10:56:53 - SPINE: Poller[1] PID[1069197] PT[140502985176896] INFO: Device[72] Thread complete and 0 to 17 sources
```
In this example, the device contains 17 polling items, but polling
completes with:
INFO: Device[72] Thread complete and 0 to 17 sources
indicating that none of the scheduled data sources were successfully
polled.
**Fix**
This change adds the same exception handling for the remaining four
OIDs:
- sysObjectID
- sysContact
- sysName
- sysLocation
If any of these OIDs return NoSuchObject, the host will no longer be
marked as ignored, allowing normal resource-related polling to continue.
**Expected Result**
Devices that do not implement one or more optional system OIDs should
still be polled successfully for resource-related metrics.
Only the unavailable system information fields should be skipped, while
normal SNMP data collection continues without interruption.
Co-authored-by: TheWitness <thewitness@cacti.net>
1162 lines
41 KiB
C
1162 lines
41 KiB
C
/*
|
|
ex: set tabstop=4 shiftwidth=4 autoindent:
|
|
+-------------------------------------------------------------------------+
|
|
| Copyright (C) 2004-2026 The Cacti Group |
|
|
| |
|
|
| This program is free software; you can redistribute it and/or |
|
|
| modify it under the terms of the GNU Lesser General Public |
|
|
| License as published by the Free Software Foundation; either |
|
|
| version 2.1 of the License, or (at your option) any later version. |
|
|
| |
|
|
| This program is distributed in the hope that it will be useful, |
|
|
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
| GNU Lesser General Public License for more details. |
|
|
| |
|
|
| You should have received a copy of the GNU Lesser General Public |
|
|
| License along with this library; if not, write to the Free Software |
|
|
| Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
|
| 02110-1301, USA |
|
|
| |
|
|
+-------------------------------------------------------------------------+
|
|
| spine: a backend data gatherer for cacti |
|
|
+-------------------------------------------------------------------------+
|
|
| This poller would not have been possible without: |
|
|
| - Larry Adams (current development and enhancements) |
|
|
| - Rivo Nurges (rrd support, mysql poller cache, misc functions) |
|
|
| - RTG (core poller code, pthreads, snmp, autoconf examples) |
|
|
| - Brady Alleman/Doug Warner (threading ideas, implementation details) |
|
|
+-------------------------------------------------------------------------+
|
|
| - Cacti - http://www.cacti.net/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "spine.h"
|
|
|
|
/* resolve problems in debian */
|
|
#ifndef NETSNMP_DS_LIB_DONT_PERSIST_STATE
|
|
#define NETSNMP_DS_LIB_DONT_PERSIST_STATE 32
|
|
#endif
|
|
|
|
#define OIDSIZE(p) (sizeof(p)/sizeof(oid))
|
|
|
|
/*! \fn void snmp_spine_init()
|
|
* \brief wrapper function for init_snmp
|
|
*
|
|
* Initializes snmp for the given application ID
|
|
*
|
|
*/
|
|
void snmp_spine_init(void) {
|
|
|
|
/* Only do numeric output */
|
|
#ifdef NETSNMP_DS_LIB_PRINT_NUMERIC_ENUM
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_ENUM, 1);
|
|
#endif
|
|
|
|
/* Prevent update of the snmpapp.conf file */
|
|
#ifdef NETSNMP_DS_LIB_DONT_PERSIST_STATE
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_PERSIST_STATE, 1);
|
|
#endif
|
|
|
|
/* Prevent update of the snmpapp.conf file */
|
|
#ifdef NETSNMP_DS_LIB_DISABLE_PERSISTENT_LOAD
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_PERSISTENT_LOAD, 1);
|
|
#endif
|
|
|
|
#ifdef NETSNMP_DS_LIB_DONT_PRINT_UNITS
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_PRINT_UNITS, 1);
|
|
#endif
|
|
|
|
setenv("MIBS", "", 1);
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_QUICK_PRINT, 1);
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_QUICKE_PRINT, 1);
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_BARE_VALUE, 1);
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_NUMERIC_TIMETICKS, 1);
|
|
|
|
/* don't check the range of the OID */
|
|
netsnmp_ds_toggle_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_CHECK_RANGE);
|
|
|
|
#if defined(VERIFY_PACKAGE_VERSION) && defined(PACKAGE_VERSION)
|
|
/* check that the headers we compiled with match the library we linked with */
|
|
SPINE_LOG_DEBUG(("DEBUG: SNMP Header Version is %s", PACKAGE_VERSION));
|
|
SPINE_LOG_DEBUG(("DEBUG: SNMP Library Version is %s", netsnmp_get_version()));
|
|
|
|
if (STRMATCH(PACKAGE_VERSION,netsnmp_get_version())) {
|
|
init_snmp("spine");
|
|
} else {
|
|
/* report the error and quit spine */
|
|
die("ERROR: SNMP Library Version Mismatch (%s vs %s)",PACKAGE_VERSION,netsnmp_get_version());
|
|
}
|
|
#else
|
|
SPINE_LOG_DEBUG(("DEBUG: Issues with SNMP Header Version information, assuming old version of Net-SNMP."));
|
|
init_snmp("spine");
|
|
#endif
|
|
}
|
|
|
|
/*! \fn void snmp_spine_close()
|
|
* \brief wrapper function for the snmp_shutdown function
|
|
*
|
|
* Closes the snmp api for the given application ID
|
|
*
|
|
*/
|
|
void snmp_spine_close(void) {
|
|
snmp_shutdown("spine");
|
|
}
|
|
|
|
/*! \fn void *snmp_host_init(int host_id, char *hostname, int snmp_version,
|
|
* char *snmp_community, char *snmp_username, char *snmp_password,
|
|
* char *snmp_auth_protocol, char *snmp_priv_passphrase, char *snmp_priv_protocol,
|
|
* char *snmp_context, char *snmp_engine_id, int snmp_port, int snmp_timeout)
|
|
* \brief initializes an snmp_session object for a Spine host
|
|
*
|
|
* This function will initialize NET-SNMP for the Spine host
|
|
* in question.
|
|
*
|
|
*/
|
|
void *snmp_host_init(int host_id, char *hostname, int snmp_version, char *snmp_community,
|
|
char *snmp_username, char *snmp_password, char *snmp_auth_protocol,
|
|
char *snmp_priv_passphrase, char *snmp_priv_protocol,
|
|
char *snmp_context, char *snmp_engine_id, int snmp_port, int snmp_timeout) {
|
|
|
|
void *sessp = NULL;
|
|
struct snmp_session session;
|
|
char hostnameport[BUFSIZE];
|
|
size_t len;
|
|
|
|
char *Apsz = NULL;
|
|
char *Xpsz = NULL;
|
|
char *Cpsz = NULL;
|
|
int priv_type;
|
|
|
|
/* initialize SNMP */
|
|
snmp_sess_init(&session);
|
|
|
|
/* Bind to snmp_clientaddr if specified */
|
|
len = strlen(set.snmp_clientaddr);
|
|
if (len > 0 && len <= SMALL_BUFSIZE) {
|
|
#if SNMP_LOCALNAME == 1
|
|
session.localname = strdup(set.snmp_clientaddr);
|
|
#endif
|
|
}
|
|
|
|
/* Prevent update of the snmpapp.conf file */
|
|
#ifdef NETSNMP_DS_LIB_DONT_PERSIST_STATE
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_PERSIST_STATE, 1);
|
|
#endif
|
|
|
|
/* Prevent update of the snmpapp.conf file */
|
|
#ifdef NETSNMP_DS_LIB_DISABLE_PERSISTENT_LOAD
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_PERSISTENT_LOAD, 1);
|
|
#endif
|
|
|
|
#ifdef NETSNMP_DS_LIB_DONT_PRINT_UNITS
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_PRINT_UNITS, 1);
|
|
#endif
|
|
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_QUICK_PRINT, 1);
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_QUICKE_PRINT, 1);
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_BARE_VALUE, 1);
|
|
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_NUMERIC_TIMETICKS, 1);
|
|
|
|
session.securityEngineID = 0;
|
|
session.securityEngineIDLen = 0;
|
|
|
|
session.securityName = 0;
|
|
session.securityNameLen = 0;
|
|
|
|
session.contextEngineID = 0;
|
|
session.contextEngineIDLen = 0;
|
|
|
|
session.contextName = 0;
|
|
session.contextNameLen = 0;
|
|
|
|
session.contextEngineID = 0;
|
|
session.contextEngineIDLen = 0;
|
|
|
|
/* verify snmp version is accurate */
|
|
if (snmp_version == 2) {
|
|
session.version = SNMP_VERSION_2c;
|
|
session.securityModel = SNMP_SEC_MODEL_SNMPv2c;
|
|
} else if (snmp_version == 1) {
|
|
session.version = SNMP_VERSION_1;
|
|
session.securityModel = SNMP_SEC_MODEL_SNMPv1;
|
|
} else if (snmp_version == 3) {
|
|
session.version = SNMP_VERSION_3;
|
|
session.securityModel = USM_SEC_MODEL_NUMBER;
|
|
} else {
|
|
SPINE_LOG(("Device[%i] ERROR: SNMP Version Error for Device '%s'", host_id, hostname));
|
|
free(session.localname);
|
|
return 0;
|
|
}
|
|
|
|
snprintf(hostnameport, BUFSIZE, "%s:%i", hostname, snmp_port);
|
|
session.peername = strdup(hostnameport);
|
|
if (!session.peername) {
|
|
SPINE_LOG(("Device[%i] ERROR: Failed to allocate peername for '%s'", host_id, hostname));
|
|
free(session.localname);
|
|
return 0;
|
|
}
|
|
session.retries = set.snmp_retries;
|
|
session.timeout = (snmp_timeout * 1000); /* net-snmp likes microseconds */
|
|
|
|
SPINE_LOG_HIGH(("Device[%i] INFO: SNMP Device '%s' has a timeout of %ld (%d), with %d retries", host_id, hostnameport, session.timeout, snmp_timeout, session.retries));
|
|
|
|
if ((snmp_version == 2) || (snmp_version == 1)) {
|
|
session.community = (unsigned char*) snmp_community;
|
|
session.community_len = strlen(snmp_community);
|
|
} else {
|
|
session.community = (unsigned char *) Cpsz;
|
|
session.community_len = 0;
|
|
|
|
session.securityName = snmp_username;
|
|
session.securityNameLen = strlen(session.securityName);
|
|
|
|
if (snmp_context && strlen(snmp_context)) {
|
|
session.contextName = snmp_context;
|
|
session.contextNameLen = strlen(session.contextName);
|
|
}
|
|
|
|
if (snmp_engine_id && strlen(snmp_engine_id)) {
|
|
session.contextEngineID = (unsigned char*) snmp_engine_id;
|
|
session.contextEngineIDLen = strlen(snmp_engine_id);
|
|
}
|
|
|
|
/* set the authentication protocol */
|
|
{
|
|
int auth_type;
|
|
const oid *auth_proto;
|
|
|
|
auth_type = usm_lookup_auth_type(snmp_auth_protocol);
|
|
if (auth_type > 0) {
|
|
auth_proto = sc_get_auth_oid(auth_type, &session.securityAuthProtoLen);
|
|
free(session.securityAuthProto);
|
|
session.securityAuthProto = snmp_duplicate_objid(auth_proto, session.securityAuthProtoLen);
|
|
} else {
|
|
SPINE_LOG(("SNMP: Device[%i] Error auth protocol %s is invalid.", host_id, snmp_auth_protocol));
|
|
free(session.peername);
|
|
free(session.localname);
|
|
return 0;
|
|
}
|
|
|
|
/* set the privacy protocol to none */
|
|
if (strcmp(snmp_priv_protocol, "[None]") == 0 || (strlen(snmp_priv_passphrase) == 0)) {
|
|
session.securityPrivProto = snmp_duplicate_objid(usmNoPrivProtocol, OID_LENGTH(usmNoPrivProtocol));
|
|
session.securityPrivProtoLen = OID_LENGTH(usmNoPrivProtocol);
|
|
session.securityPrivKeyLen = USM_PRIV_KU_LEN;
|
|
|
|
/* set the security level to authenticate, but not encrypted */
|
|
if (strlen(snmp_password)) {
|
|
session.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV;
|
|
} else {
|
|
session.securityLevel = SNMP_SEC_LEVEL_NOAUTH;
|
|
}
|
|
} else {
|
|
const oid *priv_proto;
|
|
|
|
priv_type = usm_lookup_priv_type(snmp_priv_protocol);
|
|
|
|
if (priv_type < 0) {
|
|
SPINE_LOG(("SNMP: Device[%i] Error privacy protocol %s is invalid.", host_id, snmp_priv_protocol));
|
|
free(session.peername);
|
|
free(session.securityAuthProto);
|
|
free(session.localname);
|
|
return 0;
|
|
}
|
|
|
|
priv_proto = sc_get_priv_oid(priv_type, &session.securityPrivProtoLen);
|
|
free(session.securityPrivProto);
|
|
session.securityPrivProto = snmp_duplicate_objid(priv_proto, session.securityPrivProtoLen);
|
|
session.securityLevel = SNMP_SEC_LEVEL_AUTHPRIV;
|
|
|
|
// Auth Protocol Setup: Apsz/Xpsz are this module's own heap copies. The live
|
|
// passphrase they hold is scrubbed immediately before each of their frees
|
|
// below, not here, since neither is assigned yet on entry to this block.
|
|
// snmp_password/snmp_priv_passphrase are host_t-owned and reused for later
|
|
// session rebuilds on this same host, so they are left intact rather than
|
|
// zeroed in place.
|
|
free(Apsz);
|
|
Apsz = strdup(snmp_password);
|
|
|
|
// Privacy Protocol Setup
|
|
free(Xpsz);
|
|
Xpsz = strdup(snmp_priv_passphrase);
|
|
|
|
if (Apsz) {
|
|
session.securityAuthKeyLen = USM_AUTH_KU_LEN;
|
|
if (session.securityAuthProto == NULL) {
|
|
/*
|
|
* get .conf set default
|
|
*/
|
|
const oid *def = get_default_authtype(&session.securityAuthProtoLen);
|
|
session.securityAuthProto = snmp_duplicate_objid(def, session.securityAuthProtoLen);
|
|
}
|
|
|
|
if (session.securityAuthProto == NULL) {
|
|
session.securityAuthProto = snmp_duplicate_objid(SNMP_DEFAULT_AUTH_PROTO, SNMP_DEFAULT_AUTH_PROTOLEN);
|
|
session.securityAuthProtoLen = SNMP_DEFAULT_AUTH_PROTOLEN;
|
|
}
|
|
|
|
if (generate_Ku(session.securityAuthProto,
|
|
session.securityAuthProtoLen,
|
|
(u_char *) Apsz, strlen(Apsz),
|
|
session.securityAuthKey,
|
|
&session.securityAuthKeyLen) != SNMPERR_SUCCESS) {
|
|
SPINE_LOG(("SNMP: Device[%i] Error generating SNMPv3 Ku from authentication passphrase.", host_id));
|
|
free(session.peername);
|
|
free(session.securityAuthProto);
|
|
free(session.securityPrivProto);
|
|
if (Apsz) memset(Apsz, 0x0, strlen(Apsz));
|
|
free(Apsz);
|
|
if (Xpsz) memset(Xpsz, 0x0, strlen(Xpsz));
|
|
free(Xpsz);
|
|
if (session.localname) {
|
|
free(session.localname);
|
|
session.localname = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (Apsz) memset(Apsz, 0x0, strlen(Apsz));
|
|
free(Apsz);
|
|
Apsz = NULL;
|
|
}
|
|
|
|
if (Xpsz) {
|
|
session.securityPrivKeyLen = USM_PRIV_KU_LEN;
|
|
if (session.securityPrivProto == NULL) {
|
|
/*
|
|
* get .conf set default
|
|
*/
|
|
const oid *def = get_default_privtype(&session.securityPrivProtoLen);
|
|
session.securityPrivProto =
|
|
snmp_duplicate_objid(def, session.securityPrivProtoLen);
|
|
}
|
|
|
|
if (session.securityPrivProto == NULL) {
|
|
#ifdef HAVE_USM_DES_PRIV_PROTOCOL
|
|
session.securityPrivProto = snmp_duplicate_objid(SNMP_DEFAULT_PRIV_PROTO, SNMP_DEFAULT_PRIV_PROTOLEN);
|
|
session.securityPrivProtoLen = SNMP_DEFAULT_PRIV_PROTOLEN;
|
|
#else
|
|
/* The header's default macro expands to usmDESPrivProtocol, but some
|
|
* distributions (e.g. Fedora) ship a net-snmp-config.h that advertises it
|
|
* without libnetsnmp actually exporting the symbol, which fails to link.
|
|
* Fall back to AES, which configure confirmed libnetsnmp provides. */
|
|
session.securityPrivProto = snmp_duplicate_objid(usmAESPrivProtocol, OID_LENGTH(usmAESPrivProtocol));
|
|
session.securityPrivProtoLen = OID_LENGTH(usmAESPrivProtocol);
|
|
#endif
|
|
}
|
|
|
|
if (generate_Ku(session.securityAuthProto,
|
|
session.securityAuthProtoLen,
|
|
(u_char *) Xpsz, strlen(Xpsz),
|
|
session.securityPrivKey,
|
|
&session.securityPrivKeyLen) != SNMPERR_SUCCESS) {
|
|
SPINE_LOG(("SNMP: Device[%i] Error generating SNMPv3 Ku from privacy pass phrase.", host_id));
|
|
free(session.peername);
|
|
free(session.securityAuthProto);
|
|
free(session.securityPrivProto);
|
|
if (Xpsz) memset(Xpsz, 0x0, strlen(Xpsz));
|
|
free(Xpsz);
|
|
if (session.localname) {
|
|
free(session.localname);
|
|
session.localname = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (Xpsz) memset(Xpsz, 0x0, strlen(Xpsz));
|
|
free(Xpsz);
|
|
Xpsz = NULL;
|
|
}
|
|
}
|
|
|
|
SPINE_LOG_MEDIUM(("Device[%i] SNMPv3 Using AuthProto: %s, PrivProto: %s", host_id, snmp_auth_protocol, snmp_priv_protocol));
|
|
} /* end auth/priv block */
|
|
}
|
|
|
|
/* open SNMP Session */
|
|
thread_mutex_lock(LOCK_SNMP);
|
|
sessp = snmp_sess_open(&session);
|
|
thread_mutex_unlock(LOCK_SNMP);
|
|
|
|
free(session.peername);
|
|
free(session.securityAuthProto);
|
|
free(session.securityPrivProto);
|
|
free(session.localname);
|
|
|
|
if (!sessp) {
|
|
if (is_debug_device(host_id)) {
|
|
SPINE_LOG(("ERROR: Device[%i] Problem initializing SNMP session '%s'", host_id, hostname));
|
|
} else {
|
|
SPINE_LOG_MEDIUM(("ERROR: Device[%i] Problem initializing SNMP session '%s'", host_id, hostname));
|
|
}
|
|
}
|
|
|
|
return sessp;
|
|
}
|
|
|
|
/*! \fn void snmp_host_cleanup(void *snmp_session)
|
|
* \brief closes an established snmp session
|
|
*
|
|
* This function performs cleanup of the snmp sessions once polling is completed
|
|
* for a host.
|
|
*
|
|
*/
|
|
void snmp_host_cleanup(void *snmp_session) {
|
|
if (snmp_session != NULL) {
|
|
thread_mutex_lock(LOCK_SNMP);
|
|
snmp_sess_close(snmp_session);
|
|
thread_mutex_unlock(LOCK_SNMP);
|
|
}
|
|
}
|
|
|
|
/*! \fn int snmp_varbind_is_exception(const struct variable_list *vars)
|
|
* \brief report whether a varbind carries a per-OID exception rather than data
|
|
*
|
|
* Under SNMPv2c an agent reports a single unanswerable OID as an inline
|
|
* exception varbind while the PDU errstat stays SNMP_ERR_NOERROR. Passing one
|
|
* to snprint_value() renders the exception as text, which then gets stored as
|
|
* though it were a real value.
|
|
*
|
|
* \return TRUE when the varbind is an exception
|
|
*/
|
|
int snmp_varbind_is_exception(const struct variable_list *vars) {
|
|
if (vars == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
return (vars->type == SNMP_NOSUCHOBJECT ||
|
|
vars->type == SNMP_NOSUCHINSTANCE ||
|
|
vars->type == SNMP_ENDOFMIBVIEW);
|
|
}
|
|
|
|
/*! \fn char *snmp_get_base(host_t *current_host, const char *snmp_oid, bool should_fail)
|
|
* \brief performs a single snmp_get for a specific snmp OID
|
|
*
|
|
* This function will poll a specific snmp OID for a host. The host snmp
|
|
* session must already be established.
|
|
*
|
|
* \return returns the character representation of the snmp OID, or "U" if
|
|
* unsuccessful.
|
|
*
|
|
*/
|
|
char *snmp_get_base(host_t *current_host, const char *snmp_oid, bool should_fail) {
|
|
struct snmp_pdu *pdu = NULL;
|
|
struct snmp_pdu *response = NULL;
|
|
struct variable_list *vars = NULL;
|
|
size_t anOID_len = MAX_OID_LEN;
|
|
oid anOID[MAX_OID_LEN];
|
|
int status;
|
|
int sess_liberr = SNMPERR_SUCCESS;
|
|
char *result_string;
|
|
char temp_result[RESULTS_BUFFER];
|
|
|
|
if (!(result_string = (char *) malloc(RESULTS_BUFFER))) {
|
|
die("ERROR: Fatal malloc error: snmp.c snmp_get!");
|
|
}
|
|
result_string[0] = '\0';
|
|
|
|
if (current_host->ignore_host) {
|
|
SPINE_LOG_HIGH(("WARNING: Skipped oid '%s' for Device[%i] as host ignore flag is active", snmp_oid, current_host->id));
|
|
SET_UNDEFINED(result_string);
|
|
return result_string;
|
|
}
|
|
|
|
status = STAT_DESCRIP_ERROR;
|
|
|
|
if (current_host->snmp_session != NULL) {
|
|
anOID_len = MAX_OID_LEN;
|
|
|
|
SPINE_LOG_DEVDBG(("Device[%i] DEBUG: snmp_pdu_create(%s)", current_host->id, snmp_oid));
|
|
pdu = snmp_pdu_create(SNMP_MSG_GET);
|
|
SPINE_LOG_DEVDBG(("Device[%i] DEBUG: snmp_pdu_create(%s) [complete]", current_host->id, snmp_oid));
|
|
|
|
if (pdu != NULL) {
|
|
SPINE_LOG_DEVDBG(("Device[%i] DEBUG: snmp_parse_oid(%s)", current_host->id, snmp_oid));
|
|
|
|
if (!snmp_parse_oid(snmp_oid, anOID, &anOID_len)) {
|
|
SPINE_LOG_DEVDBG(("Device[%i] DEBUG: snmp_parse_oid(%s) [complete]", current_host->id, snmp_oid));
|
|
SPINE_LOG(("Device[%i] ERROR: SNMP Get Problems parsing SNMP OID %s", current_host->id, snmp_oid));
|
|
snmp_free_pdu(pdu);
|
|
SET_UNDEFINED(result_string);
|
|
return result_string;
|
|
} else {
|
|
SPINE_LOG_DEVDBG(("Device[%i] DEBUG: snmp_parse_oid(%s) [complete]", current_host->id, snmp_oid));
|
|
SPINE_LOG_DEVDBG(("Device[%i] DEBUG: snmp_add_null_var(%s)", current_host->id, snmp_oid));
|
|
snmp_add_null_var(pdu, anOID, anOID_len);
|
|
SPINE_LOG_DEVDBG(("Device[%i] DEBUG: snmp_add_null_var(%s) [complete]", current_host->id, snmp_oid));
|
|
}
|
|
|
|
/* poll host */
|
|
SPINE_LOG_DEVDBG(("Device[%i] DEBUG: snmp_sess_sync_response(%s)", current_host->id, snmp_oid));
|
|
status = snmp_sess_synch_response(current_host->snmp_session, pdu, &response);
|
|
SPINE_LOG_DEVDBG(("Device[%i] DEBUG: snmp_sess_sync_response(%s) [complete]", current_host->id, snmp_oid));
|
|
}
|
|
|
|
/* retrieve session-level error code for non-success responses */
|
|
if (status == STAT_ERROR && current_host->snmp_session != NULL) {
|
|
int liberr = 0, syserr = 0;
|
|
char *errstr = NULL;
|
|
|
|
snmp_sess_error(current_host->snmp_session, &liberr, &syserr, &errstr);
|
|
sess_liberr = liberr;
|
|
SPINE_LOG_DEBUG(("Device[%i] DEBUG: SNMP session error for oid '%s': %s (liberr=%d)",
|
|
current_host->id, snmp_oid, errstr ? errstr : "unknown", liberr));
|
|
SNMP_FREE(errstr);
|
|
}
|
|
|
|
/* liftoff, successful poll, process it!! */
|
|
if (status == STAT_DESCRIP_ERROR) {
|
|
SPINE_LOG(("ERROR: Unable to create SNMP PDU"));
|
|
|
|
SET_UNDEFINED(result_string);
|
|
status = STAT_ERROR;
|
|
response = NULL;
|
|
} else if (status == STAT_SUCCESS) {
|
|
if (response == NULL) {
|
|
SPINE_LOG(("ERROR: An internal Net-Snmp error condition detected in Cacti snmp_get"));
|
|
|
|
SET_UNDEFINED(result_string);
|
|
status = STAT_ERROR;
|
|
} else if (response->errstat == SNMP_ERR_NOERROR &&
|
|
response->variables != NULL &&
|
|
response->variables->name != NULL) {
|
|
|
|
vars = response->variables;
|
|
|
|
if (vars->type == SNMP_NOSUCHOBJECT) {
|
|
if (!strstr(snmp_oid, ".1.3.6.1.2.1.1.1.0") && !strstr(snmp_oid, ".1.3.6.1.2.1.1.3.0")) {
|
|
SET_UNDEFINED(result_string);
|
|
status = STAT_ERROR;
|
|
SPINE_LOG_DEBUG(("DEBUG: OID '%s' for Device[%i], SNMP_NOSUCHOBJECT not sysDesc or sysUptime", snmp_oid, current_host->id));
|
|
} else {
|
|
SPINE_LOG_HIGH(("DEBUG: OID '%s' for Device[%i], SNMP_NOSUCHOBJECT for sysDesc or sysUptime", snmp_oid, current_host->id));
|
|
snprint_value(temp_result, RESULTS_BUFFER, vars->name, vars->name_length, vars);
|
|
snprintf(result_string, RESULTS_BUFFER, "%s", trim(temp_result));
|
|
}
|
|
} else if (vars->type == SNMP_NOSUCHINSTANCE) {
|
|
SET_UNDEFINED(result_string);
|
|
status = STAT_ERROR;
|
|
|
|
// We will ignore the new OID error
|
|
if (!strstr(snmp_oid, ".1.3.6.1.6.3.10.2.1.3.0")) {
|
|
SPINE_LOG_HIGH(("WARNING: No such Instance for oid '%s' for Device[%i] with Status[%d]", snmp_oid, current_host->id, status));
|
|
} else {
|
|
SPINE_LOG_DEBUG(("NOTE: Legacy SNMP agent found! No per second Uptime oid found '%s' for Device[%i] with Status[%d]", snmp_oid, current_host->id, status));
|
|
}
|
|
} else if (vars->type == SNMP_ENDOFMIBVIEW) {
|
|
SET_UNDEFINED(result_string);
|
|
status = STAT_ERROR;
|
|
SPINE_LOG_HIGH(("ERROR: End of Mib for oid '%s' for Device[%i] with Status[%d]", snmp_oid, current_host->id, status));
|
|
} else {
|
|
snprint_value(temp_result, RESULTS_BUFFER, vars->name, vars->name_length, vars);
|
|
|
|
snprintf(result_string, RESULTS_BUFFER, "%s", trim(temp_result));
|
|
}
|
|
} else {
|
|
SPINE_LOG_HIGH(("ERROR: Failed to get oid '%s' for Device[%i] with Response[%ld]", snmp_oid, current_host->id, response->errstat));
|
|
}
|
|
} else if (response != NULL && response->variables != NULL) {
|
|
vars = response->variables;
|
|
|
|
if (vars->type == SNMP_NOSUCHOBJECT) {
|
|
SET_UNDEFINED(result_string);
|
|
status = STAT_ERROR;
|
|
SPINE_LOG_HIGH(("ERROR: No such Object for oid '%s' for Device[%i] with Status[%d]", snmp_oid, current_host->id, status));
|
|
} else if (vars->type == SNMP_NOSUCHINSTANCE) {
|
|
SET_UNDEFINED(result_string);
|
|
status = STAT_ERROR;
|
|
|
|
// We will ignore the new OID error
|
|
if (!strstr(snmp_oid, ".1.3.6.1.6.3.10.2.1.3.0")) {
|
|
SPINE_LOG_HIGH(("WARNING: No such Instance for oid '%s' for Device[%i] with Status[%d]", snmp_oid, current_host->id, status));
|
|
} else {
|
|
SPINE_LOG_DEBUG(("NOTE: Per second level uptime oid missing oid '%s' for Device[%i] with Status[%d]", snmp_oid, current_host->id, status));
|
|
}
|
|
} else if (vars->type == SNMP_ENDOFMIBVIEW) {
|
|
SET_UNDEFINED(result_string);
|
|
status = STAT_ERROR;
|
|
SPINE_LOG_HIGH(("ERROR: End of Mib for oid '%s' for Device[%i] with Status[%d]", snmp_oid, current_host->id, status));
|
|
} else {
|
|
SET_UNDEFINED(result_string);
|
|
status = STAT_ERROR;
|
|
SPINE_LOG_HIGH(("ERROR: Unknown error getting oid '%s' for Device[%i] with Status[%d]", snmp_oid, current_host->id, status));
|
|
}
|
|
} else if (status == STAT_TIMEOUT) {
|
|
SPINE_LOG_HIGH(("ERROR: Timeout getting oid '%s' for Device[%i] with Status[%d]", snmp_oid, current_host->id, status));
|
|
} else {
|
|
/* decode USM-specific errors instead of generic "Unknown error"
|
|
* New constant names (SNMPERR_NOT_IN_TIME_WINDOW etc.) added in Net-SNMP 5.8.
|
|
* Old names (SNMPERR_USM_NOTINTIMEWINDOW etc.) added in Net-SNMP 5.7.
|
|
* On older versions, all USM errors fall through to the default case.
|
|
*/
|
|
switch (sess_liberr) {
|
|
#if defined(SNMPERR_NOT_IN_TIME_WINDOW) || defined(SNMPERR_USM_NOTINTIMEWINDOW)
|
|
#if defined(SNMPERR_NOT_IN_TIME_WINDOW)
|
|
case SNMPERR_NOT_IN_TIME_WINDOW:
|
|
#else
|
|
case SNMPERR_USM_NOTINTIMEWINDOW:
|
|
#endif
|
|
SPINE_LOG_MEDIUM(("WARNING: Device[%i] USM notInTimeWindow for oid '%s' -- engine time drift (recoverable)",
|
|
current_host->id, snmp_oid));
|
|
/* treat as transient, do not mark host down */
|
|
status = STAT_SUCCESS;
|
|
SET_UNDEFINED(result_string);
|
|
break;
|
|
#endif
|
|
#if defined(SNMPERR_UNKNOWN_ENG_ID) || defined(SNMPERR_USM_UNKNOWNENGINEID)
|
|
#if defined(SNMPERR_UNKNOWN_ENG_ID)
|
|
case SNMPERR_UNKNOWN_ENG_ID:
|
|
#else
|
|
case SNMPERR_USM_UNKNOWNENGINEID:
|
|
#endif
|
|
SPINE_LOG_HIGH(("ERROR: Device[%i] USM unknownEngineID for oid '%s'",
|
|
current_host->id, snmp_oid));
|
|
break;
|
|
#endif
|
|
#if defined(SNMPERR_UNKNOWN_USER_NAME) || defined(SNMPERR_USM_UNKNOWNSECURITYNAME)
|
|
#if defined(SNMPERR_UNKNOWN_USER_NAME)
|
|
case SNMPERR_UNKNOWN_USER_NAME:
|
|
#else
|
|
case SNMPERR_USM_UNKNOWNSECURITYNAME:
|
|
#endif
|
|
SPINE_LOG_HIGH(("ERROR: Device[%i] USM unknownSecurityName for oid '%s'",
|
|
current_host->id, snmp_oid));
|
|
break;
|
|
#endif
|
|
#if defined(SNMPERR_AUTHENTICATION_FAILURE) || defined(SNMPERR_USM_AUTHENTICATIONFAILURE)
|
|
#if defined(SNMPERR_AUTHENTICATION_FAILURE)
|
|
case SNMPERR_AUTHENTICATION_FAILURE:
|
|
#else
|
|
case SNMPERR_USM_AUTHENTICATIONFAILURE:
|
|
#endif
|
|
SPINE_LOG_HIGH(("ERROR: Device[%i] USM authenticationFailure for oid '%s'",
|
|
current_host->id, snmp_oid));
|
|
break;
|
|
#endif
|
|
#if defined(SNMPERR_DECRYPTION_ERR) || defined(SNMPERR_USM_DECRYPTIONERROR)
|
|
#if defined(SNMPERR_DECRYPTION_ERR)
|
|
case SNMPERR_DECRYPTION_ERR:
|
|
#else
|
|
case SNMPERR_USM_DECRYPTIONERROR:
|
|
#endif
|
|
SPINE_LOG_HIGH(("ERROR: Device[%i] USM decryptionError for oid '%s'",
|
|
current_host->id, snmp_oid));
|
|
break;
|
|
#endif
|
|
#if defined(SNMPERR_UNSUPPORTED_SEC_LEVEL) || defined(SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL)
|
|
#if defined(SNMPERR_UNSUPPORTED_SEC_LEVEL)
|
|
case SNMPERR_UNSUPPORTED_SEC_LEVEL:
|
|
#else
|
|
case SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL:
|
|
#endif
|
|
SPINE_LOG_HIGH(("ERROR: Device[%i] USM unsupportedSecurityLevel for oid '%s'",
|
|
current_host->id, snmp_oid));
|
|
break;
|
|
#endif
|
|
default:
|
|
SPINE_LOG_HIGH(("ERROR: Unknown error getting oid '%s' for Device[%i] with Status[%d] Errno[%d]",
|
|
snmp_oid, current_host->id, status, sess_liberr));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* update host status after all adjustments */
|
|
current_host->snmp_status = status;
|
|
|
|
if (response != NULL && status != STAT_DESCRIP_ERROR) {
|
|
snmp_free_pdu(response);
|
|
response = NULL;
|
|
}
|
|
}
|
|
|
|
if (status != STAT_SUCCESS && should_fail) {
|
|
current_host->ignore_host = TRUE;
|
|
|
|
SET_UNDEFINED(result_string);
|
|
}
|
|
|
|
return result_string;
|
|
}
|
|
|
|
char *snmp_get(host_t *current_host, const char *snmp_oid) {
|
|
return snmp_get_base(current_host, snmp_oid, true);
|
|
}
|
|
|
|
char *snmp_get_allow_fail(host_t *current_host, const char *snmp_oid) {
|
|
return snmp_get_base(current_host, snmp_oid, false);
|
|
}
|
|
|
|
/*! \fn char *snmp_getnext(host_t *current_host, const char *snmp_oid)
|
|
* \brief performs a single snmp_getnext for a specific snmp OID
|
|
*
|
|
* This function will poll a specific snmp OID for a host. The host snmp
|
|
* session must already be established.
|
|
*
|
|
* \return returns the character representation of the snmp OID, or "U" if
|
|
* unsuccessful.
|
|
*
|
|
*/
|
|
char *snmp_getnext(host_t *current_host, const char *snmp_oid) {
|
|
struct snmp_pdu *pdu = NULL;
|
|
struct snmp_pdu *response = NULL;
|
|
struct variable_list *vars = NULL;
|
|
size_t anOID_len = MAX_OID_LEN;
|
|
oid anOID[MAX_OID_LEN];
|
|
int status;
|
|
int sess_liberr = SNMPERR_SUCCESS;
|
|
char *result_string;
|
|
char temp_result[RESULTS_BUFFER];
|
|
|
|
if (!(result_string = (char *) malloc(RESULTS_BUFFER))) {
|
|
die("ERROR: Fatal malloc error: snmp.c snmp_get!");
|
|
}
|
|
result_string[0] = '\0';
|
|
|
|
status = STAT_DESCRIP_ERROR;
|
|
|
|
if (current_host->snmp_session != NULL) {
|
|
anOID_len = MAX_OID_LEN;
|
|
pdu = snmp_pdu_create(SNMP_MSG_GETNEXT);
|
|
|
|
if (!snmp_parse_oid(snmp_oid, anOID, &anOID_len)) {
|
|
SPINE_LOG(("Device[%i] ERROR: SNMP Getnext Problems parsing SNMP OID %s", current_host->id, snmp_oid));
|
|
snmp_free_pdu(pdu);
|
|
SET_UNDEFINED(result_string);
|
|
return result_string;
|
|
} else {
|
|
snmp_add_null_var(pdu, anOID, anOID_len);
|
|
}
|
|
|
|
/* poll host */
|
|
status = snmp_sess_synch_response(current_host->snmp_session, pdu, &response);
|
|
|
|
/* retrieve session-level error code for non-success responses */
|
|
if (status == STAT_ERROR && current_host->snmp_session != NULL) {
|
|
int liberr = 0, syserr = 0;
|
|
char *errstr = NULL;
|
|
|
|
snmp_sess_error(current_host->snmp_session, &liberr, &syserr, &errstr);
|
|
sess_liberr = liberr;
|
|
SPINE_LOG_DEBUG(("Device[%i] DEBUG: SNMP getnext session error for oid '%s': %s (liberr=%d)",
|
|
current_host->id, snmp_oid, errstr ? errstr : "unknown", liberr));
|
|
SNMP_FREE(errstr);
|
|
}
|
|
|
|
/* liftoff, successful poll, process it!! */
|
|
if (status == STAT_SUCCESS) {
|
|
if (response == NULL) {
|
|
SPINE_LOG(("ERROR: An internal Net-Snmp error condition detected in Cacti snmp_getnext"));
|
|
|
|
SET_UNDEFINED(result_string);
|
|
status = STAT_ERROR;
|
|
} else {
|
|
if (response->errstat == SNMP_ERR_NOERROR) {
|
|
vars = response->variables;
|
|
|
|
if (vars != NULL && !snmp_varbind_is_exception(vars)) {
|
|
snprint_value(temp_result, RESULTS_BUFFER, vars->name, vars->name_length, vars);
|
|
|
|
snprint_asciistring(result_string, RESULTS_BUFFER, (unsigned char *)temp_result, strlen(temp_result));
|
|
} else {
|
|
SET_UNDEFINED(result_string);
|
|
status = STAT_ERROR;
|
|
}
|
|
}
|
|
}
|
|
} else if (status == STAT_TIMEOUT) {
|
|
SPINE_LOG_HIGH(("ERROR: Timeout getting oid '%s' for Device[%i] with Status[%d]", snmp_oid, current_host->id, status));
|
|
} else if (status == STAT_ERROR) {
|
|
/* decode USM-specific errors instead of generic "Unknown error"
|
|
* See version notes in snmp_get_base above.
|
|
*/
|
|
switch (sess_liberr) {
|
|
#if defined(SNMPERR_NOT_IN_TIME_WINDOW) || defined(SNMPERR_USM_NOTINTIMEWINDOW)
|
|
#if defined(SNMPERR_NOT_IN_TIME_WINDOW)
|
|
case SNMPERR_NOT_IN_TIME_WINDOW:
|
|
#else
|
|
case SNMPERR_USM_NOTINTIMEWINDOW:
|
|
#endif
|
|
SPINE_LOG_MEDIUM(("WARNING: Device[%i] USM notInTimeWindow for getnext oid '%s' -- engine time drift (recoverable)",
|
|
current_host->id, snmp_oid));
|
|
/* treat as transient, do not mark host down */
|
|
status = STAT_SUCCESS;
|
|
SET_UNDEFINED(result_string);
|
|
break;
|
|
#endif
|
|
#if defined(SNMPERR_UNKNOWN_ENG_ID) || defined(SNMPERR_USM_UNKNOWNENGINEID)
|
|
#if defined(SNMPERR_UNKNOWN_ENG_ID)
|
|
case SNMPERR_UNKNOWN_ENG_ID:
|
|
#else
|
|
case SNMPERR_USM_UNKNOWNENGINEID:
|
|
#endif
|
|
SPINE_LOG_HIGH(("ERROR: Device[%i] USM unknownEngineID for getnext oid '%s'",
|
|
current_host->id, snmp_oid));
|
|
break;
|
|
#endif
|
|
#if defined(SNMPERR_UNKNOWN_USER_NAME) || defined(SNMPERR_USM_UNKNOWNSECURITYNAME)
|
|
#if defined(SNMPERR_UNKNOWN_USER_NAME)
|
|
case SNMPERR_UNKNOWN_USER_NAME:
|
|
#else
|
|
case SNMPERR_USM_UNKNOWNSECURITYNAME:
|
|
#endif
|
|
SPINE_LOG_HIGH(("ERROR: Device[%i] USM unknownSecurityName for getnext oid '%s'",
|
|
current_host->id, snmp_oid));
|
|
break;
|
|
#endif
|
|
#if defined(SNMPERR_AUTHENTICATION_FAILURE) || defined(SNMPERR_USM_AUTHENTICATIONFAILURE)
|
|
#if defined(SNMPERR_AUTHENTICATION_FAILURE)
|
|
case SNMPERR_AUTHENTICATION_FAILURE:
|
|
#else
|
|
case SNMPERR_USM_AUTHENTICATIONFAILURE:
|
|
#endif
|
|
SPINE_LOG_HIGH(("ERROR: Device[%i] USM authenticationFailure for getnext oid '%s'",
|
|
current_host->id, snmp_oid));
|
|
break;
|
|
#endif
|
|
#if defined(SNMPERR_DECRYPTION_ERR) || defined(SNMPERR_USM_DECRYPTIONERROR)
|
|
#if defined(SNMPERR_DECRYPTION_ERR)
|
|
case SNMPERR_DECRYPTION_ERR:
|
|
#else
|
|
case SNMPERR_USM_DECRYPTIONERROR:
|
|
#endif
|
|
SPINE_LOG_HIGH(("ERROR: Device[%i] USM decryptionError for getnext oid '%s'",
|
|
current_host->id, snmp_oid));
|
|
break;
|
|
#endif
|
|
#if defined(SNMPERR_UNSUPPORTED_SEC_LEVEL) || defined(SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL)
|
|
#if defined(SNMPERR_UNSUPPORTED_SEC_LEVEL)
|
|
case SNMPERR_UNSUPPORTED_SEC_LEVEL:
|
|
#else
|
|
case SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL:
|
|
#endif
|
|
SPINE_LOG_HIGH(("ERROR: Device[%i] USM unsupportedSecurityLevel for getnext oid '%s'",
|
|
current_host->id, snmp_oid));
|
|
break;
|
|
#endif
|
|
default:
|
|
SPINE_LOG_HIGH(("ERROR: Unknown error getting oid '%s' for Device[%i] with Status[%d] Errno[%d]",
|
|
snmp_oid, current_host->id, status, sess_liberr));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* update host status after all adjustments */
|
|
current_host->snmp_status = status;
|
|
|
|
if (response) {
|
|
snmp_free_pdu(response);
|
|
response = NULL;
|
|
}
|
|
} else {
|
|
status = STAT_DESCRIP_ERROR;
|
|
}
|
|
|
|
if (status != STAT_SUCCESS) {
|
|
current_host->ignore_host = TRUE;
|
|
|
|
SET_UNDEFINED(result_string);
|
|
}
|
|
|
|
return result_string;
|
|
}
|
|
|
|
/*! \fn char *snmp_count(host_t *current_host, char *snmp_oid)
|
|
* \brief counts entries of snmp table specified by a specific snmp OID
|
|
*
|
|
* This function will poll a specific snmp OID for a host. The host snmp
|
|
* session must already be established.
|
|
*
|
|
* \return returns count of table entries
|
|
*
|
|
*/
|
|
int snmp_count(host_t *current_host, const char *snmp_oid) {
|
|
struct snmp_pdu *pdu = NULL;
|
|
struct snmp_pdu *response = NULL;
|
|
struct variable_list *vars = NULL;
|
|
size_t anOID_len = MAX_OID_LEN;
|
|
size_t rootlen = MAX_OID_LEN;
|
|
oid anOID[MAX_OID_LEN];
|
|
oid root[MAX_OID_LEN];
|
|
int status;
|
|
int ok = 1;
|
|
int error_occurred = 0;
|
|
int count = 0;
|
|
|
|
status = STAT_DESCRIP_ERROR;
|
|
|
|
if (is_debug_device(current_host->id)) {
|
|
SPINE_LOG(("DEBUG: walk starts at OID %s", snmp_oid));
|
|
} else {
|
|
SPINE_LOG_DEBUG(("DEBUG: walk starts at OID %s", snmp_oid));
|
|
}
|
|
|
|
if (current_host->snmp_session != NULL) {
|
|
rootlen = MAX_OID_LEN;
|
|
/* parse input parm to an array for use with snmp functions */
|
|
if (!snmp_parse_oid(snmp_oid, root, &rootlen)) {
|
|
SPINE_LOG(("Device[%i] ERROR: SNMP Count Problems parsing SNMP OID %s", current_host->id, snmp_oid));
|
|
return -1;
|
|
}
|
|
memmove(anOID, root, rootlen * sizeof(oid));
|
|
anOID_len = rootlen;
|
|
|
|
while (ok && !error_occurred) {
|
|
/* create PDU for GETNEXT request */
|
|
pdu = snmp_pdu_create(SNMP_MSG_GETNEXT);
|
|
snmp_add_null_var(pdu, anOID, anOID_len);
|
|
|
|
/* do the request, use thread safe call */
|
|
status = snmp_sess_synch_response(current_host->snmp_session, pdu, &response);
|
|
|
|
/* add status to host structure */
|
|
current_host->snmp_status = status;
|
|
|
|
//SPINE_LOG_DEBUG(("TRACE: Status %i Response %i", status, response->errstat));
|
|
|
|
if (status == STAT_SUCCESS) {
|
|
if (response == NULL) {
|
|
SPINE_LOG(("ERROR: Device[%i] internal Net-SNMP error in snmp_count for OID %s", current_host->id, snmp_oid));
|
|
ok = 0;
|
|
error_occurred = 1;
|
|
} else if (response->errstat == SNMP_ERR_NOERROR) {
|
|
/* check resulting variables */
|
|
for (vars = response->variables; vars; vars = vars->next_variable) {
|
|
if ((vars->name_length < rootlen) || (memcmp(root, vars->name, rootlen * sizeof(oid)) != 0)) {
|
|
/* next OID is not part of snmptable */
|
|
ok = 0;
|
|
continue;
|
|
}
|
|
/* END OF MIB or NO SUCH OBJECT or NO SUCH INSTANCE */
|
|
if ((vars->type != SNMP_ENDOFMIBVIEW) &&
|
|
(vars->type != SNMP_NOSUCHOBJECT) &&
|
|
(vars->type != SNMP_NOSUCHINSTANCE)) {
|
|
/* count only real entries, not the walk terminator */
|
|
count++;
|
|
/* valid data, so perform a compare */
|
|
if (snmp_oid_compare(anOID, anOID_len, vars->name, vars->name_length) >= 0) {
|
|
SPINE_LOG(("ERROR: OID not increasing"));
|
|
ok = 0;
|
|
error_occurred = 1;
|
|
}
|
|
/* prepare next turn */
|
|
memmove((char *) anOID, (char *) vars->name, vars->name_length * sizeof(oid));
|
|
anOID_len = vars->name_length;
|
|
} else {
|
|
/* abnormal end of loop */
|
|
ok = 0;
|
|
}
|
|
}
|
|
} else if (response->errstat == SNMP_ERR_NOSUCHNAME) {
|
|
/* SNMPv1 has no endOfMibView variable type; it reports the normal
|
|
* end of a GETNEXT walk as a PDU-level noSuchName instead. */
|
|
ok = 0;
|
|
} else {
|
|
SPINE_LOG(("ERROR: Device[%i] internal Net-SNMP error %ld in snmp_count for OID %s", current_host->id, response->errstat, snmp_oid));
|
|
ok = 0;
|
|
error_occurred = 1;
|
|
}
|
|
} else if (status == STAT_TIMEOUT) {
|
|
SPINE_LOG(("ERROR: Device[%i] timeout in snmp_count for OID %s", current_host->id, snmp_oid));
|
|
ok = 0;
|
|
error_occurred = 1;
|
|
} else { /* status == STAT_ERROR */
|
|
SPINE_LOG(("ERROR: Device[%i] internal Net-SNMP error in snmp_count for OID %s (STAT_ERROR)", current_host->id, snmp_oid));
|
|
ok = 0;
|
|
error_occurred = 1;
|
|
}
|
|
|
|
if (response) {
|
|
snmp_free_pdu(response);
|
|
}
|
|
}
|
|
} else {
|
|
status = STAT_DESCRIP_ERROR;
|
|
error_occurred = 1;
|
|
}
|
|
|
|
if (status != STAT_SUCCESS) {
|
|
current_host->ignore_host = TRUE;
|
|
}
|
|
|
|
/* A negative count tells the caller this walk never produced a usable
|
|
* result, so it is not mistaken for a legitimate zero-item count. */
|
|
return error_occurred ? -1 : count;
|
|
}
|
|
|
|
/*! \fn void snmp_snprint_value(char *obuf, size_t buf_len, const oid *objid, size_t objidlen, struct variable_list *variable)
|
|
*
|
|
* \brief replacement for the buggy net-snmp.org snprint_value function
|
|
*
|
|
* This function format an output buffer with the correct string representation
|
|
* of an snmp OID result fetched with snmp_get_multi. The buffer pointed to by
|
|
* the function is modified.
|
|
*
|
|
*/
|
|
void snmp_snprint_value(char *obuf, size_t buf_len, const oid *objid, size_t objidlen, struct variable_list *variable) {
|
|
u_char *buf = NULL;
|
|
size_t out_len = 0;
|
|
UNUSED_PARAMETER(objid);
|
|
UNUSED_PARAMETER(objidlen);
|
|
|
|
if (buf_len > 0) {
|
|
if ((buf = (u_char *) calloc(buf_len, 1)) != 0) {
|
|
sprint_realloc_by_type(&buf, &buf_len, &out_len, 0, variable, NULL, NULL, NULL);
|
|
snprintf(obuf, buf_len, "%s", buf);
|
|
} else {
|
|
SET_UNDEFINED(obuf);
|
|
}
|
|
|
|
free(buf);
|
|
} else {
|
|
SET_UNDEFINED(obuf);
|
|
}
|
|
}
|
|
|
|
/*! \fn char *snmp_get_multi(host_t *current_host, target_t *poller_items, snmp_oids_t *snmp_oids, int num_oids)
|
|
* \brief performs multiple OID snmp_get's in a single network call
|
|
*
|
|
* This function will a group of snmp OID's for a host. The host snmp
|
|
* session must already be established. The function will modify elements of
|
|
* the snmp_oids array with the results from the snmp api call.
|
|
*
|
|
*/
|
|
void snmp_get_multi(host_t *current_host, target_t *poller_items, snmp_oids_t *snmp_oids, int num_oids) {
|
|
struct snmp_pdu *pdu = NULL;
|
|
struct snmp_pdu *response = NULL;
|
|
struct variable_list *vars = NULL;
|
|
int status;
|
|
int i;
|
|
int array_count;
|
|
int index_count;
|
|
char temp_result[RESULTS_BUFFER];
|
|
|
|
struct nameStruct {
|
|
oid name[MAX_OID_LEN];
|
|
size_t name_len;
|
|
} *name, *namep;
|
|
|
|
/* load up oids */
|
|
namep = name = (struct nameStruct *) calloc(num_oids, sizeof(*name));
|
|
if (name == NULL) {
|
|
SPINE_LOG(("ERROR: Failed to allocate memory for SNMP OID name array"));
|
|
return;
|
|
}
|
|
pdu = snmp_pdu_create(SNMP_MSG_GET);
|
|
for (i = 0; i < num_oids; i++) {
|
|
namep->name_len = MAX_OID_LEN;
|
|
|
|
if (!snmp_parse_oid(snmp_oids[i].oid, namep->name, &namep->name_len)) {
|
|
SPINE_LOG(("Device[%i] DS[%i] ERROR: Problems parsing Multi SNMP OID! (oid: %s), Set MAX_OIDS to 1 for this host to isolate bad OID", current_host->id, poller_items[snmp_oids[i].array_position].local_data_id, snmp_oids[i].oid));
|
|
|
|
/* Mark this OID as "bad" */
|
|
SET_UNDEFINED(snmp_oids[i].result);
|
|
} else {
|
|
snmp_add_null_var(pdu, namep->name, namep->name_len);
|
|
}
|
|
|
|
namep++;
|
|
}
|
|
|
|
status = STAT_DESCRIP_ERROR;
|
|
|
|
/* execute the multi-get request */
|
|
retry:
|
|
status = snmp_sess_synch_response(current_host->snmp_session, pdu, &response);
|
|
|
|
/* add status to host structure */
|
|
current_host->snmp_status = status;
|
|
|
|
/* liftoff, successful poll, process it!! */
|
|
if (status == STAT_SUCCESS) {
|
|
if (response == NULL) {
|
|
SPINE_LOG(("ERROR: An internal Net-Snmp error condition detected in Cacti snmp_get_multi"));
|
|
status = STAT_ERROR;
|
|
} else {
|
|
if (response->errstat == SNMP_ERR_NOERROR) {
|
|
vars = response->variables;
|
|
|
|
for (i = 0; i < num_oids && vars; i++) {
|
|
if (!IS_UNDEFINED(snmp_oids[i].result)) {
|
|
/* Under v2c an agent reports a per-OID failure as an
|
|
* exception varbind while the PDU errstat stays
|
|
* NOERROR. Without this check snprint_value() renders
|
|
* the exception as text and it is stored as a value. */
|
|
if (snmp_varbind_is_exception(vars)) {
|
|
SPINE_LOG_HIGH(("Device[%i] WARNING: No SNMP data returned for OID '%s'", current_host->id, snmp_oids[i].oid));
|
|
|
|
SET_UNDEFINED(snmp_oids[i].result);
|
|
} else {
|
|
snprint_value(temp_result, RESULTS_BUFFER, vars->name, vars->name_length, vars);
|
|
|
|
snprintf(snmp_oids[i].result, RESULTS_BUFFER, "%s", trim(temp_result));
|
|
}
|
|
|
|
vars = vars->next_variable;
|
|
}
|
|
}
|
|
} else {
|
|
if (response->errindex != 0) {
|
|
index_count = 1;
|
|
array_count = 0;
|
|
|
|
/* Find our index against errindex */
|
|
while (array_count < num_oids) {
|
|
if (IS_UNDEFINED(snmp_oids[array_count].result) ) {
|
|
array_count++;
|
|
} else {
|
|
/* if we have found our error, exit */
|
|
if (index_count == response->errindex) {
|
|
SET_UNDEFINED(snmp_oids[array_count].result);
|
|
|
|
break;
|
|
}
|
|
array_count++;
|
|
index_count++;
|
|
}
|
|
|
|
}
|
|
|
|
/* remove the invalid OID from the PDU */
|
|
pdu = snmp_fix_pdu(response, SNMP_MSG_GET);
|
|
|
|
/* free the previous response */
|
|
snmp_free_pdu(response);
|
|
|
|
response = NULL;
|
|
if (pdu != NULL) {
|
|
/* retry the request */
|
|
goto retry;
|
|
} else {
|
|
/* all OID's errored out so exit cleanly */
|
|
status = STAT_SUCCESS;
|
|
}
|
|
} else {
|
|
/* errindex==0: agent reported a PDU-level error with no specific
|
|
* failing OID (RFC 3416 s.4.2). Mark all pending results UNDEFINED
|
|
* so callers write "U" to RRD rather than an empty string. */
|
|
SPINE_LOG_HIGH(("Device[%i] WARNING: snmp_get_multi PDU error (errstat=%ld, errindex=0), all OIDs set undefined",
|
|
current_host->id, (long)response->errstat));
|
|
for (i = 0; i < num_oids; i++) {
|
|
SET_UNDEFINED(snmp_oids[i].result);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (status == STAT_TIMEOUT) {
|
|
current_host->ignore_host = 1;
|
|
for (i = 0; i < num_oids; i++) {
|
|
SET_UNDEFINED(snmp_oids[i].result);
|
|
}
|
|
}
|
|
|
|
if (response != NULL) {
|
|
snmp_free_pdu(response);
|
|
}
|
|
|
|
free(name);
|
|
}
|