aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile72
-rw-r--r--README85
-rw-r--r--cksum.c35
-rw-r--r--cksum.h18
-rw-r--r--cmdline.c59
-rw-r--r--cmdline.h16
-rw-r--r--config.c762
-rw-r--r--config.h91
-rw-r--r--default_script67
-rw-r--r--default_script.sample14
-rw-r--r--defs.h29
-rw-r--r--forkexec.c390
-rw-r--r--forkexec.h29
-rw-r--r--globals.c126
-rw-r--r--globals.h36
-rw-r--r--group_script55
-rw-r--r--icmp6_t.c118
-rw-r--r--icmp6_t.h22
-rw-r--r--icmp_t.c340
-rw-r--r--icmp_t.h22
-rw-r--r--lsm.c2162
-rw-r--r--lsm.conf56
-rw-r--r--lsm.conf.sample144
-rw-r--r--lsm.h72
-rw-r--r--lsm.init97
-rw-r--r--lsm.service14
-rw-r--r--lsm.spec883
-rw-r--r--pidfile.c89
-rw-r--r--pidfile.h18
-rw-r--r--plugin_export.c259
-rw-r--r--plugin_export.h28
-rw-r--r--rsyslog-lsm.conf.sample3
-rw-r--r--save_statuses.c93
-rw-r--r--save_statuses.h17
-rw-r--r--shorewall6_script52
-rw-r--r--shorewall_script53
-rw-r--r--signal_handler.c38
-rw-r--r--signal_handler.h16
-rw-r--r--timecalc.c56
-rw-r--r--timecalc.h22
-rw-r--r--usage.c30
-rw-r--r--usage.h16
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
+
+#
diff --git a/README b/README
new file mode 100644
index 0000000..70973d6
--- /dev/null
+++ b/README
@@ -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
diff --git a/cksum.c b/cksum.c
new file mode 100644
index 0000000..b021533
--- /dev/null
+++ b/cksum.c
@@ -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 */
diff --git a/cksum.h b/cksum.h
new file mode 100644
index 0000000..286fc4c
--- /dev/null
+++ b/cksum.h
@@ -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
+#
diff --git a/defs.h b/defs.h
new file mode 100644
index 0000000..945dc56
--- /dev/null
+++ b/defs.h
@@ -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 */
diff --git a/lsm.c b/lsm.c
new file mode 100644
index 0000000..e31aaeb
--- /dev/null
+++ b/lsm.c
@@ -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(&current_time, NULL) == -1) {
+ syslog(LOG_INFO, "gettimeofday failed \"%s\"", strerror(errno));
+ sleep(1);
+ continue;
+ }
+
+ t = cur->data;
+
+ if(timeval_diff_cmp(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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
diff --git a/lsm.h b/lsm.h
new file mode 100644
index 0000000..1728a3b
--- /dev/null
+++ b/lsm.h
@@ -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(&current_time, NULL);
+
+ /* export every 300s */
+ if(timeval_diff_cmp(&current_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 */
diff --git a/usage.c b/usage.c
new file mode 100644
index 0000000..6433488
--- /dev/null
+++ b/usage.c
@@ -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 */
diff --git a/usage.h b/usage.h
new file mode 100644
index 0000000..bab3beb
--- /dev/null
+++ b/usage.h
@@ -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 */