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-segment-mld
|
||||
package/gluon-ebtables-source-filter
|
||||
package/gluon-radv-filterd
|
||||
package/gluon-web-admin
|
||||
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