/*
* Copyright (c) 2002, 2003, 2004 Niels Provos <provos@citi.umich.edu>
* 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 <sys/types.h>
#include <sys/param.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/socket.h>
#include <sys/queue.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <assert.h>
#include <syslog.h>
#include <event.h>
#include <dnet.h>
#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__);
}
syntax highlighted by Code2HTML, v. 0.9.1