/* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** 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 Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ #include "checks_snmp.h" #include "zbxcacheconfig.h" #include "zbxip.h" #ifdef HAVE_NETSNMP #define SNMP_NO_DEBUGGING #include "zbxasyncpoller.h" #include "async_poller.h" #include "zbxpoller.h" #include "zbxcomms.h" #include "zbxalgo.h" #include "zbxjson.h" #include "zbxparam.h" #include "zbxsysinfo.h" #include "zbxdbhigh.h" #include "zbxexpr.h" #include "zbxstr.h" #include <event2/event.h> #include <event2/util.h> #include <event2/dns.h> #include <net-snmp/net-snmp-config.h> #include <net-snmp/net-snmp-includes.h> #include <net-snmp/library/large_fd_set.h> #include "zbxself.h" #ifndef EVDNS_BASE_INITIALIZE_NAMESERVERS # define EVDNS_BASE_INITIALIZE_NAMESERVERS 1 #endif /* * SNMP Dynamic Index Cache * ======================== * * Description * ----------- * * Zabbix caches the whole index table for the particular OID separately based on: * * IP address; * * port; * * community string (SNMPv2c); * * context, security name (SNMPv3). * * Zabbix revalidates each index before using it to get a value and rebuilds the index cache for the OID if the * index is invalid. * * Example * ------- * * OID for getting memory usage of process by PID (index): * HOST-RESOURCES-MIB::hrSWRunPerfMem:<PID> * * OID for getting PID (index) by process name (value): * HOST-RESOURCES-MIB::hrSWRunPath:<PID> <NAME> * * SNMP OID as configured in Zabbix to get memory usage of "snmpd" process: * HOST-RESOURCES-MIB::hrSWRunPerfMem["index","HOST-RESOURCES-MIB::hrSWRunPath","snmpd"] * * 1. Zabbix walks hrSWRunPath table and caches all <PID> and <NAME> pairs of particular SNMP agent/user. * 2. Before each GET request Zabbix revalidates the cached <PID> by getting its <NAME> from hrSWRunPath table. * 3. If the names match then Zabbix uses the cached <PID> in the GET request for the hrSWRunPerfMem. * Otherwise Zabbix rebuilds the hrSWRunPath cache for the particular agent/user (see 1.). * * Implementation * -------------- * * The cache is implemented using hash tables. In ERD: * zbx_snmpidx_main_key_t -------------------------------------------0< zbx_snmpidx_mapping_t * (OID, host, <v2c: community|v3: (context, security name)>) (index, value) */ /****************************************************************************** * * * This is zbx_snmp_walk() callback function prototype. * * * * Parameters: arg - [IN] user argument passed to zbx_snmp_walk() * * snmp_oid - [IN] OID walk function is looking for * * index - [IN] index of found OID * * value - [IN] OID value * * * ******************************************************************************/ typedef void (zbx_snmp_walk_cb_func)(void *arg, const char *snmp_oid, const char *index, const char *value); typedef enum { ZBX_ASN_OCTET_STR_UTF_8, ZBX_ASN_OCTET_STR_HEX } zbx_snmp_asn_octet_str_t; typedef struct { char *addr; unsigned short port; char *oid; char *community_context; /* community (SNMPv1 or v2c) or contextName (SNMPv3) */ char *security_name; /* only SNMPv3, empty string in case of other versions */ zbx_hashset_t *mappings; } zbx_snmpidx_main_key_t; typedef struct { char *value; char *index; } zbx_snmpidx_mapping_t; typedef struct { oid root_oid[MAX_OID_LEN]; size_t root_oid_len; char *str_oid; } zbx_snmp_oid_t; ZBX_PTR_VECTOR_DECL(snmp_oid, zbx_snmp_oid_t *) ZBX_PTR_VECTOR_IMPL(snmp_oid, zbx_snmp_oid_t *) typedef void* zbx_snmp_sess_t; typedef struct { int reqid; int waiting; int pdu_type; int operation; zbx_snmp_oid_t *p_oid; oid name[MAX_OID_LEN]; size_t name_length; int running; int vars_num; void *arg; char *error; netsnmp_large_fd_set fdset; } zbx_bulkwalk_context_t; ZBX_PTR_VECTOR_DECL(bulkwalk_context, zbx_bulkwalk_context_t*) ZBX_PTR_VECTOR_IMPL(bulkwalk_context, zbx_bulkwalk_context_t*) struct zbx_snmp_context { void *arg; void *arg_action; zbx_dc_item_context_t item; zbx_snmp_sess_t ssp; int snmp_max_repetitions; int retries; char *results; size_t results_alloc; size_t results_offset; zbx_vector_snmp_oid_t param_oids; zbx_vector_bulkwalk_context_t bulkwalk_contexts; int i; int config_timeout; int probe; unsigned char snmp_version; char *snmp_community; char *snmpv3_securityname; char *snmpv3_contextname; unsigned char snmpv3_securitylevel; unsigned char snmpv3_authprotocol; char *snmpv3_authpassphrase; unsigned char snmpv3_privprotocol; char *snmpv3_privpassphrase; const char *config_source_ip; unsigned char snmp_oid_type; zbx_async_resolve_reverse_dns_t resolve_reverse_dns; zbx_async_rdns_step_t step; char *reverse_dns; }; typedef struct { AGENT_RESULT *result; int errcode; struct event_base *base; int finished; } zbx_snmp_result_t; static ZBX_THREAD_LOCAL zbx_hashset_t snmpidx; /* Dynamic Index Cache */ static char zbx_snmp_init_done; static char zbx_snmp_init_bulkwalk_done; static pthread_rwlock_t snmp_exec_rwlock; static char snmp_rwlock_init_done; static zbx_hashset_t engineid_cache; static int engineid_cache_initialized = 0; #define ZBX_SNMP_GET 0 #define ZBX_SNMP_WALK 1 #define SNMP_MT_EXECLOCK \ if (0 != snmp_rwlock_init_done) \ pthread_rwlock_rdlock(&snmp_exec_rwlock) #define SNMP_MT_INITLOCK \ if (0 != snmp_rwlock_init_done) \ pthread_rwlock_wrlock(&snmp_exec_rwlock) #define SNMP_MT_UNLOCK \ if (0 != snmp_rwlock_init_done) \ pthread_rwlock_unlock(&snmp_exec_rwlock) static void zbx_init_snmp(const char *progname); #define ZBX_SNMP_MAX_ENGINEID_LEN 32 typedef struct { char *address; char *hostname; u_int engineboots; } zbx_snmp_engineid_device_t; ZBX_VECTOR_DECL(engineid_device, zbx_snmp_engineid_device_t) ZBX_VECTOR_IMPL(engineid_device, zbx_snmp_engineid_device_t) typedef struct { unsigned char engineid[ZBX_SNMP_MAX_ENGINEID_LEN]; size_t engineid_len; zbx_vector_engineid_device_t devices; time_t lastlog; } zbx_snmp_engineid_record_t; static zbx_hash_t snmp_engineid_cache_hash(const void *data) { const zbx_snmp_engineid_record_t *hv = (const zbx_snmp_engineid_record_t *)data; return ZBX_DEFAULT_STRING_HASH_ALGO(hv->engineid, hv->engineid_len, ZBX_DEFAULT_HASH_SEED); } static int snmp_engineid_cache_compare(const void *d1, const void *d2) { const zbx_snmp_engineid_record_t *hv1 = (const zbx_snmp_engineid_record_t *)d1; const zbx_snmp_engineid_record_t *hv2 = (const zbx_snmp_engineid_record_t *)d2; ZBX_RETURN_IF_NOT_EQUAL(hv1->engineid_len, hv2->engineid_len); return memcmp(hv1->engineid, hv2->engineid, hv1->engineid_len); } static void zbx_clear_snmp_engineid_devices(zbx_vector_engineid_device_t *d) { for (int i = 0; i < d->values_num; i++) { zbx_free(d->values[i].address); zbx_free(d->values[i].hostname); } zbx_vector_engineid_device_clear(d); zbx_vector_engineid_device_destroy(d); } void zbx_clear_snmp_engineid_cache(void) { zbx_hashset_iter_t iter; zbx_snmp_engineid_record_t *engineid; zbx_hashset_iter_reset(&engineid_cache, &iter); while (NULL != (engineid = (zbx_snmp_engineid_record_t *)zbx_hashset_iter_next(&iter))) { zbx_clear_snmp_engineid_devices(&engineid->devices); zbx_hashset_iter_remove(&iter); } } void zbx_destroy_snmp_engineid_cache(void) { zbx_clear_snmp_engineid_cache(); zbx_hashset_destroy(&engineid_cache); } static int zbx_snmp_cache_handle_engineid(netsnmp_session *session, zbx_dc_item_context_t *item_context) { zbx_snmp_engineid_record_t *ptr, local_record; zbx_snmp_engineid_device_t d; u_int current_engineboots = 0; int ret = SUCCEED; if (0 == engineid_cache_initialized) return SUCCEED; if (ZBX_SNMP_MAX_ENGINEID_LEN < session->securityEngineIDLen) { ret = FAIL; item_context->ret = NOTSUPPORTED; SET_MSG_RESULT(&item_context->result, zbx_dsprintf(NULL, "Invalid SNMP engineId length " "\"" ZBX_FS_UI64 "\"", session->securityEngineIDLen)); goto out; } local_record.engineid_len = session->securityEngineIDLen; memcpy(&local_record.engineid, session->securityEngineID, session->securityEngineIDLen); if (0 == (current_engineboots = session->engineBoots)) { Enginetime et; et = search_enginetime_list(session->securityEngineID, (u_int)session->securityEngineIDLen); while (NULL != et) { current_engineboots = et->engineBoot; et = et->next; } } if (NULL == (ptr = zbx_hashset_search(&engineid_cache, &local_record))) { zbx_vector_engineid_device_create(&local_record.devices); d.address = zbx_strdup(NULL, item_context->interface.addr); d.hostname = zbx_strdup(NULL, item_context->host); d.engineboots = current_engineboots; zbx_vector_engineid_device_append(&local_record.devices, d); local_record.lastlog = 0; zbx_hashset_insert(&engineid_cache, &local_record, sizeof(local_record)); goto out; } else { char *hosts = NULL; size_t hosts_alloc = 0, hosts_offset = 0; int diff_engineboots = 0, found = 0; for (int i = 0; i < ptr->devices.values_num; i++) { if ((0 == strcmp(item_context->interface.addr, ptr->devices.values[i].address) && 0 == strcmp(item_context->host, ptr->devices.values[i].hostname))) { ptr->devices.values[i].engineboots = current_engineboots; found = 1; continue; } if (ptr->devices.values[i].engineboots != current_engineboots) { diff_engineboots = 1; if (0 != hosts_alloc) zbx_snprintf_alloc(&hosts, &hosts_alloc, &hosts_offset, ", "); zbx_snprintf_alloc(&hosts, &hosts_alloc, &hosts_offset, "%s (%s)", ptr->devices.values[i].address, ptr->devices.values[i].hostname); } } if (0 == found) { d.address = zbx_strdup(NULL, item_context->interface.addr); d.hostname = zbx_strdup(NULL, item_context->host); d.engineboots = current_engineboots; zbx_vector_engineid_device_append(&ptr->devices, d); } if (1 == diff_engineboots) { #define ZBX_SNMP_ENGINEID_WARNING_PERIOD 300 time_t now = time(NULL); if (now >= ptr->lastlog + ZBX_SNMP_ENGINEID_WARNING_PERIOD) { zabbix_log(LOG_LEVEL_WARNING, "SNMP engineId is not unique across following " "interfaces: %s (%s), %s", item_context->interface.addr, item_context->host, hosts); ptr->lastlog = now; } zbx_free(hosts); ret = FAIL; item_context->ret = NOTSUPPORTED; SET_MSG_RESULT(&item_context->result, zbx_dsprintf(NULL, "SNMP engineId is not unique")); goto out; #undef ZBX_SNMP_ENGINEID_WARNING_PERIOD } } out: return ret; } #undef ZBX_SNMP_MAX_ENGINEID_LEN void zbx_housekeep_snmp_engineid_cache(void) { #define ZBX_SNMP_ENGINEID_RETENTION_PERIOD 86400 + 3600 zbx_hashset_iter_t iter; zbx_snmp_engineid_record_t *engineid; zbx_hashset_iter_reset(&engineid_cache, &iter); while (NULL != (engineid = (zbx_snmp_engineid_record_t *)zbx_hashset_iter_next(&iter))) { if (engineid->lastlog + ZBX_SNMP_ENGINEID_RETENTION_PERIOD <= time(NULL)) { zbx_clear_snmp_engineid_devices(&engineid->devices); zbx_hashset_iter_remove(&iter); } } #undef ZBX_SNMP_ENGINEID_RETENTION_PERIOD } void zbx_init_snmp_engineid_cache(void) { zbx_hashset_create(&engineid_cache, 100, snmp_engineid_cache_hash, snmp_engineid_cache_compare); engineid_cache_initialized = 1; } static zbx_hash_t __snmpidx_main_key_hash(const void *data) { const zbx_snmpidx_main_key_t *main_key = (const zbx_snmpidx_main_key_t *)data; zbx_hash_t hash; hash = ZBX_DEFAULT_STRING_HASH_FUNC(main_key->addr); hash = ZBX_DEFAULT_STRING_HASH_ALGO(&main_key->port, sizeof(main_key->port), hash); hash = ZBX_DEFAULT_STRING_HASH_ALGO(main_key->oid, strlen(main_key->oid), hash); hash = ZBX_DEFAULT_STRING_HASH_ALGO(main_key->community_context, strlen(main_key->community_context), hash); hash = ZBX_DEFAULT_STRING_HASH_ALGO(main_key->security_name, strlen(main_key->security_name), hash); return hash; } static int __snmpidx_main_key_compare(const void *d1, const void *d2) { const zbx_snmpidx_main_key_t *main_key1 = (const zbx_snmpidx_main_key_t *)d1; const zbx_snmpidx_main_key_t *main_key2 = (const zbx_snmpidx_main_key_t *)d2; int ret; if (0 != (ret = strcmp(main_key1->addr, main_key2->addr))) return ret; ZBX_RETURN_IF_NOT_EQUAL(main_key1->port, main_key2->port); if (0 != (ret = strcmp(main_key1->community_context, main_key2->community_context))) return ret; if (0 != (ret = strcmp(main_key1->security_name, main_key2->security_name))) return ret; return strcmp(main_key1->oid, main_key2->oid); } static void __snmpidx_main_key_clean(void *data) { zbx_snmpidx_main_key_t *main_key = (zbx_snmpidx_main_key_t *)data; zbx_free(main_key->addr); zbx_free(main_key->oid); zbx_free(main_key->community_context); zbx_free(main_key->security_name); zbx_hashset_destroy(main_key->mappings); zbx_free(main_key->mappings); } static zbx_hash_t __snmpidx_mapping_hash(const void *data) { const zbx_snmpidx_mapping_t *mapping = (const zbx_snmpidx_mapping_t *)data; return ZBX_DEFAULT_STRING_HASH_FUNC(mapping->value); } static int __snmpidx_mapping_compare(const void *d1, const void *d2) { const zbx_snmpidx_mapping_t *mapping1 = (const zbx_snmpidx_mapping_t *)d1; const zbx_snmpidx_mapping_t *mapping2 = (const zbx_snmpidx_mapping_t *)d2; return strcmp(mapping1->value, mapping2->value); } static void __snmpidx_mapping_clean(void *data) { zbx_snmpidx_mapping_t *mapping = (zbx_snmpidx_mapping_t *)data; zbx_free(mapping->value); zbx_free(mapping->index); } static int zbx_snmp_oid_compare(const zbx_snmp_oid_t **s1, const zbx_snmp_oid_t **s2) { return strcmp((*s1)->str_oid, (*s2)->str_oid); } static void vector_snmp_oid_free(zbx_snmp_oid_t *ptr) { zbx_free(ptr->str_oid); zbx_free(ptr); } static char *get_item_community_context(const zbx_dc_item_t *item) { if (ZBX_IF_SNMP_VERSION_1 == item->snmp_version || ZBX_IF_SNMP_VERSION_2 == item->snmp_version) return item->snmp_community; else if (ZBX_IF_SNMP_VERSION_3 == item->snmp_version) return item->snmpv3_contextname; THIS_SHOULD_NEVER_HAPPEN; exit(EXIT_FAILURE); } static char *get_item_security_name(const zbx_dc_item_t *item) { if (ZBX_IF_SNMP_VERSION_3 == item->snmp_version) return item->snmpv3_securityname; return ""; } /****************************************************************************** * * * Purpose: retrieves index that matches value from relevant index cache * * * * Parameters: item - [IN] Configuration of Zabbix item, contains * * IP address, port, community string, context, * * security name. * * snmp_oid - [IN] OID of table which contains indexes * * value - [IN] value for which to look up index * * idx - [IN/OUT] destination pointer for * * heap-(re)allocated index * * idx_alloc - [IN/OUT] size of (re)allocated index * * * * Return value: FAIL - dynamic index cache is empty or cache does not * * contain index matching value * * SUCCEED - idx contains found index, * * idx_alloc contains current size of * * heap-(re)allocated idx * * * ******************************************************************************/ static int cache_get_snmp_index(const zbx_dc_item_t *item, const char *snmp_oid, const char *value, char **idx, size_t *idx_alloc) { int ret = FAIL; zbx_snmpidx_main_key_t *main_key, main_key_local; zbx_snmpidx_mapping_t *mapping; size_t idx_offset = 0; zabbix_log(LOG_LEVEL_DEBUG, "In %s() OID:'%s' value:'%s'", __func__, snmp_oid, value); if (NULL == snmpidx.slots) goto end; main_key_local.addr = item->interface.addr; main_key_local.port = item->interface.port; main_key_local.oid = (char *)snmp_oid; main_key_local.community_context = get_item_community_context(item); main_key_local.security_name = get_item_security_name(item); if (NULL == (main_key = (zbx_snmpidx_main_key_t *)zbx_hashset_search(&snmpidx, &main_key_local))) goto end; if (NULL == (mapping = (zbx_snmpidx_mapping_t *)zbx_hashset_search(main_key->mappings, &value))) goto end; zbx_strcpy_alloc(idx, idx_alloc, &idx_offset, mapping->index); ret = SUCCEED; end: zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s idx:'%s'", __func__, zbx_result_string(ret), SUCCEED == ret ? *idx : ""); return ret; } /****************************************************************************** * * * Purpose: stores index-value pair in relevant index cache * * * * Parameters: item - [IN] Configuration of Zabbix item, contains * * IP address, port, community string, context, * * security name. * * snmp_oid - [IN] OID of table which contains indexes * * index - [IN] index part of index-value pair * * value - [IN] value part of index-value pair * * * ******************************************************************************/ static void cache_put_snmp_index(const zbx_dc_item_t *item, const char *snmp_oid, const char *index, const char *value) { zbx_snmpidx_main_key_t *main_key, main_key_local; zbx_snmpidx_mapping_t *mapping, mapping_local; zabbix_log(LOG_LEVEL_DEBUG, "In %s() OID:'%s' index:'%s' value:'%s'", __func__, snmp_oid, index, value); if (NULL == snmpidx.slots) { zbx_hashset_create_ext(&snmpidx, 100, __snmpidx_main_key_hash, __snmpidx_main_key_compare, __snmpidx_main_key_clean, ZBX_DEFAULT_MEM_MALLOC_FUNC, ZBX_DEFAULT_MEM_REALLOC_FUNC, ZBX_DEFAULT_MEM_FREE_FUNC); } main_key_local.addr = item->interface.addr; main_key_local.port = item->interface.port; main_key_local.oid = (char *)snmp_oid; main_key_local.community_context = get_item_community_context(item); main_key_local.security_name = get_item_security_name(item); if (NULL == (main_key = (zbx_snmpidx_main_key_t *)zbx_hashset_search(&snmpidx, &main_key_local))) { main_key_local.addr = zbx_strdup(NULL, item->interface.addr); main_key_local.oid = zbx_strdup(NULL, snmp_oid); main_key_local.community_context = zbx_strdup(NULL, get_item_community_context(item)); main_key_local.security_name = zbx_strdup(NULL, get_item_security_name(item)); main_key_local.mappings = (zbx_hashset_t *)zbx_malloc(NULL, sizeof(zbx_hashset_t)); zbx_hashset_create_ext(main_key_local.mappings, 100, __snmpidx_mapping_hash, __snmpidx_mapping_compare, __snmpidx_mapping_clean, ZBX_DEFAULT_MEM_MALLOC_FUNC, ZBX_DEFAULT_MEM_REALLOC_FUNC, ZBX_DEFAULT_MEM_FREE_FUNC); main_key = (zbx_snmpidx_main_key_t *)zbx_hashset_insert(&snmpidx, &main_key_local, sizeof(main_key_local)); } if (NULL == (mapping = (zbx_snmpidx_mapping_t *)zbx_hashset_search(main_key->mappings, &value))) { mapping_local.value = zbx_strdup(NULL, value); mapping_local.index = zbx_strdup(NULL, index); zbx_hashset_insert(main_key->mappings, &mapping_local, sizeof(mapping_local)); } else if (0 != strcmp(mapping->index, index)) { zbx_free(mapping->index); mapping->index = zbx_strdup(NULL, index); } zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__); } /****************************************************************************** * * * Purpose: deletes index-value mappings from specified index cache * * * * Parameters: item - [IN] Configuration of Zabbix item, contains * * IP address, port, community string, context, * * security name. * * snmp_oid - [IN] OID of table which contains indexes * * * * Comments: Does nothing if the index cache is empty or if it does not * * contain the cache for the specified OID. * * * ******************************************************************************/ static void cache_del_snmp_index_subtree(const zbx_dc_item_t *item, const char *snmp_oid) { zbx_snmpidx_main_key_t *main_key, main_key_local; zabbix_log(LOG_LEVEL_DEBUG, "In %s() OID:'%s'", __func__, snmp_oid); if (NULL == snmpidx.slots) goto end; main_key_local.addr = item->interface.addr; main_key_local.port = item->interface.port; main_key_local.oid = (char *)snmp_oid; main_key_local.community_context = get_item_community_context(item); main_key_local.security_name = get_item_security_name(item); if (NULL == (main_key = (zbx_snmpidx_main_key_t *)zbx_hashset_search(&snmpidx, &main_key_local))) goto end; zbx_hashset_clear(main_key->mappings); end: zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__); } static int zbx_snmpv3_set_auth_protocol(unsigned char snmpv3_authprotocol, struct snmp_session *session) { /* item snmpv3 authentication protocol */ /* SYNC WITH PHP! */ #define ITEM_SNMPV3_AUTHPROTOCOL_MD5 0 #define ITEM_SNMPV3_AUTHPROTOCOL_SHA1 1 #define ITEM_SNMPV3_AUTHPROTOCOL_SHA224 2 #define ITEM_SNMPV3_AUTHPROTOCOL_SHA256 3 #define ITEM_SNMPV3_AUTHPROTOCOL_SHA384 4 #define ITEM_SNMPV3_AUTHPROTOCOL_SHA512 5 int ret = SUCCEED; switch (snmpv3_authprotocol) { case ITEM_SNMPV3_AUTHPROTOCOL_MD5: session->securityAuthProto = usmHMACMD5AuthProtocol; session->securityAuthProtoLen = USM_AUTH_PROTO_MD5_LEN; break; case ITEM_SNMPV3_AUTHPROTOCOL_SHA1: session->securityAuthProto = usmHMACSHA1AuthProtocol; session->securityAuthProtoLen = USM_AUTH_PROTO_SHA_LEN; break; #ifdef HAVE_NETSNMP_STRONG_AUTH case ITEM_SNMPV3_AUTHPROTOCOL_SHA224: session->securityAuthProto = usmHMAC128SHA224AuthProtocol; session->securityAuthProtoLen = OID_LENGTH(usmHMAC128SHA224AuthProtocol); break; case ITEM_SNMPV3_AUTHPROTOCOL_SHA256: session->securityAuthProto = usmHMAC192SHA256AuthProtocol; session->securityAuthProtoLen = OID_LENGTH(usmHMAC192SHA256AuthProtocol); break; case ITEM_SNMPV3_AUTHPROTOCOL_SHA384: session->securityAuthProto = usmHMAC256SHA384AuthProtocol; session->securityAuthProtoLen = OID_LENGTH(usmHMAC256SHA384AuthProtocol); break; case ITEM_SNMPV3_AUTHPROTOCOL_SHA512: session->securityAuthProto = usmHMAC384SHA512AuthProtocol; session->securityAuthProtoLen = OID_LENGTH(usmHMAC384SHA512AuthProtocol); break; #endif default: ret = FAIL; } return ret; #undef ITEM_SNMPV3_AUTHPROTOCOL_MD5 #undef ITEM_SNMPV3_AUTHPROTOCOL_SHA1 #undef ITEM_SNMPV3_AUTHPROTOCOL_SHA224 #undef ITEM_SNMPV3_AUTHPROTOCOL_SHA256 #undef ITEM_SNMPV3_AUTHPROTOCOL_SHA384 #undef ITEM_SNMPV3_AUTHPROTOCOL_SHA512 } static char *zbx_get_snmp_type_error(u_char type) { switch (type) { case SNMP_NOSUCHOBJECT: return zbx_strdup(NULL, "No Such Object available on this agent at this OID"); case SNMP_NOSUCHINSTANCE: return zbx_strdup(NULL, "No Such Instance currently exists at this OID"); case SNMP_ENDOFMIBVIEW: return zbx_strdup(NULL, "No more variables left in this MIB View" " (it is past the end of the MIB tree)"); default: return zbx_dsprintf(NULL, "Value has unknown type 0x%02X", (unsigned int)type); } } static int zbx_get_snmp_response_error(const zbx_snmp_sess_t ssp, const zbx_dc_interface_t *interface, int status, const struct snmp_pdu *response, char *error, size_t max_error_len, int got_vars) { int ret; if (STAT_SUCCESS == status) { zbx_snprintf(error, max_error_len, "SNMP error: %s", snmp_errstring(response->errstat)); ret = NOTSUPPORTED; } else if (STAT_ERROR == status) { char *tmp_err_str; int snmp_err; snmp_sess_error(ssp, NULL, &snmp_err, &tmp_err_str); if (SNMPERR_AUTHENTICATION_FAILURE == snmp_err) { tmp_err_str = zbx_strdup(tmp_err_str, "Authentication failure (incorrect password, community, " "key or duplicate engineID)"); } zbx_snprintf(error, max_error_len, "Cannot connect to \"%s:%hu\": %s.", interface->addr, interface->port, tmp_err_str); zbx_free(tmp_err_str); ret = NETWORK_ERROR; } else if (STAT_TIMEOUT == status) { if (0 == got_vars) { zbx_snprintf(error, max_error_len, "Timeout while connecting to \"%s:%hu\".", interface->addr, interface->port); ret = NETWORK_ERROR; } else { zbx_snprintf(error, max_error_len, "Timeout while retrieving data from \"%s:%hu\".", interface->addr, interface->port); ret = NOTSUPPORTED; } } else { zbx_snprintf(error, max_error_len, "SNMP error: [%d]", status); ret = NOTSUPPORTED; } return ret; } static zbx_snmp_sess_t zbx_snmp_open_session(unsigned char snmp_version, const char *ip, unsigned short port, char *snmp_community, char *snmpv3_securityname, char *snmpv3_contextname, unsigned char snmpv3_securitylevel, unsigned char snmpv3_authprotocol, char *snmpv3_authpassphrase, unsigned char snmpv3_privprotocol, char *snmpv3_privpassphrase, char *error, size_t max_error_len, int timeout, const char *config_source_ip) { /* item snmpv3 privacy protocol */ /* SYNC WITH PHP! */ #define ITEM_SNMPV3_PRIVPROTOCOL_DES 0 #define ITEM_SNMPV3_PRIVPROTOCOL_AES128 1 #define ITEM_SNMPV3_PRIVPROTOCOL_AES192 2 #define ITEM_SNMPV3_PRIVPROTOCOL_AES256 3 #define ITEM_SNMPV3_PRIVPROTOCOL_AES192C 4 #define ITEM_SNMPV3_PRIVPROTOCOL_AES256C 5 struct snmp_session session; zbx_snmp_sess_t ssp = NULL; char addr[128]; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); snmp_sess_init(&session); /* Allow using sub-OIDs higher than MAX_INT, like in 'snmpwalk -Ir'. */ /* Disables the validation of varbind values against the MIB definition for the relevant OID. */ if (SNMPERR_SUCCESS != netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_CHECK_RANGE, 1)) { /* This error is not fatal and should never happen (see netsnmp_ds_set_boolean() implementation). */ /* Only items with sub-OIDs higher than MAX_INT will be unsupported. */ zabbix_log(LOG_LEVEL_WARNING, "cannot set \"DontCheckRange\" option for Net-SNMP"); } switch (snmp_version) { case ZBX_IF_SNMP_VERSION_1: session.version = SNMP_VERSION_1; break; case ZBX_IF_SNMP_VERSION_2: session.version = SNMP_VERSION_2c; break; case ZBX_IF_SNMP_VERSION_3: session.version = SNMP_VERSION_3; break; default: THIS_SHOULD_NEVER_HAPPEN; break; } session.timeout = timeout * 1000 * 1000; /* timeout of one attempt in microseconds */ /* (net-snmp default = 1 second) */ if (SUCCEED == zbx_is_ip4(ip)) zbx_snprintf(addr, sizeof(addr), "%s:%hu", ip, port); else zbx_snprintf(addr, sizeof(addr), "udp6:[%s]:%hu", ip, port); session.peername = addr; if (SNMP_VERSION_1 == session.version || SNMP_VERSION_2c == session.version) { session.community = (u_char *)snmp_community; session.community_len = strlen((char *)session.community); zabbix_log(LOG_LEVEL_DEBUG, "SNMP [%s@%s]", session.community, session.peername); } else if (SNMP_VERSION_3 == session.version) { /* set SNMPv3 user name */ session.securityName = snmpv3_securityname; session.securityNameLen = strlen(session.securityName); /* set SNMPv3 context if specified */ if ('\0' != *snmpv3_contextname) { session.contextName = snmpv3_contextname; session.contextNameLen = strlen(session.contextName); } /* set security level to authenticated, but not encrypted */ switch (snmpv3_securitylevel) { case ZBX_ITEM_SNMPV3_SECURITYLEVEL_NOAUTHNOPRIV: session.securityLevel = SNMP_SEC_LEVEL_NOAUTH; break; case ZBX_ITEM_SNMPV3_SECURITYLEVEL_AUTHNOPRIV: session.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV; if (FAIL == zbx_snmpv3_set_auth_protocol(snmpv3_authprotocol, &session)) { zbx_snprintf(error, max_error_len, "Unsupported authentication protocol [%d]", snmpv3_authprotocol); goto end; } session.securityAuthKeyLen = USM_AUTH_KU_LEN; if (SNMPERR_SUCCESS != generate_Ku(session.securityAuthProto, session.securityAuthProtoLen, (u_char *)snmpv3_authpassphrase, strlen(snmpv3_authpassphrase), session.securityAuthKey, &session.securityAuthKeyLen)) { zbx_strlcpy(error, "Error generating Ku from authentication pass phrase", max_error_len); goto end; } break; case ZBX_ITEM_SNMPV3_SECURITYLEVEL_AUTHPRIV: session.securityLevel = SNMP_SEC_LEVEL_AUTHPRIV; if (FAIL == zbx_snmpv3_set_auth_protocol(snmpv3_authprotocol, &session)) { zbx_snprintf(error, max_error_len, "Unsupported authentication protocol [%d]", snmpv3_authprotocol); goto end; } session.securityAuthKeyLen = USM_AUTH_KU_LEN; if (SNMPERR_SUCCESS != generate_Ku(session.securityAuthProto, session.securityAuthProtoLen, (u_char *)snmpv3_authpassphrase, strlen(snmpv3_authpassphrase), session.securityAuthKey, &session.securityAuthKeyLen)) { zbx_strlcpy(error, "Error generating Ku from authentication pass phrase", max_error_len); goto end; } switch (snmpv3_privprotocol) { #ifdef HAVE_NETSNMP_SESSION_DES case ITEM_SNMPV3_PRIVPROTOCOL_DES: /* set privacy protocol to DES */ session.securityPrivProto = usmDESPrivProtocol; session.securityPrivProtoLen = USM_PRIV_PROTO_DES_LEN; break; #endif case ITEM_SNMPV3_PRIVPROTOCOL_AES128: /* set privacy protocol to AES128 */ session.securityPrivProto = usmAESPrivProtocol; session.securityPrivProtoLen = USM_PRIV_PROTO_AES_LEN; break; #ifdef HAVE_NETSNMP_STRONG_PRIV case ITEM_SNMPV3_PRIVPROTOCOL_AES192: /* set privacy protocol to AES192 */ session.securityPrivProto = usmAES192PrivProtocol; session.securityPrivProtoLen = OID_LENGTH(usmAES192PrivProtocol); break; case ITEM_SNMPV3_PRIVPROTOCOL_AES256: /* set privacy protocol to AES256 */ session.securityPrivProto = usmAES256PrivProtocol; session.securityPrivProtoLen = OID_LENGTH(usmAES256PrivProtocol); break; case ITEM_SNMPV3_PRIVPROTOCOL_AES192C: /* set privacy protocol to AES192 (Cisco version) */ session.securityPrivProto = usmAES192CiscoPrivProtocol; session.securityPrivProtoLen = OID_LENGTH(usmAES192CiscoPrivProtocol); break; case ITEM_SNMPV3_PRIVPROTOCOL_AES256C: /* set privacy protocol to AES256 (Cisco version) */ session.securityPrivProto = usmAES256CiscoPrivProtocol; session.securityPrivProtoLen = OID_LENGTH(usmAES256CiscoPrivProtocol); break; #endif default: zbx_snprintf(error, max_error_len, "Unsupported privacy protocol [%d]", snmpv3_privprotocol); goto end; } session.securityPrivKeyLen = USM_PRIV_KU_LEN; if (SNMPERR_SUCCESS != generate_Ku(session.securityAuthProto, session.securityAuthProtoLen, (u_char *)snmpv3_privpassphrase, strlen(snmpv3_privpassphrase), session.securityPrivKey, &session.securityPrivKeyLen)) { zbx_strlcpy(error, "Error generating Ku from privacy pass phrase", max_error_len); goto end; } break; } zabbix_log(LOG_LEVEL_DEBUG, "SNMPv3 [%s@%s]", session.securityName, session.peername); } #ifdef HAVE_NETSNMP_SESSION_LOCALNAME if (NULL != config_source_ip) { /* In some cases specifying just local host (without local port) is not enough. We do */ /* not care about the port number though so we let the OS select one by specifying 0. */ /* See marc.info/?l=net-snmp-bugs&m=115624676507760 for details. */ static ZBX_THREAD_LOCAL char localname[64]; zbx_snprintf(localname, sizeof(localname), "%s:0", config_source_ip); session.localname = localname; } #endif SOCK_STARTUP; if (NULL == (ssp = snmp_sess_open(&session))) { SOCK_CLEANUP; zbx_strlcpy(error, "Cannot open SNMP session", max_error_len); } end: zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__); return ssp; #undef ITEM_SNMPV3_PRIVPROTOCOL_DES #undef ITEM_SNMPV3_PRIVPROTOCOL_AES128 #undef ITEM_SNMPV3_PRIVPROTOCOL_AES192 #undef ITEM_SNMPV3_PRIVPROTOCOL_AES256 #undef ITEM_SNMPV3_PRIVPROTOCOL_AES192C #undef ITEM_SNMPV3_PRIVPROTOCOL_AES256C } static void zbx_snmp_close_session(zbx_snmp_sess_t session) { zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); snmp_sess_close(session); SOCK_CLEANUP; zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__); } static char *zbx_sprint_asn_octet_str_dyn(const struct variable_list *var) { #define ZBX_MAC_ADDRESS_LEN 6 /* don't guess output format if length is equal to MAC address to avoid false positive UTF-8 */ if (var->type != ASN_OCTET_STR || NULL != memchr(var->val.string, '\0', var->val_len) || ZBX_MAC_ADDRESS_LEN == var->val_len) { return NULL; } char *strval_dyn = (char *)zbx_malloc(NULL, var->val_len + 1); memcpy(strval_dyn, var->val.string, var->val_len); strval_dyn[var->val_len] = '\0'; if (FAIL == zbx_is_ascii_printable(strval_dyn) || FAIL == zbx_is_utf8(strval_dyn)) zbx_free(strval_dyn); else zabbix_log(LOG_LEVEL_DEBUG, "%s() full value:'%s'", __func__, strval_dyn); return strval_dyn; #undef ZBX_MAC_ADDRESS_LEN } static char *zbx_snmp_get_octet_string(const struct variable_list *var, unsigned char *string_type, zbx_snmp_asn_octet_str_t snmp_asn_octet_str) { struct tree *subtree; const char *hint; char buffer[MAX_BUFFER_LEN]; char *strval_dyn = NULL; unsigned char type; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); /* find the subtree to get display hint */ subtree = get_tree(var->name, var->name_length, get_tree_head()); hint = (NULL != subtree ? subtree->hint : NULL); if (ZBX_ASN_OCTET_STR_UTF_8 == snmp_asn_octet_str && NULL == hint) { /* avoid conversion to Hex-STRING for valid UTF-8 strings without hints */ if (NULL != (strval_dyn = zbx_sprint_asn_octet_str_dyn(var))) { type = ZBX_SNMP_STR_ASCII; goto end; } } /* we will decide if we want the value from var->val or what snprint_value() returned later */ if (-1 == snprint_value(buffer, sizeof(buffer), var->name, var->name_length, var)) goto end; zabbix_log(LOG_LEVEL_DEBUG, "%s() full value:'%s' hint:'%s'", __func__, buffer, ZBX_NULL2STR(hint)); if (0 == strncmp(buffer, "Hex-STRING: ", 12)) { strval_dyn = zbx_strdup(strval_dyn, buffer + 12); type = ZBX_SNMP_STR_HEX; } else if (NULL != hint && 0 == strncmp(buffer, "STRING: ", 8)) { strval_dyn = zbx_strdup(strval_dyn, buffer + 8); type = ZBX_SNMP_STR_STRING; } else if (0 == strncmp(buffer, "OID: ", 5)) { strval_dyn = zbx_strdup(strval_dyn, buffer + 5); type = ZBX_SNMP_STR_OID; } else if (0 == strncmp(buffer, "BITS: ", 6)) { strval_dyn = zbx_strdup(strval_dyn, buffer + 6); type = ZBX_SNMP_STR_BITS; } else { /* snprint_value() escapes hintless ASCII strings, so */ /* we are copying the raw unescaped value in this case */ strval_dyn = (char *)zbx_malloc(strval_dyn, var->val_len + 1); memcpy(strval_dyn, var->val.string, var->val_len); strval_dyn[var->val_len] = '\0'; type = ZBX_SNMP_STR_ASCII; } end: if (NULL != string_type && NULL != strval_dyn) *string_type = type; zabbix_log(LOG_LEVEL_DEBUG, "End of %s():'%s'", __func__, ZBX_NULL2STR(strval_dyn)); return strval_dyn; } static int zbx_snmp_set_result(const struct variable_list *var, AGENT_RESULT *result, unsigned char *string_type, zbx_snmp_asn_octet_str_t snmp_asn_octet_str) { char *strval_dyn; int ret = SUCCEED; zabbix_log(LOG_LEVEL_DEBUG, "In %s() type:%d", __func__, (int)var->type); *string_type = ZBX_SNMP_STR_UNDEFINED; if (ASN_OCTET_STR == var->type || ASN_OBJECT_ID == var->type) { if (NULL == (strval_dyn = zbx_snmp_get_octet_string(var, string_type, snmp_asn_octet_str))) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot receive string value: out of memory.")); ret = NOTSUPPORTED; } else { zbx_set_agent_result_type(result, ITEM_VALUE_TYPE_TEXT, strval_dyn); zbx_free(strval_dyn); } } #ifdef OPAQUE_SPECIAL_TYPES else if (ASN_UINTEGER == var->type || ASN_COUNTER == var->type || ASN_OPAQUE_U64 == var->type || ASN_TIMETICKS == var->type || ASN_GAUGE == var->type) #else else if (ASN_UINTEGER == var->type || ASN_COUNTER == var->type || ASN_TIMETICKS == var->type || ASN_GAUGE == var->type) #endif { SET_UI64_RESULT(result, (unsigned long)*var->val.integer); } #ifdef OPAQUE_SPECIAL_TYPES else if (ASN_COUNTER64 == var->type || ASN_OPAQUE_COUNTER64 == var->type) #else else if (ASN_COUNTER64 == var->type) #endif { SET_UI64_RESULT(result, (((zbx_uint64_t)var->val.counter64->high) << 32) + (zbx_uint64_t)var->val.counter64->low); } #ifdef OPAQUE_SPECIAL_TYPES else if (ASN_INTEGER == var->type || ASN_OPAQUE_I64 == var->type) #else else if (ASN_INTEGER == var->type) #endif { char buffer[21]; zbx_snprintf(buffer, sizeof(buffer), "%ld", *var->val.integer); zbx_set_agent_result_type(result, ITEM_VALUE_TYPE_TEXT, buffer); } #ifdef OPAQUE_SPECIAL_TYPES else if (ASN_OPAQUE_FLOAT == var->type) { SET_DBL_RESULT(result, *var->val.floatVal); } else if (ASN_OPAQUE_DOUBLE == var->type) { SET_DBL_RESULT(result, *var->val.doubleVal); } #endif else if (ASN_IPADDRESS == var->type) { SET_STR_RESULT(result, zbx_dsprintf(NULL, "%u.%u.%u.%u", (unsigned int)var->val.string[0], (unsigned int)var->val.string[1], (unsigned int)var->val.string[2], (unsigned int)var->val.string[3])); } else if (ASN_NULL == var->type) { SET_STR_RESULT(result, zbx_strdup(NULL, "NULL")); } else { SET_MSG_RESULT(result, zbx_get_snmp_type_error(var->type)); ret = NOTSUPPORTED; } zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } static void zbx_snmp_dump_oid(char *buffer, size_t buffer_len, const oid *objid, size_t objid_len) { size_t offset = 0; *buffer = '\0'; for (size_t i = 0; i < objid_len; i++) offset += zbx_snprintf(buffer + offset, buffer_len - offset, ".%lu", (unsigned long)objid[i]); } #define ZBX_OID_INDEX_STRING 0 #define ZBX_OID_INDEX_NUMERIC 1 static int zbx_snmp_print_oid(char *buffer, size_t buffer_len, const oid *objid, size_t objid_len, int format) { if (SNMPERR_SUCCESS != netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_BREAKDOWN_OIDS, format)) { zabbix_log(LOG_LEVEL_WARNING, "cannot set \"dontBreakdownOids\" option to %d for Net-SNMP", format); return -1; } return snprint_objid(buffer, buffer_len, objid, objid_len); } static int zbx_snmp_choose_index(char *buffer, size_t buffer_len, const oid *objid, size_t objid_len, size_t root_string_len, size_t root_numeric_len, char *root_oid) { oid parsed_oid[MAX_OID_LEN]; size_t parsed_oid_len = MAX_OID_LEN; char printed_oid[MAX_STRING_LEN]; char *printed_oid_escaped; /**************************************************************************************************************/ /* */ /* When we are providing a value for {#SNMPINDEX}, we would like to provide a pretty value. This is only a */ /* concern for OIDs with string indices. For instance, suppose we are walking the following OID: */ /* */ /* SNMP-VIEW-BASED-ACM-MIB::vacmGroupName */ /* */ /* Suppose also that we are currently looking at this OID: */ /* */ /* SNMP-VIEW-BASED-ACM-MIB::vacmGroupName.3."authOnlyUser" */ /* */ /* Then, we would like to provide {#SNMPINDEX} with this value: */ /* */ /* 3."authOnlyUser" */ /* */ /* An alternative approach would be to provide {#SNMPINDEX} with numeric value. While it is equivalent to the */ /* string representation above, the string representation is more readable and thus more useful to users: */ /* */ /* 3.12.97.117.116.104.79.110.108.121.85.115.101.114 */ /* */ /* Here, 12 is the length of "authOnlyUser" and the rest is the string encoding using ASCII characters. */ /* */ /* There are two problems with always providing {#SNMPINDEX} that has an index representation as a string. */ /* */ /* The first problem is indices of type InetAddress. The Net-SNMP library has code for pretty-printing IP */ /* addresses, but no way to parse them back. As an example, consider the following OID: */ /* */ /* .1.3.6.1.2.1.4.34.1.4.1.4.192.168.3.255 */ /* */ /* Its pretty representation is like this: */ /* */ /* IP-MIB::ipAddressType.ipv4."192.168.3.255" */ /* */ /* However, when trying to parse it, it turns into this OID: */ /* */ /* .1.3.6.1.2.1.4.34.1.4.1.13.49.57.50.46.49.54.56.46.51.46.50.53.53 */ /* */ /* Apparently, this is different than the original. */ /* */ /* The second problem is indices of type OCTET STRING, which might contain unprintable characters: */ /* */ /* 1.3.6.1.2.1.17.4.3.1.1.0.0.240.122.113.21 */ /* */ /* Its pretty representation is like this (note the single quotes which stand for a fixed-length string): */ /* */ /* BRIDGE-MIB::dot1dTpFdbAddress.'...zq.' */ /* */ /* Here, '...zq.' stands for 0.0.240.122.113.21, where only 'z' (122) and 'q' (113) are printable. */ /* */ /* Apparently, this cannot be turned back into the numeric representation. */ /* */ /* So what we try to do is first print it pretty. If there is no string-looking index, return it as output. */ /* If there is such an index, we check that it can be parsed and that the result is the same as the original. */ /* */ /**************************************************************************************************************/ if (-1 == zbx_snmp_print_oid(printed_oid, sizeof(printed_oid), objid, objid_len, ZBX_OID_INDEX_STRING)) { zabbix_log(LOG_LEVEL_DEBUG, "%s(): cannot print OID with string indices", __func__); goto numeric; } if (NULL == strchr(printed_oid, '"') && NULL == strchr(printed_oid, '\'')) { if (0 != strncmp(printed_oid, root_oid, strlen(root_oid))) { size_t offset = 0; char *sep; if (NULL != (sep = strstr(printed_oid, "::"))) offset = sep - printed_oid + 2; zbx_strlcpy(buffer, printed_oid + offset, buffer_len); } else zbx_strlcpy(buffer, printed_oid + root_string_len + 1, buffer_len); return SUCCEED; } printed_oid_escaped = zbx_dyn_escape_string(printed_oid, "\\"); if (NULL == snmp_parse_oid(printed_oid_escaped, parsed_oid, &parsed_oid_len)) { zabbix_log(LOG_LEVEL_DEBUG, "%s(): cannot parse OID '%s'", __func__, printed_oid_escaped); zbx_free(printed_oid_escaped); goto numeric; } zbx_free(printed_oid_escaped); if (parsed_oid_len == objid_len && 0 == memcmp(parsed_oid, objid, parsed_oid_len * sizeof(oid))) { zbx_strlcpy(buffer, printed_oid + root_string_len + 1, buffer_len); return SUCCEED; } numeric: if (-1 == zbx_snmp_print_oid(printed_oid, sizeof(printed_oid), objid, objid_len, ZBX_OID_INDEX_NUMERIC)) { zabbix_log(LOG_LEVEL_DEBUG, "%s(): cannot print OID with numeric indices", __func__); return FAIL; } zbx_strlcpy(buffer, printed_oid + root_numeric_len + 1, buffer_len); return SUCCEED; } /****************************************************************************** * * * Functions for detecting looping in SNMP OID sequence using hashset * * * * Once there is a possibility of looping we start putting OIDs into hashset. * * We do it until a duplicate OID shows up or ZBX_OIDS_MAX_NUM OIDs have been * * collected. * * * * The hashset key is array of elements of type 'oid'. Element 0 holds the * * number of OID components (sub-OIDs), element 1 and so on - OID components * * themselves. * * * * OIDs may contain up to 128 sub-OIDs, so 1 byte is sufficient to keep the * * number of them. On the other hand, sub-OIDs are of type 'oid' which can be * * defined in NetSNMP as 'uint8_t' or 'u_long'. Sub-OIDs are compared as * * numbers, so some platforms may require they to be properly aligned in * * memory. To ensure proper alignment we keep number of elements in element 0 * * instead of using a separate structure element for it. * * * ******************************************************************************/ static zbx_hash_t __oids_seen_key_hash(const void *data) { const oid *key = (const oid *)data; return ZBX_DEFAULT_HASH_ALGO(key, (key[0] + 1) * sizeof(oid), ZBX_DEFAULT_HASH_SEED); } static int __oids_seen_key_compare(const void *d1, const void *d2) { const oid *k1 = (const oid *)d1; const oid *k2 = (const oid *)d2; if (d1 == d2) return 0; return snmp_oid_compare(k1 + 1, k1[0], k2 + 1, k2[0]); } static void zbx_detect_loop_init(zbx_hashset_t *hs) { #define ZBX_OIDS_SEEN_INIT_SIZE 500 /* minimum initial number of slots in hashset */ zbx_hashset_create(hs, ZBX_OIDS_SEEN_INIT_SIZE, __oids_seen_key_hash, __oids_seen_key_compare); #undef ZBX_OIDS_SEEN_INIT_SIZE } static int zbx_oid_is_new(zbx_hashset_t *hs, size_t root_len, const oid *p_oid, size_t oid_len) { #define ZBX_OIDS_MAX_NUM 1000000 /* max number of OIDs to store for checking duplicates */ const oid *var_oid; /* points to the first element in the variable part */ size_t var_len; /* number of elements in the variable part */ oid oid_k[MAX_OID_LEN + 1]; /* array for constructing a hashset key */ /* OIDs share a common initial part. Save space by storing only the variable part. */ var_oid = p_oid + root_len; var_len = oid_len - root_len; if (ZBX_OIDS_MAX_NUM == hs->num_data) return FAIL; oid_k[0] = var_len; memcpy(oid_k + 1, var_oid, var_len * sizeof(oid)); if (NULL != zbx_hashset_search(hs, oid_k)) return FAIL; /* OID already seen */ if (NULL != zbx_hashset_insert(hs, oid_k, (var_len + 1) * sizeof(oid))) return SUCCEED; /* new OID */ THIS_SHOULD_NEVER_HAPPEN; return FAIL; /* hashset fail */ #undef ZBX_OIDS_MAX_NUM } /****************************************************************************** * * * Purpose: retrieves information by walking OID tree * * * * Parameters: ssp - [IN] SNMP session handle * * item - [IN] configuration of Zabbix item * * snmp_oid - [IN] OID of table with values of interest * * error - [OUT] buffer to store error message * * max_error_len - [IN] maximum error message length * * max_succeed - [OUT] value of "max_repetitions" that succeeded* * min_fail - [OUT] value of "max_repetitions" that failed * * max_vars - [IN] suggested value of "max_repetitions" * * bulk - [IN] whether GetBulkRequest-PDU should be used * * walk_cb_func - [IN] callback function to process discovered * * OIDs and their values * * walk_cb_arg - [IN] argument to pass to callback function * * * * Return value: NOTSUPPORTED - OID does not exist, any other critical error * * NETWORK_ERROR - recoverable network error * * CONFIG_ERROR - item configuration error * * SUCCEED - if function successfully completed * * * ******************************************************************************/ static int zbx_snmp_walk(zbx_snmp_sess_t ssp, const zbx_dc_item_t *item, const char *snmp_oid, char *error, size_t max_error_len, int *max_succeed, int *min_fail, int max_vars, int bulk, zbx_snmp_walk_cb_func walk_cb_func, void *walk_cb_arg) { struct snmp_pdu *pdu, *response; oid anOID[MAX_OID_LEN], rootOID[MAX_OID_LEN]; size_t anOID_len = MAX_OID_LEN, rootOID_len = MAX_OID_LEN, root_string_len, root_numeric_len; char oid_index[MAX_STRING_LEN], root_oid[MAX_STRING_LEN]; struct variable_list *var; int status, level, running, num_vars, got_vars = 0, check_oid_increase = 1, ret = SUCCEED; AGENT_RESULT snmp_result; zbx_hashset_t oids_seen; struct snmp_session *ss; zabbix_log(LOG_LEVEL_DEBUG, "In %s() type:%d OID:'%s' bulk:%d", __func__, (int)item->type, snmp_oid, bulk); if (ZBX_IF_SNMP_VERSION_1 == item->snmp_version) /* GetBulkRequest-PDU available since SNMPv2 */ bulk = SNMP_BULK_DISABLED; /* create OID from string */ if (NULL == snmp_parse_oid(snmp_oid, rootOID, &rootOID_len)) { zbx_snprintf(error, max_error_len, "snmp_parse_oid(): cannot parse OID \"%s\".", snmp_oid); ret = CONFIG_ERROR; goto out; } if (-1 == zbx_snmp_print_oid(oid_index, sizeof(oid_index), rootOID, rootOID_len, ZBX_OID_INDEX_STRING)) { zbx_snprintf(error, max_error_len, "zbx_snmp_print_oid(): cannot print OID \"%s\" with string indices.", snmp_oid); ret = CONFIG_ERROR; goto out; } root_string_len = strlen(oid_index); if (-1 == zbx_snmp_print_oid(oid_index, sizeof(oid_index), rootOID, rootOID_len, ZBX_OID_INDEX_NUMERIC)) { zbx_snprintf(error, max_error_len, "zbx_snmp_print_oid(): cannot print OID \"%s\"" " with numeric indices.", snmp_oid); ret = CONFIG_ERROR; goto out; } root_numeric_len = strlen(oid_index); zbx_strlcpy(root_oid, oid_index, sizeof(root_oid)); /* copy rootOID to anOID */ memcpy(anOID, rootOID, rootOID_len * sizeof(oid)); anOID_len = rootOID_len; /* initialize variables */ level = 0; running = 1; while (1 == running) { /* create PDU */ if (NULL == (pdu = snmp_pdu_create(SNMP_BULK_ENABLED == bulk ? SNMP_MSG_GETBULK : SNMP_MSG_GETNEXT))) { zbx_strlcpy(error, "snmp_pdu_create(): cannot create PDU object.", max_error_len); ret = CONFIG_ERROR; break; } if (NULL == snmp_add_null_var(pdu, anOID, anOID_len)) /* add OID as variable to PDU */ { zbx_strlcpy(error, "snmp_add_null_var(): cannot add null variable.", max_error_len); ret = CONFIG_ERROR; snmp_free_pdu(pdu); break; } if (SNMP_BULK_ENABLED == bulk) { pdu->non_repeaters = 0; pdu->max_repetitions = max_vars; } ss = snmp_sess_session(ssp); ss->retries = (0 == bulk || (1 == max_vars && 0 == level) ? 1 : 0); /* communicate with agent */ status = snmp_sess_synch_response(ssp, pdu, &response); zabbix_log(LOG_LEVEL_DEBUG, "%s() snmp_sess_synch_response() status:%d s_snmp_errno:%d errstat:%ld" " max_vars:%d", __func__, status, ss->s_snmp_errno, NULL == response ? (long)-1 : response->errstat, max_vars); if (1 < max_vars && ((STAT_SUCCESS == status && SNMP_ERR_TOOBIG == response->errstat) || STAT_TIMEOUT == status)) { /* The logic of iteratively reducing request size here is the same as in function */ /* zbx_snmp_get_values(). Please refer to the description there for explanation. */ reduce_max_vars: if (*min_fail > max_vars) *min_fail = max_vars; if (0 == level) { max_vars /= 2; } else if (1 == level) { max_vars = 1; } level++; goto next; } else if (STAT_SUCCESS != status || SNMP_ERR_NOERROR != response->errstat) { if (1 >= level && 1 < max_vars) goto reduce_max_vars; ret = zbx_get_snmp_response_error(ssp, &item->interface, status, response, error, max_error_len, got_vars); running = 0; goto next; } if (NULL == response->variables) { if (1 >= level && 1 < max_vars) goto reduce_max_vars; zbx_strlcpy(error, "No values received.", max_error_len); ret = NOTSUPPORTED; running = 0; goto next; } /* process response */ for (num_vars = 0, var = response->variables; NULL != var; num_vars++, var = var->next_variable) { char **str_res; unsigned char val_type; got_vars = 1; /* verify if we are in the same subtree */ if (SNMP_ENDOFMIBVIEW == var->type || var->name_length < rootOID_len || 0 != memcmp(rootOID, var->name, rootOID_len * sizeof(oid))) { /* reached the end or past this subtree */ running = 0; break; } else if (SNMP_NOSUCHOBJECT != var->type && SNMP_NOSUCHINSTANCE != var->type) { /* not an exception value */ if (1 == check_oid_increase) /* typical case */ { int res; /* normally devices return OIDs in increasing order, */ /* snmp_oid_compare() will return -1 in this case */ if (-1 != (res = snmp_oid_compare(anOID, anOID_len, var->name, var->name_length))) { if (0 == res) /* got the same OID */ { zbx_strlcpy(error, "OID not changing.", max_error_len); ret = NOTSUPPORTED; running = 0; break; } else /* 1 == res */ { /* OID decreased. Disable further checks of increasing */ /* and set up a protection against endless looping. */ check_oid_increase = 0; zbx_detect_loop_init(&oids_seen); } } } if (0 == check_oid_increase && FAIL == zbx_oid_is_new(&oids_seen, rootOID_len, var->name, var->name_length)) { zbx_strlcpy(error, "OID loop detected or too many OIDs.", max_error_len); ret = NOTSUPPORTED; running = 0; break; } if (SUCCEED != zbx_snmp_choose_index(oid_index, sizeof(oid_index), var->name, var->name_length, root_string_len, root_numeric_len, root_oid)) { zbx_snprintf(error, max_error_len, "zbx_snmp_choose_index():" " cannot choose appropriate index while walking for" " OID \"%s\".", snmp_oid); ret = NOTSUPPORTED; running = 0; break; } str_res = NULL; zbx_init_agent_result(&snmp_result); if (SUCCEED == zbx_snmp_set_result(var, &snmp_result, &val_type, ZBX_ASN_OCTET_STR_HEX)) { if (ZBX_ISSET_TEXT(&snmp_result) && ZBX_SNMP_STR_HEX == val_type) zbx_remove_chars(snmp_result.text, "\r\n"); str_res = ZBX_GET_STR_RESULT(&snmp_result); } if (NULL == str_res) { char **msg; msg = ZBX_GET_MSG_RESULT(&snmp_result); zabbix_log(LOG_LEVEL_DEBUG, "cannot get index '%s' string value: %s", oid_index, NULL != msg && NULL != *msg ? *msg : "(null)"); } else walk_cb_func(walk_cb_arg, snmp_oid, oid_index, snmp_result.str); zbx_free_agent_result(&snmp_result); /* go to next variable */ memcpy((char *)anOID, (char *)var->name, var->name_length * sizeof(oid)); anOID_len = var->name_length; } else { /* an exception value, so stop */ char *errmsg; errmsg = zbx_get_snmp_type_error(var->type); zbx_strlcpy(error, errmsg, max_error_len); zbx_free(errmsg); ret = NOTSUPPORTED; running = 0; break; } } if (*max_succeed < num_vars) *max_succeed = num_vars; next: if (NULL != response) snmp_free_pdu(response); } if (0 == check_oid_increase) zbx_hashset_destroy(&oids_seen); out: zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } #undef ZBX_OID_INDEX_STRING #undef ZBX_OID_INDEX_NUMERIC static int zbx_snmp_get_values(zbx_snmp_sess_t ssp, const zbx_dc_item_t *items, char oids[][ZBX_ITEM_SNMP_OID_LEN_MAX], AGENT_RESULT *results, int *errcodes, unsigned char *query_and_ignore_type, int num, int level, char *error, size_t max_error_len, int *max_succeed, int *min_fail, unsigned char poller_type) { int status, ret = SUCCEED, mapping_num = 0; int mapping[ZBX_MAX_SNMP_ITEMS]; oid parsed_oids[ZBX_MAX_SNMP_ITEMS][MAX_OID_LEN]; size_t parsed_oid_lens[ZBX_MAX_SNMP_ITEMS]; struct snmp_pdu *pdu, *response; struct snmp_session *ss; zabbix_log(LOG_LEVEL_DEBUG, "In %s() num:%d level:%d", __func__, num, level); if (NULL == (pdu = snmp_pdu_create(SNMP_MSG_GET))) { zbx_strlcpy(error, "snmp_pdu_create(): cannot create PDU object.", max_error_len); ret = CONFIG_ERROR; goto out; } for (int i = 0; i < num; i++) { if (SUCCEED != errcodes[i]) continue; if (NULL != query_and_ignore_type && 0 == query_and_ignore_type[i]) continue; parsed_oid_lens[i] = MAX_OID_LEN; if (NULL == snmp_parse_oid(oids[i], parsed_oids[i], &parsed_oid_lens[i])) { SET_MSG_RESULT(&results[i], zbx_dsprintf(NULL, "snmp_parse_oid(): cannot parse OID \"%s\".", oids[i])); errcodes[i] = CONFIG_ERROR; continue; } if (NULL == snmp_add_null_var(pdu, parsed_oids[i], parsed_oid_lens[i])) { SET_MSG_RESULT(&results[i], zbx_strdup(NULL, "snmp_add_null_var(): cannot add null variable.")); errcodes[i] = CONFIG_ERROR; continue; } mapping[mapping_num++] = i; } if (0 == mapping_num) { snmp_free_pdu(pdu); goto out; } ss = snmp_sess_session(ssp); ss->retries = (1 == mapping_num && 0 == level && ZBX_POLLER_TYPE_UNREACHABLE != poller_type ? 1 : 0); retry: status = snmp_sess_synch_response(ssp, pdu, &response); zabbix_log(LOG_LEVEL_DEBUG, "%s() snmp_sess_synch_response() status:%d s_snmp_errno:%d errstat:%ld " "mapping_num:%d", __func__, status, ss->s_snmp_errno, NULL == response ? (long)-1 : response->errstat, mapping_num); if (STAT_SUCCESS == status && SNMP_ERR_NOERROR == response->errstat) { struct variable_list *var; int i; for (i = 0, var = response->variables;; i++, var = var->next_variable) { /* check that response variable binding matches the request variable binding */ if (i == mapping_num) { if (NULL != var) { zabbix_log(LOG_LEVEL_WARNING, "SNMP response from host \"%s\" contains" " too many variable bindings", items[0].host.host); if (1 != mapping_num) /* give device a chance to handle a smaller request */ goto halve; zbx_strlcpy(error, "Invalid SNMP response: too many variable bindings.", max_error_len); ret = NOTSUPPORTED; } break; } if (NULL == var) { zabbix_log(LOG_LEVEL_WARNING, "SNMP response from host \"%s\" contains" " too few variable bindings", items[0].host.host); if (1 != mapping_num) /* give device a chance to handle a smaller request */ goto halve; zbx_strlcpy(error, "Invalid SNMP response: too few variable bindings.", max_error_len); ret = NOTSUPPORTED; break; } int j = mapping[i]; if (parsed_oid_lens[j] != var->name_length || 0 != memcmp(parsed_oids[j], var->name, parsed_oid_lens[j] * sizeof(oid))) { char sent_oid[ZBX_ITEM_SNMP_OID_LEN_MAX], received_oid[ZBX_ITEM_SNMP_OID_LEN_MAX]; zbx_snmp_dump_oid(sent_oid, sizeof(sent_oid), parsed_oids[j], parsed_oid_lens[j]); zbx_snmp_dump_oid(received_oid, sizeof(received_oid), var->name, var->name_length); if (1 != mapping_num) { zabbix_log(LOG_LEVEL_WARNING, "SNMP response from host \"%s\" contains" " variable bindings that do not match the request:" " sent \"%s\", received \"%s\"", items[0].host.host, sent_oid, received_oid); goto halve; /* give device a chance to handle a smaller request */ } else { zabbix_log(LOG_LEVEL_DEBUG, "SNMP response from host \"%s\" contains" " variable bindings that do not match the request:" " sent \"%s\", received \"%s\"", items[0].host.host, sent_oid, received_oid); } } /* process received data */ unsigned char val_type; if (NULL != query_and_ignore_type && 1 == query_and_ignore_type[j]) (void)zbx_snmp_set_result(var, &results[j], &val_type, ZBX_ASN_OCTET_STR_HEX); else errcodes[j] = zbx_snmp_set_result(var, &results[j], &val_type, ZBX_ASN_OCTET_STR_HEX); if (ZBX_ISSET_TEXT(&results[j]) && ZBX_SNMP_STR_HEX == val_type) zbx_remove_chars(results[j].text, "\r\n"); } if (SUCCEED == ret) { if (*max_succeed < mapping_num) *max_succeed = mapping_num; } /* min_fail value is updated when bulk request is halved in the case of failure */ } else if (STAT_SUCCESS == status && SNMP_ERR_NOSUCHNAME == response->errstat && 0 != response->errindex) { /* If a request PDU contains a bad variable, the specified behavior is different between SNMPv1 and */ /* later versions. In SNMPv1, the whole PDU is rejected and "response->errindex" is set to indicate */ /* the bad variable. In SNMPv2 and later, the SNMP agent processes the PDU by filling values for the */ /* known variables and marking unknown variables individually in the variable binding list. However, */ /* SNMPv2 allows SNMPv1 behavior, too. So regardless of the SNMP version used, if we get this error, */ /* then we fix the PDU by removing the bad variable and retry the request. */ int i = response->errindex - 1; if (0 > i || i >= mapping_num) { zabbix_log(LOG_LEVEL_WARNING, "SNMP response from host \"%s\" contains" " an out of bounds error index: %ld", items[0].host.host, response->errindex); zbx_strlcpy(error, "Invalid SNMP response: error index out of bounds.", max_error_len); ret = NOTSUPPORTED; goto exit; } int j = mapping[i]; zabbix_log(LOG_LEVEL_DEBUG, "%s() snmp_sess_synch_response() errindex:%ld OID:'%s'", __func__, response->errindex, oids[j]); if (NULL == query_and_ignore_type || 0 == query_and_ignore_type[j]) { errcodes[j] = zbx_get_snmp_response_error(ssp, &items[0].interface, status, response, error, max_error_len, 0); SET_MSG_RESULT(&results[j], zbx_strdup(NULL, error)); *error = '\0'; } if (1 < mapping_num) { if (NULL != (pdu = snmp_fix_pdu(response, SNMP_MSG_GET))) { memmove(mapping + i, mapping + i + 1, sizeof(int) * (mapping_num - i - 1)); mapping_num--; snmp_free_pdu(response); goto retry; } else { zbx_strlcpy(error, "snmp_fix_pdu(): cannot fix PDU object.", max_error_len); ret = NOTSUPPORTED; } } } else if (1 < mapping_num && ((STAT_SUCCESS == status && SNMP_ERR_TOOBIG == response->errstat) || STAT_TIMEOUT == status || (STAT_ERROR == status && SNMPERR_TOO_LONG == ss->s_snmp_errno))) { /* Since we are trying to obtain multiple values from the SNMP agent, the response that it has to */ /* generate might be too big. It seems to be required by the SNMP standard that in such cases the */ /* error status should be set to "tooBig(1)". However, some devices simply do not respond to such */ /* queries and we get a timeout. Moreover, some devices exhibit both behaviors - they either send */ /* "tooBig(1)" or do not respond at all. So what we do is halve the number of variables to query - */ /* it should work in the vast majority of cases, because, since we are now querying "num" values, */ /* we know that querying "num/2" values succeeded previously. The case where it can still fail due */ /* to exceeded maximum response size is if we are now querying values that are unusually large. So */ /* if querying with half the number of the last values does not work either, we resort to querying */ /* values one by one, and the next time configuration cache gives us items to query, it will give */ /* us less. */ /* The explanation above is for the first two conditions. The third condition comes from SNMPv3, */ /* where the size of the request that we are trying to send exceeds device's "msgMaxSize" limit. */ halve: if (*min_fail > mapping_num) *min_fail = mapping_num; if (0 == level) { /* halve the number of items */ ret = zbx_snmp_get_values(ssp, items, oids, results, errcodes, query_and_ignore_type, num / 2, level + 1, error, max_error_len, max_succeed, min_fail, poller_type); if (SUCCEED != ret) goto exit; int base = num / 2; ret = zbx_snmp_get_values(ssp, items + base, oids + base, results + base, errcodes + base, NULL == query_and_ignore_type ? NULL : query_and_ignore_type + base, num - base, level + 1, error, max_error_len, max_succeed, min_fail, poller_type); } else if (1 == level) { /* resort to querying items one by one */ for (int i = 0; i < num; i++) { if (SUCCEED != errcodes[i]) continue; ret = zbx_snmp_get_values(ssp, items + i, oids + i, results + i, errcodes + i, NULL == query_and_ignore_type ? NULL : query_and_ignore_type + i, 1, level + 1, error, max_error_len, max_succeed, min_fail, poller_type); if (SUCCEED != ret) goto exit; } } } else { if (1 <= level) goto halve; ret = zbx_get_snmp_response_error(ssp, &items[0].interface, status, response, error, max_error_len, 0); } exit: if (NULL != response) snmp_free_pdu(response); out: zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } /****************************************************************************** * * * Purpose: translates well-known object identifiers into numeric form * * * ******************************************************************************/ static void zbx_snmp_translate(char *oid_translated, const char *snmp_oid, size_t max_oid_len) { typedef struct { const size_t sz; const char *mib; const char *replace; } zbx_mib_norm_t; #define LEN_STR(x) ZBX_CONST_STRLEN(x), x static ZBX_THREAD_LOCAL zbx_mib_norm_t mibs[] = { /* the most popular items first */ {LEN_STR("ifDescr"), ".1.3.6.1.2.1.2.2.1.2"}, {LEN_STR("ifInOctets"), ".1.3.6.1.2.1.2.2.1.10"}, {LEN_STR("ifOutOctets"), ".1.3.6.1.2.1.2.2.1.16"}, {LEN_STR("ifAdminStatus"), ".1.3.6.1.2.1.2.2.1.7"}, {LEN_STR("ifOperStatus"), ".1.3.6.1.2.1.2.2.1.8"}, {LEN_STR("ifIndex"), ".1.3.6.1.2.1.2.2.1.1"}, {LEN_STR("ifType"), ".1.3.6.1.2.1.2.2.1.3"}, {LEN_STR("ifMtu"), ".1.3.6.1.2.1.2.2.1.4"}, {LEN_STR("ifSpeed"), ".1.3.6.1.2.1.2.2.1.5"}, {LEN_STR("ifPhysAddress"), ".1.3.6.1.2.1.2.2.1.6"}, {LEN_STR("ifInUcastPkts"), ".1.3.6.1.2.1.2.2.1.11"}, {LEN_STR("ifInNUcastPkts"), ".1.3.6.1.2.1.2.2.1.12"}, {LEN_STR("ifInDiscards"), ".1.3.6.1.2.1.2.2.1.13"}, {LEN_STR("ifInErrors"), ".1.3.6.1.2.1.2.2.1.14"}, {LEN_STR("ifInUnknownProtos"), ".1.3.6.1.2.1.2.2.1.15"}, {LEN_STR("ifOutUcastPkts"), ".1.3.6.1.2.1.2.2.1.17"}, {LEN_STR("ifOutNUcastPkts"), ".1.3.6.1.2.1.2.2.1.18"}, {LEN_STR("ifOutDiscards"), ".1.3.6.1.2.1.2.2.1.19"}, {LEN_STR("ifOutErrors"), ".1.3.6.1.2.1.2.2.1.20"}, {LEN_STR("ifOutQLen"), ".1.3.6.1.2.1.2.2.1.21"}, {0} }; #undef LEN_STR int found = 0; zabbix_log(LOG_LEVEL_DEBUG, "In %s() OID:'%s'", __func__, snmp_oid); for (int i = 0; 0 != mibs[i].sz; i++) { if (0 == strncmp(mibs[i].mib, snmp_oid, mibs[i].sz)) { found = 1; zbx_snprintf(oid_translated, max_oid_len, "%s%s", mibs[i].replace, snmp_oid + mibs[i].sz); break; } } if (0 == found) zbx_strlcpy(oid_translated, snmp_oid, max_oid_len); zabbix_log(LOG_LEVEL_DEBUG, "End of %s() oid_translated:'%s'", __func__, oid_translated); } /* discovered SNMP object, identified by its index */ typedef struct { /* object index returned by zbx_snmp_walk */ char *index; /* an array of OID values stored in the same order as defined in OID key */ char **values; } zbx_snmp_dobject_t; ZBX_PTR_VECTOR_DECL(snmp_dobject_ptr, zbx_snmp_dobject_t*) ZBX_PTR_VECTOR_IMPL(snmp_dobject_ptr, zbx_snmp_dobject_t*) /* helper data structure used by snmp discovery */ typedef struct { /* index of OID being currently processed (walked) */ int num; /* discovered SNMP objects */ zbx_hashset_t objects; /* index (order) of discovered SNMP objects */ zbx_vector_snmp_dobject_ptr_t index; /* request data structure used to parse discovery OID key */ AGENT_REQUEST request; } zbx_snmp_ddata_t; /* discovery objects hashset support */ static zbx_hash_t zbx_snmp_dobject_hash(const void *data) { const char *index = *(const char **)data; return ZBX_DEFAULT_STRING_HASH_ALGO(index, strlen(index), ZBX_DEFAULT_HASH_SEED); } static int zbx_snmp_dobject_compare(const void *d1, const void *d2) { const char *i1 = *(const char **)d1; const char *i2 = *(const char **)d2; return strcmp(i1, i2); } /****************************************************************************** * * * Purpose: initializes snmp discovery data object * * * * Parameters: data - [IN] snmp discovery data object * * key - [IN] discovery OID key * * error - [OUT] buffer to store error message * * max_error_len - [IN] maximum error message length * * * * Return value: CONFIG_ERROR - OID key configuration error * * SUCCEED - if function successfully completed * * * ******************************************************************************/ static int zbx_snmp_ddata_init(zbx_snmp_ddata_t *data, const char *key, char *error, size_t max_error_len) { int ret = CONFIG_ERROR; zbx_init_agent_request(&data->request); if (SUCCEED != zbx_parse_item_key(key, &data->request)) { zbx_strlcpy(error, "Invalid SNMP OID: cannot parse expression.", max_error_len); goto out; } if (0 == data->request.nparam || 0 != (data->request.nparam & 1)) { zbx_strlcpy(error, "Invalid SNMP OID: pairs of macro and OID are expected.", max_error_len); goto out; } for (int i = 0; i < data->request.nparam; i += 2) { if (SUCCEED != zbx_is_discovery_macro(data->request.params[i])) { zbx_snprintf(error, max_error_len, "Invalid SNMP OID: macro \"%s\" is invalid", data->request.params[i]); goto out; } if (0 == strcmp(data->request.params[i], "{#SNMPINDEX}")) { zbx_strlcpy(error, "Invalid SNMP OID: macro \"{#SNMPINDEX}\" is not allowed.", max_error_len); goto out; } } for (int i = 2; i < data->request.nparam; i += 2) { for (int j = 0; j < i; j += 2) { if (0 == strcmp(data->request.params[i], data->request.params[j])) { zbx_strlcpy(error, "Invalid SNMP OID: unique macros are expected.", max_error_len); goto out; } } } zbx_hashset_create(&data->objects, 10, zbx_snmp_dobject_hash, zbx_snmp_dobject_compare); zbx_vector_snmp_dobject_ptr_create(&data->index); ret = SUCCEED; out: if (SUCCEED != ret) zbx_free_agent_request(&data->request); return ret; } /****************************************************************************** * * * Purpose: releases data allocated by snmp discovery * * * * Parameters: data - [IN] snmp discovery data object * * * ******************************************************************************/ static void zbx_snmp_ddata_clean(zbx_snmp_ddata_t *data) { zbx_hashset_iter_t iter; zbx_snmp_dobject_t *obj; zbx_vector_snmp_dobject_ptr_destroy(&data->index); zbx_hashset_iter_reset(&data->objects, &iter); while (NULL != (obj = (zbx_snmp_dobject_t *)zbx_hashset_iter_next(&iter))) { for (int i = 0; i < data->request.nparam / 2; i++) zbx_free(obj->values[i]); zbx_free(obj->index); zbx_free(obj->values); } zbx_hashset_destroy(&data->objects); zbx_free_agent_request(&data->request); } static void zbx_snmp_walk_discovery_cb(void *arg, const char *snmp_oid, const char *index, const char *value) { zbx_snmp_ddata_t *data = (zbx_snmp_ddata_t *)arg; zbx_snmp_dobject_t *obj; ZBX_UNUSED(snmp_oid); if (NULL == (obj = (zbx_snmp_dobject_t *)zbx_hashset_search(&data->objects, &index))) { zbx_snmp_dobject_t new_obj; new_obj.index = zbx_strdup(NULL, index); new_obj.values = (char **)zbx_malloc(NULL, sizeof(char *) * data->request.nparam / 2); memset(new_obj.values, 0, sizeof(char *) * data->request.nparam / 2); obj = (zbx_snmp_dobject_t *)zbx_hashset_insert(&data->objects, &new_obj, sizeof(new_obj)); zbx_vector_snmp_dobject_ptr_append(&data->index, obj); } obj->values[data->num] = zbx_strdup(NULL, value); } static int zbx_snmp_process_discovery(zbx_snmp_sess_t ssp, const zbx_dc_item_t *item, AGENT_RESULT *result, int *errcode, char *error, size_t max_error_len, int *max_succeed, int *min_fail, int max_vars, int bulk) { int ret; char oid_translated[ZBX_ITEM_SNMP_OID_LEN_MAX]; struct zbx_json js; zbx_snmp_ddata_t data; zbx_snmp_dobject_t *obj; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); if (SUCCEED != (ret = zbx_snmp_ddata_init(&data, item->snmp_oid, error, max_error_len))) goto out; for (data.num = 0; data.num < data.request.nparam / 2; data.num++) { zbx_snmp_translate(oid_translated, data.request.params[data.num * 2 + 1], sizeof(oid_translated)); if (SUCCEED != (ret = zbx_snmp_walk(ssp, item, oid_translated, error, max_error_len, max_succeed, min_fail, max_vars, bulk, zbx_snmp_walk_discovery_cb, (void *)&data))) { goto clean; } } zbx_json_initarray(&js, ZBX_JSON_STAT_BUF_LEN); for (int i = 0; i < data.index.values_num; i++) { obj = (zbx_snmp_dobject_t *)data.index.values[i]; zbx_json_addobject(&js, NULL); zbx_json_addstring(&js, "{#SNMPINDEX}", obj->index, ZBX_JSON_TYPE_STRING); for (int j = 0; j < data.request.nparam / 2; j++) { if (NULL == obj->values[j]) continue; zbx_json_addstring(&js, data.request.params[j * 2], obj->values[j], ZBX_JSON_TYPE_STRING); } zbx_json_close(&js); } zbx_json_close(&js); SET_TEXT_RESULT(result, zbx_strdup(NULL, js.buffer)); zbx_json_free(&js); clean: zbx_snmp_ddata_clean(&data); out: if (SUCCEED != (*errcode = ret)) SET_MSG_RESULT(result, zbx_strdup(NULL, error)); zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } static void zbx_snmp_walk_cache_cb(void *arg, const char *snmp_oid, const char *index, const char *value) { cache_put_snmp_index((const zbx_dc_item_t *)arg, snmp_oid, index, value); } typedef struct { int numeric_oids; int numeric_enum; int numeric_ts; int oid_format; int no_print_units; } zbx_snmp_format_opts_t; static void snmp_bulkwalk_get_options(zbx_snmp_format_opts_t *opts) { opts->numeric_oids = netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_OIDS); opts->numeric_enum = netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_ENUM); opts->numeric_ts = netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_NUMERIC_TIMETICKS); opts->oid_format = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_OID_OUTPUT_FORMAT); opts->no_print_units = netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_PRINT_UNITS); } static void snmp_bulkwalk_set_options(zbx_snmp_format_opts_t *opts) { netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_OIDS, opts->numeric_oids); netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_ENUM, opts->numeric_enum); netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_NUMERIC_TIMETICKS, opts->numeric_ts); netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_OID_OUTPUT_FORMAT, opts->oid_format); netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_PRINT_UNITS, opts->no_print_units); } static void snmp_bulkwalk_remove_matching_oids(zbx_vector_snmp_oid_t *oids) { zbx_vector_snmp_oid_sort(oids, (zbx_compare_func_t)zbx_snmp_oid_compare); for (int i = 1; i < oids->values_num; i++) { size_t len = strlen(oids->values[i - 1]->str_oid); while (0 == strncmp(oids->values[i - 1]->str_oid, oids->values[i]->str_oid, len)) { if ('.' != oids->values[i]->str_oid[len] && '\0' != oids->values[i]->str_oid[len]) break; vector_snmp_oid_free(oids->values[i]); zbx_vector_snmp_oid_remove(oids, i); if (i == oids->values_num) return; } } } static int snmp_bulkwalk_parse_param(const char *snmp_oid, zbx_vector_snmp_oid_t *oids_out, char *error, size_t max_error_len) { char oid_translated[ZBX_ITEM_SNMP_OID_LEN_MAX], buffer[MAX_OID_LEN]; zbx_snmp_oid_t *root_oid; zbx_snmp_translate(oid_translated, snmp_oid, sizeof(oid_translated)); root_oid = (zbx_snmp_oid_t *)zbx_malloc(NULL, sizeof(zbx_snmp_oid_t)); root_oid->root_oid_len = MAX_OID_LEN; if (NULL == snmp_parse_oid(oid_translated, root_oid->root_oid, &root_oid->root_oid_len)) { zbx_free(root_oid); zbx_snprintf(error, max_error_len, "snmp_parse_oid(): cannot parse OID \"%s\": %s", oid_translated, snmp_api_errstring(SNMPERR_GENERR)); return FAIL; } snprint_objid(buffer, sizeof(buffer), root_oid->root_oid, root_oid->root_oid_len); root_oid->str_oid = zbx_strdup(NULL, buffer); zbx_vector_snmp_oid_append(oids_out, root_oid); zabbix_log(LOG_LEVEL_DEBUG, "%s() oids_num:%d", __func__, oids_out->values_num); return SUCCEED; } static int snmp_bulkwalk_parse_params(AGENT_REQUEST *request, zbx_vector_snmp_oid_t *oids_out, char *error, size_t max_error_len) { for (int i = 0; i < request->nparam; i++) { if (FAIL == snmp_bulkwalk_parse_param(request->params[i], oids_out, error, max_error_len)) return FAIL; } if (1 < oids_out->values_num) { zbx_vector_snmp_oid_sort(oids_out, (zbx_compare_func_t)zbx_snmp_oid_compare); snmp_bulkwalk_remove_matching_oids(oids_out); } zabbix_log(LOG_LEVEL_DEBUG, "%s() oids_num:%d", __func__, oids_out->values_num); return SUCCEED; } static int snmp_get_value_from_var(struct variable_list *var, char **results, size_t *results_alloc, size_t *results_offset, char *error, size_t max_error_len) { char **str_res = NULL; AGENT_RESULT result; unsigned char val_type; int ret = SUCCEED; zbx_init_agent_result(&result); if (SUCCEED == zbx_snmp_set_result(var, &result, &val_type, ZBX_ASN_OCTET_STR_UTF_8)) { if (ZBX_ISSET_TEXT(&result) && ZBX_SNMP_STR_HEX == val_type) zbx_remove_chars(result.text, "\r\n"); str_res = ZBX_GET_STR_RESULT(&result); } if (NULL == str_res) { char **msg; msg = ZBX_GET_MSG_RESULT(&result); zbx_snprintf(error, max_error_len, "cannot get SNMP result: %s", *msg); ret = NOTSUPPORTED; } else zbx_strcpy_alloc(results, results_alloc, results_offset, *str_res); zbx_free_agent_result(&result); return ret; } /****************************************************************************** * * * Purpose: quotes string value same way Net-SNMP library does it * * * * Parameters: buffer - [OUT] output buffer * * buffer_size - [IN] output buffer size * * str - [IN] SNMP variable * * len - [IN] * * * * Return value: SUCCEED - string was quoted successfully * * FAIL - output buffer is too small * * * ******************************************************************************/ static int snmp_native_quote_string_value(char *buffer, size_t buffer_size, char *str, size_t len) { size_t output_len = 0; if (!snmp_cstrcat((unsigned char **)&buffer, &buffer_size, &output_len, 0, "\"")) { return FAIL; } if (!sprint_realloc_asciistring((unsigned char **)&buffer, &buffer_size, &output_len, 0, (unsigned char *)str, len)) { return FAIL; } if (!snmp_cstrcat((unsigned char **)&buffer, &buffer_size, &output_len, 0, "\"")) { return FAIL; } return SUCCEED; } /****************************************************************************** * * * Purpose: quotes string value if Net-SNMP library hasn't quoted it * * * * Parameters: buffer - [OUT] * * buffer_size - [IN] output buffer size * * var - [IN] SNMP variable * * * * Return value: SUCCEED - string was quoted successfully * * FAIL - output buffer is too small * * * * Comments: When producing the output, Net-SNMP library sometimes quotes * * string values, sometimes it doesn't quote them. This makes it * * impossible to correctly parse string values, because there is * * no way for parser to tell if string value was quoted - quote * * character in the beginning of the value can either indicate that * * the value was quoted, or be a part of the actual value. To deal * * with this issue, we can analyze the output that was produced by * * the library, compare it to raw value and try to guess if the * * value was quoted. If it wasn't, we can manually quote the value. * * This way, string values will always be quoted and parser should * * not have issues with parsing them. * * * * When formatting string values, Net-SNMP library checks if * * display hint is available for specific OID. If yes, then value * * is not quoted. If hint is not used, then library outputs it * * either as unoquoted Hex-STRING value, or as quoted STRING value, * * or as quoted empty string without specifying the type. * * See sprint_realloc_octet_string() for exact implementation. * * * ******************************************************************************/ static int snmp_quote_string_value(char *buffer, size_t buffer_size, struct variable_list *var) { #define TYPE_STR_HEX_STRING "Hex-STRING: " #define TYPE_STR_STRING "STRING: " int ret; char *buf; char quoted_buf[MAX_STRING_LEN]; buf = strstr(buffer, " = "); if (NULL == buf) { zabbix_log(LOG_LEVEL_WARNING, "%s() ' = ' not found in buffer '%s'", __func__, buffer); THIS_SHOULD_NEVER_HAPPEN; ret = FAIL; goto out; } buf += 3; if (0 == var->val_len && 0 == strcmp(buf, "\"\"")) { ret = SUCCEED; goto out; } if (0 == strncmp(buf, TYPE_STR_HEX_STRING, sizeof(TYPE_STR_HEX_STRING) - 1)) { char *strval_dyn; struct tree *subtree; const char *hint; subtree = get_tree(var->name, var->name_length, get_tree_head()); hint = (NULL != subtree ? subtree->hint : NULL); if (NULL == hint && NULL != (strval_dyn = zbx_sprint_asn_octet_str_dyn(var))) { char *str = NULL; size_t str_alloc = 0, str_offset = 0; zbx_strncpy_alloc(&str, &str_alloc, &str_offset, buffer, buf - buffer); zbx_strcpy_alloc(&str, &str_alloc, &str_offset, TYPE_STR_STRING); zbx_strquote_alloc_opt(&str, &str_alloc, &str_offset, strval_dyn, ZBX_STRQUOTE_DEFAULT); zbx_strlcpy(buffer, str, buffer_size); zbx_free(strval_dyn); zbx_free(str); } ret = SUCCEED; goto out; } if (0 != strncmp(buf, TYPE_STR_STRING, sizeof(TYPE_STR_STRING) - 1)) { zabbix_log(LOG_LEVEL_WARNING, "%s() expected 'STRING' type in buffer '%s'", __func__, buffer); THIS_SHOULD_NEVER_HAPPEN; ret = FAIL; goto out; } buf += sizeof(TYPE_STR_STRING) - 1; buffer_size -= (size_t)(buf - buffer); if ('"' == *buf) { if (SUCCEED != snmp_native_quote_string_value(quoted_buf, buffer_size, (char *) var->val.string, var->val_len)) { zabbix_log(LOG_LEVEL_WARNING, "%s() quoting value failed, buffer too small", __func__); ret = FAIL; goto out; } /* If value produced by Net-SNMP library is the same as "manually quoted string", then we can assume */ /* that the library quoted the value and no further actions are needed. */ /* If values are different, quote character is probably part of the value, but we cannot just use */ /* quoted_buf as the final result, because value might have been altered to produce the output */ /* because of the display hints. We have to quote the value that was produced by Net-SNMP library, */ /* not the raw value. */ if (0 == strcmp(buf, quoted_buf)) { ret = SUCCEED; goto out; } } if (SUCCEED != snmp_native_quote_string_value(quoted_buf, buffer_size, buf, strlen(buf))) { zabbix_log(LOG_LEVEL_WARNING, "%s() quoting value failed, buffer too small", __func__); ret = FAIL; goto out; } zbx_strlcpy(buf, quoted_buf, buffer_size); ret = SUCCEED; out: return ret; #undef TYPE_STR_HEX_STRING #undef TYPE_STR_STRING } static int snmp_bulkwalk_handle_response(int status, struct snmp_pdu *response, zbx_bulkwalk_context_t *bulkwalk_context, char **results, size_t *results_alloc, size_t *results_offset, const zbx_snmp_sess_t ssp, const zbx_dc_interface_t *interface, unsigned char snmp_oid_type, char *error, size_t max_error_len) { struct variable_list *var; int ret = SUCCEED; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); if (STAT_SUCCESS != status || SNMP_ERR_NOERROR != response->errstat) { ret = zbx_get_snmp_response_error(ssp, interface, status, response, error, max_error_len, (int)*results_offset); zabbix_log(LOG_LEVEL_DEBUG, "%s() response error: %s", __func__, error); bulkwalk_context->running = 0; goto out; } if (NULL == response->variables) { zabbix_log(LOG_LEVEL_DEBUG, "%s() response contains no variables, status: %i", __func__, status); if (ZBX_SNMP_GET == snmp_oid_type) { zbx_snprintf(error, max_error_len, "No variables"); ret = NOTSUPPORTED; } bulkwalk_context->running = 0; goto out; } for (var = response->variables; NULL != var; var = var->next_variable) { if (var->name_length < bulkwalk_context->p_oid->root_oid_len || 0 != memcmp(bulkwalk_context->p_oid->root_oid, var->name, bulkwalk_context->p_oid->root_oid_len * sizeof(oid))) { if (ZBX_SNMP_GET == snmp_oid_type) { if (SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_DEBUG)) { char oid_resp[MAX_OID_LEN], oid_req[MAX_OID_LEN]; snprint_objid(oid_resp, sizeof(oid_req), var->name, var->name_length); snprint_objid(oid_req, sizeof(oid_resp), bulkwalk_context->name, bulkwalk_context->name_length); zabbix_log(LOG_LEVEL_DEBUG, "OID mismatch: GET response OID (%s) doesn't" " match request OID (%s)", oid_resp, oid_req); } } else { bulkwalk_context->running = 0; break; } } if (ZBX_SNMP_GET == snmp_oid_type) { ret = snmp_get_value_from_var(var, results, results_alloc, results_offset, error, max_error_len); bulkwalk_context->running = 0; break; } if (SNMP_ENDOFMIBVIEW != var->type && SNMP_NOSUCHOBJECT != var->type && SNMP_NOSUCHINSTANCE != var->type) { char buffer[MAX_STRING_LEN]; bulkwalk_context->vars_num++; if (SNMP_MSG_GET != bulkwalk_context->pdu_type) { if (0 <= snmp_oid_compare(bulkwalk_context->name, bulkwalk_context->name_length, var->name, var->name_length)) { bulkwalk_context->running = 0; break; } } else bulkwalk_context->running = 0; snprint_variable(buffer, sizeof(buffer), var->name, var->name_length, var); if (ASN_OCTET_STR == var->type) snmp_quote_string_value(buffer, sizeof(buffer), var); if (NULL != *results) zbx_chrcpy_alloc(results, results_alloc, results_offset, '\n'); zbx_strcpy_alloc(results, results_alloc, results_offset, buffer); if (NULL == var->next_variable) { memcpy(bulkwalk_context->name, var->name, var->name_length * sizeof(oid)); bulkwalk_context->name_length = var->name_length; } } else { bulkwalk_context->running = 0; break; } } out: zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s running:%d count:%d snmp_error:%s", __func__, zbx_result_string(ret), bulkwalk_context->running, bulkwalk_context->vars_num, snmp_api_errstring(status)); return ret; } static int asynch_response(int operation, struct snmp_session *sp, int reqid, struct snmp_pdu *pdu, void *magic) { zbx_bulkwalk_context_t *bulkwalk_context; zbx_snmp_context_t *snmp_context; int stat, ret = FAIL; zabbix_log(LOG_LEVEL_DEBUG, "In %s()",__func__); ZBX_UNUSED(sp); bulkwalk_context = (zbx_bulkwalk_context_t *)magic; snmp_context = (zbx_snmp_context_t *)bulkwalk_context->arg; bulkwalk_context->operation = operation; if (reqid != bulkwalk_context->reqid && NULL != pdu && SNMP_MSG_REPORT != pdu->command) { zabbix_log(LOG_LEVEL_DEBUG, "unexpected response request id:%d expected request id:%d command:%d" " operation:%d snmp_error:%s", reqid, bulkwalk_context->reqid, pdu->command, operation, snmp_api_errstring(SNMPERR_GENERR)); zbx_free(bulkwalk_context->error); bulkwalk_context->error = zbx_dsprintf(bulkwalk_context->error, "unexpected response"); return 0; } zabbix_log(LOG_LEVEL_DEBUG, "operation:%d response id:%d command:%d probe:%d", operation, reqid, pdu ? pdu->command : -1, snmp_context->probe); bulkwalk_context->waiting = 0; if (1 == snmp_context->probe) { ret = SUCCEED; goto out; } switch (operation) { case NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE: stat = STAT_SUCCESS; break; case NETSNMP_CALLBACK_OP_TIMED_OUT: stat = STAT_TIMEOUT; zabbix_log(LOG_LEVEL_DEBUG, "operation:%d response id:%d timed out, snmp_error:%s", operation, reqid, snmp_api_errstring(SNMPERR_TIMEOUT)); break; case NETSNMP_CALLBACK_OP_SEND_FAILED: stat = STAT_ERROR; zabbix_log(LOG_LEVEL_DEBUG, "operation:%d response id:%d sending failed, snmp_error:%s", operation, reqid, snmp_api_errstring(SNMPERR_GENERR)); break; case NETSNMP_CALLBACK_OP_DISCONNECT: stat = STAT_ERROR; zabbix_log(LOG_LEVEL_DEBUG, "operation:%d response id:%d disconnected, snmp_error:%s", operation, reqid, snmp_api_errstring(SNMPERR_GENERR)); break; case NETSNMP_CALLBACK_OP_SEC_ERROR: stat = STAT_ERROR; zabbix_log(LOG_LEVEL_DEBUG, "operation:%d response id:%d security error, snmp_error:%s", operation, reqid, snmp_api_errstring(SNMPERR_GENERR)); break; case NETSNMP_CALLBACK_OP_RESEND: bulkwalk_context->reqid = reqid; case NETSNMP_CALLBACK_OP_CONNECT: default: goto out; } if (NULL != pdu) { char error[MAX_STRING_LEN]; if (SUCCEED != (ret = snmp_bulkwalk_handle_response(stat, pdu, bulkwalk_context, &snmp_context->results, &snmp_context->results_alloc, &snmp_context->results_offset, snmp_context->ssp, &snmp_context->item.interface, snmp_context->snmp_oid_type, error, sizeof(error)))) { bulkwalk_context->error = zbx_strdup(bulkwalk_context->error, error); } } else { zbx_free(bulkwalk_context->error); bulkwalk_context->error = zbx_dsprintf(bulkwalk_context->error, "SNMP error: [%d]", stat); } out: zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return 1; } static netsnmp_pdu *usm_probe_pdu_create(void) { netsnmp_pdu *pdu; if (NULL == (pdu = snmp_pdu_create(SNMP_MSG_GET))) return NULL; pdu->version = SNMP_VERSION_3; pdu->securityName = strdup(""); pdu->securityNameLen = 0; pdu->securityLevel = SNMP_SEC_LEVEL_NOAUTH; pdu->securityModel = SNMP_SEC_MODEL_USM; return pdu; } static zbx_bulkwalk_context_t *snmp_bulkwalk_context_create(zbx_snmp_context_t *snmp_context, int pdu_type, zbx_snmp_oid_t *p_oid) { zbx_bulkwalk_context_t *bulkwalk_context; bulkwalk_context = zbx_malloc(NULL, sizeof(zbx_bulkwalk_context_t)); bulkwalk_context->p_oid = p_oid; memcpy(bulkwalk_context->name, p_oid->root_oid, p_oid->root_oid_len * sizeof(oid)); bulkwalk_context->name_length = p_oid->root_oid_len; bulkwalk_context->pdu_type = pdu_type; bulkwalk_context->running = 1; bulkwalk_context->vars_num = 0; bulkwalk_context->arg = snmp_context; bulkwalk_context->error = NULL; netsnmp_large_fd_set_init(&bulkwalk_context->fdset, FD_SETSIZE); return bulkwalk_context; } static void snmp_bulkwalk_context_free(zbx_bulkwalk_context_t *bulkwalk_context) { netsnmp_large_fd_set_cleanup(&bulkwalk_context->fdset); zbx_free(bulkwalk_context->error); zbx_free(bulkwalk_context); } static int snmp_bulkwalk_add(zbx_snmp_context_t *snmp_context, int *fd, char *error, size_t max_error_len) { struct snmp_pdu *pdu; zbx_bulkwalk_context_t *bulkwalk_context = snmp_context->bulkwalk_contexts.values[snmp_context->i]; struct netsnmp_transport_s *transport; int ret, numfds = 0, block = 0; struct timeval timeout = {0}; fd_set fdset; if (1 == snmp_context->probe) { netsnmp_session *session = snmp_sess_session(snmp_context->ssp); session->flags |= SNMP_FLAGS_DONT_PROBE; if (NULL == (pdu = usm_probe_pdu_create())) { zbx_strlcpy(error, "snmp_pdu_create(): cannot create PDU object.", max_error_len); ret = CONFIG_ERROR; goto out; } } else { if (SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_DEBUG)) { char buffer[MAX_OID_LEN]; snprint_objid(buffer, sizeof(buffer), bulkwalk_context->name, bulkwalk_context->name_length); zabbix_log(LOG_LEVEL_DEBUG, "In %s() OID: '%s'",__func__, buffer); } /* create PDU */ if (NULL == (pdu = snmp_pdu_create(bulkwalk_context->pdu_type))) { zbx_strlcpy(error, "snmp_pdu_create(): cannot create PDU object.", max_error_len); ret = CONFIG_ERROR; goto out; } if (SNMP_MSG_GETBULK == bulkwalk_context->pdu_type) { pdu->non_repeaters = 0; pdu->max_repetitions = snmp_context->snmp_max_repetitions; } if (NULL == snmp_add_null_var(pdu, bulkwalk_context->name, bulkwalk_context->name_length)) { zbx_strlcpy(error, "snmp_add_null_var(): cannot add null variable.", max_error_len); ret = CONFIG_ERROR; snmp_free_pdu(pdu); goto out; } } zabbix_log(LOG_LEVEL_DEBUG, "In %s() sending", __func__); bulkwalk_context->reqid = -1; bulkwalk_context->operation = 0; if (0 == (bulkwalk_context->reqid = snmp_sess_async_send(snmp_context->ssp, pdu, asynch_response, bulkwalk_context))) { ret = zbx_get_snmp_response_error(snmp_context->ssp, &snmp_context->item.interface, STAT_ERROR, NULL, error, max_error_len, 0); snmp_free_pdu(pdu); goto out; } zabbix_log(LOG_LEVEL_DEBUG, "In %s() send completed reqid:%d", __func__, bulkwalk_context->reqid); FD_ZERO(&fdset); netsnmp_copy_fd_set_to_large_fd_set(&bulkwalk_context->fdset, &fdset); if (1 > snmp_sess_select_info2(snmp_context->ssp, &numfds, &bulkwalk_context->fdset, &timeout, &block)) { zbx_strlcpy(error, "snmp_sess_select_info2(): cannot get socket.", max_error_len); ret = NETWORK_ERROR; snmp_sess_timeout(snmp_context->ssp); goto out; } if (NULL == (transport = snmp_sess_transport(snmp_context->ssp)) || -1 == transport->sock) { zbx_strlcpy(error, "snmp_sess_transport(): cannot get socket.", max_error_len); ret = NETWORK_ERROR; snmp_sess_timeout(snmp_context->ssp); goto out; } *fd = transport->sock; ret = SUCCEED; out: zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s fd:%d", __func__, zbx_result_string(ret), *fd); return ret; } static ZBX_THREAD_LOCAL zbx_snmp_format_opts_t default_opts; void zbx_set_snmp_bulkwalk_options(const char *progname) { zbx_snmp_format_opts_t bulk_opts; zbx_init_snmp(progname); if (1 == zbx_snmp_init_bulkwalk_done) return; zbx_snmp_init_bulkwalk_done = 1; snmp_bulkwalk_get_options(&default_opts); bulk_opts.numeric_oids = 1; bulk_opts.numeric_enum = 1; bulk_opts.numeric_ts = 1; bulk_opts.no_print_units = 1; bulk_opts.oid_format = NETSNMP_OID_OUTPUT_NUMERIC; snmp_bulkwalk_set_options(&bulk_opts); } void zbx_unset_snmp_bulkwalk_options(void) { if (0 == zbx_snmp_init_bulkwalk_done) return; zbx_snmp_init_bulkwalk_done = 0; snmp_bulkwalk_set_options(&default_opts); } static int snmp_task_process(short event, void *data, int *fd, const char *addr, char *dnserr, struct event *timeout_event) { zbx_bulkwalk_context_t *bulkwalk_context; zbx_snmp_context_t *snmp_context = (zbx_snmp_context_t *)data; char error[MAX_STRING_LEN]; const char *snmp_error; int ret, task_ret = ZBX_ASYNC_TASK_STOP; zbx_poller_config_t *poller_config = (zbx_poller_config_t *)snmp_context->arg_action; zabbix_log(LOG_LEVEL_DEBUG, "In %s() %s event:%d fd:%d itemid:" ZBX_FS_UI64, __func__, zbx_get_event_string(event), event, *fd, snmp_context->item.itemid); bulkwalk_context = snmp_context->bulkwalk_contexts.values[snmp_context->i]; if (NULL != poller_config && ZBX_PROCESS_STATE_IDLE == poller_config->state) { zbx_update_selfmon_counter(poller_config->info, ZBX_PROCESS_STATE_BUSY); poller_config->state = ZBX_PROCESS_STATE_BUSY; } if (ZABBIX_ASYNC_STEP_REVERSE_DNS == snmp_context->step) { if (NULL != addr) snmp_context->reverse_dns = zbx_strdup(NULL, addr); goto stop; } if (0 != (event & EV_TIMEOUT)) { if (NULL != dnserr) { SET_MSG_RESULT(&snmp_context->item.result, zbx_dsprintf(NULL, "cannot resolve address [[%s]:%hu]: timed out: %s", snmp_context->item.interface.addr, snmp_context->item.interface.port, dnserr)); snmp_context->item.ret = TIMEOUT_ERROR; goto stop; } if (0 < snmp_context->retries--) { zabbix_log(LOG_LEVEL_DEBUG, "cannot receive response for itemid:" ZBX_FS_UI64 " from [[%s]:%hu]: timed out, retrying", snmp_context->item.itemid, snmp_context->item.interface.addr, snmp_context->item.interface.port); snmp_sess_timeout(snmp_context->ssp); if (NETSNMP_CALLBACK_OP_RESEND == bulkwalk_context->operation) { /* reset timeout and retry if read is requested after timeout */ struct timeval tv = {snmp_context->config_timeout, 0}; evtimer_add(timeout_event, &tv); task_ret = ZBX_ASYNC_TASK_READ; goto stop; } } char buffer[MAX_OID_LEN]; const char *err_detail; snprint_objid(buffer, sizeof(buffer), bulkwalk_context->name, bulkwalk_context->name_length); if (SNMP_MSG_GETBULK == bulkwalk_context->pdu_type && 0 < snmp_context->results_offset) { err_detail = "only partial data received, cannot retrieve OID"; snmp_context->item.ret = NOTSUPPORTED; } else { err_detail = "cannot retrieve OID"; snmp_context->item.ret = TIMEOUT_ERROR; } if (ZBX_IF_SNMP_VERSION_3 == snmp_context->snmp_version && 0 == snmp_context->probe) { SET_MSG_RESULT(&snmp_context->item.result, zbx_dsprintf(NULL, "Probe successful, %s: '%s' from [[%s]:%hu]:" " timed out", err_detail, buffer, snmp_context->item.interface.addr, snmp_context->item.interface.port)); } else { SET_MSG_RESULT(&snmp_context->item.result, zbx_dsprintf(NULL, "%s: '%s' from [[%s]:%hu]:" " timed out", err_detail, buffer, snmp_context->item.interface.addr, snmp_context->item.interface.port)); } goto stop; } else if (0 != event) { bulkwalk_context->waiting = 1; if (0 != snmp_sess_read2(snmp_context->ssp, &bulkwalk_context->fdset)) { char *tmp_err_str = NULL; snmp_context->item.ret = NOTSUPPORTED; snmp_sess_error(snmp_context->ssp, NULL, NULL, &tmp_err_str); if (NULL != snmp_context->ssp) { SET_MSG_RESULT(&snmp_context->item.result, zbx_dsprintf(NULL, "cannot read from" " session: %s", tmp_err_str)); } else { SET_MSG_RESULT(&snmp_context->item.result, zbx_dsprintf(NULL, "cannot read from" " session")); } zbx_free(tmp_err_str); goto stop; } /* socket became readable but callback was not invoked, this can mean that response to previous */ /* request arrived after retry and was ignored by the library, continue waiting for response */ if (1 == bulkwalk_context->waiting) { int numfds = 0, block = 0; struct timeval timeout = {0}; zabbix_log(LOG_LEVEL_DEBUG, "cannot process PDU result for itemid:" ZBX_FS_UI64, snmp_context->item.itemid); if (1 > snmp_sess_select_info2(snmp_context->ssp, &numfds, &bulkwalk_context->fdset, &timeout, &block)) { snmp_context->item.ret = NOTSUPPORTED; SET_MSG_RESULT(&snmp_context->item.result, zbx_strdup(NULL, "snmp_sess_select_info2(): cannot get socket.")); goto stop; } task_ret = ZBX_ASYNC_TASK_READ; goto stop; } if (1 == snmp_context->probe) { netsnmp_session *session = snmp_sess_session(snmp_context->ssp); if (0 != session->engineBoots || 0 != session->engineTime) { set_enginetime(session->securityEngineID, (u_int)session->securityEngineIDLen, session->engineBoots, session->engineTime, TRUE); } if (FAIL == zbx_snmp_cache_handle_engineid(session, &snmp_context->item)) goto stop; if (SNMPERR_SUCCESS != create_user_from_session(session)) { zabbix_log(LOG_LEVEL_DEBUG, "cannot process probing result for itemid:" ZBX_FS_UI64, snmp_context->item.itemid); } snmp_context->probe = 0; } if (NULL != bulkwalk_context->error) { snmp_context->item.ret = NOTSUPPORTED; SET_MSG_RESULT(&snmp_context->item.result, bulkwalk_context->error); bulkwalk_context->error = NULL; goto stop; } if (0 == bulkwalk_context->running) { if (0 == bulkwalk_context->vars_num && SNMP_MSG_GETBULK == bulkwalk_context->pdu_type) { bulkwalk_context->pdu_type = SNMP_MSG_GET; } else { snmp_context->i++; if (snmp_context->i >= snmp_context->bulkwalk_contexts.values_num) { if (NULL == snmp_context->results) SET_TEXT_RESULT(&snmp_context->item.result, zbx_strdup(NULL, "")); else SET_TEXT_RESULT(&snmp_context->item.result, snmp_context->results); snmp_context->results = NULL; snmp_context->item.ret = SUCCEED; if (ZABBIX_ASYNC_RESOLVE_REVERSE_DNS_YES == snmp_context->resolve_reverse_dns) { task_ret = ZBX_ASYNC_TASK_RESOLVE_REVERSE; snmp_context->step = ZABBIX_ASYNC_STEP_REVERSE_DNS; } goto stop; } } } } else { if (NULL == (snmp_context->ssp = zbx_snmp_open_session(snmp_context->snmp_version, addr, snmp_context->item.interface.port, snmp_context->snmp_community, snmp_context->snmpv3_securityname, snmp_context->snmpv3_contextname, snmp_context->snmpv3_securitylevel, snmp_context->snmpv3_authprotocol, snmp_context->snmpv3_authpassphrase, snmp_context->snmpv3_privprotocol, snmp_context->snmpv3_privpassphrase, error, sizeof(error), 0, snmp_context->config_source_ip))) { snmp_context->item.ret = NOTSUPPORTED; SET_MSG_RESULT(&snmp_context->item.result, zbx_dsprintf(NULL, "zbx_snmp_open_session() failed")); goto stop; } } if (SUCCEED != (ret = snmp_bulkwalk_add(snmp_context, fd, error, sizeof(error)))) { snmp_context->item.ret = ret; SET_MSG_RESULT(&snmp_context->item.result, zbx_dsprintf(NULL, "Get value failed: %s", error)); } else task_ret = ZBX_ASYNC_TASK_READ; stop: snmp_error = snmp_api_errstring(SNMPERR_SUCCESS); if ('\0' != *snmp_error) zabbix_log(LOG_LEVEL_DEBUG, "unhandled snmp error:'%s'", snmp_error); if (ZBX_ASYNC_TASK_STOP == task_ret && ZBX_ISSET_MSG(&snmp_context->item.result)) { zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s %s event:%d fd:%d itemid:" ZBX_FS_UI64 " size:%d error:%s", __func__, zbx_result_string(snmp_context->item.ret), zbx_get_event_string(event), event, *fd, snmp_context->item.itemid, snmp_context->results_offset, snmp_context->item.result.msg); } else { zabbix_log(LOG_LEVEL_DEBUG, "End of %s() %s event:%d fd:%d itemid:" ZBX_FS_UI64 " size:%d state:%s", __func__, zbx_get_event_string(event), event, *fd, snmp_context->item.itemid, snmp_context->results_offset, zbx_task_state_to_str(task_ret)); } return task_ret; } zbx_dc_item_context_t *zbx_async_check_snmp_get_item_context(zbx_snmp_context_t *snmp_context) { return &snmp_context->item; } char *zbx_async_check_snmp_get_reverse_dns(zbx_snmp_context_t *snmp_context) { return snmp_context->reverse_dns; } void *zbx_async_check_snmp_get_arg(zbx_snmp_context_t *snmp_context) { return snmp_context->arg; } void zbx_async_check_snmp_clean(zbx_snmp_context_t *snmp_context) { if (NULL != snmp_context->ssp) zbx_snmp_close_session(snmp_context->ssp); zbx_free(snmp_context->snmp_community); zbx_free(snmp_context->snmpv3_securityname); zbx_free(snmp_context->snmpv3_contextname); zbx_free(snmp_context->snmpv3_authpassphrase); zbx_free(snmp_context->snmpv3_privpassphrase); zbx_free(snmp_context->item.key); zbx_free(snmp_context->item.key_orig); zbx_free(snmp_context->results); zbx_free(snmp_context->reverse_dns); zbx_free_agent_result(&snmp_context->item.result); zbx_vector_bulkwalk_context_clear_ext(&snmp_context->bulkwalk_contexts, snmp_bulkwalk_context_free); zbx_vector_bulkwalk_context_destroy(&snmp_context->bulkwalk_contexts); zbx_vector_snmp_oid_clear_ext(&snmp_context->param_oids, vector_snmp_oid_free); zbx_vector_snmp_oid_destroy(&snmp_context->param_oids); zbx_free(snmp_context); } int zbx_async_check_snmp(zbx_dc_item_t *item, AGENT_RESULT *result, zbx_async_task_clear_cb_t clear_cb, void *arg, void *arg_action, struct event_base *base, struct evdns_base *dnsbase, const char *config_source_ip, zbx_async_resolve_reverse_dns_t resolve_reverse_dns, int retries) { int ret = SUCCEED, pdu_type, is_oid_plain = 0; AGENT_REQUEST request; zbx_snmp_context_t *snmp_context; char error[MAX_STRING_LEN]; zabbix_log(LOG_LEVEL_DEBUG, "In %s() key:'%s' host:'%s' addr:'%s' timeout:%d retries:%d max_repetitions:%d", __func__, item->key, item->host.host, item->interface.addr, item->timeout, retries, item->snmp_max_repetitions); snmp_context = zbx_malloc(NULL, sizeof(zbx_snmp_context_t)); snmp_context->resolve_reverse_dns = resolve_reverse_dns; snmp_context->step = ZABBIX_ASYNC_STEP_DEFAULT; snmp_context->reverse_dns = NULL; snmp_context->ssp = NULL; snmp_context->item.interface = item->interface; snmp_context->item.interface.addr = (item->interface.addr == item->interface.dns_orig ? snmp_context->item.interface.dns_orig : snmp_context->item.interface.ip_orig); zbx_strlcpy(snmp_context->item.host, item->host.host, sizeof(snmp_context->item.host)); snmp_context->item.itemid = item->itemid; snmp_context->item.hostid = item->host.hostid; snmp_context->item.value_type = item->value_type; snmp_context->item.flags = item->flags; snmp_context->item.key_orig = zbx_strdup(NULL, item->key_orig); if (item->key != item->key_orig) { snmp_context->item.key = item->key; item->key = NULL; } else snmp_context->item.key = zbx_strdup(NULL, item->key); snmp_context->item.version = item->interface.version; zbx_init_agent_result(&snmp_context->item.result); snmp_context->config_timeout = item->timeout; snmp_context->snmp_max_repetitions = item->snmp_max_repetitions; snmp_context->retries = retries; snmp_context->arg = arg; snmp_context->arg_action = arg_action; snmp_context->results = NULL; snmp_context->results_alloc = 0; snmp_context->results_offset = 0; snmp_context->i = 0; snmp_context->snmp_version = item->snmp_version; snmp_context->snmp_community = item->snmp_community; item->snmp_community = NULL; snmp_context->snmpv3_securityname = item->snmpv3_securityname; item->snmpv3_securityname = NULL; snmp_context->snmpv3_contextname = item->snmpv3_contextname; item->snmpv3_contextname = NULL; snmp_context->snmpv3_securitylevel = item->snmpv3_securitylevel; snmp_context->snmpv3_authprotocol = item->snmpv3_authprotocol; snmp_context->snmpv3_authpassphrase = item->snmpv3_authpassphrase; item->snmpv3_authpassphrase = NULL; snmp_context->snmpv3_privprotocol = item->snmpv3_privprotocol; snmp_context->snmpv3_privpassphrase = item->snmpv3_privpassphrase; item->snmpv3_privpassphrase = NULL; snmp_context->config_source_ip = config_source_ip; zbx_vector_bulkwalk_context_create(&snmp_context->bulkwalk_contexts); zbx_init_agent_request(&request); zbx_vector_snmp_oid_create(&snmp_context->param_oids); if (0 == strncmp(item->snmp_oid, "walk[", ZBX_CONST_STRLEN("walk["))) { snmp_context->snmp_oid_type = ZBX_SNMP_WALK; pdu_type = ZBX_IF_SNMP_VERSION_1 == item->snmp_version ? SNMP_MSG_GETNEXT : SNMP_MSG_GETBULK; } else if (0 == strncmp(item->snmp_oid, "get[", ZBX_CONST_STRLEN("get["))) { snmp_context->snmp_oid_type = ZBX_SNMP_GET; pdu_type = SNMP_MSG_GET; } else if (ZABBIX_ASYNC_RESOLVE_REVERSE_DNS_YES == resolve_reverse_dns) { /* OIDs without key are supported in case of network discovery */ snmp_context->snmp_oid_type = ZBX_SNMP_GET; pdu_type = SNMP_MSG_GET; is_oid_plain = 1; } else { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid SNMP OID: unsupported parameter.")); ret = CONFIG_ERROR; goto out; } snmp_context->probe = ZBX_IF_SNMP_VERSION_3 == item->snmp_version ? 1 : 0; if (SNMP_MSG_GETBULK == pdu_type && 1 > item->snmp_max_repetitions) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid max repetition count: it should be at least 1.")); ret = CONFIG_ERROR; goto out; } if (0 == is_oid_plain && SUCCEED != zbx_parse_item_key(item->snmp_oid, &request)) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid SNMP OID: cannot parse parameter.")); ret = CONFIG_ERROR; goto out; } if (0 == request.nparam || (1 == request.nparam && '\0' == *(request.params[0]))) { if (ZBX_SNMP_WALK == snmp_context->snmp_oid_type) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid parameters: at least one OID is expected.")); ret = CONFIG_ERROR; goto out; } if (SUCCEED != snmp_bulkwalk_parse_param(item->snmp_oid, &snmp_context->param_oids, error, sizeof(error))) { SET_MSG_RESULT(result, zbx_strdup(NULL, error)); ret = CONFIG_ERROR; goto out; } } else if (SUCCEED != snmp_bulkwalk_parse_params(&request, &snmp_context->param_oids, error, sizeof(error))) { SET_MSG_RESULT(result, zbx_strdup(NULL, error)); ret = CONFIG_ERROR; goto out; } for (int i = 0; i < snmp_context->param_oids.values_num; i++) { zbx_bulkwalk_context_t *bulkwalk_context; bulkwalk_context = snmp_bulkwalk_context_create(snmp_context, pdu_type, snmp_context->param_oids.values[i]); zbx_vector_bulkwalk_context_append(&snmp_context->bulkwalk_contexts, bulkwalk_context); } zbx_async_poller_add_task(base, dnsbase, snmp_context->item.interface.addr, snmp_context, item->timeout, snmp_task_process, clear_cb); ret = SUCCEED; out: if (SUCCEED != ret) zbx_async_check_snmp_clean(snmp_context); zbx_free_agent_request(&request); zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } static int zbx_snmp_process_dynamic(zbx_snmp_sess_t ssp, const zbx_dc_item_t *items, AGENT_RESULT *results, int *errcodes, int num, char *error, size_t max_error_len, int *max_succeed, int *min_fail, int bulk, unsigned char poller_type) { int ret, to_walk[ZBX_MAX_SNMP_ITEMS], to_walk_num = 0, to_verify[ZBX_MAX_SNMP_ITEMS], to_verify_num = 0; unsigned char query_and_ignore_type[ZBX_MAX_SNMP_ITEMS]; char to_verify_oids[ZBX_MAX_SNMP_ITEMS][ZBX_ITEM_SNMP_OID_LEN_MAX], index_oids[ZBX_MAX_SNMP_ITEMS][ZBX_ITEM_SNMP_OID_LEN_MAX], index_values[ZBX_MAX_SNMP_ITEMS][ZBX_ITEM_SNMP_OID_LEN_MAX], oids_translated[ZBX_MAX_SNMP_ITEMS][ZBX_ITEM_SNMP_OID_LEN_MAX]; char *idx = NULL; size_t idx_alloc = 32; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); idx = (char *)zbx_malloc(idx, idx_alloc); /* perform initial item validation */ for (int i = 0; i < num; i++) { char method[8]; if (SUCCEED != errcodes[i]) continue; if (3 != zbx_num_key_param(items[i].snmp_oid)) { SET_MSG_RESULT(&results[i], zbx_dsprintf(NULL, "OID \"%s\" contains unsupported parameters.", items[i].snmp_oid)); errcodes[i] = CONFIG_ERROR; continue; } zbx_get_key_param(items[i].snmp_oid, 1, method, sizeof(method)); zbx_get_key_param(items[i].snmp_oid, 2, index_oids[i], sizeof(index_oids[i])); zbx_get_key_param(items[i].snmp_oid, 3, index_values[i], sizeof(index_values[i])); if (0 != strcmp("index", method)) { SET_MSG_RESULT(&results[i], zbx_dsprintf(NULL, "Unsupported method \"%s\" in the OID \"%s\".", method, items[i].snmp_oid)); errcodes[i] = CONFIG_ERROR; continue; } zbx_snmp_translate(oids_translated[i], index_oids[i], sizeof(oids_translated[i])); if (SUCCEED == cache_get_snmp_index(&items[i], oids_translated[i], index_values[i], &idx, &idx_alloc)) { zbx_snprintf(to_verify_oids[i], sizeof(to_verify_oids[i]), "%s.%s", oids_translated[i], idx); to_verify[to_verify_num++] = i; query_and_ignore_type[i] = 1; } else { to_walk[to_walk_num++] = i; query_and_ignore_type[i] = 0; } } /* verify that cached indices are still valid */ if (0 != to_verify_num) { ret = zbx_snmp_get_values(ssp, items, to_verify_oids, results, errcodes, query_and_ignore_type, num, 0, error, max_error_len, max_succeed, min_fail, poller_type); if (SUCCEED != ret && NOTSUPPORTED != ret) goto exit; for (int i = 0; i < to_verify_num; i++) { int j = to_verify[i]; if (SUCCEED != errcodes[j]) continue; if (NULL == ZBX_GET_STR_RESULT(&results[j]) || 0 != strcmp(results[j].str, index_values[j])) { to_walk[to_walk_num++] = j; } else { /* ready to construct the final OID with index */ size_t len = strlen(oids_translated[j]); char *pl = strchr(items[j].snmp_oid, '['); *pl = '\0'; zbx_snmp_translate(oids_translated[j], items[j].snmp_oid, sizeof(oids_translated[j])); *pl = '['; zbx_strlcat(oids_translated[j], to_verify_oids[j] + len, sizeof(oids_translated[j])); } zbx_free_agent_result(&results[j]); } } /* walk OID trees to build index cache for cache misses */ if (0 != to_walk_num) { for (int i = 0; i < to_walk_num; i++) { int k, j = to_walk[i]; /* see whether this OID tree was already walked for another item */ for (k = 0; k < i; k++) { if (0 == strcmp(oids_translated[to_walk[k]], oids_translated[j])) break; } if (k != i) continue; /* walk */ cache_del_snmp_index_subtree(&items[j], oids_translated[j]); int errcode = zbx_snmp_walk(ssp, &items[j], oids_translated[j], error, max_error_len, max_succeed, min_fail, num, bulk, zbx_snmp_walk_cache_cb, (void *)&items[j]); if (NETWORK_ERROR == errcode) { /* consider a network error as relating to all items passed to */ /* this function, including those we did not just try to walk for */ ret = NETWORK_ERROR; goto exit; } if (CONFIG_ERROR == errcode || NOTSUPPORTED == errcode) { /* consider a configuration or "not supported" error as */ /* relating only to the items we have just tried to walk for */ for (k = i; k < to_walk_num; k++) { if (0 == strcmp(oids_translated[to_walk[k]], oids_translated[j])) { SET_MSG_RESULT(&results[to_walk[k]], zbx_strdup(NULL, error)); errcodes[to_walk[k]] = errcode; } } } } for (int i = 0; i < to_walk_num; i++) { int j = to_walk[i]; if (SUCCEED != errcodes[j]) continue; if (SUCCEED == cache_get_snmp_index(&items[j], oids_translated[j], index_values[j], &idx, &idx_alloc)) { /* ready to construct the final OID with index */ char *pl = strchr(items[j].snmp_oid, '['); *pl = '\0'; zbx_snmp_translate(oids_translated[j], items[j].snmp_oid, sizeof(oids_translated[j])); *pl = '['; zbx_strlcat(oids_translated[j], ".", sizeof(oids_translated[j])); zbx_strlcat(oids_translated[j], idx, sizeof(oids_translated[j])); } else { SET_MSG_RESULT(&results[j], zbx_dsprintf(NULL, "Cannot find index of \"%s\" in \"%s\".", index_values[j], index_oids[j])); errcodes[j] = NOTSUPPORTED; } } } /* query values based on the indices verified and/or determined above */ ret = zbx_snmp_get_values(ssp, items, oids_translated, results, errcodes, NULL, num, 0, error, max_error_len, max_succeed, min_fail, poller_type); exit: zbx_free(idx); zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } static int zbx_snmp_process_standard(struct snmp_session *ss, const zbx_dc_item_t *items, AGENT_RESULT *results, int *errcodes, int num, char *error, size_t max_error_len, int *max_succeed, int *min_fail, unsigned char poller_type) { int ret; char oids_translated[ZBX_MAX_SNMP_ITEMS][ZBX_ITEM_SNMP_OID_LEN_MAX]; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); for (int i = 0; i < num; i++) { if (SUCCEED != errcodes[i]) continue; if (0 != zbx_num_key_param(items[i].snmp_oid)) { SET_MSG_RESULT(&results[i], zbx_dsprintf(NULL, "OID \"%s\" contains unsupported parameters.", items[i].snmp_oid)); errcodes[i] = CONFIG_ERROR; continue; } zbx_snmp_translate(oids_translated[i], items[i].snmp_oid, sizeof(oids_translated[i])); } ret = zbx_snmp_get_values(ss, items, oids_translated, results, errcodes, NULL, num, 0, error, max_error_len, max_succeed, min_fail, poller_type); zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } /******************************************************************************************* * * * Comment: Actually this could be called by discoverer, without poller being initialized, * * so cannot call poller_get_progname(), need progname to be passed directly. * * * *******************************************************************************************/ static void zbx_init_snmp(const char *progname) { sigset_t mask, orig_mask; if (1 == zbx_snmp_init_done) return; sigemptyset(&mask); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGUSR2); sigaddset(&mask, SIGHUP); sigaddset(&mask, SIGQUIT); zbx_sigmask(SIG_BLOCK, &mask, &orig_mask); netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_PERSISTENT_LOAD, 1); netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_PERSISTENT_SAVE, 1); init_snmp(progname); zbx_snmp_init_done = 1; zbx_sigmask(SIG_SETMASK, &orig_mask, NULL); } /******************************************************************************************* * * * Comment: Actually this could be called by discoverer, without poller being initialized, * * so cannot call poller_get_progname(), need progname to be passed directly. * * * *******************************************************************************************/ static void zbx_shutdown_snmp(const char *progname) { sigset_t mask, orig_mask; sigemptyset(&mask); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGUSR2); sigaddset(&mask, SIGHUP); sigaddset(&mask, SIGQUIT); zbx_sigmask(SIG_BLOCK, &mask, &orig_mask); snmp_shutdown(progname); zbx_snmp_init_done = 0; zbx_sigmask(SIG_SETMASK, &orig_mask, NULL); } /****************************************************************************** * * * Purpose: initializes snmp and loads mibs files for multithread environment * * * ******************************************************************************/ void zbx_init_library_mt_snmp(const char *progname) { zbx_init_snmp(progname); if (0 == snmp_rwlock_init_done) { int err; if (0 != (err = pthread_rwlock_init(&snmp_exec_rwlock, NULL))) zabbix_log(LOG_LEVEL_WARNING, "cannot initialize snmp execute mutex: %s", zbx_strerror(err)); else snmp_rwlock_init_done = 1; } } void zbx_shutdown_library_mt_snmp(const char *progname) { if (1 == snmp_rwlock_init_done) { int err; pthread_rwlock_wrlock(&snmp_exec_rwlock); if (0 != (err = pthread_rwlock_destroy(&snmp_exec_rwlock))) zabbix_log(LOG_LEVEL_WARNING, "cannot destroy snmp execute mutex: %s", zbx_strerror(err)); else snmp_rwlock_init_done = 0; } zbx_shutdown_snmp(progname); } static void process_snmp_result(void *data) { zbx_snmp_context_t *snmp_context = (zbx_snmp_context_t *)data; zbx_snmp_result_t *snmp_result = (zbx_snmp_result_t *)snmp_context->arg; zabbix_log(LOG_LEVEL_DEBUG, "In %s() key:'%s' host:'%s' addr:'%s'", __func__, snmp_context->item.key, snmp_context->item.host, snmp_context->item.interface.addr); *snmp_result->result = snmp_context->item.result; zbx_init_agent_result(&snmp_context->item.result); snmp_result->errcode = snmp_context->item.ret; event_base_loopbreak(snmp_result->base); snmp_result->finished = 1; zbx_async_check_snmp_clean(snmp_context); zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__); } void get_values_snmp(zbx_dc_item_t *items, AGENT_RESULT *results, int *errcodes, int num, unsigned char poller_type, const char *config_source_ip, const int config_timeout, const char *progname) { zbx_snmp_sess_t ssp; char error[MAX_STRING_LEN]; int i, j, err = SUCCEED, max_succeed = 0, min_fail = ZBX_MAX_SNMP_ITEMS + 1, bulk = SNMP_BULK_ENABLED; zabbix_log(LOG_LEVEL_DEBUG, "In %s() host:'%s' addr:'%s' num:%d", __func__, items[0].host.host, items[0].interface.addr, num); zbx_init_snmp(progname); /* avoid high CPU usage by only initializing SNMP once used */ for (j = 0; j < num; j++) /* locate first supported item to use as a reference */ { if (SUCCEED == errcodes[j]) break; } if (j == num) /* all items already NOTSUPPORTED (with invalid key, port or SNMP parameters) */ goto out; SNMP_MT_EXECLOCK; if (0 == strncmp(items[j].snmp_oid, "walk[", ZBX_CONST_STRLEN("walk[")) || (0 == strncmp(items[j].snmp_oid, "get[", ZBX_CONST_STRLEN("get[")))) { struct evdns_base *dnsbase; zbx_snmp_result_t snmp_result = {.result = &results[j]}; if (NULL == (snmp_result.base = event_base_new())) { SET_MSG_RESULT(&results[j], zbx_strdup(NULL, "cannot initialize event base")); errcodes[j] = CONFIG_ERROR; goto out; } if (NULL == (dnsbase = evdns_base_new(snmp_result.base, EVDNS_BASE_INITIALIZE_NAMESERVERS))) { int ret; zabbix_log(LOG_LEVEL_ERR, "cannot initialize asynchronous DNS library with resolv.conf"); if (NULL == (dnsbase = evdns_base_new(snmp_result.base, 0))) { event_base_free(snmp_result.base); SET_MSG_RESULT(&results[j], zbx_strdup(NULL, "cannot initialize asynchronous DNS library")); errcodes[j] = CONFIG_ERROR; goto out; } if (0 != (ret = evdns_base_resolv_conf_parse(dnsbase, DNS_OPTIONS_ALL, ZBX_RES_CONF_FILE))) { zabbix_log(LOG_LEVEL_ERR, "cannot parse resolv.conf result: %s", zbx_resolv_conf_errstr(ret)); } } zbx_set_snmp_bulkwalk_options(progname); if (SUCCEED == (errcodes[j] = zbx_async_check_snmp(&items[j], &results[j], process_snmp_result, &snmp_result, NULL, snmp_result.base, dnsbase, config_source_ip, ZABBIX_ASYNC_RESOLVE_REVERSE_DNS_NO, ZBX_SNMP_DEFAULT_NUMBER_OF_RETRIES))) { if (1 == snmp_result.finished || -1 != event_base_dispatch(snmp_result.base)) { errcodes[j] = snmp_result.errcode; } else { SET_MSG_RESULT(&results[j], zbx_strdup(NULL, "cannot process event base")); errcodes[j] = CONFIG_ERROR; } } evdns_base_free(dnsbase, 0); event_base_free(snmp_result.base); zbx_unset_snmp_bulkwalk_options(); goto out; } else if (0 != (ZBX_FLAG_DISCOVERY_RULE & items[j].flags) || 0 == strncmp(items[j].snmp_oid, "discovery[", 10)) { int max_vars; zbx_dc_item_t *item = &items[j]; char ip_addr[ZBX_INTERFACE_IP_LEN_MAX]; zbx_getip_by_host(item->interface.addr, ip_addr, sizeof(ip_addr)); if (NULL == (ssp = zbx_snmp_open_session(item->snmp_version, ip_addr, item->interface.port, item->snmp_community, item->snmpv3_securityname, item->snmpv3_contextname, item->snmpv3_securitylevel, item->snmpv3_authprotocol, item->snmpv3_authpassphrase, item->snmpv3_privprotocol, item->snmpv3_privpassphrase, error, sizeof(error), config_timeout, config_source_ip))) { err = NETWORK_ERROR; goto exit; } max_vars = zbx_dc_config_get_suggested_snmp_vars(items[j].interface.interfaceid, &bulk); err = zbx_snmp_process_discovery(ssp, &items[j], &results[j], &errcodes[j], error, sizeof(error), &max_succeed, &min_fail, max_vars, bulk); zbx_snmp_close_session(ssp); } else if (NULL != strchr(items[j].snmp_oid, '[')) { zbx_dc_item_t *item = &items[j]; char ip_addr[ZBX_INTERFACE_IP_LEN_MAX]; zbx_getip_by_host(item->interface.addr, ip_addr, sizeof(ip_addr)); if (NULL == (ssp = zbx_snmp_open_session(item->snmp_version, ip_addr, item->interface.port, item->snmp_community, item->snmpv3_securityname, item->snmpv3_contextname, item->snmpv3_securitylevel, item->snmpv3_authprotocol, item->snmpv3_authpassphrase, item->snmpv3_privprotocol, item->snmpv3_privpassphrase, error, sizeof(error), config_timeout, config_source_ip))) { err = NETWORK_ERROR; goto exit; } (void)zbx_dc_config_get_suggested_snmp_vars(items[j].interface.interfaceid, &bulk); err = zbx_snmp_process_dynamic(ssp, items + j, results + j, errcodes + j, num - j, error, sizeof(error), &max_succeed, &min_fail, bulk, poller_type); zbx_snmp_close_session(ssp); } else { zbx_dc_item_t *item = &items[j]; char ip_addr[ZBX_INTERFACE_IP_LEN_MAX]; zbx_getip_by_host(item->interface.addr, ip_addr, sizeof(ip_addr)); if (NULL == (ssp = zbx_snmp_open_session(item->snmp_version, ip_addr, item->interface.port, item->snmp_community, item->snmpv3_securityname, item->snmpv3_contextname, item->snmpv3_securitylevel, item->snmpv3_authprotocol, item->snmpv3_authpassphrase, item->snmpv3_privprotocol, item->snmpv3_privpassphrase, error, sizeof(error), config_timeout, config_source_ip))) { err = NETWORK_ERROR; goto exit; } err = zbx_snmp_process_standard(ssp, items + j, results + j, errcodes + j, num - j, error, sizeof(error), &max_succeed, &min_fail, poller_type); zbx_snmp_close_session(ssp); } exit: if (SUCCEED != err) { zabbix_log(LOG_LEVEL_DEBUG, "getting SNMP values failed: %s", error); for (i = j; i < num; i++) { if (SUCCEED != errcodes[i]) continue; SET_MSG_RESULT(&results[i], zbx_strdup(NULL, error)); errcodes[i] = err; } } else if (SNMP_BULK_ENABLED == bulk && (0 != max_succeed || ZBX_MAX_SNMP_ITEMS + 1 != min_fail)) { zbx_dc_config_update_interface_snmp_stats(items[j].interface.interfaceid, max_succeed, min_fail); } out: SNMP_MT_UNLOCK; zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__); } /****************************************************************************** * * * Purpose: clears snmpv3 user authentication cache * * * * Parameters: process_type - [IN] * * process_num - [IN] unique id of process * * * ******************************************************************************/ void zbx_clear_cache_snmp(unsigned char process_type, int process_num) { if (FAIL != process_num) { zabbix_log(LOG_LEVEL_WARNING, "forced reloading of the snmp cache on [%s #%d]", get_process_type_string(process_type), process_num); } if (0 == zbx_snmp_init_done) return; SNMP_MT_INITLOCK; shutdown_usm(); if (ZBX_PROCESS_TYPE_SNMP_POLLER == process_type) zbx_clear_snmp_engineid_cache(); SNMP_MT_UNLOCK; } #endif /* HAVE_NETSNMP */