Merge 1c7ce32e6f into 7ae8a51126
				
					
				
			This commit is contained in:
		
						commit
						dd0ff02344
					
				| @ -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-filter-ra-dhcp | ||||||
|    package/gluon-ebtables-segment-mld |    package/gluon-ebtables-segment-mld | ||||||
|    package/gluon-ebtables-source-filter |    package/gluon-ebtables-source-filter | ||||||
|  |    package/gluon-radv-filterd | ||||||
|    package/gluon-web-admin |    package/gluon-web-admin | ||||||
|    package/gluon-web-logging |    package/gluon-web-logging | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										61
									
								
								docs/package/gluon-radv-filterd.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								docs/package/gluon-radv-filterd.rst
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | |||||||
|  | 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. | ||||||
|  | 
 | ||||||
|  | Selected router | ||||||
|  | ------------- | ||||||
|  | 
 | ||||||
|  | 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 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` | ||||||
|  | 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. | ||||||
|  | 
 | ||||||
|  | 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, | ||||||
|  |   } | ||||||
							
								
								
									
										51
									
								
								package/gluon-radv-filterd/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								package/gluon-radv-filterd/Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | |||||||
|  | include $(TOPDIR)/rules.mk | ||||||
|  | 
 | ||||||
|  | PKG_NAME:=gluon-radv-filterd | ||||||
|  | PKG_VERSION:=1 | ||||||
|  | PKG_RELEASE:=1 | ||||||
|  | 
 | ||||||
|  | PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) | ||||||
|  | 
 | ||||||
|  | include ../gluon.mk | ||||||
|  | 
 | ||||||
|  | define Package/gluon-radv-filterd | ||||||
|  |   SECTION:=gluon | ||||||
|  |   CATEGORY:=Gluon | ||||||
|  |   TITLE:=Filter IPv6 router advertisements | ||||||
|  |   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)/ | ||||||
|  | endef | ||||||
|  | 
 | ||||||
|  | define Build/Configure | ||||||
|  | endef | ||||||
|  | 
 | ||||||
|  | define Build/Compile | ||||||
|  | 	$(call Build/Compile/Default) | ||||||
|  | 	$(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/ | ||||||
|  | 
 | ||||||
|  | 	$(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 | ||||||
|  | #!/bin/sh
 | ||||||
|  | $(call GluonCheckSite,check_site.lua) | ||||||
|  | endef | ||||||
|  | 
 | ||||||
|  | $(eval $(call BuildPackage,gluon-radv-filterd)) | ||||||
							
								
								
									
										3
									
								
								package/gluon-radv-filterd/check_site.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								package/gluon-radv-filterd/check_site.lua
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | if need_table('radv_filterd', nil, false) then | ||||||
|  | 	need_number('radv_filterd.threshold') | ||||||
|  | end | ||||||
| @ -0,0 +1,2 @@ | |||||||
|  | config filterd | ||||||
|  | 	option threshold '20' | ||||||
							
								
								
									
										33
									
								
								package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										33
									
								
								package/gluon-radv-filterd/files/etc/init.d/gluon-radv-filterd
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | #!/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}" \ | ||||||
|  |             '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 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 | ||||||
|  |     procd_close_instance | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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 | ||||||
|  | } | ||||||
| @ -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' | ||||||
							
								
								
									
										11
									
								
								package/gluon-radv-filterd/luasrc/lib/gluon/upgrade/300-gluon-radv-filterd
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								package/gluon-radv-filterd/luasrc/lib/gluon/upgrade/300-gluon-radv-filterd
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | #!/usr/bin/lua | ||||||
|  | 
 | ||||||
|  | local site = require 'gluon.site_config' | ||||||
|  | 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') | ||||||
|  | end | ||||||
							
								
								
									
										49
									
								
								package/gluon-radv-filterd/src/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								package/gluon-radv-filterd/src/Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | 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) | ||||||
|  | 
 | ||||||
|  | respondd.so: respondd.c | ||||||
|  | 	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -shared -fPIC -o $@ $^ $(LDLIBS) -lgluonutil | ||||||
							
								
								
									
										796
									
								
								package/gluon-radv-filterd/src/gluon-radv-filterd.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										796
									
								
								package/gluon-radv-filterd/src/gluon-radv-filterd.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,796 @@ | |||||||
|  | /*
 | ||||||
|  |    Copyright (c) 2016 Jan-Philipp Litza <janphilipp@litza.de> | ||||||
|  |    Copyright (c) 2017 Sven Eckelmann <sven@narfation.org> | ||||||
|  |    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. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | #include <errno.h> | ||||||
|  | #include <signal.h> | ||||||
|  | #include <stdarg.h> | ||||||
|  | #include <stdbool.h> | ||||||
|  | #include <stddef.h> | ||||||
|  | #include <stdio.h> | ||||||
|  | #include <stdint.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include <time.h> | ||||||
|  | #include <unistd.h> | ||||||
|  | 
 | ||||||
|  | #include <sys/socket.h> | ||||||
|  | #include <sys/select.h> | ||||||
|  | #include <sys/types.h> | ||||||
|  | #include <sys/wait.h> | ||||||
|  | 
 | ||||||
|  | #include <net/ethernet.h> | ||||||
|  | #include <net/if.h> | ||||||
|  | 
 | ||||||
|  | #include <linux/filter.h> | ||||||
|  | #include <linux/if_packet.h> | ||||||
|  | #include <linux/limits.h> | ||||||
|  | 
 | ||||||
|  | #include <netinet/icmp6.h> | ||||||
|  | #include <netinet/in.h> | ||||||
|  | #include <netinet/ip6.h> | ||||||
|  | 
 | ||||||
|  | #include <netlink/netlink.h> | ||||||
|  | #include <netlink/genl/genl.h> | ||||||
|  | #include <netlink/genl/ctrl.h> | ||||||
|  | #include <batadv-genl.h> | ||||||
|  | 
 | ||||||
|  | #include "mac.h" | ||||||
|  | 
 | ||||||
|  | // 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 15 | ||||||
|  | 
 | ||||||
|  | // max execution time of a single ebtables call in nanoseconds
 | ||||||
|  | #define EBTABLES_TIMEOUT 500000000 // 500ms
 | ||||||
|  | 
 | ||||||
|  | // TQ value assigned to local routers
 | ||||||
|  | #define LOCAL_TQ 512 | ||||||
|  | 
 | ||||||
|  | #define BUFSIZE 1500 | ||||||
|  | 
 | ||||||
|  | #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 | ||||||
|  | 
 | ||||||
|  | #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; | ||||||
|  | 	struct ether_addr src; | ||||||
|  | 	struct timespec eol; | ||||||
|  | 	struct ether_addr originator; | ||||||
|  | 	uint16_t tq; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static 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; | ||||||
|  | 	volatile sig_atomic_t stop_daemon; | ||||||
|  | } G = { | ||||||
|  | 	.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; | ||||||
|  | 	va_start(ap, message); | ||||||
|  | 	fflush(stdout); | ||||||
|  | 	vfprintf(stderr, message, ap); | ||||||
|  | 	va_end(ap); | ||||||
|  | 
 | ||||||
|  | 	if (errnum) | ||||||
|  | 		fprintf(stderr, ": %s", strerror(errnum)); | ||||||
|  | 	fprintf(stderr, "\n"); | ||||||
|  | 	if (status) | ||||||
|  | 		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; | ||||||
|  | 	struct timespec timeout = { | ||||||
|  | 		.tv_nsec = EBTABLES_TIMEOUT, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	close(G.sock); | ||||||
|  | 
 | ||||||
|  | 	while (G.routers != NULL) { | ||||||
|  | 		router = G.routers; | ||||||
|  | 		G.routers = router->next; | ||||||
|  | 		free(router); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (G.chain) { | ||||||
|  | 		/* Reset chain to accept everything again */ | ||||||
|  | 		if (fork_execvp_timeout(&timeout, "ebtables", (const char *[]) | ||||||
|  | 				{ "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", "--concurrent", "-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) { | ||||||
|  | 	if (msg != NULL && *msg != '\0') { | ||||||
|  | 		fprintf(stderr, "ERROR: %s\n\n", msg); | ||||||
|  | 	} | ||||||
|  | 	fprintf(stderr, | ||||||
|  | 		"Usage: %s [-m <mesh_iface>] [-t <thresh>] -c <chain> -i <iface>\n\n" | ||||||
|  | 		"  -m <mesh_iface>  B.A.T.M.A.N. advanced mesh interface used to get metric\n" | ||||||
|  | 		"                   information (\"TQ\") for the available gateways. Default: bat0\n" | ||||||
|  | 		"  -t <thresh>      Minimum TQ difference required to switch the gateway.\n" | ||||||
|  | 		"                   Default: 0\n" | ||||||
|  | 		"  -c <chain>       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 <iface>       Interface to listen on for router advertisements. Should be\n" | ||||||
|  | 		"                   <mesh_iface> 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_message(1, errno, "error: %s", message); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline void warn_errno(const char *message) { | ||||||
|  | 	error_message(0, errno, "warning: %s", 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, 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_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), | ||||||
|  | 		// return false
 | ||||||
|  | 		BPF_STMT(BPF_RET|BPF_K, 0), | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	struct sock_fprog radv_filter = { | ||||||
|  | 	    .len = ARRAY_SIZE(radv_filter_code), | ||||||
|  | 	    .filter = radv_filter_code, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	int sock = socket(AF_PACKET, SOCK_DGRAM|SOCK_CLOEXEC, htons(ETH_P_IPV6)); | ||||||
|  | 	if (sock < 0) | ||||||
|  | 		exit_errno("can't open packet socket"); | ||||||
|  | 	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 = htons(ETH_P_IPV6), | ||||||
|  | 		.sll_ifindex = ifindex, | ||||||
|  | 	}; | ||||||
|  | 	ret = bind(sock, (struct sockaddr *)&bind_iface, sizeof(bind_iface)); | ||||||
|  | 	if (ret < 0) | ||||||
|  | 		exit_errno("can't bind socket"); | ||||||
|  | 
 | ||||||
|  | 	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 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)); | ||||||
|  | 	if (!router) | ||||||
|  | 		return NULL; | ||||||
|  | 
 | ||||||
|  | 	router->src = *mac; | ||||||
|  | 	router->next = G.routers; | ||||||
|  | 	G.routers = router; | ||||||
|  | 	router->eol.tv_sec = 0; | ||||||
|  | 	router->eol.tv_nsec = 0; | ||||||
|  | 	memset(&router->originator, 0, sizeof(router->originator)); | ||||||
|  | 
 | ||||||
|  | 	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); | ||||||
|  | 	if (!router) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	clock_gettime(CLOCK_MONOTONIC, &router->eol); | ||||||
|  | 	router->eol.tv_sec += timeout; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void handle_ra(int sock) { | ||||||
|  | 	struct sockaddr_ll src; | ||||||
|  | 	struct ether_addr mac; | ||||||
|  | 	socklen_t addr_size = sizeof(src); | ||||||
|  | 	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((size_t)len >= sizeof(pkt)); | ||||||
|  | 	CHECK(ntohs(pkt.ip6.ip6_plen) + sizeof(struct ip6_hdr) >= sizeof(pkt)); | ||||||
|  | 
 | ||||||
|  | 	memcpy(&mac, src.sll_addr, sizeof(mac)); | ||||||
|  | 	DEBUG_MSG("received valid RA from " F_MAC, F_MAC_VAR(mac)); | ||||||
|  | 
 | ||||||
|  | 	router_update(&mac, ntohs(pkt.ra.nd_ra_router_lifetime)); | ||||||
|  | 
 | ||||||
|  | check_failed: | ||||||
|  | 	return; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void expire_routers(void) { | ||||||
|  | 	struct router **prev_ptr = &G.routers; | ||||||
|  | 	struct router *router; | ||||||
|  | 	struct router *safe; | ||||||
|  | 	struct timespec now; | ||||||
|  | 	struct timespec diff; | ||||||
|  | 
 | ||||||
|  | 	clock_gettime(CLOCK_MONOTONIC, &now); | ||||||
|  | 
 | ||||||
|  | 	foreach_safe(router, safe, G.routers) { | ||||||
|  | 		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) | ||||||
|  | 				G.best_router = NULL; | ||||||
|  | 			free(router); | ||||||
|  | 		} else { | ||||||
|  | 			prev_ptr = &router->next; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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)); | ||||||
|  | 	foreach(router, G.routers) { | ||||||
|  | 		router->tq = 0; | ||||||
|  | 		if (ether_addr_equal(router->originator, unspec)) | ||||||
|  | 			update_originators = true; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// translate all router's MAC addresses to originators simultaneously
 | ||||||
|  | 	if (update_originators) { | ||||||
|  | 		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; | ||||||
|  | 	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) { | ||||||
|  | 		if (router->tq == 0) | ||||||
|  | 			break; | ||||||
|  | 	} | ||||||
|  | 	if (router != NULL) { | ||||||
|  | 		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) { | ||||||
|  | 		if (router->tq == 0) { | ||||||
|  | 			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)); | ||||||
|  | 			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)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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, oldsignals; | ||||||
|  | 	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); | ||||||
|  | 		fprintf(stderr, "can't execvp(\"%s\", ...): %s\n", file, strerror(errno)); | ||||||
|  | 		_exit(1); | ||||||
|  | 	} | ||||||
|  | 	else if (child < 0) { | ||||||
|  | 		perror("Failed to fork()"); | ||||||
|  | 		return -1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ret = sigtimedwait(&signals, &info, timeout); | ||||||
|  | 	sigprocmask(SIG_SETMASK, &oldsignals, NULL); | ||||||
|  | 
 | ||||||
|  | 	if (ret == SIGCHLD) { | ||||||
|  | 		if (info.si_pid != child) { | ||||||
|  | 			cleanup(); | ||||||
|  | 			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); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		waitpid(child, NULL, 0); | ||||||
|  | 
 | ||||||
|  | 		return info.si_status; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (ret < 0 && errno == EAGAIN) | ||||||
|  | 		error_message(0, 0, "warning: child %d took too long, killing", child); | ||||||
|  | 	else if (ret < 0) | ||||||
|  | 		warn_errno("sigtimedwait failed, killing child"); | ||||||
|  | 	else | ||||||
|  | 		error_message(1, 0, | ||||||
|  | 				"BUG: sigtimedwait() returned some other signal than SIGCHLD: %d", | ||||||
|  | 				ret); | ||||||
|  | 
 | ||||||
|  | 	kill(child, SIGKILL); | ||||||
|  | 	kill(child, SIGCONT); | ||||||
|  | 	waitpid(child, NULL, 0); | ||||||
|  | 	return -1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool election_required(void) | ||||||
|  | { | ||||||
|  | 	if (!G.best_router) | ||||||
|  | 		return true; | ||||||
|  | 
 | ||||||
|  | 	/* 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) { | ||||||
|  | 	struct timespec timeout = { | ||||||
|  | 		.tv_nsec = EBTABLES_TIMEOUT, | ||||||
|  | 	}; | ||||||
|  | 	char mac[F_MAC_LEN + 1]; | ||||||
|  | 	struct router *router; | ||||||
|  | 
 | ||||||
|  | 	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, | ||||||
|  | 			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; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	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 *[]) | ||||||
|  | 			{ "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", "--concurrent", "-A", G.chain, "-s", mac, "-j", "ACCEPT", NULL })) | ||||||
|  | 		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; | ||||||
|  | 	struct timeval tv; | ||||||
|  | 	struct timespec next_update; | ||||||
|  | 	struct timespec now; | ||||||
|  | 	struct timespec diff; | ||||||
|  | 
 | ||||||
|  | 	clock_gettime(CLOCK_MONOTONIC, &next_update); | ||||||
|  | 	next_update.tv_sec += MIN_INTERVAL; | ||||||
|  | 
 | ||||||
|  | 	G.sock = -1; | ||||||
|  | 	parse_cmdline(argc, argv); | ||||||
|  | 
 | ||||||
|  | 	if (G.sock < 0) | ||||||
|  | 		usage("No interface set!"); | ||||||
|  | 
 | ||||||
|  | 	if (G.chain == NULL) | ||||||
|  | 		usage("No chain set!"); | ||||||
|  | 
 | ||||||
|  | 	G.stop_daemon = 0; | ||||||
|  | 	signal(SIGINT, sighandler); | ||||||
|  | 	signal(SIGTERM, sighandler); | ||||||
|  | 
 | ||||||
|  | 	while (!G.stop_daemon) { | ||||||
|  | 		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) { | ||||||
|  | 			if (errno != EINTR) | ||||||
|  | 				exit_errno("select() failed"); | ||||||
|  | 		} else if (retval) { | ||||||
|  | 			if (FD_ISSET(G.sock, &rfds)) { | ||||||
|  | 				handle_ra(G.sock); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 			DEBUG_MSG("select() timeout expired"); | ||||||
|  | 
 | ||||||
|  | 		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(); | ||||||
|  | 
 | ||||||
|  | 				next_update = now; | ||||||
|  | 				next_update.tv_sec += MIN_INTERVAL; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	cleanup(); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								package/gluon-radv-filterd/src/mac.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								package/gluon-radv-filterd/src/mac.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | #include <stdint.h> | ||||||
|  | #include <linux/if_ether.h> | ||||||
|  | 
 | ||||||
|  | #define F_MAC "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx" | ||||||
|  | #define F_MAC_LEN 17 | ||||||
|  | #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] | ||||||
|  | #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) | ||||||
							
								
								
									
										49
									
								
								package/gluon-radv-filterd/src/respondd.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								package/gluon-radv-filterd/src/respondd.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | #include <respondd.h> | ||||||
|  | 
 | ||||||
|  | #include <json-c/json.h> | ||||||
|  | #include <libgluonutil.h> | ||||||
|  | #include <net/ethernet.h> | ||||||
|  | #include <stdio.h> | ||||||
|  | 
 | ||||||
|  | #include "mac.h" | ||||||
|  | 
 | ||||||
|  | static struct json_object * get_radv_filter() { | ||||||
|  | 	FILE *f = popen("exec ebtables --concurrent -L RADV_FILTER", "r"); | ||||||
|  | 	char *line = NULL; | ||||||
|  | 	size_t len = 0; | ||||||
|  | 	struct ether_addr mac = {}; | ||||||
|  | 	struct ether_addr unspec = {}; | ||||||
|  | 	char macstr[F_MAC_LEN + 1] = ""; | ||||||
|  | 
 | ||||||
|  | 	if (!f) | ||||||
|  | 		return NULL; | ||||||
|  | 
 | ||||||
|  | 	while (getline(&line, &len, f) > 0) { | ||||||
|  | 		if (sscanf(line, "-s " F_MAC " -j ACCEPT\n", F_MAC_VAR_REF(mac)) == ETH_ALEN) | ||||||
|  | 			break; | ||||||
|  | 	} | ||||||
|  | 	free(line); | ||||||
|  | 
 | ||||||
|  | 	pclose(f); | ||||||
|  | 
 | ||||||
|  | 	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(); | ||||||
|  | 
 | ||||||
|  | 	json_object_object_add(ret, "gateway6", get_radv_filter()); | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const struct respondd_provider_info respondd_providers[] = { | ||||||
|  | 	{"statistics", respondd_provider_statistics}, | ||||||
|  | 	{} | ||||||
|  | }; | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user