aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile122
-rw-r--r--src/icmp.c359
-rw-r--r--src/icmp.h22
-rw-r--r--src/list.h112
-rw-r--r--src/log.c72
-rw-r--r--src/log.h9
-rw-r--r--src/lua-client.c81
-rw-r--r--src/mtu.c247
-rw-r--r--src/netlink.c291
-rw-r--r--src/netlink.h6
-rw-r--r--src/pingu.c215
-rw-r--r--src/pingu.h6
-rw-r--r--src/pingu_adm.c157
-rw-r--r--src/pingu_adm.h12
-rw-r--r--src/pingu_burst.c67
-rw-r--r--src/pingu_burst.h24
-rw-r--r--src/pingu_conf.c253
-rw-r--r--src/pingu_conf.h6
-rw-r--r--src/pingu_host.c165
-rw-r--r--src/pingu_host.h45
-rw-r--r--src/pingu_iface.c344
-rw-r--r--src/pingu_iface.h62
-rw-r--r--src/pingu_netlink.c873
-rw-r--r--src/pingu_netlink.h17
-rw-r--r--src/pingu_ping.c157
-rw-r--r--src/pingu_ping.h24
-rw-r--r--src/pingu_route.c168
-rw-r--r--src/pingu_route.h34
-rw-r--r--src/pinguctl.c89
-rw-r--r--src/sockaddr_util.c102
-rw-r--r--src/sockaddr_util.h27
-rw-r--r--src/xlib.c37
-rw-r--r--src/xlib.h9
33 files changed, 4214 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..821295e
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,122 @@
+
+-include ../config.mk
+
+BIN_TARGETS = mtu
+SBIN_TARGETS = pingu pinguctl
+
+TARGETS = $(BIN_TARGETS) $(SBIN_TARGETS) $(LUA_TARGETS)
+
+prefix ?= /usr/local
+exec_prefix ?= $(prefix)
+bindir ?= $(exec_prefix)/bin
+sbindir ?= $(exec_prefix)/sbin
+sysconfdir ?= $(prefix)/etc
+localstatedir ?= $(prefix)/var
+libdir ?= $(exec_prefix)/lib
+datarootdir ?= $(prefix)/share
+
+rundir ?= $(localstatedir)/run
+
+pingustatedir = $(rundir)/pingu
+
+DESTDIR ?=
+
+INSTALL = install
+INSTALLDIR = $(INSTALL) -d
+PKG_CONFIG ?= pkg-config
+
+ifdef LUAPC
+LUA_TARGETS := client.so
+INSTALL_LUA_TARGET := install-lua
+LUA_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(LUAPC))
+LUA_VERSION ?= $(shell $(PKG_CONFIG) --variable V $(LUAPC))
+
+luasharedir := $(datarootdir)/lua/$(LUA_VERSION)
+lualibdir := $(libdir)/lua/$(LUA_VERSION)
+
+endif
+
+SUBDIRS :=
+
+CFLAGS ?= -g
+CFLAGS += -I../
+CFLAGS += -DPINGU_VERSION=\"$(PINGU_VERSION)\"
+CFLAGS += -Wall -Wstrict-prototypes -D_GNU_SOURCE -std=gnu99
+CFLAGS += -DDEFAULT_PIDFILE=\"$(pingustatedir)/pingu.pid\"
+CFLAGS += -DDEFAULT_CONFIG=\"$(sysconfdir)/pingu/pingu.conf\"
+CFLAGS += -DDEFAULT_ADM_client=\"$(pingustatedir)/pingu.ctl\"
+
+pingu_OBJS = \
+ icmp.o \
+ log.o \
+ pingu.o \
+ pingu_adm.o \
+ pingu_burst.o \
+ pingu_conf.o \
+ pingu_host.o \
+ pingu_iface.o \
+ pingu_netlink.o \
+ pingu_ping.o \
+ pingu_route.o \
+ sockaddr_util.o \
+ xlib.o
+
+pingu_LIBS = -lev
+
+pinguctl_OBJS = \
+ log.o \
+ pinguctl.o
+
+pinguctl_LIBS =
+
+mtu_OBJS = \
+ mtu.o \
+ netlink.o \
+ icmp.o
+
+lua-client.o_CFLAGS = $(LUA_CFLAGS)
+client.so_OBJS = \
+ lua-client.o
+
+client.so_LDFLAGS = -shared
+
+ALL_OBJS= $(pingu_OBJS) $(pinguctl_OBJS) $(mtu_OBJS) $(client.so_OBJS)
+
+all: $(TARGETS)
+
+%.o: %.c
+ $(CC) $(CFLAGS) $($@_CFLAGS) -c $<
+
+$(TARGETS):
+ $(CC) $(LDFLAGS) $($@_LDFLAGS) $($@_OBJS) $($@_LIBS) -o $@
+
+pingu: $(pingu_OBJS)
+pinguctl: $(pinguctl_OBJS)
+client.so: $(client.so_OBJS)
+mtu: $(mtu_OBJS)
+
+$(SUBDIRS):
+ $(MAKE) -C $@
+
+install: $(TARGETS) $(INSTALL_LUA_TARGET)
+ $(INSTALLDIR) $(DESTDIR)/$(bindir) $(DESTDIR)/$(sbindir) \
+ $(DESTDIR)/$(pingustatedir)
+ $(INSTALL) $(BIN_TARGETS) $(DESTDIR)/$(bindir)
+ $(INSTALL) $(SBIN_TARGETS) $(DESTDIR)/$(sbindir)
+ for dir in $(SUBDIRS); do \
+ $(MAKE) -C $$dir $@ || break; \
+ done
+
+install-lua: client.so pingu.lua
+ $(INSTALLDIR) $(DESTDIR)$(luasharedir) \
+ $(DESTDIR)$(lualibdir)/pingu
+ $(INSTALL) pingu.lua $(DESTDIR)$(luasharedir)/
+ $(INSTALL) client.so $(DESTDIR)$(lualibdir)/pingu/
+
+clean:
+ rm -f $(TARGETS) $(ALL_OBJS)
+ for dir in $(SUBDIRS); do \
+ $(MAKE) -C $$dir $@ || break; \
+ done
+
+.PHONY: $(SUBDIRS)
diff --git a/src/icmp.c b/src/icmp.c
new file mode 100644
index 0000000..eb1ec5c
--- /dev/null
+++ b/src/icmp.c
@@ -0,0 +1,359 @@
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <asm/types.h>
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+
+#include "icmp.h"
+
+static char *pr_addr(__u32 addr)
+{
+ struct hostent *hp;
+ static char buf[4096];
+
+ sprintf(buf, "%s", inet_ntoa(*(struct in_addr *)&addr));
+ return buf;
+}
+
+static void pr_icmph(__u8 type, __u8 code, __u32 info, struct icmphdr *icp)
+{
+ switch (type) {
+ case ICMP_ECHOREPLY:
+ printf("Echo Reply\n");
+ /* XXX ID + Seq + Data */
+ break;
+ case ICMP_DEST_UNREACH:
+ switch(code) {
+ case ICMP_NET_UNREACH:
+ printf("Destination Net Unreachable\n");
+ break;
+ case ICMP_HOST_UNREACH:
+ printf("Destination Host Unreachable\n");
+ break;
+ case ICMP_PROT_UNREACH:
+ printf("Destination Protocol Unreachable\n");
+ break;
+ case ICMP_PORT_UNREACH:
+ printf("Destination Port Unreachable\n");
+ break;
+ case ICMP_FRAG_NEEDED:
+ printf("Frag needed and DF set (mtu = %u)\n", info);
+ break;
+ case ICMP_SR_FAILED:
+ printf("Source Route Failed\n");
+ break;
+ case ICMP_PKT_FILTERED:
+ printf("Packet filtered\n");
+ break;
+ default:
+ printf("Dest Unreachable, Bad Code: %d\n", code);
+ break;
+ }
+ break;
+ case ICMP_SOURCE_QUENCH:
+ printf("Source Quench\n");
+ break;
+ case ICMP_REDIRECT:
+ switch(code) {
+ case ICMP_REDIR_NET:
+ printf("Redirect Network");
+ break;
+ case ICMP_REDIR_HOST:
+ printf("Redirect Host");
+ break;
+ case ICMP_REDIR_NETTOS:
+ printf("Redirect Type of Service and Network");
+ break;
+ case ICMP_REDIR_HOSTTOS:
+ printf("Redirect Type of Service and Host");
+ break;
+ default:
+ printf("Redirect, Bad Code: %d", code);
+ break;
+ }
+ if (icp)
+ printf("(New nexthop: %s)\n", pr_addr(icp->un.gateway));
+ break;
+ case ICMP_ECHO:
+ printf("Echo Request\n");
+ /* XXX ID + Seq + Data */
+ break;
+ case ICMP_TIME_EXCEEDED:
+ switch(code) {
+ case ICMP_EXC_TTL:
+ printf("Time to live exceeded\n");
+ break;
+ case ICMP_EXC_FRAGTIME:
+ printf("Frag reassembly time exceeded\n");
+ break;
+ default:
+ printf("Time exceeded, Bad Code: %d\n", code);
+ break;
+ }
+ break;
+ case ICMP_PARAMETERPROB:
+ printf("Parameter problem: pointer = %u\n", icp ? (ntohl(icp->un.gateway)>>24) : info);
+ break;
+ case ICMP_TIMESTAMP:
+ printf("Timestamp\n");
+ /* XXX ID + Seq + 3 timestamps */
+ break;
+ case ICMP_TIMESTAMPREPLY:
+ printf("Timestamp Reply\n");
+ /* XXX ID + Seq + 3 timestamps */
+ break;
+ case ICMP_INFO_REQUEST:
+ printf("Information Request\n");
+ /* XXX ID + Seq */
+ break;
+ case ICMP_INFO_REPLY:
+ printf("Information Reply\n");
+ /* XXX ID + Seq */
+ break;
+#ifdef ICMP_MASKREQ
+ case ICMP_MASKREQ:
+ printf("Address Mask Request\n");
+ break;
+#endif
+#ifdef ICMP_MASKREPLY
+ case ICMP_MASKREPLY:
+ printf("Address Mask Reply\n");
+ break;
+#endif
+ default:
+ printf("Bad ICMP type: %d\n", type);
+ }
+}
+
+static u_short in_cksum(const u_short *addr, register int len, u_short csum)
+{
+ const u_short *w = addr;
+ u_short answer;
+ int sum = csum, nleft = len;
+
+ while (nleft > 1) {
+ sum += *w++;
+ nleft -= 2;
+ }
+
+ if (nleft == 1)
+ sum += htons(*(u_char *)w << 8);
+
+ sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
+ sum += (sum >> 16); /* add carry */
+ answer = ~sum; /* truncate to 16 bits */
+
+ return answer;
+}
+
+
+int icmp_parse_reply(__u8 *buf, int len, int seq,
+ struct sockaddr *addr,
+ struct sockaddr *origdest)
+{
+ struct sockaddr_in *from = (struct sockaddr_in *) addr;
+ struct sockaddr_in *to = (struct sockaddr_in *) origdest;
+ struct icmphdr *icp;
+ struct iphdr *ip;
+ int hlen, csfailed;
+
+ /* Check the IP header */
+ ip = (struct iphdr *) buf;
+ hlen = ip->ihl * 4;
+ if (len < hlen + 8 || ip->ihl < 5)
+ return 1;
+
+ /* Now the ICMP part */
+ len -= hlen;
+ icp = (struct icmphdr *)(buf + hlen);
+ csfailed = in_cksum((u_short *)icp, len, 0);
+
+ if (icp->type == ICMP_ECHOREPLY) {
+ if (icp->un.echo.id != getpid() ||
+ ntohs(icp->un.echo.sequence) != seq)
+ return 1; /* 'Twas not our ECHO */
+
+ printf("From %s: icmp_seq=%u bytes=%d\n",
+ pr_addr(from->sin_addr.s_addr),
+ ntohs(icp->un.echo.sequence), len);
+ } else {
+ /* We fall here when a redirect or source quench arrived.
+ * Also this branch processes icmp errors, when IP_RECVERR
+ * is broken. */
+
+ switch (icp->type) {
+ case ICMP_ECHO:
+ /* MUST NOT */
+ return 1;
+ case ICMP_SOURCE_QUENCH:
+ case ICMP_REDIRECT:
+ case ICMP_DEST_UNREACH:
+ case ICMP_TIME_EXCEEDED:
+ case ICMP_PARAMETERPROB:
+ {
+ struct iphdr * iph = (struct iphdr *)(&icp[1]);
+ struct icmphdr *icp1 = (struct icmphdr*)((unsigned char *)iph + iph->ihl*4);
+ int error_pkt;
+ if (len < 8 + sizeof(struct iphdr) + 8 ||
+ len < 8 + iph->ihl * 4 + 8)
+ return 1;
+ if (icp1->type != ICMP_ECHO ||
+ iph->daddr != to->sin_addr.s_addr ||
+ icp1->un.echo.id != getpid() ||
+ ntohs(icp1->un.echo.sequence) != seq)
+ return 1;
+ error_pkt = (icp->type != ICMP_REDIRECT &&
+ icp->type != ICMP_SOURCE_QUENCH);
+ if (error_pkt) {
+ //acknowledge(ntohs(icp1->un.echo.sequence));
+ }
+
+ printf("From %s: icmp_seq=%u ",
+ pr_addr(from->sin_addr.s_addr),
+ ntohs(icp1->un.echo.sequence));
+ if (csfailed)
+ printf("(BAD CHECKSUM)");
+ pr_icmph(icp->type, icp->code, ntohl(icp->un.gateway), icp);
+ return !error_pkt;
+ }
+ default:
+ /* MUST NOT */
+ break;
+ }
+ printf("From %s: ", pr_addr(from->sin_addr.s_addr));
+ pr_icmph(icp->type, icp->code, ntohl(icp->un.gateway), icp);
+ return 0;
+ }
+
+ return 0;
+}
+
+int icmp_send(int fd, struct sockaddr *to, int tolen, void *buf, int buflen)
+{
+ int i;
+
+ i = sendto(fd, buf, buflen, 0, to, tolen);
+ if (i != buflen)
+ return -1;
+
+ return 0;
+}
+
+int icmp_send_frag_needed(int fd, struct sockaddr *to, int tolen,
+ struct iphdr *iph, int newmtu)
+{
+ struct sockaddr_in *to_in = (struct sockaddr_in *) to;
+ const int len = sizeof(struct icmphdr) + sizeof(struct iphdr) + 8;
+ char packet[len];
+ struct icmphdr *icp;
+
+ icp = (struct icmphdr *) packet;
+ icp->type = ICMP_DEST_UNREACH;
+ icp->code = ICMP_FRAG_NEEDED;
+ icp->checksum = 0;
+ icp->un.frag.__unused = 0;
+ icp->un.frag.mtu = htons(newmtu);
+
+ /* copy ip header + 64-bits of original packet */
+ memcpy(icp + 1, iph, sizeof(struct iphdr) + 8);
+
+ icp->checksum = in_cksum((u_short *) icp, len, 0);
+
+ printf("To %s: frag_needed mtu=%d\n",
+ pr_addr(to_in->sin_addr.s_addr), newmtu);
+
+ return icmp_send(fd, to, tolen, packet, len);
+}
+
+int icmp_send_ping(int fd, struct sockaddr *to, int tolen,
+ int seq, int total_size)
+{
+ struct sockaddr_in *to_in = (struct sockaddr_in *) to;
+ __u8 packet[1500];
+ struct icmphdr *icp;
+ int len;
+
+ if (total_size > sizeof(packet))
+ return -1;
+ if (total_size < sizeof(struct iphdr) + sizeof(struct icmphdr))
+ total_size = sizeof(struct iphdr) + sizeof(struct icmphdr);
+
+ len = total_size - sizeof(struct iphdr);
+ memset(packet, 0, sizeof(packet));
+
+ icp = (struct icmphdr *) packet;
+ icp->type = ICMP_ECHO;
+ icp->code = 0;
+ icp->checksum = 0;
+ icp->un.echo.sequence = htons(seq);
+ icp->un.echo.id = getpid();
+ icp->checksum = in_cksum((u_short *) icp, len, 0);
+#if 0
+ printf("To %s: icmp_seq=%u bytes=%d\n",
+ pr_addr(to_in->sin_addr.s_addr), seq, len);
+#endif
+ return icmp_send(fd, to, tolen, (void *) packet, len);
+}
+
+int icmp_read_reply(int fd, struct sockaddr *from, int fromlen,
+ __u8 *buf, int buflen)
+{
+ struct iovec iov;
+ int len;
+
+ len = recvfrom(fd, buf, buflen, 0, from, &fromlen);
+ if (len < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+ return -1;
+ }
+
+ return len;
+}
+
+int icmp_open(float timeout)
+{
+ const int pmtudisc = IP_PMTUDISC_DO, yes = 1;
+ struct timeval tv;
+ int fd;
+
+ fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+ if (fd < 0) {
+ perror("mtuinject: socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)");
+ goto err;
+ }
+
+ if (setsockopt(fd, SOL_IP, IP_MTU_DISCOVER,
+ &pmtudisc, sizeof(pmtudisc)) == -1) {
+ perror("ping: IP_MTU_DISCOVER");
+ goto err_close;
+ }
+
+ tv.tv_sec = (time_t) timeout;
+ tv.tv_usec = (timeout - tv.tv_sec) * 1000000;
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(tv));
+
+ tv.tv_sec = (time_t) timeout;
+ tv.tv_usec = (timeout - tv.tv_sec) * 1000000;
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO,
+ (char*)&tv, sizeof(tv)) == -1)
+ goto err_close;
+
+ return fd;
+
+err_close:
+ close(fd);
+err:
+ return -1;
+}
+
+void icmp_close(int fd)
+{
+ close(fd);
+}
+
diff --git a/src/icmp.h b/src/icmp.h
new file mode 100644
index 0000000..0fc94e1
--- /dev/null
+++ b/src/icmp.h
@@ -0,0 +1,22 @@
+#ifndef PINGU_ICMP_H
+#define PINGU_ICMP_H
+
+#include <asm/types.h>
+#include <sys/socket.h>
+#include <netinet/ip.h>
+
+int icmp_parse_reply(__u8 *buf, int len, int seq,
+ struct sockaddr *addr,
+ struct sockaddr *origdest);
+int icmp_send(int fd, struct sockaddr *to, int tolen, void *buf, int buflen);
+int icmp_send_frag_needed(int fd, struct sockaddr *to, int tolen,
+ struct iphdr *iph, int newmtu);
+int icmp_send_ping(int fd, struct sockaddr *to, int tolen,
+ int seq, int total_size);
+int icmp_read_reply(int fd, struct sockaddr *from, int fromlen,
+ __u8 *buf, int buflen);
+int icmp_open(float timeout);
+void icmp_close(int fd);
+
+
+#endif
diff --git a/src/list.h b/src/list.h
new file mode 100644
index 0000000..1fca5e7
--- /dev/null
+++ b/src/list.h
@@ -0,0 +1,112 @@
+/* list.h - Single and double linked list macros
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 or later as
+ * published by the Free Software Foundation.
+ *
+ * See http://www.gnu.org/ for details.
+ *
+ * This is more or less based on the code in the linux kernel. There are
+ * minor differences and this is only a subset of the kernel version.
+ */
+
+#ifndef LIST_H
+#define LIST_H
+
+#ifndef NULL
+#define NULL 0L
+#endif
+
+#ifndef offsetof
+#ifdef __compiler_offsetof
+#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
+#else
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+#endif
+#endif
+
+#ifndef container_of
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+#endif
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+#define LIST_INITIALIZER(l) { .next = &l, .prev = &l }
+
+static inline void list_init(struct list_head *list)
+{
+ list->next = list;
+ list->prev = list;
+}
+
+static inline void __list_add(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head, head->next);
+}
+
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head->prev, head);
+}
+
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+static inline void list_del(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ entry->next = NULL;
+ entry->prev = NULL;
+}
+
+static inline int list_hashed(const struct list_head *n)
+{
+ return n->next != n && n->next != NULL;
+}
+
+static inline int list_empty(const struct list_head *n)
+{
+ return !list_hashed(n);
+}
+
+#define list_next(ptr, type, member) \
+ (list_hashed(ptr) ? container_of((ptr)->next,type,member) : NULL)
+
+#define list_entry(ptr, type, member) container_of(ptr,type,member)
+
+#define list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+#define list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, n = pos->next)
+
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member))
+
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member), \
+ n = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+#endif
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..85a32e6
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,72 @@
+/* log.c - Logging via syslog
+ * copied from opennhrp
+ *
+ * Copyright (C) 2007 Timo Teräs <timo.teras@iki.fi>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 or later as
+ * published by the Free Software Foundation.
+ *
+ * See http://www.gnu.org/ for details.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <stdarg.h>
+
+#include "pingu.h"
+#include "log.h"
+
+static int log_verbose = 0;
+
+void log_init(const char* prefix, int verbose)
+{
+ log_verbose = verbose;
+ openlog(prefix, LOG_PERROR | LOG_PID, LOG_DAEMON);
+}
+
+void log_debug(const char *format, ...)
+{
+ va_list va;
+
+ if (log_verbose) {
+ va_start(va, format);
+ vsyslog(LOG_DEBUG, format, va);
+ va_end(va);
+ }
+}
+
+void log_perror(const char *message)
+{
+ log_error("%s: %s", message, strerror(errno));
+}
+
+void log_error(const char *format, ...)
+{
+ va_list va;
+
+ va_start(va, format);
+ vsyslog(LOG_ERR, format, va);
+ va_end(va);
+}
+
+void log_info(const char *format, ...)
+{
+ va_list va;
+
+ va_start(va, format);
+ vsyslog(LOG_INFO, format, va);
+ va_end(va);
+}
+
+void log_warning(const char *format, ...)
+{
+ va_list va;
+
+ va_start(va, format);
+ vsyslog(LOG_WARNING, format, va);
+ va_end(va);
+}
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..a7d4688
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,9 @@
+#ifndef LOG_H
+#define LOG_H
+void log_init(const char* prefix, int verbose);
+void log_debug(const char *format, ...);
+void log_perror(const char *message);
+void log_error(const char *format, ...);
+void log_info(const char *format, ...);
+void log_warning(const char *format, ...);
+#endif
diff --git a/src/lua-client.c b/src/lua-client.c
new file mode 100644
index 0000000..ad18f30
--- /dev/null
+++ b/src/lua-client.c
@@ -0,0 +1,81 @@
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "pingu_adm.h"
+
+#define LIBNAME "pingu.client"
+
+#if LUA_VERSION_NUM < 502
+# define luaL_newlib(L,l) (lua_newtable(L), luaL_register(L,NULL,l))
+#endif
+
+static int pusherror(lua_State *L, const char *info)
+{
+ lua_pushnil(L);
+ if (info == NULL)
+ lua_pushstring(L, strerror(errno));
+ else
+ lua_pushfstring(L, "%s: %s", info, strerror(errno));
+ lua_pushinteger(L, errno);
+ return 3;
+}
+
+static int pushfile(lua_State *L, int fd, const char *mode)
+{
+ FILE **f = (FILE **)lua_newuserdata(L, sizeof(FILE *));
+ *f = NULL;
+ luaL_getmetatable(L, "FILE*");
+ lua_setmetatable(L, -2);
+ *f = fdopen(fd, mode);
+ return (*f != NULL);
+}
+
+static int Padm_open(lua_State *L)
+{
+ const char *socket_path = luaL_optstring(L, 1, DEFAULT_ADM_SOCKET);
+ struct sockaddr_un sun;
+ int fd, ret;
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strncpy(sun.sun_path, socket_path, sizeof(sun.sun_path));
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ return pusherror(L, "socket");
+
+ if (connect(fd, (struct sockaddr *) &sun, sizeof(sun)) < 0) {
+ ret = pusherror(L, socket_path);
+ goto close_err;
+ }
+
+ return pushfile(L, fd, "r+");
+
+close_err:
+ close(fd);
+ return ret;
+}
+
+static const luaL_Reg reg_pingu_methods[] = {
+ {"open", Padm_open},
+ {NULL, NULL},
+};
+
+
+LUALIB_API int luaopen_pingu_client(lua_State *L)
+{
+ luaL_newlib(L, reg_pingu_methods);
+ lua_pushliteral(L, "version");
+ lua_pushliteral(L, PINGU_VERSION);
+ lua_settable(L, -3);
+ return 1;
+}
diff --git a/src/mtu.c b/src/mtu.c
new file mode 100644
index 0000000..77766eb
--- /dev/null
+++ b/src/mtu.c
@@ -0,0 +1,247 @@
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <linux/if.h>
+#include <linux/sockios.h>
+#include <netinet/ip_icmp.h>
+
+#include "icmp.h"
+
+static int fd, mtu_size;
+static struct sockaddr_in to;
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "usage: mtu -i <mtu-size> <host>\n"
+ " mtu -I <host>\n"
+ " mtu -d <host>\n"
+ " mtu -D <host>\n"
+ "\n"
+ " -i <mtu-size> Inject <mtu-size> as PMTU to <host>\n"
+ " -I Inject local PMTU as PMTU to <host>\n"
+ " -d Discover PMTU to <host>\n"
+ " -D Discover PMTU to <host> and assign it to interface MTU\n"
+ "\n");
+ exit(3);
+}
+
+static int do_ping(int seq, int size)
+{
+ __u8 buf[1500];
+ struct iphdr *ip = (struct iphdr *) buf;
+ struct icmphdr *icp;
+ struct sockaddr_in from;
+ int len, retry;
+
+ for (retry = 0; retry < 3; retry++) {
+ icmp_send_ping(fd, (struct sockaddr *) &to, sizeof(to),
+ seq, size);
+
+not_mine:
+ if ((len = icmp_read_reply(fd, (struct sockaddr *) &from,
+ sizeof(from), buf, sizeof(buf))) <= 0)
+ continue;
+
+ if (icmp_parse_reply(buf, len, seq,
+ (struct sockaddr *) &from,
+ (struct sockaddr *) &to))
+ goto not_mine;
+
+ icp = (struct icmphdr *) &buf[ip->ihl * 4];
+ switch (icp->type) {
+ case ICMP_ECHOREPLY:
+ return 0;
+ case ICMP_DEST_UNREACH:
+ if (icp->code != ICMP_FRAG_NEEDED)
+ return 0;
+ return ntohs(icp->un.frag.mtu);
+ default:
+ return -1;
+ }
+ }
+
+ return -1;
+}
+
+static int discover_mtu(void)
+{
+ int seq = 1, low_mtu, high_mtu, try_mtu, r;
+
+ /* Check if the host is up */
+ if (do_ping(seq++, 0) < 0)
+ return -1;
+
+ /* Discover PMTU */
+ low_mtu = 68/2;
+ high_mtu = 1500/2;
+ try_mtu = 1500/2;
+ while (1) {
+ r = do_ping(seq++, try_mtu * 2);
+ if (r > 0 && r < try_mtu * 2) {
+ /* pmtu */
+ high_mtu = r/2;
+ try_mtu = high_mtu;
+ continue;
+ }
+ if (r == 0)
+ low_mtu = try_mtu;
+ else
+ high_mtu = try_mtu - 1;
+ if (low_mtu >= high_mtu)
+ return 2 * low_mtu;
+
+ try_mtu = low_mtu + (high_mtu - low_mtu + 1) / 2;
+ }
+}
+
+static void do_discover(void)
+{
+ int mtu;
+
+ mtu = discover_mtu();
+ if (mtu > 0)
+ fprintf(stdout, "%d\n", mtu);
+ else
+ fprintf(stderr, "Host is not up\n");
+}
+
+static int set_mtu(const char *dev, int mtu)
+{
+ struct ifreq ifr;
+ int fd;
+
+ fd = socket(PF_INET, SOCK_DGRAM, 0);
+ if (fd < 0)
+ return -1;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, dev, IFNAMSIZ);
+ ifr.ifr_mtu = mtu;
+ if (ioctl(fd, SIOCSIFMTU, &ifr) < 0) {
+ perror("SIOCSIFMTU");
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
+
+static void do_discover_and_write(void)
+{
+ int mtu;
+ char iface[IFNAMSIZ];
+
+ mtu = discover_mtu();
+ if (mtu < 0) {
+ fprintf(stderr, "Failed to determine MTU\n");
+ return;
+ }
+
+ if (!netlink_route_get(&to, NULL, iface)) {
+ fprintf(stderr, "Failed to determine route interface\n");
+ return;
+ }
+
+ printf("Writing %d to %s\n", mtu, iface);
+ set_mtu(iface, mtu);
+}
+
+static void do_inject(void)
+{
+ __u8 buf[1500];
+ struct sockaddr_in from;
+ int len, i, seq = 0;
+
+ icmp_send_ping(fd, (struct sockaddr *) &to, sizeof(to),
+ ++seq, mtu_size);
+ for (i = 0; i < 5; i++) {
+ if ((len = icmp_read_reply(fd, (struct sockaddr *) &from,
+ sizeof(from),
+ buf, sizeof(buf))) <= 0)
+ continue;
+
+ if (icmp_parse_reply(buf, len, seq,
+ (struct sockaddr *) &from,
+ (struct sockaddr *) &to))
+ continue;
+
+ if (seq != 1)
+ sleep(1);
+
+ icmp_send_ping(fd, (struct sockaddr *) &to, sizeof(to),
+ ++seq, mtu_size);
+ icmp_send_frag_needed(fd, (struct sockaddr *) &to, sizeof(to),
+ (struct iphdr *) buf, mtu_size - 2);
+ }
+}
+
+static void do_inject_pmtu(void)
+{
+ u_int16_t mtu;
+
+ if (!netlink_route_get(&to, &mtu, NULL)) {
+ fprintf(stderr, "Failed to determine Path MTU\n");
+ return;
+ }
+ if (mtu == 1500)
+ return;
+
+ mtu_size = mtu;
+ do_inject();
+}
+
+int main(int argc, char **argv)
+{
+ struct hostent *hp;
+ void (*action)(void) = NULL;
+ char *target;
+ int opt;
+
+ while ((opt = getopt(argc, argv, "DdIi:")) != -1) {
+ switch (opt) {
+ case 'D':
+ action = do_discover_and_write;
+ break;
+ case 'd':
+ action = do_discover;
+ break;
+ case 'i':
+ action = do_inject;
+ mtu_size = atoi(optarg);
+ break;
+ case 'I':
+ action = do_inject_pmtu;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ if (action == NULL || optind >= argc)
+ usage();
+
+ target = argv[optind];
+
+ fd = icmp_open(1.0);
+ if (fd < 0)
+ exit(1);
+
+ memset((char *) &to, 0, sizeof(to));
+ to.sin_family = AF_INET;
+ if (inet_aton(target, &to.sin_addr) != 1) {
+ hp = gethostbyname(target);
+ if (!hp) {
+ fprintf(stderr, "mtu: unknown host %s\n", target);
+ exit(2);
+ }
+ memcpy(&to.sin_addr, hp->h_addr, 4);
+ }
+
+ action();
+}
+
diff --git a/src/netlink.c b/src/netlink.c
new file mode 100644
index 0000000..5e43116
--- /dev/null
+++ b/src/netlink.c
@@ -0,0 +1,291 @@
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <asm/types.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/ip.h>
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#define NLMSG_TAIL(nmsg) \
+ ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+#define NDA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg))))
+#define NDA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndmsg))
+
+struct netlink_fd {
+ int fd;
+ __u32 seq;
+};
+
+static void netlink_parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
+{
+ memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
+ while (RTA_OK(rta, len)) {
+ if (rta->rta_type <= max)
+ tb[rta->rta_type] = rta;
+ rta = RTA_NEXT(rta,len);
+ }
+}
+
+static int netlink_add_rtattr_l(struct nlmsghdr *n, int maxlen, int type,
+ const void *data, int alen)
+{
+ int len = RTA_LENGTH(alen);
+ struct rtattr *rta;
+
+ if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen)
+ return FALSE;
+
+ rta = NLMSG_TAIL(n);
+ rta->rta_type = type;
+ rta->rta_len = len;
+ memcpy(RTA_DATA(rta), data, alen);
+ n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
+ return TRUE;
+}
+
+static int netlink_add_rtaddr_l(struct nlmsghdr *n, int maxlen, int type,
+ const struct sockaddr *addr)
+{
+ switch (addr->sa_family) {
+ case AF_INET: {
+ struct sockaddr_in *sin = (struct sockaddr_in *) addr;
+ return netlink_add_rtattr_l(n, maxlen, type, &sin->sin_addr,
+ sizeof(sin->sin_addr));
+ }
+ default:
+ return FALSE;
+ }
+}
+
+static void netlink_close(struct netlink_fd *fd)
+{
+ if (fd->fd >= 0) {
+ close(fd->fd);
+ fd->fd = 0;
+ }
+}
+
+static int netlink_open(struct netlink_fd *fd)
+{
+ int buf = 16 * 1024;
+
+ fd->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ fd->seq = time(NULL);
+ if (fd->fd < 0) {
+ perror("Cannot open netlink socket");
+ return FALSE;
+ }
+
+ fcntl(fd->fd, F_SETFD, FD_CLOEXEC);
+ if (setsockopt(fd->fd, SOL_SOCKET, SO_SNDBUF, &buf, sizeof(buf)) < 0) {
+ perror("SO_SNDBUF");
+ goto error;
+ }
+
+ if (setsockopt(fd->fd, SOL_SOCKET, SO_RCVBUF, &buf, sizeof(buf)) < 0) {
+ perror("SO_RCVBUF");
+ goto error;
+ }
+ return TRUE;
+
+error:
+ netlink_close(fd);
+ return FALSE;
+}
+
+static int netlink_receive(struct netlink_fd *fd, struct nlmsghdr *reply)
+{
+ struct sockaddr_nl nladdr;
+ struct iovec iov;
+ struct msghdr msg = {
+ .msg_name = &nladdr,
+ .msg_namelen = sizeof(nladdr),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ int got_reply = FALSE, len;
+ char buf[16*1024];
+
+ iov.iov_base = buf;
+ while (!got_reply) {
+ int status;
+ struct nlmsghdr *h;
+
+ iov.iov_len = sizeof(buf);
+ status = recvmsg(fd->fd, &msg, MSG_DONTWAIT);
+ if (status < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno == EAGAIN)
+ return reply == NULL;
+ fprintf(stderr, "Netlink overrun\n");
+ continue;
+ }
+
+ if (status == 0) {
+ fprintf(stderr, "Netlink returned EOF\n");
+ return FALSE;
+ }
+
+ h = (struct nlmsghdr *) buf;
+ while (NLMSG_OK(h, status)) {
+ if (reply != NULL &&
+ h->nlmsg_seq == reply->nlmsg_seq) {
+ len = h->nlmsg_len;
+ if (len > reply->nlmsg_len) {
+ fprintf(stderr, "Netlink message "
+ "truncated\n");
+ len = reply->nlmsg_len;
+ }
+ memcpy(reply, h, len);
+ got_reply = TRUE;
+ } else if (h->nlmsg_type != NLMSG_DONE) {
+ fprintf(stderr,
+ "Unknown NLmsg: 0x%08x, len %d\n",
+ h->nlmsg_type, h->nlmsg_len);
+ }
+ h = NLMSG_NEXT(h, status);
+ }
+ }
+
+ return TRUE;
+}
+
+static int netlink_send(struct netlink_fd *fd, struct nlmsghdr *req)
+{
+ struct sockaddr_nl nladdr;
+ struct iovec iov = {
+ .iov_base = (void*) req,
+ .iov_len = req->nlmsg_len
+ };
+ struct msghdr msg = {
+ .msg_name = &nladdr,
+ .msg_namelen = sizeof(nladdr),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ int status;
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+
+ req->nlmsg_seq = ++fd->seq;
+
+ status = sendmsg(fd->fd, &msg, 0);
+ if (status < 0) {
+ fprintf(stderr, "Cannot talk to rtnetlink\n");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int netlink_talk(struct nlmsghdr *req, size_t replysize,
+ struct nlmsghdr *reply)
+{
+ struct netlink_fd fd;
+ int ret = FALSE;
+
+ if (!netlink_open(&fd))
+ return FALSE;
+
+ if (reply == NULL)
+ req->nlmsg_flags |= NLM_F_ACK;
+
+ if (!netlink_send(&fd, req))
+ goto out;
+
+ if (reply != NULL) {
+ reply->nlmsg_len = replysize;
+ ret = netlink_receive(&fd, reply);
+ } else {
+ ret = TRUE;
+ }
+out:
+ netlink_close(&fd);
+ return ret;
+}
+
+int netlink_route_get(struct sockaddr *dst, u_int16_t *mtu, char *ifname)
+{
+ struct {
+ struct nlmsghdr n;
+ union {
+ struct rtmsg r;
+ struct ifinfomsg i;
+ };
+ char buf[1024];
+ } req;
+ struct rtmsg *r = NLMSG_DATA(&req.n);
+ struct rtattr *rta[RTA_MAX+1];
+ struct rtattr *rtax[RTAX_MAX+1];
+ struct rtattr *ifla[IFLA_MAX+1];
+ int index;
+
+ memset(&req, 0, sizeof(req));
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.n.nlmsg_type = RTM_GETROUTE;
+ req.r.rtm_family = dst->sa_family;
+
+ netlink_add_rtaddr_l(&req.n, sizeof(req), RTA_DST, dst);
+ req.r.rtm_dst_len = 32;
+
+ if (!netlink_talk(&req.n, sizeof(req), &req.n))
+ return FALSE;
+
+ netlink_parse_rtattr(rta, RTA_MAX, RTM_RTA(r),
+ RTM_PAYLOAD(&req.n));
+
+ if (mtu != NULL) {
+ if (rta[RTA_METRICS] == NULL)
+ return FALSE;
+
+ netlink_parse_rtattr(rtax, RTAX_MAX,
+ RTA_DATA(rta[RTA_METRICS]),
+ RTA_PAYLOAD(rta[RTA_METRICS]));
+ if (rtax[RTAX_MTU] == NULL)
+ return FALSE;
+
+ *mtu = *(int*) RTA_DATA(rtax[RTAX_MTU]);
+ }
+
+ if (ifname != NULL) {
+ if (rta[RTA_OIF] == NULL)
+ return FALSE;
+
+ index = *(int*) RTA_DATA(rta[RTA_OIF]);
+
+ memset(&req, 0, sizeof(req));
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.n.nlmsg_type = RTM_GETLINK;
+ req.i.ifi_index = index;
+ if (!netlink_talk(&req.n, sizeof(req), &req.n))
+ return FALSE;
+
+ netlink_parse_rtattr(ifla, IFLA_MAX, IFLA_RTA(r),
+ IFLA_PAYLOAD(&req.n));
+ if (ifla[IFLA_IFNAME] == NULL)
+ return FALSE;
+
+ memcpy(ifname, RTA_DATA(ifla[IFLA_IFNAME]),
+ RTA_PAYLOAD(ifla[IFLA_IFNAME]));
+ }
+
+ return TRUE;
+}
diff --git a/src/netlink.h b/src/netlink.h
new file mode 100644
index 0000000..4e2ceb2
--- /dev/null
+++ b/src/netlink.h
@@ -0,0 +1,6 @@
+#ifndef PINGU_NETLINK_H
+#define PINGU_NETLINK_H
+
+int netlink_route_get(struct sockaddr *dst, u_int16_t *mtu, char *ifname);
+
+#endif
diff --git a/src/pingu.c b/src/pingu.c
new file mode 100644
index 0000000..badd735
--- /dev/null
+++ b/src/pingu.c
@@ -0,0 +1,215 @@
+
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <ev.h>
+
+#include "log.h"
+
+#include "pingu_adm.h"
+#include "pingu_conf.h"
+#include "pingu_host.h"
+#include "pingu_iface.h"
+#include "pingu_netlink.h"
+
+#ifndef DEFAULT_CONFIG
+#define DEFAULT_CONFIG "/etc/pingu/pingu.conf"
+#endif
+
+#ifndef DEFAULT_PIDFILE
+#define DEFAULT_PIDFILE "/var/run/pingu/pingu.pid"
+#endif
+
+int pingu_verbose = 0, pid_file_fd = 0, pingu_daemonize = 0;
+char *pid_file = DEFAULT_PIDFILE;
+
+static void print_version(const char *program)
+{
+ printf("%s " PINGU_VERSION "\n", program);
+}
+
+int usage(const char *program)
+{
+ print_version(program);
+ fprintf(stderr, "usage: %s [-dhiv] [-a ADMSOCKET] [-c CONFIG] [-p PIDFILE]\n"
+ "\n"
+ "options:\n"
+ " -a Use administration socket ADMSOCKET (default is "
+ DEFAULT_ADM_SOCKET ")\n"
+ " -c Read configuration from CONFIG (default is "
+ DEFAULT_CONFIG ")\n"
+ " -d Fork to background (damonize)\n"
+ " -h Show this help\n"
+ " -p Use PIDFILE as pidfile (default is "
+ DEFAULT_PIDFILE ")\n"
+ " -V Print version and exit\n"
+ " -v Run in verbose mode. Will log debug messages\n"
+ "\n",
+ program);
+ return 1;
+}
+
+static void remove_pid_file(void)
+{
+ if (pid_file_fd != 0) {
+ close(pid_file_fd);
+ pid_file_fd = 0;
+ remove(pid_file);
+ }
+}
+
+static int daemonize(void)
+{
+ char tmp[16];
+ pid_t pid;
+
+ pid = fork();
+ if (pid < 0)
+ return -1;
+ if (pid > 0)
+ exit(0);
+
+ if (setsid() < 0)
+ return -1;
+
+ pid = fork();
+ if (pid < 0)
+ return -1;
+ if (pid > 0)
+ exit(0);
+
+ if (chdir("/") < 0)
+ return -1;
+
+ pid_file_fd = open(pid_file, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
+ if (pid_file_fd < 0) {
+ log_error("Unable to open %s: %s.", pid_file, strerror(errno));
+ return -1;
+ }
+
+ if (flock(pid_file_fd, LOCK_EX | LOCK_NB) < 0) {
+ log_error("Unable to lock pid file (already running?).");
+ close(pid_file_fd);
+ pid_file_fd = 0;
+ return -1;
+ }
+
+ ftruncate(pid_file_fd, 0);
+ write(pid_file_fd, tmp, sprintf(tmp, "%d\n", getpid()));
+ atexit(remove_pid_file);
+
+ freopen("/dev/null", "r", stdin);
+ freopen("/dev/null", "w", stdout);
+ freopen("/dev/null", "w", stderr);
+
+ umask(0);
+
+ return 0;
+}
+
+static void sigint_cb(struct ev_loop *loop, ev_signal *w, int revents)
+{
+ ev_break(loop, EVBREAK_ALL);
+}
+
+static pid_t get_running_pid(void) {
+ size_t n;
+ int fd;
+ char buf[32] = "/proc/";
+ fd = open(pid_file, O_RDONLY);
+ if (fd < 0)
+ return 0;
+ n = read(fd, &buf[6], sizeof(buf)-7);
+ close(fd);
+ if (n < sizeof(buf)-6)
+ buf[5+n] = '\0'; /* chomp newline */
+ if (access(buf, R_OK) == 0)
+ return atoi(&buf[6]);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ const char *config_file = DEFAULT_CONFIG;
+ const char *adm_socket = DEFAULT_ADM_SOCKET;
+ int verbose = 0;
+ static struct ev_loop *loop;
+ static struct ev_signal signal_watcher;
+ pid_t pid;
+
+ while ((c = getopt(argc, argv, "a:c:dhp:Vv")) != -1) {
+ switch (c) {
+ case 'a':
+ adm_socket = optarg;
+ break;
+ case 'c':
+ config_file = optarg;
+ break;
+ case 'd':
+ pingu_daemonize++;
+ break;
+ case 'h':
+ return usage(basename(argv[0]));
+ case 'p':
+ pid_file = optarg;
+ break;
+ case 'V':
+ print_version(basename(argv[0]));
+ return 0;
+ case 'v':
+ verbose++;
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ log_init("pingu", verbose);
+
+ pid = get_running_pid();
+ if (pid)
+ errx(1, "appears to be running already (pid %i)", pid);
+
+ loop = ev_default_loop(0);
+
+ if (pingu_conf_parse(config_file) < 0)
+ return 1;
+
+ if (pingu_iface_init(loop) < 0)
+ return 1;
+
+ if (pingu_host_init(loop) < 0)
+ return 1;
+
+ if (pingu_adm_init(loop, adm_socket) < 0)
+ return 1;
+
+ if (pingu_daemonize) {
+ if (daemonize() == -1)
+ return 1;
+ }
+
+ kernel_init(loop);
+ ev_signal_init(&signal_watcher, sigint_cb, SIGINT);
+ ev_signal_start(loop, &signal_watcher);
+
+ ev_run(loop, 0);
+ log_info("Shutting down");
+ pingu_iface_cleanup(loop);
+ pingu_host_cleanup();
+ kernel_close();
+ ev_loop_destroy(loop);
+ return 0;
+}
+
diff --git a/src/pingu.h b/src/pingu.h
new file mode 100644
index 0000000..e5906ad
--- /dev/null
+++ b/src/pingu.h
@@ -0,0 +1,6 @@
+#ifndef PINGU_H
+#define PINGU_H
+
+extern int pingu_verbose;
+
+#endif
diff --git a/src/pingu_adm.c b/src/pingu_adm.c
new file mode 100644
index 0000000..9062e6e
--- /dev/null
+++ b/src/pingu_adm.c
@@ -0,0 +1,157 @@
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <ev.h>
+
+#include "list.h"
+#include "log.h"
+#include "pingu_adm.h"
+#include "pingu_iface.h"
+#include "pingu_host.h"
+
+static struct ev_io accept_io;
+
+struct adm_conn {
+ struct ev_io io;
+ struct ev_timer timeout;
+ int num_read;
+ char cmd[512];
+};
+
+static void pingu_adm_free_conn(struct ev_loop *loop, struct adm_conn *rm)
+{
+ int fd = rm->io.fd;
+
+ ev_io_stop(loop, &rm->io);
+ ev_timer_stop(loop, &rm->timeout);
+ close(fd);
+ free(rm);
+ log_debug("Admin connection closed");
+}
+
+static struct {
+ const char *command;
+ void (*handler)(int fd, char *args);
+} adm_handler[] = {
+ { "host-status", pingu_host_dump_status },
+ { "gateway-status", pingu_iface_dump_status },
+ { "pings", pingu_iface_dump_pings },
+ { "routes", pingu_iface_dump_routes },
+ { NULL, NULL }
+};
+
+static void pingu_adm_recv_cb (struct ev_loop *loop, struct ev_io *w,
+ int revents)
+{
+ struct adm_conn *conn = container_of(w, struct adm_conn, io);
+ int len, i;
+ char *args;
+
+ len = recv(conn->io.fd, conn->cmd, sizeof(conn->cmd) - conn->num_read,
+ MSG_DONTWAIT);
+ if (len < 0 && errno == EAGAIN)
+ return;
+ if (len <= 0)
+ goto err;
+
+ conn->num_read += len;
+ if (conn->num_read >= sizeof(conn->cmd))
+ goto err;
+ if (conn->cmd[conn->num_read - 1] != '\n')
+ goto err;
+
+ conn->num_read--;
+ conn->cmd[conn->num_read] = '\0';
+
+ args = strchr(conn->cmd, ' ');
+ if (args != NULL)
+ *args++ = '\0';
+
+ for (i = 0; adm_handler[i].command != NULL; i++) {
+ if (strncmp(conn->cmd, adm_handler[i].command, len) != 0)
+ continue;
+ log_debug("Admin command: %s (args='%s')", conn->cmd, args ? args : "NULL");
+ adm_handler[i].handler(conn->io.fd, args);
+ conn->cmd[0] = '\0';
+ conn->num_read = 0;
+ break;
+ }
+
+ if (adm_handler[i].command == NULL)
+ log_error("%s: unknown admim command", conn->cmd);
+
+err:
+ pingu_adm_free_conn(loop, conn);
+}
+
+static void pingu_adm_timeout_cb (struct ev_loop *loop, struct ev_timer *t,
+ int revents)
+{
+ log_debug("Admin connection timed out");
+ pingu_adm_free_conn(loop, container_of(t, struct adm_conn, timeout));
+}
+
+static void pingu_adm_accept_cb(struct ev_loop *loop, struct ev_io *w,
+ int revents)
+{
+ struct adm_conn *conn;
+ struct sockaddr_storage from;
+ socklen_t fromlen = sizeof(from);
+ int fd;
+
+ fd = accept(w->fd, (struct sockaddr *) &from, &fromlen);
+ if (fd < 0) {
+ log_perror("accept");
+ return;
+ }
+ log_debug("New admin connection");
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ conn = calloc(1, sizeof(struct adm_conn));
+
+ ev_io_init(&conn->io, pingu_adm_recv_cb, fd, EV_READ);
+ ev_io_start(loop, &conn->io);
+ ev_timer_init(&conn->timeout, pingu_adm_timeout_cb, 10.0, 0.);
+ ev_timer_start(loop, &conn->timeout);
+}
+
+
+int pingu_adm_init(struct ev_loop *loop, const char *socket_path)
+{
+ struct sockaddr_un sun;
+ int fd;
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strncpy(sun.sun_path, socket_path, sizeof(sun.sun_path));
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) {
+ log_perror("socket");
+ return -1;
+ }
+
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ unlink(socket_path);
+ if (bind(fd, (struct sockaddr *) &sun, sizeof(sun)) < 0)
+ goto perr_close;
+
+ if (listen(fd, 5) < 0)
+ goto perr_close;
+
+ ev_io_init(&accept_io, pingu_adm_accept_cb, fd, EV_READ);
+ ev_io_start(loop, &accept_io);
+ return 0;
+
+perr_close:
+ log_perror(socket_path);
+ close(fd);
+ return -1;
+
+}
diff --git a/src/pingu_adm.h b/src/pingu_adm.h
new file mode 100644
index 0000000..3b50b06
--- /dev/null
+++ b/src/pingu_adm.h
@@ -0,0 +1,12 @@
+#ifndef PINGU_ADM_H
+#define PINGU_ADM_H
+
+#include <ev.h>
+
+#ifndef DEFAULT_ADM_SOCKET
+#define DEFAULT_ADM_SOCKET "/var/run/pingu/pingu.ctl"
+#endif
+
+int pingu_adm_init(struct ev_loop *loop, const char *socket_path);
+
+#endif
diff --git a/src/pingu_burst.c b/src/pingu_burst.c
new file mode 100644
index 0000000..3505c94
--- /dev/null
+++ b/src/pingu_burst.c
@@ -0,0 +1,67 @@
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <netdb.h>
+#include <string.h>
+
+#include <ev.h>
+
+#include "log.h"
+#include "pingu_burst.h"
+#include "pingu_host.h"
+#include "pingu_ping.h"
+#include "pingu_iface.h"
+
+void ping_burst_start(struct ev_loop *loop, struct pingu_host *host)
+{
+ struct addrinfo hints;
+ struct addrinfo *ai = NULL, *rp;
+ int r;
+ char buf[64];
+
+ /* we bind to device every burst in case an iface disappears and
+ comes back. e.g ppp0 */
+ if (pingu_iface_bind_socket(host->iface, host->status) < 0) {
+ pingu_host_set_status(host, PINGU_HOST_STATUS_OFFLINE);
+ return;
+ }
+
+ host->burst.active = 1;
+ host->burst.pings_sent = 0;
+ host->burst.pings_replied = 0;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ r = getaddrinfo(host->host, NULL, &hints, &ai);
+ if (r < 0) {
+ log_error("getaddrinfo(%s): %s", host->host, gai_strerror(r));
+ return;
+ }
+
+ for (rp = ai; rp != NULL; rp = rp->ai_next) {
+ sockaddr_from_addrinfo(&host->burst.saddr, ai);
+ r = pingu_ping_send(loop, host, PINGU_PING_IGNORE_ERROR);
+ if (r == 0)
+ break;
+ }
+
+ sockaddr_to_string(&host->burst.saddr, buf, sizeof(buf));
+ if (rp == NULL) {
+ log_debug("%s: failed to send first ping to %s", host->label, buf);
+ host->burst.active = 0;
+ }
+ freeaddrinfo(ai);
+}
+
+void pingu_burst_timeout_cb(struct ev_loop *loop, struct ev_timer *w,
+ int revents)
+{
+ struct pingu_host *host = container_of(w, struct pingu_host, burst_timeout_watcher);
+
+ if (host->burst.active) {
+ log_warning("%s: burst already active", host->host);
+ return;
+ }
+ log_debug("%s: new burst to %s via %s", host->label, host->host, host->iface->name);
+ ping_burst_start(loop, host);
+}
diff --git a/src/pingu_burst.h b/src/pingu_burst.h
new file mode 100644
index 0000000..6e3b5e6
--- /dev/null
+++ b/src/pingu_burst.h
@@ -0,0 +1,24 @@
+#ifndef PINGU_BURST_H
+#define PINGU_BURST_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <ev.h>
+
+#include "list.h"
+#include "sockaddr_util.h"
+
+struct pingu_burst {
+ union sockaddr_any saddr;
+// size_t saddrlen;
+ int pings_sent;
+ int pings_replied;
+ int active;
+ struct list_head ping_burst_entry;
+};
+
+void pingu_burst_timeout_cb(struct ev_loop *loop, struct ev_timer *w,
+ int revents);
+
+#endif
diff --git a/src/pingu_conf.c b/src/pingu_conf.c
new file mode 100644
index 0000000..263a1f4
--- /dev/null
+++ b/src/pingu_conf.c
@@ -0,0 +1,253 @@
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "log.h"
+#include "pingu_iface.h"
+#include "pingu_host.h"
+#include "xlib.h"
+
+struct pingu_conf {
+ const char *filename;
+ FILE *fh;
+ int lineno;
+};
+
+static float default_burst_interval = 30.0;
+static float default_timeout = 1.0;
+static int default_max_retries = 5;
+static int default_required_replies = 3;
+static char *default_up_action = NULL;
+static char *default_down_action = NULL;
+
+/* note: this overwrite the line buffer */
+static void parse_line(char *line, char **key, char **value)
+{
+ char *p;
+
+ (*value) = NULL;
+ (*key) = NULL;
+
+ /* strip comments and trailing \n */
+ p = strpbrk(line, "#\n");
+ if (p)
+ *p = '\0';
+
+ /* skip leading whitespace */
+ while (isspace(*line))
+ line++;
+ if (*line == '\0')
+ return;
+
+ (*key) = line;
+
+ /* find space between keyword and value */
+ p = line;
+ while (!isspace(*p)) {
+ if (*p == '\0')
+ return;
+ p++;
+ }
+ *p++ = '\0';
+
+ /* find value */
+ while (isspace(*p))
+ p++;
+ if (*p == '\0')
+ return;
+
+ (*value) = p;
+}
+
+static struct pingu_conf *pingu_conf_open(const char *filename)
+{
+ struct pingu_conf *f = calloc(1, sizeof(struct pingu_conf));
+ if (f == NULL) {
+ log_perror("calloc");
+ return NULL;
+ }
+ f->fh = fopen(filename, "r");
+ if (f->fh == NULL) {
+ log_perror(filename);
+ free(f);
+ return NULL;
+ }
+ f->filename = filename;
+ return f;
+}
+
+static void pingu_conf_close(struct pingu_conf *f)
+{
+ fclose(f->fh);
+ free(f);
+}
+
+static char *chomp_bracket(char *str)
+{
+ char *p = str;
+ /* chomp */
+ while (!isspace(*p))
+ p++;
+ *p = '\0';
+ return str;
+}
+
+static char *pingu_conf_read_key_value(struct pingu_conf *conf, char **key,
+ char **value)
+{
+ static char line[1024];
+ char *k = NULL, *v = NULL;
+ while (k == NULL || *k == '\0') {
+ if (fgets(line, sizeof(line), conf->fh) == NULL)
+ return NULL;
+ conf->lineno++;
+ parse_line(line, &k, &v);
+ }
+ *key = k;
+ *value = v;
+ return line;
+}
+
+static struct pingu_host *pingu_conf_new_host(const char *hoststr)
+{
+ return pingu_host_new(xstrdup(hoststr), default_burst_interval,
+ default_max_retries, default_required_replies,
+ default_timeout, default_up_action,
+ default_down_action);
+}
+
+static int pingu_conf_read_iface(struct pingu_conf *conf, char *ifname)
+{
+ struct pingu_iface *iface;
+ char *key, *value;
+
+ iface = pingu_iface_get_by_name(ifname);
+ if (iface != NULL) {
+ log_error("Interface %s already declared (line %i)", conf->lineno);
+ return -1;
+ }
+
+ iface = pingu_iface_get_by_name_or_new(ifname);
+ if (iface == NULL)
+ return -1;
+
+ while (pingu_conf_read_key_value(conf, &key, &value)) {
+ if (key == NULL || key[0] == '}')
+ break;
+ if (strcmp(key, "route-table") == 0) {
+ pingu_iface_set_route_table(iface, atoi(value));
+ } else if (strcmp(key, "label") == 0) {
+ iface->label = xstrdup(value);
+ } else if (strcmp(key, "gateway-up-action") == 0) {
+ iface->gw_up_action = xstrdup(value);
+ } else if (strcmp(key, "gateway-down-action") == 0) {
+ iface->gw_down_action = xstrdup(value);
+ } else if (strcmp(key, "required-hosts-online") == 0) {
+ iface->required_hosts_online = atoi(value);
+ } else if (strcmp(key, "rule-priority") == 0) {
+ iface->rule_priority = atoi(value);
+ } else if (strcmp(key, "load-balance") == 0) {
+ int weight = 0;
+ if (value != NULL) {
+ weight = atoi(value);
+ if (weight <= 0 || weight > 256) {
+ log_error("Invalid load-balance weight %i on line %i",
+ weight, conf->lineno);
+ return -1;
+ }
+ }
+ pingu_iface_set_balance(iface, weight);
+ } else if (strcmp(key, "ping") == 0) {
+ struct pingu_host *host = pingu_conf_new_host(value);
+ host->iface = iface;
+ } else if (strcmp(key, "fwmark") == 0) {
+ iface->fwmark = atoi(value);
+ } else {
+ log_error("Unknown keyword '%s' on line %i", key,
+ conf->lineno);
+ }
+ }
+ return 0;
+}
+
+static int pingu_conf_read_host(struct pingu_conf *conf, char *hoststr)
+{
+ char *key, *value;
+ struct pingu_host *host = pingu_conf_new_host(hoststr);
+
+ while (pingu_conf_read_key_value(conf, &key, &value)) {
+ if (key == NULL || key[0] == '}')
+ break;
+ if (strcmp(key, "bind-interface") == 0) {
+ host->iface = pingu_iface_get_by_name_or_new(value);
+ if (host->iface == NULL) {
+ log_error("Undefined interface %s on line %i",
+ value, conf->lineno);
+ return -1;
+ }
+ } else if (strcmp(key, "label") == 0) {
+ host->label = xstrdup(value);
+ } else if (strcmp(key, "up-action") == 0) {
+ host->up_action = xstrdup(value);
+ } else if (strcmp(key, "down-action") == 0) {
+ host->down_action = xstrdup(value);
+ } else if (strcmp(key, "retry") == 0) {
+ host->max_retries = atoi(value);
+ } else if (strcmp(key, "required") == 0) {
+ host->required_replies = atoi(value);
+ } else if (strcmp(key, "timeout") == 0) {
+ host->timeout = atof(value);
+ } else if (strcmp(key, "interval") == 0) {
+ host->burst_interval = atof(value);
+ } else {
+ log_error("Unknown keyword '%s' on line %i", key,
+ conf->lineno);
+ }
+ }
+ if (host->iface == NULL)
+ host->iface = pingu_iface_get_by_name_or_new(NULL);
+ return 0;
+}
+
+int pingu_conf_parse(const char *filename)
+{
+ int r = 0;
+ char *key, *value;
+ struct pingu_conf *conf = pingu_conf_open(filename);
+
+ if (conf == NULL)
+ return -1;
+
+ while (pingu_conf_read_key_value(conf, &key, &value)) {
+ if (strcmp(key, "interface") == 0) {
+ r = pingu_conf_read_iface(conf, chomp_bracket(value));
+ if (r < 0)
+ break;
+ } else if (strcmp(key, "host") == 0) {
+ r = pingu_conf_read_host(conf, chomp_bracket(value));
+ if (r < 0)
+ break;
+ } else if (strcmp(key, "interval") == 0) {
+ default_burst_interval = atof(value);
+ } else if (strcmp(key, "retry") == 0) {
+ default_max_retries = atoi(value);
+ } else if (strcmp(key, "required") == 0) {
+ default_required_replies = atoi(value);
+ } else if (strcmp(key, "timeout") == 0) {
+ default_timeout = atof(value);
+ } else if (strcmp(key, "up-action") == 0) {
+ default_up_action = xstrdup(value);
+ } else if (strcmp(key, "down-action") == 0) {
+ default_down_action = xstrdup(value);
+ } else {
+ log_error("Unknown keyword '%s' on line %i", key,
+ conf->lineno);
+ r = -1;
+ break;
+ }
+ }
+ pingu_conf_close(conf);
+ return r;
+}
diff --git a/src/pingu_conf.h b/src/pingu_conf.h
new file mode 100644
index 0000000..37c932a
--- /dev/null
+++ b/src/pingu_conf.h
@@ -0,0 +1,6 @@
+#ifndef PINGU_CONF_H
+#define PINGU_CONF_H
+
+int pingu_conf_parse(const char *filename);
+
+#endif
diff --git a/src/pingu_host.c b/src/pingu_host.c
new file mode 100644
index 0000000..f7695c4
--- /dev/null
+++ b/src/pingu_host.c
@@ -0,0 +1,165 @@
+
+#include <arpa/inet.h>
+#include <linux/rtnetlink.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ev.h>
+
+#include "list.h"
+#include "log.h"
+#include "pingu_burst.h"
+#include "pingu_host.h"
+#include "pingu_iface.h"
+#include "pingu_ping.h"
+#include "xlib.h"
+
+static struct list_head host_list = LIST_INITIALIZER(host_list);
+
+void execute_action(const char *action)
+{
+ pid_t pid;
+ const char *shell;
+
+ if (action == NULL)
+ return;
+
+ shell = getenv("SHELL");
+ if (shell == NULL)
+ shell = "/bin/sh";
+
+ log_debug("executing '%s'", action);
+ pid = fork();
+ if (pid < 0) {
+ log_perror("fork");
+ return;
+ }
+ if (pid == 0) {
+ execl(shell, shell, "-c", action, NULL);
+ log_perror(action);
+ exit(1);
+ }
+}
+
+int pingu_host_set_status(struct pingu_host *host, int status)
+{
+ const char *action, *statusstr = "";
+ int adjustment;
+ host->burst.active = 0;
+ if (host->status == status) {
+ log_debug("%s: status is still %i", host->label, status);
+ return status;
+ }
+ host->status = status;
+ switch (host->status) {
+ case PINGU_HOST_STATUS_OFFLINE:
+ action = host->down_action;
+ adjustment = -1;
+ statusstr = "OFFLINE";
+ break;
+ case PINGU_HOST_STATUS_ONLINE:
+ action = host->up_action;
+ adjustment = 1;
+ statusstr = "ONLINE";
+ break;
+ }
+ log_info("%s: went %s", host->label, statusstr);
+
+ execute_action(action);
+ pingu_iface_adjust_hosts_online(host->iface, adjustment);
+ return status;
+}
+
+int pingu_host_verify_status(struct ev_loop *loop, struct pingu_host *host)
+{
+ if (host->burst.pings_replied >= host->required_replies) {
+ pingu_host_set_status(host, PINGU_HOST_STATUS_ONLINE);
+ } else if (host->burst.pings_sent >= host->max_retries) {
+ pingu_host_set_status(host, PINGU_HOST_STATUS_OFFLINE);
+ } else
+ pingu_ping_send(loop, host, PINGU_PING_SET_STATUS_ON_ERROR);
+ return 0;
+}
+
+struct pingu_host *pingu_host_new(char *hoststr, float burst_interval,
+ int max_retries, int required_replies,
+ float timeout,
+ const char *up_action,
+ const char *down_action)
+{
+ struct pingu_host *host = calloc(1, sizeof(struct pingu_host));
+
+ if (host == NULL) {
+ log_perror(hoststr);
+ return NULL;
+ }
+
+ host->host = hoststr;
+ host->status = PINGU_HOST_DEFAULT_STATUS;
+ host->burst_interval = burst_interval;
+ host->max_retries = max_retries;
+ host->required_replies = required_replies;
+ host->timeout = timeout;
+ host->up_action = up_action;
+ host->down_action = down_action;
+
+ list_add(&host->host_list_entry, &host_list);
+ return host;
+}
+
+void pingu_host_dump_status(int fd, char *filter)
+{
+ struct pingu_host *host;
+ char buf[512];
+ list_for_each_entry(host, &host_list, host_list_entry) {
+ if (filter != NULL && strcmp(filter, host->label) != 0)
+ continue;
+ snprintf(buf, sizeof(buf), "%s: %i\n", host->label, host->status);
+ write(fd, buf, strlen(buf));
+ }
+ write(fd, "\n", 1);
+}
+
+int pingu_host_init(struct ev_loop *loop)
+{
+ struct pingu_host *host;
+ list_for_each_entry(host, &host_list, host_list_entry) {
+ if (host->label == NULL)
+ host->label = host->host;
+ ev_timer_init(&host->burst_timeout_watcher,
+ pingu_burst_timeout_cb, 1.0, host->burst_interval);
+ ev_timer_start(loop, &host->burst_timeout_watcher);
+
+ if (host->iface->required_hosts_online == 0)
+ host->iface->required_hosts_online = 1;
+ host->iface->hosts_online += PINGU_HOST_DEFAULT_STATUS;
+ }
+ return 0;
+}
+
+void pingu_host_iface_deleted(struct pingu_iface *iface)
+{
+ struct pingu_host *host;
+ list_for_each_entry(host, &host_list, host_list_entry)
+ if (host->iface == iface)
+ pingu_host_set_status(host, 0);
+}
+
+void pingu_host_cleanup(void)
+{
+ struct pingu_host *host, *n;
+ list_for_each_entry_safe(host, n, &host_list, host_list_entry) {
+ if (host->host != NULL)
+ free(host->host);
+ if (host->label != NULL)
+ free(host->label);
+ if (host->up_action != NULL)
+ free((void *)host->up_action);
+ if (host->down_action != NULL)
+ free((void *)host->down_action);
+ list_del(&host->host_list_entry);
+ free(host);
+ }
+}
diff --git a/src/pingu_host.h b/src/pingu_host.h
new file mode 100644
index 0000000..4f4c341
--- /dev/null
+++ b/src/pingu_host.h
@@ -0,0 +1,45 @@
+#ifndef PINGU_HOST_H
+#define PINGU_HOST_H
+
+#include <ev.h>
+
+#include "pingu_burst.h"
+
+#define PINGU_HOST_STATUS_OFFLINE 0
+#define PINGU_HOST_STATUS_ONLINE 1
+
+/* consider online by default */
+#define PINGU_HOST_DEFAULT_STATUS PINGU_HOST_STATUS_ONLINE
+
+struct pingu_host {
+ struct list_head host_list_entry;
+ char *host;
+ char *label;
+ const char *up_action;
+ const char *down_action;
+ int status;
+ int max_retries;
+ int required_replies;
+ ev_tstamp timeout;
+
+ ev_tstamp burst_interval;
+ struct ev_timer burst_timeout_watcher;
+ struct pingu_burst burst;
+ struct pingu_iface *iface;
+};
+
+void execute_action(const char *action);
+
+struct pingu_host *pingu_host_new(char *hoststr, float burst_interval,
+ int max_retries, int required_replies,
+ float timeout,
+ const char *up_action,
+ const char *down_action);
+int pingu_host_set_status(struct pingu_host *host, int status);
+int pingu_host_init(struct ev_loop *loop);
+int pingu_host_verify_status(struct ev_loop *loop, struct pingu_host *host);
+void pingu_host_dump_status(int fd, char *filter);
+void pingu_host_iface_deleted(struct pingu_iface *iface);
+void pingu_host_cleanup(void);
+
+#endif
diff --git a/src/pingu_iface.c b/src/pingu_iface.c
new file mode 100644
index 0000000..815b1e9
--- /dev/null
+++ b/src/pingu_iface.c
@@ -0,0 +1,344 @@
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <linux/rtnetlink.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+#include <ev.h>
+
+#include "list.h"
+#include "log.h"
+#include "pingu_burst.h"
+#include "pingu_host.h"
+#include "pingu_iface.h"
+#include "pingu_ping.h"
+#include "pingu_netlink.h"
+#include "sockaddr_util.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef HAVE_STRLCPY
+#define strlcpy(dst, src, dstsize) snprintf(dst, dstsize, "%s", src)
+#endif
+
+static struct list_head iface_list = LIST_INITIALIZER(iface_list);
+
+#define PINGU_ROUTE_TABLE_MIN 1
+#define PINGU_ROUTE_TABLE_MAX 253
+unsigned char used_route_table[256];
+
+/* do we have any load-balance at all? */
+static int load_balanced = 0;
+
+static void pingu_iface_socket_cb(struct ev_loop *loop, struct ev_io *w,
+ int revents)
+{
+ struct pingu_iface *iface = container_of(w, struct pingu_iface, socket_watcher);
+
+ if (revents & EV_READ)
+ pingu_ping_read_reply(loop, iface);
+}
+
+int pingu_iface_bind_socket(struct pingu_iface *iface, int log_error)
+{
+ int r;
+ if (iface->name[0] == '\0')
+ return 0;
+ r = setsockopt(iface->fd, SOL_SOCKET, SO_BINDTODEVICE, iface->name,
+ strlen(iface->name));
+ if (r < 0 && log_error)
+ log_perror(iface->name);
+
+ r = bind(iface->fd, &iface->primary_addr.sa,
+ sockaddr_len(&iface->primary_addr));
+ if (r < 0 && log_error)
+ log_perror(iface->name);
+ iface->has_binding = (r == 0);
+ return r;
+}
+
+static int pingu_iface_init_socket(struct ev_loop *loop,
+ struct pingu_iface *iface)
+{
+ iface->fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+ if (iface->fd < 0) {
+ log_perror("socket");
+ return -1;
+ }
+
+ ev_io_init(&iface->socket_watcher, pingu_iface_socket_cb,
+ iface->fd, EV_READ);
+ ev_io_start(loop, &iface->socket_watcher);
+ return 0;
+}
+
+int pingu_iface_usable(struct pingu_iface *iface)
+{
+ if (iface->name[0] == '\0')
+ return 1;
+ return iface->has_link && iface->has_address && iface->has_binding;
+}
+
+struct pingu_iface *pingu_iface_get_by_name(const char *name)
+{
+ struct pingu_iface *iface;
+ list_for_each_entry(iface, &iface_list, iface_list_entry) {
+ if (name == NULL) {
+ if (iface->name[0] == '\0')
+ return iface;
+ } else if (strncmp(name, iface->name, sizeof(iface->name)) == 0) {
+ return iface;
+ }
+ }
+ return NULL;
+}
+
+struct pingu_iface *pingu_iface_get_by_index(int index)
+{
+ struct pingu_iface *iface;
+ list_for_each_entry(iface, &iface_list, iface_list_entry) {
+ if (iface->index == index)
+ return iface;
+ }
+ return NULL;
+}
+
+struct pingu_iface *pingu_iface_get_by_name_or_new(const char *name)
+{
+ struct pingu_iface *iface = pingu_iface_get_by_name(name);
+ if (iface != NULL)
+ return iface;
+
+ iface = calloc(1, sizeof(struct pingu_iface));
+ if (iface == NULL) {
+ log_perror("calloc(iface)");
+ return NULL;
+ }
+
+ if (name != NULL)
+ strlcpy(iface->name, name, sizeof(iface->name));
+
+ list_init(&iface->ping_list);
+ list_init(&iface->route_list);
+ list_add(&iface->iface_list_entry, &iface_list);
+ return iface;
+}
+
+void pingu_iface_set_addr(struct pingu_iface *iface, int family,
+ void *data, int len)
+{
+ sockaddr_init(&iface->primary_addr, family, data);
+ if (len <= 0 || data == NULL) {
+ iface->has_address = 0;
+ iface->has_binding = 0;
+ pingu_route_del_all(&iface->route_list);
+ log_debug("%s: address removed", iface->name);
+ return;
+ }
+ iface->has_address = 1;
+ log_debug("%s: new address: %s", iface->name,
+ inet_ntoa(iface->primary_addr.sin.sin_addr));
+}
+
+void pingu_iface_set_balance(struct pingu_iface *iface, int balance_weight)
+{
+ load_balanced++;
+ iface->balance = 1;
+ iface->balance_weight = balance_weight;
+}
+
+#if 0
+void pingu_route_dump(struct pingu_iface *iface)
+{
+ struct pingu_route *gw;
+ list_for_each_entry(gw, &iface->route_list, route_list_entry) {
+ char buf[64];
+ sockaddr_to_string(&gw->gw_addr, buf, sizeof(buf));
+ log_debug("dump: %s: via %s metric %i", iface->name, buf,
+ gw->metric);
+ }
+}
+#endif
+
+void pingu_iface_gw_action(struct pingu_iface *iface,
+ struct pingu_route *gw, int action)
+{
+ switch (action) {
+ case RTM_NEWROUTE:
+ pingu_route_add(&iface->route_list, gw);
+ log_debug("%s: added route", iface->name);
+ /* if we get a new default gateway for an interface
+ * that is marked "down", remove the default gw again
+ * from main table and let pingu detect that it went up
+ *
+ * This solves the case when dhcp will renew a lease,
+ * recreates the default gw and ISP is broke a bit
+ * futher down the road.
+ */
+ if (is_default_gw(gw) && !pingu_iface_gw_is_online(iface)) {
+ pingu_iface_update_routes(iface, RTM_DELROUTE);
+ /* avoid doing the multipath twice*/
+ return;
+ }
+ break;
+ case RTM_DELROUTE:
+ pingu_route_del(&iface->route_list, gw);
+ log_debug("%s: removed route", iface->name);
+ break;
+ }
+ if (load_balanced > 1 && is_default_gw(gw))
+ kernel_route_multipath(RTM_NEWROUTE, &iface_list, RT_TABLE_MAIN);
+}
+
+void pingu_iface_update_routes(struct pingu_iface *iface, int action)
+{
+ struct pingu_route *route;
+ list_for_each_entry(route, &iface->route_list, route_list_entry) {
+ if (is_default_gw(route) && iface->has_address)
+ kernel_route_modify(action, route, RT_TABLE_MAIN);
+ }
+ if (load_balanced > 1)
+ kernel_route_multipath(RTM_NEWROUTE, &iface_list, RT_TABLE_MAIN);
+}
+
+int pingu_iface_gw_is_online(struct pingu_iface *iface)
+{
+ return iface->hosts_online >= iface->required_hosts_online;
+}
+
+/* check if we should bring up/down this ISP */
+void pingu_iface_adjust_hosts_online(struct pingu_iface *iface, int adjustment)
+{
+ int old_status, new_status, route_action;
+ char *action, *statusstr;
+
+ old_status = pingu_iface_gw_is_online(iface);
+ iface->hosts_online += adjustment;
+ new_status = pingu_iface_gw_is_online(iface);
+
+ if (old_status == new_status)
+ return;
+
+ if (new_status) {
+ statusstr = "ONLINE";
+ route_action = RTM_NEWROUTE;
+ action = iface->gw_up_action;
+ } else {
+ statusstr = "OFFLINE";
+ route_action = RTM_DELROUTE;
+ action = iface->gw_down_action;
+ }
+
+ log_info("%s: went %s", iface->label ? iface->label : iface->name,
+ statusstr);
+ pingu_iface_update_routes(iface, route_action);
+ execute_action(action);
+}
+
+int pingu_iface_set_route_table(struct pingu_iface *iface, int table)
+{
+ static int initialized = 0;
+ int i = 1;
+ if (!initialized) {
+ memset(used_route_table, 0, sizeof(used_route_table));
+ initialized = 1;
+ }
+ if (table == PINGU_ROUTE_TABLE_AUTO) {
+ while (i < 253 && used_route_table[i])
+ i++;
+ table = i;
+ }
+ if (table < PINGU_ROUTE_TABLE_MIN || table >= PINGU_ROUTE_TABLE_MAX) {
+ log_error("Invalid route table %i", table);
+ return -1;
+ }
+ used_route_table[table] = 1;
+ iface->route_table = table;
+ return table;
+}
+
+void pingu_iface_dump_status(int fd, char *filter)
+{
+ struct pingu_iface *iface;
+ char buf[512];
+ list_for_each_entry(iface, &iface_list, iface_list_entry) {
+ if (filter != NULL && strcmp(filter, iface->label) != 0)
+ continue;
+ snprintf(buf, sizeof(buf), "%s: %i\n",
+ iface->label != NULL ? iface->label : iface->name,
+ pingu_iface_gw_is_online(iface));
+ write(fd, buf, strlen(buf));
+ }
+ write(fd, "\n", 1);
+}
+
+void pingu_iface_dump_pings(int fd, char *filter)
+{
+ struct pingu_iface *iface;
+ list_for_each_entry(iface, &iface_list, iface_list_entry) {
+ if (filter != NULL && strcmp(filter, iface->name) != 0)
+ continue;
+ pingu_ping_dump(fd, &iface->ping_list, iface->name);
+ }
+ write(fd, "\n", 1);
+}
+
+void pingu_iface_dump_routes(int fd, char *filter)
+{
+ struct pingu_iface *iface;
+ list_for_each_entry(iface, &iface_list, iface_list_entry) {
+ if (filter != NULL && strcmp(filter, iface->name) != 0)
+ continue;
+ pingu_route_dump(fd, &iface->route_list);
+ }
+ write(fd, "\n", 1);
+}
+
+int pingu_iface_init(struct ev_loop *loop)
+{
+ struct pingu_iface *iface;
+ list_for_each_entry(iface, &iface_list, iface_list_entry) {
+ if (iface->route_table == 0)
+ pingu_iface_set_route_table(iface, PINGU_ROUTE_TABLE_AUTO);
+ if (pingu_iface_init_socket(loop, iface) == -1)
+ return -1;
+ }
+ if (load_balanced == 1)
+ log_warning("Only a single interface was configured with load-balance");
+ return 0;
+}
+
+void pingu_iface_cleanup(struct ev_loop *loop)
+{
+ struct pingu_iface *iface, *n;
+ /* remove loadbalance route */
+ if (load_balanced > 1) {
+ int err = kernel_route_multipath(RTM_DELROUTE, &iface_list, RT_TABLE_MAIN);
+ if (err > 0)
+ log_error("Failed to delete load-balance route: %s", strerror(err));
+ }
+
+ list_for_each_entry(iface, &iface_list, iface_list_entry) {
+ kernel_cleanup_iface_routes(iface);
+ close(iface->fd);
+ }
+ list_for_each_entry_safe(iface, n, &iface_list, iface_list_entry) {
+ list_del(&iface->iface_list_entry);
+ if (iface->label != NULL)
+ free(iface->label);
+ if (iface->gw_up_action != NULL)
+ free(iface->gw_up_action);
+ if (iface->gw_down_action != NULL)
+ free(iface->gw_down_action);
+ pingu_ping_cleanup(loop, &iface->ping_list);
+ pingu_route_cleanup(&iface->route_list);
+ free(iface);
+ }
+}
diff --git a/src/pingu_iface.h b/src/pingu_iface.h
new file mode 100644
index 0000000..cab98fd
--- /dev/null
+++ b/src/pingu_iface.h
@@ -0,0 +1,62 @@
+#ifndef PINGU_IFACE_H
+#define PINGU_IFACE_H
+
+#include <netinet/in.h>
+#include <ev.h>
+
+#include "pingu_route.h"
+#include "sockaddr_util.h"
+#include "list.h"
+
+#define PINGU_ROUTE_TABLE_AUTO -1
+
+struct pingu_iface {
+ char name[32];
+ char *label;
+ char *gw_up_action;
+ char *gw_down_action;
+ int hosts_online;
+ int required_hosts_online;
+
+ int index;
+ int has_link;
+ int has_address;
+ int has_binding;
+ int has_route_rule;
+ int has_multipath;
+ int balance;
+ int balance_weight;
+ int fd;
+ union sockaddr_any primary_addr;
+ int route_table;
+ int rule_priority;
+ int fwmark;
+ struct list_head iface_list_entry;
+ struct list_head ping_list;
+ struct list_head route_list;
+ struct ev_io socket_watcher;
+};
+
+struct pingu_iface *pingu_iface_get_by_name(const char *name);
+struct pingu_iface *pingu_iface_get_by_index(int index);
+struct pingu_iface *pingu_iface_get_by_name_or_new(const char *name);
+int pingu_iface_bind_socket(struct pingu_iface *iface, int log_error);
+int pingu_iface_usable(struct pingu_iface *iface);
+int pingu_iface_init(struct ev_loop *loop);
+
+void pingu_iface_set_balance(struct pingu_iface *iface, int balance_weight);
+void pingu_iface_set_addr(struct pingu_iface *iface, int family,
+ void *data, int len);
+void pingu_iface_adjust_hosts_online(struct pingu_iface *iface, int adjustment);
+int pingu_iface_set_route_table(struct pingu_iface *iface, int table);
+
+int pingu_iface_gw_is_online(struct pingu_iface *iface);
+void pingu_iface_gw_action(struct pingu_iface *iface,
+ struct pingu_route *gw, int action);
+
+void pingu_iface_dump_status(int fd, char *filter);
+void pingu_iface_dump_pings(int fd, char *filter);
+void pingu_iface_dump_routes(int fd, char *filter);
+void pingu_iface_update_routes(struct pingu_iface *iface, int action);
+void pingu_iface_cleanup(struct ev_loop *loop);
+#endif
diff --git a/src/pingu_netlink.c b/src/pingu_netlink.c
new file mode 100644
index 0000000..d149275
--- /dev/null
+++ b/src/pingu_netlink.c
@@ -0,0 +1,873 @@
+/* pingu_netlink.c - Linux netlink glue
+ *
+ * Copyright (C) 2007-2009 Timo Teräs <timo.teras@iki.fi>
+ * Copyright (C) 2011 Natanael Copa <ncopa@alpinelinux.org>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 or later as
+ * published by the Free Software Foundation.
+ *
+ * See http://www.gnu.org/ for details.
+ */
+
+#include <arpa/inet.h>
+#include <asm/types.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/ip.h>
+#include <net/if.h>
+#include <linux/fib_rules.h>
+#include <netinet/in.h>
+
+#include <time.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <string.h>
+#include <stdint.h>
+
+#include <ev.h>
+
+#include "log.h"
+#include "pingu_iface.h"
+#include "pingu_host.h"
+#include "pingu_netlink.h"
+
+#ifndef IFF_LOWER_UP
+/* from linux/if.h which conflicts with net/if.h */
+#define IFF_LOWER_UP 0x10000 /* driver signals L1 up */
+#endif
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#define NETLINK_KERNEL_BUFFER (256 * 1024)
+#define NETLINK_RECV_BUFFER (8 * 1024)
+
+#define NLMSG_TAIL(nmsg) \
+ ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+#define NDA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg))))
+#define NDA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndmsg))
+
+typedef void (*netlink_dispatch_f)(struct nlmsghdr *msg);
+
+struct netlink_fd {
+ int fd;
+ __u32 seq;
+ struct ev_io io;
+
+ int dispatch_size;
+ const netlink_dispatch_f *dispatch;
+};
+
+static const int netlink_groups[] = {
+ 0,
+ RTMGRP_LINK,
+ RTMGRP_IPV4_IFADDR,
+ RTMGRP_IPV4_ROUTE,
+};
+static struct netlink_fd netlink_fds[ARRAY_SIZE(netlink_groups)];
+#define talk_fd netlink_fds[0]
+
+static void netlink_parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
+{
+ memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
+ while (RTA_OK(rta, len)) {
+ if (rta->rta_type <= max)
+ tb[rta->rta_type] = rta;
+ rta = RTA_NEXT(rta,len);
+ }
+}
+
+static int netlink_add_rtattr_l(struct nlmsghdr *n, int maxlen, int type,
+ const void *data, int alen)
+{
+ int len = RTA_LENGTH(alen);
+ struct rtattr *rta;
+
+ if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen)
+ return FALSE;
+
+ rta = NLMSG_TAIL(n);
+ rta->rta_type = type;
+ rta->rta_len = len;
+ memcpy(RTA_DATA(rta), data, alen);
+ n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
+ return TRUE;
+}
+
+static int netlink_add_rtattr_addr_any(struct nlmsghdr *n, int maxlen,
+ int type, union sockaddr_any *sa)
+{
+ switch (sa->sa.sa_family) {
+ case AF_INET:
+ return netlink_add_rtattr_l(n, maxlen, type, &sa->sin.sin_addr, 4);
+ break;
+ case AF_INET6:
+ return netlink_add_rtattr_l(n, maxlen, type, &sa->sin6.sin6_addr, 16);
+ break;
+ }
+ return FALSE;
+}
+
+static int netlink_add_subrtattr_l(struct rtattr *rta, int maxlen, int type,
+ const void *data, int alen)
+{
+ struct rtattr *subrta;
+ int len = RTA_LENGTH(alen);
+
+ if (RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len) > maxlen)
+ return FALSE;
+
+ subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len));
+ subrta->rta_type = type;
+ subrta->rta_len = len;
+ memcpy(RTA_DATA(subrta), data, alen);
+ rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len);
+ return alen;
+}
+
+static int netlink_add_subrtattr_addr_any(struct rtattr *rta, int maxlen,
+ int type, union sockaddr_any *sa)
+{
+ switch (sa->sa.sa_family) {
+ case AF_INET:
+ return netlink_add_subrtattr_l(rta, maxlen, type, &sa->sin.sin_addr, 4);
+ break;
+ case AF_INET6:
+ return netlink_add_subrtattr_l(rta, maxlen, type, &sa->sin6.sin6_addr, 16);
+ break;
+ }
+ return FALSE;
+}
+
+static int netlink_get_error(struct nlmsghdr *hdr)
+{
+ struct nlmsgerr *nlerr = (struct nlmsgerr*)NLMSG_DATA(hdr);
+ if (hdr->nlmsg_type != NLMSG_ERROR)
+ return 0;
+ if (hdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
+ log_error("Netlink error message truncated");
+ return -1;
+ }
+ return -nlerr->error;
+}
+
+static int netlink_log_error(struct nlmsghdr *hdr)
+{
+ int err = netlink_get_error(hdr);
+ if (err > 0)
+ log_error("Netlink error: %s", strerror(err));
+ return err;
+}
+
+static int netlink_receive(struct netlink_fd *fd, struct nlmsghdr *reply)
+{
+ struct sockaddr_nl nladdr;
+ struct iovec iov;
+ struct msghdr msg = {
+ .msg_name = &nladdr,
+ .msg_namelen = sizeof(nladdr),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ int got_reply = FALSE, len;
+ char buf[NETLINK_RECV_BUFFER];
+
+ iov.iov_base = buf;
+ while (!got_reply) {
+ int status;
+ struct nlmsghdr *h;
+
+ iov.iov_len = sizeof(buf);
+ status = recvmsg(fd->fd, &msg, MSG_DONTWAIT);
+ if (status < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno == EAGAIN)
+ return reply == NULL;
+ log_perror("Netlink overrun");
+ continue;
+ }
+
+ if (status == 0) {
+ log_error("Netlink returned EOF");
+ return FALSE;
+ }
+
+ h = (struct nlmsghdr *) buf;
+ while (NLMSG_OK(h, status)) {
+ if (reply != NULL &&
+ h->nlmsg_seq == reply->nlmsg_seq) {
+ len = h->nlmsg_len;
+ if (len > reply->nlmsg_len) {
+ log_error("Netlink message truncated");
+ len = reply->nlmsg_len;
+ }
+ memcpy(reply, h, len);
+ got_reply = TRUE;
+ } else if (h->nlmsg_type <= fd->dispatch_size &&
+ fd->dispatch[h->nlmsg_type] != NULL) {
+ fd->dispatch[h->nlmsg_type](h);
+ } else if (h->nlmsg_type == NLMSG_ERROR) {
+ return netlink_log_error(h);
+ } else if (h->nlmsg_type != NLMSG_DONE) {
+ log_info("Unknown NLmsg: 0x%08x, len %d",
+ h->nlmsg_type, h->nlmsg_len);
+ }
+ h = NLMSG_NEXT(h, status);
+ }
+ }
+
+ return TRUE;
+}
+
+static int netlink_send(struct netlink_fd *fd, struct nlmsghdr *req)
+{
+ struct sockaddr_nl nladdr;
+ struct iovec iov = {
+ .iov_base = (void*) req,
+ .iov_len = req->nlmsg_len
+ };
+ struct msghdr msg = {
+ .msg_name = &nladdr,
+ .msg_namelen = sizeof(nladdr),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ int status;
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+
+ req->nlmsg_seq = ++fd->seq;
+
+ status = sendmsg(fd->fd, &msg, 0);
+ if (status < 0) {
+ log_perror("Cannot talk to rtnetlink");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int netlink_talk(struct netlink_fd *fd, struct nlmsghdr *req,
+ size_t replysize, struct nlmsghdr *reply)
+{
+ if (reply == NULL)
+ req->nlmsg_flags |= NLM_F_ACK;
+
+ if (!netlink_send(fd, req))
+ return FALSE;
+
+ if (reply == NULL)
+ return TRUE;
+
+ reply->nlmsg_len = replysize;
+ return netlink_receive(fd, reply);
+}
+
+static int netlink_enumerate(struct netlink_fd *fd, int family, int type)
+{
+ struct {
+ struct nlmsghdr nlh;
+ struct rtgenmsg g;
+ } req;
+ struct sockaddr_nl addr;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.nl_family = AF_NETLINK;
+
+ memset(&req, 0, sizeof(req));
+ req.nlh.nlmsg_len = sizeof(req);
+ req.nlh.nlmsg_type = type;
+ req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
+ req.nlh.nlmsg_pid = 0;
+ req.nlh.nlmsg_seq = ++fd->seq;
+ req.g.rtgen_family = family;
+
+ return sendto(fd->fd, (void *) &req, sizeof(req), 0,
+ (struct sockaddr *) &addr, sizeof(addr)) >= 0;
+}
+
+int netlink_route_modify(struct netlink_fd *fd, int action_type,
+ struct pingu_route *route,
+ int table)
+{
+ struct {
+ struct nlmsghdr nlh;
+ struct rtmsg msg;
+ char buf[1024];
+ } req;
+
+ memset(&req, 0, sizeof(req));
+
+ req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ req.nlh.nlmsg_type = action_type;
+ if (action_type == RTM_NEWROUTE)
+ req.nlh.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE;
+
+ req.msg.rtm_family = route->dest.sa.sa_family;
+ req.msg.rtm_table = table;
+ req.msg.rtm_dst_len = route->dst_len;
+ req.msg.rtm_protocol = route->protocol;
+ req.msg.rtm_scope = route->scope;
+ req.msg.rtm_type = route->type;
+
+ netlink_add_rtattr_addr_any(&req.nlh, sizeof(req), RTA_DST,
+ &route->dest);
+ netlink_add_rtattr_addr_any(&req.nlh, sizeof(req), RTA_GATEWAY,
+ &route->gw_addr);
+ netlink_add_rtattr_l(&req.nlh, sizeof(req), RTA_OIF, &route->dev_index, 4);
+ if (route->metric != 0)
+ netlink_add_rtattr_l(&req.nlh, sizeof(req), RTA_PRIORITY,
+ &route->metric, 4);
+
+ if (!netlink_talk(fd, &req.nlh, sizeof(req), &req.nlh))
+ return FALSE;
+ return netlink_get_error(&req.nlh);
+}
+
+static int add_one_nh(struct rtattr *rta, struct rtnexthop *rtnh,
+ struct pingu_iface *iface,
+ struct pingu_route *route)
+{
+ int addr_size;
+ char addrbuf[40] = "";
+ if (route == NULL)
+ return 0;
+ addr_size = netlink_add_subrtattr_addr_any(rta, 1024, RTA_GATEWAY,
+ &route->gw_addr);
+ if (addr_size > 0)
+ rtnh->rtnh_len += sizeof(struct rtattr) + addr_size;
+ log_debug("adding nexthop%s%s dev %s",
+ route->gw_addr.sa.sa_family ? " via " : "",
+ sockaddr_to_string(&route->gw_addr, addrbuf, sizeof(addrbuf)),
+ iface->name);
+ if (iface->balance_weight)
+ rtnh->rtnh_hops = iface->balance_weight - 1;
+ rtnh->rtnh_ifindex = iface->index;
+ return 1;
+}
+
+static int add_nexthops(struct nlmsghdr *nlh, size_t nlh_size,
+ struct list_head *iface_list, int action_type)
+{
+ char buf[1024];
+ struct rtattr *rta = (void *)buf;
+ struct rtnexthop *rtnh;
+ struct pingu_iface *iface;
+ struct pingu_route *route;
+ int count = 0;
+
+ memset(buf, 0, sizeof(buf));
+ rta->rta_type = RTA_MULTIPATH;
+ rta->rta_len = RTA_LENGTH(0);
+ rtnh = RTA_DATA(rta);
+
+ list_for_each_entry(iface, iface_list, iface_list_entry) {
+ route = pingu_route_first_default(&iface->route_list);
+ switch (action_type) {
+ case RTM_NEWROUTE:
+ if ((!iface->balance) || iface->index == 0
+ || !pingu_iface_gw_is_online(iface)
+ || route == NULL) {
+ iface->has_multipath = 0;
+ continue;
+ }
+ iface->has_multipath = 1;
+ break;
+ case RTM_DELROUTE:
+ if (!iface->has_multipath)
+ continue;
+ iface->has_multipath = 0;
+ break;
+ }
+ memset(rtnh, 0, sizeof(*rtnh));
+ rtnh->rtnh_len = sizeof(*rtnh);
+ rta->rta_len += rtnh->rtnh_len;
+ count += add_one_nh(rta, rtnh, iface, route);
+ rtnh = RTNH_NEXT(rtnh);
+ }
+ if (rta->rta_len > RTA_LENGTH(0))
+ netlink_add_rtattr_l(nlh, nlh_size, RTA_MULTIPATH,
+ RTA_DATA(rta), RTA_PAYLOAD(rta));
+ return count;
+}
+
+int netlink_route_multipath(struct netlink_fd *fd, int action_type,
+ struct list_head *iface_list, int table)
+{
+ struct {
+ struct nlmsghdr nlh;
+ struct rtmsg msg;
+ char buf[1024];
+ } req;
+ union sockaddr_any dest;
+ int count = 0;
+
+ memset(&req, 0, sizeof(req));
+ memset(&dest, 0, sizeof(dest));
+ dest.sa.sa_family = AF_INET;
+
+ req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ req.nlh.nlmsg_type = action_type;
+ if (action_type == RTM_NEWROUTE)
+ req.nlh.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE;
+
+ req.msg.rtm_family = AF_INET;
+ req.msg.rtm_table = table;
+ req.msg.rtm_dst_len = 0;
+ req.msg.rtm_protocol = RTPROT_BOOT;
+ req.msg.rtm_scope = RT_SCOPE_UNIVERSE;
+ req.msg.rtm_type = RTN_UNICAST;
+
+ netlink_add_rtattr_addr_any(&req.nlh, sizeof(req), RTA_DST,
+ &dest);
+
+ count = add_nexthops(&req.nlh, sizeof(req), iface_list, action_type);
+ if (count == 0)
+ req.nlh.nlmsg_type = RTM_DELROUTE;
+
+ if (!netlink_talk(fd, &req.nlh, sizeof(req), &req.nlh))
+ return -1;
+ return netlink_get_error(&req.nlh);
+}
+
+int netlink_route_replace_or_add(struct netlink_fd *fd,
+ struct pingu_route *route,
+ int table)
+{
+ return netlink_route_modify(fd, RTM_NEWROUTE, route, table);
+}
+
+int netlink_route_delete(struct netlink_fd *fd,
+ struct pingu_route *route,
+ int table)
+{
+ return netlink_route_modify(fd, RTM_DELROUTE, route, table);
+}
+
+static void netlink_route_flush(struct netlink_fd *fd, struct pingu_iface *iface)
+{
+ struct pingu_route *gw;
+ int err;
+ list_for_each_entry(gw, &iface->route_list, route_list_entry) {
+ err = netlink_route_delete(fd, gw, iface->route_table);
+ if (err > 0)
+ log_error("%s: Failed to clean up route in table %i: ",
+ iface->name, iface->route_table, strerror(err));
+ }
+}
+
+int netlink_rule_modify(struct netlink_fd *fd,
+ struct pingu_iface *iface, int rtm_type, int rule_type)
+{
+ struct {
+ struct nlmsghdr nlh;
+ struct rtmsg msg;
+ char buf[1024];
+ } req;
+
+ memset(&req, 0, sizeof(req));
+
+ req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ req.nlh.nlmsg_type = rtm_type;
+ if (rtm_type == RTM_NEWRULE)
+ req.nlh.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE;
+
+ req.msg.rtm_family = AF_INET;
+ req.msg.rtm_table = iface->route_table;
+ req.msg.rtm_protocol = RTPROT_BOOT;
+ req.msg.rtm_scope = RT_SCOPE_UNIVERSE;
+ req.msg.rtm_type = RTN_UNICAST;
+
+ switch (rule_type) {
+ case FRA_SRC:
+ req.msg.rtm_src_len = 32;
+ netlink_add_rtattr_addr_any(&req.nlh, sizeof(req), FRA_SRC,
+ &iface->primary_addr);
+ break;
+ case FRA_FWMARK:
+ netlink_add_rtattr_l(&req.nlh, sizeof(req), FRA_FWMARK,
+ &iface->fwmark, 4);
+ break;
+ default:
+ log_error("%s: unsupported route rule. Should not happen.",
+ iface->name);
+ }
+
+ if (iface->rule_priority != 0)
+ netlink_add_rtattr_l(&req.nlh, sizeof(req), FRA_PRIORITY,
+ &iface->rule_priority, 4);
+
+ if (!netlink_talk(fd, &req.nlh, sizeof(req), &req.nlh))
+ return -1;
+
+ return netlink_get_error(&req.nlh);
+}
+
+int netlink_rule_del(struct netlink_fd *fd, struct pingu_iface *iface)
+{
+ if (iface->fwmark)
+ netlink_rule_modify(fd, iface, RTM_DELRULE, FRA_FWMARK);
+ return netlink_rule_modify(fd, iface, RTM_DELRULE, FRA_SRC);
+}
+
+int netlink_rule_replace_or_add(struct netlink_fd *fd, struct pingu_iface *iface)
+{
+ netlink_rule_del(fd, iface);
+ if (iface->fwmark)
+ netlink_rule_modify(fd, iface, RTM_NEWRULE, FRA_FWMARK);
+ return netlink_rule_modify(fd, iface, RTM_NEWRULE, FRA_SRC);
+}
+
+static void netlink_link_new_cb(struct nlmsghdr *msg)
+{
+ struct pingu_iface *iface;
+ struct ifinfomsg *ifi = NLMSG_DATA(msg);
+ struct rtattr *rta[IFLA_MAX+1];
+ const char *ifname;
+
+ netlink_parse_rtattr(rta, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(msg));
+ if (rta[IFLA_IFNAME] == NULL)
+ return;
+
+ ifname = RTA_DATA(rta[IFLA_IFNAME]);
+ iface = pingu_iface_get_by_name(ifname);
+ if (iface == NULL)
+ return;
+
+ if (iface->index == 0 && ifi->ifi_index != 0)
+ log_info("New interface: %s", ifname);
+
+ iface->index = ifi->ifi_index;
+ if (ifi->ifi_flags & IFF_LOWER_UP) {
+ log_info("%s: got link", ifname);
+ iface->has_link = 1;
+ }
+}
+
+static void netlink_link_del_cb(struct nlmsghdr *msg)
+{
+ struct pingu_iface *iface;
+ struct ifinfomsg *ifi = NLMSG_DATA(msg);
+ struct rtattr *rta[IFLA_MAX+1];
+ const char *ifname;
+
+ netlink_parse_rtattr(rta, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(msg));
+ if (rta[IFLA_IFNAME] == NULL)
+ return;
+
+ ifname = RTA_DATA(rta[IFLA_IFNAME]);
+ iface = pingu_iface_get_by_name(ifname);
+ if (iface == NULL)
+ return;
+
+ log_info("Interface '%s' deleted", ifname);
+ iface->index = 0;
+ iface->has_link = 0;
+ pingu_host_iface_deleted(iface);
+}
+
+static void netlink_addr_new_cb(struct nlmsghdr *msg)
+{
+ struct pingu_iface *iface;
+ struct ifaddrmsg *ifa = NLMSG_DATA(msg);
+ struct rtattr *rta[IFA_MAX+1];
+ int err;
+
+ if (ifa->ifa_flags & IFA_F_SECONDARY)
+ return;
+
+ netlink_parse_rtattr(rta, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(msg));
+ if (rta[IFA_LOCAL] == NULL)
+ return;
+
+ iface = pingu_iface_get_by_index(ifa->ifa_index);
+ if (iface == NULL || rta[IFA_LOCAL] == NULL)
+ return;
+
+ pingu_iface_set_addr(iface, ifa->ifa_family,
+ RTA_DATA(rta[IFA_LOCAL]),
+ RTA_PAYLOAD(rta[IFA_LOCAL]));
+ pingu_iface_bind_socket(iface, 1);
+ err = netlink_rule_replace_or_add(&talk_fd, iface);
+ if (err == 0)
+ iface->has_route_rule = 1;
+ if (err > 0)
+ log_error("%s: Failed to add route rule: %s", iface->name,
+ strerror(err));
+}
+
+static void netlink_addr_del_cb(struct nlmsghdr *nlmsg)
+{
+ struct pingu_iface *iface;
+ struct ifaddrmsg *ifa = NLMSG_DATA(nlmsg);
+ struct rtattr *rta[IFA_MAX+1];
+
+ if (ifa->ifa_flags & IFA_F_SECONDARY)
+ return;
+
+ netlink_parse_rtattr(rta, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(nlmsg));
+ if (rta[IFA_LOCAL] == NULL)
+ return;
+
+ iface = pingu_iface_get_by_index(ifa->ifa_index);
+ if (iface == NULL)
+ return;
+
+ netlink_rule_del(&talk_fd, iface);
+ pingu_iface_set_addr(iface, 0, NULL, 0);
+}
+
+static struct pingu_route *gw_from_rtmsg(struct pingu_route *gw,
+ struct rtmsg *rtm,
+ struct rtattr **rta)
+{
+ memset(gw, 0, sizeof(*gw));
+ gw->dst_len = rtm->rtm_dst_len;
+ gw->src_len = rtm->rtm_src_len;
+ gw->dest.sa.sa_family = rtm->rtm_family;
+ gw->protocol = rtm->rtm_protocol;
+ gw->scope = rtm->rtm_scope;
+ gw->type = rtm->rtm_type;
+ gw->dev_index = *(int*)RTA_DATA(rta[RTA_OIF]);
+
+ if (rta[RTA_SRC] != NULL)
+ sockaddr_init(&gw->src, rtm->rtm_family, RTA_DATA(rta[RTA_SRC]));
+
+ if (rta[RTA_DST] != NULL)
+ sockaddr_init(&gw->dest, rtm->rtm_family, RTA_DATA(rta[RTA_DST]));
+
+ if (rta[RTA_PRIORITY] != NULL)
+ gw->metric = *(uint32_t *)RTA_DATA(rta[RTA_PRIORITY]);
+
+ if (rta[RTA_GATEWAY] != NULL)
+ sockaddr_init(&gw->gw_addr, rtm->rtm_family, RTA_DATA(rta[RTA_GATEWAY]));
+ return gw;
+}
+
+static void log_route_change(struct pingu_route *route, int table,
+ int action)
+{
+ char routestr[512] = "";
+ char *actionstr = "New";
+ if (action == RTM_DELROUTE)
+ actionstr = "Delete";
+
+ log_info("%s route to %s table %i", actionstr,
+ pingu_route_to_string(route, routestr, sizeof(routestr)),
+ table);
+}
+
+void route_changed_for_iface(struct pingu_iface *iface,
+ struct pingu_route *route, int action)
+{
+ int err = 0;
+ log_route_change(route, iface->route_table, action);
+ /* Kernel will remove the alternate route when we lose the
+ * address so we don't need try remove it ourselves */
+ if (action != RTM_DELROUTE || iface->has_address)
+ err = netlink_route_modify(&talk_fd, action, route,
+ iface->route_table);
+ if (err > 0)
+ log_error("Failed to %s route to table %i",
+ action == RTM_NEWROUTE ? "add" : "delete",
+ iface->route_table);
+ pingu_iface_gw_action(iface, route, action);
+}
+
+static void netlink_route_cb_action(struct nlmsghdr *msg, int action)
+{
+ struct pingu_iface *iface;
+ struct rtmsg *rtm = NLMSG_DATA(msg);
+ struct rtattr *rta[RTA_MAX+1];
+
+ struct pingu_route route;
+
+ /* ignore route changes that we made ourselves via talk_fd */
+ if (msg->nlmsg_pid == getpid())
+ return;
+
+ netlink_parse_rtattr(rta, RTA_MAX, RTM_RTA(rtm), RTM_PAYLOAD(msg));
+ if (rta[RTA_OIF] == NULL || rtm->rtm_family != PF_INET
+ || rtm->rtm_table != RT_TABLE_MAIN)
+ return;
+
+ gw_from_rtmsg(&route, rtm, rta);
+ iface = pingu_iface_get_by_index(route.dev_index);
+ if (iface == NULL)
+ return;
+
+ route_changed_for_iface(iface, &route, action);
+}
+
+static void netlink_route_new_cb(struct nlmsghdr *msg)
+{
+ netlink_route_cb_action(msg, RTM_NEWROUTE);
+}
+
+
+static void netlink_route_del_cb(struct nlmsghdr *msg)
+{
+ netlink_route_cb_action(msg, RTM_DELROUTE);
+}
+
+static const netlink_dispatch_f route_dispatch[RTM_MAX] = {
+ [RTM_NEWLINK] = netlink_link_new_cb,
+ [RTM_DELLINK] = netlink_link_del_cb,
+ [RTM_NEWADDR] = netlink_addr_new_cb,
+ [RTM_DELADDR] = netlink_addr_del_cb,
+ [RTM_NEWROUTE] = netlink_route_new_cb,
+ [RTM_DELROUTE] = netlink_route_del_cb,
+};
+
+static void netlink_read_cb(struct ev_loop *loop, struct ev_io *w, int revents)
+{
+ struct netlink_fd *nfd = container_of(w, struct netlink_fd, io);
+
+ if (revents & EV_READ)
+ netlink_receive(nfd, NULL);
+}
+
+static void netlink_close(struct ev_loop *loop, struct netlink_fd *fd)
+{
+ if (fd->fd >= 0) {
+ if (loop != NULL)
+ ev_io_stop(loop, &fd->io);
+ close(fd->fd);
+ fd->fd = 0;
+ }
+}
+
+static int netlink_open(struct ev_loop *loop, struct netlink_fd *fd,
+ int protocol, int groups)
+{
+ struct sockaddr_nl addr;
+ int buf = NETLINK_KERNEL_BUFFER;
+
+ fd->fd = socket(AF_NETLINK, SOCK_RAW, protocol);
+ fd->seq = time(NULL);
+ if (fd->fd < 0) {
+ log_perror("Cannot open netlink socket");
+ return FALSE;
+ }
+
+ fcntl(fd->fd, F_SETFD, FD_CLOEXEC);
+ if (setsockopt(fd->fd, SOL_SOCKET, SO_SNDBUF, &buf, sizeof(buf)) < 0) {
+ log_perror("SO_SNDBUF");
+ goto error;
+ }
+
+ if (setsockopt(fd->fd, SOL_SOCKET, SO_RCVBUF, &buf, sizeof(buf)) < 0) {
+ log_perror("SO_RCVBUF");
+ goto error;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.nl_family = AF_NETLINK;
+ addr.nl_groups = groups;
+ if (bind(fd->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ log_perror("Cannot bind netlink socket");
+ goto error;
+ }
+
+ ev_io_init(&fd->io, netlink_read_cb, fd->fd, EV_READ);
+ ev_io_start(loop, &fd->io);
+
+ return TRUE;
+
+error:
+ netlink_close(loop, fd);
+ return FALSE;
+}
+
+
+int kernel_route_modify(int action, struct pingu_route *route,
+ int table)
+{
+ log_route_change(route, table, action);
+ return netlink_route_modify(&talk_fd, action, route, table);
+}
+
+int kernel_route_multipath(int action, struct list_head *iface_list, int table)
+{
+ return netlink_route_multipath(&talk_fd, action, iface_list, table);
+}
+
+int kernel_init(struct ev_loop *loop)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(netlink_groups); i++) {
+ netlink_fds[i].dispatch_size = sizeof(route_dispatch) / sizeof(route_dispatch[0]);
+ netlink_fds[i].dispatch = route_dispatch;
+ if (!netlink_open(loop, &netlink_fds[i], NETLINK_ROUTE,
+ netlink_groups[i]))
+ goto err_close_all;
+ }
+
+ netlink_enumerate(&talk_fd, PF_UNSPEC, RTM_GETLINK);
+ netlink_read_cb(loop, &talk_fd.io, EV_READ);
+
+ netlink_enumerate(&talk_fd, PF_UNSPEC, RTM_GETADDR);
+ netlink_read_cb(loop, &talk_fd.io, EV_READ);
+
+ /* man page netlink(7) says that first created netlink socket will
+ * get the getpid() assigned as nlmsg_pid. This is our talk_fd.
+ *
+ * Our route callbacks will ignore route changes made by ourselves
+ * (nlmsg_pid == getpid()) but we still need to get the initial
+ * route enumration. Therefore we use another netlink socket to
+ * "pretend" that it was not us who created those routes and the
+ * route callback will pick them up.
+ */
+ netlink_enumerate(&netlink_fds[1], PF_UNSPEC, RTM_GETROUTE);
+ netlink_read_cb(loop, &talk_fd.io, EV_READ);
+
+ return TRUE;
+
+err_close_all:
+ for (i = 0; i < ARRAY_SIZE(netlink_groups); i++)
+ netlink_close(loop, &netlink_fds[i]);
+
+ return FALSE;
+}
+
+void kernel_cleanup_iface_routes(struct pingu_iface *iface)
+{
+ int err = 0;
+ if (iface->has_route_rule) {
+ err = netlink_rule_del(&talk_fd, iface);
+ if (err == 0)
+ iface->has_route_rule = 0;
+ if (err > 0)
+ log_error("Failed to delete route rule for %s", iface->name);
+ netlink_route_flush(&talk_fd, iface);
+ }
+}
+
+void kernel_close(void)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(netlink_groups); i++)
+ netlink_close(NULL, &netlink_fds[i]);
+}
+
diff --git a/src/pingu_netlink.h b/src/pingu_netlink.h
new file mode 100644
index 0000000..9d99a17
--- /dev/null
+++ b/src/pingu_netlink.h
@@ -0,0 +1,17 @@
+#ifndef PINGU_NETLINK_H
+#define PINGU_NETLINK_H
+
+#include <ev.h>
+#include "pingu_iface.h"
+
+int kernel_init(struct ev_loop *loop);
+int kernel_route_modify(int action, struct pingu_route *route,
+ int table);
+void route_changed_for_iface(struct pingu_iface *iface,
+ struct pingu_route *route, int action);
+int kernel_route_multipath(int action, struct list_head *iface_list,
+ int table);
+void kernel_cleanup_iface_routes(struct pingu_iface *iface);
+void kernel_close(void);
+
+#endif
diff --git a/src/pingu_ping.c b/src/pingu_ping.c
new file mode 100644
index 0000000..186673e
--- /dev/null
+++ b/src/pingu_ping.c
@@ -0,0 +1,157 @@
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <ev.h>
+
+#include "icmp.h"
+#include "list.h"
+#include "log.h"
+#include "pingu_burst.h"
+#include "pingu_host.h"
+#include "pingu_iface.h"
+#include "pingu_ping.h"
+#include "sockaddr_util.h"
+
+#define PING_SEQ_MAX 32000
+
+static int pingu_ping_get_seq(void)
+{
+ static int seq = 0;
+ seq = (seq + 1) % PING_SEQ_MAX;
+ return seq;
+}
+
+static void pingu_ping_free(struct ev_loop *loop, struct pingu_ping *ping)
+{
+ list_del(&ping->ping_list_entry);
+ ev_timer_stop(loop, &ping->timeout_watcher);
+ free(ping);
+}
+
+static void pingu_ping_verify_and_free(struct ev_loop *loop, struct pingu_ping *ping)
+{
+ pingu_host_verify_status(loop, ping->host);
+ pingu_ping_free(loop, ping);
+}
+
+static void pingu_ping_timeout_cb(struct ev_loop *loop, ev_timer *w,
+ int revents)
+{
+ struct pingu_ping *ping = container_of(w, struct pingu_ping, timeout_watcher);
+ log_debug("%s: seq %i (%i/%i) timed out", ping->host->label, ping->seq,
+ ping->host->burst.pings_sent, ping->host->max_retries);
+ pingu_ping_verify_and_free(loop, ping);
+}
+
+static struct pingu_ping *pingu_ping_add(struct ev_loop *loop,
+ struct pingu_host *host, int seq)
+{
+ struct pingu_ping *ping = calloc(1, sizeof(struct pingu_ping));
+ if (ping == NULL)
+ return NULL;
+ ping->seq = seq;
+ ping->host = host;
+ ping->host->burst.pings_sent++;
+ ev_timer_init(&ping->timeout_watcher, pingu_ping_timeout_cb,
+ host->timeout, 0);
+ ev_timer_start(loop, &ping->timeout_watcher);
+ list_add(&ping->ping_list_entry, &host->iface->ping_list);
+ return ping;
+}
+
+static struct pingu_ping *pingu_ping_find(struct icmphdr *icp,
+ union sockaddr_any *from,
+ struct list_head *ping_list)
+{
+ struct pingu_ping *ping;
+ if (icp->type != ICMP_ECHOREPLY || icp->un.echo.id != getpid())
+ return NULL;
+
+ list_for_each_entry(ping, ping_list, ping_list_entry) {
+ if (sockaddr_cmp(&ping->host->burst.saddr, from) == 0
+ && ping->seq == ntohs(icp->un.echo.sequence))
+ return ping;
+ }
+ return NULL;
+}
+
+static void pingu_ping_handle_reply(struct ev_loop *loop,
+ struct pingu_ping *ping)
+{
+ ping->host->burst.pings_replied++;
+ log_debug("%s: got seq %i (%i/%i)", ping->host->label, ping->seq,
+ ping->host->burst.pings_replied,
+ ping->host->required_replies);
+ pingu_ping_verify_and_free(loop, ping);
+}
+
+int pingu_ping_send(struct ev_loop *loop, struct pingu_host *host,
+ int set_status_on_failure)
+{
+ int packetlen = sizeof(struct iphdr) + sizeof(struct icmphdr);
+ struct pingu_ping *ping;
+ int seq, r;
+
+ if (!pingu_iface_usable(host->iface))
+ return pingu_host_set_status(host, PINGU_HOST_STATUS_OFFLINE) - 1;
+
+ seq = pingu_ping_get_seq();
+ r = icmp_send_ping(host->iface->fd, &host->burst.saddr.sa,
+ sizeof(host->burst.saddr), seq, packetlen);
+ if (r < 0) {
+ if (set_status_on_failure)
+ pingu_host_set_status(host, PINGU_HOST_STATUS_OFFLINE);
+ return -1;
+ }
+
+ ping = pingu_ping_add(loop, host, seq);
+ return ping == NULL ? -1 : 0;
+}
+
+void pingu_ping_read_reply(struct ev_loop *loop, struct pingu_iface *iface)
+{
+ union sockaddr_any from;
+ unsigned char buf[1500];
+ struct iphdr *ip = (struct iphdr *) buf;
+ struct pingu_ping *ping;
+
+ int len = icmp_read_reply(iface->fd, &from.sa, sizeof(from), buf,
+ sizeof(buf));
+ if (len <= 0)
+ return;
+ ping = pingu_ping_find((struct icmphdr *) &buf[ip->ihl * 4], &from,
+ &iface->ping_list);
+ if (ping == NULL)
+ return;
+
+ pingu_ping_handle_reply(loop, ping);
+}
+
+void pingu_ping_cleanup(struct ev_loop *loop, struct list_head *ping_list)
+{
+ struct pingu_ping *ping, *n;
+ list_for_each_entry_safe(ping, n, ping_list, ping_list_entry) {
+ pingu_ping_free(loop, ping);
+ }
+}
+
+void pingu_ping_dump(int fd, struct list_head *ping_list, const char *prefix)
+{
+ struct pingu_ping *ping;
+ char str[IF_NAMESIZE + 80];
+ list_for_each_entry(ping, ping_list, ping_list_entry) {
+ snprintf(str, sizeof(str), "%s %s %i\n",
+ prefix, ping->host->host, ping->seq);
+ write(fd, str, strlen(str));
+ }
+}
diff --git a/src/pingu_ping.h b/src/pingu_ping.h
new file mode 100644
index 0000000..e0bda08
--- /dev/null
+++ b/src/pingu_ping.h
@@ -0,0 +1,24 @@
+#ifndef PINGU_PING_H
+#define PINGU_PING_H
+
+#include <ev.h>
+
+#include "list.h"
+#include "pingu_host.h"
+
+#define PINGU_PING_IGNORE_ERROR 0
+#define PINGU_PING_SET_STATUS_ON_ERROR 1
+
+struct pingu_ping {
+ int seq;
+ struct pingu_host *host;
+ struct list_head ping_list_entry;
+ struct ev_timer timeout_watcher;
+};
+
+int pingu_ping_send(struct ev_loop *loop, struct pingu_host *host,
+ int set_status_on_failure);
+void pingu_ping_read_reply(struct ev_loop *loop, struct pingu_iface *iface);
+void pingu_ping_cleanup(struct ev_loop *loop, struct list_head *ping_list);
+void pingu_ping_dump(int fd, struct list_head *ping_list, const char *prefix);
+#endif
diff --git a/src/pingu_route.c b/src/pingu_route.c
new file mode 100644
index 0000000..a2462c1
--- /dev/null
+++ b/src/pingu_route.c
@@ -0,0 +1,168 @@
+
+#include <net/if.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "list.h"
+#include "log.h"
+#include "pingu_route.h"
+
+static void pingu_route_add_sorted(struct list_head *route_list,
+ struct pingu_route *new_gw)
+{
+ struct pingu_route *gw;
+ list_for_each_entry(gw, route_list, route_list_entry) {
+ if (gw->metric > new_gw->metric) {
+ list_add_tail(&new_gw->route_list_entry,
+ &gw->route_list_entry);
+ return;
+ }
+ }
+ list_add_tail(&new_gw->route_list_entry, route_list);
+}
+
+static struct pingu_route *pingu_route_clone(struct pingu_route *gw)
+{
+ struct pingu_route *new_gw = calloc(1, sizeof(struct pingu_route));
+ if (gw == NULL) {
+ log_perror("Failed to allocate gateway");
+ return NULL;
+ }
+ /* copy the fields without overwriting the list entry */
+ memcpy(&new_gw->dest, &gw->dest, sizeof(new_gw->dest));
+ memcpy(&new_gw->gw_addr, &gw->gw_addr, sizeof(new_gw->gw_addr));
+ new_gw->dst_len = gw->dst_len;
+ new_gw->src_len = gw->src_len;
+ new_gw->metric = gw->metric;
+ new_gw->protocol = gw->protocol;
+ new_gw->scope = gw->scope;
+ new_gw->type = gw->type;
+ return new_gw;
+}
+
+char *pingu_route_to_string(struct pingu_route *route,
+ char *buf, size_t bufsize)
+{
+ char deststr[64] = "", gwstr[64] = "", viastr[68] = "";
+ char ifname[IF_NAMESIZE] = "", devstr[IF_NAMESIZE + 5] = "";
+
+ sockaddr_to_string(&route->dest, deststr, sizeof(deststr));
+ sockaddr_to_string(&route->gw_addr, gwstr, sizeof(gwstr));
+ if (gwstr[0] != '\0')
+ snprintf(viastr, sizeof(viastr), "via %s ", gwstr);
+ if (if_indextoname(route->dev_index, ifname) != NULL)
+ snprintf(devstr, sizeof(devstr), "dev %s ", ifname);
+
+ snprintf(buf, bufsize, "%s/%i %s%smetric %i", deststr,
+ route->dst_len, viastr, devstr, route->metric);
+ return buf;
+}
+
+static void log_debug_gw(char *msg, struct pingu_route *gw)
+{
+ char routestr[512] = "";
+ log_debug("%s: %s", msg,
+ pingu_route_to_string(gw, routestr, sizeof(routestr)));
+}
+
+static int gateway_cmp(struct pingu_route *a, struct pingu_route *b)
+{
+ int r;
+ if (a->dst_len != b->dst_len)
+ return a->dst_len - b->dst_len;
+ r = sockaddr_cmp(&a->dest, &b->dest);
+ if (r != 0)
+ return r;
+ r = sockaddr_cmp(&a->gw_addr, &b->gw_addr);
+ if (r != 0)
+ return r;
+ return a->metric - b->metric;
+}
+
+static struct pingu_route *pingu_route_get(struct list_head *route_list,
+ struct pingu_route *gw)
+{
+ struct pingu_route *entry;
+ list_for_each_entry(entry, route_list, route_list_entry) {
+ if (gateway_cmp(entry, gw) == 0)
+ return entry;
+ }
+ return NULL;
+}
+
+void pingu_route_del_all(struct list_head *head)
+{
+ struct pingu_route *gw, *n;
+ list_for_each_entry_safe(gw, n, head, route_list_entry) {
+ list_del(&gw->route_list_entry);
+ free(gw);
+ }
+}
+
+void pingu_route_add(struct list_head *route_list,
+ struct pingu_route *gw)
+{
+ struct pingu_route *new_gw = pingu_route_clone(gw);
+ if (new_gw == NULL)
+ return;
+ pingu_route_add_sorted(route_list, new_gw);
+}
+
+void pingu_route_del(struct list_head *route_list,
+ struct pingu_route *delete)
+{
+ struct pingu_route *gw = pingu_route_get(route_list, delete);
+ if (gw == NULL)
+ return;
+ log_debug_gw("removed", gw);
+ list_del(&gw->route_list_entry);
+ free(gw);
+}
+
+int is_default_gw(struct pingu_route *route)
+{
+ switch (route->dest.sa.sa_family) {
+ case AF_INET:
+ return (route->dest.sin.sin_addr.s_addr == 0);
+ break;
+ case AF_INET6:
+ log_debug("TODO: ipv6");
+ break;
+ }
+ return 0;
+}
+
+struct pingu_route *pingu_route_first_default(struct list_head *route_list)
+{
+ struct pingu_route *entry;
+ list_for_each_entry(entry, route_list, route_list_entry) {
+ if (is_default_gw(entry))
+ return entry;
+ }
+ return NULL;
+}
+
+void pingu_route_dump(int fd, struct list_head *route_list)
+{
+ struct pingu_route *entry;
+ list_for_each_entry(entry, route_list, route_list_entry) {
+ char str[512] = "";
+ pingu_route_to_string(entry, str, sizeof(str));
+ if (str[0] != '\0') {
+ strncat(str, "\n", sizeof(str));
+ write(fd, str, strlen(str));
+ }
+ }
+}
+
+void pingu_route_cleanup(struct list_head *route_list)
+{
+ struct pingu_route *entry, *n;
+ list_for_each_entry_safe(entry, n, route_list, route_list_entry) {
+ list_del(&entry->route_list_entry);
+ free(entry);
+ }
+}
diff --git a/src/pingu_route.h b/src/pingu_route.h
new file mode 100644
index 0000000..bd4abf9
--- /dev/null
+++ b/src/pingu_route.h
@@ -0,0 +1,34 @@
+#ifndef pingu_route_H
+#define pingu_route_H
+
+#include "list.h"
+#include "sockaddr_util.h"
+
+struct pingu_route {
+ union sockaddr_any gw_addr;
+ union sockaddr_any dest;
+ union sockaddr_any src;
+ unsigned char dst_len;
+ unsigned char src_len;
+
+ int metric;
+ int dev_index;
+ unsigned char protocol;
+ unsigned char scope;
+ unsigned char type;
+ struct list_head route_list_entry;
+};
+
+char *pingu_route_to_string(struct pingu_route *route,
+ char *buf, size_t bufsize);
+void pingu_route_del_all(struct list_head *head);
+void pingu_route_add(struct list_head *route_list,
+ struct pingu_route *gw);
+void pingu_route_del(struct list_head *route_list,
+ struct pingu_route *gw);
+int is_default_gw(struct pingu_route *route);
+struct pingu_route *pingu_route_first_default(struct list_head *route_list);
+void pingu_route_dump(int fd, struct list_head *route_list);
+void pingu_route_cleanup(struct list_head *route_list);
+
+#endif
diff --git a/src/pinguctl.c b/src/pinguctl.c
new file mode 100644
index 0000000..02dbe14
--- /dev/null
+++ b/src/pinguctl.c
@@ -0,0 +1,89 @@
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "pingu_adm.h"
+
+static int adm_init(const char *socket_path)
+{
+ struct sockaddr_un sun;
+ int fd;
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strncpy(sun.sun_path, socket_path, sizeof(sun.sun_path));
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) {
+ log_perror("socket");
+ return -1;
+ }
+
+ if (connect(fd, (struct sockaddr *) &sun, sizeof(sun)) < 0) {
+ log_perror(socket_path);
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+static int adm_send_cmd(int fd, const char *cmd)
+{
+ char buf[256];
+ size_t len;
+
+ snprintf(buf, sizeof(buf), "%s\n", cmd);
+ len = strlen(buf);
+ if (write(fd, buf, len) != len)
+ return -1;
+ return len;
+}
+
+static int adm_recv(int fd)
+{
+ char buf[1024];
+ int n, total = 0;
+
+ while (1) {
+ n = recv(fd, buf, sizeof(buf), 0);
+ if (n <= 0)
+ break;
+ write(STDOUT_FILENO, buf, n);
+ total += n;
+ }
+ return total;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *socket_path = DEFAULT_ADM_SOCKET;
+ int i, fd;
+
+ while ((i = getopt(argc, argv, "a:")) != -1) {
+ switch (i) {
+ case 'a':
+ socket_path = optarg;
+ break;
+ }
+ argc -= optind;
+ argv += optind;
+ }
+ log_init("pinguctl", 0);
+ fd = adm_init(socket_path);
+ if (fd < 0)
+ return 1;
+
+ for (i = 1; i < argc; i++) {
+ if (adm_send_cmd(fd, argv[i]) < 0 || adm_recv(fd) < 0)
+ return 1;
+ }
+
+ close(fd);
+ return 0;
+}
diff --git a/src/sockaddr_util.c b/src/sockaddr_util.c
new file mode 100644
index 0000000..57487c1
--- /dev/null
+++ b/src/sockaddr_util.c
@@ -0,0 +1,102 @@
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <string.h>
+
+#include "log.h"
+#include "sockaddr_util.h"
+
+int sockaddr_cmp(union sockaddr_any *a, union sockaddr_any *b)
+{
+ if (a->sa.sa_family != b->sa.sa_family)
+ return a->sa.sa_family - b->sa.sa_family;
+
+ switch (a->sa.sa_family) {
+ case AF_INET:
+ return a->sin.sin_addr.s_addr - b->sin.sin_addr.s_addr;
+ break;
+ case AF_INET6:
+ return memcmp((char *) &a->sin6.sin6_addr,
+ (char *) &b->sin6.sin6_addr,
+ sizeof(a->sin6.sin6_addr));
+ break;
+ }
+ return -1;
+}
+
+union sockaddr_any *sockaddr_set4(union sockaddr_any *sa, void *addr)
+{
+ sa->sa.sa_family = AF_INET;
+ sa->sin.sin_addr.s_addr = *(uint32_t *)addr;
+ return sa;
+}
+
+union sockaddr_any *sockaddr_set6(union sockaddr_any *sa, void *addr)
+{
+ sa->sa.sa_family = AF_INET6;
+ memcpy(&sa->sin6.sin6_addr, addr, sizeof(sa->sin6.sin6_addr));
+ return sa;
+}
+
+union sockaddr_any *sockaddr_init(union sockaddr_any *sa, int family,
+ void *addr)
+{
+ memset(sa, 0, sizeof(*sa));
+ if (addr == NULL)
+ return sa;
+ switch (family) {
+ case AF_INET:
+ return sockaddr_set4(sa, addr);
+ break;
+ case AF_INET6:
+ return sockaddr_set6(sa, addr);
+ break;
+ }
+ return NULL;
+}
+
+union sockaddr_any *sockaddr_from_addrinfo(union sockaddr_any *sa,
+ struct addrinfo *ai)
+{
+ struct sockaddr_in *in = (struct sockaddr_in *)ai->ai_addr;
+ struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)ai->ai_addr;
+ memset(sa, 0, sizeof(*sa));
+ if (ai == NULL)
+ return sa;
+ switch (ai->ai_family) {
+ case AF_INET:
+ return sockaddr_set4(sa, &in->sin_addr);
+ break;
+ case AF_INET6:
+ return sockaddr_set6(sa, &in6->sin6_addr);
+ break;
+ }
+ return NULL;
+}
+
+char *sockaddr_to_string(union sockaddr_any *sa, char *str, size_t size)
+{
+ switch (sa->sa.sa_family) {
+ case AF_INET:
+ inet_ntop(sa->sa.sa_family, &sa->sin.sin_addr, str, size);
+ break;
+ case AF_INET6:
+ inet_ntop(sa->sa.sa_family, &sa->sin6.sin6_addr, str, size);
+ break;
+ }
+ return str;
+}
+
+socklen_t sockaddr_len(union sockaddr_any *sa)
+{
+ socklen_t len = 0;
+ switch (sa->sa.sa_family) {
+ case AF_INET:
+ len = sizeof(sa->sin);
+ break;
+ case AF_INET6:
+ len = sizeof(sa->sin6);
+ break;
+ }
+ return len;
+}
diff --git a/src/sockaddr_util.h b/src/sockaddr_util.h
new file mode 100644
index 0000000..e716f96
--- /dev/null
+++ b/src/sockaddr_util.h
@@ -0,0 +1,27 @@
+/* sockaddr_any utils */
+
+#ifndef SOCKADDR_UTIL_H
+#define SOCKADDR_UTIL_H
+
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netdb.h>
+
+union sockaddr_any {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+};
+
+int sockaddr_cmp(union sockaddr_any *a, union sockaddr_any *b);
+union sockaddr_any *sockaddr_init(union sockaddr_any *sa, int family,
+ void *addr);
+union sockaddr_any *sockaddr_from_addrinfo(union sockaddr_any *sa,
+ struct addrinfo *ai);
+
+char *sockaddr_to_string(union sockaddr_any *sa, char *str, size_t size);
+socklen_t sockaddr_len(union sockaddr_any *sa);
+
+#endif /* SOCKADDR_UTIL_H */
diff --git a/src/xlib.c b/src/xlib.c
new file mode 100644
index 0000000..0bc8123
--- /dev/null
+++ b/src/xlib.c
@@ -0,0 +1,37 @@
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include <err.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "xlib.h"
+
+void *xmalloc(size_t size)
+{
+ void *p = malloc(size);
+ if (p == NULL)
+ err(EXIT_FAILURE, "malloc");
+ return p;
+}
+
+void *xrealloc(void *ptr, size_t size)
+{
+ void *p = realloc(ptr, size);
+ if (p == NULL)
+ err(EXIT_FAILURE, "realloc");
+ return p;
+}
+
+char *xstrdup(const char *str)
+{
+ char *s = strdup(str);
+ if (s == NULL)
+ err(EXIT_FAILURE, "strdup");
+ return s;
+}
+
diff --git a/src/xlib.h b/src/xlib.h
new file mode 100644
index 0000000..a66a6bd
--- /dev/null
+++ b/src/xlib.h
@@ -0,0 +1,9 @@
+#ifndef XLIB_H
+#define XLIB_H
+
+void *xmalloc(size_t size);
+void *xrealloc(void *ptr, size_t size);
+char *xstrdup(const char *str);
+
+int init_sockaddr(struct sockaddr_in *addr, const char *host);
+#endif