#include #include #include #include #include #include #include "configfile.h" #include "event.h" #include "linebuffer.h" #include "list.h" #include "logging.h" #include "rrdtool.h" #define MAX_PORT_VALUES 8 #define MAX_RRD_DEFINE_SIZE 512 #define MAX_RRD_RESULT_SIZE 128 struct sw_porttype { struct list_head list; // global list of sw_porttype(s) const char *name; // "es2024port" const char *oid_base[MAX_PORT_VALUES]; // "IF-MIB::IfInOctets" int value_count; // number of snmp values struct lbuf *rrd_definition; // rrd/rra definition }; struct sw_port { struct list_head list; // per sw_hosttype list of sw_port(s) const char *name; // "port01" struct sw_porttype *porttype; const char *oid_ext; // "1" }; struct sw_hosttype { struct list_head list; // global list of sw_hosttype(s) const char *name; // "es2024" struct list_head ports; // list of sw_port(s) int port_count; // number of ports }; struct sw_host { struct list_head list; // global list of sw_host(s) const char *name; // "router01" struct sw_hosttype *hosttype; int active; struct sw_port *current_port; // current port int current_value; // current value/oid struct lbuf *result; struct snmp_session *session; // snmp private data }; static int snmp_interval; static LIST_HEAD(porttype_list); static LIST_HEAD(hosttype_list); static LIST_HEAD(host_list); static void log_snmp_error(struct snmp_session *session, const char *prefix) { // char *snmp_error_str = NULL; // snmp_perror("read_objid"); // snmp_error(session, &errno, &snmp_errno, &snmp_error_str); log_print(LOG_INFO, "%s: '%s'", prefix, snmp_api_errstring(snmp_errno)); snmp_errno = 0; } int snmp_pre_select_cb(int *maxfd, void *readfds, void *writefds, struct timeval *timeout, void *privdata) { int block = 1; snmp_select_info(maxfd, (fd_set *)readfds, timeout, &block); return 0; } int snmp_post_select_cb(int retval, void *readfds, void *writefds, void *privdata) { if (retval != 0) { snmp_read((fd_set *)readfds); } else { snmp_timeout(); } return 0; } static int add_portvalue_cb(const char *parameter, void *privdata) { struct sw_porttype *porttype = (struct sw_porttype *)privdata; struct strtoken *tokens = strtokenize(parameter, ",", 2); if (tokens == NULL) { log_print(LOG_ERROR, "add_portvalue_cb(): out of memory(1)"); return 1; } if (tokens->count != 2) { log_print(LOG_ERROR, "add_portvalue_cb(): invalid value line"); free(tokens); return 1; } if (porttype->value_count < MAX_PORT_VALUES -1) { porttype->oid_base[porttype->value_count++] = strdup(tokens->field[0]); } else { log_print(LOG_WARN, "add_portvalue_cb(): too many oids for %s", porttype->name); free(tokens); return 1; } if (lbuf_printf(porttype->rrd_definition, "%s ", tokens->field[1]) < 0) { log_print(LOG_WARN, "add_portvalue_cb(): truncated rrd definition for %s", porttype->name); free(tokens); return 1; } free(tokens); return 0; } static int add_portdef_cb(const char *parameter, void *privdata) { struct sw_porttype *porttype = (struct sw_porttype *)privdata; if (lbuf_printf(porttype->rrd_definition, "%s ", parameter) < 0) { log_print(LOG_WARN, "add_portdef_cb(): truncated rrd definition for %s", porttype->name); return 1; } return 0; } static int add_porttype_cb(const char *parameter, void *privdata) { struct sw_porttype *porttype = malloc(sizeof(struct sw_porttype)); if (porttype == NULL) { log_print(LOG_WARN, "add_porttype_cb(): out of memory"); return 1; } memset(porttype, 0, sizeof(struct sw_porttype)); porttype->name = strdup(parameter); porttype->rrd_definition = lbuf_create(MAX_RRD_DEFINE_SIZE); char section[32]; snprintf(section, sizeof(section), "porttype_%s", parameter); config_get_strings(section, "value", add_portvalue_cb, (void *)porttype); config_get_strings(section, "rra", add_portdef_cb, (void *)porttype); if (porttype->value_count > 0) { list_add_tail(&porttype->list, &porttype_list); log_print(LOG_INFO, "adding porttype '%s' with %d snmp values", porttype->name, porttype->value_count); } else { lbuf_free(porttype->rrd_definition); free((char *)porttype->name); free(porttype); } return 0; } static struct sw_porttype * find_porttype_by_name(const char *name) { struct sw_porttype *porttype; list_for_each_entry(porttype, &porttype_list, list) { if (strcmp(porttype->name, name) == 0) return porttype; } return NULL; } static int add_port_cb(const char *parameter, void *privdata) { struct sw_hosttype *hosttype = (struct sw_hosttype *)privdata; struct strtoken *tokens = strtokenize(parameter, ",", 3); if (tokens == NULL) { log_print(LOG_ERROR, "add_port_cb(): out of memory(1)"); return 1; } if (tokens->count != 3) { log_print(LOG_ERROR, "add_port_cb(): invalid port line"); free(tokens); return 1; } struct sw_port *port = malloc(sizeof(struct sw_port)); if (port == NULL) { log_print(LOG_WARN, "add_port_cb(): out of memory(2)"); free(tokens); return 1; } port->porttype = find_porttype_by_name(tokens->field[1]); if (port->porttype == NULL) { log_print(LOG_WARN, "add_port_cb(): invalid porttype '%s'", tokens->field[1]); free(port); free(tokens); return 1; } port->name = strdup(tokens->field[0]); port->oid_ext = strdup(tokens->field[2]); list_add_tail(&port->list, &hosttype->ports); hosttype->port_count++; free(tokens); return 0; } static int add_hosttype_cb(const char *parameter, void *privdata) { struct sw_hosttype *hosttype = malloc(sizeof(struct sw_hosttype)); if (hosttype == NULL) { log_print(LOG_WARN, "add_hosttype_cb(): out of memory"); return 1; } memset(hosttype, 0, sizeof(struct sw_hosttype)); hosttype->name = strdup(parameter); INIT_LIST_HEAD(&hosttype->ports); char section[32]; snprintf(section, sizeof(section), "hosttype_%s", parameter); config_get_strings(section, "port", add_port_cb, (void *)hosttype); if (hosttype->port_count > 0) { list_add_tail(&hosttype->list, &hosttype_list); log_print(LOG_INFO, "adding hosttype '%s' with %d ports", hosttype->name, hosttype->port_count); } else { free((char *)hosttype->name); free(hosttype); } return 0; } static struct sw_hosttype * find_hosttype_by_name(const char *name) { struct sw_hosttype *hosttype; list_for_each_entry(hosttype, &hosttype_list, list) { if (strcmp(hosttype->name, name) == 0) return hosttype; } return NULL; } static int snmp_async_cb(int operation, struct snmp_session *sp, int reqid, struct snmp_pdu *pdu, void *magic); static int add_host_cb(const char *parameter, void *privdata) { struct strtoken *tokens = strtokenize(parameter, ",", 4); if (tokens == NULL) { log_print(LOG_ERROR, "add_host_cb(): out of memory(1)"); return 1; } if (tokens->count != 4) { log_print(LOG_ERROR, "add_host_cb(): invalid host line"); return 1; } struct sw_host *host = malloc(sizeof(struct sw_host)); if (host == NULL) { log_print(LOG_ERROR, "add_host_cb(): out of memory(2)"); free(tokens); return 1; } host->hosttype = find_hosttype_by_name(tokens->field[1]); if (host->hosttype == NULL) { log_print(LOG_ERROR, "add_host_cb(): invalid hosttype '%s'", tokens->field[1]); free(host); free(tokens); return 1; } host->result = lbuf_create(MAX_RRD_RESULT_SIZE); if (host->result == NULL) { log_print(LOG_ERROR, "add_host_cb(): out of memory(3)"); free(host); free(tokens); return 1; } host->name = strdup(tokens->field[0]); struct snmp_session sess; snmp_sess_init(&sess); sess.version = SNMP_VERSION_2c; sess.peername = strdup(tokens->field[2]); sess.community = (unsigned char *)strdup(tokens->field[3]); sess.community_len = strlen((char *)sess.community); sess.callback = snmp_async_cb; sess.callback_magic = host; if ((host->session = snmp_open(&sess)) == NULL) { log_snmp_error(&sess, "add_host_cb(): snmp_open()"); free((char *)sess.community); free((char *)host->name); lbuf_free(host->result); free(host->hosttype); free(host); free(tokens); return 1; } host->active = 0; host->current_port = NULL; host->current_value = 0; log_print(LOG_INFO, "adding host %s", host->session->peername); list_add_tail(&host->list, &host_list); free((char *)sess.community); return 0; } static int do_snmp_poll(void *privdata); int snmp_init(void) { init_snmp("snmp2rrd"); errno = 0; config_get_strings("global", "porttype", add_porttype_cb, NULL); config_get_strings("global", "hosttype", add_hosttype_cb, NULL); config_get_strings("global", "host", add_host_cb, NULL); config_get_int("global", "interval", &snmp_interval, 60); struct timeval tv_interval = { .tv_sec = snmp_interval, .tv_usec = 0, }; event_add_timeout(&tv_interval, do_snmp_poll, NULL); do_snmp_poll(NULL); return 0; } static int send_next_request(struct sw_host *host) { /* first port */ if (host->current_port == NULL) { host->current_port = list_entry(host->hosttype->ports.next, struct sw_port, list); host->current_value = 0; // use "Now" as timespec in RRD commit lbuf_clear(host->result); lbuf_printf(host->result, "N"); } else { host->current_value++; if (host->current_value >= host->current_port->porttype->value_count) { char filename[64]; snprintf(filename, sizeof(filename), "%s/%s.rrd", host->name, host->current_port->name); char *rrd_definition = lbuf_getdata(host->current_port->porttype->rrd_definition, NULL); char *result = lbuf_getdata(host->result, NULL); rrd_submit(filename, rrd_definition, snmp_interval, result); // log_print(LOG_INFO, "%s %s %d %s", filename, rrd_definition, snmp_interval, result); // check if this is the last port if (host->current_port->list.next == &host->hosttype->ports) { host->current_port = NULL; host->active = 0; return -1; } // get next port host->current_port = list_entry(host->current_port->list.next, struct sw_port, list); host->current_value = 0; // use "Now" as timespec in RRD commit lbuf_clear(host->result); lbuf_printf(host->result, "N"); } } char buf[128]; snprintf(buf, sizeof(buf), "%s.%s", host->current_port->porttype->oid_base[host->current_value], host->current_port->oid_ext); // log_print(LOG_INFO, "polling: %-10s %-6s %2d => %s", host->name, host->current_port->name, host->current_value, buf); oid oidx[MAX_OID_LEN]; size_t oid_len = MAX_OID_LEN; if (!read_objid(buf, oidx, &oid_len)) { log_snmp_error(host->session, "send_next_request(): read_objid()"); return -1; } struct snmp_pdu *req = snmp_pdu_create(SNMP_MSG_GET); snmp_add_null_var(req, oidx, oid_len); if (!snmp_send(host->session, req)) { log_snmp_error(host->session, "send_next_request(): read_objid()"); snmp_free_pdu(req); return -1; } return 0; } static const char * snmp_get_op_str(int operation) { switch (operation) { case NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE: return "OP_RECEIVED_MESSAGE"; case NETSNMP_CALLBACK_OP_TIMED_OUT: return "OP_TIMED_OUT"; case NETSNMP_CALLBACK_OP_SEND_FAILED: return "OP_SEND_FAILED"; case NETSNMP_CALLBACK_OP_CONNECT: return "OP_CONNECT"; case NETSNMP_CALLBACK_OP_DISCONNECT: return "OP_DISCONNECT"; default: return ""; } } static int snmp_async_cb(int operation, struct snmp_session *sp, int reqid, struct snmp_pdu *pdu, void *magic) { struct sw_host *host = (struct sw_host *)magic; if (operation == NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE) { struct variable_list *vp = pdu->variables; if (pdu->errstat == SNMP_ERR_NOERROR) { // print_variable(vp->name, vp->name_length, vp); switch (vp->type) { case ASN_GAUGE: case ASN_COUNTER: lbuf_printf(host->result, ":%d", *vp->val.integer); break; case ASN_COUNTER64: lbuf_printf(host->result, ":%lld", (uint64_t)vp->val.counter64->high * 0x100000000ULL + (uint64_t)vp->val.counter64->low); break; default: lbuf_printf(host->result, ":U"); break; } } else { lbuf_printf(host->result, ":U"); } } else if (operation == NETSNMP_CALLBACK_OP_TIMED_OUT) { log_print(LOG_WARN, "snmp_async_cb(%s): on host %s", snmp_get_op_str(operation), host->name); host->current_port = NULL; host->active = 0; } else { log_print(LOG_WARN, "snmp_async_cb(%s) on host %s", snmp_get_op_str(operation), host->name); } if (host->active) send_next_request(host); return 1; } static int do_snmp_poll(void *privdata) { struct sw_host *host; list_for_each_entry(host, &host_list, list) { if (host->active) { log_print(LOG_WARN, "host '%s' still active", host->name); continue; } host->active = 1; send_next_request(host); } return 0; }