From: Florian Forster Date: Thu, 18 May 2017 08:10:06 +0000 (+0200) Subject: Merge remote-tracking branch 'github/pr/2105' X-Git-Tag: collectd-5.8.0~171 X-Git-Url: https://git.octo.it/?p=collectd.git;a=commitdiff_plain;h=662c44a84ae3daecd4ffdea940fffce35a41b52a;hp=d45f9cdfc084fc5e8783073b993d58b84deb5d58 Merge remote-tracking branch 'github/pr/2105' --- diff --git a/Makefile.am b/Makefile.am index d1d5071a..ebba2f34 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1490,6 +1490,14 @@ snmp_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBNETSNMP_LDFLAGS) snmp_la_LIBADD = $(BUILD_WITH_LIBNETSNMP_LIBS) endif +if BUILD_PLUGIN_SNMP_AGENT +pkglib_LTLIBRARIES += snmp_agent.la +snmp_agent_la_SOURCES = src/snmp_agent.c +snmp_agent_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBNETSNMP_CPPFLAGS) +snmp_agent_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBNETSNMP_LDFLAGS) +snmp_agent_la_LIBADD = $(BUILD_WITH_LIBNETSNMP_LIBS) +endif + if BUILD_PLUGIN_STATSD pkglib_LTLIBRARIES += statsd.la statsd_la_SOURCES = src/statsd.c diff --git a/README b/README index 18602816..b0b59214 100644 --- a/README +++ b/README @@ -497,6 +497,13 @@ Features updates to the files and write a bunch of updates at once, which lessens system load a lot. + - snmp_agent + Receives and handles queries from SNMP master agent and returns the data + collected by read plugins. Handles requests only for OIDs specified in + configuration file. To handle SNMP queries the plugin gets data from + collectd and translates requested values from collectd's internal format + to SNMP format. + - unixsock One can query the values from the unixsock plugin whenever they're needed. Please read collectd-unixsock(5) for a description on how that's @@ -824,7 +831,11 @@ Prerequisites This library is part of the “Manage ONTAP SDK” published by NetApp. * libnetsnmp (optional) - For the `snmp' plugin. + For the `snmp' and 'snmp_agent' plugins. + + + * libnetsnmpagent (optional) + Required for the 'snmp_agent' plugin. * libnotify (optional) diff --git a/configure.ac b/configure.ac index 1142910b..371c4b12 100644 --- a/configure.ac +++ b/configure.ac @@ -3763,6 +3763,7 @@ AC_SUBST([LIBNETAPP_LIBS]) # }}} # --with-libnetsnmp {{{ +with_libnetsnmpagent="no" AC_ARG_WITH(libnetsnmp, [AS_HELP_STRING([--with-libnetsnmp@<:@=PREFIX@:>@], [Path to the Net-SNMPD library.])], [ if test "x$withval" = "xno" @@ -3775,6 +3776,7 @@ AC_ARG_WITH(libnetsnmp, [AS_HELP_STRING([--with-libnetsnmp@<:@=PREFIX@:>@], [Pat with_libnetsnmp_cppflags="-I$withval/include" with_libnetsnmp_ldflags="-I$withval/lib" with_libnetsnmp="yes" + with_libnetsnmpagent="yes" fi; fi ], [with_libnetsnmp="yes"]) @@ -3787,6 +3789,10 @@ then [with_libnetsnmp="yes"], [with_libnetsnmp="no (net-snmp/net-snmp-config.h not found)"] ) + AC_CHECK_HEADERS(net-snmp/agent/agent_module_config.h, + [], + [with_libnetsnmpagent="no (net-snmp/agent/agent_module_config.h not found)"] + ) CPPFLAGS="$SAVE_CPPFLAGS" fi @@ -3800,6 +3806,11 @@ then [with_libnetsnmp="no (libnetsnmp not found)"], [$with_snmp_libs]) + AC_CHECK_LIB(netsnmpagent, init_agent, + [with_libnetsnmpagent="yes"], + [with_libnetsnmpagent="no (libnetsnmpagent not found)"], + [$with_snmp_libs]) + LDFLAGS="$SAVE_LDFLAGS" fi if test "x$with_libnetsnmp" = "xyes" @@ -3808,6 +3819,10 @@ then BUILD_WITH_LIBNETSNMP_LDFLAGS="$with_libnetsnmp_ldflags" BUILD_WITH_LIBNETSNMP_LIBS="-lnetsnmp" fi +if test "x$with_libnetsnmpagent" = "xyes" +then + BUILD_WITH_LIBNETSNMP_LIBS+=" -lnetsnmpagent" +fi AC_SUBST(BUILD_WITH_LIBNETSNMP_CPPFLAGS) AC_SUBST(BUILD_WITH_LIBNETSNMP_LDFLAGS) AC_SUBST(BUILD_WITH_LIBNETSNMP_LIBS) @@ -6035,6 +6050,7 @@ plugin_processes="no" plugin_protocols="no" plugin_python="no" plugin_serial="no" +plugin_snmp_agent="no" plugin_smart="no" plugin_swap="no" plugin_tape="no" @@ -6328,6 +6344,10 @@ if test "x$with_kvm_getswapinfo" = "xyes"; then plugin_swap="yes" fi +if test "x$with_libnetsnmp" = "xyes" && test "x$with_libnetsnmpagent" = "xyes"; then + plugin_snmp_agent="yes" +fi + if test "x$have_swapctl" = "xyes" && test "x$c_cv_have_swapctl_two_args" = "xyes"; then plugin_swap="yes" fi @@ -6490,6 +6510,7 @@ AC_PLUGIN([serial], [$plugin_serial], [serial port traffic AC_PLUGIN([sigrok], [$with_libsigrok], [sigrok acquisition sources]) AC_PLUGIN([smart], [$plugin_smart], [SMART statistics]) AC_PLUGIN([snmp], [$with_libnetsnmp], [SNMP querying plugin]) +AC_PLUGIN([snmp_agent], [$plugin_snmp_agent], [SNMP agent plugin]) AC_PLUGIN([statsd], [yes], [StatsD plugin]) AC_PLUGIN([swap], [$plugin_swap], [Swap usage statistics]) AC_PLUGIN([syslog], [$have_syslog], [Syslog logging plugin]) @@ -6916,6 +6937,7 @@ AC_MSG_RESULT([ serial . . . . . . . $enable_serial]) AC_MSG_RESULT([ sigrok . . . . . . . $enable_sigrok]) AC_MSG_RESULT([ smart . . . . . . . . $enable_smart]) AC_MSG_RESULT([ snmp . . . . . . . . $enable_snmp]) +AC_MSG_RESULT([ snmp_agent . . . . . $enable_snmp_agent]) AC_MSG_RESULT([ statsd . . . . . . . $enable_statsd]) AC_MSG_RESULT([ swap . . . . . . . . $enable_swap]) AC_MSG_RESULT([ syslog . . . . . . . $enable_syslog]) diff --git a/src/collectd.conf.in b/src/collectd.conf.in index ad9437bb..fb161afd 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -188,6 +188,7 @@ #@BUILD_PLUGIN_SIGROK_TRUE@LoadPlugin sigrok #@BUILD_PLUGIN_SMART_TRUE@LoadPlugin smart #@BUILD_PLUGIN_SNMP_TRUE@LoadPlugin snmp +#@BUILD_PLUGIN_SNMP_AGENT_TRUE@LoadPlugin snmp_agent #@BUILD_PLUGIN_STATSD_TRUE@LoadPlugin statsd #@BUILD_PLUGIN_SWAP_TRUE@LoadPlugin swap #@BUILD_PLUGIN_TABLE_TRUE@LoadPlugin table @@ -1275,6 +1276,30 @@ # # +# +# +# Plugin "memory" +# Type "memory" +# TypeInstance "free" +# OIDs "1.3.6.1.4.1.2021.4.6.0" +# +# +# IndexOID "IF-MIB::ifIndex" +# SizeOID "IF-MIB::ifNumber" +# +# Instance true +# Plugin "interface" +# OIDs "IF-MIB::ifDescr" +# +# +# Plugin "interface" +# Type "if_octets" +# TypeInstance "" +# OIDs "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets" +# +#
+#
+ # # Host "::" # Port "8125" diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 93103437..058edf2f 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -7182,6 +7182,115 @@ Since the configuration of the C is a little more complicated than other plugins, its documentation has been moved to an own manpage, L. Please see there for details. +=head2 Plugin C + +The I plugin is an AgentX subagent that receives and handles queries +from SNMP master agent and returns the data collected by read plugins. +The I plugin handles requests only for OIDs specified in +configuration file. To handle SNMP queries the plugin gets data from collectd +and translates requested values from collectd's internal format to SNMP format. +This plugin is a generic plugin and cannot work without configuration. +For more details on AgentX subagent see + + +B + + + + Plugin "memory" + Type "memory" + TypeInstance "free" + OIDs "1.3.6.1.4.1.2021.4.6.0" + + + IndexOID "IF-MIB::ifIndex" + SizeOID "IF-MIB::ifNumber" + + Instance true + Plugin "interface" + OIDs "IF-MIB::ifDescr" + + + Plugin "interface" + Type "if_octets" + TypeInstance "" + OIDs "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets" + +
+
+ +There are two types of blocks that can be contained in the +CPluginE snmp_agentE> block: B and B: + +=head3 The B block + +The B block defines a list OIDs that are to be handled. This block can +define scalar or table OIDs. If B block is defined inside of B
+block it reperesents table OIDs. +The following options can be set: + +=over 4 + +=item B I + +When B is set to B, the value for requested OID is copied from +plugin instance field of corresponding collectd value. If B block defines +scalar data type B has no effect and can be omitted. + +=item B I + +Read plugin name whose collected data will be mapped to specified OIDs. + +=item B I + +Collectd's type that is to be used for specified OID, e.Eg. "if_octets" +for example. The types are read from the B (see L). + +=item B I + +Collectd's type-instance that is to be used for specified OID. + +=item B I [I ...] + +Configures the OIDs to be handled by I plugin. Values for these OIDs +are taken from collectd data type specified by B, B, +B fields of this B block. Number of the OIDs configured +should correspond to number of values in specified B. +For example two OIDs "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets" can be mapped to +"rx" and "tx" values of "if_octets" type. + +=item B I + +The values taken from collectd are multiplied by I. The field is optional +and the default is B<1.0>. + +=item B I + +I is added to values from collectd after they have been multiplied by +B value. The field is optional and the default value is B<0.0>. + +=back + +=head3 The B
block + +The B
block defines a collection of B blocks that belong to one +snmp table. In addition to multiple B blocks the following options can be +set: + +=over 4 + +=item B I + +OID that is handled by the plugin and is mapped to numerical index value that is +generated by the plugin for each table record. + +=item B I + +OID that is handled by the plugin. Returned value is the number of records in +the table. The field is optional. + +=back + =head2 Plugin C The I listens to a UDP socket, reads "events" in the statsd diff --git a/src/daemon/utils_avltree.c b/src/daemon/utils_avltree.c index 6a4e0f3e..87568fb2 100644 --- a/src/daemon/utils_avltree.c +++ b/src/daemon/utils_avltree.c @@ -534,6 +534,8 @@ int c_avl_pick(c_avl_tree_t *t, void **key, void **value) { c_avl_node_t *n; c_avl_node_t *p; + assert(t != NULL); + if ((key == NULL) || (value == NULL)) return -1; if (t->root == NULL) diff --git a/src/daemon/utils_cache.c b/src/daemon/utils_cache.c index 5e111e6d..18aa66b7 100644 --- a/src/daemon/utils_cache.c +++ b/src/daemon/utils_cache.c @@ -477,6 +477,75 @@ gauge_t *uc_get_rate(const data_set_t *ds, const value_list_t *vl) { return ret; } /* gauge_t *uc_get_rate */ +int uc_get_value_by_name(const char *name, value_t **ret_values, + size_t *ret_values_num) { + value_t *ret = NULL; + size_t ret_num = 0; + cache_entry_t *ce = NULL; + int status = 0; + + pthread_mutex_lock(&cache_lock); + + if (c_avl_get(cache_tree, name, (void *) &ce) == 0) { + assert(ce != NULL); + + /* remove missing values from getval */ + if (ce->state == STATE_MISSING) { + status = -1; + } else { + ret_num = ce->values_num; + ret = malloc(ret_num * sizeof(*ret)); + if (ret == NULL) { + ERROR("utils_cache: uc_get_value_by_name: malloc failed."); + status = -1; + } else { + memcpy(ret, ce->values_raw, ret_num * sizeof(value_t)); + } + } + } + else { + DEBUG("utils_cache: uc_get_value_by_name: No such value: %s", name); + status = -1; + } + + pthread_mutex_unlock(&cache_lock); + + if (status == 0) { + *ret_values = ret; + *ret_values_num = ret_num; + } + + return (status); +} /* int uc_get_value_by_name */ + +value_t *uc_get_value(const data_set_t *ds, const value_list_t *vl) { + char name[6 * DATA_MAX_NAME_LEN]; + value_t *ret = NULL; + size_t ret_num = 0; + int status; + + if (FORMAT_VL(name, sizeof(name), vl) != 0) { + ERROR("utils_cache: uc_get_value: FORMAT_VL failed."); + return (NULL); + } + + status = uc_get_value_by_name(name, &ret, &ret_num); + if (status != 0) + return (NULL); + + /* This is important - the caller has no other way of knowing how many + * values are returned. */ + if (ret_num != (size_t) ds->ds_num) { + ERROR("utils_cache: uc_get_value: ds[%s] has %zu values, " + "but uc_get_value_by_name returned %zu.", ds->type, ds->ds_num, + ret_num); + sfree(ret); + return (NULL); + } + + return (ret); +} /* value_t *uc_get_value */ + size_t uc_get_size(void) { size_t size_arrays = 0; diff --git a/src/daemon/utils_cache.h b/src/daemon/utils_cache.h index 24c58ff7..8ba7133a 100644 --- a/src/daemon/utils_cache.h +++ b/src/daemon/utils_cache.h @@ -42,6 +42,8 @@ int uc_update(const data_set_t *ds, const value_list_t *vl); int uc_get_rate_by_name(const char *name, gauge_t **ret_values, size_t *ret_values_num); gauge_t *uc_get_rate(const data_set_t *ds, const value_list_t *vl); +int uc_get_value_by_name(const char *name, value_t **ret_values, size_t *ret_values_num); +value_t *uc_get_value(const data_set_t *ds, const value_list_t *vl); size_t uc_get_size(void); int uc_get_names(char ***ret_names, cdtime_t **ret_times, size_t *ret_number); diff --git a/src/snmp_agent.c b/src/snmp_agent.c new file mode 100644 index 00000000..6cbe881a --- /dev/null +++ b/src/snmp_agent.c @@ -0,0 +1,1576 @@ +/** + * collectd - src/snmp_agent.c + * + * Copyright(c) 2017 Intel Corporation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Roman Korynkevych + * Serhiy Pshyk + **/ + +#include "collectd.h" + +#include "common.h" +#include "utils_avltree.h" +#include "utils_cache.h" +#include "utils_llist.h" + +#include + +#include + +#include + +#define PLUGIN_NAME "snmp_agent" +#define ERR_BUF_SIZE 1024 +#define TYPE_STRING -1 + +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif + +struct oid_s { + oid oid[MAX_OID_LEN]; + size_t oid_len; + u_char type; +}; +typedef struct oid_s oid_t; + +struct table_definition_s { + char *name; + oid_t index_oid; + oid_t size_oid; + llist_t *columns; + c_avl_tree_t *instance_index; + c_avl_tree_t *index_instance; +}; +typedef struct table_definition_s table_definition_t; + +struct data_definition_s { + char *name; + char *plugin; + char *plugin_instance; + char *type; + char *type_instance; + const table_definition_t *table; + _Bool is_instance; + oid_t *oids; + size_t oids_len; + double scale; + double shift; +}; +typedef struct data_definition_s data_definition_t; + +struct snmp_agent_ctx_s { + pthread_t thread; + pthread_mutex_t lock; + pthread_mutex_t agentx_lock; + struct tree *tp; + + llist_t *tables; + llist_t *scalars; +}; +typedef struct snmp_agent_ctx_s snmp_agent_ctx_t; + +static snmp_agent_ctx_t *g_agent = NULL; + +#define CHECK_DD_TYPE(_dd, _p, _pi, _t, _ti) \ + (_dd->plugin ? !strcmp(_dd->plugin, _p) : 0) && \ + (_dd->plugin_instance ? !strcmp(_dd->plugin_instance, _pi) : 1) && \ + (_dd->type ? !strcmp(_dd->type, _t) : 0) && \ + (_dd->type_instance ? !strcmp(_dd->type_instance, _ti) : 1) + +static void *snmp_agent_thread_run(void *arg); +static int snmp_agent_register_oid(oid_t *oid, Netsnmp_Node_Handler *handler); +static int snmp_agent_set_vardata(void *dst_buf, size_t *dst_buf_len, + u_char asn_type, double scale, double shift, + const void *value, size_t len, int type); +static int snmp_agent_unregister_oid_index(oid_t *oid, int index); + +static u_char snmp_agent_get_asn_type(oid *oid, size_t oid_len) { + struct tree *node = get_tree(oid, oid_len, g_agent->tp); + + return (node != NULL) ? mib_to_asn_type(node->type) : 0; +} + +static char *snmp_agent_get_oid_name(oid *oid, size_t oid_len) { + struct tree *node = get_tree(oid, oid_len, g_agent->tp); + + return (node != NULL) ? node->label : NULL; +} + +static int snmp_agent_oid_to_string(char *buf, size_t buf_size, + oid_t const *o) { + char oid_str[MAX_OID_LEN][16]; + char *oid_str_ptr[MAX_OID_LEN]; + + for (size_t i = 0; i < o->oid_len; i++) { + ssnprintf(oid_str[i], sizeof(oid_str[i]), "%lu", (unsigned long)o->oid[i]); + oid_str_ptr[i] = oid_str[i]; + } + + return strjoin(buf, buf_size, oid_str_ptr, o->oid_len, "."); +} + +static void snmp_agent_dump_data(void) { +#if COLLECT_DEBUG + char oid_str[DATA_MAX_NAME_LEN]; + + for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) { + table_definition_t *td = te->value; + + DEBUG(PLUGIN_NAME ": Table:"); + DEBUG(PLUGIN_NAME ": Name: %s", td->name); + if (td->index_oid.oid_len != 0) { + snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &td->index_oid); + DEBUG(PLUGIN_NAME ": IndexOID: %s", oid_str); + } + if (td->size_oid.oid_len != 0) { + snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &td->size_oid); + DEBUG(PLUGIN_NAME ": SizeOID: %s", oid_str); + } + + for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) { + data_definition_t *dd = de->value; + + DEBUG(PLUGIN_NAME ": Column:"); + DEBUG(PLUGIN_NAME ": Name: %s", dd->name); + if (dd->plugin) + DEBUG(PLUGIN_NAME ": Plugin: %s", dd->plugin); + if (dd->plugin_instance) + DEBUG(PLUGIN_NAME ": PluginInstance: %s", dd->plugin_instance); + if (dd->is_instance) + DEBUG(PLUGIN_NAME ": Instance: true"); + if (dd->type) + DEBUG(PLUGIN_NAME ": Type: %s", dd->type); + if (dd->type_instance) + DEBUG(PLUGIN_NAME ": TypeInstance: %s", dd->type_instance); + for (int i = 0; i < dd->oids_len; i++) { + snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &dd->oids[i]); + DEBUG(PLUGIN_NAME ": OID[%d]: %s", i, oid_str); + } + DEBUG(PLUGIN_NAME ": Scale: %g", dd->scale); + DEBUG(PLUGIN_NAME ": Shift: %g", dd->shift); + } + } + + for (llentry_t *e = llist_head(g_agent->scalars); e != NULL; e = e->next) { + data_definition_t *dd = e->value; + + DEBUG(PLUGIN_NAME ": Scalar:"); + DEBUG(PLUGIN_NAME ": Name: %s", dd->name); + if (dd->plugin) + DEBUG(PLUGIN_NAME ": Plugin: %s", dd->plugin); + if (dd->plugin_instance) + DEBUG(PLUGIN_NAME ": PluginInstance: %s", dd->plugin_instance); + if (dd->is_instance) + DEBUG(PLUGIN_NAME ": Instance: true"); + if (dd->type) + DEBUG(PLUGIN_NAME ": Type: %s", dd->type); + if (dd->type_instance) + DEBUG(PLUGIN_NAME ": TypeInstance: %s", dd->type_instance); + for (int i = 0; i < dd->oids_len; i++) { + snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &dd->oids[i]); + DEBUG(PLUGIN_NAME ": OID[%d]: %s", i, oid_str); + } + DEBUG(PLUGIN_NAME ": Scale: %g", dd->scale); + DEBUG(PLUGIN_NAME ": Shift: %g", dd->shift); + } +#endif /* COLLECT_DEBUG */ +} + +static int snmp_agent_validate_data(void) { + + snmp_agent_dump_data(); + + for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) { + table_definition_t *td = te->value; + + for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) { + data_definition_t *dd = de->value; + + if (!dd->plugin) { + ERROR(PLUGIN_NAME ": Plugin not defined for '%s'.'%s'", td->name, + dd->name); + return -EINVAL; + } + + if (dd->plugin_instance) { + ERROR(PLUGIN_NAME ": PluginInstance should not be defined for table " + "data type '%s'.'%s'", + td->name, dd->name); + return -EINVAL; + } + + if (dd->oids_len == 0) { + ERROR(PLUGIN_NAME ": No OIDs defined for '%s'.'%s'", td->name, + dd->name); + return -EINVAL; + } + + if (dd->is_instance) { + + if (dd->type || dd->type_instance) { + ERROR(PLUGIN_NAME ": Type and TypeInstance are not valid for " + "instance data '%s'.'%s'", + td->name, dd->name); + return -EINVAL; + } + + if (dd->oids_len > 1) { + ERROR( + PLUGIN_NAME + ": Only one OID should be specified for instance data '%s'.'%s'", + td->name, dd->name); + return -EINVAL; + } + } else { + + if (!dd->type) { + ERROR(PLUGIN_NAME ": Type not defined for data '%s'.'%s'", td->name, + dd->name); + return -EINVAL; + } + } + } + } + + for (llentry_t *e = llist_head(g_agent->scalars); e != NULL; e = e->next) { + data_definition_t *dd = e->value; + + if (!dd->plugin) { + ERROR(PLUGIN_NAME ": Plugin not defined for '%s'", dd->name); + return -EINVAL; + } + + if (dd->oids_len == 0) { + ERROR(PLUGIN_NAME ": No OIDs defined for '%s'", dd->name); + return -EINVAL; + } + + if (dd->is_instance) { + ERROR(PLUGIN_NAME + ": Instance flag can't be specified for scalar data '%s'", + dd->name); + return -EINVAL; + } + + if (!dd->type) { + ERROR(PLUGIN_NAME ": Type not defined for data '%s'", dd->name); + return -EINVAL; + } + } + + return 0; +} + +static void snmp_agent_generate_oid2string(oid_t *oid, int offset, char *key) { + int key_len = oid->oid[offset]; + int i; + + for (i = 0; i < key_len && offset < oid->oid_len; i++) + key[i] = oid->oid[++offset]; + + key[i] = '\0'; +} + +static int snmp_agent_generate_string2oid(oid_t *oid, const char *key) { + int key_len = strlen(key); + + oid->oid[oid->oid_len++] = key_len; + for (int i = 0; i < key_len; i++) { + oid->oid[oid->oid_len++] = key[i]; + if (oid->oid_len >= MAX_OID_LEN) { + ERROR(PLUGIN_NAME ": Conversion key string %s to OID failed", key); + return -EINVAL; + } + } + + return 0; +} + +static int snmp_agent_register_oid_string(oid_t *oid, const char *key, + Netsnmp_Node_Handler *handler) { + oid_t new_oid; + + memcpy(&new_oid, oid, sizeof(*oid)); + int ret = snmp_agent_generate_string2oid(&new_oid, key); + if (ret != 0) + return ret; + + return snmp_agent_register_oid(&new_oid, handler); +} + +static int snmp_agent_unregister_oid_string(oid_t *oid, const char *key) { + oid_t new_oid; + + memcpy(&new_oid, oid, sizeof(*oid)); + int ret = snmp_agent_generate_string2oid(&new_oid, key); + if (ret != 0) + return ret; + + return unregister_mib(new_oid.oid, new_oid.oid_len); +} + +static int snmp_agent_table_row_remove(table_definition_t *td, + const char *instance) { + int *index = NULL; + char *ins = NULL; + + if (td->index_oid.oid_len) { + if ((c_avl_get(td->instance_index, instance, (void **)&index) != 0) || + (c_avl_get(td->index_instance, index, (void **)&ins) != 0)) + return 0; + } else { + if (c_avl_get(td->instance_index, instance, (void **)&ins) != 0) + return 0; + } + + pthread_mutex_lock(&g_agent->agentx_lock); + + if (td->index_oid.oid_len) + snmp_agent_unregister_oid_index(&td->index_oid, *index); + + for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) { + data_definition_t *dd = de->value; + + for (int i = 0; i < dd->oids_len; i++) + if (td->index_oid.oid_len) + snmp_agent_unregister_oid_index(&dd->oids[i], *index); + else + snmp_agent_unregister_oid_string(&dd->oids[i], ins); + } + + pthread_mutex_unlock(&g_agent->agentx_lock); + + DEBUG(PLUGIN_NAME ": Removed row for '%s' table [%d, %s]", td->name, + (index != NULL) ? *index : -1, ins); + + notification_t n = { + .severity = NOTIF_WARNING, .time = cdtime(), .plugin = PLUGIN_NAME}; + sstrncpy(n.host, hostname_g, sizeof(n.host)); + sstrncpy(n.plugin_instance, ins, sizeof(n.plugin_instance)); + ssnprintf(n.message, sizeof(n.message), + "Removed data row from table %s instance %s index %d", td->name, + ins, (index != NULL) ? *index : -1); + plugin_dispatch_notification(&n); + + if (td->index_oid.oid_len) { + c_avl_remove(td->index_instance, index, NULL, (void **)&ins); + c_avl_remove(td->instance_index, instance, NULL, (void **)&index); + sfree(index); + sfree(ins); + } else { + c_avl_remove(td->instance_index, instance, NULL, (void **)&ins); + sfree(ins); + } + + return 0; +} + +static int snmp_agent_clear_missing(const value_list_t *vl, + __attribute__((unused)) user_data_t *ud) { + if (vl == NULL) + return -EINVAL; + + for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) { + table_definition_t *td = te->value; + + for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) { + data_definition_t *dd = de->value; + + if (!dd->is_instance) { + if (CHECK_DD_TYPE(dd, vl->plugin, vl->plugin_instance, vl->type, + vl->type_instance)) { + snmp_agent_table_row_remove(td, vl->plugin_instance); + return 0; + } + } + } + } + + return 0; +} + +static void snmp_agent_free_data(data_definition_t **dd) { + + if (dd == NULL || *dd == NULL) + return; + + /* unregister scalar type OID */ + if ((*dd)->table == NULL) { + for (int i = 0; i < (*dd)->oids_len; i++) + unregister_mib((*dd)->oids[i].oid, (*dd)->oids[i].oid_len); + } + if (!(*dd)->table->index_oid.oid_len) { + char *instance; + + c_avl_iterator_t *iter = c_avl_get_iterator((*dd)->table->instance_index); + while (c_avl_iterator_next(iter, (void *)&instance, (void *)&instance) == + 0) { + for (int i = 0; i < (*dd)->oids_len; i++) + snmp_agent_unregister_oid_string(&(*dd)->oids[i], instance); + } + c_avl_iterator_destroy(iter); + } else { + /* unregister all table OIDs */ + int *index; + char *value; + + c_avl_iterator_t *iter = c_avl_get_iterator((*dd)->table->index_instance); + while (c_avl_iterator_next(iter, (void *)&index, (void *)&value) == 0) { + for (int i = 0; i < (*dd)->oids_len; i++) + snmp_agent_unregister_oid_index(&(*dd)->oids[i], *index); + } + c_avl_iterator_destroy(iter); + } + + sfree((*dd)->name); + sfree((*dd)->plugin); + sfree((*dd)->plugin_instance); + sfree((*dd)->type); + sfree((*dd)->type_instance); + sfree((*dd)->oids); + + sfree(*dd); + + return; +} + +static void snmp_agent_free_table(table_definition_t **td) { + + if (td == NULL || *td == NULL) + return; + + if ((*td)->size_oid.oid_len) + unregister_mib((*td)->size_oid.oid, (*td)->size_oid.oid_len); + + if ((*td)->index_oid.oid_len) { + int *index; + char *value; + + c_avl_iterator_t *iter = c_avl_get_iterator((*td)->index_instance); + while (c_avl_iterator_next(iter, (void *)&index, (void *)&value) == 0) + snmp_agent_unregister_oid_index(&(*td)->index_oid, *index); + + c_avl_iterator_destroy(iter); + } + + for (llentry_t *de = llist_head((*td)->columns); de != NULL; de = de->next) { + data_definition_t *dd = de->value; + snmp_agent_free_data(&dd); + } + + llist_destroy((*td)->columns); + + void *key = NULL; + void *value = NULL; + + /* index_instance and instance_index contain the same pointers */ + c_avl_destroy((*td)->index_instance); + (*td)->index_instance = NULL; + + if ((*td)->instance_index != NULL) { + while (c_avl_pick((*td)->instance_index, &key, &value) == 0) { + if (key != value) + sfree(key); + sfree(value); + } + c_avl_destroy((*td)->instance_index); + (*td)->instance_index = NULL; + } + + sfree((*td)->name); + sfree(*td); + + return; +} + +static int snmp_agent_form_reply(struct netsnmp_request_info_s *requests, + data_definition_t *dd, char *instance, + int oid_index) { + char name[DATA_MAX_NAME_LEN]; + format_name(name, sizeof(name), hostname_g, dd->plugin, + instance ? instance : dd->plugin_instance, dd->type, + dd->type_instance); + DEBUG(PLUGIN_NAME ": Identifier '%s'", name); + + value_t *values; + size_t values_num; + const data_set_t *ds = plugin_get_ds(dd->type); + if (ds == NULL) { + ERROR(PLUGIN_NAME ": Data set not found for '%s' type", dd->type); + return SNMP_NOSUCHINSTANCE; + } + + int ret = uc_get_value_by_name(name, &values, &values_num); + + if (ret != 0) { + ERROR(PLUGIN_NAME ": Failed to get value for '%s'", name); + return SNMP_NOSUCHINSTANCE; + } + + assert(ds->ds_num == values_num); + assert(oid_index < values_num); + + char data[DATA_MAX_NAME_LEN]; + size_t data_len = sizeof(data); + ret = snmp_agent_set_vardata( + data, &data_len, dd->oids[oid_index].type, dd->scale, dd->shift, + &values[oid_index], sizeof(values[oid_index]), ds->ds[oid_index].type); + + sfree(values); + + if (ret != 0) { + ERROR(PLUGIN_NAME ": Failed to convert '%s' value to snmp data", name); + return SNMP_NOSUCHINSTANCE; + } + + requests->requestvb->type = dd->oids[oid_index].type; + snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type, data, + data_len); + + return SNMP_ERR_NOERROR; +} + +static int +snmp_agent_table_oid_handler(struct netsnmp_mib_handler_s *handler, + struct netsnmp_handler_registration_s *reginfo, + struct netsnmp_agent_request_info_s *reqinfo, + struct netsnmp_request_info_s *requests) { + + if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) { + DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode); + return SNMP_ERR_NOERROR; + } + + pthread_mutex_lock(&g_agent->lock); + + oid_t oid; + memcpy(oid.oid, requests->requestvb->name, + sizeof(oid.oid[0]) * requests->requestvb->name_length); + oid.oid_len = requests->requestvb->name_length; + +#if COLLECT_DEBUG + char oid_str[DATA_MAX_NAME_LEN]; + snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &oid); + DEBUG(PLUGIN_NAME ": Get request received for table OID '%s'", oid_str); +#endif + + for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) { + table_definition_t *td = te->value; + + for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) { + data_definition_t *dd = de->value; + + for (int i = 0; i < dd->oids_len; i++) { + int ret = snmp_oid_ncompare(oid.oid, oid.oid_len, dd->oids[i].oid, + dd->oids[i].oid_len, + MIN(oid.oid_len, dd->oids[i].oid_len)); + if (ret != 0) + continue; + + char *instance; + + if (!td->index_oid.oid_len) { + char key[MAX_OID_LEN]; + + memset(key, 0, sizeof(key)); + snmp_agent_generate_oid2string( + &oid, MIN(oid.oid_len, dd->oids[i].oid_len), key); + + ret = c_avl_get(td->instance_index, key, (void **)&instance); + if (ret != 0) { + DEBUG(PLUGIN_NAME ": Nonexisting index string '%s' requested", key); + pthread_mutex_unlock(&g_agent->lock); + return SNMP_NOSUCHINSTANCE; + } + } else { + int index = oid.oid[oid.oid_len - 1]; + + ret = c_avl_get(td->index_instance, &index, (void **)&instance); + if (ret != 0) { + DEBUG(PLUGIN_NAME ": Nonexisting index '%d' requested", index); + pthread_mutex_unlock(&g_agent->lock); + return SNMP_NOSUCHINSTANCE; + } + } + + if (dd->is_instance) { + requests->requestvb->type = ASN_OCTET_STR; + snmp_set_var_typed_value(requests->requestvb, + requests->requestvb->type, instance, + strlen((instance))); + + pthread_mutex_unlock(&g_agent->lock); + + return SNMP_ERR_NOERROR; + } + + ret = snmp_agent_form_reply(requests, dd, instance, i); + + pthread_mutex_unlock(&g_agent->lock); + + return ret; + } + } + } + + pthread_mutex_unlock(&g_agent->lock); + + return SNMP_NOSUCHINSTANCE; +} + +static int snmp_agent_table_index_oid_handler( + struct netsnmp_mib_handler_s *handler, + struct netsnmp_handler_registration_s *reginfo, + struct netsnmp_agent_request_info_s *reqinfo, + struct netsnmp_request_info_s *requests) { + + if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) { + DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode); + return SNMP_ERR_NOERROR; + } + + pthread_mutex_lock(&g_agent->lock); + + oid_t oid; + memcpy(oid.oid, requests->requestvb->name, + sizeof(oid.oid[0]) * requests->requestvb->name_length); + oid.oid_len = requests->requestvb->name_length; + + for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) { + table_definition_t *td = te->value; + + if (td->index_oid.oid_len && + (snmp_oid_ncompare(oid.oid, oid.oid_len, td->index_oid.oid, + td->index_oid.oid_len, + MIN(oid.oid_len, td->index_oid.oid_len)) == 0)) { + + DEBUG(PLUGIN_NAME ": Handle '%s' table index OID", td->name); + + int index = oid.oid[oid.oid_len - 1]; + + int ret = c_avl_get(td->index_instance, &index, &(void *){NULL}); + if (ret != 0) { + /* nonexisting index requested */ + pthread_mutex_unlock(&g_agent->lock); + return SNMP_NOSUCHINSTANCE; + } + + requests->requestvb->type = ASN_INTEGER; + snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type, + &index, sizeof(index)); + + pthread_mutex_unlock(&g_agent->lock); + + return SNMP_ERR_NOERROR; + } + } + + pthread_mutex_unlock(&g_agent->lock); + + return SNMP_NOSUCHINSTANCE; +} + +static int snmp_agent_table_size_oid_handler( + struct netsnmp_mib_handler_s *handler, + struct netsnmp_handler_registration_s *reginfo, + struct netsnmp_agent_request_info_s *reqinfo, + struct netsnmp_request_info_s *requests) { + + if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) { + DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode); + return SNMP_ERR_NOERROR; + } + + pthread_mutex_lock(&g_agent->lock); + + oid_t oid; + memcpy(oid.oid, requests->requestvb->name, + sizeof(oid.oid[0]) * requests->requestvb->name_length); + oid.oid_len = requests->requestvb->name_length; + + DEBUG(PLUGIN_NAME ": Get request received for table size OID"); + + for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) { + table_definition_t *td = te->value; + + if (td->size_oid.oid_len && + (snmp_oid_ncompare(oid.oid, oid.oid_len, td->size_oid.oid, + td->size_oid.oid_len, + MIN(oid.oid_len, td->size_oid.oid_len)) == 0)) { + DEBUG(PLUGIN_NAME ": Handle '%s' table size OID", td->name); + + long size = c_avl_size(td->index_instance); + + requests->requestvb->type = td->size_oid.type; + snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type, + &size, sizeof(size)); + + pthread_mutex_unlock(&g_agent->lock); + + return SNMP_ERR_NOERROR; + } + } + + pthread_mutex_unlock(&g_agent->lock); + + return SNMP_NOSUCHINSTANCE; +} + +static int +snmp_agent_scalar_oid_handler(struct netsnmp_mib_handler_s *handler, + struct netsnmp_handler_registration_s *reginfo, + struct netsnmp_agent_request_info_s *reqinfo, + struct netsnmp_request_info_s *requests) { + + if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) { + DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode); + return SNMP_ERR_NOERROR; + } + + pthread_mutex_lock(&g_agent->lock); + + oid_t oid; + memcpy(oid.oid, requests->requestvb->name, + sizeof(oid.oid[0]) * requests->requestvb->name_length); + oid.oid_len = requests->requestvb->name_length; + +#if COLLECT_DEBUG + char oid_str[DATA_MAX_NAME_LEN]; + snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &oid); + DEBUG(PLUGIN_NAME ": Get request received for scalar OID '%s'", oid_str); +#endif + + for (llentry_t *de = llist_head(g_agent->scalars); de != NULL; + de = de->next) { + data_definition_t *dd = de->value; + + for (int i = 0; i < dd->oids_len; i++) { + + int ret = snmp_oid_compare(oid.oid, oid.oid_len, dd->oids[i].oid, + dd->oids[i].oid_len); + if (ret != 0) + continue; + + ret = snmp_agent_form_reply(requests, dd, NULL, i); + + pthread_mutex_unlock(&g_agent->lock); + + return ret; + } + } + + pthread_mutex_unlock(&g_agent->lock); + + return SNMP_NOSUCHINSTANCE; +} + +static int snmp_agent_register_table_oids(void) { + + for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) { + table_definition_t *td = te->value; + + if (td->size_oid.oid_len != 0) { + td->size_oid.type = + snmp_agent_get_asn_type(td->size_oid.oid, td->size_oid.oid_len); + td->size_oid.oid_len++; + int ret = snmp_agent_register_oid(&td->size_oid, + snmp_agent_table_size_oid_handler); + if (ret != 0) + return ret; + } + + for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) { + data_definition_t *dd = de->value; + + for (int i = 0; i < dd->oids_len; i++) { + dd->oids[i].type = + snmp_agent_get_asn_type(dd->oids[i].oid, dd->oids[i].oid_len); + } + } + } + + return 0; +} + +static int snmp_agent_register_scalar_oids(void) { + + for (llentry_t *e = llist_head(g_agent->scalars); e != NULL; e = e->next) { + data_definition_t *dd = e->value; + + for (int i = 0; i < dd->oids_len; i++) { + + dd->oids[i].type = + snmp_agent_get_asn_type(dd->oids[i].oid, dd->oids[i].oid_len); + + int ret = + snmp_agent_register_oid(&dd->oids[i], snmp_agent_scalar_oid_handler); + if (ret != 0) + return ret; + } + } + + return 0; +} + +static int snmp_agent_config_data_oids(data_definition_t *dd, + oconfig_item_t *ci) { + if (ci->values_num < 1) { + WARNING(PLUGIN_NAME ": `OIDs' needs at least one argument"); + return -EINVAL; + } + + for (int i = 0; i < ci->values_num; i++) + if (ci->values[i].type != OCONFIG_TYPE_STRING) { + WARNING(PLUGIN_NAME ": `OIDs' needs only string argument"); + return -EINVAL; + } + + if (dd->oids != NULL) + sfree(dd->oids); + dd->oids_len = 0; + dd->oids = calloc(ci->values_num, sizeof(*dd->oids)); + if (dd->oids == NULL) + return -ENOMEM; + dd->oids_len = (size_t)ci->values_num; + + for (int i = 0; i < ci->values_num; i++) { + dd->oids[i].oid_len = MAX_OID_LEN; + + if (NULL == snmp_parse_oid(ci->values[i].value.string, dd->oids[i].oid, + &dd->oids[i].oid_len)) { + ERROR(PLUGIN_NAME ": snmp_parse_oid (%s) failed", + ci->values[i].value.string); + sfree(dd->oids); + dd->oids_len = 0; + return -1; + } + } + + return 0; +} + +static int snmp_agent_config_table_size_oid(table_definition_t *td, + oconfig_item_t *ci) { + if (ci->values_num < 1) { + WARNING(PLUGIN_NAME ": `TableSizeOID' is empty"); + return -EINVAL; + } + + if (ci->values[0].type != OCONFIG_TYPE_STRING) { + WARNING(PLUGIN_NAME ": `TableSizeOID' needs only string argument"); + return -EINVAL; + } + + td->size_oid.oid_len = MAX_OID_LEN; + + if (NULL == snmp_parse_oid(ci->values[0].value.string, td->size_oid.oid, + &td->size_oid.oid_len)) { + ERROR(PLUGIN_NAME ": Failed to parse table size OID (%s)", + ci->values[0].value.string); + td->size_oid.oid_len = 0; + return -EINVAL; + } + + return 0; +} + +static int snmp_agent_config_table_index_oid(table_definition_t *td, + oconfig_item_t *ci) { + + if (ci->values_num < 1) { + WARNING(PLUGIN_NAME ": `IndexOID' is empty"); + return -EINVAL; + } + + if (ci->values[0].type != OCONFIG_TYPE_STRING) { + WARNING(PLUGIN_NAME ": `IndexOID' needs only string argument"); + return -EINVAL; + } + + td->index_oid.oid_len = MAX_OID_LEN; + + if (NULL == snmp_parse_oid(ci->values[0].value.string, td->index_oid.oid, + &td->index_oid.oid_len)) { + ERROR(PLUGIN_NAME ": Failed to parse table index OID (%s)", + ci->values[0].value.string); + td->index_oid.oid_len = 0; + return -EINVAL; + } + + return 0; +} + +static int snmp_agent_config_table_data(table_definition_t *td, + oconfig_item_t *ci) { + data_definition_t *dd; + int ret = 0; + + assert(ci != NULL); + + dd = calloc(1, sizeof(*dd)); + if (dd == NULL) { + ERROR(PLUGIN_NAME ": Failed to allocate memory for table data definition"); + return -ENOMEM; + } + + ret = cf_util_get_string(ci, &dd->name); + if (ret != 0) { + sfree(dd); + return -1; + } + + dd->scale = 1.0; + dd->shift = 0.0; + + dd->table = td; + + for (int i = 0; i < ci->children_num; i++) { + oconfig_item_t *option = ci->children + i; + + if (strcasecmp("Instance", option->key) == 0) + ret = cf_util_get_boolean(option, &dd->is_instance); + else if (strcasecmp("Plugin", option->key) == 0) + ret = cf_util_get_string(option, &dd->plugin); + else if (strcasecmp("PluginInstance", option->key) == 0) + ret = cf_util_get_string(option, &dd->plugin_instance); + else if (strcasecmp("Type", option->key) == 0) + ret = cf_util_get_string(option, &dd->type); + else if (strcasecmp("TypeInstance", option->key) == 0) + ret = cf_util_get_string(option, &dd->type_instance); + else if (strcasecmp("Shift", option->key) == 0) + ret = cf_util_get_double(option, &dd->shift); + else if (strcasecmp("Scale", option->key) == 0) + ret = cf_util_get_double(option, &dd->scale); + else if (strcasecmp("OIDs", option->key) == 0) + ret = snmp_agent_config_data_oids(dd, option); + else { + WARNING(PLUGIN_NAME ": Option `%s' not allowed here", option->key); + ret = -1; + } + + if (ret != 0) { + snmp_agent_free_data(&dd); + return -1; + } + } + + llentry_t *entry = llentry_create(dd->name, dd); + if (entry == NULL) { + snmp_agent_free_data(&dd); + return -ENOMEM; + } + + llist_append(td->columns, entry); + + return 0; +} + +static int snmp_agent_config_data(oconfig_item_t *ci) { + data_definition_t *dd; + int ret = 0; + + assert(ci != NULL); + + dd = calloc(1, sizeof(*dd)); + if (dd == NULL) { + ERROR(PLUGIN_NAME ": Failed to allocate memory for data definition"); + return -ENOMEM; + } + + ret = cf_util_get_string(ci, &dd->name); + if (ret != 0) { + free(dd); + return -1; + } + + dd->scale = 1.0; + dd->shift = 0.0; + + for (int i = 0; i < ci->children_num; i++) { + oconfig_item_t *option = ci->children + i; + + if (strcasecmp("Instance", option->key) == 0) + ret = cf_util_get_boolean(option, &dd->is_instance); + else if (strcasecmp("Plugin", option->key) == 0) + ret = cf_util_get_string(option, &dd->plugin); + else if (strcasecmp("PluginInstance", option->key) == 0) + ret = cf_util_get_string(option, &dd->plugin_instance); + else if (strcasecmp("Type", option->key) == 0) + ret = cf_util_get_string(option, &dd->type); + else if (strcasecmp("TypeInstance", option->key) == 0) + ret = cf_util_get_string(option, &dd->type_instance); + else if (strcasecmp("Shift", option->key) == 0) + ret = cf_util_get_double(option, &dd->shift); + else if (strcasecmp("Scale", option->key) == 0) + ret = cf_util_get_double(option, &dd->scale); + else if (strcasecmp("OIDs", option->key) == 0) + ret = snmp_agent_config_data_oids(dd, option); + else { + WARNING(PLUGIN_NAME ": Option `%s' not allowed here", option->key); + ret = -1; + } + + if (ret != 0) { + snmp_agent_free_data(&dd); + return -1; + } + } + + llentry_t *entry = llentry_create(dd->name, dd); + if (entry == NULL) { + snmp_agent_free_data(&dd); + return -ENOMEM; + } + + llist_append(g_agent->scalars, entry); + + return 0; +} + +static int num_compare(const int *a, const int *b) { + assert((a != NULL) && (b != NULL)); + if (*a < *b) + return -1; + else if (*a > *b) + return 1; + else + return 0; +} + +static int snmp_agent_config_table(oconfig_item_t *ci) { + table_definition_t *td; + int ret = 0; + + assert(ci != NULL); + + td = calloc(1, sizeof(*td)); + if (td == NULL) { + ERROR(PLUGIN_NAME ": Failed to allocate memory for table definition"); + return -ENOMEM; + } + + ret = cf_util_get_string(ci, &td->name); + if (ret != 0) { + sfree(td); + return -1; + } + + td->columns = llist_create(); + if (td->columns == NULL) { + ERROR(PLUGIN_NAME ": Failed to allocate memory for columns list"); + snmp_agent_free_table(&td); + return -ENOMEM; + } + + for (int i = 0; i < ci->children_num; i++) { + oconfig_item_t *option = ci->children + i; + + if (strcasecmp("IndexOID", option->key) == 0) + ret = snmp_agent_config_table_index_oid(td, option); + else if (strcasecmp("SizeOID", option->key) == 0) + ret = snmp_agent_config_table_size_oid(td, option); + else if (strcasecmp("Data", option->key) == 0) + ret = snmp_agent_config_table_data(td, option); + else { + WARNING(PLUGIN_NAME ": Option `%s' not allowed here", option->key); + ret = -1; + } + + if (ret != 0) { + snmp_agent_free_table(&td); + return -ENOMEM; + } + } + + llentry_t *entry = llentry_create(td->name, td); + if (entry == NULL) { + snmp_agent_free_table(&td); + return -ENOMEM; + } + + td->instance_index = + c_avl_create((int (*)(const void *, const void *))strcmp); + if (td->instance_index == NULL) { + snmp_agent_free_table(&td); + return -ENOMEM; + } + + td->index_instance = + c_avl_create((int (*)(const void *, const void *))num_compare); + if (td->index_instance == NULL) { + snmp_agent_free_table(&td); + return -ENOMEM; + } + + llist_append(g_agent->tables, entry); + + return 0; +} + +static int snmp_agent_get_value_from_ds_type(const value_t *val, int type, + double scale, double shift, + long *value) { + switch (type) { + case DS_TYPE_COUNTER: + *value = (long)((val->counter * scale) + shift); + break; + case DS_TYPE_ABSOLUTE: + *value = (long)((val->absolute * scale) + shift); + break; + case DS_TYPE_DERIVE: + *value = (long)((val->derive * scale) + shift); + break; + case DS_TYPE_GAUGE: + *value = (long)((val->gauge * scale) + shift); + break; + case TYPE_STRING: + break; + default: + ERROR(PLUGIN_NAME ": Unknown data source type: %i", type); + return -EINVAL; + } + + return 0; +} + +static int snmp_agent_set_vardata(void *data, size_t *data_len, u_char asn_type, + double scale, double shift, const void *value, + size_t len, int type) { + + int ret; + netsnmp_vardata var; + const value_t *val; + long new_value = 0; + + val = value; + var.string = (u_char *)data; + + ret = snmp_agent_get_value_from_ds_type(val, type, scale, shift, &new_value); + if (ret != 0) + return ret; + + switch (asn_type) { + case ASN_INTEGER: + case ASN_UINTEGER: + case ASN_COUNTER: + case ASN_TIMETICKS: + case ASN_GAUGE: + if (*data_len < sizeof(*var.integer)) + return -EINVAL; + *var.integer = new_value; + *data_len = sizeof(*var.integer); + break; + case ASN_COUNTER64: + if (*data_len < sizeof(*var.counter64)) + return -EINVAL; + var.counter64->high = (u_long)((int64_t)new_value >> 32); + var.counter64->low = (u_long)((int64_t)new_value & 0xFFFFFFFF); + *data_len = sizeof(*var.counter64); + break; + case ASN_OCTET_STR: + if (type == DS_TYPE_GAUGE) { + char buf[DATA_MAX_NAME_LEN]; + snprintf(buf, sizeof(buf), "%.2f", val->gauge); + if (*data_len < strlen(buf)) + return -EINVAL; + *data_len = strlen(buf); + memcpy(var.string, buf, *data_len); + } else { + ERROR(PLUGIN_NAME ": Failed to convert %d ds type to %d asn type", type, + asn_type); + return -EINVAL; + } + break; + default: + ERROR(PLUGIN_NAME ": Failed to convert %d ds type to %d asn type", type, + asn_type); + return -EINVAL; + } + + return 0; +} + +static int snmp_agent_register_oid_index(oid_t *oid, int index, + Netsnmp_Node_Handler *handler) { + oid_t new_oid; + memcpy(&new_oid, oid, sizeof(*oid)); + new_oid.oid[new_oid.oid_len++] = index; + return snmp_agent_register_oid(&new_oid, handler); +} + +static int snmp_agent_unregister_oid_index(oid_t *oid, int index) { + oid_t new_oid; + memcpy(&new_oid, oid, sizeof(*oid)); + new_oid.oid[new_oid.oid_len++] = index; + return unregister_mib(new_oid.oid, new_oid.oid_len); +} + +static int snmp_agent_update_index(table_definition_t *td, + const char *instance) { + + if (c_avl_get(td->instance_index, instance, NULL) == 0) + return 0; + + int ret; + int *index = NULL; + char *ins; + + ins = strdup(instance); + if (ins == NULL) + return -ENOMEM; + + /* need to generate index for the table */ + if (td->index_oid.oid_len) { + index = calloc(1, sizeof(*index)); + if (index == NULL) { + sfree(ins); + return -ENOMEM; + } + + *index = c_avl_size(td->instance_index) + 1; + + ret = c_avl_insert(td->instance_index, ins, index); + if (ret != 0) { + sfree(ins); + sfree(index); + return ret; + } + + ret = c_avl_insert(td->index_instance, index, ins); + if (ret < 0) { + DEBUG(PLUGIN_NAME ": Failed to update index_instance for '%s' table", + td->name); + c_avl_remove(td->instance_index, ins, NULL, (void **)&index); + sfree(ins); + sfree(index); + return ret; + } + + ret = snmp_agent_register_oid_index(&td->index_oid, *index, + snmp_agent_table_index_oid_handler); + if (ret != 0) + return ret; + } else { + /* instance as a key is required for any table */ + ret = c_avl_insert(td->instance_index, ins, ins); + if (ret != 0) { + sfree(ins); + return ret; + } + } + + /* register new oids for all columns */ + for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) { + data_definition_t *dd = de->value; + + for (int i = 0; i < dd->oids_len; i++) { + if (td->index_oid.oid_len) { + ret = snmp_agent_register_oid_index(&dd->oids[i], *index, + snmp_agent_table_oid_handler); + } else { + ret = snmp_agent_register_oid_string(&dd->oids[i], ins, + snmp_agent_table_oid_handler); + } + + if (ret != 0) + return ret; + } + } + + DEBUG(PLUGIN_NAME ": Updated index for '%s' table [%d, %s]", td->name, + (index != NULL) ? *index : -1, ins); + + notification_t n = { + .severity = NOTIF_OKAY, .time = cdtime(), .plugin = PLUGIN_NAME}; + sstrncpy(n.host, hostname_g, sizeof(n.host)); + sstrncpy(n.plugin_instance, ins, sizeof(n.plugin_instance)); + ssnprintf(n.message, sizeof(n.message), + "Data row added to table %s instance %s index %d", td->name, ins, + (index != NULL) ? *index : -1); + plugin_dispatch_notification(&n); + + return 0; +} + +static int snmp_agent_write(value_list_t const *vl) { + + if (vl == NULL) + return -EINVAL; + + for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) { + table_definition_t *td = te->value; + + for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) { + data_definition_t *dd = de->value; + + if (!dd->is_instance) { + if (CHECK_DD_TYPE(dd, vl->plugin, vl->plugin_instance, vl->type, + vl->type_instance)) { + snmp_agent_update_index(td, vl->plugin_instance); + return 0; + } + } + } + } + + return 0; +} + +static int snmp_agent_collect(const data_set_t *ds, const value_list_t *vl, + user_data_t __attribute__((unused)) * user_data) { + + pthread_mutex_lock(&g_agent->lock); + + snmp_agent_write(vl); + + pthread_mutex_unlock(&g_agent->lock); + + return 0; +} + +static int snmp_agent_preinit(void) { + if (g_agent != NULL) { + /* already initialized if config callback was called before init callback */ + return 0; + } + + g_agent = calloc(1, sizeof(*g_agent)); + if (g_agent == NULL) { + ERROR(PLUGIN_NAME ": Failed to allocate memory for snmp agent context"); + return -ENOMEM; + } + + g_agent->tables = llist_create(); + g_agent->scalars = llist_create(); + + int err; + /* make us a agentx client. */ + err = netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE, + 1); + if (err != 0) { + ERROR(PLUGIN_NAME ": Failed to set agent role (%d)", err); + return -1; + } + + /* + * For SNMP debug purposes uses snmp_set_do_debugging(1); + */ + + /* initialize the agent library */ + err = init_agent(PLUGIN_NAME); + if (err != 0) { + ERROR(PLUGIN_NAME ": Failed to initialize the agent library (%d)", err); + return -1; + } + + init_snmp(PLUGIN_NAME); + + g_agent->tp = read_all_mibs(); + + return 0; +} + +static int snmp_agent_init(void) { + int ret; + + ret = snmp_agent_preinit(); + if (ret != 0) + return ret; + + ret = snmp_agent_register_scalar_oids(); + if (ret != 0) + return ret; + + ret = snmp_agent_register_table_oids(); + if (ret != 0) + return ret; + + /* create a second thread to listen for requests from AgentX*/ + ret = pthread_create(&g_agent->thread, NULL, &snmp_agent_thread_run, NULL); + if (ret != 0) { + ERROR(PLUGIN_NAME ": Failed to create a separate thread, err %u", ret); + return ret; + } + + ret = pthread_mutex_init(&g_agent->lock, NULL); + if (ret != 0) { + ERROR(PLUGIN_NAME ": Failed to initialize mutex, err %u", ret); + return ret; + } + + ret = pthread_mutex_init(&g_agent->agentx_lock, NULL); + if (ret != 0) { + ERROR(PLUGIN_NAME ": Failed to initialize AgentX mutex, err %u", ret); + return ret; + } + + return 0; +} + +static void *snmp_agent_thread_run(void __attribute__((unused)) * arg) { + INFO(PLUGIN_NAME ": Thread is up and running"); + + for (;;) { + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + + pthread_mutex_lock(&g_agent->agentx_lock); + agent_check_and_process(0); /* 0 == don't block */ + pthread_mutex_unlock(&g_agent->agentx_lock); + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + usleep(10); + } + + pthread_exit(0); +} + +static int snmp_agent_register_oid(oid_t *oid, Netsnmp_Node_Handler *handler) { + netsnmp_handler_registration *reg; + char *oid_name = snmp_agent_get_oid_name(oid->oid, oid->oid_len - 1); + char oid_str[DATA_MAX_NAME_LEN]; + + snmp_agent_oid_to_string(oid_str, sizeof(oid_str), oid); + + if (oid_name == NULL) { + WARNING(PLUGIN_NAME + ": Skipped registration: OID (%s) is not found in main tree", + oid_str); + return 0; + } + + reg = netsnmp_create_handler_registration(oid_name, handler, oid->oid, + oid->oid_len, HANDLER_CAN_RONLY); + if (reg == NULL) { + ERROR(PLUGIN_NAME ": Failed to create handler registration for OID (%s)", + oid_str); + return -1; + } + + pthread_mutex_lock(&g_agent->agentx_lock); + + if (netsnmp_register_instance(reg) != MIB_REGISTERED_OK) { + ERROR(PLUGIN_NAME ": Failed to register handler for OID (%s)", oid_str); + pthread_mutex_unlock(&g_agent->agentx_lock); + return -1; + } + + pthread_mutex_unlock(&g_agent->agentx_lock); + + DEBUG(PLUGIN_NAME ": Registered handler for OID (%s)", oid_str); + + return 0; +} + +static int snmp_agent_free_config(void) { + + if (g_agent == NULL) + return -EINVAL; + + for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) + snmp_agent_free_table((table_definition_t **)&te->value); + llist_destroy(g_agent->tables); + + for (llentry_t *de = llist_head(g_agent->scalars); de != NULL; de = de->next) + snmp_agent_free_data((data_definition_t **)&de->value); + llist_destroy(g_agent->scalars); + + return 0; +} + +static int snmp_agent_shutdown(void) { + int ret = 0; + + DEBUG(PLUGIN_NAME ": snmp_agent_shutdown"); + + if (g_agent == NULL) { + ERROR(PLUGIN_NAME ": snmp_agent_shutdown: plugin not initialized"); + return -EINVAL; + } + + if (pthread_cancel(g_agent->thread) != 0) + ERROR(PLUGIN_NAME ": snmp_agent_shutdown: failed to cancel the thread"); + + if (pthread_join(g_agent->thread, NULL) != 0) + ERROR(PLUGIN_NAME ": snmp_agent_shutdown: failed to join the thread"); + + snmp_agent_free_config(); + + snmp_shutdown(PLUGIN_NAME); + + pthread_mutex_destroy(&g_agent->lock); + pthread_mutex_destroy(&g_agent->agentx_lock); + + sfree(g_agent); + + return ret; +} + +static int snmp_agent_config(oconfig_item_t *ci) { + + int ret = snmp_agent_preinit(); + + if (ret != 0) { + sfree(g_agent); + return -EINVAL; + } + + for (int i = 0; i < ci->children_num; i++) { + oconfig_item_t *child = ci->children + i; + if (strcasecmp("Data", child->key) == 0) { + ret = snmp_agent_config_data(child); + } else if (strcasecmp("Table", child->key) == 0) { + ret = snmp_agent_config_table(child); + } else { + ERROR(PLUGIN_NAME ": Unknown configuration option `%s'", child->key); + ret = (-EINVAL); + } + + if (ret != 0) { + ERROR(PLUGIN_NAME ": Failed to parse configuration"); + snmp_agent_free_config(); + snmp_shutdown(PLUGIN_NAME); + sfree(g_agent); + return -EINVAL; + } + } + + ret = snmp_agent_validate_data(); + if (ret != 0) { + ERROR(PLUGIN_NAME ": Invalid configuration provided"); + snmp_agent_free_config(); + snmp_shutdown(PLUGIN_NAME); + sfree(g_agent); + return -EINVAL; + } + + return 0; +} + +void module_register(void) { + plugin_register_init(PLUGIN_NAME, snmp_agent_init); + plugin_register_complex_config(PLUGIN_NAME, snmp_agent_config); + plugin_register_write(PLUGIN_NAME, snmp_agent_collect, NULL); + plugin_register_missing(PLUGIN_NAME, snmp_agent_clear_missing, NULL); + plugin_register_shutdown(PLUGIN_NAME, snmp_agent_shutdown); +}