#include #include #include #include #include #include #include #include #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, load_balanced > 1); /* 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, int do_multipath) { 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 (do_multipath) 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, load_balanced > 1); 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) { /* restore main route table */ if (!pingu_iface_gw_is_online(iface)) pingu_iface_update_routes(iface, RTM_NEWROUTE, 0); 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); } }