Files
spine/snmp.c
T
YongKyunandTheWitness de182d4f07 prevent host ignore on NoSuchObject for system OIDs (#553)
**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>
2026-09-22 11:05:32 -04:00

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);
}