/* * Copyright (c) 2002, 2003, 2004 Niels Provos * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #ifdef HAVE_SYS_TIME_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rrdtool.h" extern rand_t *honeyd_rand; static void rrdtool_restart(int, short, void *); static void rrdtool_write_command(struct rrdtool_drv *, char *); /* * Very simple RRDTOOL driver */ void rrdtool_evb_readcb(struct bufferevent *bev, void *parameter) { struct rrdtool_drv *req = parameter; char *start, *end; start = EVBUFFER_DATA(bev->input); if ((end = evbuffer_find(bev->input, "OK ", 3)) == NULL) return; /* Find the end of the line */ if (strchr(end, '\n') == NULL) return; /* Communicate everything before the OK to the call back */ *end = '\0'; rrdtool_command_done(req, start); /* * We drain all the input because we do not currently interleave * commands. */ evbuffer_drain(bev->input, -1); return; } void rrdtool_evb_writecb(struct bufferevent *bev, void *parameter) { /* We wrote all of the command - now read the response */ bufferevent_enable(bev, EV_READ); } /* * We start this timeout if we spam the rrdtool too fast. */ static void rrdtool_restart(int fd, short what, void *arg) { struct rrdtool_drv *drv = arg; struct timeval tv; /* Terminate the bugger */ if (drv->fd != -1) { close(drv->fd); drv->fd = -1; } if (drv->pid != 0) { kill(drv->pid, SIGTERM); drv->pid = 0; } /* If we respawn too quickly, we need to wait a little whilte */ gettimeofday(&tv, NULL); timersub(&tv, &drv->tv_started, &tv); if (tv.tv_sec < 5) { syslog(LOG_NOTICE, "Respawing rrdtool too quickly"); tv.tv_sec = 5; tv.tv_usec = 0; evtimer_add(&drv->ev_timeout, &tv); return; } /* Bad hack - we need to disable all events */ bufferevent_disable(drv->evb, EV_READ|EV_WRITE); if (rrdtool_fork(drv) == -1) { syslog(LOG_WARNING, "Terminating rrdtool driver."); rrdtool_free(drv); } else { struct rrdtool_command *cmd = TAILQ_FIRST(&drv->commands); /* This is yet another bad hack */ drv->evb->ev_read.ev_fd = drv->fd; drv->evb->ev_write.ev_fd = drv->fd; bufferevent_enable(drv->evb, EV_WRITE); /* Restart the last command */ if (cmd != NULL) rrdtool_write_command(drv, cmd->command); } } void rrdtool_evb_errcb(struct bufferevent *bev, short what, void *parameter) { struct rrdtool_drv *drv = parameter; syslog(LOG_NOTICE, "rrdtool returning errors - restarting."); rrdtool_restart(-1, EV_TIMEOUT, drv); } /* * Creates a new rrdtool process */ struct rrdtool_drv * rrdtool_init(const char *path_rrdtool) { struct rrdtool_drv *rrd; if ((rrd = calloc(1, sizeof(struct rrdtool_drv))) == NULL) { warn("%s: calloc", __func__); goto error; } /* Remember the path so that we can respawn */ rrd->bin_path = path_rrdtool; if (rrdtool_fork(rrd) == -1) { goto error; } if ((rrd->evb = bufferevent_new(rrd->fd, rrdtool_evb_readcb, rrdtool_evb_writecb, rrdtool_evb_errcb, rrd)) == NULL) goto error; TAILQ_INIT(&rrd->commands); bufferevent_disable(rrd->evb, EV_READ); bufferevent_enable(rrd->evb, EV_WRITE); evtimer_set(&rrd->ev_timeout, rrdtool_restart, rrd); return rrd; error: if (rrd != NULL) { if (rrd->fd != -1) close(rrd->fd); free(rrd); } return (NULL); } /* * Frees a rrdtool driver structure */ void rrdtool_free(struct rrdtool_drv *drv) { event_del(&drv->ev_timeout); bufferevent_free(drv->evb); if (drv->fd != -1) { close(drv->fd); drv->fd = -1; } if (drv->pid != 0) { kill(drv->pid, SIGTERM); drv->pid = 0; } free(drv); } void rrdtool_db_free(struct rrdtool_db *db) { int i; for (i = 0; i < db->ndatasrcs; i++) free(db->datasrcs[i]); free(db); } /* Write a single command to rrdtool */ static void rrdtool_write_command(struct rrdtool_drv *drv, char *command) { bufferevent_write(drv->evb, command, strlen(command)); if (command[strlen(command) - 1] != '\n') bufferevent_write(drv->evb, "\n", 1); } void rrdtool_command_done(struct rrdtool_drv *drv, char *result) { struct rrdtool_command *cmd = TAILQ_FIRST(&drv->commands); assert(cmd != NULL); TAILQ_REMOVE(&drv->commands, cmd, next); if (cmd->cb != NULL) (*cmd->cb)(result, cmd->cb_arg); free(cmd->command); free(cmd); bufferevent_disable(drv->evb, EV_READ); if ((cmd = TAILQ_FIRST(&drv->commands)) != NULL) { rrdtool_write_command(drv, cmd->command); } } void rrdtool_command(struct rrdtool_drv *drv, char *command, void (*cb)(char *, void *), void *cb_arg) { struct rrdtool_command *cmd; if ((cmd = calloc(1, sizeof(struct rrdtool_command))) == NULL) err(1, "%s: calloc", __func__); if ((cmd->command = strdup(command)) == NULL) err(1, "%s: strdup", __func__); cmd->cb = cb; cmd->cb_arg = cb_arg; if (TAILQ_FIRST(&drv->commands) == NULL) rrdtool_write_command(drv, command); TAILQ_INSERT_TAIL(&drv->commands, cmd, next); } /* * Create a new data base. */ struct rrdtool_db * rrdtool_db_start(struct rrdtool_drv *drv, char *filename, int stepsize) { struct rrdtool_db *db; if ((db = calloc(1, sizeof(struct rrdtool_db))) == NULL) err(1, "%s: calloc", __func__); db->drv = drv; strlcpy(db->rrd_file, filename, sizeof(db->rrd_file)); snprintf(db->create_command, sizeof(db->create_command), "create %s --step %d", filename, stepsize); return (db); } int rrdtool_db_datasource(struct rrdtool_db *db, char *name, char *type, int heartbeat) { char line[1024]; if (db->ndatasrcs >= MAX_RRD_DATASRCS) { warnx("%s: too many data sources", __func__); return (-1); } if (strcasecmp(type, "COUNTER") && strcasecmp(type, "GAUGE")) { warnx("%s: bad data source type: %s", __func__, type); return (-1); } snprintf(line, sizeof(line), "DS:%s:%s:%d:U:U", name, type, heartbeat); if ((db->datasrcs[db->ndatasrcs++] = strdup(line)) == NULL) err(1, "%s: strdup"); strlcat(db->create_command, " ", sizeof(db->create_command)); if ( strlcat(db->create_command, line, sizeof(db->create_command)) >= sizeof(db->create_command)) { warnx("%s: command too long", __func__); return (-1); } return (0); } static void rrdtool_db_commit_cb(char *result, void *arg) { struct rrdtool_db *db = arg; if (strlen(result) && strncasecmp(result, "ERROR", 5) == 0) { result[strlen(result) - 1] = '\0'; syslog(LOG_NOTICE, "rrdtool create of %s: %s", db->rrd_file, result); } } int rrdtool_db_commit(struct rrdtool_db *db) { if (strlcat(db->create_command, " " "RRA:AVERAGE:0.5:1:600 " "RRA:AVERAGE:0.5:6:700 " "RRA:AVERAGE:0.5:24:775 " "RRA:AVERAGE:0.5:288:797 " "RRA:MAX:0.5:1:600 " "RRA:MAX:0.5:6:700 " "RRA:MAX:0.5:24:775 " "RRA:MAX:0.5:288:797", sizeof(db->create_command)) >= sizeof(db->create_command)) { warnx("%s: command too long", __func__); return (-1); } /* Don't clobber the database if we do not have to */ if (access(db->rrd_file, W_OK) == -1) { rrdtool_command(db->drv, db->create_command, rrdtool_db_commit_cb, db); } return (0); } /* * Takes a line of descriptions and updates the database. */ int rrdtool_db_update(struct rrdtool_db *db, struct timeval *tv, char *update) { char line[1024]; struct timeval tv_now; if (tv == NULL) { gettimeofday(&tv_now, NULL); tv = &tv_now; } snprintf(line, sizeof(line), "update %s %ld:%s", db->rrd_file, tv->tv_sec, update); rrdtool_command(db->drv, line, NULL, NULL); return (0); } /* * Create a graph for the specified location */ void rrdtool_graph(struct rrdtool_db *db, char *filename, struct timeval *tv_start, struct timeval *tv_end, char *spec) { char line[1024]; struct timeval tv; gettimeofday(&tv, NULL); if (tv_start == NULL) tv_start = &tv; else if (tv_start->tv_sec < 0) { /* Negative start time is subtracted from current time */ timeradd(tv_start, &tv, tv_start); } if (tv_end == NULL) tv_end = &tv; snprintf(line, sizeof(line), "graph %s --start %ld --end %ld %s", filename, tv_start->tv_sec, tv_end->tv_sec, spec); rrdtool_command(db->drv, line, NULL, NULL); } int rrdtool_fork(struct rrdtool_drv *drv) { const char *argv[3]; int pair[2]; sigset_t sigmask; argv[0] = drv->bin_path; argv[1] = "-"; argv[2] = NULL; if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) return (-1); /* Block SIGCHLD */ sigemptyset(&sigmask); sigaddset(&sigmask, SIGCHLD); if (sigprocmask(SIG_BLOCK, &sigmask, NULL) == -1) { warn("sigprocmask"); goto fork_err; } /* * Record the time that we tried to fork, so that we can see * if we get too many errors. */ gettimeofday(&drv->tv_started, NULL); drv->pid = fork(); if (drv->pid == -1) { warn("fork"); goto unmask_err; } if (drv->pid == 0) { /* Child */ close(pair[0]); if (dup2(pair[1], fileno(stdout)) == -1) err(1, "%s: dup2", __func__); if (dup2(pair[1], fileno(stdin)) == -1) err(1, "%s: dup2", __func__); close(pair[1]); if (execvp(drv->bin_path, (char * const*)argv) == -1) err(1, "%s: execv(%s)", __func__, drv->bin_path); /* NOT REACHED */ } close(pair[1]); drv->fd = pair[0]; if (fcntl(drv->fd, F_SETFD, 1) == -1) warn("fcntl(F_SETFD)"); if (fcntl(drv->fd, F_SETFL, O_NONBLOCK) == -1) warn("fcntl(F_SETFL)"); /* Install old signal handler */ if (sigprocmask(SIG_UNBLOCK, &sigmask, NULL) == -1) { warn("sigprocmask"); goto fork_err; } return (0); /* Error cleanup */ unmask_err: /* Install old signal handler */ if (sigprocmask(SIG_UNBLOCK, &sigmask, NULL) == -1) warn("sigprocmask"); fork_err: close(pair[0]); close(pair[1]); drv->fd = -1; return (-1); } /* * ----------- * Unittesting * ----------- */ void rrdtool_test_done(char *something, void *arg) { struct timeval tv; timerclear(&tv); tv.tv_sec = 1; event_loopexit(&tv); } void rrdtool_test(void) { extern char *honeyd_rrdtool_path; struct rrdtool_drv *drv; struct rrdtool_db *db; struct timeval tv, tv_now; char line[1024]; int i, in = 0, out = 0; drv = rrdtool_init(honeyd_rrdtool_path); assert(drv != NULL); db = rrdtool_db_start(drv, "/tmp/myrouter.rrd", 300); assert(db != NULL); rrdtool_db_datasource(db, "input", "COUNTER", 600); rrdtool_db_datasource(db, "output", "COUNTER", 600); rrdtool_db_commit(db); gettimeofday(&tv, NULL); tv_now = tv; for (i = 0; i < 500; i++) { tv.tv_sec += 60; in += (i*5) % 500; out += i % 400 + rand_uint16(honeyd_rand) % 1000; snprintf(line, sizeof(line), "%u:%u", in, out); rrdtool_db_update(db, &tv, line); } unlink("/tmp/honeyd_myrouter.gif"); snprintf(line, sizeof(line), "graph /tmp/honeyd_myrouter.gif --start %ld --end %ld " "--height 300 --width 600 " "DEF:inoctets=/tmp/myrouter.rrd:input:AVERAGE " "DEF:outoctets=/tmp/myrouter.rrd:output:AVERAGE " "CDEF:mout=outoctets,-1,* " "AREA:inoctets#00FF00:\"In traffic\" " "AREA:mout#0000FF:\"Out traffic\"", tv_now.tv_sec, tv.tv_sec); rrdtool_command(drv, line, rrdtool_test_done, NULL); event_dispatch(); if (access("/tmp/honeyd_myrouter.gif", R_OK) == -1) errx(1, "%s: graph creation failed", __func__); rrdtool_free(drv); fprintf(stderr, "\t%s: OK\n", __func__); }