From f963e054df75677690d9de1e88ac6892648a52bf Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Sat, 30 Jul 2016 00:19:06 +0200 Subject: [PATCH 01/51] Add package gluon-radv-filterd This package drops all incoming router advertisements except for the default router with the best metric according to B.A.T.M.A.N. advanced. Note that advertisements originating from the node itself (for example via gluon-radvd) are not affected. --- package/gluon-radv-filterd/Makefile | 46 ++ package/gluon-radv-filterd/README.md | 28 + package/gluon-radv-filterd/check_site.lua | 3 + .../files/etc/config/gluon-radv-filterd | 4 + .../files/etc/init.d/gluon-radv-filterd | 34 ++ .../files/lib/gluon/ebtables/400-radv-filterd | 3 + .../lib/gluon/upgrade/300-gluon-radv-filterd | 11 + package/gluon-radv-filterd/src/Makefile | 4 + .../src/gluon-radv-filterd.c | 513 ++++++++++++++++++ 9 files changed, 646 insertions(+) create mode 100644 package/gluon-radv-filterd/Makefile create mode 100644 package/gluon-radv-filterd/README.md create mode 100644 package/gluon-radv-filterd/check_site.lua create mode 100644 package/gluon-radv-filterd/files/etc/config/gluon-radv-filterd create mode 100755 package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd create mode 100644 package/gluon-radv-filterd/files/lib/gluon/ebtables/400-radv-filterd create mode 100644 package/gluon-radv-filterd/luasrc/lib/gluon/upgrade/300-gluon-radv-filterd create mode 100644 package/gluon-radv-filterd/src/Makefile create mode 100644 package/gluon-radv-filterd/src/gluon-radv-filterd.c diff --git a/package/gluon-radv-filterd/Makefile b/package/gluon-radv-filterd/Makefile new file mode 100644 index 00000000..fbad5b4d --- /dev/null +++ b/package/gluon-radv-filterd/Makefile @@ -0,0 +1,46 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=gluon-radv-filterd +PKG_VERSION:=1 +PKG_RELEASE:=1 + +PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) + +include $(INCLUDE_DIR)/package.mk + +define Package/gluon-radv-filterd + SECTION:=gluon + CATEGORY:=Gluon + TITLE:=Filter IPv6 router advertisements + DEPENDS:=+gluon-ebtables +endef + +define Package/gluon-radv-filterd/description + Gluon community wifi mesh firmware framework: filter IPv6 router advertisements +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + +define Build/Configure +endef + +define Build/Compile + CFLAGS="$(TARGET_CFLAGS)" CPPFLAGS="$(TARGET_CPPFLAGS)" $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS) +endef + +define Package/gluon-radv-filterd/install + $(CP) ./files/* $(1)/ + + $(INSTALL_DIR) $(1)/usr/sbin/ + $(INSTALL_BIN) $(PKG_BUILD_DIR)/gluon-radv-filterd $(1)/usr/sbin/ +endef + +define Package/gluon-radv-filterd/postinst +#!/bin/sh +$(call GluonCheckSite,check_site.lua) +endef + +$(eval $(call BuildPackage,gluon-radv-filterd)) diff --git a/package/gluon-radv-filterd/README.md b/package/gluon-radv-filterd/README.md new file mode 100644 index 00000000..01e104e0 --- /dev/null +++ b/package/gluon-radv-filterd/README.md @@ -0,0 +1,28 @@ +gluon-radv-filterd +================== +This package drops all incoming router advertisements except for the +default router with the best metric according to B.A.T.M.A.N. advanced. + +Note that advertisements originating from the node itself (for example +via gluon-radvd) are not affected and considered at all. + +"Best" router +------------- +The best router is determined by the TQ that is reported for its originator by +B.A.T.M.A.N. advanced. If, for some reason, another gateway with a better TQ +appears or an existing gateway increases its TQ above that of the chosen +gateway, the chosen gateway will remain selected until the better gateway has a +TQ value at least X higher than the selected gateway. This is called +hysteresis, and X can be specified on the commandline/via UCI/the site.conf and +defaults to 20 (just as for the IPv4 gateway selection feature built into +B.A.T.M.A.N. advanced). + +"Local" routers +--------------- +The package has functionality to assign "local" routers, i.e. those connected +via cable or WLAN instead of via the mesh (technically: appearing in the +`transtable_local`), a fake TQ of 512 so that they are always preferred. +However, if used together with the `gluon-ebtables-filter-ra-dhcp` package, +these router advertisements are filtered anyway and reach neither the node nor +any other client. You currently have to disable the package or insert custom +ebtables rules in order to use local routers. diff --git a/package/gluon-radv-filterd/check_site.lua b/package/gluon-radv-filterd/check_site.lua new file mode 100644 index 00000000..242804bc --- /dev/null +++ b/package/gluon-radv-filterd/check_site.lua @@ -0,0 +1,3 @@ +if need_table('radv_filterd', nil, false) then + need_number('radv_filterd.threshold') +end diff --git a/package/gluon-radv-filterd/files/etc/config/gluon-radv-filterd b/package/gluon-radv-filterd/files/etc/config/gluon-radv-filterd new file mode 100644 index 00000000..32e1bbb9 --- /dev/null +++ b/package/gluon-radv-filterd/files/etc/config/gluon-radv-filterd @@ -0,0 +1,4 @@ +config filterd + option iface 'br-client' + option chain 'RADV_FILTER' + option threshold '20' diff --git a/package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd b/package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd new file mode 100755 index 00000000..8fc08b1e --- /dev/null +++ b/package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd @@ -0,0 +1,34 @@ +#!/bin/sh /etc/rc.common + +USE_PROCD=1 +START=50 +DAEMON=/usr/sbin/gluon-radv-filterd + +validate_filterd_section() { + uci_validate_section gluon-radv-filterd filterd "${1}" \ + 'iface:string' \ + 'chain:string:RADV_FILTER' \ + 'threshold:uinteger:20' +} + +start_service() { + config_load gluon-radv-filterd + config_foreach start_filterd filterd +} + +start_filterd() { + local iface chain threshold + validate_filterd_section "$1" + + procd_open_instance + procd_set_param command $DAEMON -i "$iface" -c "$chain" -t $threshold + procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5} + procd_set_param netdev br-client + procd_set_param stderr 1 + procd_close_instance +} + +service_triggers() { + procd_add_reload_trigger "gluon-radv-filterd" + procd_add_validation "validate_filterd_section" +} diff --git a/package/gluon-radv-filterd/files/lib/gluon/ebtables/400-radv-filterd b/package/gluon-radv-filterd/files/lib/gluon/ebtables/400-radv-filterd new file mode 100644 index 00000000..178084d4 --- /dev/null +++ b/package/gluon-radv-filterd/files/lib/gluon/ebtables/400-radv-filterd @@ -0,0 +1,3 @@ +chain('RADV_FILTER', 'DROP') +rule 'FORWARD -p IPv6 -i bat0 --ip6-protocol ipv6-icmp --ip6-icmp-type router-advertisement -j RADV_FILTER' +rule 'RADV_FILTER -j ACCEPT' diff --git a/package/gluon-radv-filterd/luasrc/lib/gluon/upgrade/300-gluon-radv-filterd b/package/gluon-radv-filterd/luasrc/lib/gluon/upgrade/300-gluon-radv-filterd new file mode 100644 index 00000000..e177d3f5 --- /dev/null +++ b/package/gluon-radv-filterd/luasrc/lib/gluon/upgrade/300-gluon-radv-filterd @@ -0,0 +1,11 @@ +#!/usr/bin/lua + +local site = require 'gluon.site_config' +local uci = (require 'luci.model.uci').cursor() + +if site.radv_filterd and site.radv_filterd.threshold then + uci:foreach('gluon-radv-filterd', 'filterd', function(section) + uci:set('gluon-radv-filterd', section['.name'], 'threshold', site.radv_filterd.threshold) + end) + uci:save('gluon-radv-filterd') +end diff --git a/package/gluon-radv-filterd/src/Makefile b/package/gluon-radv-filterd/src/Makefile new file mode 100644 index 00000000..652c6703 --- /dev/null +++ b/package/gluon-radv-filterd/src/Makefile @@ -0,0 +1,4 @@ +all: gluon-radv-filterd + +gluon-radv-filterd: gluon-radv-filterd.c + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -Wall -o $@ $^ $(LDLIBS) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c new file mode 100644 index 00000000..91b5dd4a --- /dev/null +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -0,0 +1,513 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +// Recheck TQs after this time even if no RA was received +#define MAX_INTERVAL 60 + +// Recheck TQs at most this often, even if new RAs were received (they won't +// become the preferred routers until the TQs have been rechecked) +// Also, the first update will take at least this long +#define MIN_INTERVAL 5 + +// max execution time of a single ebtables call in nanoseconds +#define EBTABLES_TIMEOUT 1e8 // 100ms + +// TQ value assigned to local routers +#define LOCAL_TQ 512 + +#define BUFSIZE 1500 + +#define DEBUGFS "/sys/kernel/debug/batman_adv/%s/" +#define ORIGINATORS DEBUGFS "originators" +#define TRANSTABLE_GLOBAL DEBUGFS "transtable_global" +#define TRANSTABLE_LOCAL DEBUGFS "transtable_local" + +#define F_MAC "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx" +#define F_MAC_IGN "%*2x:%*2x:%*2x:%*2x:%*2x:%*2x" +#define F_MAC_VAR(var) var[0], var[1], var[2], var[3], var[4], var[5] + +#ifdef DEBUG +#define CHECK(stmt) \ + if(!(stmt)) { \ + fprintf(stderr, "check failed: " #stmt "\n"); \ + goto check_failed; \ + } +#define DEBUG_MSG(msg, ...) fprintf(stderr, msg "\n", ##__VA_ARGS__) +#else +#define CHECK(stmt) if(!(stmt)) goto check_failed; +#define DEBUG_MSG(msg, ...) do {} while(0) +#endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0])) +#endif + +typedef uint8_t macaddr_t[ETH_ALEN]; + +struct list_item { + struct list *next; +}; + +#define foreach(item, list) \ + for(item = list; item != NULL; item = item->next) + +struct router { + struct router *next; + macaddr_t src; + time_t eol; + macaddr_t originator; + uint16_t tq; +}; + +struct global { + int sock; + struct router *routers; + const char *mesh_iface; + const char *chain; + uint16_t max_tq; + uint16_t hysteresis_thresh; + struct router *best_router; +} G = { + .mesh_iface = "bat0", +}; + + +static void cleanup() { + struct router *router; + close(G.sock); + + while (G.routers != NULL) { + router = G.routers; + G.routers = router->next; + free(router); + } +} + +static void usage(const char *msg) { + if (msg != NULL && *msg != '\0') { + fprintf(stderr, "ERROR: %s\n\n", msg); + } + fprintf(stderr, + "Usage: %s [-m ] [-t ] -c -i \n\n" + " -m B.A.T.M.A.N. advanced mesh interface used to get metric\n" + " information (\"TQ\") for the available gateways. Default: bat0\n" + " -t Minimum TQ difference required to switch the gateway.\n" + " Default: 0\n" + " -c ebtables chain that should be managed by the daemon. The\n" + " chain already has to exist on program invocation and should\n" + " have a DROP policy. It will be flushed by the program!\n" + " -i Interface to listen on for router advertisements. Should be\n" + " or a bridge on top of it, as no metric\n" + " information will be available for hosts on other interfaces.\n\n", + program_invocation_short_name); + cleanup(); + if (msg == NULL) + exit(EXIT_SUCCESS); + else + exit(EXIT_FAILURE); +} + +#define exit_errmsg(message, ...) { \ + fprintf(stderr, message "\n", ##__VA_ARGS__); \ + cleanup(); \ + exit(1); \ + } + +static inline void exit_errno(const char *message) { + cleanup(); + error(1, errno, "error: %s", message); +} + +static inline void warn_errno(const char *message) { + error(0, errno, "warning: %s", message); +} + +static int init_packet_socket(unsigned int ifindex) { + // generated by tcpdump -i tun "icmp6 and ip6[40] = 134" -dd + // Important: Generate on TUN interface (because the socket is SOCK_DGRAM)! + struct sock_filter radv_filter_code[] = { + { 0x30, 0, 0, 0x00000000 }, + { 0x54, 0, 0, 0x000000f0 }, + { 0x15, 0, 8, 0x00000060 }, + { 0x30, 0, 0, 0x00000006 }, + { 0x15, 3, 0, 0x0000003a }, + { 0x15, 0, 5, 0x0000002c }, + { 0x30, 0, 0, 0x00000028 }, + { 0x15, 0, 3, 0x0000003a }, + { 0x30, 0, 0, 0x00000028 }, + { 0x15, 0, 1, 0x00000086 }, + { 0x06, 0, 0, 0x0000ffff }, + { 0x06, 0, 0, 0x00000000 }, + }; + + struct sock_fprog radv_filter = { + .len = ARRAY_SIZE(radv_filter_code), + .filter = radv_filter_code, + }; + + int sock = socket(AF_PACKET, SOCK_DGRAM|SOCK_CLOEXEC, ETH_P_IPV6); + if (sock < 0) + exit_errno("can't open packet socket"); + setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &radv_filter, sizeof(radv_filter)); + + struct sockaddr_ll bind_iface = { + .sll_family = AF_PACKET, + .sll_protocol = ETH_P_IPV6, + .sll_ifindex = ifindex, + }; + bind(sock, (struct sockaddr *)&bind_iface, sizeof(bind_iface)); + + return sock; +} + +static void parse_cmdline(int argc, char *argv[]) { + int c; + unsigned int ifindex; + unsigned long int threshold; + char *endptr; + while ((c = getopt(argc, argv, "c:hi:m:t:")) != -1) { + switch (c) { + case 'i': + if (G.sock != 0) + usage("-i given more than once"); + ifindex = if_nametoindex(optarg); + if (ifindex == 0) + exit_errmsg("Unknown interface: %s", optarg); + G.sock = init_packet_socket(ifindex); + break; + case 'm': + G.mesh_iface = optarg; + break; + case 'c': + G.chain = optarg; + break; + case 't': + threshold = strtoul(optarg, &endptr, 10); + if (*endptr != '\0') + exit_errmsg("Threshold must be a number: %s", optarg); + if (threshold >= LOCAL_TQ) + exit_errmsg("Threshold too large: %ld (max is %d)", threshold, LOCAL_TQ); + G.hysteresis_thresh = (uint16_t) threshold; + break; + case 'h': + usage(NULL); + break; + default: + usage(""); + break; + } + } +} + +static void handle_ra(int sock) { + struct sockaddr_ll src; + unsigned int addr_size = sizeof(src); + size_t len; + uint8_t buffer[BUFSIZE] __attribute__((aligned(8))); + struct ip6_hdr *pkt; + struct ip6_ext *ext; + struct nd_router_advert *ra; + uint8_t ext_type; + + len = recvfrom(sock, buffer, BUFSIZE, 0, (struct sockaddr *)&src, &addr_size); + + // skip IPv6 headers, ensuring packet is long enough + CHECK(len > sizeof(struct ip6_hdr)); + pkt = (struct ip6_hdr *)buffer; + CHECK(len >= ntohs(pkt->ip6_plen) + sizeof(struct ip6_hdr)); + ext_type = pkt->ip6_nxt; + ext = (void*)pkt + sizeof(struct ip6_hdr); + while (ext_type != IPPROTO_ICMPV6) { + CHECK((void*)ext < (void*)pkt + sizeof(struct ip6_hdr) + len); + CHECK(ext->ip6e_len > 0); + ext_type = ext->ip6e_nxt; + ext = (void*)ext + ext->ip6e_len; + } + + // partially parse router advertisement + CHECK((void*)ext + sizeof(struct nd_router_advert) <= (void*)pkt + sizeof(struct ip6_hdr) + len); + ra = (struct nd_router_advert *) ext; + CHECK(ra->nd_ra_type == ND_ROUTER_ADVERT); + CHECK(ra->nd_ra_code == 0); + // we only want default routers + CHECK(ra->nd_ra_router_lifetime > 0); + + DEBUG_MSG("received valid RA from " F_MAC, F_MAC_VAR(src.sll_addr)); + + // update list of known routers + struct router *router; + foreach(router, G.routers) { + if (!memcmp(router->src, src.sll_addr, sizeof(macaddr_t))) { + break; + } + } + if (!router) { + router = malloc(sizeof(struct router)); + memcpy(router->src, src.sll_addr, 8); + router->next = G.routers; + G.routers = router; + } + router->eol = time(NULL) + ra->nd_ra_router_lifetime; + +check_failed: + return; +} + +static void expire_routers() { + struct router **prev_ptr = &G.routers; + struct router *router; + time_t now = time(NULL); + + foreach(router, G.routers) { + if (router->eol < now) { + DEBUG_MSG("router " F_MAC " expired", F_MAC_VAR(router->src)); + *prev_ptr = router->next; + free(router); + } else { + prev_ptr = &router->next; + } + } +} + +static void update_tqs() { + FILE *f; + struct router *router; + char path[PATH_MAX]; + char *line = NULL; + size_t len = 0; + uint8_t tq; + macaddr_t mac_a, mac_b; + + // reset values + foreach(router, G.routers) { + router->tq = 0; + memset(router->originator, 0, sizeof(macaddr_t)); + } + + // TODO: Currently, we iterate over the whole list of routers all the + // time. Maybe it would be a good idea to sort routers that already + // have the current piece of information to the back. That way, we + // could abort as soon as we hit the first router with the current + // information filled in. + + // translate all router's MAC addresses to originators simultaneously + snprintf(path, PATH_MAX, TRANSTABLE_GLOBAL, G.mesh_iface); + f = fopen(path, "r"); + while (getline(&line, &len, f) != -1) { + if (sscanf(line, " * " F_MAC " (%*3u) via " F_MAC " (%*3u) (0x%*4x) [%*3c]", + F_MAC_VAR(&mac_a), F_MAC_VAR(&mac_b)) != 12) + continue; + + foreach(router, G.routers) { + if (!memcmp(router->src, mac_a, sizeof(macaddr_t))) { + memcpy(router->originator, mac_b, sizeof(macaddr_t)); + break; // foreach + } + } + } + fclose(f); + + // look up TQs of originators + G.max_tq = 0; + snprintf(path, PATH_MAX, ORIGINATORS, G.mesh_iface); + f = fopen(path, "r"); + while (getline(&line, &len, f) != -1) { + if (sscanf(line, F_MAC " %*fs (%hhu) " F_MAC_IGN "[ %*s]: " F_MAC_IGN " (%*3u)", + F_MAC_VAR(&mac_a), &tq) != 7) + continue; + + foreach(router, G.routers) { + if (!memcmp(router->originator, mac_a, sizeof(macaddr_t))) { + router->tq = tq; + if (tq > G.max_tq) + G.max_tq = tq; + break; // foreach + } + } + } + fclose(f); + + // if all routers have a TQ value, we don't need to check translocal + foreach(router, G.routers) { + if (router->tq == 0) + break; + } + if (router != NULL) { + // rate local routers (if present) the highest + snprintf(path, PATH_MAX, TRANSTABLE_LOCAL, G.mesh_iface); + f = fopen(path, "r"); + while (getline(&line, &len, f) != -1) { + if (sscanf(line, " * " F_MAC "[%*5s] %*f", F_MAC_VAR(&mac_a)) != 6) + continue; + + foreach(router, G.routers) { + if (!memcmp(router->src, mac_a, sizeof(macaddr_t))) { + router->tq = LOCAL_TQ; + G.max_tq = LOCAL_TQ; + break; // foreach + } + } + } + fclose(f); + } + + foreach(router, G.routers) { + if (router->tq == 0) { + fprintf(stderr, "didn't find TQ for non-local " F_MAC "\n", F_MAC_VAR(router->src)); + } + } + + free(line); +} + +static int fork_execvp_timeout(struct timespec *timeout, const char *file, const char *const argv[]) { + int ret; + pid_t child; + siginfo_t info; + sigset_t signals; + sigemptyset(&signals); + sigaddset(&signals, SIGCHLD); + + child = fork(); + if (!child) { + // casting discards const, but should be safe + // (see http://stackoverflow.com/q/36925388) + execvp(file, (char**) argv); + error(1, errno, "can't execvp(\"%s\", ...)", file); + } + + sigprocmask(SIG_BLOCK, &signals, NULL); + ret = sigtimedwait(&signals, &info, timeout); + sigprocmask(SIG_UNBLOCK, &signals, NULL); + + if (ret == SIGCHLD) { + if (info.si_pid != child) { + cleanup(); + error_at_line(1, 0, __FILE__, __LINE__, + "BUG: We received a SIGCHLD from a child we didn't spawn (expected PID %d, got %d)", + child, info.si_pid); + } + + waitpid(child, NULL, 0); + + return info.si_status; + } + + if (ret < 0 && errno == EAGAIN) + error(0, 0, "warning: child %d took too long, killing", child); + else if (ret < 0) + warn_errno("sigtimedwait failed, killing child"); + else + error_at_line(1, 0, __FILE__, __LINE__, + "BUG: sigtimedwait() return some other signal than SIGCHLD: %d", + ret); + + kill(child, SIGKILL); + kill(child, SIGCONT); + waitpid(child, NULL, 0); + return -1; +} + +static void update_ebtables() { + struct timespec timeout = { + .tv_nsec = EBTABLES_TIMEOUT, + }; + char mac[18]; + struct router *router; + + if (G.best_router && G.best_router->tq >= G.max_tq - G.hysteresis_thresh) { + DEBUG_MSG(F_MAC " is still good enough with TQ=%d (max_tq=%d), not executing ebtables", + F_MAC_VAR(G.best_router->src), + G.best_router->tq, + G.max_tq); + return; + } + + foreach(router, G.routers) { + if (router->tq == G.max_tq) { + snprintf(mac, sizeof(mac), F_MAC, F_MAC_VAR(router->src)); + break; + } + } + DEBUG_MSG("Determined %s as new best router with TQ=%d", mac, G.max_tq); + G.best_router = router; + + if (fork_execvp_timeout(&timeout, "ebtables", (const char *[]) + { "ebtables", "-F", G.chain, NULL })) + error(0, 0, "warning: flushing ebtables chain %s failed, not adding a new rule", G.chain); + else if (fork_execvp_timeout(&timeout, "ebtables", (const char *[]) + { "ebtables", "-A", G.chain, "-s", mac, "-j", "ACCEPT", NULL })) + error(0, 0, "warning: adding new rule to ebtables chain %s failed", G.chain); +} + +int main(int argc, char *argv[]) { + int retval; + fd_set rfds; + struct timeval tv; + time_t last_update = time(NULL); + + parse_cmdline(argc, argv); + + if (G.sock == 0) + usage("No interface set!"); + + if (G.chain == NULL) + usage("No chain set!"); + + while (1) { + FD_ZERO(&rfds); + FD_SET(G.sock, &rfds); + + tv.tv_sec = MAX_INTERVAL; + tv.tv_usec = 0; + retval = select(G.sock + 1, &rfds, NULL, NULL, &tv); + + if (retval < 0) + exit_errno("select() failed"); + else if (retval) { + if (FD_ISSET(G.sock, &rfds)) { + handle_ra(G.sock); + } + } + else + DEBUG_MSG("select() timeout expired"); + + if (G.routers != NULL && last_update <= time(NULL) - MIN_INTERVAL) { + expire_routers(); + + // all routers could have expired, check again + if (G.routers != NULL) { + update_tqs(); + update_ebtables(); + last_update = time(NULL); + } + } + } + + cleanup(); + return 0; +} From e3b4dc5031ac7e27082d0af4b9eea4517a168a28 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Sat, 26 Nov 2016 12:52:23 +0100 Subject: [PATCH 02/51] gluon-radv-filterd: Update originators only if one is unknown --- .../src/gluon-radv-filterd.c | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 91b5dd4a..af1b3207 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -300,12 +301,18 @@ static void update_tqs() { char *line = NULL; size_t len = 0; uint8_t tq; + bool update_originators = false; + int i; macaddr_t mac_a, mac_b; - // reset values + // reset TQs foreach(router, G.routers) { router->tq = 0; - memset(router->originator, 0, sizeof(macaddr_t)); + for (i = 0; i < 6; i++) + if (router->originator[i] != 0) + break; + if (i >= 6) + update_originators = true; } // TODO: Currently, we iterate over the whole list of routers all the @@ -314,22 +321,24 @@ static void update_tqs() { // could abort as soon as we hit the first router with the current // information filled in. - // translate all router's MAC addresses to originators simultaneously - snprintf(path, PATH_MAX, TRANSTABLE_GLOBAL, G.mesh_iface); - f = fopen(path, "r"); - while (getline(&line, &len, f) != -1) { - if (sscanf(line, " * " F_MAC " (%*3u) via " F_MAC " (%*3u) (0x%*4x) [%*3c]", - F_MAC_VAR(&mac_a), F_MAC_VAR(&mac_b)) != 12) - continue; + if (update_originators) { + // translate all router's MAC addresses to originators simultaneously + snprintf(path, PATH_MAX, TRANSTABLE_GLOBAL, G.mesh_iface); + f = fopen(path, "r"); + while (getline(&line, &len, f) != -1) { + if (sscanf(line, " * " F_MAC " (%*3u) via " F_MAC " (%*3u) (0x%*4x) [%*3c]", + F_MAC_VAR(&mac_a), F_MAC_VAR(&mac_b)) != 12) + continue; - foreach(router, G.routers) { - if (!memcmp(router->src, mac_a, sizeof(macaddr_t))) { - memcpy(router->originator, mac_b, sizeof(macaddr_t)); - break; // foreach + foreach(router, G.routers) { + if (!memcmp(router->src, mac_a, sizeof(macaddr_t))) { + memcpy(router->originator, mac_b, sizeof(macaddr_t)); + break; // foreach + } } } + fclose(f); } - fclose(f); // look up TQs of originators G.max_tq = 0; From 7d20a24608b91b44c58b66ae777d018002732637 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Sat, 26 Nov 2016 12:53:12 +0100 Subject: [PATCH 03/51] gluon-radv-filterd: Work with newer batman-adv versions --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index af1b3207..cc9441a2 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -326,7 +326,9 @@ static void update_tqs() { snprintf(path, PATH_MAX, TRANSTABLE_GLOBAL, G.mesh_iface); f = fopen(path, "r"); while (getline(&line, &len, f) != -1) { - if (sscanf(line, " * " F_MAC " (%*3u) via " F_MAC " (%*3u) (0x%*4x) [%*3c]", + if (sscanf(line, " * " F_MAC " %*d (%*3u) via " F_MAC " (%*3u) (0x%*4x) [%*3c]", + F_MAC_VAR(&mac_a), F_MAC_VAR(&mac_b)) != 12 + && sscanf(line, " * " F_MAC " (%*3u) via " F_MAC " (%*3u) (0x%*4x) [%*3c]", F_MAC_VAR(&mac_a), F_MAC_VAR(&mac_b)) != 12) continue; From 615fb9115998c0ee2aa8677027a04dcb788de0a9 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Sat, 26 Nov 2016 12:59:47 +0100 Subject: [PATCH 04/51] gluon-radv-filterd: Fix upgrade script permissions and compilation --- package/gluon-radv-filterd/Makefile | 4 +++- .../luasrc/lib/gluon/upgrade/300-gluon-radv-filterd | 0 2 files changed, 3 insertions(+), 1 deletion(-) mode change 100644 => 100755 package/gluon-radv-filterd/luasrc/lib/gluon/upgrade/300-gluon-radv-filterd diff --git a/package/gluon-radv-filterd/Makefile b/package/gluon-radv-filterd/Makefile index fbad5b4d..623bf27d 100644 --- a/package/gluon-radv-filterd/Makefile +++ b/package/gluon-radv-filterd/Makefile @@ -6,7 +6,7 @@ PKG_RELEASE:=1 PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) -include $(INCLUDE_DIR)/package.mk +include $(GLUONDIR)/include/package.mk define Package/gluon-radv-filterd SECTION:=gluon @@ -29,10 +29,12 @@ endef define Build/Compile CFLAGS="$(TARGET_CFLAGS)" CPPFLAGS="$(TARGET_CPPFLAGS)" $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS) + $(call GluonSrcDiet,./luasrc,$(PKG_BUILD_DIR)/luadest/) endef define Package/gluon-radv-filterd/install $(CP) ./files/* $(1)/ + $(CP) $(PKG_BUILD_DIR)/luadest/* $(1)/ $(INSTALL_DIR) $(1)/usr/sbin/ $(INSTALL_BIN) $(PKG_BUILD_DIR)/gluon-radv-filterd $(1)/usr/sbin/ diff --git a/package/gluon-radv-filterd/luasrc/lib/gluon/upgrade/300-gluon-radv-filterd b/package/gluon-radv-filterd/luasrc/lib/gluon/upgrade/300-gluon-radv-filterd old mode 100644 new mode 100755 From 3667d6061d924d8c597fd09900cb40cb83a8c790 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Thu, 8 Dec 2016 18:53:54 +0100 Subject: [PATCH 05/51] gluon-radv-filterd: Add license to source file --- .../src/gluon-radv-filterd.c | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index cc9441a2..8b702f57 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -1,3 +1,28 @@ +/* + Copyright (c) 2016 Jan-Philipp Litza + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + #define _GNU_SOURCE #include #include From e4457f2f681f66963c8ee1c37f689aa33d964d6f Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Fri, 9 Dec 2016 20:13:35 +0100 Subject: [PATCH 06/51] gluon-radv-filterd: Fix use-after-free when best router expires --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 8b702f57..3e089529 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -312,6 +312,8 @@ static void expire_routers() { if (router->eol < now) { DEBUG_MSG("router " F_MAC " expired", F_MAC_VAR(router->src)); *prev_ptr = router->next; + if (G.best_router == router) + G.best_router = NULL; free(router); } else { prev_ptr = &router->next; From 2f8eed6f9ab923b3afea9275cda4acc5a9ba9442 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Fri, 9 Dec 2016 20:15:15 +0100 Subject: [PATCH 07/51] gluon-radv-filterd: Add more debugging messages and more detailed errors --- .../gluon-radv-filterd/src/gluon-radv-filterd.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 3e089529..7748e48c 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -361,6 +361,7 @@ static void update_tqs() { foreach(router, G.routers) { if (!memcmp(router->src, mac_a, sizeof(macaddr_t))) { + DEBUG_MSG("Found originator for " F_MAC ", it's " F_MAC, F_MAC_VAR(router->src), F_MAC_VAR(mac_b)); memcpy(router->originator, mac_b, sizeof(macaddr_t)); break; // foreach } @@ -380,6 +381,7 @@ static void update_tqs() { foreach(router, G.routers) { if (!memcmp(router->originator, mac_a, sizeof(macaddr_t))) { + DEBUG_MSG("Found TQ for router " F_MAC " (originator " F_MAC "), it's %d", F_MAC_VAR(router->src), F_MAC_VAR(router->originator), tq); router->tq = tq; if (tq > G.max_tq) G.max_tq = tq; @@ -404,6 +406,7 @@ static void update_tqs() { foreach(router, G.routers) { if (!memcmp(router->src, mac_a, sizeof(macaddr_t))) { + DEBUG_MSG("Found router " F_MAC " in transtable_local, assigning TQ %d", F_MAC_VAR(router->src), LOCAL_TQ); router->tq = LOCAL_TQ; G.max_tq = LOCAL_TQ; break; // foreach @@ -415,7 +418,18 @@ static void update_tqs() { foreach(router, G.routers) { if (router->tq == 0) { - fprintf(stderr, "didn't find TQ for non-local " F_MAC "\n", F_MAC_VAR(router->src)); + for (i = 0; i < 6; i++) + if (router->originator[i] != 0) + break; + if (i >= 6) + fprintf(stderr, + "Unable to find router " F_MAC " in transtable_{global,local}\n", + F_MAC_VAR(router->src)); + else + fprintf(stderr, + "Unable to find TQ for originator " F_MAC " (router " F_MAC ")\n", + F_MAC_VAR(router->originator), + F_MAC_VAR(router->src)); } } From bd85e99078d37d366a22f6364ed6faa353eb7e91 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Fri, 9 Dec 2016 20:15:37 +0100 Subject: [PATCH 08/51] gluon-radv-filterd: Use fscanf() instead of getline() & sscanf() for BATMAN data This reduces the average CPU consumption (on a WDR4300) from 3% to 1%. Also, this commit adds error messages when the parsing fails and makes matching more flexible after all relevant fields have been found. --- .../src/gluon-radv-filterd.c | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 7748e48c..08f1b463 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -352,12 +352,11 @@ static void update_tqs() { // translate all router's MAC addresses to originators simultaneously snprintf(path, PATH_MAX, TRANSTABLE_GLOBAL, G.mesh_iface); f = fopen(path, "r"); - while (getline(&line, &len, f) != -1) { - if (sscanf(line, " * " F_MAC " %*d (%*3u) via " F_MAC " (%*3u) (0x%*4x) [%*3c]", - F_MAC_VAR(&mac_a), F_MAC_VAR(&mac_b)) != 12 - && sscanf(line, " * " F_MAC " (%*3u) via " F_MAC " (%*3u) (0x%*4x) [%*3c]", - F_MAC_VAR(&mac_a), F_MAC_VAR(&mac_b)) != 12) - continue; + // skip header + while (fgetc(f) != '\n'); + while (fgetc(f) != '\n'); + while (fscanf(f, " %*[*+] " F_MAC "%*[0-9 -] (%*3u) via " F_MAC " %*[^]]]\n", + F_MAC_VAR(&mac_a), F_MAC_VAR(&mac_b)) == 12) { foreach(router, G.routers) { if (!memcmp(router->src, mac_a, sizeof(macaddr_t))) { @@ -367,6 +366,10 @@ static void update_tqs() { } } } + if (!feof(f)) { + getline(&line, &len, f); + fprintf(stderr, "Parsing transtable_global aborted at this line: %s\n", line); + } fclose(f); } @@ -374,10 +377,11 @@ static void update_tqs() { G.max_tq = 0; snprintf(path, PATH_MAX, ORIGINATORS, G.mesh_iface); f = fopen(path, "r"); - while (getline(&line, &len, f) != -1) { - if (sscanf(line, F_MAC " %*fs (%hhu) " F_MAC_IGN "[ %*s]: " F_MAC_IGN " (%*3u)", - F_MAC_VAR(&mac_a), &tq) != 7) - continue; + // skip header + while (fgetc(f) != '\n'); + while (fgetc(f) != '\n'); + while (fscanf(f, F_MAC " %*fs (%hhu) " F_MAC_IGN " [ %*[^]]]: " F_MAC_IGN " (%*3u)\n", + F_MAC_VAR(&mac_a), &tq) == 7) { foreach(router, G.routers) { if (!memcmp(router->originator, mac_a, sizeof(macaddr_t))) { @@ -389,6 +393,10 @@ static void update_tqs() { } } } + if (!feof(f)) { + getline(&line, &len, f); + fprintf(stderr, "Parsing originators aborted at this line: %s\n", line); + } fclose(f); // if all routers have a TQ value, we don't need to check translocal @@ -400,10 +408,10 @@ static void update_tqs() { // rate local routers (if present) the highest snprintf(path, PATH_MAX, TRANSTABLE_LOCAL, G.mesh_iface); f = fopen(path, "r"); - while (getline(&line, &len, f) != -1) { - if (sscanf(line, " * " F_MAC "[%*5s] %*f", F_MAC_VAR(&mac_a)) != 6) - continue; - + // skip header + while (fgetc(f) != '\n'); + while (fgetc(f) != '\n'); + while (fscanf(f, " * " F_MAC " [%*5s] %*f", F_MAC_VAR(&mac_a)) == 6) { foreach(router, G.routers) { if (!memcmp(router->src, mac_a, sizeof(macaddr_t))) { DEBUG_MSG("Found router " F_MAC " in transtable_local, assigning TQ %d", F_MAC_VAR(router->src), LOCAL_TQ); @@ -413,6 +421,10 @@ static void update_tqs() { } } } + if (!feof(f)) { + getline(&line, &len, f); + fprintf(stderr, "Parsing transtable_local aborted at this line: %s\n", line); + } fclose(f); } From c887960e90018bdbea8fcc47c4a04a96ac8abd9d Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Fri, 9 Dec 2016 20:24:01 +0100 Subject: [PATCH 09/51] gluon-radv-filterd: Tweak constants As I have seen instances of ebtables being killed with a timeout of 100ms, increasing it to 500ms. Also, to ease the straint on the CPU, increasing minimum time between TQ checks to 15 seconds. --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 08f1b463..57ea8959 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -57,10 +57,10 @@ // Recheck TQs at most this often, even if new RAs were received (they won't // become the preferred routers until the TQs have been rechecked) // Also, the first update will take at least this long -#define MIN_INTERVAL 5 +#define MIN_INTERVAL 15 // max execution time of a single ebtables call in nanoseconds -#define EBTABLES_TIMEOUT 1e8 // 100ms +#define EBTABLES_TIMEOUT 5e8 // 500ms // TQ value assigned to local routers #define LOCAL_TQ 512 From a313af733d5be30a08ae58fa8d94262e6a3d98d7 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Fri, 9 Dec 2016 20:29:11 +0100 Subject: [PATCH 10/51] gluon-radv-filterd: Always output a message when choosing new router --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 57ea8959..56e90e19 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -517,7 +517,16 @@ static void update_ebtables() { break; } } - DEBUG_MSG("Determined %s as new best router with TQ=%d", mac, G.max_tq); + if (G.best_router) + fprintf(stderr, "Switching from " F_MAC " (TQ=%d) to %s (TQ=%d)\n", + F_MAC_VAR(G.best_router->src), + G.best_router->tq, + mac, + G.max_tq); + else + fprintf(stderr, "Switching to %s (TQ=%d)\n", + mac, + G.max_tq); G.best_router = router; if (fork_execvp_timeout(&timeout, "ebtables", (const char *[]) From 43664bf383e3908997a60fabe4bee85da07efd33 Mon Sep 17 00:00:00 2001 From: Julian Labus Date: Thu, 15 Dec 2016 13:26:29 +0100 Subject: [PATCH 11/51] gluon-radv-filterd: updated socket filter --- .../src/gluon-radv-filterd.c | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 56e90e19..848cd7a5 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -173,21 +174,11 @@ static inline void warn_errno(const char *message) { } static int init_packet_socket(unsigned int ifindex) { - // generated by tcpdump -i tun "icmp6 and ip6[40] = 134" -dd - // Important: Generate on TUN interface (because the socket is SOCK_DGRAM)! struct sock_filter radv_filter_code[] = { - { 0x30, 0, 0, 0x00000000 }, - { 0x54, 0, 0, 0x000000f0 }, - { 0x15, 0, 8, 0x00000060 }, - { 0x30, 0, 0, 0x00000006 }, - { 0x15, 3, 0, 0x0000003a }, - { 0x15, 0, 5, 0x0000002c }, - { 0x30, 0, 0, 0x00000028 }, - { 0x15, 0, 3, 0x0000003a }, - { 0x30, 0, 0, 0x00000028 }, - { 0x15, 0, 1, 0x00000086 }, - { 0x06, 0, 0, 0x0000ffff }, - { 0x06, 0, 0, 0x00000000 }, + BPF_STMT(BPF_LD|BPF_B|BPF_ABS, sizeof(struct ip6_hdr) + offsetof(struct icmp6_hdr, icmp6_type)), + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ND_ROUTER_ADVERT, 1, 0), + BPF_STMT(BPF_RET|BPF_K, 0), + BPF_STMT(BPF_RET|BPF_K, 0xffffffff), }; struct sock_fprog radv_filter = { @@ -195,14 +186,16 @@ static int init_packet_socket(unsigned int ifindex) { .filter = radv_filter_code, }; - int sock = socket(AF_PACKET, SOCK_DGRAM|SOCK_CLOEXEC, ETH_P_IPV6); + int sock = socket(AF_PACKET, SOCK_DGRAM|SOCK_CLOEXEC, htons(ETH_P_IPV6)); if (sock < 0) exit_errno("can't open packet socket"); - setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &radv_filter, sizeof(radv_filter)); + int ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &radv_filter, sizeof(radv_filter)); + if (ret < 0) + exit_errno("can't attach socket filter"); struct sockaddr_ll bind_iface = { .sll_family = AF_PACKET, - .sll_protocol = ETH_P_IPV6, + .sll_protocol = htons(ETH_P_IPV6), .sll_ifindex = ifindex, }; bind(sock, (struct sockaddr *)&bind_iface, sizeof(bind_iface)); From 5a4dc1f983266fd079c32a90acd37f603ceb8010 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Wed, 8 Feb 2017 17:05:25 +0100 Subject: [PATCH 12/51] gluon-radv-filterd: Avoid use of GLUONDIR variable --- package/gluon-radv-filterd/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/gluon-radv-filterd/Makefile b/package/gluon-radv-filterd/Makefile index 623bf27d..88f79736 100644 --- a/package/gluon-radv-filterd/Makefile +++ b/package/gluon-radv-filterd/Makefile @@ -6,7 +6,7 @@ PKG_RELEASE:=1 PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) -include $(GLUONDIR)/include/package.mk +include ../gluon.mk define Package/gluon-radv-filterd SECTION:=gluon From 3bf4d129111b94b421a9f6b4d63c2aab42c367c3 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Wed, 8 Feb 2017 17:05:52 +0100 Subject: [PATCH 13/51] gluon-radv-filterd: Ship with own implementation of error() --- .../src/gluon-radv-filterd.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 848cd7a5..b85a2084 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -24,7 +24,6 @@ */ #define _GNU_SOURCE -#include #include #include #include @@ -123,6 +122,18 @@ struct global { }; +static void error(int status, int errnum, char *message, ...) { + va_list ap; + va_start(ap, message); + fflush(stdout); + vfprintf(stderr, message, ap); + if (errnum) + fprintf(stderr, ": %s", strerror(errnum)); + fprintf(stderr, "\n"); + if (status) + exit(status); +} + static void cleanup() { struct router *router; close(G.sock); @@ -464,7 +475,7 @@ static int fork_execvp_timeout(struct timespec *timeout, const char *file, const if (ret == SIGCHLD) { if (info.si_pid != child) { cleanup(); - error_at_line(1, 0, __FILE__, __LINE__, + error(1, 0, "BUG: We received a SIGCHLD from a child we didn't spawn (expected PID %d, got %d)", child, info.si_pid); } @@ -479,8 +490,8 @@ static int fork_execvp_timeout(struct timespec *timeout, const char *file, const else if (ret < 0) warn_errno("sigtimedwait failed, killing child"); else - error_at_line(1, 0, __FILE__, __LINE__, - "BUG: sigtimedwait() return some other signal than SIGCHLD: %d", + error(1, 0, + "BUG: sigtimedwait() returned some other signal than SIGCHLD: %d", ret); kill(child, SIGKILL); From a1fea711da37c6ef24d271ab64620eed15b202e4 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Thu, 9 Feb 2017 19:29:24 +0100 Subject: [PATCH 14/51] gluon-radv-filterd: Add respondd module reporting the chosen gateway --- package/gluon-radv-filterd/Makefile | 9 ++-- package/gluon-radv-filterd/src/Makefile | 5 ++- .../src/gluon-radv-filterd.c | 9 +--- package/gluon-radv-filterd/src/mac.h | 8 ++++ package/gluon-radv-filterd/src/respondd.c | 45 +++++++++++++++++++ 5 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 package/gluon-radv-filterd/src/mac.h create mode 100644 package/gluon-radv-filterd/src/respondd.c diff --git a/package/gluon-radv-filterd/Makefile b/package/gluon-radv-filterd/Makefile index 88f79736..fcf5c552 100644 --- a/package/gluon-radv-filterd/Makefile +++ b/package/gluon-radv-filterd/Makefile @@ -12,11 +12,7 @@ define Package/gluon-radv-filterd SECTION:=gluon CATEGORY:=Gluon TITLE:=Filter IPv6 router advertisements - DEPENDS:=+gluon-ebtables -endef - -define Package/gluon-radv-filterd/description - Gluon community wifi mesh firmware framework: filter IPv6 router advertisements + DEPENDS:=+gluon-ebtables +libgluonutil endef define Build/Prepare @@ -38,6 +34,9 @@ define Package/gluon-radv-filterd/install $(INSTALL_DIR) $(1)/usr/sbin/ $(INSTALL_BIN) $(PKG_BUILD_DIR)/gluon-radv-filterd $(1)/usr/sbin/ + + $(INSTALL_DIR) $(1)/lib/gluon/respondd + $(CP) $(PKG_BUILD_DIR)/respondd.so $(1)/lib/gluon/respondd/radv-filterd.so endef define Package/gluon-radv-filterd/postinst diff --git a/package/gluon-radv-filterd/src/Makefile b/package/gluon-radv-filterd/src/Makefile index 652c6703..82a2bace 100644 --- a/package/gluon-radv-filterd/src/Makefile +++ b/package/gluon-radv-filterd/src/Makefile @@ -1,4 +1,7 @@ -all: gluon-radv-filterd +all: gluon-radv-filterd respondd.so gluon-radv-filterd: gluon-radv-filterd.c $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -Wall -o $@ $^ $(LDLIBS) + +respondd.so: respondd.c + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -shared -fPIC -D_GNU_SOURCE -o $@ $^ $(LDLIBS) -lgluonutil diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index b85a2084..eeef8328 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -45,12 +45,13 @@ #include #include -#include #include #include #include +#include "mac.h" + // Recheck TQs after this time even if no RA was received #define MAX_INTERVAL 60 @@ -72,10 +73,6 @@ #define TRANSTABLE_GLOBAL DEBUGFS "transtable_global" #define TRANSTABLE_LOCAL DEBUGFS "transtable_local" -#define F_MAC "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx" -#define F_MAC_IGN "%*2x:%*2x:%*2x:%*2x:%*2x:%*2x" -#define F_MAC_VAR(var) var[0], var[1], var[2], var[3], var[4], var[5] - #ifdef DEBUG #define CHECK(stmt) \ if(!(stmt)) { \ @@ -92,8 +89,6 @@ #define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0])) #endif -typedef uint8_t macaddr_t[ETH_ALEN]; - struct list_item { struct list *next; }; diff --git a/package/gluon-radv-filterd/src/mac.h b/package/gluon-radv-filterd/src/mac.h new file mode 100644 index 00000000..4635a7b7 --- /dev/null +++ b/package/gluon-radv-filterd/src/mac.h @@ -0,0 +1,8 @@ +#include +#include + +#define F_MAC "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx" +#define F_MAC_IGN "%*2x:%*2x:%*2x:%*2x:%*2x:%*2x" +#define F_MAC_VAR(var) var[0], var[1], var[2], var[3], var[4], var[5] + +typedef uint8_t macaddr_t[ETH_ALEN]; diff --git a/package/gluon-radv-filterd/src/respondd.c b/package/gluon-radv-filterd/src/respondd.c new file mode 100644 index 00000000..fe5ddbfe --- /dev/null +++ b/package/gluon-radv-filterd/src/respondd.c @@ -0,0 +1,45 @@ +#include + +#include +#include +#include + +#include "mac.h" + +static struct json_object * get_radv_filter() { + FILE *f = popen("exec ebtables -L RADV_FILTER", "r"); + char *line = NULL; + size_t len = 0; + macaddr_t mac = { 0 }; + struct json_object *ret = NULL; + + if (!f) + return NULL; + + while (getline(&line, &len, f) > 0) { + if (sscanf(line, "-s " F_MAC " -j ACCEPT\n", F_MAC_VAR(&mac)) == ETH_ALEN) + break; + } + + pclose(f); + + sprintf(line, F_MAC, F_MAC_VAR(mac)); + ret = gluonutil_wrap_string(line); + free(line); + return ret; +} + +static struct json_object * respondd_provider_statistics() { + struct json_object *ret = json_object_new_object(); + + struct json_object *radv_filter = get_radv_filter(); + if (radv_filter) + json_object_object_add(ret, "gateway6", radv_filter); + + return ret; +} + +const struct respondd_provider_info respondd_providers[] = { + {"statistics", respondd_provider_statistics}, + {} +}; From 5f3f371ee8586989d6f747ac25c59ec1f848665b Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Mon, 6 Mar 2017 21:47:03 +0100 Subject: [PATCH 15/51] gluon-radv-filterd: Fix and simplify originators parsing Previously, only one nexthop was recognized. The parsing of the whole file failed immediately when two or more hops were possible for *any* originator (not only for one with a router behind it). This makes the parser ignore most of the line in the originators table. --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index eeef8328..27bdd338 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -379,7 +379,7 @@ static void update_tqs() { // skip header while (fgetc(f) != '\n'); while (fgetc(f) != '\n'); - while (fscanf(f, F_MAC " %*fs (%hhu) " F_MAC_IGN " [ %*[^]]]: " F_MAC_IGN " (%*3u)\n", + while (fscanf(f, F_MAC " %*fs (%hhu) %*[^\n]\n", F_MAC_VAR(&mac_a), &tq) == 7) { foreach(router, G.routers) { From 20d83bca76d866560ab10c3e852b5daf93830986 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Fri, 31 Mar 2017 07:34:09 +0200 Subject: [PATCH 16/51] gluon-radv-filterd: Refactor packet checking Move more to BPF code and do not try to parse extension headers in C that wouldn't have made it through BPF anyway. --- .../src/gluon-radv-filterd.c | 53 +++++++++---------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 27bdd338..86ed5d65 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -48,6 +48,7 @@ #include #include +#include #include #include "mac.h" @@ -181,10 +182,22 @@ static inline void warn_errno(const char *message) { static int init_packet_socket(unsigned int ifindex) { struct sock_filter radv_filter_code[] = { + // check that this is an ICMPv6 packet + BPF_STMT(BPF_LD|BPF_B|BPF_ABS, offsetof(struct ip6_hdr, ip6_nxt)), + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, IPPROTO_ICMPV6, 0, 7), + // check that this is a router advertisement BPF_STMT(BPF_LD|BPF_B|BPF_ABS, sizeof(struct ip6_hdr) + offsetof(struct icmp6_hdr, icmp6_type)), - BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ND_ROUTER_ADVERT, 1, 0), - BPF_STMT(BPF_RET|BPF_K, 0), + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ND_ROUTER_ADVERT, 0, 5), + // check that the code field in the ICMPv6 header is 0 + BPF_STMT(BPF_LD|BPF_B|BPF_ABS, sizeof(struct ip6_hdr) + offsetof(struct nd_router_advert, nd_ra_code)), + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 0, 0, 3), + // check that this is a default route (lifetime > 0) + BPF_STMT(BPF_LD|BPF_B|BPF_ABS, sizeof(struct ip6_hdr) + offsetof(struct nd_router_advert, nd_ra_router_lifetime)), + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 0, 1, 0), + // return true BPF_STMT(BPF_RET|BPF_K, 0xffffffff), + // return false + BPF_STMT(BPF_RET|BPF_K, 0), }; struct sock_fprog radv_filter = { @@ -252,34 +265,16 @@ static void handle_ra(int sock) { struct sockaddr_ll src; unsigned int addr_size = sizeof(src); size_t len; - uint8_t buffer[BUFSIZE] __attribute__((aligned(8))); - struct ip6_hdr *pkt; - struct ip6_ext *ext; - struct nd_router_advert *ra; - uint8_t ext_type; + struct { + struct ip6_hdr ip6; + struct nd_router_advert ra; + } pkt; - len = recvfrom(sock, buffer, BUFSIZE, 0, (struct sockaddr *)&src, &addr_size); + len = recvfrom(sock, &pkt, sizeof(pkt), 0, (struct sockaddr *)&src, &addr_size); - // skip IPv6 headers, ensuring packet is long enough - CHECK(len > sizeof(struct ip6_hdr)); - pkt = (struct ip6_hdr *)buffer; - CHECK(len >= ntohs(pkt->ip6_plen) + sizeof(struct ip6_hdr)); - ext_type = pkt->ip6_nxt; - ext = (void*)pkt + sizeof(struct ip6_hdr); - while (ext_type != IPPROTO_ICMPV6) { - CHECK((void*)ext < (void*)pkt + sizeof(struct ip6_hdr) + len); - CHECK(ext->ip6e_len > 0); - ext_type = ext->ip6e_nxt; - ext = (void*)ext + ext->ip6e_len; - } - - // partially parse router advertisement - CHECK((void*)ext + sizeof(struct nd_router_advert) <= (void*)pkt + sizeof(struct ip6_hdr) + len); - ra = (struct nd_router_advert *) ext; - CHECK(ra->nd_ra_type == ND_ROUTER_ADVERT); - CHECK(ra->nd_ra_code == 0); - // we only want default routers - CHECK(ra->nd_ra_router_lifetime > 0); + // BPF already checked that this is an ICMPv6 RA of a default router + CHECK(len >= sizeof(pkt)); + CHECK(ntohs(pkt.ip6.ip6_plen) + sizeof(struct ip6_hdr) >= sizeof(pkt)); DEBUG_MSG("received valid RA from " F_MAC, F_MAC_VAR(src.sll_addr)); @@ -296,7 +291,7 @@ static void handle_ra(int sock) { router->next = G.routers; G.routers = router; } - router->eol = time(NULL) + ra->nd_ra_router_lifetime; + router->eol = time(NULL) + pkt.ra.nd_ra_router_lifetime; check_failed: return; From 66a26d0d23cf2367a2537880bae353478f95e431 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Fri, 31 Mar 2017 07:41:36 +0200 Subject: [PATCH 17/51] gluon-radv-filterd: Various small fixes due to NeoRaider --- package/gluon-radv-filterd/Makefile | 2 +- package/gluon-radv-filterd/check_site.lua | 2 +- .../lib/gluon/upgrade/300-gluon-radv-filterd | 10 +++--- .../src/gluon-radv-filterd.c | 31 ++++++++++--------- package/gluon-radv-filterd/src/mac.h | 1 + package/gluon-radv-filterd/src/respondd.c | 9 +++--- 6 files changed, 30 insertions(+), 25 deletions(-) diff --git a/package/gluon-radv-filterd/Makefile b/package/gluon-radv-filterd/Makefile index fcf5c552..444894ca 100644 --- a/package/gluon-radv-filterd/Makefile +++ b/package/gluon-radv-filterd/Makefile @@ -24,7 +24,7 @@ define Build/Configure endef define Build/Compile - CFLAGS="$(TARGET_CFLAGS)" CPPFLAGS="$(TARGET_CPPFLAGS)" $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS) + $(call Build/Compile/Default) $(call GluonSrcDiet,./luasrc,$(PKG_BUILD_DIR)/luadest/) endef diff --git a/package/gluon-radv-filterd/check_site.lua b/package/gluon-radv-filterd/check_site.lua index 242804bc..bdd7364b 100644 --- a/package/gluon-radv-filterd/check_site.lua +++ b/package/gluon-radv-filterd/check_site.lua @@ -1,3 +1,3 @@ if need_table('radv_filterd', nil, false) then - need_number('radv_filterd.threshold') + need_number('radv_filterd.threshold') end diff --git a/package/gluon-radv-filterd/luasrc/lib/gluon/upgrade/300-gluon-radv-filterd b/package/gluon-radv-filterd/luasrc/lib/gluon/upgrade/300-gluon-radv-filterd index e177d3f5..99f4ccac 100755 --- a/package/gluon-radv-filterd/luasrc/lib/gluon/upgrade/300-gluon-radv-filterd +++ b/package/gluon-radv-filterd/luasrc/lib/gluon/upgrade/300-gluon-radv-filterd @@ -1,11 +1,11 @@ #!/usr/bin/lua local site = require 'gluon.site_config' -local uci = (require 'luci.model.uci').cursor() +local uci = (require 'simple-uci').cursor() if site.radv_filterd and site.radv_filterd.threshold then - uci:foreach('gluon-radv-filterd', 'filterd', function(section) - uci:set('gluon-radv-filterd', section['.name'], 'threshold', site.radv_filterd.threshold) - end) - uci:save('gluon-radv-filterd') + uci:foreach('gluon-radv-filterd', 'filterd', function(section) + uci:set('gluon-radv-filterd', section['.name'], 'threshold', site.radv_filterd.threshold) + end) + uci:save('gluon-radv-filterd') end diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 86ed5d65..39d65b7a 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -62,7 +62,7 @@ #define MIN_INTERVAL 15 // max execution time of a single ebtables call in nanoseconds -#define EBTABLES_TIMEOUT 5e8 // 500ms +#define EBTABLES_TIMEOUT 500000000 // 500ms // TQ value assigned to local routers #define LOCAL_TQ 512 @@ -287,7 +287,7 @@ static void handle_ra(int sock) { } if (!router) { router = malloc(sizeof(struct router)); - memcpy(router->src, src.sll_addr, 8); + memcpy(router->src, src.sll_addr, sizeof(router->src)); router->next = G.routers; G.routers = router; } @@ -325,14 +325,12 @@ static void update_tqs() { bool update_originators = false; int i; macaddr_t mac_a, mac_b; + macaddr_t unspec = {}; // reset TQs foreach(router, G.routers) { router->tq = 0; - for (i = 0; i < 6; i++) - if (router->originator[i] != 0) - break; - if (i >= 6) + if (!memcmp(router->originator, unspec, sizeof(unspec))) update_originators = true; } @@ -347,8 +345,8 @@ static void update_tqs() { snprintf(path, PATH_MAX, TRANSTABLE_GLOBAL, G.mesh_iface); f = fopen(path, "r"); // skip header - while (fgetc(f) != '\n'); - while (fgetc(f) != '\n'); + while (fgetc(f) != '\n') {} + while (fgetc(f) != '\n') {} while (fscanf(f, " %*[*+] " F_MAC "%*[0-9 -] (%*3u) via " F_MAC " %*[^]]]\n", F_MAC_VAR(&mac_a), F_MAC_VAR(&mac_b)) == 12) { @@ -446,21 +444,26 @@ static int fork_execvp_timeout(struct timespec *timeout, const char *file, const int ret; pid_t child; siginfo_t info; - sigset_t signals; + sigset_t signals, oldsignals; sigemptyset(&signals); sigaddset(&signals, SIGCHLD); child = fork(); - if (!child) { + if (child == 0) { // casting discards const, but should be safe // (see http://stackoverflow.com/q/36925388) execvp(file, (char**) argv); - error(1, errno, "can't execvp(\"%s\", ...)", file); + fprintf(stderr, "can't execvp(\"%s\", ...): %s\n", file, strerror(errno)); + _exit(1); + } + else if (child < 0) { + perror("Failed to fork()"); + return -1; } - sigprocmask(SIG_BLOCK, &signals, NULL); + sigprocmask(SIG_BLOCK, &signals, &oldsignals); ret = sigtimedwait(&signals, &info, timeout); - sigprocmask(SIG_UNBLOCK, &signals, NULL); + sigprocmask(SIG_SETMASK, &oldsignals, NULL); if (ret == SIGCHLD) { if (info.si_pid != child) { @@ -494,7 +497,7 @@ static void update_ebtables() { struct timespec timeout = { .tv_nsec = EBTABLES_TIMEOUT, }; - char mac[18]; + char mac[F_MAC_LEN + 1]; struct router *router; if (G.best_router && G.best_router->tq >= G.max_tq - G.hysteresis_thresh) { diff --git a/package/gluon-radv-filterd/src/mac.h b/package/gluon-radv-filterd/src/mac.h index 4635a7b7..74715ac5 100644 --- a/package/gluon-radv-filterd/src/mac.h +++ b/package/gluon-radv-filterd/src/mac.h @@ -2,6 +2,7 @@ #include #define F_MAC "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx" +#define F_MAC_LEN 17 #define F_MAC_IGN "%*2x:%*2x:%*2x:%*2x:%*2x:%*2x" #define F_MAC_VAR(var) var[0], var[1], var[2], var[3], var[4], var[5] diff --git a/package/gluon-radv-filterd/src/respondd.c b/package/gluon-radv-filterd/src/respondd.c index fe5ddbfe..1a40d31d 100644 --- a/package/gluon-radv-filterd/src/respondd.c +++ b/package/gluon-radv-filterd/src/respondd.c @@ -10,8 +10,9 @@ static struct json_object * get_radv_filter() { FILE *f = popen("exec ebtables -L RADV_FILTER", "r"); char *line = NULL; size_t len = 0; - macaddr_t mac = { 0 }; + macaddr_t mac = {}; struct json_object *ret = NULL; + char macstr[F_MAC_LEN + 1] = ""; if (!f) return NULL; @@ -20,12 +21,12 @@ static struct json_object * get_radv_filter() { if (sscanf(line, "-s " F_MAC " -j ACCEPT\n", F_MAC_VAR(&mac)) == ETH_ALEN) break; } + free(line); pclose(f); - sprintf(line, F_MAC, F_MAC_VAR(mac)); - ret = gluonutil_wrap_string(line); - free(line); + snprintf(macstr, sizeof(macstr), F_MAC, F_MAC_VAR(mac)); + ret = gluonutil_wrap_string(macstr); return ret; } From ccfd1027806354cf5f2f356957a3542bebb4202c Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Fri, 31 Mar 2017 07:51:52 +0200 Subject: [PATCH 18/51] gluon-radv-filterd: Remove all UCI options except threshold --- .../gluon-radv-filterd/files/etc/config/gluon-radv-filterd | 2 -- .../gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/package/gluon-radv-filterd/files/etc/config/gluon-radv-filterd b/package/gluon-radv-filterd/files/etc/config/gluon-radv-filterd index 32e1bbb9..7a7cdaba 100644 --- a/package/gluon-radv-filterd/files/etc/config/gluon-radv-filterd +++ b/package/gluon-radv-filterd/files/etc/config/gluon-radv-filterd @@ -1,4 +1,2 @@ config filterd - option iface 'br-client' - option chain 'RADV_FILTER' option threshold '20' diff --git a/package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd b/package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd index 8fc08b1e..4e1bc179 100755 --- a/package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd +++ b/package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd @@ -6,8 +6,6 @@ DAEMON=/usr/sbin/gluon-radv-filterd validate_filterd_section() { uci_validate_section gluon-radv-filterd filterd "${1}" \ - 'iface:string' \ - 'chain:string:RADV_FILTER' \ 'threshold:uinteger:20' } @@ -21,7 +19,7 @@ start_filterd() { validate_filterd_section "$1" procd_open_instance - procd_set_param command $DAEMON -i "$iface" -c "$chain" -t $threshold + procd_set_param command $DAEMON -i br-client -c RADV_FILTER -t $threshold procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5} procd_set_param netdev br-client procd_set_param stderr 1 From 31660c7402166aa8d602af9e3e609cfa2e9faef1 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Sun, 30 Apr 2017 12:23:23 +0200 Subject: [PATCH 19/51] gluon-radv-filterd: Seperate macros for printf() and scanf() --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 6 +++--- package/gluon-radv-filterd/src/mac.h | 3 ++- package/gluon-radv-filterd/src/respondd.c | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 39d65b7a..b6804dbc 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -348,7 +348,7 @@ static void update_tqs() { while (fgetc(f) != '\n') {} while (fgetc(f) != '\n') {} while (fscanf(f, " %*[*+] " F_MAC "%*[0-9 -] (%*3u) via " F_MAC " %*[^]]]\n", - F_MAC_VAR(&mac_a), F_MAC_VAR(&mac_b)) == 12) { + F_MAC_VAR_REF(mac_a), F_MAC_VAR_REF(mac_b)) == 12) { foreach(router, G.routers) { if (!memcmp(router->src, mac_a, sizeof(macaddr_t))) { @@ -373,7 +373,7 @@ static void update_tqs() { while (fgetc(f) != '\n'); while (fgetc(f) != '\n'); while (fscanf(f, F_MAC " %*fs (%hhu) %*[^\n]\n", - F_MAC_VAR(&mac_a), &tq) == 7) { + F_MAC_VAR_REF(mac_a), &tq) == 7) { foreach(router, G.routers) { if (!memcmp(router->originator, mac_a, sizeof(macaddr_t))) { @@ -403,7 +403,7 @@ static void update_tqs() { // skip header while (fgetc(f) != '\n'); while (fgetc(f) != '\n'); - while (fscanf(f, " * " F_MAC " [%*5s] %*f", F_MAC_VAR(&mac_a)) == 6) { + while (fscanf(f, " * " F_MAC " [%*5s] %*f", F_MAC_VAR_REF(mac_a)) == 6) { foreach(router, G.routers) { if (!memcmp(router->src, mac_a, sizeof(macaddr_t))) { DEBUG_MSG("Found router " F_MAC " in transtable_local, assigning TQ %d", F_MAC_VAR(router->src), LOCAL_TQ); diff --git a/package/gluon-radv-filterd/src/mac.h b/package/gluon-radv-filterd/src/mac.h index 74715ac5..18913097 100644 --- a/package/gluon-radv-filterd/src/mac.h +++ b/package/gluon-radv-filterd/src/mac.h @@ -4,6 +4,7 @@ #define F_MAC "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx" #define F_MAC_LEN 17 #define F_MAC_IGN "%*2x:%*2x:%*2x:%*2x:%*2x:%*2x" -#define F_MAC_VAR(var) var[0], var[1], var[2], var[3], var[4], var[5] +#define F_MAC_VAR(var) (var)[0], (var)[1], (var)[2], (var)[3], (var)[4], (var)[5] +#define F_MAC_VAR_REF(var) &(var)[0], &(var)[1], &(var)[2], &(var)[3], &(var)[4], &(var)[5] typedef uint8_t macaddr_t[ETH_ALEN]; diff --git a/package/gluon-radv-filterd/src/respondd.c b/package/gluon-radv-filterd/src/respondd.c index 1a40d31d..203185b6 100644 --- a/package/gluon-radv-filterd/src/respondd.c +++ b/package/gluon-radv-filterd/src/respondd.c @@ -18,7 +18,7 @@ static struct json_object * get_radv_filter() { return NULL; while (getline(&line, &len, f) > 0) { - if (sscanf(line, "-s " F_MAC " -j ACCEPT\n", F_MAC_VAR(&mac)) == ETH_ALEN) + if (sscanf(line, "-s " F_MAC " -j ACCEPT\n", F_MAC_VAR_REF(mac)) == ETH_ALEN) break; } free(line); From 5b34931a97e45b08d91552a13d4826bf88698dfc Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Fri, 26 May 2017 23:16:24 +0200 Subject: [PATCH 20/51] gluon-radv-filterd: Fix bug in BPF router lifetime filter The router lifetime field actually is 2 bytes long, but we only checked the first one, thus falsely discarding RAs with router lifetime < 256 seconds. --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index b6804dbc..5a2075d4 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -192,7 +192,7 @@ static int init_packet_socket(unsigned int ifindex) { BPF_STMT(BPF_LD|BPF_B|BPF_ABS, sizeof(struct ip6_hdr) + offsetof(struct nd_router_advert, nd_ra_code)), BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 0, 0, 3), // check that this is a default route (lifetime > 0) - BPF_STMT(BPF_LD|BPF_B|BPF_ABS, sizeof(struct ip6_hdr) + offsetof(struct nd_router_advert, nd_ra_router_lifetime)), + BPF_STMT(BPF_LD|BPF_H|BPF_ABS, sizeof(struct ip6_hdr) + offsetof(struct nd_router_advert, nd_ra_router_lifetime)), BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 0, 1, 0), // return true BPF_STMT(BPF_RET|BPF_K, 0xffffffff), From 0aca838f37ca8de4d574b56b199d570127b3207f Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Sun, 20 Aug 2017 14:25:54 +0200 Subject: [PATCH 21/51] gluon-radv-filterd: rename error() -> error_message() --- .../gluon-radv-filterd/src/gluon-radv-filterd.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 5a2075d4..f660ae15 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -118,7 +118,7 @@ struct global { }; -static void error(int status, int errnum, char *message, ...) { +static void error_message(int status, int errnum, char *message, ...) { va_list ap; va_start(ap, message); fflush(stdout); @@ -173,11 +173,11 @@ static void usage(const char *msg) { static inline void exit_errno(const char *message) { cleanup(); - error(1, errno, "error: %s", message); + error_message(1, errno, "error: %s", message); } static inline void warn_errno(const char *message) { - error(0, errno, "warning: %s", message); + error_message(0, errno, "warning: %s", message); } static int init_packet_socket(unsigned int ifindex) { @@ -468,7 +468,7 @@ static int fork_execvp_timeout(struct timespec *timeout, const char *file, const if (ret == SIGCHLD) { if (info.si_pid != child) { cleanup(); - error(1, 0, + error_message(1, 0, "BUG: We received a SIGCHLD from a child we didn't spawn (expected PID %d, got %d)", child, info.si_pid); } @@ -479,11 +479,11 @@ static int fork_execvp_timeout(struct timespec *timeout, const char *file, const } if (ret < 0 && errno == EAGAIN) - error(0, 0, "warning: child %d took too long, killing", child); + error_message(0, 0, "warning: child %d took too long, killing", child); else if (ret < 0) warn_errno("sigtimedwait failed, killing child"); else - error(1, 0, + error_message(1, 0, "BUG: sigtimedwait() returned some other signal than SIGCHLD: %d", ret); @@ -528,10 +528,10 @@ static void update_ebtables() { if (fork_execvp_timeout(&timeout, "ebtables", (const char *[]) { "ebtables", "-F", G.chain, NULL })) - error(0, 0, "warning: flushing ebtables chain %s failed, not adding a new rule", G.chain); + error_message(0, 0, "warning: flushing ebtables chain %s failed, not adding a new rule", G.chain); else if (fork_execvp_timeout(&timeout, "ebtables", (const char *[]) { "ebtables", "-A", G.chain, "-s", mac, "-j", "ACCEPT", NULL })) - error(0, 0, "warning: adding new rule to ebtables chain %s failed", G.chain); + error_message(0, 0, "warning: adding new rule to ebtables chain %s failed", G.chain); } int main(int argc, char *argv[]) { From 93e2820f61c0d742c5b346569e244b771dfb9fc9 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Sun, 20 Aug 2017 14:33:27 +0200 Subject: [PATCH 22/51] gluon-radv-filterd: Some minor fixes and cleanup --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index f660ae15..18d6f303 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -90,10 +90,6 @@ #define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0])) #endif -struct list_item { - struct list *next; -}; - #define foreach(item, list) \ for(item = list; item != NULL; item = item->next) @@ -422,10 +418,7 @@ static void update_tqs() { foreach(router, G.routers) { if (router->tq == 0) { - for (i = 0; i < 6; i++) - if (router->originator[i] != 0) - break; - if (i >= 6) + if (!memcmp(router->originator, unspec, sizeof(unspec))) fprintf(stderr, "Unable to find router " F_MAC " in transtable_{global,local}\n", F_MAC_VAR(router->src)); @@ -448,8 +441,10 @@ static int fork_execvp_timeout(struct timespec *timeout, const char *file, const sigemptyset(&signals); sigaddset(&signals, SIGCHLD); + sigprocmask(SIG_BLOCK, &signals, &oldsignals); child = fork(); if (child == 0) { + sigprocmask(SIG_SETMASK, &oldsignals, NULL); // casting discards const, but should be safe // (see http://stackoverflow.com/q/36925388) execvp(file, (char**) argv); @@ -461,7 +456,6 @@ static int fork_execvp_timeout(struct timespec *timeout, const char *file, const return -1; } - sigprocmask(SIG_BLOCK, &signals, &oldsignals); ret = sigtimedwait(&signals, &info, timeout); sigprocmask(SIG_SETMASK, &oldsignals, NULL); From c9eccb9c47b87fbc66da0bee4083c082e1c5e67b Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 11:03:15 +0100 Subject: [PATCH 23/51] gluon-radv-filterd: Use _GNU_SOURCE by default Both source files require the _GNU_SOURCE preprocessor. But for one file it is defined in the source and for the other one in the Makefile. It is better to have it in the Makefile to avoid that different source files in the same project use different. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/Makefile | 4 +++- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package/gluon-radv-filterd/src/Makefile b/package/gluon-radv-filterd/src/Makefile index 82a2bace..87000139 100644 --- a/package/gluon-radv-filterd/src/Makefile +++ b/package/gluon-radv-filterd/src/Makefile @@ -1,7 +1,9 @@ all: gluon-radv-filterd respondd.so +CPPFLAGS += -D_GNU_SOURCE + gluon-radv-filterd: gluon-radv-filterd.c $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -Wall -o $@ $^ $(LDLIBS) respondd.so: respondd.c - $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -shared -fPIC -D_GNU_SOURCE -o $@ $^ $(LDLIBS) -lgluonutil + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -shared -fPIC -o $@ $^ $(LDLIBS) -lgluonutil diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 18d6f303..3aae38d0 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -23,7 +23,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#define _GNU_SOURCE #include #include #include From 779f17af897df24a8e577bf0a863d824dd625c05 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 11:16:13 +0100 Subject: [PATCH 24/51] gluon-radv-filterd: Fix use-after-free in expire_routers The macro foreach is dereferencing router to get the next node in list. This even happens when the node was just freed in the last iteration (and thus could crash the program). To avoid this crash, the next pointer has to be saved before the node is freed. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 3aae38d0..0fbcb1cf 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -92,6 +92,11 @@ #define foreach(item, list) \ for(item = list; item != NULL; item = item->next) +#define foreach_safe(item, safe, list) \ + for (item = (list); \ + (item) && (((safe) = item->next) || 1); \ + item = safe) + struct router { struct router *next; macaddr_t src; @@ -295,9 +300,10 @@ check_failed: static void expire_routers() { struct router **prev_ptr = &G.routers; struct router *router; + struct router *safe; time_t now = time(NULL); - foreach(router, G.routers) { + foreach_safe(router, safe, G.routers) { if (router->eol < now) { DEBUG_MSG("router " F_MAC " expired", F_MAC_VAR(router->src)); *prev_ptr = router->next; From 8d4a7bdaf9c3b26eca6fc88d2c99dfca6596e893 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Wed, 3 Jan 2018 15:20:21 +0100 Subject: [PATCH 25/51] gluon-radv-filterd: Fix parenthesis in foreach macros --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 0fbcb1cf..1a30666a 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -90,12 +90,12 @@ #endif #define foreach(item, list) \ - for(item = list; item != NULL; item = item->next) + for((item) = (list); (item) != NULL; (item) = (item)->next) #define foreach_safe(item, safe, list) \ - for (item = (list); \ + for ((item) = (list); \ (item) && (((safe) = item->next) || 1); \ - item = safe) + (item) = (safe)) struct router { struct router *next; From 87bf15ec0947faa0fea6d3ced4b0cd23edb1010a Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 11:19:24 +0100 Subject: [PATCH 26/51] gluon-radv-filterd: Remove unused variable Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 1 - 1 file changed, 1 deletion(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 1a30666a..36e64433 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -324,7 +324,6 @@ static void update_tqs() { size_t len = 0; uint8_t tq; bool update_originators = false; - int i; macaddr_t mac_a, mac_b; macaddr_t unspec = {}; From f9b3b2438c9f1af547271b1e425b902c640dc52f Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 11:20:55 +0100 Subject: [PATCH 27/51] gluon-radv-filterd: Keep global variables static These variables are only used in the the same file. They can therefore be static and don't have to be exported by the executable. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 36e64433..82ee060b 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -105,7 +105,7 @@ struct router { uint16_t tq; }; -struct global { +static struct global { int sock; struct router *routers; const char *mesh_iface; From 9d194c3f7f9057cdb6acad17d8dfd47fd81ddb73 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 11:24:10 +0100 Subject: [PATCH 28/51] gluon-radv-filterd: Handle bind errors The bind to an interface can fail and the socket then isn't working as expected. The daemon must therefore handle this problem. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 82ee060b..c6f9e7f2 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -130,7 +130,7 @@ static void error_message(int status, int errnum, char *message, ...) { exit(status); } -static void cleanup() { +static void cleanup(void) { struct router *router; close(G.sock); @@ -217,7 +217,9 @@ static int init_packet_socket(unsigned int ifindex) { .sll_protocol = htons(ETH_P_IPV6), .sll_ifindex = ifindex, }; - bind(sock, (struct sockaddr *)&bind_iface, sizeof(bind_iface)); + ret = bind(sock, (struct sockaddr *)&bind_iface, sizeof(bind_iface)); + if (ret < 0) + exit_errno("can't bind socket"); return sock; } @@ -297,7 +299,7 @@ check_failed: return; } -static void expire_routers() { +static void expire_routers(void) { struct router **prev_ptr = &G.routers; struct router *router; struct router *safe; @@ -316,7 +318,7 @@ static void expire_routers() { } } -static void update_tqs() { +static void update_tqs(void) { FILE *f; struct router *router; char path[PATH_MAX]; @@ -491,7 +493,7 @@ static int fork_execvp_timeout(struct timespec *timeout, const char *file, const return -1; } -static void update_ebtables() { +static void update_ebtables(void) { struct timespec timeout = { .tv_nsec = EBTABLES_TIMEOUT, }; From 86c3fa879a49503b5a3635d71aba8998ac4a13a3 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 11:43:51 +0100 Subject: [PATCH 29/51] gluon-radv-filterd: Use existing type ether_addr for mac addresses Signed-off-by: Sven Eckelmann --- .../src/gluon-radv-filterd.c | 30 +++++++++++-------- package/gluon-radv-filterd/src/mac.h | 13 ++++++-- package/gluon-radv-filterd/src/respondd.c | 3 +- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index c6f9e7f2..9f705c30 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -40,6 +40,7 @@ #include #include +#include #include #include @@ -99,9 +100,9 @@ struct router { struct router *next; - macaddr_t src; + struct ether_addr src; time_t eol; - macaddr_t originator; + struct ether_addr originator; uint16_t tq; }; @@ -265,6 +266,7 @@ static void parse_cmdline(int argc, char *argv[]) { static void handle_ra(int sock) { struct sockaddr_ll src; + struct ether_addr mac; unsigned int addr_size = sizeof(src); size_t len; struct { @@ -278,18 +280,19 @@ static void handle_ra(int sock) { CHECK(len >= sizeof(pkt)); CHECK(ntohs(pkt.ip6.ip6_plen) + sizeof(struct ip6_hdr) >= sizeof(pkt)); - DEBUG_MSG("received valid RA from " F_MAC, F_MAC_VAR(src.sll_addr)); + memcpy(&mac, src.sll_addr, sizeof(mac)); + DEBUG_MSG("received valid RA from " F_MAC, F_MAC_VAR(mac)); // update list of known routers struct router *router; foreach(router, G.routers) { - if (!memcmp(router->src, src.sll_addr, sizeof(macaddr_t))) { + if (!memcmp(&router->src, src.sll_addr, sizeof(struct ether_addr))) { break; } } if (!router) { router = malloc(sizeof(struct router)); - memcpy(router->src, src.sll_addr, sizeof(router->src)); + memcpy(&router->src, src.sll_addr, sizeof(router->src)); router->next = G.routers; G.routers = router; } @@ -326,13 +329,14 @@ static void update_tqs(void) { size_t len = 0; uint8_t tq; bool update_originators = false; - macaddr_t mac_a, mac_b; - macaddr_t unspec = {}; + struct ether_addr mac_a, mac_b; + struct ether_addr unspec; // reset TQs + memset(&unspec, 0, sizeof(unspec)); foreach(router, G.routers) { router->tq = 0; - if (!memcmp(router->originator, unspec, sizeof(unspec))) + if (ether_addr_equal(router->originator, unspec)) update_originators = true; } @@ -353,9 +357,9 @@ static void update_tqs(void) { F_MAC_VAR_REF(mac_a), F_MAC_VAR_REF(mac_b)) == 12) { foreach(router, G.routers) { - if (!memcmp(router->src, mac_a, sizeof(macaddr_t))) { + if (ether_addr_equal(router->src, mac_a)) { DEBUG_MSG("Found originator for " F_MAC ", it's " F_MAC, F_MAC_VAR(router->src), F_MAC_VAR(mac_b)); - memcpy(router->originator, mac_b, sizeof(macaddr_t)); + router->originator = mac_b; break; // foreach } } @@ -378,7 +382,7 @@ static void update_tqs(void) { F_MAC_VAR_REF(mac_a), &tq) == 7) { foreach(router, G.routers) { - if (!memcmp(router->originator, mac_a, sizeof(macaddr_t))) { + if (ether_addr_equal(router->originator, mac_a)) { DEBUG_MSG("Found TQ for router " F_MAC " (originator " F_MAC "), it's %d", F_MAC_VAR(router->src), F_MAC_VAR(router->originator), tq); router->tq = tq; if (tq > G.max_tq) @@ -407,7 +411,7 @@ static void update_tqs(void) { while (fgetc(f) != '\n'); while (fscanf(f, " * " F_MAC " [%*5s] %*f", F_MAC_VAR_REF(mac_a)) == 6) { foreach(router, G.routers) { - if (!memcmp(router->src, mac_a, sizeof(macaddr_t))) { + if (ether_addr_equal(router->src, mac_a)) { DEBUG_MSG("Found router " F_MAC " in transtable_local, assigning TQ %d", F_MAC_VAR(router->src), LOCAL_TQ); router->tq = LOCAL_TQ; G.max_tq = LOCAL_TQ; @@ -424,7 +428,7 @@ static void update_tqs(void) { foreach(router, G.routers) { if (router->tq == 0) { - if (!memcmp(router->originator, unspec, sizeof(unspec))) + if (ether_addr_equal(router->originator, unspec)) fprintf(stderr, "Unable to find router " F_MAC " in transtable_{global,local}\n", F_MAC_VAR(router->src)); diff --git a/package/gluon-radv-filterd/src/mac.h b/package/gluon-radv-filterd/src/mac.h index 18913097..fb1ff398 100644 --- a/package/gluon-radv-filterd/src/mac.h +++ b/package/gluon-radv-filterd/src/mac.h @@ -4,7 +4,14 @@ #define F_MAC "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx" #define F_MAC_LEN 17 #define F_MAC_IGN "%*2x:%*2x:%*2x:%*2x:%*2x:%*2x" -#define F_MAC_VAR(var) (var)[0], (var)[1], (var)[2], (var)[3], (var)[4], (var)[5] -#define F_MAC_VAR_REF(var) &(var)[0], &(var)[1], &(var)[2], &(var)[3], &(var)[4], &(var)[5] +#define F_MAC_VAR(var) \ + (var).ether_addr_octet[0], (var).ether_addr_octet[1], \ + (var).ether_addr_octet[2], (var).ether_addr_octet[3], \ + (var).ether_addr_octet[4], (var).ether_addr_octet[5] +#define F_MAC_VAR_REF(var) \ + &(var).ether_addr_octet[0], &(var).ether_addr_octet[1], \ + &(var).ether_addr_octet[2], &(var).ether_addr_octet[3], \ + &(var).ether_addr_octet[4], &(var).ether_addr_octet[5] -typedef uint8_t macaddr_t[ETH_ALEN]; +#define ether_addr_equal(_a, _b) (memcmp((_a).ether_addr_octet, \ + (_b).ether_addr_octet, ETH_ALEN) == 0) diff --git a/package/gluon-radv-filterd/src/respondd.c b/package/gluon-radv-filterd/src/respondd.c index 203185b6..221f532b 100644 --- a/package/gluon-radv-filterd/src/respondd.c +++ b/package/gluon-radv-filterd/src/respondd.c @@ -2,6 +2,7 @@ #include #include +#include #include #include "mac.h" @@ -10,7 +11,7 @@ static struct json_object * get_radv_filter() { FILE *f = popen("exec ebtables -L RADV_FILTER", "r"); char *line = NULL; size_t len = 0; - macaddr_t mac = {}; + struct ether_addr mac = {}; struct json_object *ret = NULL; char macstr[F_MAC_LEN + 1] = ""; From 7014d9eb14eae90eccf97d7100c797f1787fbffd Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 12:19:15 +0100 Subject: [PATCH 30/51] gluon-radv-filterd: Move router code to extra functions The router access code is spread throughout the program. It is easier to modify it when the common functionality is encapsulated in some helper functions. Signed-off-by: Sven Eckelmann --- .../src/gluon-radv-filterd.c | 104 +++++++++++------- 1 file changed, 66 insertions(+), 38 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 9f705c30..47e408a8 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -264,6 +264,50 @@ static void parse_cmdline(int argc, char *argv[]) { } } +static struct router *router_find_src(const struct ether_addr *src) { + struct router *router; + + foreach(router, G.routers) { + if (ether_addr_equal(router->src, *src)) + return router; + } + + return NULL; +} + +static struct router *router_find_orig(const struct ether_addr *orig) { + struct router *router; + + foreach(router, G.routers) { + if (ether_addr_equal(router->originator, *orig)) + return router; + } + + return NULL; +} + +static struct router *router_add(const struct ether_addr *mac) { + struct router *router; + + router = malloc(sizeof(*router)); + router->src = *mac; + router->next = G.routers; + G.routers = router; + router->eol = 0; + + return router; +} + +static void router_update(const struct ether_addr *mac, uint16_t timeout) { + struct router *router; + + router = router_find_src(mac); + if (!router) + router = router_add(mac); + + router->eol = time(NULL) + timeout; +} + static void handle_ra(int sock) { struct sockaddr_ll src; struct ether_addr mac; @@ -283,20 +327,7 @@ static void handle_ra(int sock) { memcpy(&mac, src.sll_addr, sizeof(mac)); DEBUG_MSG("received valid RA from " F_MAC, F_MAC_VAR(mac)); - // update list of known routers - struct router *router; - foreach(router, G.routers) { - if (!memcmp(&router->src, src.sll_addr, sizeof(struct ether_addr))) { - break; - } - } - if (!router) { - router = malloc(sizeof(struct router)); - memcpy(&router->src, src.sll_addr, sizeof(router->src)); - router->next = G.routers; - G.routers = router; - } - router->eol = time(NULL) + pkt.ra.nd_ra_router_lifetime; + router_update(&mac, pkt.ra.nd_ra_router_lifetime); check_failed: return; @@ -356,13 +387,12 @@ static void update_tqs(void) { while (fscanf(f, " %*[*+] " F_MAC "%*[0-9 -] (%*3u) via " F_MAC " %*[^]]]\n", F_MAC_VAR_REF(mac_a), F_MAC_VAR_REF(mac_b)) == 12) { - foreach(router, G.routers) { - if (ether_addr_equal(router->src, mac_a)) { - DEBUG_MSG("Found originator for " F_MAC ", it's " F_MAC, F_MAC_VAR(router->src), F_MAC_VAR(mac_b)); - router->originator = mac_b; - break; // foreach - } - } + router = router_find_src(&mac_a); + if (!router) + continue; + + DEBUG_MSG("Found originator for " F_MAC ", it's " F_MAC, F_MAC_VAR(router->src), F_MAC_VAR(mac_b)); + router->originator = mac_b; } if (!feof(f)) { getline(&line, &len, f); @@ -381,15 +411,14 @@ static void update_tqs(void) { while (fscanf(f, F_MAC " %*fs (%hhu) %*[^\n]\n", F_MAC_VAR_REF(mac_a), &tq) == 7) { - foreach(router, G.routers) { - if (ether_addr_equal(router->originator, mac_a)) { - DEBUG_MSG("Found TQ for router " F_MAC " (originator " F_MAC "), it's %d", F_MAC_VAR(router->src), F_MAC_VAR(router->originator), tq); - router->tq = tq; - if (tq > G.max_tq) - G.max_tq = tq; - break; // foreach - } - } + router = router_find_orig(&mac_a); + if (!router) + continue; + + DEBUG_MSG("Found TQ for router " F_MAC " (originator " F_MAC "), it's %d", F_MAC_VAR(router->src), F_MAC_VAR(router->originator), tq); + router->tq = tq; + if (tq > G.max_tq) + G.max_tq = tq; } if (!feof(f)) { getline(&line, &len, f); @@ -410,14 +439,13 @@ static void update_tqs(void) { while (fgetc(f) != '\n'); while (fgetc(f) != '\n'); while (fscanf(f, " * " F_MAC " [%*5s] %*f", F_MAC_VAR_REF(mac_a)) == 6) { - foreach(router, G.routers) { - if (ether_addr_equal(router->src, mac_a)) { - DEBUG_MSG("Found router " F_MAC " in transtable_local, assigning TQ %d", F_MAC_VAR(router->src), LOCAL_TQ); - router->tq = LOCAL_TQ; - G.max_tq = LOCAL_TQ; - break; // foreach - } - } + router = router_find_src(&mac_a); + if (!router) + continue; + + DEBUG_MSG("Found router " F_MAC " in transtable_local, assigning TQ %d", F_MAC_VAR(router->src), LOCAL_TQ); + router->tq = LOCAL_TQ; + G.max_tq = LOCAL_TQ; } if (!feof(f)) { getline(&line, &len, f); From 2d6cd71f821952c57e91c0c7bd82b6218ba07c51 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 12:21:39 +0100 Subject: [PATCH 31/51] gluon-radv-filterd: Handle malloc errors The allocation of a new router object can fail. It must therefore be handled to avoid segfaults. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 47e408a8..77a0f4ce 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -290,6 +290,9 @@ static struct router *router_add(const struct ether_addr *mac) { struct router *router; router = malloc(sizeof(*router)); + if (!router) + return NULL; + router->src = *mac; router->next = G.routers; G.routers = router; @@ -304,6 +307,8 @@ static void router_update(const struct ether_addr *mac, uint16_t timeout) { router = router_find_src(mac); if (!router) router = router_add(mac); + if (!router) + return; router->eol = time(NULL) + timeout; } From 07a760494b861a9c99ead1f62c0c984f38e19f2b Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 12:22:46 +0100 Subject: [PATCH 32/51] gluon-radv-filterd: Fix byte order of nd_ra_router_lifetime The ICMPv6 packet is stored in network byte order. It must therefore always be converted to host byteorder before it can be used in calculations. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 77a0f4ce..fde27091 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -332,7 +332,7 @@ static void handle_ra(int sock) { memcpy(&mac, src.sll_addr, sizeof(mac)); DEBUG_MSG("received valid RA from " F_MAC, F_MAC_VAR(mac)); - router_update(&mac, pkt.ra.nd_ra_router_lifetime); + router_update(&mac, ntohs(pkt.ra.nd_ra_router_lifetime)); check_failed: return; From 3c8b9fd281c091e014bee077a25f54a682ed0990 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 12:25:04 +0100 Subject: [PATCH 33/51] gluon-radv-filterd: Finish va_start with va_end All invocations of va_start must have a corresponding va_end. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index fde27091..451c14ba 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -124,6 +124,8 @@ static void error_message(int status, int errnum, char *message, ...) { va_start(ap, message); fflush(stdout); vfprintf(stderr, message, ap); + va_end(ap); + if (errnum) fprintf(stderr, ": %s", strerror(errnum)); fprintf(stderr, "\n"); From 1633c7c0050b11e14c6507be8b3942d2d197fab1 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 12:29:24 +0100 Subject: [PATCH 34/51] gluon-radv-filterd: Check for recvfrom errors The recvfrom can fail and return -1. The caller must check for this error to avoid that it reads uninitialized data from pkt. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 451c14ba..8d515677 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -319,16 +319,17 @@ static void handle_ra(int sock) { struct sockaddr_ll src; struct ether_addr mac; unsigned int addr_size = sizeof(src); - size_t len; + ssize_t len; struct { struct ip6_hdr ip6; struct nd_router_advert ra; } pkt; len = recvfrom(sock, &pkt, sizeof(pkt), 0, (struct sockaddr *)&src, &addr_size); + CHECK(len >= 0); // BPF already checked that this is an ICMPv6 RA of a default router - CHECK(len >= sizeof(pkt)); + CHECK((size_t)len >= sizeof(pkt)); CHECK(ntohs(pkt.ip6.ip6_plen) + sizeof(struct ip6_hdr) >= sizeof(pkt)); memcpy(&mac, src.sll_addr, sizeof(mac)); From a6298493f7627c8a1c45bd90cc6fb5108c3d1a4b Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 12:31:07 +0100 Subject: [PATCH 35/51] gluon-radv-filterd: Fix size argument of recvfrom The 6th argument to recvfrom is not an unsigned int pointer. This may work on systems where socklen_t and unsigned int are both 4 byte but other systems may use 8 byte for that (glibc uses size_t as type for socklen_t and size_t is 8 byte on amd64 and similar architectures). Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 8d515677..d1f8dc76 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -318,7 +318,7 @@ static void router_update(const struct ether_addr *mac, uint16_t timeout) { static void handle_ra(int sock) { struct sockaddr_ll src; struct ether_addr mac; - unsigned int addr_size = sizeof(src); + socklen_t addr_size = sizeof(src); ssize_t len; struct { struct ip6_hdr ip6; From c9f661740c16fa870daf593065217d32f7289ac2 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 12:37:18 +0100 Subject: [PATCH 36/51] gluon-radv-filterd: Move election prereq checks into function The check of prerequisitions is rather long and becomes unreadable. Having it in an extra function makes the code slightly more structured and better readable. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index d1f8dc76..5cd00a59 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -533,6 +533,14 @@ static int fork_execvp_timeout(struct timespec *timeout, const char *file, const return -1; } +static bool election_required(void) +{ + if (!G.best_router) + return true; + + return G.best_router->tq < G.max_tq - G.hysteresis_thresh; +} + static void update_ebtables(void) { struct timespec timeout = { .tv_nsec = EBTABLES_TIMEOUT, @@ -540,7 +548,7 @@ static void update_ebtables(void) { char mac[F_MAC_LEN + 1]; struct router *router; - if (G.best_router && G.best_router->tq >= G.max_tq - G.hysteresis_thresh) { + if (!election_required()) { DEBUG_MSG(F_MAC " is still good enough with TQ=%d (max_tq=%d), not executing ebtables", F_MAC_VAR(G.best_router->src), G.best_router->tq, From 8251de682ac0c301dda31d8c480870ec3f2683f6 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 12:44:54 +0100 Subject: [PATCH 37/51] gluon-radv-filterd: Fix integer underflow with low TQs The TQ of the best router can be lower than the hysteresis_thresh. The check could cause an integer underflow which then causes an election which is not necessary. This can be avoided by reordering the check slightly and only substracting values which will not cause underflows. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 5cd00a59..3cb2d7a5 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -538,7 +538,14 @@ static bool election_required(void) if (!G.best_router) return true; - return G.best_router->tq < G.max_tq - G.hysteresis_thresh; + /* should never happen. G.max_tq also contains G.best_router->tq */ + if (G.max_tq < G.best_router->tq) + return false; + + if ((G.max_tq - G.best_router->tq) <= G.hysteresis_thresh) + return false; + + return true; } static void update_ebtables(void) { From dc70f244c8b538cd4e0e76b5b96f4cf7a7dcf1cf Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 13:08:16 +0100 Subject: [PATCH 38/51] gluon-radv-filterd: Use monotonic time source The value returned by time is not monotonic. It can jump around because it depends on a user configurable clock. This can lead to hangs in the processing of routers. A monotonic clock must be used instead to avoid this problem. Signed-off-by: Sven Eckelmann --- .../src/gluon-radv-filterd.c | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 3cb2d7a5..75f213d9 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -101,7 +101,7 @@ struct router { struct router *next; struct ether_addr src; - time_t eol; + struct timespec eol; struct ether_addr originator; uint16_t tq; }; @@ -133,6 +133,20 @@ static void error_message(int status, int errnum, char *message, ...) { exit(status); } +static int timespec_diff(struct timespec *tv1, struct timespec *tv2, + struct timespec *tvdiff) +{ + tvdiff->tv_sec = tv1->tv_sec - tv2->tv_sec; + if (tv1->tv_nsec < tv2->tv_nsec) { + tvdiff->tv_nsec = 1000000000 + tv1->tv_nsec - tv2->tv_nsec; + tvdiff->tv_sec -= 1; + } else { + tvdiff->tv_nsec = tv1->tv_nsec - tv2->tv_nsec; + } + + return (tvdiff->tv_sec >= 0); +} + static void cleanup(void) { struct router *router; close(G.sock); @@ -298,7 +312,8 @@ static struct router *router_add(const struct ether_addr *mac) { router->src = *mac; router->next = G.routers; G.routers = router; - router->eol = 0; + router->eol.tv_sec = 0; + router->eol.tv_nsec = 0; return router; } @@ -312,7 +327,8 @@ static void router_update(const struct ether_addr *mac, uint16_t timeout) { if (!router) return; - router->eol = time(NULL) + timeout; + clock_gettime(CLOCK_MONOTONIC, &router->eol); + router->eol.tv_sec += timeout; } static void handle_ra(int sock) { @@ -345,10 +361,13 @@ static void expire_routers(void) { struct router **prev_ptr = &G.routers; struct router *router; struct router *safe; - time_t now = time(NULL); + struct timespec now; + struct timespec diff; + + clock_gettime(CLOCK_MONOTONIC, &now); foreach_safe(router, safe, G.routers) { - if (router->eol < now) { + if (timespec_diff(&now, &router->eol, &diff)) { DEBUG_MSG("router " F_MAC " expired", F_MAC_VAR(router->src)); *prev_ptr = router->next; if (G.best_router == router) @@ -593,7 +612,12 @@ int main(int argc, char *argv[]) { int retval; fd_set rfds; struct timeval tv; - time_t last_update = time(NULL); + struct timespec next_update; + struct timespec now; + struct timespec diff; + + clock_gettime(CLOCK_MONOTONIC, &next_update); + next_update.tv_sec += MIN_INTERVAL; parse_cmdline(argc, argv); @@ -621,14 +645,18 @@ int main(int argc, char *argv[]) { else DEBUG_MSG("select() timeout expired"); - if (G.routers != NULL && last_update <= time(NULL) - MIN_INTERVAL) { + clock_gettime(CLOCK_MONOTONIC, &now); + if (G.routers != NULL && + timespec_diff(&now, &next_update, &diff)) { expire_routers(); // all routers could have expired, check again if (G.routers != NULL) { update_tqs(); update_ebtables(); - last_update = time(NULL); + + next_update = now; + next_update.tv_sec += MIN_INTERVAL; } } } From bc3b3e300faa5de72d51e64e5785b39f17604deb Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 14:17:35 +0100 Subject: [PATCH 39/51] gluon-radv-filterd: Don't kill daemon when select is interrupted The select can be interrupted when it receives a signal. But the signal might be handled and thus it should not result in an kill. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 75f213d9..e47ea543 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -635,9 +635,10 @@ int main(int argc, char *argv[]) { tv.tv_usec = 0; retval = select(G.sock + 1, &rfds, NULL, NULL, &tv); - if (retval < 0) - exit_errno("select() failed"); - else if (retval) { + if (retval < 0) { + if (errno != EINTR) + exit_errno("select() failed"); + } else if (retval) { if (FD_ISSET(G.sock, &rfds)) { handle_ra(G.sock); } From c39a62a63a5d268c247da35187c14e6a0a5902e8 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 14:32:39 +0100 Subject: [PATCH 40/51] gluon-radv-filterd: Reset chain when daemon shuts down The daemon must make sure that it doesn't filter any incoming router advertisement when it was shut down. This can be achieved by flushing all current rules and/or adding an ACCEPT all rule at the end. When both commands work, the state of the chain will be the same as /lib/gluon/ebtables/400-radv-filter created it. This doesn't handle the problem that the daemon may have been crashed and thus the chain is in an undefined state. Signed-off-by: Sven Eckelmann --- .../gluon-radv-filterd/src/gluon-radv-filterd.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index e47ea543..262509db 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -118,6 +118,8 @@ static struct global { .mesh_iface = "bat0", }; +static int fork_execvp_timeout(struct timespec *timeout, const char *file, + const char *const argv[]); static void error_message(int status, int errnum, char *message, ...) { va_list ap; @@ -149,6 +151,10 @@ static int timespec_diff(struct timespec *tv1, struct timespec *tv2, static void cleanup(void) { struct router *router; + struct timespec timeout = { + .tv_nsec = EBTABLES_TIMEOUT, + }; + close(G.sock); while (G.routers != NULL) { @@ -156,6 +162,17 @@ static void cleanup(void) { G.routers = router->next; free(router); } + + if (G.chain) { + /* Reset chain to accept everything again */ + if (fork_execvp_timeout(&timeout, "ebtables", (const char *[]) + { "ebtables", "-F", G.chain, NULL })) + DEBUG_MSG("warning: flushing ebtables chain %s failed, not adding a new rule", G.chain); + + if (fork_execvp_timeout(&timeout, "ebtables", (const char *[]) + { "ebtables", "-A", G.chain, "-j", "ACCEPT", NULL })) + DEBUG_MSG("warning: adding new rule to ebtables chain %s failed", G.chain); + } } static void usage(const char *msg) { From 34daf355291849e295cfdd6178ac681dc4d6da67 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 15:16:32 +0100 Subject: [PATCH 41/51] gluon-radv-filterd: Initialize router->originator after alloc The memory returned after malloc is not initialized. It must be initialized before it is accessed in update_tqs and compared against 00:00:00:00:00:00. Otherwise the TQ retrievel could fail because the originator address is never updated. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 262509db..b36a058b 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -331,6 +331,7 @@ static struct router *router_add(const struct ether_addr *mac) { G.routers = router; router->eol.tv_sec = 0; router->eol.tv_nsec = 0; + memset(&router->originator, 0, sizeof(router->originator)); return router; } From b06f12669a25d1f64641045d5b1de97eee76651c Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 15:08:18 +0100 Subject: [PATCH 42/51] gluon-radv-filterd: Use generic netlink to request batman-adv data The correct way to get the data from batman-adv is not to try to parse the freeform debugfs files. Instead, the generic netlink family "batadv" should be used to request the tables in binary form. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/Makefile | 6 +- package/gluon-radv-filterd/src/Makefile | 40 +++ .../src/gluon-radv-filterd.c | 268 ++++++++++++------ package/gluon-radv-filterd/src/mac.h | 3 +- 4 files changed, 231 insertions(+), 86 deletions(-) diff --git a/package/gluon-radv-filterd/Makefile b/package/gluon-radv-filterd/Makefile index 444894ca..23daa42f 100644 --- a/package/gluon-radv-filterd/Makefile +++ b/package/gluon-radv-filterd/Makefile @@ -12,9 +12,13 @@ define Package/gluon-radv-filterd SECTION:=gluon CATEGORY:=Gluon TITLE:=Filter IPv6 router advertisements - DEPENDS:=+gluon-ebtables +libgluonutil + DEPENDS:=+gluon-ebtables +libgluonutil +libbatadv +libnl-tiny endef +MAKE_VARS += \ + LIBNL_NAME="libnl-tiny" \ + LIBNL_GENL_NAME="libnl-tiny" + define Build/Prepare mkdir -p $(PKG_BUILD_DIR) $(CP) ./src/* $(PKG_BUILD_DIR)/ diff --git a/package/gluon-radv-filterd/src/Makefile b/package/gluon-radv-filterd/src/Makefile index 87000139..17b65584 100644 --- a/package/gluon-radv-filterd/src/Makefile +++ b/package/gluon-radv-filterd/src/Makefile @@ -2,6 +2,46 @@ all: gluon-radv-filterd respondd.so CPPFLAGS += -D_GNU_SOURCE +ifeq ($(origin PKG_CONFIG), undefined) + PKG_CONFIG = pkg-config + ifeq ($(shell which $(PKG_CONFIG) 2>/dev/null),) + $(error $(PKG_CONFIG) not found) + endif +endif + +ifeq ($(origin LIBNL_CFLAGS) $(origin LIBNL_LDLIBS), undefined undefined) + LIBNL_NAME ?= libnl-3.0 + ifeq ($(shell $(PKG_CONFIG) --modversion $(LIBNL_NAME) 2>/dev/null),) + $(error No $(LIBNL_NAME) development libraries found!) + endif + LIBNL_CFLAGS += $(shell $(PKG_CONFIG) --cflags $(LIBNL_NAME)) + LIBNL_LDLIBS += $(shell $(PKG_CONFIG) --libs $(LIBNL_NAME)) +endif +CFLAGS += $(LIBNL_CFLAGS) +LDLIBS += $(LIBNL_LDLIBS) + +ifeq ($(origin LIBNL_GENL_CFLAGS) $(origin LIBNL_GENL_LDLIBS), undefined undefined) + LIBNL_GENL_NAME ?= libnl-genl-3.0 + ifeq ($(shell $(PKG_CONFIG) --modversion $(LIBNL_GENL_NAME) 2>/dev/null),) + $(error No $(LIBNL_GENL_NAME) development libraries found!) + endif + LIBNL_GENL_CFLAGS += $(shell $(PKG_CONFIG) --cflags $(LIBNL_GENL_NAME)) + LIBNL_GENL_LDLIBS += $(shell $(PKG_CONFIG) --libs $(LIBNL_GENL_NAME)) +endif +CFLAGS += $(LIBNL_GENL_CFLAGS) +LDLIBS += $(LIBNL_GENL_LDLIBS) + +ifeq ($(origin LIBBATADV_CFLAGS) $(origin LIBBATADV_LDLIBS), undefined undefined) + LIBBATADV_NAME ?= libbatadv + ifeq ($(shell $(PKG_CONFIG) --modversion $(LIBBATADV_NAME) 2>/dev/null),) + $(error No $(LIBBATADV_NAME) development libraries found!) + endif + LIBBATADV_CFLAGS += $(shell $(PKG_CONFIG) --cflags $(LIBBATADV_NAME)) + LIBBATADV_LDLIBS += $(shell $(PKG_CONFIG) --libs $(LIBBATADV_NAME)) +endif +CFLAGS += $(LIBBATADV_CFLAGS) +LDLIBS += $(LIBBATADV_LDLIBS) + gluon-radv-filterd: gluon-radv-filterd.c $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -Wall -o $@ $^ $(LDLIBS) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index b36a058b..60786a16 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -1,5 +1,6 @@ /* Copyright (c) 2016 Jan-Philipp Litza + Copyright (c) 2017 Sven Eckelmann All rights reserved. Redistribution and use in source and binary forms, with or without @@ -51,6 +52,11 @@ #include #include +#include +#include +#include +#include + #include "mac.h" // Recheck TQs after this time even if no RA was received @@ -69,11 +75,6 @@ #define BUFSIZE 1500 -#define DEBUGFS "/sys/kernel/debug/batman_adv/%s/" -#define ORIGINATORS DEBUGFS "originators" -#define TRANSTABLE_GLOBAL DEBUGFS "transtable_global" -#define TRANSTABLE_LOCAL DEBUGFS "transtable_local" - #ifdef DEBUG #define CHECK(stmt) \ if(!(stmt)) { \ @@ -397,16 +398,167 @@ static void expire_routers(void) { } } -static void update_tqs(void) { - FILE *f; - struct router *router; - char path[PATH_MAX]; - char *line = NULL; - size_t len = 0; - uint8_t tq; - bool update_originators = false; +static int parse_tt_global(struct nl_msg *msg, + void *arg __attribute__((unused))) +{ + static const enum batadv_nl_attrs mandatory[] = { + BATADV_ATTR_TT_ADDRESS, + BATADV_ATTR_ORIG_ADDRESS, + }; + struct nlattr *attrs[BATADV_ATTR_MAX + 1]; + struct nlmsghdr *nlh = nlmsg_hdr(msg); struct ether_addr mac_a, mac_b; + struct genlmsghdr *ghdr; + struct router *router; + uint8_t *addr; + uint8_t *orig; + + // parse netlink entry + if (!genlmsg_valid_hdr(nlh, 0)) + return NL_OK; + + ghdr = nlmsg_data(nlh); + + if (ghdr->cmd != BATADV_CMD_GET_TRANSTABLE_GLOBAL) + return NL_OK; + + if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), + genlmsg_len(ghdr), batadv_genl_policy)) { + return NL_OK; + } + + if (batadv_genl_missing_attrs(attrs, mandatory, ARRAY_SIZE(mandatory))) + return NL_OK; + + addr = nla_data(attrs[BATADV_ATTR_TT_ADDRESS]); + orig = nla_data(attrs[BATADV_ATTR_ORIG_ADDRESS]); + + if (!attrs[BATADV_ATTR_FLAG_BEST]) + return NL_OK; + + MAC2ETHER(mac_a, addr); + MAC2ETHER(mac_b, orig); + + // update router + router = router_find_src(&mac_a); + if (!router) + return NL_OK; + + DEBUG_MSG("Found originator for " F_MAC ", it's " F_MAC, + F_MAC_VAR(router->src), F_MAC_VAR(mac_b)); + router->originator = mac_b; + + return NL_OK; +} + +static int parse_originator(struct nl_msg *msg, + void *arg __attribute__((unused))) +{ + + static const enum batadv_nl_attrs mandatory[] = { + BATADV_ATTR_ORIG_ADDRESS, + BATADV_ATTR_TQ, + }; + struct nlattr *attrs[BATADV_ATTR_MAX + 1]; + struct nlmsghdr *nlh = nlmsg_hdr(msg); + struct ether_addr mac_a; + struct genlmsghdr *ghdr; + struct router *router; + uint8_t *orig; + uint8_t tq; + + // parse netlink entry + if (!genlmsg_valid_hdr(nlh, 0)) + return NL_OK; + + ghdr = nlmsg_data(nlh); + + if (ghdr->cmd != BATADV_CMD_GET_ORIGINATORS) + return NL_OK; + + if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), + genlmsg_len(ghdr), batadv_genl_policy)) { + return NL_OK; + } + + if (batadv_genl_missing_attrs(attrs, mandatory, ARRAY_SIZE(mandatory))) + return NL_OK; + + orig = nla_data(attrs[BATADV_ATTR_ORIG_ADDRESS]); + tq = nla_get_u8(attrs[BATADV_ATTR_TQ]); + + if (!attrs[BATADV_ATTR_FLAG_BEST]) + return NL_OK; + + MAC2ETHER(mac_a, orig); + + // update router + router = router_find_orig(&mac_a); + if (!router) + return NL_OK; + + DEBUG_MSG("Found TQ for router " F_MAC " (originator " F_MAC "), it's %d", + F_MAC_VAR(router->src), F_MAC_VAR(router->originator), tq); + router->tq = tq; + if (router->tq > G.max_tq) + G.max_tq = router->tq; + + return NL_OK; +} + +static int parse_tt_local(struct nl_msg *msg, + void *arg __attribute__((unused))) +{ + static const enum batadv_nl_attrs mandatory[] = { + BATADV_ATTR_TT_ADDRESS, + }; + struct nlattr *attrs[BATADV_ATTR_MAX + 1]; + struct nlmsghdr *nlh = nlmsg_hdr(msg); + struct ether_addr mac_a; + struct genlmsghdr *ghdr; + struct router *router; + uint8_t *addr; + + // parse netlink entry + if (!genlmsg_valid_hdr(nlh, 0)) + return NL_OK; + + ghdr = nlmsg_data(nlh); + + if (ghdr->cmd != BATADV_CMD_GET_TRANSTABLE_LOCAL) + return NL_OK; + + if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), + genlmsg_len(ghdr), batadv_genl_policy)) { + return NL_OK; + } + + if (batadv_genl_missing_attrs(attrs, mandatory, ARRAY_SIZE(mandatory))) + return NL_OK; + + addr = nla_data(attrs[BATADV_ATTR_TT_ADDRESS]); + MAC2ETHER(mac_a, addr); + + // update router + router = router_find_src(&mac_a); + if (!router) + return NL_OK; + + DEBUG_MSG("Found router " F_MAC " in transtable_local, assigning TQ %d", + F_MAC_VAR(router->src), LOCAL_TQ); + router->tq = LOCAL_TQ; + if (router->tq > G.max_tq) + G.max_tq = router->tq; + + return NL_OK; +} + +static void update_tqs(void) { + struct router *router; + bool update_originators = false; struct ether_addr unspec; + struct batadv_nlquery_opts opts; + int ret; // reset TQs memset(&unspec, 0, sizeof(unspec)); @@ -416,60 +568,24 @@ static void update_tqs(void) { update_originators = true; } - // TODO: Currently, we iterate over the whole list of routers all the - // time. Maybe it would be a good idea to sort routers that already - // have the current piece of information to the back. That way, we - // could abort as soon as we hit the first router with the current - // information filled in. - + // translate all router's MAC addresses to originators simultaneously if (update_originators) { - // translate all router's MAC addresses to originators simultaneously - snprintf(path, PATH_MAX, TRANSTABLE_GLOBAL, G.mesh_iface); - f = fopen(path, "r"); - // skip header - while (fgetc(f) != '\n') {} - while (fgetc(f) != '\n') {} - while (fscanf(f, " %*[*+] " F_MAC "%*[0-9 -] (%*3u) via " F_MAC " %*[^]]]\n", - F_MAC_VAR_REF(mac_a), F_MAC_VAR_REF(mac_b)) == 12) { - - router = router_find_src(&mac_a); - if (!router) - continue; - - DEBUG_MSG("Found originator for " F_MAC ", it's " F_MAC, F_MAC_VAR(router->src), F_MAC_VAR(mac_b)); - router->originator = mac_b; - } - if (!feof(f)) { - getline(&line, &len, f); - fprintf(stderr, "Parsing transtable_global aborted at this line: %s\n", line); - } - fclose(f); + opts.err = 0; + ret = batadv_genl_query(G.mesh_iface, + BATADV_CMD_GET_TRANSTABLE_GLOBAL, + parse_tt_global, NLM_F_DUMP, &opts); + if (ret < 0) + fprintf(stderr, "Parsing of global translation table failed\n"); } // look up TQs of originators G.max_tq = 0; - snprintf(path, PATH_MAX, ORIGINATORS, G.mesh_iface); - f = fopen(path, "r"); - // skip header - while (fgetc(f) != '\n'); - while (fgetc(f) != '\n'); - while (fscanf(f, F_MAC " %*fs (%hhu) %*[^\n]\n", - F_MAC_VAR_REF(mac_a), &tq) == 7) { - - router = router_find_orig(&mac_a); - if (!router) - continue; - - DEBUG_MSG("Found TQ for router " F_MAC " (originator " F_MAC "), it's %d", F_MAC_VAR(router->src), F_MAC_VAR(router->originator), tq); - router->tq = tq; - if (tq > G.max_tq) - G.max_tq = tq; - } - if (!feof(f)) { - getline(&line, &len, f); - fprintf(stderr, "Parsing originators aborted at this line: %s\n", line); - } - fclose(f); + opts.err = 0; + ret = batadv_genl_query(G.mesh_iface, + BATADV_CMD_GET_ORIGINATORS, + parse_originator, NLM_F_DUMP, &opts); + if (ret < 0) + fprintf(stderr, "Parsing of originators failed\n"); // if all routers have a TQ value, we don't need to check translocal foreach(router, G.routers) { @@ -477,26 +593,12 @@ static void update_tqs(void) { break; } if (router != NULL) { - // rate local routers (if present) the highest - snprintf(path, PATH_MAX, TRANSTABLE_LOCAL, G.mesh_iface); - f = fopen(path, "r"); - // skip header - while (fgetc(f) != '\n'); - while (fgetc(f) != '\n'); - while (fscanf(f, " * " F_MAC " [%*5s] %*f", F_MAC_VAR_REF(mac_a)) == 6) { - router = router_find_src(&mac_a); - if (!router) - continue; - - DEBUG_MSG("Found router " F_MAC " in transtable_local, assigning TQ %d", F_MAC_VAR(router->src), LOCAL_TQ); - router->tq = LOCAL_TQ; - G.max_tq = LOCAL_TQ; - } - if (!feof(f)) { - getline(&line, &len, f); - fprintf(stderr, "Parsing transtable_local aborted at this line: %s\n", line); - } - fclose(f); + opts.err = 0; + ret = batadv_genl_query(G.mesh_iface, + BATADV_CMD_GET_TRANSTABLE_LOCAL, + parse_tt_local, NLM_F_DUMP, &opts); + if (ret < 0) + fprintf(stderr, "Parsing of global translation table failed\n"); } foreach(router, G.routers) { @@ -512,8 +614,6 @@ static void update_tqs(void) { F_MAC_VAR(router->src)); } } - - free(line); } static int fork_execvp_timeout(struct timespec *timeout, const char *file, const char *const argv[]) { diff --git a/package/gluon-radv-filterd/src/mac.h b/package/gluon-radv-filterd/src/mac.h index fb1ff398..cc24d907 100644 --- a/package/gluon-radv-filterd/src/mac.h +++ b/package/gluon-radv-filterd/src/mac.h @@ -3,7 +3,6 @@ #define F_MAC "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx" #define F_MAC_LEN 17 -#define F_MAC_IGN "%*2x:%*2x:%*2x:%*2x:%*2x:%*2x" #define F_MAC_VAR(var) \ (var).ether_addr_octet[0], (var).ether_addr_octet[1], \ (var).ether_addr_octet[2], (var).ether_addr_octet[3], \ @@ -12,6 +11,8 @@ &(var).ether_addr_octet[0], &(var).ether_addr_octet[1], \ &(var).ether_addr_octet[2], &(var).ether_addr_octet[3], \ &(var).ether_addr_octet[4], &(var).ether_addr_octet[5] +#define MAC2ETHER(_ether, _mac) memcpy((_ether).ether_addr_octet, \ + (_mac), ETH_ALEN) #define ether_addr_equal(_a, _b) (memcmp((_a).ether_addr_octet, \ (_b).ether_addr_octet, ETH_ALEN) == 0) From e3cb03010d1867a17a3ab824fd5a06f4c4b28867 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 18:51:18 +0100 Subject: [PATCH 43/51] gluon-radv-filterd: Fix sock initialization check A socket with the value 0 is valid (and it the first opened socket). It is therefore a bad idea to check for 0 when wanting to find out whether a socket was initialized. Instead initialize it with -1 and check for < 0 to find out whether the socket was initialized or not. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 60786a16..818b0920 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -267,7 +267,7 @@ static void parse_cmdline(int argc, char *argv[]) { while ((c = getopt(argc, argv, "c:hi:m:t:")) != -1) { switch (c) { case 'i': - if (G.sock != 0) + if (G.sock >= 0) usage("-i given more than once"); ifindex = if_nametoindex(optarg); if (ifindex == 0) @@ -737,9 +737,10 @@ int main(int argc, char *argv[]) { clock_gettime(CLOCK_MONOTONIC, &next_update); next_update.tv_sec += MIN_INTERVAL; + G.sock = -1; parse_cmdline(argc, argv); - if (G.sock == 0) + if (G.sock < 0) usage("No interface set!"); if (G.chain == NULL) From 2d3614c1a9dc55ff76ef4cfa1664068a496fd89c Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sat, 23 Dec 2017 20:02:49 +0100 Subject: [PATCH 44/51] gluon-radv-filterd: Trigger config reload checks on interface.* events The init scripts adds the br-client as netdev for the daemon. The daemon will automatically be restarted when the netdev's ifindex is changed and the reload target of the init script is called. But something has to call this script first. This can be done the procd triggers interface which can simply wait for all events from type "interface.*". The reload target will always be called but the daemon will only be restarted when the br-client ifindex actually changed. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd | 1 + 1 file changed, 1 insertion(+) diff --git a/package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd b/package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd index 4e1bc179..951a3932 100755 --- a/package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd +++ b/package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd @@ -29,4 +29,5 @@ start_filterd() { service_triggers() { procd_add_reload_trigger "gluon-radv-filterd" procd_add_validation "validate_filterd_section" + procd_add_raw_trigger "interface.*" 1000 /etc/init.d/gluon-radv-filterd reload } From 2394ad0c075008f2c61026c968fb00fce6b0961b Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 14:02:08 +0100 Subject: [PATCH 45/51] gluon-radv-filterd: Call cleanup when stopping daemon Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 818b0920..32b954cc 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -115,6 +115,7 @@ static struct global { uint16_t max_tq; uint16_t hysteresis_thresh; struct router *best_router; + volatile sig_atomic_t stop_daemon; } G = { .mesh_iface = "bat0", }; @@ -726,6 +727,11 @@ static void update_ebtables(void) { error_message(0, 0, "warning: adding new rule to ebtables chain %s failed", G.chain); } +static void sighandler(int sig __attribute__((unused))) +{ + G.stop_daemon = 1; +} + int main(int argc, char *argv[]) { int retval; fd_set rfds; @@ -746,7 +752,11 @@ int main(int argc, char *argv[]) { if (G.chain == NULL) usage("No chain set!"); - while (1) { + G.stop_daemon = 0; + signal(SIGINT, sighandler); + signal(SIGTERM, sighandler); + + while (!G.stop_daemon) { FD_ZERO(&rfds); FD_SET(G.sock, &rfds); From 4cc2ba26a63156675d0db5b9beb3500c9f3ce014 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Wed, 3 Jan 2018 19:23:21 +0100 Subject: [PATCH 46/51] gluon-radv-filterd: Mention respondd module in readme --- package/gluon-radv-filterd/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/package/gluon-radv-filterd/README.md b/package/gluon-radv-filterd/README.md index 01e104e0..b2623e6d 100644 --- a/package/gluon-radv-filterd/README.md +++ b/package/gluon-radv-filterd/README.md @@ -26,3 +26,13 @@ However, if used together with the `gluon-ebtables-filter-ra-dhcp` package, these router advertisements are filtered anyway and reach neither the node nor any other client. You currently have to disable the package or insert custom ebtables rules in order to use local routers. + +respondd module +--------------- +This package also contains a module for [respondd] that announces the currently +selected router via the `statistics.gateway6` property using its interface MAC +address. Note that this is different from the `statistics.gateway` property, +which contains the MAC address of the main B.A.T.M.A.N. adv slave interface of +the selected IPv4 gateway. + +[respondd]: https://github.com/freifunk-gluon/packages/tree/master/net/respondd From 9b3a2f2be209c871673cba0a938ff8efd257aa99 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 20 Dec 2017 13:16:56 +0100 Subject: [PATCH 47/51] gluon-radv-filterd: Use ebtables locking This enables the ebtables internal locking mechanism which will avoid race conditions between multiple, concurrent ebtables calls. Signed-off-by: Sven Eckelmann --- package/gluon-radv-filterd/src/gluon-radv-filterd.c | 8 ++++---- package/gluon-radv-filterd/src/respondd.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c index 32b954cc..990885ea 100644 --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c @@ -168,11 +168,11 @@ static void cleanup(void) { if (G.chain) { /* Reset chain to accept everything again */ if (fork_execvp_timeout(&timeout, "ebtables", (const char *[]) - { "ebtables", "-F", G.chain, NULL })) + { "ebtables", "--concurrent", "-F", G.chain, NULL })) DEBUG_MSG("warning: flushing ebtables chain %s failed, not adding a new rule", G.chain); if (fork_execvp_timeout(&timeout, "ebtables", (const char *[]) - { "ebtables", "-A", G.chain, "-j", "ACCEPT", NULL })) + { "ebtables", "--concurrent", "-A", G.chain, "-j", "ACCEPT", NULL })) DEBUG_MSG("warning: adding new rule to ebtables chain %s failed", G.chain); } } @@ -720,10 +720,10 @@ static void update_ebtables(void) { G.best_router = router; if (fork_execvp_timeout(&timeout, "ebtables", (const char *[]) - { "ebtables", "-F", G.chain, NULL })) + { "ebtables", "--concurrent", "-F", G.chain, NULL })) error_message(0, 0, "warning: flushing ebtables chain %s failed, not adding a new rule", G.chain); else if (fork_execvp_timeout(&timeout, "ebtables", (const char *[]) - { "ebtables", "-A", G.chain, "-s", mac, "-j", "ACCEPT", NULL })) + { "ebtables", "--concurrent", "-A", G.chain, "-s", mac, "-j", "ACCEPT", NULL })) error_message(0, 0, "warning: adding new rule to ebtables chain %s failed", G.chain); } diff --git a/package/gluon-radv-filterd/src/respondd.c b/package/gluon-radv-filterd/src/respondd.c index 221f532b..1e0aa128 100644 --- a/package/gluon-radv-filterd/src/respondd.c +++ b/package/gluon-radv-filterd/src/respondd.c @@ -8,7 +8,7 @@ #include "mac.h" static struct json_object * get_radv_filter() { - FILE *f = popen("exec ebtables -L RADV_FILTER", "r"); + FILE *f = popen("exec ebtables --concurrent -L RADV_FILTER", "r"); char *line = NULL; size_t len = 0; struct ether_addr mac = {}; From f6f1ae9c4f898fd9824b3f60ad6fe545ac28ae3e Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Wed, 3 Jan 2018 22:18:23 +0100 Subject: [PATCH 48/51] gluon-radv-filterd: Return null via respondd without chosen gateway --- package/gluon-radv-filterd/src/respondd.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/package/gluon-radv-filterd/src/respondd.c b/package/gluon-radv-filterd/src/respondd.c index 1e0aa128..7e65a3d9 100644 --- a/package/gluon-radv-filterd/src/respondd.c +++ b/package/gluon-radv-filterd/src/respondd.c @@ -12,7 +12,7 @@ static struct json_object * get_radv_filter() { char *line = NULL; size_t len = 0; struct ether_addr mac = {}; - struct json_object *ret = NULL; + struct ether_addr unspec = {}; char macstr[F_MAC_LEN + 1] = ""; if (!f) @@ -26,17 +26,19 @@ static struct json_object * get_radv_filter() { pclose(f); - snprintf(macstr, sizeof(macstr), F_MAC, F_MAC_VAR(mac)); - ret = gluonutil_wrap_string(macstr); - return ret; + memset(&unspec, 0, sizeof(unspec)); + if (ether_addr_equal(mac, unspec)) { + return NULL; + } else { + snprintf(macstr, sizeof(macstr), F_MAC, F_MAC_VAR(mac)); + return gluonutil_wrap_string(macstr); + } } static struct json_object * respondd_provider_statistics() { struct json_object *ret = json_object_new_object(); - struct json_object *radv_filter = get_radv_filter(); - if (radv_filter) - json_object_object_add(ret, "gateway6", radv_filter); + json_object_object_add(ret, "gateway6", get_radv_filter()); return ret; } From f1a9196deb313b895eb6b5a1578b6584dadd0716 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Wed, 3 Jan 2018 23:11:58 +0100 Subject: [PATCH 49/51] gluon-radv-filterd: Move readme to docs/package/ --- docs/index.rst | 1 + .../package/gluon-radv-filterd.rst | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) rename package/gluon-radv-filterd/README.md => docs/package/gluon-radv-filterd.rst (64%) diff --git a/docs/index.rst b/docs/index.rst index a1b93c9d..29c57bbf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -60,6 +60,7 @@ Several Freifunk communities in Germany use Gluon as the foundation of their Fre package/gluon-ebtables-filter-ra-dhcp package/gluon-ebtables-segment-mld package/gluon-ebtables-source-filter + package/gluon-radv-filterd package/gluon-web-admin .. toctree:: diff --git a/package/gluon-radv-filterd/README.md b/docs/package/gluon-radv-filterd.rst similarity index 64% rename from package/gluon-radv-filterd/README.md rename to docs/package/gluon-radv-filterd.rst index b2623e6d..f5083bf4 100644 --- a/package/gluon-radv-filterd/README.md +++ b/docs/package/gluon-radv-filterd.rst @@ -1,5 +1,6 @@ gluon-radv-filterd ================== + This package drops all incoming router advertisements except for the default router with the best metric according to B.A.T.M.A.N. advanced. @@ -8,6 +9,7 @@ via gluon-radvd) are not affected and considered at all. "Best" router ------------- + The best router is determined by the TQ that is reported for its originator by B.A.T.M.A.N. advanced. If, for some reason, another gateway with a better TQ appears or an existing gateway increases its TQ above that of the chosen @@ -19,20 +21,20 @@ B.A.T.M.A.N. advanced). "Local" routers --------------- + The package has functionality to assign "local" routers, i.e. those connected via cable or WLAN instead of via the mesh (technically: appearing in the -`transtable_local`), a fake TQ of 512 so that they are always preferred. -However, if used together with the `gluon-ebtables-filter-ra-dhcp` package, -these router advertisements are filtered anyway and reach neither the node nor -any other client. You currently have to disable the package or insert custom -ebtables rules in order to use local routers. +``transtable_local``), a fake TQ of 512 so that they are always preferred. +However, if used together with the :doc:`package/gluon-ebtables-filter-ra-dhcp` +package, these router advertisements are filtered anyway and reach neither the +node nor any other client. You currently have to disable the package or insert +custom ebtables rules in order to use local routers. respondd module --------------- -This package also contains a module for [respondd] that announces the currently -selected router via the `statistics.gateway6` property using its interface MAC -address. Note that this is different from the `statistics.gateway` property, + +This package also contains a module for respondd that announces the currently +selected router via the ``statistics.gateway6`` property using its interface MAC +address. Note that this is different from the ``statistics.gateway`` property, which contains the MAC address of the main B.A.T.M.A.N. adv slave interface of the selected IPv4 gateway. - -[respondd]: https://github.com/freifunk-gluon/packages/tree/master/net/respondd From 32c191892d8eebc525773e68b577a4e8ca23d5a7 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Wed, 3 Jan 2018 23:38:22 +0100 Subject: [PATCH 50/51] gluon-radv-filterd: Add documentation of site.conf parameter --- docs/package/gluon-radv-filterd.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/package/gluon-radv-filterd.rst b/docs/package/gluon-radv-filterd.rst index f5083bf4..e4f72b52 100644 --- a/docs/package/gluon-radv-filterd.rst +++ b/docs/package/gluon-radv-filterd.rst @@ -38,3 +38,17 @@ selected router via the ``statistics.gateway6`` property using its interface MAC address. Note that this is different from the ``statistics.gateway`` property, which contains the MAC address of the main B.A.T.M.A.N. adv slave interface of the selected IPv4 gateway. + +site.conf +--------- + +radv_filterd.threshold : optional + - minimal difference in TQ value that another gateway has to be better than + the currently chosen gateway to become the new chosen gateway + - defaults to ``20`` + +Example:: + + radv_filterd = { + threshold = 20, + } From 1c7ce32e6fce1d6ed750d58168ecfb60cb30dd1b Mon Sep 17 00:00:00 2001 From: lemoer Date: Sat, 6 Jan 2018 00:18:39 +0100 Subject: [PATCH 51/51] gluon-radv-filterd: improve documentation of selected router --- docs/package/gluon-radv-filterd.rst | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/docs/package/gluon-radv-filterd.rst b/docs/package/gluon-radv-filterd.rst index e4f72b52..14566f32 100644 --- a/docs/package/gluon-radv-filterd.rst +++ b/docs/package/gluon-radv-filterd.rst @@ -7,22 +7,29 @@ default router with the best metric according to B.A.T.M.A.N. advanced. Note that advertisements originating from the node itself (for example via gluon-radvd) are not affected and considered at all. -"Best" router +Selected router ------------- -The best router is determined by the TQ that is reported for its originator by -B.A.T.M.A.N. advanced. If, for some reason, another gateway with a better TQ -appears or an existing gateway increases its TQ above that of the chosen -gateway, the chosen gateway will remain selected until the better gateway has a -TQ value at least X higher than the selected gateway. This is called -hysteresis, and X can be specified on the commandline/via UCI/the site.conf and -defaults to 20 (just as for the IPv4 gateway selection feature built into -B.A.T.M.A.N. advanced). +The router selection mechanism is independent from the batman-adv gateway mode. +In contrast, the device originating the router advertisment could be any router +or client connected to the mesh, as radv-filterd captures all router +advertisements originating from it. All nodes announcing router advertisement +**with** a default lifetime greater than 0 are being considered as candidates. + +In case a router is not a batman-adv originator itself, its TQ is defined by +the originator it is connected to. This lookup uses the batman-adv global +translation table. + +Initially the router is the selected by choosing the candidate with the +strongest TQ. When another candidate can provide a better TQ metric it is not +picked up as the selected router until it will outperform the currently +selected router by X metric units. The hysteresis threshold is configurable +and prevents excessive flapping of the gateway. "Local" routers --------------- -The package has functionality to assign "local" routers, i.e. those connected +The package has functionality to select "local" routers, i.e. those connected via cable or WLAN instead of via the mesh (technically: appearing in the ``transtable_local``), a fake TQ of 512 so that they are always preferred. However, if used together with the :doc:`package/gluon-ebtables-filter-ra-dhcp`