diff options
Diffstat (limited to 'src/pingu_iface.c')
-rw-r--r-- | src/pingu_iface.c | 344 |
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); + } +} |