aboutsummaryrefslogtreecommitdiffstats
path: root/src/pingu_iface.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pingu_iface.c')
-rw-r--r--src/pingu_iface.c344
1 files changed, 344 insertions, 0 deletions
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);
+ }
+}