diff options
| author | Lucas Castro <lucas@gnuabordo.com.br> | 2024-01-10 22:33:44 -0300 |
|---|---|---|
| committer | Lucas Castro <lucas@gnuabordo.com.br> | 2024-01-10 22:33:44 -0300 |
| commit | a4270020addc324fb60d2b1b446b41bc31e3baf8 (patch) | |
| tree | 8708f08c66a63f08c0cd88ab1a46faadcd3f4706 | |
Import Upstream version 1.0.4upstream/1.0.4
| -rw-r--r-- | Makefile | 72 | ||||
| -rw-r--r-- | README | 85 | ||||
| -rw-r--r-- | cksum.c | 35 | ||||
| -rw-r--r-- | cksum.h | 18 | ||||
| -rw-r--r-- | cmdline.c | 59 | ||||
| -rw-r--r-- | cmdline.h | 16 | ||||
| -rw-r--r-- | config.c | 762 | ||||
| -rw-r--r-- | config.h | 91 | ||||
| -rw-r--r-- | default_script | 67 | ||||
| -rw-r--r-- | default_script.sample | 14 | ||||
| -rw-r--r-- | defs.h | 29 | ||||
| -rw-r--r-- | forkexec.c | 390 | ||||
| -rw-r--r-- | forkexec.h | 29 | ||||
| -rw-r--r-- | globals.c | 126 | ||||
| -rw-r--r-- | globals.h | 36 | ||||
| -rw-r--r-- | group_script | 55 | ||||
| -rw-r--r-- | icmp6_t.c | 118 | ||||
| -rw-r--r-- | icmp6_t.h | 22 | ||||
| -rw-r--r-- | icmp_t.c | 340 | ||||
| -rw-r--r-- | icmp_t.h | 22 | ||||
| -rw-r--r-- | lsm.c | 2162 | ||||
| -rw-r--r-- | lsm.conf | 56 | ||||
| -rw-r--r-- | lsm.conf.sample | 144 | ||||
| -rw-r--r-- | lsm.h | 72 | ||||
| -rw-r--r-- | lsm.init | 97 | ||||
| -rw-r--r-- | lsm.service | 14 | ||||
| -rw-r--r-- | lsm.spec | 883 | ||||
| -rw-r--r-- | pidfile.c | 89 | ||||
| -rw-r--r-- | pidfile.h | 18 | ||||
| -rw-r--r-- | plugin_export.c | 259 | ||||
| -rw-r--r-- | plugin_export.h | 28 | ||||
| -rw-r--r-- | rsyslog-lsm.conf.sample | 3 | ||||
| -rw-r--r-- | save_statuses.c | 93 | ||||
| -rw-r--r-- | save_statuses.h | 17 | ||||
| -rw-r--r-- | shorewall6_script | 52 | ||||
| -rw-r--r-- | shorewall_script | 53 | ||||
| -rw-r--r-- | signal_handler.c | 38 | ||||
| -rw-r--r-- | signal_handler.h | 16 | ||||
| -rw-r--r-- | timecalc.c | 56 | ||||
| -rw-r--r-- | timecalc.h | 22 | ||||
| -rw-r--r-- | usage.c | 30 | ||||
| -rw-r--r-- | usage.h | 16 |
42 files changed, 6604 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..997d856 --- /dev/null +++ b/Makefile @@ -0,0 +1,72 @@ +# +# (C) 2009-2011 Mika Ilmaranta <ilmis@nullnet.fi> +# +# License: GPLv2 +# + +VERSION ?= $(lastword $(shell grep ^Version: lsm.spec)) +PROGS = lsm +PKG = lsm + +CC = gcc +override CFLAGS += -Wall -O2 -DLSM_VERSION=\"$(VERSION)\" +#override CFLAGS += -D NO_PLUGIN_EXPORT +#override CFLAGS += -D NO_PLUGIN_EXPORT_MUNIN +#override CFLAGS += -D NO_PLUGIN_EXPORT_STATUS + +LDFLAGS = + +INITDDIR ?= /etc/init.d +DOCDIR ?= /usr/share/doc/lsm + +DOCFILES = README lsm.conf.sample default_script.sample rsyslog-lsm.conf.sample +SCRIPTS = shorewall_script shorewall6_script default_script group_script + +.PHONY: all clean distclean tar rpm + +all: $(PROGS) + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +lsm: lsm.o icmp_t.o icmp6_t.o config.o globals.o cksum.o forkexec.o signal_handler.o timecalc.o plugin_export.o save_statuses.o pidfile.o cmdline.o usage.o + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +clean distclean: + rm -rf *~ .*~ *.o $(PROGS) Makefile.depend debugfiles.list debuglinks.list debugsources.list *.orig + +tar: distclean + tar zcvf ../$(PKG)-$(VERSION).tar.gz \ + --transform=s,.,$(PKG)-$(VERSION), \ + --show-transformed-name \ + --exclude .git \ + --exclude .gitignore \ + . + +rpm: tar + cp ../$(PKG)-$(VERSION).tar.gz ~/rpmbuild/SOURCES + cp $(PKG).spec ~/rpmbuild/SPECS + rpmbuild -ba ~/rpmbuild/SPECS/$(PKG).spec + +Makefile.depend: *.c *.h Makefile + $(CC) -MM *.c > $@ + +install: all + mkdir -p $(DESTDIR)/etc/lsm + mkdir -p $(DESTDIR)$(INITDDIR) + mkdir -p $(DESTDIR)/usr/sbin + mkdir -p $(DESTDIR)$(DOCDIR) + mkdir -p $(DESTDIR)/usr/share/lsm + mkdir -p $(DESTDIR)/usr/libexec/lsm + mkdir -p $(DESTDIR)/var/lib/lsm + install -m0644 lsm.conf $(DESTDIR)/etc/lsm + install -m0755 lsm.init $(DESTDIR)$(INITDDIR)/lsm + install -m0755 lsm $(DESTDIR)/usr/sbin + install -m0644 $(DOCFILES) $(DESTDIR)$(DOCDIR) + install -m0755 $(SCRIPTS) $(DESTDIR)/usr/libexec/lsm + ln -sf ../../libexec/lsm/default_script $(DESTDIR)/usr/share/lsm/default_script + ln -sf ../../libexec/lsm/shorewall_script $(DESTDIR)/usr/share/lsm/shorewall_script + +-include Makefile.depend + +# @@ -0,0 +1,85 @@ +README +====== + +License: GPLv2 + +You should be able to find GNU Public License from www.gnu.org. + + +LSM is a Link Status Monitor which can be used to monitor +for example a Linux router/firewall connectivity and if +you happen to have multiple connections it can change +routing when an up/down event happens by utilizing external +script. + +When a SIGUSR1 is received current connection states are +syslogged. + +This package is highly influenced by fping and iputils arping. +Many thanks for their efforts. + + +TODO +==== + +- Add "odd" icmp packet handling (may not be necessary) +- You can't set source ip for ping packets. It's always + autodiscovered. +- Could the arping connection targets use only one socket ... +- IPv6 support. +- Should be able to check ENODEV somehow before first sendto. + Now exits with error code 2 after daemon which init-script + can't handle. + * This is now changed so that ENODEV causes LOG_ERR with each ping + but no exit and behave just as the packet was sent. So for ENODEV + you should get a down -event just as if there was packet loss. +- The source is kind of ugly ... + + +DECISION MAKING +=============== +As of lsm v0.27, the detection algorithm works like this: + +LSM keeps track of + 1) the result of the most recent pings (up to 100) + 2) the number of consecutive lost pings. + 3) the number of consecutive received pings. + +Let A == the number of recently lost pings +Let B == the number of consecutive lost pings +Let C == the number of consecutive returned pings + +If connection (or group) is UP + AND ( (A >= max_packet_loss) OR (B >= max_successive_pkts_lost) + then change the connection (or group) to down. + +Else If connection (or group) is DOWN + AND ( (A <= min_packet_loss) AND (C > min_successive_pkts_rcved) ) + then change the connection (or group) to up. + +Note: LSM assumes each connection starts UP. + + +DEPENDENCIES +============ + +default_script uses /bin/mail that's the only reason to depend on +mailx-package. + + +INSTALLING +========== + +There is now Makefile target install for those of you not using rpms. I use only +rpm based distros so that may not be in sync with lsm.spec which does all what +I need. + + +AUTHORS +======= + +Mika Ilmaranta <ilmis@nullnet.fi> + +See lsm.spec's changelog section for patch submitters. + +#EOF @@ -0,0 +1,35 @@ +/* + +(C) 2009-2011 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#include "cksum.h" + +int in_cksum(u_short *p, int n) +{ + register u_short answer; + register long sum = 0; + u_short odd_byte = 0; + + while(n > 1) { + sum += *p++; + n -= 2; + } + + /* mop up an odd byte, if necessary */ + if(n == 1){ + *(u_char*)(&odd_byte) = *(u_char*)p; + sum += odd_byte; + } + + sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + answer = ~sum; /* ones-complement, truncate */ + + return(answer); +} + +/* EOF */ @@ -0,0 +1,18 @@ +/* + +(C) 2009-2011 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#ifndef __CKSUM_H__ +#define __CKSUM_H__ + +#include <sys/types.h> + +int in_cksum(u_short *p, int n); + +#endif + +/* EOF */ diff --git a/cmdline.c b/cmdline.c new file mode 100644 index 0000000..9864c89 --- /dev/null +++ b/cmdline.c @@ -0,0 +1,59 @@ +/* + + (C) 2014 Mika Ilmaranta <ilmis@nullnet.fi> + + License: GPLv2 + +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> + +#include "globals.h" +#include "usage.h" +#include "cmdline.h" + +void cmdline_parse(int argc, char *argv[]) +{ + static struct option long_options[] = { + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'v' }, + { "config", 1, 0, 'c' }, + { "pidfile", 1, 0, 'p' }, + { "no-fork", 0, 0, 'f' }, + { 0, 0, 0, 0 }, + }; + + set_prog(argv[0]); + + for(;;) { + int i = getopt_long(argc, argv, "hvc:p:f", long_options, NULL); + if(i == -1) break; + switch(i) { + case 'h': + case 'v': + usage_and_exit(); + + case 'c': + set_configfile(optarg); + break; + + case 'p': + set_pidfile(optarg); + break; + + case 'f': + set_nodaemon(1); + break; + + default: + usage_and_exit(); + } + } + + if(optind < argc) + usage_and_exit(); +} + +/* EOF */ diff --git a/cmdline.h b/cmdline.h new file mode 100644 index 0000000..9aa66c3 --- /dev/null +++ b/cmdline.h @@ -0,0 +1,16 @@ +/* + + (C) 2014 Mika Ilmaranta <ilmis@nullnet.fi> + + License: GPLv2 + +*/ + +#ifndef __CMDLINE_H__ +#define __CMDLINE_H__ + +void cmdline_parse(int argc, char *argv[]); + +#endif + +/* EOF */ diff --git a/config.c b/config.c new file mode 100644 index 0000000..5f653f2 --- /dev/null +++ b/config.c @@ -0,0 +1,762 @@ +/* + +(C) 2009-2015 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <dirent.h> +#include <fnmatch.h> + +#include "config.h" +#include "defs.h" + +static CONFIG defaults; +static int errors = 0; + +GLOBAL cfg; + +static void read_one_config(char *fn, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg); +static int find_all_configs(char* fn, int mustexist, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg); +static void reassign(char **dst, char *src); +static void release(char **dst); +static int eqcmp(char *str, char *pat); +static int check_addrs(CONFIG *cur); + +static void reassign(char **dst, char *src) +{ + if(*dst) free(*dst); + *dst = strdup(src); +} + +static void release(char **dst) +{ + if(*dst) free(*dst); + *dst = NULL; +} + +static int eqcmp(char *str, char *pat) +{ + int i; + + i = strlen(pat); + if(!strncmp(str, pat, i) && str[i] == '=') return 0; + return 1; +} + +int reload_config(char *fn, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg) { + free_config(first, last, firstg, lastg); + init_config(); + return(read_config(fn, first, last, firstg, lastg)); +} + +void free_config(CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg) { + CONFIG *cur, *prev; + GROUPS *curg, *prevg; + GROUP_MEMBERS *curgm, *prevgm; + + cur = (*first); + while(cur) { + if(cur->name && cur->name != defaults.name) release(&cur->name); + if(cur->sourceip && cur->sourceip != defaults.sourceip) release(&cur->sourceip); + if(cur->srcinfo) freeaddrinfo(cur->srcinfo); + if(cur->checkip && cur->checkip != defaults.checkip) release(&cur->checkip); + if(cur->dstinfo) freeaddrinfo(cur->dstinfo); + if(cur->eventscript && cur->eventscript != defaults.eventscript) release(&cur->eventscript); + if(cur->notifyscript && cur->notifyscript != defaults.notifyscript) release(&cur->notifyscript); + if(cur->warn_email && cur->warn_email != defaults.warn_email) release(&cur->warn_email); + if(cur->device && cur->device != defaults.device) release(&cur->device); + if(cur->queue && cur->queue != defaults.queue) release(&cur->queue); + if(cur->long_down_email && cur->long_down_email != defaults.long_down_email) release(&cur->long_down_email); + if(cur->long_down_notifyscript && cur->long_down_notifyscript != defaults.long_down_notifyscript) release(&cur->long_down_notifyscript); + if(cur->long_down_eventscript && cur->long_down_eventscript != defaults.long_down_eventscript) release(&cur->long_down_eventscript); + + prev = cur; + cur = cur->next; + free(prev); + } + + *first = NULL; + *last = NULL; + + curg = (*firstg); + while(curg) { + curgm = curg->fgm; + while(curgm) { + free(curgm->name); + + prevgm = curgm; + curgm = curgm->next; + free(prevgm); + } + curg->fgm = NULL; + curg->lgm = NULL; + + if(curg->name && curg->name != defaults.name) release(&curg->name); + if(curg->eventscript && curg->eventscript != defaults.eventscript) release(&curg->eventscript); + if(curg->notifyscript && curg->notifyscript != defaults.notifyscript) release(&curg->notifyscript); + if(curg->warn_email && curg->warn_email != defaults.warn_email) release(&curg->warn_email); + if(curg->queue && curg->queue != defaults.queue) release(&curg->queue); + + prevg = curg; + curg = curg->next; + free(prevg); + } + + *firstg = NULL; + *lastg = NULL; + + if(defaults.name) release(&defaults.name); + if(defaults.checkip) release(&defaults.checkip); + if(defaults.eventscript) release(&defaults.eventscript); + if(defaults.notifyscript) release(&defaults.notifyscript); + if(defaults.warn_email) release(&defaults.warn_email); + if(defaults.sourceip) release(&defaults.sourceip); + if(defaults.device) release(&defaults.device); + if(defaults.queue) release(&defaults.queue); + + if(defaults.long_down_email) release(&defaults.long_down_email); + if(defaults.long_down_notifyscript) release(&defaults.long_down_notifyscript); + if(defaults.long_down_eventscript) release(&defaults.long_down_eventscript); +} + +void init_config(void) +{ + /* zero cfg and defaults */ + memset(&cfg, 0, sizeof(cfg)); + memset(&defaults, 0, sizeof(defaults)); + + /* initialize to sane value */ + cfg.debug = 8; + + defaults.name = strdup("defaults"); + defaults.checkip = strdup("127.0.0.1"); + defaults.eventscript = NULL; + defaults.notifyscript = strdup("/usr/libexec/lsm/default_script"); + defaults.max_packet_loss = 15; + defaults.max_successive_pkts_lost = 7; + defaults.min_packet_loss = 5; + defaults.min_successive_pkts_rcvd = 10; + defaults.interval_ms = 1000; + defaults.timeout_ms = 1000; + defaults.warn_email = strdup("root"); + defaults.check_arp = 0; + defaults.sourceip = NULL; + defaults.ttl = 0; + + /* assume default unknown state for connections unless user has stated otherwise later in config */ + defaults.status = UNKNOWN; + + /* no exec queue by default */ + defaults.queue = NULL; + + defaults.long_down_email = NULL; + + /* by default don't execute notify script on unkown to up event */ + defaults.unknown_up_notify = 0; + + /* by default no accelerated startup */ + defaults.startup_acceleration = 0; + + /* by default no startup burst */ + defaults.startup_burst_pkts = 0; + defaults.startup_burst_interval = MIN_PERHOST_INTERVAL; +} + +static int find_all_configs(char* fn, int mustexist, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg) +{ + struct dirent **namelist; + char dir[BUFSIZ], pattern[128], *p, s[BUFSIZ]; + int n, i, found; + + /* Split fn to dir/pattern */ + strcpy(dir, fn); + if((p = strrchr(dir, '/')) == NULL) { + strcpy(dir, "."); + strcpy(pattern, fn); + } else { + *p = 0; + strcpy(pattern, p + 1); + } + + /* Find list of all files */ + n = scandir(dir, &namelist, 0, alphasort); + if (n < 0) { + if (mustexist == 0) + return(0); + syslog(LOG_ERR, "%s: can't read directory \"%s\"", __FUNCTION__, dir); + return(-1); + } + + /* See if name matches pattern */ + found = 0; + for (i = 0; i < n; i++) { + if (fnmatch(pattern, namelist[i]->d_name, 0) == 0 && + fnmatch("*~", namelist[i]->d_name, 0) != 0) { + snprintf(s, BUFSIZ, "%s/%s", dir, namelist[i]->d_name); + read_one_config(s, first, last, firstg, lastg); + found++; + } + free(namelist[i]); + } + free(namelist); + + /* Fail if no matches found */ + if (found == 0) { + if (mustexist == 0) + return(0); + syslog(LOG_ERR, "%s: no config files found for \"%s\"", __FUNCTION__, fn); + return(-1); + } + + return(0); +} + +int read_config(char *fn, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg) +{ + CONFIG *cur = NULL; + GROUPS *curg = NULL; + GROUP_MEMBERS *curgm = NULL; + + errors = 0; + + read_one_config(fn, first, last, firstg, lastg); + + for(curg = *firstg; curg; curg = curg->next) { + for(curgm = curg->fgm; curgm; curgm = curgm->next) { + int found = 0; + + for(cur = *first; cur; cur = cur->next) { + if(!strcmp(cur->name, curgm->name)) { + curgm->cfg_ptr = cur; + found = 1; + break; + } + } + if(!found) { + syslog(LOG_ERR, "%s: %s: connection group member \"%s\" not found", __FILE__, __FUNCTION__, curgm->name); + errors++; + } + } + } + + /* some parameter sanity checking */ + for(cur = *first; cur; cur = cur->next) { + if(strlen(cur->checkip) == 0) { + syslog(LOG_ERR, "WARNING: connection \"%s\" has no checkip parameter set", cur->name); + errors++; + } else { + if(check_addrs(cur) < 0) { + errors++; + } + } + + if(cur->max_packet_loss <= cur->min_packet_loss) { + syslog(LOG_ERR, "WARNING: connection \"%s\" max_packet_loss (%d) <= min_packet_loss (%d). that would cause flip-flop effect", cur->name, cur->max_packet_loss, cur->min_packet_loss); + errors++; + } + + } + if(errors) return(-1); + + return(0); +} + +static void read_one_config(char *fn, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg) +{ + CONFIG *cur = NULL; + GROUPS *curg = NULL; + GROUP_MEMBERS *curgm = NULL; + FILE *fp; + char buf[BUFSIZ]; + int mode = 0; + int line = 1; + + if((fp = fopen(fn, "r")) == 0) { + syslog(LOG_ERR, "%s: can't open config file \"%s\"", __FUNCTION__, fn); + return; + } + + while(fgets(buf, BUFSIZ, fp)) { + char *p = NULL; + + if(*buf && buf[strlen(buf) - 1] == '\n') buf[strlen(buf) - 1] = '\0'; /* strip lf */ + if((p = strchr(buf, '#')) != NULL) *p = '\0'; /* strip comment */ + while((p = strchr(buf, '\t')) != NULL) *p = ' '; /* tabs -> spaces */ + while(*buf == ' ') memmove(buf, buf + 1, strlen(buf)); /* strip leading space */ + while((p = strstr(buf, " ")) != NULL) memmove(p, p + 1, strlen(p)); /* strip multi white space */ + while((p = strstr(buf, " =")) != NULL) memmove(p, p + 1, strlen(p)); /* strip spaces before = */ + while((p = strstr(buf, "= ")) != NULL) memmove(p + 1, p + 2, strlen(p + 1)); /* strip spaces after = */ + while(*buf && buf[strlen(buf) - 1] == ' ') buf[strlen(buf) - 1] = '\0'; /* strip tailing space */ + + if(!*buf) continue; + + if(mode) { + if (!strcmp(buf, "}")) { + mode=0; + continue; + } + + switch(mode) { + case 1: /* defaults */ + if(!eqcmp(buf, "name")) + reassign(&defaults.name, strchr(buf, '=') + 1); + else if(!eqcmp(buf, "checkip")) + reassign(&defaults.checkip, strchr(buf, '=') + 1); + else if(!eqcmp(buf, "eventscript")) + reassign(&defaults.eventscript, strchr(buf, '=') + 1); + else if(!eqcmp(buf, "notifyscript")) + reassign(&defaults.notifyscript, strchr(buf, '=') + 1); + + else if(!eqcmp(buf, "unknown_up_notify")) + defaults.unknown_up_notify = atoi(strchr(buf, '=') + 1); + + else if(!eqcmp(buf, "max_packet_loss")) + defaults.max_packet_loss = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "max_successive_pkts_lost")) + defaults.max_successive_pkts_lost = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "min_packet_loss")) + defaults.min_packet_loss = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "min_successive_pkts_rcvd")) + defaults.min_successive_pkts_rcvd = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "interval_ms")) + defaults.interval_ms = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "timeout_ms")) + defaults.timeout_ms = atoi(strchr(buf, '=') + 1); + + else if(!eqcmp(buf, "warn_email")) + reassign(&defaults.warn_email, strchr(buf, '=') + 1); + + else if(!eqcmp(buf, "check_arp")) + defaults.check_arp = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "sourceip")) + reassign(&defaults.sourceip, strchr(buf, '=') + 1); + else if(!eqcmp(buf, "device")) + reassign(&defaults.device, strchr(buf, '=') + 1); + else if(!eqcmp(buf, "ttl")) + defaults.ttl = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "status")) + defaults.status = atoi(strchr(buf, '=') + 1); + + else if(!eqcmp(buf, "queue")) + reassign(&defaults.queue, strchr(buf, '=') + 1); + + else if(!eqcmp(buf, "long_down_time")) + defaults.long_down_time = atoi(strchr(buf, '=') + 1); + + else if(!eqcmp(buf, "long_down_email")) + reassign(&defaults.long_down_email, strchr(buf, '=') + 1); + else if(!eqcmp(buf, "long_down_notifyscript")) + reassign(&defaults.long_down_notifyscript, strchr(buf, '=') + 1); + else if(!eqcmp(buf, "long_down_eventscript")) + reassign(&defaults.long_down_eventscript, strchr(buf, '=') + 1); + + else if(!eqcmp(buf, "startup_acceleration")) + defaults.startup_acceleration = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "startup_burst_pkts")) + defaults.startup_burst_pkts = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "startup_burst_interval")) + defaults.startup_burst_interval = atoi(strchr(buf, '=') + 1); + else { + syslog(LOG_ERR, "%s: %s: unrecognised " + "default config option on " + "line %d \"%s\"", __FILE__, + __FUNCTION__, line, buf); + errors++; + } + break; + case 2: /* connection */ + if(!cur) { + syslog(LOG_ERR, "read_config: cur == NULL"); + break; + } + + if(!eqcmp(buf, "name")) cur->name = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "checkip")) cur->checkip = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "eventscript")) cur->eventscript = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "notifyscript")) cur->notifyscript = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "unknown_up_notify")) cur->unknown_up_notify = atoi(strchr(buf, '=') + 1); + + else if(!eqcmp(buf, "max_packet_loss")) cur->max_packet_loss = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "max_successive_pkts_lost")) cur->max_successive_pkts_lost = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "min_packet_loss")) cur->min_packet_loss = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "min_successive_pkts_rcvd")) cur->min_successive_pkts_rcvd = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "interval_ms")) cur->interval_ms = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "timeout_ms")) cur->timeout_ms = atoi(strchr(buf, '=') + 1); + + else if(!eqcmp(buf, "warn_email")) cur->warn_email = strdup(strchr(buf, '=') + 1); + + else if(!eqcmp(buf, "check_arp")) cur->check_arp = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "sourceip")) cur->sourceip = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "device")) cur->device = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "ttl")) cur->ttl = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "status")) cur->status = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "queue")) cur->queue = strdup(strchr(buf, '=') + 1); + + else if(!eqcmp(buf, "long_down_time")) cur->long_down_time = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "long_down_email")) cur->long_down_email = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "long_down_notifyscript")) cur->long_down_notifyscript = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "long_down_eventscript")) cur->long_down_eventscript = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "startup_acceleration")) cur->startup_acceleration = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "startup_burst_pkts")) cur->startup_burst_pkts = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "startup_burst_interval")) cur->startup_burst_interval = atoi(strchr(buf, '=') + 1); + else { + syslog(LOG_ERR, "%s: %s: unrecognised connection config option on line %d \"%s\"", __FILE__, __FUNCTION__, line, buf); + errors++; + } + break; + case 3: /* group */ + if(!eqcmp(buf, "name")) curg->name = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "eventscript")) curg->eventscript = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "notifyscript")) curg->notifyscript = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "unknown_up_notify")) curg->unknown_up_notify = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "warn_email")) curg->warn_email = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "logic")) curg->logic = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "status")) curg->status = atoi(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "queue")) curg->queue = strdup(strchr(buf, '=') + 1); + else if(!eqcmp(buf, "member-connection")) { + if((curgm = (GROUP_MEMBERS *)malloc(sizeof(GROUP_MEMBERS))) == NULL) { + syslog(LOG_ERR, "%s: %s: can't malloc for group member", __FILE__, __FUNCTION__); + fclose(fp); + return; + } + curgm->name = strdup(strchr(buf, '=') + 1); + curgm->cfg_ptr = NULL; + + if(curg->lgm) { /* insert as last */ + curgm->next = NULL; + curgm->prev = curg->lgm; + curg->lgm->next = curgm; + curg->lgm = curgm; + } else { /* empty member list */ + curgm->next = NULL; + curgm->prev = NULL; + curg->fgm = curgm; + curg->lgm = curgm; + } + } + else { + syslog(LOG_ERR, "%s: %s: unrecognised group config option on line %d \"%s\"", __FILE__, __FUNCTION__, line, buf); + errors++; + } + break; + default: + syslog(LOG_ERR, "%s: %s: switch(mode) hit default: should never happen. mode was %d", __FILE__, __FUNCTION__, mode); + errors++; + break; + } + } + else { + /* global config */ + if(!eqcmp(buf, "debug")) + cfg.debug = atoi(strchr(buf, '=') + 1); + + /* per connection configs */ + else if(!strcmp(buf, "defaults {")) + mode=1; + else if(!strcmp(buf, "connection {")) { + mode=2; + if((cur = malloc(sizeof(CONFIG))) == NULL) { + syslog(LOG_ERR, "%s: %s: can't malloc for config", __FILE__, __FUNCTION__); + return; + } + + if(*last) { /* not first */ + (*last)->next = cur; + cur->prev = *last; + cur->next = NULL; + *last = cur; + } + else { + *first = cur; + *last = cur; + cur->prev = NULL; + cur->next = NULL; + } + /* fill in defaults */ + if(defaults.name) { + cur->name = defaults.name; + cur->sourceip = defaults.sourceip; + cur->srcinfo = NULL; + cur->checkip = defaults.checkip; + cur->dstinfo = NULL; + cur->eventscript = defaults.eventscript; + cur->notifyscript = defaults.notifyscript; + cur->unknown_up_notify = defaults.unknown_up_notify; + cur->max_packet_loss = defaults.max_packet_loss; + cur->max_successive_pkts_lost = defaults.max_successive_pkts_lost; + cur->min_packet_loss = defaults.min_packet_loss; + cur->min_successive_pkts_rcvd = defaults.min_successive_pkts_rcvd; + cur->interval_ms = defaults.interval_ms; + cur->timeout_ms = defaults.timeout_ms; + cur->warn_email = defaults.warn_email; + cur->check_arp = defaults.check_arp; + cur->device = defaults.device; + cur->ttl = defaults.ttl; + cur->status = defaults.status; + cur->queue = defaults.queue; + cur->long_down_time = defaults.long_down_time; + cur->long_down_email = defaults.long_down_email; + cur->long_down_notifyscript = defaults.long_down_notifyscript; + cur->long_down_eventscript = defaults.long_down_eventscript; + cur->startup_acceleration = defaults.startup_acceleration; + cur->startup_burst_pkts = defaults.startup_burst_pkts; + cur->startup_burst_interval = defaults.startup_burst_interval; + } + else + syslog(LOG_ERR, "%s: %s: defaults not set", __FILE__, __FUNCTION__); + } + else if(!strcmp(buf, "group {")) { + mode = 3; + + if((curg = (GROUPS *)malloc(sizeof(GROUPS))) == NULL) { + syslog(LOG_ERR, "read_config: can't malloc for group"); + return; + } + + /* apply sane defaults for group */ + curg->name = defaults.name; + curg->eventscript = defaults.eventscript; + curg->notifyscript = defaults.notifyscript; + curg->unknown_up_notify = defaults.unknown_up_notify; + curg->warn_email = defaults.warn_email; + curg->logic = 0; /* default group logic or */ + curg->status = defaults.status; + curg->queue = defaults.queue; + + curg->fgm = NULL; + curg->lgm = NULL; + + if(*lastg) { /* not first group */ + (*lastg)->next = curg; + curg->prev = *lastg; + curg->next = NULL; + *lastg = curg; + } else { + *firstg = curg; + *lastg = curg; + curg->prev = NULL; + curg->next = NULL; + } + } + else if(!strncmp(buf, "include ", 8)) { + if(find_all_configs(strchr(buf, ' ') + 1, 1, first, last, firstg, lastg) != 0) { + syslog(LOG_ERR, "%s: %s: failed to process included config file on line %d \"%s\"", __FILE__, __FUNCTION__, line, strchr(buf, ' ') + 1); + errors++; + } + continue; + } + else if(!strncmp(buf, "-include ", 9)) { + if(find_all_configs(strchr(buf, ' ') + 1, 0, first, last, firstg, lastg) != 0) { + syslog(LOG_ERR, "%s: %s: failed to process included config file on line %d \"%s\"", __FILE__, __FUNCTION__, line, strchr(buf, ' ') + 1); + errors++; + } + continue; + } + else { + syslog(LOG_ERR, "%s: %s: unrecognised global config option in file \"%s\" on line %d \"%s\"", __FILE__, __FUNCTION__, fn, line, buf); + errors++; + } + } + line++; + } + + if(mode != 0) { + syslog(LOG_ERR, "%s: %s: missing closing bracket at the end of config file \"%s\"", __FILE__, __FUNCTION__, fn); + errors++; + } + + fclose(fp); +} + +void dump_config(CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg) +{ + CONFIG *cur; + GROUPS *curg; + GROUP_MEMBERS *curgm; + + syslog(LOG_INFO, "cfg.debug = \"%d\"", cfg.debug); + + for(cur = *first; cur; cur = cur->next) { + syslog(LOG_INFO, "cur->name = \"%s\"", cur->name); + syslog(LOG_INFO, "cur->sourceip = \"%s\"", cur->sourceip); +#if defined(DEBUG) + if(cur->srcinfo) { + char sbuf[INET6_ADDRSTRLEN]; + + syslog(LOG_INFO, "cur->srcinfo = \"%s\"", inet_ntop(cur->srcinfo->ai_family, &cur->srcinfo->ai_addr, sbuf, INET6_ADDRSTRLEN)); + } +#endif + syslog(LOG_INFO, "cur->checkip = \"%s\"", cur->checkip); +#if defined(DEBUG) + if(cur->dstinfo) { + char sbuf[INET6_ADDRSTRLEN]; + + syslog(LOG_INFO, "cur->dstinfo = \"%s\"", inet_ntop(cur->dstinfo->ai_family, &cur->dstinfo->ai_addr, sbuf, INET6_ADDRSTRLEN)); + } +#endif + syslog(LOG_INFO, "cur->eventscript = \"%s\"", cur->eventscript); + syslog(LOG_INFO, "cur->notifyscript = \"%s\"", cur->notifyscript); + syslog(LOG_INFO, "cur->unknown_up_notify = \"%d\"", cur->unknown_up_notify); + + syslog(LOG_INFO, "cur->max_packet_loss = \"%d\"", cur->max_packet_loss); + syslog(LOG_INFO, "cur->max_successive_pkts_lost = \"%d\"", cur->max_successive_pkts_lost); + syslog(LOG_INFO, "cur->min_packet_loss = \"%d\"", cur->min_packet_loss); + syslog(LOG_INFO, "cur->min_successive_pkts_rcvd = \"%d\"", cur->min_successive_pkts_rcvd); + syslog(LOG_INFO, "cur->interval_ms = \"%d\"", cur->interval_ms); + syslog(LOG_INFO, "cur->timeout_ms = \"%d\"", cur->timeout_ms); + + syslog(LOG_INFO, "cur->warn_email = \"%s\"", cur->warn_email); + + syslog(LOG_INFO, "cur->check_arp = \"%d\"", cur->check_arp); + syslog(LOG_INFO, "cur->device = \"%s\"", cur->device); + syslog(LOG_INFO, "cur->ttl = \"%d\"", cur->ttl); + syslog(LOG_INFO, "cur->status = \"%d\"", cur->status); + syslog(LOG_INFO, "cur->startup_acceleration = \"%d\"", cur->startup_acceleration); + syslog(LOG_INFO, "cur->startup_burst_pkts = \"%d\"", cur->startup_burst_pkts); + syslog(LOG_INFO, "cur->startup_burst_interval = \"%d\"", cur->startup_burst_interval); + } + + for(curg = *firstg; curg; curg = curg->next) { + syslog(LOG_INFO, "curg->name = \"%s\"", curg->name); + syslog(LOG_INFO, "curg->eventscript = \"%s\"", curg->eventscript); + syslog(LOG_INFO, "curg->notifyscript = \"%s\"", curg->notifyscript); + syslog(LOG_INFO, "curg->unknown_up_notify = \"%d\"", curg->unknown_up_notify); + syslog(LOG_INFO, "curg->warn_email = \"%s\"", curg->warn_email); + syslog(LOG_INFO, "curg->logic = \"%s\"", curg->logic == 0 ? "OR" : "AND"); + + for(curgm = curg->fgm; curgm; curgm = curgm->next) { + syslog(LOG_INFO, "curgm->name = \"%s\"", curgm->name); + } + } +} + +static int check_addrs(CONFIG *cur) +{ + struct in6_addr serveraddr; + struct addrinfo hints; +#if defined(DEBUG) + struct addrinfo *rp; +#endif + int rc; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICSERV; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if((rc = inet_pton(AF_INET, cur->checkip, &serveraddr)) == 1) { /* valid v4 addr */ + hints.ai_family = AF_INET; + hints.ai_flags |= AI_NUMERICHOST; + } + else if((rc = inet_pton(AF_INET6, cur->checkip, &serveraddr)) == 1) { /* valid v6 addr */ + hints.ai_family = AF_INET6; + hints.ai_flags |= AI_NUMERICHOST; + } + + if((rc = getaddrinfo(cur->checkip, "1025", &hints, &(cur->dstinfo))) != 0) { + syslog(LOG_ERR, "WARNING: connection \"%s\" checkip is invalid %s, %s", cur->name, cur->checkip, gai_strerror(rc)); + return(-1); + } + +#if defined(DEBUG) + for(rp = cur->dstinfo; rp; rp = rp->ai_next) { + unsigned char *s; + char buf[BUFSIZ]; + char sbuf[INET6_ADDRSTRLEN]; + int i; + + memset(buf, 0, BUFSIZ); + s = (unsigned char *)rp; + + strcat(buf, "hex dump:"); + for(i = 0; i < sizeof(struct addrinfo); i++) + sprintf(buf + strlen(buf), " %2x", s[i]); + syslog(LOG_INFO, "%s: %s: dst %s", __FILE__, __FUNCTION__, buf); + + memset(buf, 0, BUFSIZ); + s = (unsigned char *)rp; + + strcat(buf, "dec dump:"); + for(i = 0; i < sizeof(struct addrinfo); i++) + sprintf(buf + strlen(buf), " %3d", s[i]); + syslog(LOG_INFO, "%s: %s: dst %s", __FILE__, __FUNCTION__, buf); + + syslog(LOG_INFO, "%s: %s: dst %s = %s", __FILE__, __FUNCTION__, cur->checkip, inet_ntop(rp->ai_family, &rp->ai_addr, sbuf, INET6_ADDRSTRLEN)); + } +#endif + + if(cur->dstinfo->ai_family == AF_INET6 && cur->check_arp) { + syslog(LOG_ERR, "WARNING: connection \"%s\" ipv6 and arping are not compatible", cur->name); + return(-1); + } + + if(!cur->sourceip || !*cur->sourceip) return(0); /* sourceip is not mandatory */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICSERV; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if((rc = inet_pton(AF_INET, cur->sourceip, &serveraddr)) == 1) { /* valid v4 addr */ + hints.ai_family = AF_INET; + hints.ai_flags |= AI_NUMERICHOST; + } + else if((rc = inet_pton(AF_INET6, cur->sourceip, &serveraddr)) == 1) { /* valid v6 addr */ + hints.ai_family = AF_INET6; + hints.ai_flags |= AI_NUMERICHOST; + } + + if((rc = getaddrinfo(cur->sourceip, "1025", &hints, &(cur->srcinfo))) != 0) { + syslog(LOG_ERR, "WARNING: connection \"%s\" sourceip is invalid %s, %s", cur->name, cur->sourceip, gai_strerror(rc)); + return(-1); + } + +#if defined(DEBUG) + for(rp = cur->srcinfo; rp; rp = rp->ai_next) { + unsigned char *s; + char buf[BUFSIZ]; + char sbuf[INET6_ADDRSTRLEN]; + int i; + + memset(buf, 0, BUFSIZ); + s = (unsigned char *)rp; + + strcat(buf, "hex dump:"); + for(i = 0; i < sizeof(struct addrinfo); i++) + sprintf(buf + strlen(buf), " %2x", s[i]); + syslog(LOG_INFO, "%s: %s: src %s", __FILE__, __FUNCTION__, buf); + + memset(buf, 0, BUFSIZ); + s = (unsigned char *)rp; + + strcat(buf, "dec dump:"); + for(i = 0; i < sizeof(struct addrinfo); i++) + sprintf(buf + strlen(buf), " %3d", s[i]); + syslog(LOG_INFO, "%s: %s: src %s", __FILE__, __FUNCTION__, buf); + + syslog(LOG_INFO, "%s: %s: src %s = %s", __FILE__, __FUNCTION__, cur->checkip, inet_ntop(rp->ai_family, &rp->ai_addr, sbuf, INET6_ADDRSTRLEN)); + } +#endif + + if(cur->srcinfo->ai_family != cur->dstinfo->ai_family) { + syslog(LOG_ERR, "WARNING: connection \"%s\" sourceip and checkip have unmatching protocol families", cur->name); + return(-1); + } + + return(0); +} + +/* EOF */ diff --git a/config.h b/config.h new file mode 100644 index 0000000..27bb360 --- /dev/null +++ b/config.h @@ -0,0 +1,91 @@ +/* + +(C) 2009-2015 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + + +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> + +typedef enum status { + DOWN = 0, + UP = 1, + UNKNOWN = 2, + LONG_DOWN = 3 +} STATUS; + +typedef struct config { + struct config *prev, *next; + char *name; + char *sourceip; + struct addrinfo *srcinfo; + char *checkip; + struct addrinfo *dstinfo; + char *eventscript; + int unknown_up_notify; + char *notifyscript; + int max_packet_loss; + int max_successive_pkts_lost; + int min_packet_loss; + int min_successive_pkts_rcvd; + int interval_ms; + int timeout_ms; + char *warn_email; + int long_down_time; + char *long_down_email; + char *long_down_notifyscript; + char *long_down_eventscript; + int check_arp; + char *device; + int ttl; + STATUS status; + char *queue; + int startup_acceleration; + int startup_burst_pkts; + int startup_burst_interval; + + void *data; +} CONFIG; + +typedef struct group_members { + struct group_members *prev, *next; + char *name; + CONFIG *cfg_ptr; +} GROUP_MEMBERS; + +typedef struct groups { + struct groups *prev, *next; + char *name; + char *eventscript; + char *notifyscript; + int unknown_up_notify; + char *warn_email; + int logic; /* or = 0, and = 1 */ + STATUS status; + char *queue; + + GROUP_MEMBERS *fgm, *lgm; +} GROUPS; + +typedef struct global { + int debug; +} GLOBAL; + +extern GLOBAL cfg; + +void init_config(void); +int read_config(char *fn, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg); +int reload_config(char *fn, CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg); +void dump_config(CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg); +void free_config(CONFIG **first, CONFIG **last, GROUPS **firstg, GROUPS **lastg); + +#endif + +/* EOF */ diff --git a/default_script b/default_script new file mode 100644 index 0000000..2fe3e7c --- /dev/null +++ b/default_script @@ -0,0 +1,67 @@ +#!/bin/sh +# +# Copyright (C) 2009-2015 Mika Ilmaranta <ilmis@nullnet.fi> +# Copyright (C) 2015 Tuomo Soini <tis@foobar.fi> +# +# License: GPLv2 +# + +# +# default event handling script +# + +STATE=${1} +NAME=${2} +CHECKIP=${3} +DEVICE=${4} +WARN_EMAIL=${5} +REPLIED=${6} +WAITING=${7} +TIMEOUT=${8} +REPLY_LATE=${9} +CONS_RCVD=${10} +CONS_WAIT=${11} +CONS_MISS=${12} +AVG_RTT=${13} +SRCIP=${14} +PREVSTATE=${15} +TIMESTAMP=${16} + +if [ -z "${WARN_EMAIL}" ] ; then + exit 0 +fi + +DATE=$(date --date=@${TIMESTAMP}) + +cat <<EOM | mail -s "LSM: ${NAME} ${STATE}, IP ${CHECKIP}" ${WARN_EMAIL} + +Hi, + +Your connection ${NAME} has changed its state to ${STATE} at ${DATE}. + +Following parameters were passed: +prevstate = ${PREVSTATE} +newstate = ${STATE} +name = ${NAME} +checkip = ${CHECKIP} +device = ${DEVICE} +sourceip = ${SRCIP} +warn_email = ${WARN_EMAIL} + +Packet statuses: +replied = ${REPLIED} packets replied +waiting = ${WAITING} packets waiting for reply +timeout = ${TIMEOUT} packets that have timed out (= packet loss) +reply_late = ${REPLY_LATE} packets that received a reply after timeout +cons_rcvd = ${CONS_RCVD} consecutively received replies in sequence +cons_wait = ${CONS_WAIT} consecutive packets waiting for reply +cons_miss = ${CONS_MISS} consecutive packets that have timed out +avg_rtt = ${AVG_RTT} average rtt [usec], calculated from received packets + +BR, +Your LSM installation + +EOM + +exit 0 +# diff --git a/default_script.sample b/default_script.sample new file mode 100644 index 0000000..e602859 --- /dev/null +++ b/default_script.sample @@ -0,0 +1,14 @@ +#!/bin/sh + +# You can call a set of scripts like so on an event + +cd /etc/lsm/script.d +for script in $(ls); do + if [ ! -x $script ]; then + continue + fi + ./$script "$@" +done + +exit 0 +# @@ -0,0 +1,29 @@ +/* + +(C) 2009-2011 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#ifndef __DEFS_H__ +#define __DEFS_H__ + +#ifndef TRUE +#define TRUE (1) +#define FALSE (0) +#endif + +#define MIN_PERHOST_INTERVAL (20000L) /* 20ms in between sends minimum */ +#define DEFAULT_SELECT_WAIT (10000L) /* wait at least 10ms for incoming packet */ + +#define FOLLOWED_PKTS (100) /* THIS ABSOLUTELY CAN'T EXCEED 0xffff (65535 decimal) OR THINGS BREAK */ +#define SEQ_LIMITER ((0x10000 / FOLLOWED_PKTS) * FOLLOWED_PKTS) + +#define min(x, y) ((x)<(y) ? (x) : (y)) + +#define PLUGIN_EXPORT_DIR "/var/lib/lsm" + +#endif + +/* EOF */ diff --git a/forkexec.c b/forkexec.c new file mode 100644 index 0000000..b1b2cd9 --- /dev/null +++ b/forkexec.c @@ -0,0 +1,390 @@ +/* + +(C) 2009-2011 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdarg.h> +#include <syslog.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "config.h" +#include "forkexec.h" + +static void sigchld_hdl(int sig); + +typedef struct exec_queue +{ + pid_t pid; + char **argv; + char **envp; + struct exec_queue *next; +} EXEC_QUEUE; + +typedef struct exec_queues +{ + char *name; + EXEC_QUEUE *first; + EXEC_QUEUE *last; + struct exec_queues *next; +} EXEC_QUEUES; + +static EXEC_QUEUES *exec_queues_first = NULL; +static EXEC_QUEUES *exec_queues_last = NULL; + +pid_t forkexec(char **argv, char **envp) +{ + pid_t pid; +#if defined(DEBUG) + int i; +#endif + + if((pid = fork()) == -1) { + syslog(LOG_ERR, "%s: %s: %d: fork() failed \"%s\"", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); + return(0); + } + + if(pid) { + /* parent */ + if(cfg.debug >= 9) syslog(LOG_INFO, "%s: %s: %d: child process forked with pid: %d", __FILE__, __FUNCTION__, __LINE__, pid); + return(pid); + } + + /* child */ + closelog(); /* openlog doesn't return a fd to set close on exec */ + +#if defined(DEBUG) + for(i = 0; argv[i]; i++) { + fprintf(stderr, "argv[%d] = \"%s\"\n", i, argv[i]); + } + for(i = 0; envp[i]; i++) { + fprintf(stderr, "envp[%d] = \"%s\"\n", i, envp[i]); + } +#endif + + execve(argv[0], argv, envp); + syslog(LOG_ERR, "%s: %s: %d: child process failed to execute external command", __FILE__, __FUNCTION__, __LINE__); + exit(1); /* exec failed ... */ +} + +/* + Set up child signal handler to handle termination of children. + This should be done once only for the main program. +*/ +void create_sigchld_hdl(void) +{ + struct sigaction act; + + memset (&act, 0, sizeof(act)); + act.sa_handler = sigchld_hdl; + if (sigaction(SIGCHLD, &act, 0)) { + syslog(LOG_ERR, "%s: %s: %d: failed to set up child signal handler: %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); + return; + } else { + if(cfg.debug >= 9) syslog(LOG_INFO, "%s: %s: %d: successfully set up child signal handler", __FILE__, __FUNCTION__, __LINE__); + } +} + +/* + SIGCHLD handler. Will be called for all children. +*/ +static void sigchld_hdl(int sig) +{ + /* Wait for the dead process. + * We use a non-blocking call to be sure this signal handler will not + * block if a child was cleaned up in another part of the program. */ + int exitval; + pid_t pid; + + if((pid = waitpid(-1, &exitval, WNOHANG)) == -1) { + if(cfg.debug >= 9 && errno != ECHILD) syslog(LOG_ERR, "%s: %s: %d: waitpid failed %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); + } else { + if(cfg.debug >= 9 && exitval) syslog(LOG_ERR, "%s: %s: %d: child script with pid %d exited with non null exit value %d", __FILE__, __FUNCTION__, __LINE__, pid, exitval); + else if(cfg.debug >= 9) syslog(LOG_ERR, "%s: %s: %d: child script with pid %d exited successfully", __FILE__, __FUNCTION__, __LINE__, pid); + exec_queue_delete(pid); + } +} + +void exec_queue_add(char *queue, char **argv, char **envp) +{ + EXEC_QUEUES *eqs; + EXEC_QUEUE *eq; + + if(!exec_queues_first) { /* first queue */ + if((eqs = malloc(sizeof(EXEC_QUEUES))) == NULL) { + syslog(LOG_ERR, "%s: %s: %d: malloc failed %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); + return; + } + eqs->name = strdup(queue); + eqs->next = NULL; + + if((eq = malloc(sizeof(EXEC_QUEUE))) == NULL) { + syslog(LOG_ERR, "%s: %s: %d: malloc failed %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); + return; + } + eq->pid = 0; + eq->argv = argv; + eq->envp = envp; + eq->next = NULL; + + eqs->first = eq; + eqs->last = eq; + + exec_queues_first = eqs; + exec_queues_last = eqs; + + } else { /* not first queue */ + for(eqs = exec_queues_first; eqs; eqs = eqs->next) { + if(!strcmp(eqs->name, queue)) { + /* queue exists, add to it */ + if(cfg.debug >= 9) syslog(LOG_INFO, "%s: %s: %d: found queue %s", __FILE__, __FUNCTION__, __LINE__, eqs->name); + + if((eq = malloc(sizeof(EXEC_QUEUE))) == NULL) { + syslog(LOG_ERR, "%s: %s: %d: malloc failed %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); + return; + } + + eq->pid = 0; + eq->argv = argv; + eq->envp = envp; + eq->next = NULL; + + if(!eqs->first) { /* empty queue */ + eqs->first = eq; + eqs->last = eq; + + } else { /* add after last */ + eqs->last->next = eq; + eqs->last = eq; + } + + break; + } + } + + if(!eqs) { /* not found, create a new queue and add to it */ + if(cfg.debug >= 9) syslog(LOG_INFO, "%s: %s: %d: queue %s not found adding new queue", __FILE__, __FUNCTION__, __LINE__, queue); + + if((eqs = malloc(sizeof(EXEC_QUEUES))) == NULL) { + syslog(LOG_ERR, "%s: %s: %d: malloc failed %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); + return; + } + eqs->name = strdup(queue); + eqs->next = NULL; + + exec_queues_last->next = eqs; + exec_queues_last = eqs; + + if((eq = malloc(sizeof(EXEC_QUEUE))) == NULL) { + syslog(LOG_ERR, "%s: %s: %d: malloc failed %s", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); + return; + } + + eq->pid = 0; + eq->argv = argv; + eq->envp = envp; + eq->next = NULL; + + eqs->first = eq; + eqs->last = eq; + } + } + return; +} + +#if defined(DEBUG) +void exec_queue_dump(void) +{ + EXEC_QUEUES *eqs; + EXEC_QUEUE *eq; + int i; + + + for(eqs = exec_queues_first; eqs; eqs = eqs->next) { + syslog(LOG_INFO, "%s: %s: %d: eqs->name %s", __FILE__, __FUNCTION__, __LINE__, eqs->name); + for(eq = eqs->first; eq; eq = eq->next) { + syslog(LOG_INFO, "%s: %s: %d: eq->pid %d", __FILE__, __FUNCTION__, __LINE__, eq->pid); + for(i = 0; eq->argv[i]; i++) { + syslog(LOG_INFO, "%s: %s: %d: argv[%d] = %s", __FILE__, __FUNCTION__, __LINE__, i, eq->argv[i]); + } + } + } +} +#endif + +void exec_queue_process(void) +{ + EXEC_QUEUES *eqs; + EXEC_QUEUE *eq; + + for(eqs = exec_queues_first; eqs; eqs = eqs->next) { + eq = eqs->first; + if(eq && eq->pid == 0) eq->pid = forkexec(eq->argv, eq->envp); + } +} + +void exec_queue_delete(pid_t pid) +{ + EXEC_QUEUES *eqs; + EXEC_QUEUE *eq; + + for(eqs = exec_queues_first; eqs; eqs = eqs->next) { + EXEC_QUEUE *prev = NULL; + + for(eq = eqs->first; eq; eq = eq->next) { + if(eq->pid == pid) { + if(!prev) { /* this is first */ + if(!eq->next) { /* last */ + eqs->first = NULL; + eqs->last = NULL; + } else { /* not last */ + eqs->first = eq->next; + } + } else { /* not first */ + prev->next = eq->next; + } + exec_queue_argv_free(eq->argv); + exec_queue_envp_free(eq->envp); + free(eq); + return; + } + + prev = eq; + } + } + if(cfg.debug >= 9) syslog(LOG_ERR, "%s: %s: %d: child pid %d not found", __FILE__, __FUNCTION__, __LINE__, pid); +} + +void exec_queue_free(void) +{ + EXEC_QUEUES *eqs; + EXEC_QUEUE *eq; + + eqs = exec_queues_first; + while(eqs) { + EXEC_QUEUES *prev_eqs = eqs; + + eq = eqs->first; + while(eq) { + EXEC_QUEUE *prev_eq = eq; + + eq = eq->next; + free(prev_eq); + } + + eqs = eqs->next; + free(prev_eqs); + } + + exec_queues_first = NULL; + exec_queues_last = NULL; +} + +char **exec_queue_argv(char *fmt, ...) +{ + va_list vl; + char **argv; + char *s; + int fmt_cnt; + char buf[BUFSIZ]; + int i; + + s = fmt; + fmt_cnt = 0; + while(*s) { + if(*s++ == '%') fmt_cnt++; + } + + if((argv = malloc((fmt_cnt + 1) * sizeof(char *))) == NULL) { + syslog(LOG_ERR, "%s: %s: failed to malloc %s", __FILE__, __FUNCTION__, strerror(errno)); + return(NULL); + } + + s = fmt; + i = 0; + va_start(vl, fmt); + while(*s) { + if(*s == '%') { + s++; + switch(*s++) { + case 's': /* string */ + argv[i] = strdup(va_arg(vl, char *)); + i++; + break; + + case 'd': /* int */ + snprintf(buf, BUFSIZ - 1, "%d", va_arg(vl, int)); + argv[i] = strdup(buf); + i++; + break; + + default: /* skip unknown directives */ + break; + } + } else { + s++; + } + } + va_end(vl); + + argv[i] = NULL; + + return(argv); +} + +void exec_queue_argv_free(char **argv) +{ + int i; + + for(i = 0; argv[i]; i++) { + free(argv[i]); + } + + free(argv); +} + +char **exec_queue_envp(void) +{ + char **envp; + char buf[BUFSIZ]; + + if((envp = malloc(4 * sizeof(char *))) == NULL) { + syslog(LOG_ERR, "%s: %s: malloc failed %s", __FILE__, __FUNCTION__, strerror(errno)); + return(NULL); + } + + snprintf(buf, BUFSIZ - 1, "LANG=%s", getenv("LANG")); + envp[0] = strdup(buf); + + snprintf(buf, BUFSIZ - 1, "PATH=%s", getenv("PATH")); + envp[1] = strdup(buf); + + snprintf(buf, BUFSIZ - 1, "TERM=%s", getenv("TERM")); + envp[2] = strdup(buf); + + envp[3] = NULL; + + return(envp); +} + +void exec_queue_envp_free(char **envp) +{ + int i; + + for(i = 0; envp[i]; i++) { + free(envp[i]); + } + + free(envp); +} + +/* EOF */ diff --git a/forkexec.h b/forkexec.h new file mode 100644 index 0000000..e4ee0e9 --- /dev/null +++ b/forkexec.h @@ -0,0 +1,29 @@ +/* + +(C) 2009-2011 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#ifndef __FORKEXEC_H__ +#define __FORKEXEC_H__ + +pid_t forkexec(char **argv, char **envp); +void create_sigchld_hdl(void); +void exec_queue_add(char *queue, char **argv, char **envp); +void exec_queue_process(void); +char **exec_queue_argv(char *fmt, ...); +void exec_queue_argv_free(char **argv); +char **exec_queue_envp(void); +void exec_queue_envp_free(char **envp); +void exec_queue_delete(pid_t pid); +void exec_queue_free(void); + +#if defined(DEBUG) +void exec_queue_dump(void); +#endif + +#endif + +/* EOF */ diff --git a/globals.c b/globals.c new file mode 100644 index 0000000..9af3a6f --- /dev/null +++ b/globals.c @@ -0,0 +1,126 @@ +/* + +(C) 2009-2011 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#include <stdio.h> +#include <syslog.h> + +#include "globals.h" +#include "defs.h" + +static char *prog = NULL; +static int cont = TRUE; +static int dump = FALSE; +static int ident = 0; +static int reload_cfg = FALSE; +static int dump_if_list = FALSE; +static char *configfile = "/etc/lsm/lsm.conf"; +static char *pidfile = "/var/run/lsm.pid"; +static int nodaemon = 0; +static char *status_str[] = { "down", "up", "unknown", "long_down" }; + +void set_prog(char *val) +{ + prog = val; +} + +char *get_prog(void) +{ + if(prog == NULL) { + syslog(LOG_ERR, "%s: called with prog unset", __FUNCTION__); + return("prog unset"); + } + + return(prog); +} + +void set_cont(const int val) +{ + cont = val; +} + +int get_cont(void) +{ + return(cont); +} + +void set_dump(const int val) +{ + dump = val; +} + +int get_dump(void) +{ + return(dump); +} + +void set_ident(const int val) +{ + ident = val; +} + +int get_ident(void) +{ + return(ident); +} + +void set_reload_cfg(const int val) +{ + reload_cfg = val; +} + +int get_reload_cfg(void) +{ + return(reload_cfg); +} + +void set_dump_if_list(const int val) +{ + dump_if_list = val; +} + +int get_dump_if_list(void) +{ + return(dump_if_list); +} + +void set_configfile(char *val) +{ + configfile = val; +} + +char *get_configfile(void) +{ + return(configfile); +} + +void set_pidfile(char *val) +{ + pidfile = val; +} + +char *get_pidfile(void) +{ + return(pidfile); +} + +void set_nodaemon(const int val) +{ + nodaemon = val; +} + +int get_nodaemon(void) +{ + return(nodaemon); +} + +char *get_status_str(STATUS val) +{ + return(status_str[val]); +} + +/* EOF */ diff --git a/globals.h b/globals.h new file mode 100644 index 0000000..3ea3199 --- /dev/null +++ b/globals.h @@ -0,0 +1,36 @@ +/* + +(C) 2009-2011 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#ifndef __GLOBALS_H__ +#define __GLOBALS_H__ + +#include "config.h" + +void set_prog(char *val); +char *get_prog(void); +void set_cont(const int val); +int get_cont(void); +void set_dump(const int val); +int get_dump(void); +void set_ident(const int val); +int get_ident(void); +void set_reload_cfg(const int val); +int get_reload_cfg(void); +void set_dump_if_list(const int val); +int get_dump_if_list(void); +void set_configfile(char *val); +char *get_configfile(void); +void set_pidfile(char *val); +char *get_pidfile(void); +void set_nodaemon(const int val); +int get_nodaemon(void); +char *get_status_str(STATUS val); + +#endif + +/* EOF */ diff --git a/group_script b/group_script new file mode 100644 index 0000000..7010492 --- /dev/null +++ b/group_script @@ -0,0 +1,55 @@ +#!/bin/sh +# +# Copyright (C) 2012,2015 Tuomo Soini <tis@foobar.fi> +# +# License: GPLv2 +# + +# +# event handling script for use with lsm groups +# + +STATE=${1} +NAME=${2} +CHECKIP=${3} +DEVICE=${4} +WARN_EMAIL=${5} +REPLIED=${6} +WAITING=${7} +TIMEOUT=${8} +REPLY_LATE=${9} +CONS_RCVD=${10} +CONS_WAIT=${11} +CONS_MISS=${12} +AVG_RTT=${13} +SRCIP=${14} +PREVSTATE=${15} +TIMESTAMP=${16} + +if [ -z "${WARN_EMAIL}" ] ; then + exit 0 +fi + +DATE=$(date --date=@${TIMESTAMP}) + +cat <<EOM | mail -s "LSM: ${NAME} ${STATE}" ${WARN_EMAIL} + +Hi, + +Your group ${NAME} has changed its state to ${STATE} at ${DATE}. + +Following parameters were passed: +prevstate = ${PREVSTATE} +newstate = ${STATE} +name = ${NAME} +warn_email = ${WARN_EMAIL} + +Packet statuses are not available for groups. + +BR, +Your LSM installation + +EOM + +exit 0 +# diff --git a/icmp6_t.c b/icmp6_t.c new file mode 100644 index 0000000..e0928f8 --- /dev/null +++ b/icmp6_t.c @@ -0,0 +1,118 @@ +/* + + (C) 2011 Mika Ilmaranta <ilmis@nullnet.fi> + +*/ + +#include <stdio.h> + +#include "icmp6_t.h" + +struct icmp6msg icmp6msgs[] = { + /* error messages */ + { 0, 0, "Reserved", "" }, + + { 1, 0, "Destination Unreachable", "no route to destination" }, + { 1, 1, "Destination Unreachable", "communication with destination administratively prohibited" }, + { 1, 2, "Destination Unreachable", "beyond scope of source address" }, + { 1, 3, "Destination Unreachable", "address unreachable" }, + { 1, 4, "Destination Unreachable", "port unreachable" }, + { 1, 5, "Destination Unreachable", "source address failed ingress/egress policy" }, + { 1, 6, "Destination Unreachable", "reject route to destination" }, + { 1, 7, "Destination Unreachable", "Error in Source Routing Header" }, + + { 2, 0, "Packet Too Big", "" }, + + { 3, 0, "Time Exceeded", "hop limit exceeded in transit" }, + { 3, 1, "Time Exceeded", "fragment reassembly time exceeded" }, + + { 4, 0, "Parameter Problem", "erroneous header field encountered" }, + { 4, 1, "Parameter Problem", "unrecognized Next Header type encountered" }, + { 4, 2, "Parameter Problem", "unrecognized IPv6 option encountered" }, + + { 100, 0, "Private experimentation", "" }, + { 101, 0, "Private experimentation", "" }, + { 127, 0, "Reserved for expansion of ICMPv6 error messages", "" }, + + /* infomational messages */ + { 128, 0, "Echo Request", "" }, + + { 129, 0, "Echo Reply", "" }, + + { 130, 0, "Multicast Listener Query", "" }, + + { 131, 0, "Multicast Listener Report", "" }, + + { 132, 0, "Multicast Listener Done", "" }, + + { 133, 0, "Router Solicitation (NDP)", "" }, + + { 134, 0, "Router Advertisement (NDP)", "" }, + + { 135, 0, "Neighbor Solicitation (NDP)", "" }, + + { 136, 0, "Neighbor Advertisement (NDP)", "" }, + + { 137, 0, "Redirect Message (NDP)", "" }, + + { 138, 0, "Router Renumbering", "Router Renumbering Command" }, + { 138, 1, "Router Renumbering", "Router Renumbering Result" }, + { 138, 255, "Router Renumbering", "Sequence Number Reset" }, + + { 139, 0, "ICMP Node Information Query", "The Data field contains an IPv6 address which is the Subject of this Query." }, + { 139, 1, "ICMP Node Information Query", "The Data field contains a name which is the Subject of this Query, or is empty, as in the case of a NOOP." }, + { 139, 2, "ICMP Node Information Query", "The Data field contains an IPv4 address which is the Subject of this Query." }, + + { 140, 0, "ICMP Node Information Response", "A successful reply. The Reply Data field may or may not be empty." }, + { 140, 1, "ICMP Node Information Response", "The Responder refuses to supply the answer. The Reply Data field will be empty." }, + { 140, 2, "ICMP Node Information Response", "The Qtype of the Query is unknown to the Responder. The Reply Data field will be empty." }, + + { 141, 0, "Inverse Neighbor Discovery Solicitation Message", "" }, + + { 142, 0, "Inverse Neighbor Discovery Advertisement Message", "" }, + + { 143, 0, "Multicast Listener Discovery (MLDv2) reports (RFC 3810)", "" }, + + { 144, 0, "Home Agent Address Discovery Request Message", "" }, + + { 145, 0, "Home Agent Address Discovery Reply Message", "" }, + + { 146, 0, "Mobile Prefix Solicitation", "" }, + + { 147, 0, "Mobile Prefix Advertisement", "" }, + + { 148, 0, "Certification Path Solicitation (SEND)", "" }, + + { 149, 0, "Certification Path Advertisement (SEND)", "" }, + + { 151, 0, "Multicast Router Advertisement (MRD)", "" }, + + { 152, 0, "Multicast Router Solicitation (MRD)", "" }, + + { 153, 0, "Multicast Router Termination (MRD)", "" }, + + { 200, 0, "Private experimentation", "" }, + + { 201, 0, "Private experimentation", "" }, + + { 255, 0, "Reserved for expansion of ICMPv6 informational messages", "" }, + + { 256, 256, "impossible combination", "impossible combination" } +}; + +struct icmp6msg *stricmp6(int type, int code) +{ + int i; + + if(type > 255) return(&((struct icmp6msg) { 256, 256, "unknown", "unknown" })); + + for(i = 0; icmp6msgs[i].type <= type; i++) + if(icmp6msgs[i].type == type && icmp6msgs[i].code == code) return(&(icmp6msgs[i])); + + for(i = 0; icmp6msgs[i].type <= type; i++) + if(icmp6msgs[i].type == type) return(&(icmp6msgs[i])); + + return(&((struct icmp6msg) { 256, 256, "unknown", "unknown" })); +} + +/* EOF */ diff --git a/icmp6_t.h b/icmp6_t.h new file mode 100644 index 0000000..d3c1f5b --- /dev/null +++ b/icmp6_t.h @@ -0,0 +1,22 @@ +/* + + (C) 2011 Mika Ilmaranta <ilmis@nullnet.fi> + +*/ + +#ifndef __ICMP6_T_H__ +#define __ICMP6_T_H__ + +struct icmp6msg +{ + int type; + int code; + char *type_msg; + char *code_msg; +}; + +struct icmp6msg *stricmp6(int type, int code); + +#endif + +/* EOF */ diff --git a/icmp_t.c b/icmp_t.c new file mode 100644 index 0000000..3620920 --- /dev/null +++ b/icmp_t.c @@ -0,0 +1,340 @@ +/* + +(C) 2009 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#include <stdio.h> + +#include "icmp_t.h" + +static struct icmpmsg icmpmsgs[] = { + { 0, 0, "Echo Reply", "" }, + + { 1, 0, "Reserved", "" }, + { 2, 0, "Reserved", "" }, + + { 3, 0, "Destination Unreachable", "Destination network unreachable" }, + { 3, 1, "Destination Unreachable", "Destination host unreachable" }, + { 3, 2, "Destination Unreachable", "Destination protocol unreachable" }, + { 3, 3, "Destination Unreachable", "Destination port unreachable" }, + { 3, 4, "Destination Unreachable", "Fragmentation required, and DF flag set" }, + { 3, 5, "Destination Unreachable", "Source route failed" }, + { 3, 6, "Destination Unreachable", "Destination network unknown" }, + { 3, 7, "Destination Unreachable", "Destination host unknown" }, + { 3, 8, "Destination Unreachable", "Source host isolated" }, + { 3, 9, "Destination Unreachable", "Network administratively prohibited" }, + { 3, 10, "Destination Unreachable", "Host administratively prohibited" }, + { 3, 11, "Destination Unreachable", "Network unreachable for TOS" }, + { 3, 12, "Destination Unreachable", "Host unreachable for TOS" }, + { 3, 13, "Destination Unreachable", "Communication administratively prohibited" }, + + { 4, 0, "Source Quench", "Source quench (congestion control)" }, + + { 5, 0, "Redirect Message", "Redirect Datagram for the Network" }, + { 5, 1, "Redirect Message", "Redirect Datagram for the Host" }, + { 5, 2, "Redirect Message", "Redirect Datagram for the TOS & network" }, + { 5, 3, "Redirect Message", "Redirect Datagram for the TOS & host" }, + + { 6, 0, "Alternate Host Address", "" }, + + { 7, 0, "Reserved", "" }, + + { 8, 0, "Echo Request", "Echo request (used to ping)" }, + + { 9, 0, "Router Advertisement", "Router Advertisement" }, + + { 10, 0, "Router Solicitation", "Router discovery/selection/solicitation" }, + + { 11, 0, "Time Exceeded", "TTL expired in transit" }, + { 11, 1, "Time Exceeded", "Fragment reassembly time exceeded" }, + + { 12, 0, "Parameter Problem: Bad IP header", "Pointer indicates the error" }, + { 12, 1, "Parameter Problem: Bad IP header", "Missing a required option" }, + { 12, 2, "Parameter Problem: Bad IP header", "Bad length" }, + + { 13, 0, "Timestamp", "Timestamp" }, + + { 14, 0, "Timestamp Reply", "Timestamp reply" }, + + { 15, 0, "Information Request", "Information Request" }, + + { 16, 0, "Information Reply", "Information Reply" }, + + { 17, 0, "Address Mask Request", "Address Mask Request" }, + + { 18, 0, "Address Mask Reply", "Address Mask Reply" }, + + { 19, 0, "Reserved for security", "Reserved for security" }, + + { 20, 0, "Reserved for robustness experiment", "" }, + { 21, 0, "Reserved for robustness experiment", "" }, + { 22, 0, "Reserved for robustness experiment", "" }, + { 23, 0, "Reserved for robustness experiment", "" }, + { 24, 0, "Reserved for robustness experiment", "" }, + { 25, 0, "Reserved for robustness experiment", "" }, + { 26, 0, "Reserved for robustness experiment", "" }, + { 27, 0, "Reserved for robustness experiment", "" }, + { 28, 0, "Reserved for robustness experiment", "" }, + { 29, 0, "Reserved for robustness experiment", "" }, + + { 30, 0, "Traceroute", "Information Request" }, + + { 31, 0, "Datagram Conversion Error", "" }, + + { 32, 0, "Mobile Host Redirect", "" }, + + { 33, 0, "Where-Are-You (originally meant for IPv6)", "" }, + + { 34, 0, "Here-I-Am (originally meant for IPv6)", "" }, + + { 35, 0, "Mobile Registration Request", "" }, + + { 36, 0, "Mobile Registration Reply", "" }, + + { 37, 0, "Domain Name Request", "" }, + + { 38, 0, "Domain Name Reply", "" }, + + { 39, 0, "SKIP Algorithm Discovery Protocol, Simple Key-Management for Internet Protocol", "" }, + + { 40, 0, "Photuris, Security failures", "" }, + + { 41, 0, "ICMP for experimental mobility protocols such as Seamoby [RFC4065]", "" }, + + { 42, 0, "Reserved", "" }, + { 43, 0, "Reserved", "" }, + { 44, 0, "Reserved", "" }, + { 45, 0, "Reserved", "" }, + { 46, 0, "Reserved", "" }, + { 47, 0, "Reserved", "" }, + { 48, 0, "Reserved", "" }, + { 49, 0, "Reserved", "" }, + { 50, 0, "Reserved", "" }, + { 51, 0, "Reserved", "" }, + { 52, 0, "Reserved", "" }, + { 53, 0, "Reserved", "" }, + { 54, 0, "Reserved", "" }, + { 55, 0, "Reserved", "" }, + { 56, 0, "Reserved", "" }, + { 57, 0, "Reserved", "" }, + { 58, 0, "Reserved", "" }, + { 59, 0, "Reserved", "" }, + { 60, 0, "Reserved", "" }, + { 61, 0, "Reserved", "" }, + { 62, 0, "Reserved", "" }, + { 63, 0, "Reserved", "" }, + { 64, 0, "Reserved", "" }, + { 65, 0, "Reserved", "" }, + { 66, 0, "Reserved", "" }, + { 67, 0, "Reserved", "" }, + { 68, 0, "Reserved", "" }, + { 69, 0, "Reserved", "" }, + { 70, 0, "Reserved", "" }, + { 71, 0, "Reserved", "" }, + { 72, 0, "Reserved", "" }, + { 73, 0, "Reserved", "" }, + { 74, 0, "Reserved", "" }, + { 75, 0, "Reserved", "" }, + { 76, 0, "Reserved", "" }, + { 77, 0, "Reserved", "" }, + { 78, 0, "Reserved", "" }, + { 79, 0, "Reserved", "" }, + { 80, 0, "Reserved", "" }, + { 81, 0, "Reserved", "" }, + { 82, 0, "Reserved", "" }, + { 83, 0, "Reserved", "" }, + { 84, 0, "Reserved", "" }, + { 85, 0, "Reserved", "" }, + { 86, 0, "Reserved", "" }, + { 87, 0, "Reserved", "" }, + { 88, 0, "Reserved", "" }, + { 89, 0, "Reserved", "" }, + { 90, 0, "Reserved", "" }, + { 91, 0, "Reserved", "" }, + { 92, 0, "Reserved", "" }, + { 93, 0, "Reserved", "" }, + { 94, 0, "Reserved", "" }, + { 95, 0, "Reserved", "" }, + { 96, 0, "Reserved", "" }, + { 97, 0, "Reserved", "" }, + { 98, 0, "Reserved", "" }, + { 99, 0, "Reserved", "" }, + { 100, 0, "Reserved", "" }, + { 101, 0, "Reserved", "" }, + { 102, 0, "Reserved", "" }, + { 103, 0, "Reserved", "" }, + { 104, 0, "Reserved", "" }, + { 105, 0, "Reserved", "" }, + { 106, 0, "Reserved", "" }, + { 107, 0, "Reserved", "" }, + { 108, 0, "Reserved", "" }, + { 109, 0, "Reserved", "" }, + { 110, 0, "Reserved", "" }, + { 111, 0, "Reserved", "" }, + { 112, 0, "Reserved", "" }, + { 113, 0, "Reserved", "" }, + { 114, 0, "Reserved", "" }, + { 115, 0, "Reserved", "" }, + { 116, 0, "Reserved", "" }, + { 117, 0, "Reserved", "" }, + { 118, 0, "Reserved", "" }, + { 119, 0, "Reserved", "" }, + { 120, 0, "Reserved", "" }, + { 121, 0, "Reserved", "" }, + { 122, 0, "Reserved", "" }, + { 123, 0, "Reserved", "" }, + { 124, 0, "Reserved", "" }, + { 125, 0, "Reserved", "" }, + { 126, 0, "Reserved", "" }, + { 127, 0, "Reserved", "" }, + { 128, 0, "Reserved", "" }, + { 129, 0, "Reserved", "" }, + { 130, 0, "Reserved", "" }, + { 131, 0, "Reserved", "" }, + { 132, 0, "Reserved", "" }, + { 133, 0, "Reserved", "" }, + { 134, 0, "Reserved", "" }, + { 135, 0, "Reserved", "" }, + { 136, 0, "Reserved", "" }, + { 137, 0, "Reserved", "" }, + { 138, 0, "Reserved", "" }, + { 139, 0, "Reserved", "" }, + { 140, 0, "Reserved", "" }, + { 141, 0, "Reserved", "" }, + { 142, 0, "Reserved", "" }, + { 143, 0, "Reserved", "" }, + { 144, 0, "Reserved", "" }, + { 145, 0, "Reserved", "" }, + { 146, 0, "Reserved", "" }, + { 147, 0, "Reserved", "" }, + { 148, 0, "Reserved", "" }, + { 149, 0, "Reserved", "" }, + { 150, 0, "Reserved", "" }, + { 151, 0, "Reserved", "" }, + { 152, 0, "Reserved", "" }, + { 153, 0, "Reserved", "" }, + { 154, 0, "Reserved", "" }, + { 155, 0, "Reserved", "" }, + { 156, 0, "Reserved", "" }, + { 157, 0, "Reserved", "" }, + { 158, 0, "Reserved", "" }, + { 159, 0, "Reserved", "" }, + { 160, 0, "Reserved", "" }, + { 161, 0, "Reserved", "" }, + { 162, 0, "Reserved", "" }, + { 163, 0, "Reserved", "" }, + { 164, 0, "Reserved", "" }, + { 165, 0, "Reserved", "" }, + { 166, 0, "Reserved", "" }, + { 167, 0, "Reserved", "" }, + { 168, 0, "Reserved", "" }, + { 169, 0, "Reserved", "" }, + { 170, 0, "Reserved", "" }, + { 171, 0, "Reserved", "" }, + { 172, 0, "Reserved", "" }, + { 173, 0, "Reserved", "" }, + { 174, 0, "Reserved", "" }, + { 175, 0, "Reserved", "" }, + { 176, 0, "Reserved", "" }, + { 177, 0, "Reserved", "" }, + { 178, 0, "Reserved", "" }, + { 179, 0, "Reserved", "" }, + { 180, 0, "Reserved", "" }, + { 181, 0, "Reserved", "" }, + { 182, 0, "Reserved", "" }, + { 183, 0, "Reserved", "" }, + { 184, 0, "Reserved", "" }, + { 185, 0, "Reserved", "" }, + { 186, 0, "Reserved", "" }, + { 187, 0, "Reserved", "" }, + { 188, 0, "Reserved", "" }, + { 189, 0, "Reserved", "" }, + { 190, 0, "Reserved", "" }, + { 191, 0, "Reserved", "" }, + { 192, 0, "Reserved", "" }, + { 193, 0, "Reserved", "" }, + { 194, 0, "Reserved", "" }, + { 195, 0, "Reserved", "" }, + { 196, 0, "Reserved", "" }, + { 197, 0, "Reserved", "" }, + { 198, 0, "Reserved", "" }, + { 199, 0, "Reserved", "" }, + { 200, 0, "Reserved", "" }, + { 201, 0, "Reserved", "" }, + { 202, 0, "Reserved", "" }, + { 203, 0, "Reserved", "" }, + { 204, 0, "Reserved", "" }, + { 205, 0, "Reserved", "" }, + { 206, 0, "Reserved", "" }, + { 207, 0, "Reserved", "" }, + { 208, 0, "Reserved", "" }, + { 209, 0, "Reserved", "" }, + { 210, 0, "Reserved", "" }, + { 211, 0, "Reserved", "" }, + { 212, 0, "Reserved", "" }, + { 213, 0, "Reserved", "" }, + { 214, 0, "Reserved", "" }, + { 215, 0, "Reserved", "" }, + { 216, 0, "Reserved", "" }, + { 217, 0, "Reserved", "" }, + { 218, 0, "Reserved", "" }, + { 219, 0, "Reserved", "" }, + { 220, 0, "Reserved", "" }, + { 221, 0, "Reserved", "" }, + { 222, 0, "Reserved", "" }, + { 223, 0, "Reserved", "" }, + { 224, 0, "Reserved", "" }, + { 225, 0, "Reserved", "" }, + { 226, 0, "Reserved", "" }, + { 227, 0, "Reserved", "" }, + { 228, 0, "Reserved", "" }, + { 229, 0, "Reserved", "" }, + { 230, 0, "Reserved", "" }, + { 231, 0, "Reserved", "" }, + { 232, 0, "Reserved", "" }, + { 233, 0, "Reserved", "" }, + { 234, 0, "Reserved", "" }, + { 235, 0, "Reserved", "" }, + { 236, 0, "Reserved", "" }, + { 237, 0, "Reserved", "" }, + { 238, 0, "Reserved", "" }, + { 239, 0, "Reserved", "" }, + { 240, 0, "Reserved", "" }, + { 241, 0, "Reserved", "" }, + { 242, 0, "Reserved", "" }, + { 243, 0, "Reserved", "" }, + { 244, 0, "Reserved", "" }, + { 245, 0, "Reserved", "" }, + { 246, 0, "Reserved", "" }, + { 247, 0, "Reserved", "" }, + { 248, 0, "Reserved", "" }, + { 249, 0, "Reserved", "" }, + { 250, 0, "Reserved", "" }, + { 251, 0, "Reserved", "" }, + { 252, 0, "Reserved", "" }, + { 253, 0, "Reserved", "" }, + { 254, 0, "Reserved", "" }, + { 255, 0, "Reserved", "" }, + + { 256, 256, "impossible combination", "impossible combination" } +}; + +struct icmpmsg *stricmp(int type, int code) +{ + int i; + + if(type > 255) return(&((struct icmpmsg) { 256, 256, "unknown", "unknown" })); + + for(i = 0; icmpmsgs[i].type <= type; i++) + if(icmpmsgs[i].type == type && icmpmsgs[i].code == code) return(&(icmpmsgs[i])); + + for(i = 0; icmpmsgs[i].type <= type; i++) + if(icmpmsgs[i].type == type) return(&(icmpmsgs[i])); + + return(&((struct icmpmsg) { 256, 256, "unknown", "unknown" })); +} + +/* EOF */ diff --git a/icmp_t.h b/icmp_t.h new file mode 100644 index 0000000..59266d7 --- /dev/null +++ b/icmp_t.h @@ -0,0 +1,22 @@ +/* + + (C) 2009 Mika Ilmaranta <ilmis@nullnet.fi> + +*/ + +#ifndef __ICMP_T_H__ +#define __ICMP_T_H__ + +struct icmpmsg +{ + int type; + int code; + char *type_msg; + char *code_msg; +}; + +struct icmpmsg *stricmp(int type, int code); + +#endif + +/* EOF */ @@ -0,0 +1,2162 @@ +/* + +(C) 2009-2015 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <errno.h> +#include <time.h> +#include <signal.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stddef.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <netinet/in_systm.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/select.h> +#include <syslog.h> +#include <sys/wait.h> +#include <stdarg.h> +#include <fcntl.h> + +#include <sys/ioctl.h> +#include <linux/if.h> +#include <sys/uio.h> + +#include "icmp_t.h" +#include "icmp6_t.h" +#include "config.h" +#include "cksum.h" +#include "globals.h" +#include "signal_handler.h" +#include "forkexec.h" +#include "timecalc.h" +#include "lsm.h" +#ifndef NO_PLUGIN_EXPORT +#include "plugin_export.h" +#endif +#include "save_statuses.h" +#include "pidfile.h" +#include "cmdline.h" +#include "usage.h" + +typedef struct ping_data { + unsigned short id; /* target id */ + long ping_count; /* counts up to -c count or 1 */ + struct timeval ping_ts; /* time sent */ +} PING_DATA; + +static void update_stats(CONFIG *first); +static void dump_statuses(CONFIG *first); +static void decide(CONFIG *first); +static void groups_decide(GROUPS *firstg); +static int wait_for_replies(CONFIG **ctable); +static int ping_send(CONFIG *cur); +static int ping_rcv(CONFIG *first, char *buf, int len, struct sockaddr_in6 *saddr, unsigned int *slen, long usec, CONFIG **arp); +static int event_script_check(const char *path); +static int open_arp_sock(CONFIG *cur); +static int open_icmp_sock(CONFIG *cur); +static int probe_src_ip_addr(CONFIG *cur); +static void init_config_data(CONFIG *first, CONFIG *last, CONFIG ***ctable); +static void free_config_data(CONFIG *first); +#if defined(DEBUG) +static void dump_pkt(const void *buf, size_t len); +#endif + +static int num_hosts = 0; + +/* Main */ +int main(int argc, char *argv[]) { + TARGET *t = NULL; + CONFIG *first = NULL, *last = NULL, *cur; + GROUPS *firstg = NULL, *lastg = NULL; + CONFIG **ctable = NULL; + struct timeval last_sent_time = {0, 0}; + int start = 0; + + openlog("lsm", LOG_PID, LOG_DAEMON); + + cmdline_parse(argc, argv); + + init_config(); + + if(read_config(get_configfile(), &first, &last, &firstg, &lastg)) { + usage_and_exit(); + } + + if(cfg.debug >= 9) syslog(LOG_INFO, "my ident is %d\n", get_ident()); + + if(!first) { + syslog(LOG_ERR, "no targets found in config file"); + exit(1); + } + + if(cfg.debug >= 9) dump_config(&first, &last, &firstg, &lastg); + + /* check pid file */ + if(pidfile_open() != 0) exit(1); + + /* detach from controlling terminal if nodaemon global is not set */ + if(get_nodaemon() == 0) { + if(daemon(1, 0)) { + syslog(LOG_ERR, "daemon failed while trying to detach"); + return(1); + } + } + + if(pidfile_update() != 0) exit(1); + + set_ident(getpid() & 0xFFFF); + +#ifndef NO_PLUGIN_EXPORT + plugin_export_init(); +#endif + + init_config_data(first, last, &ctable); + + signal(SIGINT, signal_handler); + signal(SIGUSR1, signal_handler); + signal(SIGUSR2, signal_handler); + signal(SIGHUP, signal_handler); + + /* + Create the handler for child signals. This will clean up + any forked child after an event has occured. + */ + create_sigchld_hdl(); + + struct timeval last_decision = {0, 0}; + + /* the main loop */ + while(get_cont()) { + struct timeval tv = {0, 0}; + + if(get_reload_cfg()) { + + save_statuses(first); + + /* reload config */ + free(ctable); + free_config_data(first); + if(reload_config(get_configfile(), &first, &last, &firstg, &lastg)) { + syslog(LOG_ERR, "reload config failed"); + exit(2); + } + init_config_data(first, last, &ctable); + + restore_statuses(first); + + set_reload_cfg(0); + } + + for(cur = first; cur; cur = cur->next) { + struct timeval current_time = {0, 0}; + + if(start) + while(wait_for_replies(ctable)); + + if(gettimeofday(¤t_time, NULL) == -1) { + syslog(LOG_INFO, "gettimeofday failed \"%s\"", strerror(errno)); + sleep(1); + continue; + } + + t = cur->data; + + if(timeval_diff_cmp(¤t_time, &last_sent_time, TIMEVAL_DIFF_CMP_LT, MIN_PERHOST_INTERVAL / 1000000L, MIN_PERHOST_INTERVAL % 1000000L)) continue; + + if(cur->startup_burst_pkts && t->used <= cur->startup_burst_pkts && !timeval_diff_cmp(¤t_time, &(t->last_send_time), TIMEVAL_DIFF_CMP_LT, (cur->startup_burst_interval * 1000) / 1000000L, (cur->startup_burst_interval * 1000) % 1000000L)); + else if(timeval_diff_cmp(¤t_time, &(t->last_send_time), TIMEVAL_DIFF_CMP_LT, (cur->interval_ms * 1000) / 1000000L, (cur->interval_ms * 1000) % 1000000L)) continue; + + if(cur->check_arp) { + open_arp_sock(cur); + } else { + open_icmp_sock(cur); + } + + if(ping_send(cur)) { + if(cfg.debug >= 9) syslog(LOG_INFO, "ping_send failed to %s", cur->name); + } + else { + gettimeofday(&last_sent_time, NULL); + start = 1; + } + } + + gettimeofday(&tv, NULL); + if(timeval_diff_cmp(&tv, &last_decision, TIMEVAL_DIFF_CMP_GT, 1, 0)) { /* make decisions at 1s intervals */ + gettimeofday(&last_decision, NULL); + + update_stats(first); + decide(first); + dump_statuses(first); + + groups_decide(firstg); + +#if defined(DEBUG) + exec_queue_dump(); +#endif + exec_queue_process(); + +#ifndef NO_PLUGIN_EXPORT + plugin_export(first); +#endif + } + } /* while cont */ + + /* if we wrote pid file then close and remove it */ + pidfile_close(); + + free(ctable); + free_config_data(first); + free_config(&first, &last, &firstg, &lastg); + exec_queue_free(); + + closelog(); + + return(0); +} + +static void free_config_data(CONFIG *first) { + CONFIG *cur; + + for(cur = first; cur; cur = cur->next) { + TARGET *t; + + t = cur->data; + if(t->sock != -1) close(t->sock); + free(t); + } +} + +static void update_stats(CONFIG *first) { + struct timeval current_time = {0, 0}; + CONFIG *cur; + + gettimeofday(¤t_time, NULL); + + for(cur = first; cur; cur = cur->next) { + TARGET *t; + int i, seq, ind; + long rtt = 0; + + t = cur->data; + + t->timeout = 0; + t->replied = 0; + t->waiting = 0; + t->reply_late = 0; + t->consecutive_waiting = 0; + t->consecutive_missing = 0; + t->consecutive_rcvd = 0; + + /* check consecutive pkts */ + seq = t->seq % FOLLOWED_PKTS; + + for(i = (seq - 2); i > (seq - 2) - FOLLOWED_PKTS; i--) { + ind = (i >= 0) ? i : i + FOLLOWED_PKTS; + if(!t->sentpkts[ind].flags.used) break; + + if(t->sentpkts[ind].flags.waiting) t->consecutive_waiting++; + else break; + } + + for(i = (seq - 2); i > (seq - 2) - FOLLOWED_PKTS; i--) { + ind = (i >= 0) ? i : i + FOLLOWED_PKTS; + if(!t->sentpkts[ind].flags.used) break; + + if(t->sentpkts[ind].flags.timeout || t->sentpkts[ind].flags.waiting) t->consecutive_missing++; + else break; + } + + for(i = (seq - 2); i > (seq - 2) - FOLLOWED_PKTS; i--) { + ind = (i >= 0) ? i : i + FOLLOWED_PKTS; + if(!t->sentpkts[ind].flags.used) break; + + if(t->sentpkts[ind].flags.replied && !t->sentpkts[ind].flags.timeout) t->consecutive_rcvd++; + else break; + } + + /* count pkt states */ + for(i = 0; i < FOLLOWED_PKTS; i++) { + if(!t->sentpkts[i].flags.used) continue; + + if(timeval_diff_cmp(¤t_time, &t->sentpkts[i].sent_time, TIMEVAL_DIFF_CMP_GT, (cur->timeout_ms * 1000) / 1000000L, (cur->timeout_ms * 1000) % 1000000L) && t->sentpkts[i].flags.waiting) { + t->sentpkts[i].flags.timeout = 1; + } + + if(t->sentpkts[i].flags.replied && t->sentpkts[i].flags.timeout) t->reply_late++; + + if(t->sentpkts[i].flags.replied) { + t->replied++; + rtt += t->sentpkts[i].rtt; /* count rtt sum in usec from replied pkts rtt which is in usec */ + } + if(t->sentpkts[i].flags.timeout) t->timeout++; + if(t->sentpkts[i].flags.waiting) t->waiting++; + } + /* avg_rtt in usec */ + t->avg_rtt = rtt / (t->replied ? t->replied : 1); + + /* update loss max info */ + if(t->timeout > t->timeout_max) t->timeout_max = t->timeout; + if(t->consecutive_missing > t->consecutive_missing_max) t->consecutive_missing_max = t->consecutive_missing; + + if(cfg.debug >= 9) syslog(LOG_INFO, "name = %s, replied = %d, waiting = %d, timeout = %d, late reply = %d, cons rcvd = %d, cons wait = %d, cons miss = %d, avg_rtt = %.3f, seq = %d, status = %s", + cur->name, t->replied, t->waiting, t->timeout, t->reply_late, t->consecutive_rcvd, t->consecutive_waiting, t->consecutive_missing, t->avg_rtt / 1000.0, t->seq, get_status_str(t->status)); + + } +} + +static void dump_statuses(CONFIG *first) { + CONFIG *cur; + + for(cur = first; cur; cur = cur->next) { + TARGET *t; + + t = cur->data; + + if((t->status == DOWN || t->status == LONG_DOWN) && t->downseq == (t->seq % FOLLOWED_PKTS) && t->seq != t->downseqreported && !t->status_change) syslog(LOG_INFO, "link %s still down", cur->name); + + /* dump is controlled by SIGUSR1 and then we should show all statuses anyway */ + if(get_dump() || t->status_change || ((t->status == DOWN || t->status == LONG_DOWN) && t->downseq == (t->seq % FOLLOWED_PKTS) && t->seq != t->downseqreported && !t->status_change)) { + if(cfg.debug >= 6) syslog(LOG_INFO, "name = %s, replied = %d, waiting = %d, timeout = %d, timeout max = %d, late reply = %d, cons rcvd = %d, cons wait = %d, cons miss = %d, cons miss max = %d, avg_rtt = %.3f, seq = %d, status = %s", + cur->name, t->replied, t->waiting, t->timeout, t->timeout_max, t->reply_late, t->consecutive_rcvd, t->consecutive_waiting, t->consecutive_missing, t->consecutive_missing_max, t->avg_rtt / 1000.0, t->seq, get_status_str(t->status)); + + if(cfg.debug >= 7) { + /* 100 should be enough for the comments and such, but I don't care to count */ + char buf[FOLLOWED_PKTS + 100]; + int i, seq; + + seq = t->seq % FOLLOWED_PKTS; + + sprintf(buf, "seq "); + for(i = 0; i < FOLLOWED_PKTS; i++) { + if(i == seq) strcat(buf, "*"); + else strcat(buf, " "); + } + syslog(LOG_INFO, "%s", buf); + + sprintf(buf, "used "); + for(i = 0; i < FOLLOWED_PKTS; i++) { + sprintf(buf + strlen(buf), "%d", t->sentpkts[i].flags.used); + } + syslog(LOG_INFO, "%s", buf); + + sprintf(buf, "wait "); + for(i = 0; i < FOLLOWED_PKTS; i++) { + sprintf(buf + strlen(buf), "%d", t->sentpkts[i].flags.waiting); + } + syslog(LOG_INFO, "%s", buf); + + sprintf(buf, "replied "); + for(i = 0; i < FOLLOWED_PKTS; i++) { + sprintf(buf + strlen(buf), "%d", t->sentpkts[i].flags.replied); + } + syslog(LOG_INFO, "%s", buf); + + sprintf(buf, "timeout "); + for(i = 0; i < FOLLOWED_PKTS; i++) { + sprintf(buf + strlen(buf), "%d", t->sentpkts[i].flags.timeout); + } + syslog(LOG_INFO, "%s", buf); + + sprintf(buf, "error "); + for(i = 0; i < FOLLOWED_PKTS; i++) { + sprintf(buf + strlen(buf), "%d", t->sentpkts[i].flags.error); + } + syslog(LOG_INFO, "%s", buf); + + if(t->status == UP && t->status_change) { + t->timeout_max = 0; + t->consecutive_missing_max = 0; + } + } + + t->downseqreported = t->seq; + } + } + if(get_dump()) set_dump(0); /* if we just dumped then don't dump next time. flags don't change that frequently */ +} + +static void decide(CONFIG *first) { + struct timeval current_time = {0, 0}; + CONFIG *cur; + + gettimeofday(¤t_time, NULL); + + for(cur = first; cur; cur = cur->next) { + TARGET *t; + STATUS prevstatus; + + t = cur->data; + + /* reset any previous connection status_change state */ + t->status_change = 0; + + prevstatus = t->status; + + /* up or unknown */ + if(t->status == UP || t->status == UNKNOWN) { + if(t->timeout >= cur->max_packet_loss || t->consecutive_missing >= cur->max_successive_pkts_lost) { + /* change to down state */ + t->status_change = 1; + t->status = DOWN; + +#if !defined(NO_PLUGIN_EXPORT) && !defined(NO_PLUGIN_EXPORT_STATUS) + plugin_export_status(first); +#endif + + if(cfg.debug >= 8) syslog(LOG_INFO, "link %s down event", cur->name); + if(event_script_check(cur->eventscript)) { + char sbuf[INET6_ADDRSTRLEN]; + char **argv; + char **envp; + + argv = exec_queue_argv("%s %s %s %s %s %s %d %d %d %d %d %d %d %d %s %s %d", + cur->eventscript, + get_status_str(t->status), + cur->name, + cur->checkip, + cur->device ? cur->device : "", + cur->warn_email ? cur->warn_email : "", + t->replied, + t->waiting, + t->timeout, + t->reply_late, + t->consecutive_rcvd, + t->consecutive_waiting, + t->consecutive_missing, + t->avg_rtt, + cur->dstinfo->ai_family == AF_INET ? inet_ntoa(t->src) : inet_ntop(AF_INET6, &t->src6, sbuf, INET6_ADDRSTRLEN), + get_status_str(prevstatus), + current_time.tv_sec); + envp = exec_queue_envp(); + + if(cur->queue && *cur->queue) { + exec_queue_add(cur->queue, argv, envp); + } else { + forkexec(argv, envp); + + exec_queue_argv_free(argv); + exec_queue_envp_free(envp); + } + } + + if(event_script_check(cur->notifyscript)) { + char sbuf[INET6_ADDRSTRLEN]; + char **argv; + char **envp; + + argv = exec_queue_argv("%s %s %s %s %s %s %d %d %d %d %d %d %d %d %s %s %d", + cur->notifyscript, + get_status_str(t->status), + cur->name, + cur->checkip, + cur->device ? cur->device : "", + cur->warn_email ? cur->warn_email : "", + t->replied, + t->waiting, + t->timeout, + t->reply_late, + t->consecutive_rcvd, + t->consecutive_waiting, + t->consecutive_missing, + t->avg_rtt, + cur->dstinfo->ai_family == AF_INET ? inet_ntoa(t->src) : inet_ntop(AF_INET6, &t->src6, sbuf, INET6_ADDRSTRLEN), + get_status_str(prevstatus), + current_time.tv_sec); + + envp = exec_queue_envp(); + + forkexec(argv, envp); + + exec_queue_argv_free(argv); + exec_queue_envp_free(envp); + } + + if(gettimeofday(&t->down_timestamp, NULL) == -1) { + syslog(LOG_INFO, "gettimeofday failed \"%s\"", strerror(errno)); + } + t->downseq = t->seq % FOLLOWED_PKTS; + t->downseqreported = 0; + } + } + + /* has it been down long? */ + if(t->status == DOWN && cur->long_down_time) { + if(timeval_diff_cmp(¤t_time, &t->down_timestamp, TIMEVAL_DIFF_CMP_GT, cur->long_down_time, 0)) { + /* special, LONG_DOWN is considered DOWN thus no status_change */ + t->status = LONG_DOWN; + + if(cfg.debug >= 8) syslog(LOG_INFO, "link %s long down event", cur->name); + if(event_script_check(cur->long_down_eventscript)) { + char sbuf[INET6_ADDRSTRLEN]; + char **argv; + char **envp; + + argv = exec_queue_argv("%s %s %s %s %s %s %d %d %d %d %d %d %d %d %s %s %d", + cur->long_down_eventscript, + get_status_str(t->status), + cur->name, + cur->checkip, + cur->device ? cur->device : "", + cur->long_down_email ? cur->long_down_email : "", + t->replied, + t->waiting, + t->timeout, + t->reply_late, + t->consecutive_rcvd, + t->consecutive_waiting, + t->consecutive_missing, + t->avg_rtt, + cur->dstinfo->ai_family == AF_INET ? inet_ntoa(t->src) : inet_ntop(AF_INET6, &t->src6, sbuf, INET6_ADDRSTRLEN), + get_status_str(prevstatus), + t->down_timestamp.tv_sec); + + envp = exec_queue_envp(); + + if(cur->queue && *cur->queue) { + exec_queue_add(cur->queue, argv, envp); + } else { + forkexec(argv, envp); + + exec_queue_argv_free(argv); + exec_queue_envp_free(envp); + } + } + + if(event_script_check(cur->long_down_notifyscript)) { + char sbuf[INET6_ADDRSTRLEN]; + char **argv; + char **envp; + + argv = exec_queue_argv("%s %s %s %s %s %s %d %d %d %d %d %d %d %d %s %s %d", + cur->long_down_notifyscript, + get_status_str(t->status), + cur->name, + cur->checkip, + cur->device ? cur->device : "", + cur->long_down_email ? cur->long_down_email : "", + t->replied, + t->waiting, + t->timeout, + t->reply_late, + t->consecutive_rcvd, + t->consecutive_waiting, + t->consecutive_missing, + t->avg_rtt, + cur->dstinfo->ai_family == AF_INET ? inet_ntoa(t->src) : inet_ntop(AF_INET6, &t->src6, sbuf, INET6_ADDRSTRLEN), + get_status_str(prevstatus), + t->down_timestamp.tv_sec); + + envp = exec_queue_envp(); + + forkexec(argv, envp); + + exec_queue_argv_free(argv); + exec_queue_envp_free(envp); + } + } + } + + /* down, long down or unknown */ + if(t->status == DOWN || t->status == LONG_DOWN || t->status == UNKNOWN) { + if((cur->startup_acceleration && t->consecutive_rcvd >= cur->startup_acceleration && (t->consecutive_rcvd + 1) >= t->used) || (t->timeout <= cur->min_packet_loss && t->consecutive_rcvd >= cur->min_successive_pkts_rcvd)) { + + t->status_change = 1; + t->status = UP; + +#if !defined(NO_PLUGIN_EXPORT) && !defined(NO_PLUGIN_EXPORT_STATUS) + plugin_export_status(first); +#endif + + /* report long_down to up */ + if(prevstatus == LONG_DOWN) { + if(event_script_check(cur->long_down_eventscript)) { + char sbuf[INET6_ADDRSTRLEN]; + char **argv; + char **envp; + + argv = exec_queue_argv("%s %s %s %s %s %s %d %d %d %d %d %d %d %d %s %s %d", + cur->long_down_eventscript, + get_status_str(t->status), + cur->name, + cur->checkip, + cur->device ? cur->device : "", + cur->long_down_email ? cur->long_down_email : "", + t->replied, + t->waiting, + t->timeout, + t->reply_late, + t->consecutive_rcvd, + t->consecutive_waiting, + t->consecutive_missing, + t->avg_rtt, + cur->dstinfo->ai_family == AF_INET ? inet_ntoa(t->src) : inet_ntop(AF_INET6, &t->src6, sbuf, INET6_ADDRSTRLEN), + get_status_str(prevstatus), + current_time.tv_sec); + + envp = exec_queue_envp(); + + if(cur->queue && *cur->queue) { + exec_queue_add(cur->queue, argv, envp); + } else { + forkexec(argv, envp); + + exec_queue_argv_free(argv); + exec_queue_envp_free(envp); + } + } + + if(event_script_check(cur->long_down_notifyscript)) { + char sbuf[INET6_ADDRSTRLEN]; + char **argv; + char **envp; + + argv = exec_queue_argv("%s %s %s %s %s %s %d %d %d %d %d %d %d %d %s %s %d", + cur->long_down_notifyscript, + get_status_str(t->status), + cur->name, + cur->checkip, + cur->device ? cur->device : "", + cur->long_down_email ? cur->long_down_email : "", + t->replied, + t->waiting, + t->timeout, + t->reply_late, + t->consecutive_rcvd, + t->consecutive_waiting, + t->consecutive_missing, + t->avg_rtt, + cur->dstinfo->ai_family == AF_INET ? inet_ntoa(t->src) : inet_ntop(AF_INET6, &t->src6, sbuf, INET6_ADDRSTRLEN), + get_status_str(prevstatus), + current_time.tv_sec); + + envp = exec_queue_envp(); + + forkexec(argv, envp); + + exec_queue_argv_free(argv); + exec_queue_envp_free(envp); + } + } + + /* change to up state */ + if(cfg.debug >= 8) syslog(LOG_INFO, "link %s up event", cur->name); + if(event_script_check(cur->eventscript)) { + char sbuf[INET6_ADDRSTRLEN]; + char **argv; + char **envp; + + argv = exec_queue_argv("%s %s %s %s %s %s %d %d %d %d %d %d %d %d %s %s %d", + cur->eventscript, + get_status_str(t->status), + cur->name, + cur->checkip, + cur->device ? cur->device : "", + cur->warn_email ? cur->warn_email : "", + t->replied, + t->waiting, + t->timeout, + t->reply_late, + t->consecutive_rcvd, + t->consecutive_waiting, + t->consecutive_missing, + t->avg_rtt, + cur->dstinfo->ai_family == AF_INET ? inet_ntoa(t->src) : inet_ntop(AF_INET6, &t->src6, sbuf, INET6_ADDRSTRLEN), + get_status_str(prevstatus), + current_time.tv_sec); + + envp = exec_queue_envp(); + + if(cur->queue && *cur->queue) { + exec_queue_add(cur->queue, argv, envp); + } else { + forkexec(argv, envp); + + exec_queue_argv_free(argv); + exec_queue_envp_free(envp); + } + } + + if((cur->unknown_up_notify || t->status != UNKNOWN) && event_script_check(cur->notifyscript)) { + char sbuf[INET6_ADDRSTRLEN]; + char **argv; + char **envp; + + argv = exec_queue_argv("%s %s %s %s %s %s %d %d %d %d %d %d %d %d %s %s %d", + cur->notifyscript, + get_status_str(t->status), + cur->name, + cur->checkip, + cur->device ? cur->device : "", + cur->warn_email ? cur->warn_email : "", + t->replied, + t->waiting, + t->timeout, + t->reply_late, + t->consecutive_rcvd, + t->consecutive_waiting, + t->consecutive_missing, + t->avg_rtt, + cur->dstinfo->ai_family == AF_INET ? inet_ntoa(t->src) : inet_ntop(AF_INET6, &t->src6, sbuf, INET6_ADDRSTRLEN), + get_status_str(prevstatus), + current_time.tv_sec); + + envp = exec_queue_envp(); + + forkexec(argv, envp); + + exec_queue_argv_free(argv); + exec_queue_envp_free(envp); + } + } + } + } +} + +static void groups_decide(GROUPS *firstg){ + GROUPS *curg; + GROUP_MEMBERS *curgm; + TARGET *t; + STATUS prevstatus; + struct timeval current_time = {0, 0}; + + gettimeofday(¤t_time, NULL); + + curg = firstg; + while(curg) { + prevstatus = curg->status; + curg->status = curg->logic; + + curgm = curg->fgm; + while(curgm) { + if(!curgm->cfg_ptr) break; + + t = curgm->cfg_ptr->data; + + /* if any one group member is in unknown status, group is in unknown status */ + if(t->status == UNKNOWN) { + curg->status = UNKNOWN; + break; + } + + if(!curg->logic) { + curg->status |= (t->status == DOWN || t->status == LONG_DOWN) ? DOWN : t->status; + } else { + curg->status &= (t->status == DOWN || t->status == LONG_DOWN) ? DOWN : t->status; + } + + curgm = curgm->next; + } + if(curg->status != prevstatus) { + if(curg->status == UP) { + /* group up event */ + if(cfg.debug >= 8) syslog(LOG_INFO, "group %s up event", curg->name); + if(event_script_check(curg->eventscript)) { + char **argv; + char **envp; + + argv = exec_queue_argv("%s %s %s %s %s %s %d %d %d %d %d %d %d %d %s %s %d", + curg->eventscript, + get_status_str(curg->status), + curg->name, + "", + "", + curg->warn_email ? curg->warn_email : "", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "", + get_status_str(prevstatus), + current_time.tv_sec); + envp = exec_queue_envp(); + + if(curg->queue && *curg->queue) { + exec_queue_add(curg->queue, argv, envp); + } else { + forkexec(argv, envp); + + exec_queue_argv_free(argv); + exec_queue_envp_free(envp); + } + } + if((curg->unknown_up_notify || prevstatus != UNKNOWN) && event_script_check(curg->notifyscript)) { + char **argv; + char **envp; + + argv = exec_queue_argv("%s %s %s %s %s %s %d %d %d %d %d %d %d %d %s %s %d", + curg->notifyscript, + get_status_str(curg->status), + curg->name, + "", + "", + curg->warn_email ? curg->warn_email : "", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "", + get_status_str(prevstatus), + current_time.tv_sec); + envp = exec_queue_envp(); + + forkexec(argv, envp); + + exec_queue_argv_free(argv); + exec_queue_envp_free(envp); + } + } + + if(curg->status == DOWN) { + /* group down event */ + if(cfg.debug >= 8) syslog(LOG_INFO, "group %s down event", curg->name); + if(event_script_check(curg->eventscript)) { + char **argv; + char **envp; + + argv = exec_queue_argv("%s %s %s %s %s %s %d %d %d %d %d %d %d %d %s %s %d", + curg->eventscript, + get_status_str(curg->status), + curg->name, + "", + "", + curg->warn_email ? curg->warn_email : "", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "", + get_status_str(prevstatus), + current_time.tv_sec); + envp = exec_queue_envp(); + + if(curg->queue && *curg->queue) { + exec_queue_add(curg->queue, argv, envp); + } else { + forkexec(argv, envp); + + exec_queue_argv_free(argv); + exec_queue_envp_free(envp); + } + } + if(event_script_check(curg->notifyscript)) { + char **argv; + char **envp; + + argv = exec_queue_argv("%s %s %s %s %s %s %d %d %d %d %d %d %d %d %s %s %d", + curg->notifyscript, + get_status_str(curg->status), + curg->name, + "", + "", + curg->warn_email ? curg->warn_email : "", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "", + get_status_str(prevstatus), + current_time.tv_sec); + envp = exec_queue_envp(); + + forkexec(argv, envp); + + exec_queue_argv_free(argv); + exec_queue_envp_free(envp); + } + } + } + curg = curg->next; + } +} + +static int wait_for_replies(CONFIG **ctable) { + struct ip *ip; + int hlen = 0; + struct icmp *icp; + struct icmp6_hdr *icp6; + PING_DATA *pdp; + int this_count; + struct timeval sent_time = {0, 0}; + struct timeval current_time = {0, 0}; + long time_diff; + char buf[BUFSIZ]; + union { + struct sockaddr_in6 saddr6; + struct sockaddr_in saddr; + struct sockaddr_ll FROM; + } from_addr; + unsigned int slen; + int result; + TARGET *t; + int seq; + CONFIG *arp; + + slen = sizeof(from_addr); + result = ping_rcv(ctable[0], buf, BUFSIZ, (struct sockaddr_in6 *)&from_addr, &slen, DEFAULT_SELECT_WAIT, &arp); + + if(result <= 0) { + return(0); + } + + gettimeofday(¤t_time, NULL); + + if(arp) { + struct sockaddr_ll *FROM = &from_addr.FROM; + TARGET *t = arp->data; + struct arphdr *ah = (struct arphdr*)buf; + unsigned char *p = (unsigned char *)(ah+1); + struct in_addr src_ip, dst_ip; + int ind; + + /* Filter out wild packets */ + if(FROM->sll_pkttype != PACKET_HOST && + FROM->sll_pkttype != PACKET_BROADCAST && + FROM->sll_pkttype != PACKET_MULTICAST) + return(1); + + /* Only these types are recognised */ +#if 0 + if(ah->ar_op != htons(ARPOP_REQUEST) && + ah->ar_op != htons(ARPOP_REPLY)) + return(1); +#else + if(ah->ar_op != htons(ARPOP_REPLY)) + return(1); +#endif + + /* ARPHRD check and this darned FDDI hack here :-( */ + if(ah->ar_hrd != htons(FROM->sll_hatype) && + (FROM->sll_hatype != ARPHRD_FDDI || ah->ar_hrd != htons(ARPHRD_ETHER))) + return(1); + + /* Protocol must be IP. */ + if(ah->ar_pro != htons(ETH_P_IP)) + return(1); + if(ah->ar_pln != 4) + return(1); + if(ah->ar_hln != t->me.sll_halen) + return(1); + +#if defined(DEBUG) + for(ind = 0; ind < result; ind ++) { + fprintf(stderr, "%02x", (unsigned char)buf[ind]); + if(!((ind + 1) % 2)) fprintf(stderr, " "); + if(!((ind + 1) % 32)) fprintf(stderr, "\n"); + } + fprintf(stderr, "\n"); + for(ind = 0; ind < result; ind ++) { + fprintf(stderr, "%3u", (unsigned char)buf[ind]); + if(!((ind + 1) % 32)) fprintf(stderr, "\n"); + else fprintf(stderr, ","); + } + fprintf(stderr, "\n"); +#endif + + if(result < sizeof(*ah) + 2*(4 + ah->ar_hln)) + return(1); + + memcpy(&src_ip, p+ah->ar_hln, 4); + memcpy(&dst_ip, p+ah->ar_hln+4+ah->ar_hln, 4); + + if(src_ip.s_addr != t->dst.s_addr) + return(1); + if(t->src.s_addr != dst_ip.s_addr) + return(1); + if(memcmp(p+ah->ar_hln+4, &t->me.sll_addr, ah->ar_hln)) + return(1); + + /* update packet log here */ + /* there are no sequence numbers in arp replies so just mark seq - 1 replied */ + ind = ((t->seq - 1) >= 0 ? (t->seq - 1) : (FOLLOWED_PKTS + (t->seq - 1))) % FOLLOWED_PKTS; + t->sentpkts[ind].flags.replied = 1; + t->sentpkts[ind].flags.waiting = 0; + t->sentpkts[ind].replied_time = current_time; + t->sentpkts[ind].rtt = timeval_diff(¤t_time, &t->sentpkts[ind].sent_time); + + return(1); + } + +#if defined(DEBUG) + if(cfg.debug >= 9) { + char sbuf[INET6_ADDRSTRLEN]; + + syslog(LOG_INFO, "not arp: family = %d, AF_INET = %d, AF_INET6 = %d, inet_ntop addr = %s, inet_ntoa addr = %s", from_addr.saddr6.sin6_family, AF_INET, AF_INET6, inet_ntop(from_addr.saddr6.sin6_family, &from_addr.saddr6.sin6_addr, sbuf, INET6_ADDRSTRLEN), inet_ntoa(from_addr.saddr.sin_addr)); + } +#endif + + switch(from_addr.saddr6.sin6_family) { + case AF_INET: +#if defined(DEBUG) + syslog(LOG_INFO, "%s: %s: AF_INET reply", __FILE__, __FUNCTION__); +#endif + ip = (struct ip *)buf; + hlen = ip->ip_hl << 2; + + icp = (struct icmp *)(buf + hlen); + + if(icp->icmp_type == ICMP_ECHO) { + return(1); + } + + if(icp->icmp_type == ICMP_ECHOREPLY) { + if(icp->icmp_id != get_ident()) { + /* fprintf(stderr, "icmp_id = %d funny, got reply from %s to something else ...\n", icp->icmp_id, inet_ntoa(saddr.sin_addr)); */ + return(1); + } + + if(result < sizeof(struct icmp) + sizeof(PING_DATA)) { + /* fprintf(stderr, "too short ping reply\n"); */ + return(1); + } + + pdp = (PING_DATA *)(buf + hlen + sizeof(struct icmp)); + + this_count = pdp->ping_count; + sent_time = pdp->ping_ts; + time_diff = timeval_diff(¤t_time, &sent_time); + + if(pdp->id >= num_hosts) { +#if defined(DEBUG) + syslog(LOG_INFO, "out of range: pdp->id = %d >= num_hosts = %d from %s", pdp->id, num_hosts, inet_ntoa(from_addr.saddr.sin_addr)); + dump_pkt(buf, sizeof(struct ip) + sizeof(struct icmp) + sizeof(PING_DATA)); + set_dump(1); +#endif + return(1); + } + + t = ctable[pdp->id]->data; + + if(memcmp(&ip->ip_src, &t->dst, sizeof(struct in_addr) != 0)) { + return(1); + } + + seq = icp->icmp_seq % FOLLOWED_PKTS; + if(t->sentpkts[seq].seq == icp->icmp_seq) { + t->sentpkts[seq].flags.replied = 1; + t->sentpkts[seq].flags.waiting = 0; + t->sentpkts[seq].replied_time = current_time; + t->sentpkts[seq].rtt = timeval_diff(¤t_time, &t->sentpkts[seq].sent_time); + } + else + if(cfg.debug >= 9) syslog(LOG_INFO, "sentpkts seq != icmp_seq"); + + if(cfg.debug >= 9) syslog(LOG_INFO, "received seq = %d from %s, id = %d, num_sent = %d, target id = %u, time_diff = %ld", icp->icmp_seq, inet_ntoa(from_addr.saddr.sin_addr), icp->icmp_id, this_count, pdp->id, time_diff); + + return(1); + + } else { + struct icmpmsg *msg; + + msg = stricmp(icp->icmp_type, icp->icmp_code); + + if(cfg.debug >= 9) syslog(LOG_INFO, "got odd reply from %s, icmp_type = %d %s, icmp_code = %d %s", inet_ntoa(from_addr.saddr.sin_addr), icp->icmp_type, msg->type_msg, icp->icmp_code, msg->code_msg); + + return(1); + } + break; + case AF_INET6: +#if defined(DEBUG) + syslog(LOG_INFO, "%s: %s: AF_INET6 reply", __FILE__, __FUNCTION__); +#endif + icp6 = (struct icmp6_hdr *)buf; + + if(icp6->icmp6_type == ICMP6_ECHO_REQUEST) { + return(1); + } + + if (icp6->icmp6_type == ICMP6_ECHO_REPLY) { /* v6 reply */ + char sbuf[INET6_ADDRSTRLEN]; +#if defined(DEBUG) + dump_pkt(buf, sizeof(struct icmp6_hdr) + sizeof(PING_DATA)); +#endif + /* syslog(LOG_INFO, "sizeof struct icmp6_hdr = %ld\n", sizeof(struct icmp6_hdr)); */ + + if(icp6->icmp6_id != get_ident()) { + return(1); + } + + if(result < sizeof(struct icmp6_hdr) + sizeof(PING_DATA)) { + return(1); + } + + /* pdp = (PING_DATA *)(buf + hlen + sizeof(struct icmp6_hdr)); */ + /* pdp = (PING_DATA *)(buf + sizeof(struct icmp6_hdr)); */ + pdp = (PING_DATA *)(buf + sizeof(struct icmp6_hdr)); + + this_count = pdp->ping_count; + sent_time = pdp->ping_ts; + time_diff = timeval_diff(¤t_time, &sent_time); + +#if defined(DEBUG) + syslog(LOG_INFO, "%s: %s: this_count = %d, sent_time = %ld,%ld, pdp->id = %d", __FILE__, __FUNCTION__, this_count, sent_time.tv_sec, sent_time.tv_usec, pdp->id); +#endif + + if(pdp->id >= num_hosts) { +#if defined(DEBUG) + syslog(LOG_INFO, "out of range: pdp->id = %d >= num_hosts = %d from %s", pdp->id, num_hosts, inet_ntop(AF_INET6, &from_addr.saddr6.sin6_addr, sbuf, INET6_ADDRSTRLEN)); + dump_pkt(buf, sizeof(struct icmp6_hdr) + sizeof(PING_DATA)); + set_dump(1); +#endif + return(1); + } + + t = ctable[pdp->id]->data; + + if(memcmp(&from_addr.saddr6.sin6_addr, &t->dst6, sizeof(struct in6_addr)) != 0) { + return(1); + } + + seq = ntohs(icp6->icmp6_seq) % FOLLOWED_PKTS; + if(t->sentpkts[seq].seq == ntohs(icp6->icmp6_seq)) { + t->sentpkts[seq].flags.replied = 1; + t->sentpkts[seq].flags.waiting = 0; + t->sentpkts[seq].replied_time = current_time; + t->sentpkts[seq].rtt = timeval_diff(¤t_time, &t->sentpkts[seq].sent_time); + } + else + if (cfg.debug >= 9) syslog(LOG_INFO, "sentpkts seq != icmp_seq"); + + if(cfg.debug >= 9) syslog(LOG_INFO, "received seq = %d from %s, id = %d, num_sent = %d, target id = %u, time_diff = %ld", ntohs(icp6->icmp6_seq), inet_ntop(AF_INET6, &from_addr.saddr6.sin6_addr, sbuf, INET6_ADDRSTRLEN), icp6->icmp6_id, this_count, pdp->id, time_diff); + + return(1); + } else { + char sbuf[INET6_ADDRSTRLEN]; + struct icmp6msg *msg; + + msg = stricmp6(icp6->icmp6_type, icp6->icmp6_code); + + if(cfg.debug >= 9) syslog(LOG_INFO, "got odd reply from %s, icmp_type = %d %s, icmp_code = %d %s", inet_ntop(from_addr.saddr6.sin6_family, &from_addr.saddr6.sin6_addr, sbuf, INET6_ADDRSTRLEN), icp6->icmp6_type, msg->type_msg, icp6->icmp6_code, msg->code_msg); + + return(1); + } + break; + default: + syslog(LOG_INFO, "%s: %s: unknown family reply", __FILE__, __FUNCTION__); + break; + } + + return(1); +} + +static int ping_rcv(CONFIG *first, char *buf, int len, struct sockaddr_in6 *saddr, unsigned int *slen, long usec, CONFIG **arp) { + int nfound, n; + fd_set readset; + struct timeval to = {0, 0}; + int max; + CONFIG *cur; + TARGET *t; + int cnt_targets = 0; + + FD_ZERO(&readset); + max = 0; + + for(cur = first; cur; cur = cur->next) { + t = cur->data; + if(t->sock == -1) continue; + if(t->sock > max) max = t->sock; + FD_SET(t->sock, &readset); + cnt_targets++; + } + + /* no point in calling select if we didn't find any open sockets. so sleep and return ... */ + if(cnt_targets == 0) { + sleep(1); + return(0); + } + + to.tv_sec = usec / 1000000; + to.tv_usec = (usec - (to.tv_sec * 1000000)); + +#if defined(DEBUG) + printf("to.tv_sec = %ld, to.tv_usec = %ld\n", to.tv_sec, to.tv_usec); +#endif + + nfound = select(max + 1, &readset, NULL, NULL, &to); + + if(nfound < 0) { + if(errno != EINTR) syslog(LOG_INFO, "select failed \"%s\"", strerror(errno)); + return(0); + } + + if(nfound == 0) return(-1); + + for(cur = first; cur; cur = cur->next) { + t = cur->data; + if(t->sock == -1) continue; + if(FD_ISSET(t->sock, &readset)) { + if(!cur->check_arp) { + *arp = (CONFIG *)NULL; + } else { + *arp = cur; + } + + n = recvfrom(t->sock, buf, len, 0, (struct sockaddr *)saddr, slen); + + if(n < 0) { + syslog(LOG_INFO, "recvfrom failed with %s \"%s\"\n", cur->name, strerror(errno)); + close(t->sock); + t->sock = -1; + return(0); + } + + return(n); + } + } + return(0); +} + +static int ping_send(CONFIG *cur) { + char buf[BUFSIZ]; + struct icmp *icp; + PING_DATA *pdp; + TARGET *t; + int n; + int ping_pkt_size; + + t = cur->data; + + gettimeofday(&t->last_send_time, NULL); + + if(cur->check_arp) { + int err; + unsigned char buf[256]; + struct arphdr *ah = (struct arphdr*)buf; + unsigned char *p = (unsigned char *)(ah+1); + + if(cur->dstinfo->ai_family == AF_INET6) { + syslog(LOG_ERR, "%s: %s: ipv6 arping not supported", __FILE__, __FUNCTION__); + return(-1); + } + + ah->ar_hrd = htons(t->me.sll_hatype); + if(ah->ar_hrd == htons(ARPHRD_FDDI)) + ah->ar_hrd = htons(ARPHRD_ETHER); + ah->ar_pro = htons(ETH_P_IP); + ah->ar_hln = t->me.sll_halen; + ah->ar_pln = 4; + ah->ar_op = htons(ARPOP_REQUEST); + + memcpy(p, &t->me.sll_addr, ah->ar_hln); + p += t->me.sll_halen; + + memcpy(p, &t->src, 4); + p += 4; + + memcpy(p, &t->he.sll_addr, ah->ar_hln); + p += ah->ar_hln; + + memcpy(p, &t->dst, 4); + p += 4; + +#if defined(DEBUG) + { + int ind; + for(ind = 0; ind < p - buf; ind ++) { + fprintf(stderr, "%02x", (unsigned char)buf[ind]); + if(!((ind + 1) % 2)) fprintf(stderr, " "); + if(!((ind + 1) % 32)) fprintf(stderr, "\n"); + } + fprintf(stderr, "\n"); + for(ind = 0; ind < p - buf; ind ++) { + fprintf(stderr, "%3u", (unsigned char)buf[ind]); + if(!((ind + 1) % 32)) fprintf(stderr, "\n"); + else fprintf(stderr, ","); + } + fprintf(stderr, "\n"); + } +#endif + + if(t->sock != -1) { + err = sendto(t->sock, buf, p - buf, 0, (struct sockaddr*)&t->he, sizeof(t->he)); + if(err < 0) { + if(cfg.debug >= 9) syslog(LOG_ERR, "arping sendto failed to %s on %s reason \"%s\"", cur->name, cur->device, strerror(errno)); + close(t->sock); + t->sock = -1; + } + } else { + if(cfg.debug >= 9) syslog(LOG_INFO, "arping sendto socket not open for %s", cur->name); + err = -1; + } + + { /* we don't care what the error was just advance with seq */ + int seq; + + seq = t->seq % FOLLOWED_PKTS; + t->sentpkts[seq].seq = t->seq; + t->sentpkts[seq].sent_time = t->last_send_time; + t->sentpkts[seq].flags.replied = 0; + t->sentpkts[seq].flags.timeout = 0; + t->sentpkts[seq].flags.waiting = 1; + if(t->sentpkts[seq].flags.used == 0) t->used++; + t->sentpkts[seq].flags.used = 1; + t->sentpkts[seq].flags.error = (err == -1) ? 1 : 0; + + t->seq = (t->seq + 1) % SEQ_LIMITER; /* limit seq so that consecutive missing and received pkt counting doesn't get confused when seq "overflows" */ + t->num_sent++; + + } + if(err == (p - buf)) { + return(0); + } + return(err); + } + + if(cur->dstinfo->ai_family == AF_INET6) { + struct icmp6_hdr *icp6; + + ping_pkt_size = sizeof(struct icmp6_hdr) + sizeof(PING_DATA); + + memset(buf, 0, ping_pkt_size); + + icp6 = (struct icmp6_hdr *)buf; + + icp6->icmp6_type = ICMP6_ECHO_REQUEST; + icp6->icmp6_code = 0; + icp6->icmp6_seq = htons(t->seq); /* I saw a tcpdump suggesting that there is something wrong with seq thus htons() */ + icp6->icmp6_id = get_ident(); + + pdp = (PING_DATA *)(buf + sizeof(struct icmp6_hdr)); + pdp->ping_count = t->num_sent; + pdp->ping_ts = t->last_send_time; + pdp->id = t->id; + + icp6->icmp6_cksum = 0; /* the ipv6 stack calculates the checksum for us */ + + if(t->sock != -1) { + if(cfg.debug >= 9) syslog(LOG_INFO, "cmsglen = %d", t->cmsglen); + + if(t->cmsglen == 0) { + n = sendto(t->sock, buf, ping_pkt_size, 0, (struct sockaddr *)&t->dst_addr6, sizeof(t->dst_addr6)); + if(n < 0) { + if(errno == ENODEV) { + if(cfg.debug >= 9) syslog(LOG_ERR, "connection %s no such device %s \"%s\"", cur->name, cur->device, strerror(errno)); + } else + if (cfg.debug >= 9) syslog(LOG_ERR, "ping6 sendto failed to %s on %s reason \"%s\"", cur->name, cur->device, strerror(errno)); + + if(t->sock != -1) { + close(t->sock); + t->sock = -1; + } + } + } else { + struct msghdr mhdr; + struct iovec iov; + int confirm = 0; + + iov.iov_len = ping_pkt_size; + iov.iov_base = buf; + + mhdr.msg_name = &t->dst_addr6; + mhdr.msg_namelen = sizeof(struct sockaddr_in6); + mhdr.msg_iov = &iov; + mhdr.msg_iovlen = 1; + mhdr.msg_control = t->cmsgbuf; + mhdr.msg_controllen = t->cmsglen; + + n = sendmsg(t->sock, &mhdr, confirm); + if(cfg.debug >= 9 && n < 0) syslog(LOG_INFO, "sendmsg failed for %s %s", cur->name, strerror(errno)); + if(n < 0) { + close(t->sock); + t->sock = -1; + } + } + } else { + if(cfg.debug >= 9) syslog(LOG_INFO, "ping sendto socket not open for %s", cur->name); + n = -1; + } + { + /* we don't care what the error was just advance with seq */ + int seq; + + seq = t->seq % FOLLOWED_PKTS; +#if defined(DEBUG) + fprintf(stderr, "ping_send seq = %ld to %s, num_sent = %ld, %ld, pkt_size = %d\n", t->seq, inet_ntoa(t->saddr.sin_addr), t->num_sent, pdp->ping_count, ping_pkt_size); +#endif + t->sentpkts[seq].seq = t->seq; + t->sentpkts[seq].sent_time = t->last_send_time; + t->sentpkts[seq].flags.replied = 0; + t->sentpkts[seq].flags.timeout = 0; + t->sentpkts[seq].flags.waiting = 1; + if(t->sentpkts[seq].flags.used == 0) t->used++; + t->sentpkts[seq].flags.used = 1; + t->sentpkts[seq].flags.error = (n < 1) ? 1 : 0; + + t->seq = (t->seq + 1) % SEQ_LIMITER; + /* limit seq so that consecutive missing and received pkt counting doesn't get confused when seq "overflows" */ + t->num_sent++; + } + if(n == ping_pkt_size) { + return(0); + } + + return(n); + } + + /* send a ping packet */ + ping_pkt_size = sizeof(struct icmp) + sizeof(PING_DATA); + + memset(buf, 0, ping_pkt_size); + + icp = (struct icmp *)buf; + + icp->icmp_type = ICMP_ECHO; + icp->icmp_code = 0; + icp->icmp_cksum = 0; + icp->icmp_seq = t->seq; + icp->icmp_id = get_ident(); + + pdp = (PING_DATA *)(buf + sizeof(struct icmp)); + pdp->ping_count = t->num_sent; + pdp->ping_ts = t->last_send_time; + pdp->id = t->id; + + icp->icmp_cksum = in_cksum((u_short *)icp, ping_pkt_size); + + if(t->sock != -1) { + n = sendto(t->sock, buf, ping_pkt_size, 0, (struct sockaddr *)&t->dst_addr, sizeof(struct sockaddr)); + + if(n < 0) { + if(errno == ENODEV) { + if(cfg.debug >= 9) syslog(LOG_ERR, "connection %s no such device %s \"%s\"", cur->name, cur->device, strerror(errno)); + /* exit(2); */ /* commented out. handle this situation like the packet had been sent. see below. */ + } + else + if(cfg.debug >= 9) syslog(LOG_ERR, "ping sendto failed to %s on %s reason \"%s\"", cur->name, cur->device, strerror(errno)); + + if(t->sock != -1) { + close(t->sock); + t->sock = -1; + } + } + } else { + if(cfg.debug >= 9) syslog(LOG_INFO, "ping sendto socket not open for %s", cur->name); + n = -1; + } + + { /* we don't care what the error was just advance with seq */ + int seq; + + seq = t->seq % FOLLOWED_PKTS; +#if defined(DEBUG) + fprintf(stderr, "ping_send seq = %ld to %s, num_sent = %ld, %ld, pkt_size = %d\n", t->seq, inet_ntoa(t->saddr.sin_addr), t->num_sent, pdp->ping_count, ping_pkt_size); +#endif + t->sentpkts[seq].seq = t->seq; + t->sentpkts[seq].sent_time = t->last_send_time; + t->sentpkts[seq].flags.replied = 0; + t->sentpkts[seq].flags.timeout = 0; + t->sentpkts[seq].flags.waiting = 1; + if(t->sentpkts[seq].flags.used == 0) t->used++; + t->sentpkts[seq].flags.used = 1; + t->sentpkts[seq].flags.error = (n < 1) ? 1 : 0; + + t->seq = (t->seq + 1) % SEQ_LIMITER; /* limit seq so that consecutive missing and received pkt counting doesn't get confused when seq "overflows" */ + t->num_sent++; + } + + if(n == ping_pkt_size) { + return(0); + } + return(n); +} + +static int event_script_check(const char *path) +{ + struct stat statbuf; + + if(!path) { + if(cfg.debug >= 9) syslog(LOG_ERR, "NULL pointer event script"); + return(0); + } + + if(!*path) { + if(cfg.debug >= 9) syslog(LOG_ERR, "null string event script"); + return(0); + } + + /* check that the script is owner executable */ + if(stat(path, &statbuf) == -1) { + syslog(LOG_ERR, "failed to stat event script \"%s\" reason \"%s\"", path, strerror(errno)); + return(0); + } + + if((statbuf.st_mode & S_IXUSR) == 0) { + syslog(LOG_ERR, "event script \"%s\" is not executable by owner, please check permissions", path); + return(0); + } + + return(1); +} + +static void init_config_data(CONFIG *first, CONFIG *last, CONFIG ***ctable) +{ + int i; + CONFIG *cur; + TARGET *t = NULL; + + /* initialize config->data */ + for(cur = first, num_hosts = 0; cur; cur = cur->next, num_hosts++) { + u_int ipaddress; + + if((t = malloc(sizeof(TARGET))) == NULL) { + syslog(LOG_ERR, "main: initializing targets failed to malloc"); + exit(1); + } + memset(t, 0, sizeof(TARGET)); + + cur->data = t; + + /* protocol family independent init */ + t->seq = 0; + t->downseq = 0; + t->downseqreported = 0; + t->last_send_time.tv_sec = 0; + t->last_send_time.tv_usec = 0; + t->num_sent = 0; + t->timeout_max = 0; + t->consecutive_missing_max = 0; + t->used = 0; + + memset(t->cmsgbuf, 0, sizeof(t->cmsgbuf)); + t->cmsglen = 0; + + t->id = num_hosts; + + /* get initial connection state assumption from config */ + t->status = cur->status; + + t->sock = -1; + + if(cur->dstinfo->ai_family == AF_INET6) { + /* ipv6 init */ + if(cur->srcinfo) { + if(inet_pton(AF_INET6, cur->sourceip, &t->src6) != 1) { + syslog(LOG_ERR, "%s: %s: src6 inet_pton failed for %s", __FILE__, __FUNCTION__, cur->name); + } + + t->src_addr6.sin6_family = cur->srcinfo->ai_family; + if(inet_pton(AF_INET6, cur->sourceip, &t->src_addr6.sin6_addr) != 1) { + syslog(LOG_ERR, "%s: %s: src6 inet_pton failed for %s", __FILE__, __FUNCTION__, cur->name); + } + } + + if(inet_pton(AF_INET6, cur->checkip, &t->dst6) != 1) { + syslog(LOG_ERR, "%s: %s: dst6 inet_pton failed for %s", __FILE__, __FUNCTION__, cur->name); + } + + t->dst_addr6.sin6_family = cur->dstinfo->ai_family; + if(inet_pton(AF_INET6, cur->checkip, &t->dst_addr6.sin6_addr) != 1) { + syslog(LOG_ERR, "%s: %s: dst6 inet_pton failed for %s", __FILE__, __FUNCTION__, cur->name); + } + } else { + /* ipv4 init */ + ipaddress = inet_addr(cur->checkip); + t->dst_addr.sin_family = AF_INET; + t->dst_addr.sin_addr = *((struct in_addr *)&ipaddress); + t->dst = *((struct in_addr *)&ipaddress); + } + } + + if(((*ctable) = (CONFIG **)malloc(sizeof(CONFIG *) * num_hosts)) == NULL) { + syslog(LOG_ERR, "main: can't malloc for ctable"); + exit(1); + } + + /* create pointer table */ + for(cur = first, i = 0; cur; cur = cur->next, i++) { + (*ctable)[i] = cur; + } + +} + +static int open_arp_sock(CONFIG *cur) +{ + int ifindex = 0; + TARGET *t = (TARGET *)cur->data; + + if(t->sock != -1) return(0); + + if(cur->dstinfo->ai_family == AF_INET6) { + syslog(LOG_ERR, "%s: %s: protocol family is ipv6?", __FILE__, __FUNCTION__); + return(1); + } + + t->sock = socket(PF_PACKET, SOCK_DGRAM, 0); + if(t->sock < 0) { + syslog(LOG_ERR, "could not open socket for %s arp ping \"%s\"", cur->name, strerror(errno)); + t->sock = -1; + return(1); + } + if(fcntl(t->sock, F_SETFD, FD_CLOEXEC) == -1) { + syslog(LOG_ERR, "failed to set close on exec on socket %s reason \"%s\"", cur->name, strerror(errno)); + } + + if(cur->device && *cur->device) { + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, cur->device, IFNAMSIZ-1); + if(ioctl(t->sock, SIOCGIFINDEX, &ifr) < 0) { + syslog(LOG_ERR, "unknown iface \"%s\"", cur->device); + close(t->sock); + t->sock = -1; + return(2); + } + ifindex = ifr.ifr_ifindex; + + if(ioctl(t->sock, SIOCGIFFLAGS, (char*)&ifr)) { + syslog(LOG_ERR, "ioctl(SIOCGIFFLAGS) \"%s\"", strerror(errno)); + close(t->sock); + t->sock = -1; + return(2); + } + if(!(ifr.ifr_flags&IFF_UP)) { + syslog(LOG_ERR, "Interface \"%s\" is down", cur->device); + close(t->sock); + t->sock = -1; + return(2); + } + if(ifr.ifr_flags&(IFF_NOARP|IFF_LOOPBACK)) { + syslog(LOG_ERR, "Interface \"%s\" is not ARPable", cur->device); + close(t->sock); + t->sock = -1; + return(2); + } + } + + if(inet_aton(cur->checkip, &t->dst) != 1) { + struct hostent *hp; + hp = gethostbyname2(cur->checkip, AF_INET); + if(!hp) { + syslog(LOG_ERR, "unknown host %s\n", cur->checkip); + close(t->sock); + t->sock = -1; + return(2); + } + memcpy(&t->dst, hp->h_addr, 4); + } + + if(cur->sourceip && *cur->sourceip) + if(inet_aton(cur->sourceip, &t->src) != 1) { + syslog(LOG_ERR, "invalid source %s\n", cur->sourceip); + close(t->sock); + t->sock = -1; + return(2); + } + + if(probe_src_ip_addr(cur) != 0) { + close(t->sock); + t->sock = -1; + return(2); + } + + t->me.sll_family = AF_PACKET; + t->me.sll_ifindex = ifindex; + t->me.sll_protocol = htons(ETH_P_ARP); + if(bind(t->sock, (struct sockaddr*)&t->me, sizeof(t->me)) == -1) { + syslog(LOG_ERR, "bind \"%s\"", strerror(errno)); + close(t->sock); + t->sock = -1; + return(2); + } + + { + int alen = sizeof(t->me); + if(getsockname(t->sock, (struct sockaddr*)&t->me, (socklen_t*)&alen) == -1) { + syslog(LOG_ERR, "getsockname \"%s\"", strerror(errno)); + close(t->sock); + t->sock = -1; + return(2); + } + } + if(t->me.sll_halen == 0) { + syslog(LOG_ERR, "Interface \"%s\" is not ARPable (no ll address)", cur->device); + close(t->sock); + t->sock = -1; + return(2); + } + + t->he = t->me; + memset(t->he.sll_addr, -1, min(t->he.sll_halen, sizeof t->he.sll_addr)); + +#if 0 + printf("ARPING %s ", inet_ntoa(t->dst)); + printf("from %s %s\n", inet_ntoa(t->src), cur->device ? : ""); +#endif + + if(!t->src.s_addr) { + syslog(LOG_ERR, "no source address for %s", cur->name); + close(t->sock); + t->sock = -1; + return(2); + } + if(cur->ttl) { + int ittl = cur->ttl; + if(setsockopt(t->sock, IPPROTO_IP, IP_MULTICAST_TTL, + &cur->ttl, 1) == -1) { + syslog(LOG_ERR, "can't set multicast time-to-live \"%s\"", strerror(errno)); + close(t->sock); + t->sock = -1; + return(2); + } + if(setsockopt(t->sock, IPPROTO_IP, IP_TTL, + &ittl, sizeof(ittl)) == -1) { + syslog(LOG_ERR, "can't set unicast time-to-live \"%s\"", strerror(errno)); + close(t->sock); + t->sock = -1; + return(2); + } + } + + return(0); +} + +static int open_icmp_sock(CONFIG *cur) +{ + TARGET *t = (TARGET *)cur->data; + struct protoent *proto; + int pf = cur->dstinfo->ai_family; + + if(t->sock != -1) return(0); + + if(pf == AF_INET6) { + if((proto = getprotobyname("ipv6-icmp")) == NULL) { + syslog(LOG_ERR, "no ipv6-icmp proto found"); + return(1); + } + } else { + if((proto = getprotobyname("icmp")) == NULL) { + syslog(LOG_ERR, "no icmp proto found"); + return(1); + } + } + + t->sock = socket(pf, SOCK_RAW, proto->p_proto); + + if(t->sock < 0) { + syslog(LOG_ERR, "could not open socket for ping target \"%s\" reason \"%s\"\n", cur->name, strerror(errno)); + t->sock = -1; + return(1); + } + if(fcntl(t->sock, F_SETFD, FD_CLOEXEC) == -1) { + syslog(LOG_ERR, "failed to set close on exec on socket %s reason \"%s\"", cur->name, strerror(errno)); + } + + if(pf == AF_INET6) { + int opton = 1; + +#ifdef IPV6_RECVHOPOPTS + if(setsockopt(t->sock, IPPROTO_IPV6, IPV6_RECVHOPOPTS, &opton, + sizeof(opton))) { + syslog(LOG_ERR, "setsockopt(IPV6_RECVHOPOPTS)"); + close(t->sock); + t->sock = -1; + return(2); + } +#else /* old adv. API */ + if(setsockopt(t->sock, IPPROTO_IPV6, IPV6_HOPOPTS, &opton, + sizeof(opton))) { + syslog(LOG_ERR, "setsockopt(IPV6_HOPOPTS)"); + close(t->sock); + t->sock = -1; + return(s); + } +#endif +#ifdef IPV6_RECVDSTOPTS + if(setsockopt(t->sock, IPPROTO_IPV6, IPV6_RECVDSTOPTS, &opton, + sizeof(opton))) { + syslog(LOG_ERR, "setsockopt(IPV6_RECVDSTOPTS)"); + close(t->sock); + t->sock = -1; + return(2); + } +#else /* old adv. API */ + if(setsockopt(t->sock, IPPROTO_IPV6, IPV6_DSTOPTS, &opton, + sizeof(opton))) { + syslog(LOG_ERR, "setsockopt(IPV6_DSTOPTS)"); + close(t->sock); + t->sock = -1; + return(2); + } +#endif +#ifdef IPV6_RECVRTHDRDSTOPTS + if(setsockopt(t->sock, IPPROTO_IPV6, IPV6_RECVRTHDRDSTOPTS, &opton, + sizeof(opton))) { + syslog(LOG_ERR, "setsockopt(IPV6_RECVRTHDRDSTOPTS)"); + close(t->sock); + t->sock = -1; + return(2); + } +#endif +#ifdef IPV6_RECVRTHDR + if(setsockopt(t->sock, IPPROTO_IPV6, IPV6_RECVRTHDR, &opton, + sizeof(opton))) { + syslog(LOG_ERR, "setsockopt(IPV6_RECVRTHDR)"); + close(t->sock); + t->sock = -1; + return(2); + } +#else /* old adv. API */ + if(setsockopt(t->sock, IPPROTO_IPV6, IPV6_RTHDR, &opton, + sizeof(opton))) { + syslog(LOG_ERR, "setsockopt(IPV6_RTHDR)"); + close(t->sock); + t->sock = -1; + return(2); + } +#endif +#ifndef USE_SIN6_SCOPE_ID +#ifdef IPV6_RECVPKTINFO + if(setsockopt(t->sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &opton, + sizeof(opton))) { + syslog(LOG_ERR, "setsockopt(IPV6_RECVPKTINFO)"); + close(t->sock); + t->sock = -1; + return(2); + } +#else /* old adv. API */ + if(setsockopt(t->sock, IPPROTO_IPV6, IPV6_PKTINFO, &opton, + sizeof(opton))) { + syslog(LOG_ERR, "setsockopt(IPV6_PKTINFO)"); + close(t->sock); + t->sock = -1; + return(2); + } +#endif +#endif /* USE_SIN6_SCOPE_ID */ +#ifdef IPV6_RECVHOPLIMIT + if(setsockopt(t->sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &opton, + sizeof(opton))) { + syslog(LOG_ERR, "setsockopt(IPV6_RECVHOPLIMIT)"); + close(t->sock); + t->sock = -1; + return(2); + } +#else /* old adv. API */ + if(setsockopt(t->sock, IPPROTO_IPV6, IPV6_HOPLIMIT, &opton, + sizeof(opton))) { + syslog(LOG_ERR, "setsockopt(IPV6_HOPLIMIT)"); + close(t->sock); + t->sock = -1; + return(2); + } +#endif +#ifdef IPV6_CHECKSUM +#ifndef SOL_RAW +#define SOL_RAW IPPROTO_IPV6 +#endif + opton = 2; + + if(setsockopt(t->sock, SOL_RAW, IPV6_CHECKSUM, &opton, + sizeof(opton))) { + syslog(LOG_ERR, "setsockopt(SOL_RAW,IPV6_CHECKSUM)"); + close(t->sock); + t->sock = -1; + return(2); + } +#endif + } + + if(pf == AF_INET6) { + int hold = 1; + + ICMP6_FILTER_SETBLOCKALL(&t->filter); + + if (setsockopt(t->sock, SOL_IPV6, IPV6_RECVERR, (char *)&hold, sizeof(hold))) { + syslog(LOG_INFO, "WARNING: your kernel is veeery old. No problems."); + + ICMP6_FILTER_SETPASS(ICMP6_DST_UNREACH, &t->filter); + ICMP6_FILTER_SETPASS(ICMP6_PACKET_TOO_BIG, &t->filter); + ICMP6_FILTER_SETPASS(ICMP6_TIME_EXCEEDED, &t->filter); + ICMP6_FILTER_SETPASS(ICMP6_PARAM_PROB, &t->filter); + } + + ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &t->filter); + + if(setsockopt(t->sock, IPPROTO_ICMPV6, ICMP6_FILTER, &t->filter, sizeof(struct icmp6_filter)) < 0) { + syslog(LOG_ERR, "setsockopt(ICMP6_FILTER)"); + return(2); + } + } + + if(cur->ttl) { + if(pf == AF_INET6) { + if(setsockopt(t->sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &cur->ttl, sizeof(cur->ttl)) == -1) { + syslog(LOG_ERR, "can't set multicast hop limit \"%s\"", strerror(errno)); + close(t->sock); + t->sock = -1; + return(2); + } + if(setsockopt(t->sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, + &cur->ttl, sizeof(cur->ttl)) == -1) { + syslog(LOG_ERR, "can't set unicast hop limit \"%s\"", strerror(errno)); + close(t->sock); + t->sock = -1; + return(2); + } + } else if(pf == AF_INET) { /* AF_INET */ + int ittl = cur->ttl; + if(setsockopt(t->sock, IPPROTO_IP, IP_MULTICAST_TTL, + &cur->ttl, 1) == -1) { + syslog(LOG_ERR, "can't set multicast time-to-live \"%s\"", strerror(errno)); + close(t->sock); + t->sock = -1; + return(2); + } + if(setsockopt(t->sock, IPPROTO_IP, IP_TTL, + &ittl, sizeof(ittl)) == -1) { + syslog(LOG_ERR, "can't set unicast time-to-live \"%s\"", strerror(errno)); + close(t->sock); + t->sock = -1; + return(2); + } + } + } + + if(pf == AF_INET && cur->device && *cur->device) { + if(setsockopt(t->sock, SOL_SOCKET, SO_BINDTODEVICE, cur->device, strlen(cur->device) + 1) == -1) { + syslog(LOG_INFO, "failed to bind to ping interface device \"%s\", \"%s\"", cur->device, strerror(errno)); + close(t->sock); + t->sock = -1; + return(2); + } + } + +#if defined(DEBUG) + if(cfg.debug >= 9) syslog(LOG_INFO, "%s: %s: probing for src ip for %s", __FILE__, __FUNCTION__, cur->name); +#endif + if(probe_src_ip_addr(cur) != 0) { + close(t->sock); + t->sock = -1; + return(2); + } +#if defined(DEBUG) + if(cfg.debug >= 9) syslog(LOG_INFO, "%s: %s: probing for src ip for %s done", __FILE__, __FUNCTION__, cur->name); +#endif + if(cur->sourceip && *cur->sourceip) { + if(cur->srcinfo->ai_family == AF_INET) { + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(cur->sourceip); + + if(bind(t->sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) { + syslog(LOG_ERR, "ping can't bind \"%s\"", strerror(errno)); + return(1); + } + } else { + struct sockaddr_in6 addr; +#if defined(DEBUG) + if(cfg.debug >= 9) syslog(LOG_INFO, "%s: %s: setting v6 src addr", __FILE__, __FUNCTION__); +#endif + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + if(inet_pton(AF_INET6, cur->sourceip, &addr.sin6_addr) != 1) { + syslog(LOG_ERR, "ping6 failed to convert connection %s address %s", cur->name, cur->sourceip); + return(1); + } + if(bind(t->sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) { + syslog(LOG_ERR, "ping6 can't bind %s to %s, \"%s\"", cur->name, cur->sourceip, strerror(errno)); + return(1); + } +#if defined(DEBUG) + if(cfg.debug >= 9) syslog(LOG_INFO, "%s: %s: setting v6 src addr done", __FILE__, __FUNCTION__); +#endif + } + } + + if(pf == AF_INET6 && cur->device && *cur->device) { + struct ifreq ifr; + struct cmsghdr *cmsg; + struct in6_pktinfo *ipi; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, cur->device, IFNAMSIZ-1); + if(ioctl(t->sock, SIOCGIFINDEX, &ifr) < 0) { + syslog(LOG_ERR, "ping6 unknown iface %s", cur->device); + return(2); + } + + memset(&t->cmsgbuf, 0, sizeof(t->cmsgbuf)); + t->cmsglen = 0; + + cmsg = (struct cmsghdr *)t->cmsgbuf; + t->cmsglen += CMSG_SPACE(sizeof(*ipi)); + cmsg->cmsg_len = CMSG_LEN(sizeof(*ipi)); + cmsg->cmsg_level = SOL_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + + ipi = (struct in6_pktinfo *)CMSG_DATA(cmsg); + memset(ipi, 0, sizeof(*ipi)); + ipi->ipi6_ifindex = ifr.ifr_ifindex; + } + + return(0); +} + +static int probe_src_ip_addr(CONFIG *cur) +{ /* probe for src ip address */ + TARGET *t = (TARGET *)cur->data; + int probe_fd; + int pf = cur->dstinfo->ai_family; + + probe_fd = socket(pf, SOCK_DGRAM, 0); + + if(probe_fd < 0) { + syslog(LOG_ERR, "ping probe socket for %s failed \"%s\"", cur->name, strerror(errno)); + return(2); + } + if(fcntl(t->sock, F_SETFD, FD_CLOEXEC) == -1) { + syslog(LOG_ERR, "ping probe failed to set close on exec on probe socket for %s reason \"%s\"", cur->name, strerror(errno)); + } + + if(cur->device && *cur->device) { + if(setsockopt(probe_fd, SOL_SOCKET, SO_BINDTODEVICE, cur->device, strlen(cur->device) + 1) == -1) + syslog(LOG_INFO, "WARNING: ping probe interface \"%s\" is ignored for %s reason \"%s\"", cur->device, cur->name, strerror(errno)); + } + + if(pf == AF_INET) { + struct sockaddr_in saddr; + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + if(t->src.s_addr) { + saddr.sin_addr = t->src; + if(bind(probe_fd, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) { + syslog(LOG_ERR, "ping probe bind failed for %s \"%s\"", cur->name, strerror(errno)); + close(probe_fd); + /* earlier probed src addr is not usable, wipe it */ + memset(&t->src, 0, sizeof(t->src)); + return(2); + } + } + else { + int on = 1; + int alen = sizeof(saddr); + + saddr.sin_port = htons(1025); + saddr.sin_addr = t->dst; + + if(setsockopt(probe_fd, SOL_SOCKET, SO_DONTROUTE, (char*)&on, sizeof(on)) == -1) + syslog(LOG_INFO, "WARNING: ping probe setsockopt(SO_DONTROUTE) \"%s\"", strerror(errno)); + if(connect(probe_fd, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) { + syslog(LOG_ERR, "ping probe connect for %s failed \"%s\"", cur->name, strerror(errno)); + close(probe_fd); + return(2); + } + if(getsockname(probe_fd, (struct sockaddr*)&saddr, (socklen_t*)&alen) == -1) { + syslog(LOG_ERR, "ping probe getsockname for %s failed \"%s\"", cur->name, strerror(errno)); + close(probe_fd); + return(2); + } + t->src = saddr.sin_addr; + } + } else if (pf == AF_INET6) { /* not AF_INET */ + struct sockaddr_in6 saddr; + unsigned char nulladdr[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + memset(&saddr, 0, sizeof(saddr)); + saddr.sin6_family = AF_INET6; + if(memcmp(&t->src6, nulladdr, sizeof(t->src6)) != 0) { /* is not null addr */ + memcpy(&saddr.sin6_addr, &t->src6, sizeof(t->src6)); + if(bind(probe_fd, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) { + syslog(LOG_ERR, "ping6 probe bind failed for %s \"%s\"", cur->name,strerror(errno)); + close(probe_fd); + /* earlier probed src addr is not usable, wipe it */ + memset(&t->src6, 0, sizeof(t->src6)); + return(2); + } + } else { /* is null addr */ + socklen_t alen = sizeof(saddr); + + saddr.sin6_port = htons(1025); + saddr.sin6_family = cur->dstinfo->ai_family; + memcpy(&saddr.sin6_addr, &t->dst6, sizeof(t->dst6)); +#if 0 + if(setsockopt(probe_fd, SOL_SOCKET, SO_DONTROUTE, (char *)&on, sizeof(on)) == -1) + syslog(LOG_INFO, "WARNING: ping6 probe setsockopt(SO_DONTROUTE) for %s \"%s\"", cur->name, strerror(errno)); +#endif + if(connect(probe_fd, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) { + syslog(LOG_ERR, "ping6 probe connect for %s failed \"%s\"", cur->name, strerror(errno)); + close(probe_fd); + return(2); + } + if(getsockname(probe_fd, (struct sockaddr *)&saddr, &alen) == -1) { + syslog(LOG_ERR, "ping6 probe getsockname for %s failed \"%s\"", cur->name, strerror(errno)); + close(probe_fd); + return(2); + } + memcpy(&t->src6, &saddr.sin6_addr, sizeof(saddr.sin6_addr)); + } + } /* if AF_INET */ + + close(probe_fd); + + return(0); +} + +#if defined(DEBUG) +static void dump_pkt(const void *buf, size_t len) +{ + int i; + unsigned char *s; + char obuf[BUFSIZ]; + char *pad; + + memset(obuf, 0, BUFSIZ); + + s = (unsigned char *)buf; + pad = ""; + for(i = 0; i < len && i < BUFSIZ; i++) { + snprintf(obuf + strlen(obuf), BUFSIZ, "%s%02x", pad, s[i]); + pad = " "; + } + + syslog(LOG_INFO, "%s: %s: hexdump %s", __FILE__, __FUNCTION__, obuf); + + memset(obuf, 0, BUFSIZ); + + s = (unsigned char *)buf; + pad = ""; + for(i = 0; i < len && i < BUFSIZ; i++) { + snprintf(obuf + strlen(obuf), BUFSIZ, "%s%03d", pad, s[i]); + pad = " "; + } + + syslog(LOG_INFO, "%s: %s: decdump %s", __FILE__, __FUNCTION__, obuf); +} +#endif + +/* EOF */ diff --git a/lsm.conf b/lsm.conf new file mode 100644 index 0000000..14473e0 --- /dev/null +++ b/lsm.conf @@ -0,0 +1,56 @@ +# +# Copyright (C) 2009-2015 Mika Ilmaranta <ilmis@nullnet.fi> +# +# License: GPLv2 +# + +# +# Debug level: 0 .. 8 are normal, 9 gives lots of stuff and 100 doesn't +# bother to detach +# +#debug=10 +#debug=9 +#debug=8 + +# +# Defaults for the connection entries +# These are set in the code. You may override any values here. +# +#defaults { +# name=defaults +# checkip=127.0.0.1 +# eventscript= +# notifyscript=/usr/libexec/lsm/default_script +# max_packet_loss=15 +# max_successive_pkts_lost=7 +# min_packet_loss=5 +# min_successive_pkts_rcvd=10 +# interval_ms=1000 +# timeout_ms=1000 +# warn_email=root +# check_arp=0 +# sourceip= +# if using ping probes for monitoring only then defaults should +# not define a default device for packets to autodiscover their path +# to destination +# device=eth0 +# use system default ttl +# ttl=0 +# assume initial up state at lsm startup +# (1 = up, 0 = down, 2 = unknown (default)) +# status=1 +# how many packets have to go through before a connection is declared up +# (0 = disabled) +# startup_acceleration=0 +# send first 10 packets in a burst (0 = disabled) with interval 200ms +# startup_burst_pkts=10 +# startup_burst_interval=200 +#} + +# +# Some example connections are found in lsm.conf.sample +# + +-include /etc/lsm/local*.conf + +#EOF diff --git a/lsm.conf.sample b/lsm.conf.sample new file mode 100644 index 0000000..9a6f187 --- /dev/null +++ b/lsm.conf.sample @@ -0,0 +1,144 @@ +# +# (C) 2009 Mika Ilmaranta <ilmis@nullnet.fi> +# +# License: GPLv2 +# + +# +# Debug level: 0 .. 8 are normal, 9 gives lots of stuff and 100 doesn't +# bother to detach +# +#debug=10 +#debug=9 +debug=8 + +# +# Defaults for the connection entries +# +defaults { + name=defaults + checkip=127.0.0.1 + eventscript=/usr/libexec/lsm/default_script + max_packet_loss=15 + max_successive_pkts_lost=7 + min_packet_loss=5 + min_successive_pkts_rcvd=10 + interval_ms=1000 + timeout_ms=1000 + warn_email=root + check_arp=0 + sourceip= +# if using ping probes for monitoring only then defaults should +# not define a default device for packets to autodiscover their path +# to destination +# device=eth0 +# use system default ttl + ttl=0 +# assume initial up state at lsm startup (1 = up, 0 = down, 2 = unknown (default)) +# status=1 +} + +# +# Some example connections +# NOTE: don't use any white space in name ... +# +# connection { +# name=connection-1 +# checkip=127.108.68.69 +# eventscript=/usr/libexec/lsm/conn1 +# max_packet_loss=15 +# max_successive_pkts_lost=7 +# min_packet_loss=5 +# min_successive_pkts_rcvd=10 +# interval_ms=1000 +# timeout_ms=1000 +# warn_email=root1@some.tld +# check_arp=0 +# sourceip= +# device= +# ttl=64 +# } + +# connection { +# name=connection-2 +# checkip=127.108.68.65 +# eventscript=/usr/libexec/lsm/conn2 +# max_packet_loss=15 +# max_successive_pkts_lost=7 +# min_packet_loss=5 +# min_successive_pkts_rcvd=10 +# interval_ms=1000 +# timeout_ms=1000 +# warn_email=root2@some.tld +# check_arp=0 +# sourceip= +# device= +# ttl=64 +# } + +# connection { +# name=connection-3 +# checkip=127.108.68.68 +# eventscript=/usr/libexec/lsm/conn3 +# max_packet_loss=15 +# max_successive_pkts_lost=7 +# min_packet_loss=5 +# min_successive_pkts_rcvd=10 +# interval_ms=1000 +# timeout_ms=1000 +# warn_email=root3@some.tld +# check_arp=0 +# sourceip= +# device= +# ttl=64 +# } + +# connection { +# name=connection-4 +# checkip=127.108.68.75 +# } + +# +# Arping example +# +# connection { +# name=connection-5 +# checkip=127.108.68.71 +# check_arp=1 +# # if the remote end is not behind the defaults device +# # then you have to set this +# device=eth0 +# # setting source ip is not mandatory +# sourceip=127.108.68.68 +# # use system default ttl +# ttl=0 +# } + +# +# Group example +# +# connection { +# name=conn-a +# checkip=127.108.68.99 +# eventscript= +# } +# +# connection { +# name=conn-b +# checkip=127.108.68.100 +# eventscript= +# } +# +# group { +# name=conn-group-a +# eventscript=/usr/libexec/lsm/default_script +# warn_email=root@some.domain.tld +# # logic between member connetion statuses +# # logic=0 == or +# # logic=1 == and +# logic=0 +# member-connection=conn-a +# member-connection=conn-b +# } + +#EOF @@ -0,0 +1,72 @@ +/* + +(C) 2013 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#ifndef __LSM_H__ +#define __LSM_H__ + +#include <netinet/in.h> /* for struct sockaddr_in */ +#include <linux/if_arp.h> /* for struct sockadd_ll */ +#include <netinet/icmp6.h> /* for struct icmp6_filter */ + +#include "defs.h" + +typedef struct sentpkt { + unsigned short seq; + struct timeval sent_time; + struct timeval replied_time; + unsigned long rtt; + struct { + unsigned replied:1; + unsigned timeout:1; + unsigned waiting:1; + unsigned used:1; + unsigned error:1; + } flags; +} SENTPKT; + +typedef struct target { + unsigned short id; /* target id */ + unsigned short seq; + unsigned short downseq; + unsigned short downseqreported; + struct timeval down_timestamp; + struct sockaddr_in src_addr; + struct sockaddr_in dst_addr; + struct sockaddr_in6 src_addr6; + struct sockaddr_in6 dst_addr6; + struct sockaddr_ll me; /* arping only */ + struct sockaddr_ll he; /* arping only */ + struct in_addr src; + struct in_addr dst; + struct in6_addr src6; + struct in6_addr dst6; + unsigned long num_sent; + struct timeval last_send_time; + STATUS status; + int sock; + unsigned char cmsgbuf[4096]; + int cmsglen; + struct icmp6_filter filter; + SENTPKT sentpkts[FOLLOWED_PKTS]; + int timeout; + int timeout_max; + int replied; + int waiting; + int reply_late; + int used; + int consecutive_waiting; + int consecutive_missing; + int consecutive_missing_max; + int consecutive_rcvd; + long avg_rtt; + int status_change; +} TARGET; + +#endif + +/* EOF */ diff --git a/lsm.init b/lsm.init new file mode 100644 index 0000000..eb4f868 --- /dev/null +++ b/lsm.init @@ -0,0 +1,97 @@ +#!/bin/sh +# +# /etc/init.d/lsm +# +# This shellscript takes care of starting and stopping lsm. +# +# chkconfig: - 79 31 +# description: Lsm, Link Status Monitor +# +### BEGIN INIT INFO +# Provides: lsm +# Required-Start: $network $syslog +# Required-Stop: +# Default-Stop: 0 1 6 +# Short-Description: LSM - link status monitor +# Description: LSM is the link status monitor +# LSM can ping multiple targets and when up or down event happens +# it will execute user configured external script so it can be used +# as poor man's routing protocol. +### END INIT INFO + +# Source function library. +. /etc/init.d/functions + +# Source networking configuration. +. /etc/sysconfig/network + +# Check that networking is up. +[ ${NETWORKING} = "no" ] && exit 0 + +CONFIGFILE="/etc/lsm/lsm.conf" +PIDFILE="/var/run/lsm.pid" + +[ -f /etc/sysconfig/lsm ] && . /etc/sysconfig/lsm +[ -x /usr/sbin/lsm ] || exit 0 + +RETVAL=0 + +start() { + echo -n $"Starting lsm: " + daemon --pidfile=${PIDFILE} /usr/sbin/lsm --config $CONFIGFILE --pidfile $PIDFILE + RETVAL=$? + /bin/usleep 10000 + echo + [ $RETVAL = 0 ] && touch /var/lock/subsys/lsm + return $RETVAL +} + +stop() { + echo -n $"Stopping lsm: " + killproc /usr/sbin/lsm + RETVAL=$? + echo + [ $RETVAL = 0 ] && rm -f /var/lock/subsys/lsm + return $RETVAL +} + +restart() { + stop + start +} + +reload() { + echo -n $"Reloading lsm: " + killproc -p ${PIDFILE} /usr/sbin/lsm -HUP + RETVAL=$? + echo + return $RETVAL +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + reload) + reload + ;; + condrestart) + [ -f /var/lock/subsys/lsm ] && restart + ;; + status) + status -p ${PIDFILE} lsm + RETVAL=$? + ;; + *) + echo "Usage: lsm {start|stop|restart|condrestart|status}" + RETVAL=2 +esac + +exit $RETVAL diff --git a/lsm.service b/lsm.service new file mode 100644 index 0000000..4b3ed8c --- /dev/null +++ b/lsm.service @@ -0,0 +1,14 @@ +[Unit] +Description=LSM is the link status monitor +Documentation=http://lsm.foobar.fi/ +Wants=network-online.target +After=network-online.target shorewall.service shorewall6.service + +[Service] +Type=simple +ExecStart=/usr/sbin/lsm --config /etc/lsm/lsm.conf --no-fork +ExecReload=/bin/kill -HUP $MAINPID +Restart=on-abort + +[Install] +WantedBy=multi-user.target diff --git a/lsm.spec b/lsm.spec new file mode 100644 index 0000000..dddce20 --- /dev/null +++ b/lsm.spec @@ -0,0 +1,883 @@ +#define devel 1 + +Summary: The Link Status Monitor +Name: lsm +Version: 1.0.4 +Release: 1%{?dist} +License: GPLv2 +Group: System Environment/Daemons +URL: http://lsm.foobar.fi/ +Source: %{name}-%{version}.tar.gz +%if 0%{?rhel} >= 7 || 0%{?fedora} +%bcond_without systemd # enabled +Requires(post): systemd +Requires(preun): systemd +Requires(postun): systemd +BuildRequires: systemd +%else +%bcond_with systemd # disabled +Requires(post): chkconfig +Requires(postun): /sbin/service +Requires(preun): /sbin/service +Requires(preun): chkconfig +%endif +%if 0%{?rhel} && 0%{?rhel} <= 5 +%global _sharedstatedir /var/lib +%endif +Requires: mailx +%if 0%{?devel} +BuildRequires: ElectricFence +%endif +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +%description +Lsm is the link status monitor. + +Lsm can ping multiple targets and when up or down event happens +it will execute user configured external script so it can be used +as poor man's routing protocol. + +%prep +%setup -q + +%build +EFENCE= +%if 0%{?devel} +# Disable -O2 temporarily +RPM_OPT_FLAGS="$(echo "%{optflags}" | sed 's/-O.\ / /')" +EFENCE="-lefence" +%endif +make CFLAGS="$RPM_OPT_FLAGS" LDFLAGS=${EFENCE} %{?_smp_mflags} + +%install +rm -rf %{buildroot} + +mkdir -p %{buildroot}%{_sysconfdir}/lsm +mkdir -p %{buildroot}%{_sbindir} +mkdir -p %{buildroot}%{_libexecdir}/lsm +mkdir -p %{buildroot}%{_sharedstatedir}/lsm + +install -m0755 lsm %{buildroot}%{_sbindir} +install -m0644 lsm.conf %{buildroot}%{_sysconfdir}/lsm +install -m0755 default_script group_script shorewall_script shorewall6_script \ + %{buildroot}%{_libexecdir}/lsm/ + +%if %{with systemd} +mkdir -p %{buildroot}%{_unitdir} +install -m0644 lsm.service %{buildroot}%{_unitdir}/ +%else +mkdir -p %{buildroot}%{_initrddir} +install -m0755 lsm.init %{buildroot}%{_initrddir}/lsm +# Compat symlinks for older system versions +mkdir -p %{buildroot}%{_datadir}/lsm +ln -s ../../libexec/lsm/shorewall_script \ + %{buildroot}%{_datadir}/lsm/ +ln -s ../../libexec/lsm/default_script \ + %{buildroot}%{_datadir}/lsm/ +%endif + +%clean +rm -rf %{buildroot} + +%post +%if %{with systemd} +%systemd_post lsm.service +%else +/sbin/chkconfig --add lsm +%endif + +%preun +%if %{with systemd} +%systemd_preun lsm.service +%else +if [ $1 -eq 0 ]; then + /sbin/service lsm stop >/dev/null 2>&1 || : + /sbin/chkconfig --del lsm +fi +%endif + +%postun +%if %{with systemd} +%systemd_postun_with_restart lsm.service +%else +if [ $1 -ge 1 ]; then + /sbin/service lsm condrestart >/dev/null 2>&1 || : +fi +%endif + + +%files +%defattr(-,root,root) +%doc README lsm.conf.sample default_script.sample rsyslog-lsm.conf.sample +%if %{with systemd} +%{_unitdir}/lsm.service +%else +%{_initrddir}/lsm +%dir %{_datadir}/lsm +%{_datadir}/lsm/default_script +%{_datadir}/lsm/shorewall_script +%endif +%dir %{_libexecdir}/lsm +%{_libexecdir}/lsm/default_script +%{_libexecdir}/lsm/group_script +%{_libexecdir}/lsm/shorewall_script +%{_libexecdir}/lsm/shorewall6_script +%dir %{_sysconfdir}/lsm +%config(noreplace) %{_sysconfdir}/lsm/lsm.conf +%{_sbindir}/lsm +%dir %{_sharedstatedir}/lsm + +%changelog +* Tue Jan 26 2016 Mika Ilmaranta <ilmis@nullnet.fi> - 1.0.4-1 +- For systemd start after shorewall otherwise shorewall_script + may be executed too early + +* Fri Dec 4 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 1.0.3-1 +- recursive read_config reported errors many times + +* Fri Dec 4 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 1.0.2-1 +- call init_config in reload_config + +* Fri Dec 4 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 1.0.1-1 +- double free() fix? + +* Thu Nov 19 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 1.0-1 +- script API change. pass empty strings to scripts without converting to + "-" or "NA". +- include and -include now support patterns + +* Tue Nov 17 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.195-1 +- fix dynamic memory handling in sane values in code. + +* Mon Nov 16 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.194-1 +- set sane values in code. overridable in config defaults section as + before. + +* Sat Oct 31 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.193-1 +- use full path for -included file + +* Fri Oct 30 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.192-1 +- default_script: run date after checks + +* Fri Oct 30 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.191-1 +- support for -include aka ignore include errors if file is missing + +* Fri Oct 23 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.190-1 +- fix groups_decide logic when group logic is 'or'. Thanks to + Filippo Carletti for noticing there was a problem. + +* Mon Jun 1 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.189-1 +- update stats must not clear target used count + +* Mon Jun 1 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.188-1 +- moved target used slots book keeping to send function + +* Mon Jun 1 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.187-1 +- update stats after each ping round so that startup burst can use used slot + count + +* Mon Jun 1 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.186-1 +- dump startup acceleration and startup burst config also +- startup acceleration logic fix + +* Mon Jun 1 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.185-1 +- startup burst logic rewrite + +* Mon Jun 1 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.184-1 +- two separate config params for startup burst + +* Sun May 31 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.183-1 +- startup acceleration configurable +- startup burst configurable + +* Sun May 31 2015 Tuomo Soini <tis@foobar.fi> - 0.182-1 +- add shorewall6_script and group_script +- install scripts to /usr/libexec/lsm +- add compatibility symlinks to /usr/share/lsm + +* Fri May 29 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.181-1 +- accelerate decision at startup, must have received at least one packet + +* Fri May 29 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.180-1 +- accelerate decision at startup + +* Tue Feb 3 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.179-1 +- lsm.service use correct path for the binary + +* Wed Jan 14 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.178-1 +- export status info to separate file +- cleaned up compilation with different sets of NO_PLUGIN_EXPORT defines + +* Mon Jan 12 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.177-1 +- export connection statuses to plugin directory + +* Sun Jan 11 2015 Mika Ilmaranta <ilmis@nullnet.fi> - 0.176-1 +- log status with other attributes (Luigi Iotti) + +* Sat Sep 13 2014 Mika Ilmaranta <ilmis@nullnet.fi> - 0.175-1 +- if --no-fork don't write pid file + +* Sat Sep 13 2014 Mika Ilmaranta <ilmis@nullnet.fi> - 0.174-1 +- systemd support + +* Wed Aug 6 2014 Mika Ilmaranta <ilmis@nullnet.fi> - 0.173-1 +- fixed -v parameter + +* Wed Aug 6 2014 Mika Ilmaranta <ilmis@nullnet.fi> - 0.172-1 +- better usage help +- fixed bug in optarg use + +* Wed Aug 6 2014 Mika Ilmaranta <ilmis@nullnet.fi> - 0.171-1 +- split source for pidfile processing +- real cmdline argument processing with optarg +- support no-daemon cmdline option for integration with systemd +- debug level 100 no longer suppresses daemonization use the above + mentioned cmdline option instead + +* Sun Feb 16 2014 Mika Ilmaranta <ilmis@nullnet.fi> - 0.170-1 +- Makefile: debian frendlier install target + +* Sun Feb 16 2014 Mika Ilmaranta <ilmis@nullnet.fi> - 0.169-1 +- Makefile: install target, not guaranteed to work +- README: updated with INSTALL section + +* Fri Nov 22 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.168-1 +- zero timeout_max and consecutive_missing_max in dump_statuses if there is + status_change to up state not in decide + +* Fri Nov 22 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.167-1 +- update timeout_max and consecutive_missing_max regardless of state + +* Fri Nov 22 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.166-1 +- added timeout_max + +* Fri Nov 22 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.165-1 +- drop max_successive_waiting, it delivers no additional info +- renamed max_successive_missing to successive_missing_max + +* Fri Nov 22 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.164-1 +- added max_successive_waiting and max_successive_missing +- cfg.debug controls now how much info is syslogged on dump_status + debug >= 6, log calculated connection status + debug >= 7, log connection probe statuses + +* Fri Aug 9 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.163-1 +- don't suppress all up notifies when unknown_up_notify is off + +* Thu Jul 25 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.162-1 +- option to skip executing notify script on unknown to up event for groups + also + +* Thu Jul 25 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.161-1 +- added option to skip executing notify script on unknown to up event + +* Thu Jul 25 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.160-1 +- moved save/restore statuses to their own file from lsm.c + +* Wed Jul 24 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.159-1 +- updated shorewall_script with new parameters + +* Wed Jul 24 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.158-1 +- clean up munin titles + +* Tue Jul 23 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.157-1 +- use connection names in munin labels + +* Fri Jul 19 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.156-1 +- plugin_export: changed strlower to munin_src_name + +* Thu Jul 18 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.155-1 +- plugin_export: export statistics for munin + +* Wed Jul 17 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.154-1 +- fixed divide error + +* Wed Jul 17 2013 Mika Ilamranta <ilmis@nullnet.fi> - 0.153-1 +- lsm.c: drop casts and keep avg_rtt in usec, divide by 1000.0 if needed as + float + +* Wed Jul 17 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.152-1 +- lsm.c: cast avg_rtt for fork_exec + +* Wed Jul 17 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.151-1 +- prepare for exporting statistics to zabbix and munin + +* Wed Jul 10 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.150-1 +- lsm.c: change status to up directly from long_down + +* Tue Jul 9 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.149-1 +- default_script: added missing closing curly bracket + +* Mon Jul 8 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.148-1 +- pass event timestamp from lsm to scripts + +* Mon Jul 8 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.147-1 +- pass empty warn_email addr to script as hyphen +- if script gets hyphen for email addr do not send mail + +* Thu Jul 4 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.146-1 +- long_down_to down -> long_down_to_down + +* Thu Jul 4 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.145-1 +- check also for nul-string email addr + +* Mon Jul 1 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.144-1 +- use enum for status +- new config options for reporting only longer down time: +- long_down_time = how many seconds down time is considered long +- long_down_email = where to report long down time +- long_down_notifyscript = script to use when reporting long down time +- long_down_eventscript = script to use when reacting to long down time + +* Fri May 10 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.143-1 +- discard probed src addr if bind fails + +* Thu Mar 28 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.142-1 +- free exec_queue on exit + +* Thu Mar 28 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.141-1 +- make exec_queue_dump activate with -DDEBUG +- rsyslog-lsm.conf.sample by Dimitar Angelov + +* Thu Mar 28 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.140-1 +- debug with exec_queue_dump + +* Thu Mar 28 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.139-1 +- forkexec.c: check that eq is set in exec_queue_process + +* Thu Mar 28 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.138-1 +- config.c: defaults.queue NULL + +* Thu Mar 28 2013 Mika Ilmaranta <ilmis@nullnet.fi> - 0.137-1 +- exec queue, run event scripts sychronously + +* Thu Oct 11 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.136-1 +- lsm.c: clean up debugging and add check that reply addr matches + original destination addr + +* Thu Oct 11 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.135-1 +- lsm.c: more debugging when pdp->id is out of range + +* Thu Oct 11 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.134-1 +- lsm.c: do bounds check on pdp->id before use as ctable index + +* Mon Aug 13 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.133-1 +- lsm.c: rethought group up/down logic. (original was wrong up event on + transition to unknown state and 0.132 fix reported both up and down + events) + +* Mon Aug 13 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.132-1 +- default_script: better english for timeout packets +- config.c: recognise status for group, use default start status for group +- lsm.c: report group prevstate textually + +* Sat May 12 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.131-1 +- close socket on recvfrom fail + +* Tue May 8 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.130-1 +- initialize cmsgbuf and cmsglen in open_icmp_sock as socket may be + closed and reopened + +* Sun May 6 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.129-1 +- close socket also when sendmsg fails + +* Sun May 6 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.128-1 +- cleanup debugging +- don't SO_BINDTODEVICE for AF_INET6 when device is specified as it uses + sendmsg instead of sendto + +* Sun May 6 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.127-1 +- debug sendmsg a little + +* Sun May 6 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.126-1 +- set v6 filter + +* Sun May 6 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.125-1 +- handle v6 device setting like ping6 does + +* Sat May 5 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.124-1 +- v6 sequence now correct? +- set v6 sockopts also if ttl is not set + +* Thu Apr 19 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.123-1 +- removed unnesessary debugging +- v6 src ip addr autodiscovery works now + +* Thu Apr 19 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.122-1 +- debug memcpy t->src6 results + +* Thu Apr 19 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.121-1 +- check t->src6 contents + +* Thu Apr 19 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.120-1 +- debug v6 getsockname unconditionally + +* Thu Apr 19 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.119-1 +- debug getsockname for v6 conn + +* Thu Apr 19 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.118-1 +- no SO_DONTROUTE for v6 probe + +* Fri Apr 13 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.117-1 +- lsm.c: set more v6 socket options +- TODO: check why probe_src_ip_addr doesn't work for v6 + +* Sat Mar 10 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.116-2 +- fix forkexec format str + +* Sat Mar 10 2012 Mika Ilmaranta <ilmis@nullnet.fi> - 0.116-1 +- report prevstate as string up/down/unknown + +* Wed Mar 7 2012 Tuomo Soini <tis@foobar.fi> - 0.115-2 +- fix %%postun not to fail + +* Wed Dec 21 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.115-1 +- forkexec.c: remove space after func name +- lsm.c: free_config_data: don't close t->sock if it is -1 + +* Wed Dec 21 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.114-1 +- moved time calculation functions to their own files + +* Wed Dec 21 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.113-1 +- decode all icmp and icmp6 types and codes + +* Tue Dec 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.112-1 +- Andrew Beverley's signal child handler +- made static functions static + +* Tue Dec 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.111-1 +- moved children handling from forkexec to main loop + +* Tue Dec 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.110-1 +- lsm.c: use t->dst6 in v6 probe not cur->dstinfo + +* Tue Dec 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.109-1 +- rebuild with efence + +* Tue Dec 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.108-1 +- ipv6 support seems now stable enough + +* Tue Dec 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.107-1 +- lsm.c: debug probe_src_ip_addr call + +* Tue Dec 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.106-1 +- lsm.c: debug open_icmp_sock socket call removed +- lsm.c: debug setting v6 src addr + +* Tue Dec 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.105-1 +- lsm.c: debug open_icmp_sock socket call + +* Tue Dec 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.104-1 +- lsm.c: more sin6_family fixes + +* Tue Dec 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.103-1 +- lsm.c: set sin6_family and show also v4 addr in debug + +* Tue Dec 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.102-1 +- lsm.c: debug reply pkts more + +* Mon Dec 19 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.101-1 +- lsm.c: fix inet_pton parameters + +* Mon Dec 19 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.100-1 +- lsm.c: fail if device can not be bound to by Andrew Beverley +- lsm.c: bind also to ipv6 sourceip + +* Mon Dec 19 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.99-1 +- lsm.c: fix ping6 sendto params + +* Mon Dec 19 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.98-1 +- initial ipv6 support + +* Mon Dec 19 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.97-1 +- include version information in directory name inside tar pkg + +* Mon Dec 19 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.96-1 +- lsm.c: initialize last decision outside main loop. fixes decide call + interval. patch by Andrew Beverley +- config.c: debugging information added for unmatched config option by + Andrew Beverley. strcpy changed to memmove in strip leading space +- lsm.c: show correct usage +- config.c: clean up config file lines harder before parsing + +* Fri Dec 9 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.95-1 +- shorewall_script: removed email notification, use it as eventscript and + default_script as notifyscript for example + +* Fri Dec 9 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.94-1 +- added support for worker script (= eventscript) and notify script + (= notifyscript) differentiation, both are called with same parameters. + new script parameter previous state + +* Thu Dec 8 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.93-1 +- for the following two patches thanks to Pablo Gomez +- lsm.c: use sourceip from config for icmp pkts if set +- lsm.c,config.c: new default state is unknown, which can be overridden in + config. this allows lsm to run event script "on startup" after connection + statuses are discovered. same rules apply as for actual events. + +* Thu Oct 20 2011 Mika Ilmaranta <ilmis@nullnet.fi>> - 0.92-1 +- report same seq status only once + +* Thu Oct 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.91-1 +- dump conn status to syslog every maxseq when status is down only + if no status change + +* Thu Oct 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.90-1 +- dump conn status to syslog every maxseq when status is down + +* Tue Sep 27 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.89-1 +- added comment to shrewall_script about shorewall version requirement + +* Tue Sep 27 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.88-1 +- added shorewall_script + +* Fri Jul 15 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.87-1 +- production setting for compilation (no efence and use optimization) + +* Wed Jun 22 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.86-1 +- protect FD_ISSET also from closed socket + +* Wed Jun 22 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.85-1 +- compile with ElectricFence + +* Wed Jun 22 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.84-1 +- revert to v0.64 base +- Makefile: merge v0.83 changeset +- lsm.spec: v0.83 spec +- config: removed reopen_on_enodev and added patch for double free +- lsm.c: removed reopen_on_enodev handling +- lsm.conf: removed reopen_on_enodev +- added cksum files +- lsm.c: use external cksum +- v0.83 forkexec, globals and signal_handler +- lsm.c: separate defs.h, merged fixes from v0.83 +- lsm.c: close socket on fail and reopen just before next ping +- spec: version and changelog + +* Wed Jun 22 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.83-1 +- check that device names match + +* Wed Jun 22 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.82-1 +- compile with ElectricFence depending on devel define + +* Wed Jun 22 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.81-1 +- check arp header differently + +* Tue Jun 21 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.80-1 +- further debugging of disappearing arp replies + +* Tue Jun 21 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.79-1 +- try to find out where arp replies vanish + +* Tue Jun 21 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.78-1 +- config.c: fix double free on warn_email and other group parameters + +* Tue Jun 21 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.77-1 +- fix BuildRequires for -lefence + +* Tue Jun 21 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.76-1 +- compile with -lefence + +* Tue Jun 21 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.75-1 +- check packet from addr to determine which connection it + belongs to + +* Tue Jun 21 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.74-1 +- moved rest of io functions to io.c +- io.c: split icmp and arp reply handling +- io.c: differentiate ping_rcv error logging on find_interface use + +* Tue Jun 21 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.73-1 +- bind SIGUSR2 to signal_handler + +* Tue Jun 21 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.72-1 +- dump interface list on SIGUSR2 + +* Mon Jun 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.71-1 +- io.c: due to function splitting add missing gettimeofday call in icmp_send + +* Mon Jun 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.70-1 +- timeval_diff_[lt,gt] fix diff_usec calculation + +* Mon Jun 20 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.69-1 +- removed sock from target structure +- added interface handling to interface.c and made other parts use it + +* Fri Jun 17 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.68-1 +- broke timeval_diff_cmp to two functions +- globals.c: check for unset prog +- lsm.c: removed handle_odd_icmp function as it did nothing +- interface.c: made probe for src ip address function probe_addresses + +* Fri Jun 17 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.67-1 +- moved global variable handling to its own block +- moved decision functions to their own block +- moved signal_handler to its own block +- moved ping function to its own file +- lsm.c: reset reload_cfg after reloading config, not before +- Makefile: clean also ~ files starting with dot + +* Fri Jun 17 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.66-1 +- moved structure definitions to lsm.h +- lsm.c: moved socket opening functionality to interface.c +- Makefile: use Makefile.depend +- .gitignore: ignore Makefile.depend, obj files and lsm binary + +* Wed Jun 15 2011 Mika Ilmaranta <ilmis@nullnet.fi> - 0.65-1 +- started rewrite +- moved forkexec function and defines to their own files +- added interface handling src files +- moved cksum function to its own file +- moved timeval functions to their own files + +* Fri Dec 31 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.64-1 +- fill up target struct src address for ping also + do it at startup (should this be done every time we send ping packet?) + +* Fri Dec 31 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.63-1 +- Added src ip to script parameters + src ip paramater is the last one so that every single old config + doesn't have to be rewritten. + +* Fri Dec 10 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.62-1 +- check for valid ip-address in checkip parameter + +* Fri Dec 10 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.61-1 +- changed lsm.c init_config to init_config_data +- added init_config to config.c which is then called before read_config +- added a warning when checkip is not set + +* Sat Oct 9 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.60-1 +- fix recursive read_config use. set default values only once. +- use cfg.debug only after config is read +- added default_script.sample + +* Mon Sep 27 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.59-1 +- remember connection statuses after config reload + +* Mon Sep 27 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.58-1 +- reopen_on_enodev support. set this to 1 so lsm will try to reopen ping + device when it encounters ENODEV error when sending ping packet + +* Sun Sep 19 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.57-1 +- added defaults status to give initial assumption of the connection status +- added config status for connections. is it assumed down = 0 or up = 1 + at lsm start + +* Sat Sep 18 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.56-1 +- fixed rpmlint-v0.91 warnings. + +* Sat Apr 24 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.53-1 +- added error checking to ftruncate and write so that mock build doesn't + complain +- introduced timeval_diff_cmp function as it seems quite problematic to + really compare times in usec since epoch using integer values. + especially 32bit systems were seeing 99% CPU loads because of this. + +* Thu Apr 22 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.52-1 +- initialize all struct timeval structures to tv_sec = 0 and tv_usec = 0 +- added use of error flag to arping sending + +* Fri Mar 5 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.51-1 +- fix avg rtt calculation comment +- show in the default mail template avg rtt unit [usec] +- in syslog avg rtt is reported in [msec] as of v0.50 like ping does + +* Fri Mar 5 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.50-1 +- report rtt with three decimals accuracy + +* Thu Mar 4 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.49-1 +- count average rtt in milliseconds not in microseconds + +* Thu Mar 4 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.48-1 +- don't count timeouted late replies in cons rcvd + +* Thu Mar 4 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.47-1 +- added some missing fields to lsm.init + +* Thu Mar 4 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.46-1 +- dump all statuses only if requested by SIGUSR1 +- otherwise dump only connection status data of connection whose status changed + +* Wed Mar 3 2010 Mika Ilmaranta <ilmis@nullnet.fi> - 0.45-1 +- use LOG_PID openlog option + +* Thu Dec 17 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.44-1 +- added some parameter sanity checking. if max_packet_loss <= min_packet_loss then there can be a flip-flop effect + and many many and still a few reports mailed to warn_email address. + +* Mon Nov 9 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.43-1 +- convert all tabs to spaces before processing config line + +* Sun Sep 27 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.42-1 +- changed action script to event script to follow suite with config + +* Sun Sep 27 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.41-1 +- added action_script_check() to check for valid action script + +* Wed Sep 2 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.40-1 +- lseek to start of pid file and ftruncate it to zero size before writing + our new pid. this prevents us having two pids in the file if previous + lsm crashed or exited and did not remove the file (fcntl lock is cleared + by kernel as process dies "prematurely"). + +* Mon Jun 29 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.39-1 +- set close on exec flag for fds and sockets +- call closelog() within forked child before exec + +* Thu Jun 18 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.38-1 +- require mailx for /bin/mail as default_script uses it + +* Thu Jun 18 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.37-1 +- when dumping config log also group's warn_email and logic +- set last previous last group members next to the new last member + +* Thu Jun 18 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.36-1 +- apply sane defaults to group parameters + +* Thu Jun 4 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.35-1 +- only log sendto errors with debug >= 9 + +* Thu Jun 4 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.34-1 +- better sendto error ignore fix +- changed syslog calls for Mandrake + +* Thu Jun 4 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.33-1 +- ping_send: don't care about sendto errors + +* Wed Apr 29 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.32-1 +- split long debug explanation line in lsm.conf + +* Wed Apr 29 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.31-1 +- left only defaults in lsm.conf and moved examples to + lsm.conf.sample + +* Tue Apr 28 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.30-1 +- timeval_diff calculation order change to prevent + long overflow. nobody has encoutered that but just to be + sure +- use default ttl=0 which uses system default ttl + +* Sat Apr 18 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.29-1 +- Added decision making section to README written by Dean Takemori + he suggested to include it in lsm.conf but I thought it was + long enough already + +* Sat Apr 18 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.28-1 +- Tom Eastep's fix for last_sent_time initialization +- added time stamp to default_script mail body + +* Fri Apr 10 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.27-1 +- added checks for missing group members + +* Fri Apr 10 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.26-1 +- added connection grouping + +* Thu Apr 9 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.25-1 +- gettimeofday failure patch from Dean Takemori + +* Sun Apr 5 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.24-1 +- add support for SIGHUP to reload config + +* Tue Mar 24 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.23-1 +- changed ping packets to use own socket for each target. + looks like setsockopt SO_BINDTOINTERFACE is not reversible and + according to documentation I found using it multiple times + may lead to unpredicted results due to kernel caching. + +* Tue Mar 24 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.22-1 +- changed indentation to tabs + +* Mon Mar 16 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.21-1 +- added LSM: to default_script mail subject + +* Tue Mar 10 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.20-1 +- handle ENODEV as if the ping packet was sent, but do barf to syslog + about it. this will eventually cause a down event. added + an error flag which is set if sendto returns with value < 0. +- dump connection statuses to syslog when up/down-event happens. +- moved status dump's "header" -line above the pkt status bits. +- added error flag dumping. + +* Fri Mar 6 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.19-1 +- fix pid file write order + +* Thu Mar 5 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.18-1 +- added pid file handling + +* Thu Mar 5 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.17-1 +- rebuild because of mystical i386 build problems + +* Wed Mar 4 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.16-1 +- pass only LANG, PATH and TERM environment variables to + scripts + +* Wed Mar 4 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.15-1 +- fixed all rpmlint errors reported in binary pkg + which means that default_script is moved to /usr/share/lsm + +* Wed Mar 4 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.14-1 +- added ipv6 support to TODO list +- fixed rpmlint errors in lsm.spec + +* Tue Feb 24 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.13-1 +- typos: lisence -> license +- mention SIGUSR1 behaviour in README +- added a check for ENODEV for ping packets + +* Fri Feb 20 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.12-1 +- don't define device= in defaults + +* Wed Feb 18 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.11-1 +- if device is not specified return NA + +* Wed Feb 18 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.10-1 +- added device to script parameters +- first try on binding ping packets to device + +* Thu Feb 12 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.9-1 +- fixed rtt comments in default_script + +* Thu Feb 12 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.8-1 +- init script reload fix + +* Sat Feb 7 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.7-1 +- fixed typos +- init script reload was missing + +* Sun Feb 1 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.6-1 +- fixed comments and readme to correspond current status + +* Sun Feb 1 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.6-1 +- now each target has its own ttl setting + +* Sat Jan 31 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.5-8 +- check for no targets specified in conf + +* Sat Jan 31 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.5-7 +- all except DEBUG is now syslogged + +* Sat Jan 31 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.5-4 +- added ttl setting handling for ping packets. currently all + ping monitored links share a common ttl value which is + taken from the first config entry's ttl value. +- when SIGUSR1 is received lsm dumps current packet info to + syslog ... + +* Sat Jan 31 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.5-3 +- changed avg rtt calculation so that only replied packets' + rtt is counted + +* Sat Jan 31 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.5-2 +- Added a ping reply packet min size check + +* Fri Jan 30 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.5-1 +- started adding support for arp check which is actually + using arp packets rather than ping packets in case + your gw administrators have blocked ping + +* Thu Jan 29 2009 Mika Ilmaranta <ilmis@nullnet.fi> - 0.4-1 +- Initial build + +#EOF diff --git a/pidfile.c b/pidfile.c new file mode 100644 index 0000000..1273519 --- /dev/null +++ b/pidfile.c @@ -0,0 +1,89 @@ +/* + + (C) 2014 Mika Ilmaranta <ilmis@nullnet.fi> + + License: GPLv2 + + */ + +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <syslog.h> +#include <unistd.h> +#include <string.h> + +#include "pidfile.h" +#include "globals.h" + +static int pidfilefh = 0; + +int pidfile_open(void) +{ + if(get_nodaemon()) return(0); + + pidfilefh = open(get_pidfile(), O_RDWR|O_CREAT, 0640); + + if(pidfilefh < 0) { + syslog(LOG_ERR, "can't open pid file %s", get_pidfile()); + return(1); /* can not open */ + } + if(fcntl(pidfilefh, F_SETFD, FD_CLOEXEC) == -1) { + syslog(LOG_ERR, "failed to set close on exec on pid file %s", get_pidfile()); + } + + if(lockf(pidfilefh, F_TLOCK, 0) < 0) { + syslog(LOG_ERR, "can't lock pid file %s", get_pidfile()); + return(1); /* can not lock */ + } + + return(0); +} + +int pidfile_update(void) +{ + if(get_nodaemon()) return(0); + + if(pidfilefh) { + char str[BUFSIZ]; + ssize_t n; + + lseek(pidfilefh, 0, SEEK_SET); + if(ftruncate(pidfilefh, 0) == -1) { + syslog(LOG_ERR, "ftruncate failed \"%s\"", strerror(errno)); + return(1); + } + + sprintf(str, "%d\n", getpid()); + n = write(pidfilefh, str, strlen(str)); /* record pid to lockfile */ + if(n == -1) { + syslog(LOG_ERR, "write failed \"%s\"", strerror(errno)); + return(1); + } + if(n != strlen(str)) { +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) + syslog(LOG_ERR, "write failed, %ld bytes written of %ld bytes", n, strlen(str)); +#else + syslog(LOG_ERR, "write failed, %d bytes written of %d bytes", n, strlen(str)); +#endif + return(1); + } + } + + return(0); +} + +void pidfile_close(void) +{ + if(get_nodaemon()) return; + + if(pidfilefh) { + close(pidfilefh); + pidfilefh = 0; + unlink(get_pidfile()); + } +} + +/* EOF */ diff --git a/pidfile.h b/pidfile.h new file mode 100644 index 0000000..f61ea50 --- /dev/null +++ b/pidfile.h @@ -0,0 +1,18 @@ +/* + + (C) 2014 Mika Ilmaranta <ilmis@nullnet.fi> + + License: GPLv2 + +*/ + +#ifndef __PIDFILE_H__ +#define __PIDFILE_H__ + +int pidfile_open(void); +int pidfile_update(void); +void pidfile_close(void); + +#endif + +/* EOF */ diff --git a/plugin_export.c b/plugin_export.c new file mode 100644 index 0000000..b2a9f82 --- /dev/null +++ b/plugin_export.c @@ -0,0 +1,259 @@ +/* + +(C) 2013 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#ifndef NO_PLUGIN_EXPORT + +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> +#include <sys/time.h> +#include <string.h> +#include <ctype.h> + +#include "plugin_export.h" +#include "timecalc.h" +#include "defs.h" +#ifndef NO_PLUGIN_EXPORT_STATUS +#include "globals.h" +#endif + +#ifndef NO_PLUGIN_EXPORT_MUNIN +static char *munin_data_src_name(const char *src); +static void plugin_export_munin(CONFIG *first); +#endif + +static struct timeval export_time = {0, 0}; + +void plugin_export_init(void) +{ + gettimeofday(&export_time, NULL); +} + +void plugin_export(CONFIG *first) +{ + struct timeval current_time = {0, 0}; + + gettimeofday(¤t_time, NULL); + + /* export every 300s */ + if(timeval_diff_cmp(¤t_time, &export_time, TIMEVAL_DIFF_CMP_GT, 300, 0) == FALSE) return; + + /* next export after 300 sec */ + timeval_add(&export_time, 300, 0); + +#ifndef NO_PLUGIN_EXPORT_MUNIN + plugin_export_munin(first); +#endif +} + +#ifndef NO_PLUGIN_EXPORT_MUNIN +static void plugin_export_munin(CONFIG *first) +{ + FILE *fp; + char buf[BUFSIZ]; + CONFIG *cur; + TARGET *t; + + /* export avg_rtt graph config */ + snprintf(buf, BUFSIZ - 1, "%s/%s", PLUGIN_EXPORT_DIR, "config.rtt"); + + if((fp = fopen(buf, "w")) == NULL) { + syslog(LOG_ERR, "%s: %s: failed to open file %s for write", __FILE__, __FUNCTION__, buf); + return; + } + + fprintf(fp, "graph_title LSM Average Ping Latency\n"); + fprintf(fp, "graph_vlabel ms\n"); + fprintf(fp, "graph_info This graph shows LSM status\n"); + fprintf(fp, "graph_category network\n"); + fprintf(fp, "graph_args --base 1000 -l 0\n"); + + for(cur = first; cur; cur = cur->next) { + char *name = munin_data_src_name(cur->name); + + fprintf(fp, "%s_rtt.label %s rtt\n", name, cur->name); + fprintf(fp, "%s_rtt.type GAUGE\n", name); + } + + fclose(fp); + + /* export avg_rtt values */ + snprintf(buf, BUFSIZ - 1, "%s/%s", PLUGIN_EXPORT_DIR, "status.rtt"); + + if((fp = fopen(buf, "w")) == NULL) { + syslog(LOG_ERR, "%s: %s: failed to open file %s for write", __FILE__, __FUNCTION__, buf); + return; + } + + for(cur = first; cur; cur = cur->next) { + char *name = munin_data_src_name(cur->name); + t = cur->data; + + fprintf(fp, "%s_rtt.value %.2f\n", name, (t->status == DOWN || t->status == LONG_DOWN) ? 0.0 : t->avg_rtt / 1000.0); + } + + fclose(fp); + + /* export other counts config */ + snprintf(buf, BUFSIZ - 1, "%s/%s", PLUGIN_EXPORT_DIR, "config.counts"); + + if((fp = fopen(buf, "w")) == NULL) { + syslog(LOG_ERR, "%s: %s: failed to open file %s for write", __FILE__, __FUNCTION__, buf); + return; + } + + fprintf(fp, "graph_title LSM packet counts\n"); + fprintf(fp, "graph_vlabel percent\n"); + fprintf(fp, "graph_info This graph shows LSM status\n"); + fprintf(fp, "graph_category network\n"); + fprintf(fp, "graph_args --base 1000 -l 0\n"); + + for(cur = first; cur; cur = cur->next) { + char *name = munin_data_src_name(cur->name); + + fprintf(fp, "%s_timeout.label %s Timed out\n", name, cur->name); + fprintf(fp, "%s_timeout.type GAUGE\n", name); + + fprintf(fp, "%s_replied.label %s Replied\n", name, cur->name); + fprintf(fp, "%s_replied.type GAUGE\n", name); + + fprintf(fp, "%s_waiting.label %s Waiting\n", name, cur->name); + fprintf(fp, "%s_waiting.type GAUGE\n", name); + + fprintf(fp, "%s_latereply.label %s Late replied\n", name, cur->name); + fprintf(fp, "%s_latereply.type GAUGE\n", name); + + fprintf(fp, "%s_cwait.label %s Consecutive waiting\n", name, cur->name); + fprintf(fp, "%s_cwait.type GAUGE\n", name); + + fprintf(fp, "%s_cmiss.label %s Consecutive missing\n", name, cur->name); + fprintf(fp, "%s_cmiss.type GAUGE\n", name); + + fprintf(fp, "%s_crcvd.label %s Consecutive received\n", name, cur->name); + fprintf(fp, "%s_crcvd.type GAUGE\n", name); + } + + fclose(fp); + + /* export other counts values */ + snprintf(buf, BUFSIZ - 1, "%s/%s", PLUGIN_EXPORT_DIR, "status.counts"); + + if((fp = fopen(buf, "w")) == NULL) { + syslog(LOG_ERR, "%s: %s: failed to open file %s for write", __FILE__, __FUNCTION__, buf); + return; + } + + for(cur = first; cur; cur = cur->next) { + char *name = munin_data_src_name(cur->name); + t = cur->data; + + fprintf(fp, "%s_timeout.value %d\n", name, t->timeout); + + fprintf(fp, "%s_replied.value %d\n", name, t->replied); + + fprintf(fp, "%s_waiting.value %d\n", name, t->waiting); + + fprintf(fp, "%s_latereply.value %d\n", name, t->reply_late); + + fprintf(fp, "%s_cwait.value %d\n", name, t->consecutive_waiting); + + fprintf(fp, "%s_cmiss.value %d\n", name, t->consecutive_missing); + + fprintf(fp, "%s_crcvd.value %d\n", name, t->consecutive_rcvd); + } + + fclose(fp); + + /* export connection status config */ + snprintf(buf, BUFSIZ - 1, "%s/%s", PLUGIN_EXPORT_DIR, "config.status"); + + if((fp = fopen(buf, "w")) == NULL) { + syslog(LOG_ERR, "%s: %s: failed to open file %s for write", __FILE__, __FUNCTION__, buf); + return; + } + + fprintf(fp, "graph_title LSM connection statuses\n"); + fprintf(fp, "graph_vlabel Status\n"); + fprintf(fp, "graph_info This graph shows LSM connection statuses\n"); + fprintf(fp, "graph_category network\n"); + fprintf(fp, "graph_info Status: 0 = DOWN, 1 = UP, 2 = UNKNOWN, 3 = LONG_DOWN\n"); + fprintf(fp, "graph_args --base 1000 --lower-limit 0 --upper-limit 3\n"); + + for(cur = first; cur; cur = cur->next) { + char *name = munin_data_src_name(cur->name); + + fprintf(fp, "%s_status.label %s Status\n", name, cur->name); + } + + fclose(fp); + + /* export connection status values */ + snprintf(buf, BUFSIZ - 1, "%s/%s", PLUGIN_EXPORT_DIR, "status.status"); + + if((fp = fopen(buf, "w")) == NULL) { + syslog(LOG_ERR, "%s: %s: failed to open file %s for write", __FILE__, __FUNCTION__, buf); + return; + } + + for(cur = first; cur; cur = cur->next) { + char *name = munin_data_src_name(cur->name); + t = cur->data; + + fprintf(fp, "%s_status.value %d\n", name, t->status); + } + + fclose(fp); +} +#endif + +#ifndef NO_PLUGIN_EXPORT_STATUS +void plugin_export_status(CONFIG *first) +{ + FILE *fp; + char buf[BUFSIZ]; + CONFIG *cur; + TARGET *t; + + snprintf(buf, BUFSIZ - 1, "%s/%s", PLUGIN_EXPORT_DIR, "status_export"); + + if((fp = fopen(buf, "w")) == NULL) { + syslog(LOG_ERR, "%s: %s: failed to open file %s for write", __FILE__, __FUNCTION__, buf); + return; + } + + for(cur = first; cur; cur = cur->next) { + t = cur->data; + + fprintf(fp, "%s %s\n", cur->name, get_status_str(t->status)); + } + + fclose(fp); +} +#endif + +#ifndef NO_PLUGIN_EXPORT_MUNIN +static char *munin_data_src_name(const char *src) +{ + static char buf[BUFSIZ]; + char *p; + + strcpy(buf, "_"); + + strncat(buf, src, BUFSIZ - 1); + + for(p = buf; *p; p++) { + if(*p == '-') *p = '_'; + if(*p == ' ') *p = '_'; + } + return(buf); +} +#endif + +#endif + +/* EOF */ diff --git a/plugin_export.h b/plugin_export.h new file mode 100644 index 0000000..79c714b --- /dev/null +++ b/plugin_export.h @@ -0,0 +1,28 @@ +/* + +(C) 2013 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#ifndef NO_PLUGIN_EXPORT + +#ifndef __PLUGIN_EXPORT_H__ +#define __PLUGIN_EXPORT_H__ + +#include "config.h" +#include "lsm.h" + +void plugin_export_init(void); +void plugin_export(CONFIG *first); + +#ifndef NO_PLUGIN_EXPORT_STATUS +void plugin_export_status(CONFIG *first); +#endif + +#endif + +#endif + +/* EOF */ diff --git a/rsyslog-lsm.conf.sample b/rsyslog-lsm.conf.sample new file mode 100644 index 0000000..a0da81c --- /dev/null +++ b/rsyslog-lsm.conf.sample @@ -0,0 +1,3 @@ +# Link Status Monitor Log file +if $programname == 'lsm' then /var/log/lsm.log +if $programname == 'lsm' then ~ diff --git a/save_statuses.c b/save_statuses.c new file mode 100644 index 0000000..d1178a4 --- /dev/null +++ b/save_statuses.c @@ -0,0 +1,93 @@ +/* + +(C) 2013 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#include <stdlib.h> +#include <syslog.h> +#include <string.h> + +#include "config.h" +#include "lsm.h" +#include "save_statuses.h" + +typedef struct status_data { + char *name; + STATUS status; + struct status_data *next; +} STATUS_DATA; + +static STATUS_DATA *first_status = NULL; +static STATUS_DATA *last_status = NULL; +static STATUS_DATA *cur_status = NULL; + +void save_statuses(CONFIG *first) +{ + CONFIG *cur; + + if(first_status != NULL) { + syslog(LOG_ERR, "%s: %s: statuses already saved?", __FILE__, __FUNCTION__); + return; + } + + /* store connection statuses temporarily */ + for(cur = first; cur; cur = cur->next) { + if((cur_status = malloc(sizeof(STATUS_DATA))) == NULL) { + syslog(LOG_ERR, "%s: %s: couldn't malloc for status_data", __FILE__, __FUNCTION__); + exit(1); + } + cur_status->name = strdup(cur->name); + cur_status->status = ((TARGET *)cur->data)->status; + + if(last_status) { + last_status->next = cur_status; + last_status = cur_status; + cur_status->next = NULL; + } else { + first_status = cur_status; + last_status = cur_status; + cur_status->next = NULL; + } + } +} + +void restore_statuses(CONFIG *first) +{ + CONFIG *cur; + + if(first_status == NULL) { + syslog(LOG_ERR, "%s: %s: can't restore statuses, none saved?", __FILE__, __FUNCTION__); + return; + } + + /* restore connection statuses */ + for(cur_status = first_status; cur_status; cur_status = cur_status->next) { + for(cur = first; cur; cur = cur->next) { + if(strcmp(cur_status->name, cur->name) == 0) { + ((TARGET *)cur->data)->status = cur_status->status; + } + } + } + + /* get rid of temporary statuses */ + cur_status = first_status; + while(cur_status) { + STATUS_DATA *tmp; + + tmp = cur_status->next; + + free(cur_status->name); + free(cur_status); + + cur_status = tmp; + } + + first_status = NULL; + last_status = NULL; + cur_status = NULL; +} + +/* EOF */ diff --git a/save_statuses.h b/save_statuses.h new file mode 100644 index 0000000..2086b34 --- /dev/null +++ b/save_statuses.h @@ -0,0 +1,17 @@ +/* + +(C) 2013 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#ifndef __SAVE_STATUSES_H__ +#define __SAVE_STATUSES_H__ + +void save_statuses(CONFIG *first); +void restore_statuses(CONFIG *first); + +#endif + +/* EOF */ diff --git a/shorewall6_script b/shorewall6_script new file mode 100644 index 0000000..b6d59c7 --- /dev/null +++ b/shorewall6_script @@ -0,0 +1,52 @@ +#!/bin/sh +# +# Copyright (C) 2015 Tuomo Soini <tis@foobar.fi> +# +# License: GPLv2 +# + +# +# event handling script for use with shorewall6 multi-isp setup +# To be able to utilize this script you must have shorewall6 >= 4.4.23.3 +# + +STATE=${1} +NAME=${2} +CHECKIP=${3} +DEVICE=${4} +WARN_EMAIL=${5} +REPLIED=${6} +WAITING=${7} +TIMEOUT=${8} +REPLY_LATE=${9} +CONS_RCVD=${10} +CONS_WAIT=${11} +CONS_MISS=${12} +AVG_RTT=${13} +SRCIP=${14} +PREVSTATE=${15} +TIMESTAMP=${16} + +DATE=$(date --date=@${TIMESTAMP}) + +if [ ${STATE} = up ]; then + state=0 + action=enable +else + state=1 + action=disable +fi + +VARDIR=$(shorewall6 show vardir) +VARDIR=${VARDIR:-/var/lib/shorewall6} + +echo ${state} > ${VARDIR}/${DEVICE}.status + +if [ -x ${VARDIR}/firewall ]; then + ${VARDIR}/firewall ${action} ${DEVICE} +else + shorewall6 -q restart +fi + +exit 0 +# diff --git a/shorewall_script b/shorewall_script new file mode 100644 index 0000000..b9aa859 --- /dev/null +++ b/shorewall_script @@ -0,0 +1,53 @@ +#!/bin/sh +# +# Copyright (C) 2009,2013 Mika Ilmaranta <ilmis@nullnet.fi> +# Copyright (C) 2009-2010,2015 Tuomo Soini <tis@foobar.fi> +# +# License: GPLv2 +# + +# +# event handling script for use with shorewall multi-isp setup +# To be able to utilize this script you must have shorewall >= 4.4.23.3 +# + +STATE=${1} +NAME=${2} +CHECKIP=${3} +DEVICE=${4} +WARN_EMAIL=${5} +REPLIED=${6} +WAITING=${7} +TIMEOUT=${8} +REPLY_LATE=${9} +CONS_RCVD=${10} +CONS_WAIT=${11} +CONS_MISS=${12} +AVG_RTT=${13} +SRCIP=${14} +PREVSTATE=${15} +TIMESTAMP=${16} + +DATE=$(date --date=@${TIMESTAMP}) + +if [ ${STATE} = up ]; then + state=0 + action=enable +else + state=1 + action=disable +fi + +VARDIR=$(shorewall show vardir) +VARDIR=${VARDIR:-/var/lib/shorewall} + +echo ${state} > ${VARDIR}/${DEVICE}.status + +if [ -x ${VARDIR}/firewall ]; then + ${VARDIR}/firewall ${action} ${DEVICE} +else + shorewall -q restart +fi + +exit 0 +# diff --git a/signal_handler.c b/signal_handler.c new file mode 100644 index 0000000..8052eba --- /dev/null +++ b/signal_handler.c @@ -0,0 +1,38 @@ +/* + +(C) 2009-2011 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#include <stdio.h> +#include <errno.h> +#include <signal.h> + +#include "globals.h" + +void signal_handler(int signo) { + int errno_save; + + errno_save = errno; + + switch(signo) { + case SIGINT: + set_cont(0); + break; + case SIGUSR1: + set_dump(1); + break; + case SIGUSR2: + set_dump_if_list(1); + break; + case SIGHUP: + set_reload_cfg(1); + break; + } + + errno = errno_save; +} + +/* EOF */ diff --git a/signal_handler.h b/signal_handler.h new file mode 100644 index 0000000..328436c --- /dev/null +++ b/signal_handler.h @@ -0,0 +1,16 @@ +/* + +(C) 2009-2011 Mika Ilmaranta <ilmis@nullnet.fi> + +License: GPLv2 + +*/ + +#ifndef __SIGNAL_HANDLER_H__ +#define __SIGNAL_HANDLER_H__ + +void signal_handler(int signo); + +#endif + +/* EOF */ diff --git a/timecalc.c b/timecalc.c new file mode 100644 index 0000000..cee4e80 --- /dev/null +++ b/timecalc.c @@ -0,0 +1,56 @@ +/* + + (C) 2011 Mika Ilmaranta + +*/ + +#include <syslog.h> + +#include "defs.h" +#include "timecalc.h" + +int timeval_diff_cmp(struct timeval *a, struct timeval *b, int operation, time_t sec, suseconds_t usec) { + time_t diff_sec; + suseconds_t diff_usec; + + diff_sec = a->tv_sec - b->tv_sec; + diff_usec = a->tv_usec - b->tv_usec; + + if(diff_usec < 0) { + diff_sec--; + diff_usec = 1000000L + diff_usec; + } + + switch(operation) { + case TIMEVAL_DIFF_CMP_GT: + if(diff_sec > sec) return(TRUE); + if(diff_sec == sec && diff_usec > usec) return(TRUE); + return(FALSE); + break; + case TIMEVAL_DIFF_CMP_LT: + if(diff_sec < sec) return(TRUE); + if(diff_sec == sec && diff_usec < usec) return(TRUE); + return(FALSE); + break; + default: + syslog(LOG_ERR, "%s: %s: warning: unknown timeval_diff_cmp operation requested %d", __FILE__, __FUNCTION__, operation); + return(FALSE); + } +} + +long timeval_diff(struct timeval *a, struct timeval *b) +{ + return( ((a->tv_sec - b->tv_sec) * 1000000L) + (a->tv_usec - b->tv_usec) ); +} + +void timeval_add(struct timeval *a, time_t sec, suseconds_t usec) +{ + a->tv_sec += sec; + a->tv_usec += usec; + if(a->tv_usec >= 1000000L) { + a->tv_sec++; + a->tv_usec -= 1000000L; + } +} + +/* EOF */ diff --git a/timecalc.h b/timecalc.h new file mode 100644 index 0000000..d3c80ad --- /dev/null +++ b/timecalc.h @@ -0,0 +1,22 @@ +/* + + (C) 2011 Mika Ilmaranta <ilmis@nullnet.fi> + +*/ + +#ifndef __TIMECALC_H__ +#define __TIMECALC_H__ + +#include <time.h> +#include <sys/time.h> + +#define TIMEVAL_DIFF_CMP_GT (0) +#define TIMEVAL_DIFF_CMP_LT (1) + +int timeval_diff_cmp(struct timeval *a, struct timeval *b, int operation, time_t sec, suseconds_t usec); +long timeval_diff(struct timeval *a, struct timeval *b); +void timeval_add(struct timeval *a, time_t sec, suseconds_t usec); + +#endif + +/* EOF */ @@ -0,0 +1,30 @@ +/* + + (C) 2014 Mika Ilmaranta <ilmis@nullnet.fi> + + License: GPLv2 + +*/ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> + +#include "globals.h" +#include "usage.h" + +void usage_and_exit(void) { +#if defined(LSM_VERSION) + printf("%s version %s\n", get_prog(), LSM_VERSION); +#endif + printf("usage: %s\n" + " [-h|--help|-v|--version]\n" + " [-c|--config <config_file>]\n" + " [-p|--pidfile <pid_file>]\n" + " [-f|--no-fork]\n", get_prog()); + printf("check syslog for debug/error messages\n"); + + exit(2); +} + +/* EOF */ @@ -0,0 +1,16 @@ +/* + + (C) 2014 Mika Ilmaranta <ilmis@nullnet.fi> + + License: GPLv2 + +*/ + +#ifndef __USAGE_H__ +#define __USAGE_H__ + +void usage_and_exit(void); + +#endif + +/* EOF */ |
