snmp2rrd/snmp.c

500 lines
12 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#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 "<unknown>";
}
}
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;
}