500 lines
12 KiB
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;
|
||
|
}
|