diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 122 | ||||
-rw-r--r-- | src/icmp.c | 359 | ||||
-rw-r--r-- | src/icmp.h | 22 | ||||
-rw-r--r-- | src/list.h | 112 | ||||
-rw-r--r-- | src/log.c | 72 | ||||
-rw-r--r-- | src/log.h | 9 | ||||
-rw-r--r-- | src/lua-client.c | 81 | ||||
-rw-r--r-- | src/mtu.c | 247 | ||||
-rw-r--r-- | src/netlink.c | 291 | ||||
-rw-r--r-- | src/netlink.h | 6 | ||||
-rw-r--r-- | src/pingu.c | 215 | ||||
-rw-r--r-- | src/pingu.h | 6 | ||||
-rw-r--r-- | src/pingu_adm.c | 157 | ||||
-rw-r--r-- | src/pingu_adm.h | 12 | ||||
-rw-r--r-- | src/pingu_burst.c | 67 | ||||
-rw-r--r-- | src/pingu_burst.h | 24 | ||||
-rw-r--r-- | src/pingu_conf.c | 253 | ||||
-rw-r--r-- | src/pingu_conf.h | 6 | ||||
-rw-r--r-- | src/pingu_host.c | 165 | ||||
-rw-r--r-- | src/pingu_host.h | 45 | ||||
-rw-r--r-- | src/pingu_iface.c | 344 | ||||
-rw-r--r-- | src/pingu_iface.h | 62 | ||||
-rw-r--r-- | src/pingu_netlink.c | 873 | ||||
-rw-r--r-- | src/pingu_netlink.h | 17 | ||||
-rw-r--r-- | src/pingu_ping.c | 157 | ||||
-rw-r--r-- | src/pingu_ping.h | 24 | ||||
-rw-r--r-- | src/pingu_route.c | 168 | ||||
-rw-r--r-- | src/pingu_route.h | 34 | ||||
-rw-r--r-- | src/pinguctl.c | 89 | ||||
-rw-r--r-- | src/sockaddr_util.c | 102 | ||||
-rw-r--r-- | src/sockaddr_util.h | 27 | ||||
-rw-r--r-- | src/xlib.c | 37 | ||||
-rw-r--r-- | src/xlib.h | 9 |
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 |