Skip to content
Snippets Groups Projects
Commit 246a8f29 authored by Jan Luebbe's avatar Jan Luebbe Committed by Chris Fiege
Browse files

gluon-radv-filterd: redirect IPv6 packets to correct router


When multiple routers are in the same local mesh and clients roam from one (A)
to the next (B), the change of global IP and default gateway are not
synchronized. This leads to packets with an address belonging to router A to be
sent via router B (or the other way around). Those packets are then dropped by
wireguard at the concentrator.

To avoid this, we let gluon-radv-filterd monitor router advertisements and keep
a list of neighbouring v6 networks. With this information, it can maintain a
set of ebtables DNAT rules to redirect the packets to the matching gateway.

Signed-off-by: default avatarJan Luebbe <jluebbe@debian.org>
parent e4ada137
No related branches found
No related tags found
No related merge requests found
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'
chain('REDIRECT', 'RETURN', 'nat')
rule('PREROUTING -p IPv6 --logical-in br-client --ip6-destination 2000::/3 -j REDIRECT', 'nat')
rule('OUTPUT -p IPv6 --logical-out --ip6-destination 2000::/3 -j REDIRECT', 'nat')
......@@ -24,6 +24,8 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// #define DEBUG
#include <errno.h>
#include <signal.h>
#include <stdarg.h>
......@@ -52,6 +54,8 @@
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <arpa/inet.h>
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
......@@ -109,6 +113,9 @@ struct router {
struct timespec eol;
struct ether_addr originator;
uint16_t tq;
bool redirected;
struct in6_addr lladdr;
struct in6_addr prefix;
};
static struct global {
......@@ -178,6 +185,10 @@ static void cleanup(void) {
if (fork_execvp_timeout(&timeout, "ebtables-tiny", (const char *[])
{ "ebtables-tiny", "-A", G.chain, "-j", "ACCEPT", NULL }))
DEBUG_MSG("warning: adding new rule to ebtables chain %s failed", G.chain);
if (fork_execvp_timeout(&timeout, "ebtables-tiny", (const char *[])
{ "ebtables-tiny", "-t", "nat", "-F", "REDIRECT", NULL}))
DEBUG_MSG("warning: flushing ebtables nat chain REDIRECT failed", G.chain);
}
}
......@@ -254,7 +265,7 @@ static int init_packet_socket(unsigned int ifindex) {
struct sockaddr_ll bind_iface = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_IPV6),
.sll_protocol = htons(ETH_P_ALL), /* seems needed to recieve packets on bat0 */
.sll_ifindex = ifindex,
};
ret = bind(sock, (struct sockaddr *)&bind_iface, sizeof(bind_iface));
......@@ -342,40 +353,85 @@ static struct router *router_add(const struct ether_addr *mac) {
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;
uint8_t *ptr;
struct {
struct ip6_hdr ip6;
struct nd_router_advert ra;
struct {
struct ip6_hdr ip6;
struct nd_router_advert ra;
} hdr;
uint8_t options[128];
} pkt;
struct router *router;
char addr_str[INET6_ADDRSTRLEN];
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));
CHECK((size_t)len >= sizeof(pkt.hdr));
CHECK(ntohs(pkt.hdr.ip6.ip6_plen) + sizeof(struct ip6_hdr) >= sizeof(pkt.hdr));
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));
router = router_find_src(&mac);
if (!router)
router = router_add(&mac);
if (!router)
return;
clock_gettime(CLOCK_MONOTONIC, &router->eol);
router->eol.tv_sec += ntohs(pkt.hdr.ra.nd_ra_router_lifetime);
memcpy(&router->lladdr, &pkt.hdr.ip6.ip6_src, sizeof(router->lladdr));
DEBUG_MSG("%d bytes in packet", len);
// find prefix option
len -= sizeof(pkt.hdr);
ptr = (uint8_t*)&pkt + sizeof(pkt.hdr);
while (len >= 8) {
unsigned int o_type = ptr[0];
unsigned int o_len = (unsigned int)ptr[1] << 3;
struct nd_opt_prefix_info *o_pi;
if (o_type != 3) {
ptr += o_len;
len -= o_len;
DEBUG_MSG("skipping option %d (size %d)", o_type, o_len);
continue;
}
CHECK(len >= o_len);
DEBUG_MSG("found option option %d (size %d)", o_type, o_len);
o_pi = (struct nd_opt_prefix_info*)ptr;
memcpy(&router->prefix, &o_pi->nd_opt_pi_prefix, sizeof(router->prefix));
ptr += o_len;
len -= o_len;
break;
}
DEBUG_MSG("%d bytes remaining", len);
if (inet_ntop(AF_INET6, &router->lladdr, addr_str, sizeof(addr_str))) {
DEBUG_MSG("lladdr: %s", addr_str);
} else {
DEBUG_MSG("lladdr: error");
}
if (inet_ntop(AF_INET6, &router->prefix, addr_str, sizeof(addr_str))) {
DEBUG_MSG("prefix: %s", addr_str);
} else {
DEBUG_MSG("prefix: error");
}
check_failed:
return;
......@@ -620,6 +676,43 @@ static void update_tqs(void) {
}
}
static void update_redirect(void) {
struct router *router;
struct timespec timeout = {
.tv_nsec = EBTABLES_TIMEOUT,
};
foreach(router, G.routers) {
char mac[F_MAC_LEN + 1];
char addr[INET6_ADDRSTRLEN];
char prefix[INET6_ADDRSTRLEN];
if (router->redirected)
continue;
router->redirected = true;
snprintf(mac, sizeof(mac), F_MAC, F_MAC_VAR(router->src));
if (inet_ntop(AF_INET6, &router->prefix, addr, sizeof(addr)) == NULL) {
error_message(0, 0, "warning: failed to format prefix");
continue;
}
snprintf(prefix, sizeof(prefix), "%s/64", addr);
if (fork_execvp_timeout(&timeout, "ebtables-tiny", (const char *[])
{ "ebtables-tiny", "-t", "nat", "-A", "REDIRECT",
"-p", "IPv6",
"--ip6-source", prefix,
"--ip6-destination", "!", prefix,
"-d", "!", mac,
"-j", "dnat",
"--to-destination", mac,
NULL }))
error_message(0, 0, "warning: adding new rule to ebtables chain REDIRECT failed");
}
}
static int fork_execvp_timeout(struct timespec *timeout, const char *file, const char *const argv[]) {
int ret;
pid_t child;
......@@ -795,6 +888,8 @@ int main(int argc, char *argv[]) {
timespec_diff(&now, &next_update, &diff)) {
expire_routers();
update_redirect();
// all routers could have expired, check again
if (G.routers != NULL) {
if(timespec_diff(&now, &next_invalidation, &diff)) {
......
......@@ -4,6 +4,7 @@
#include <libgluonutil.h>
#include <net/ethernet.h>
#include <stdio.h>
#include <string.h>
#include "mac.h"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment