From 0a939bbc3610cf80d7358377b86f24e536620f0c Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Thu, 5 Nov 2020 09:46:03 +0100 Subject: [PATCH 01/15] Implement multicast bind support in configuration framework 1) Adds sourceaddr (packet source address for outgoing packets) and interval (discovery interval for multicast enabled sockets) to the corresponding bind address structure. 2) Implements the corresponding grammar for the configuration file. 3) Adds option to let the configuration resolve the actual address to bind to for explicit link-local IPv6 binds. 4) Adds sanity checks for the address setup in configuration. --- src/build.h.in | 7 ++++ src/config.c | 112 ++++++++++++++++++++++++++++++++++++++++++++----- src/config.h | 4 +- src/config.y | 53 +++++++++++++++++++---- src/fastd.h | 57 ++++++++++++++++++++++--- src/lex.c | 3 ++ src/options.c | 3 +- src/socket.c | 2 +- 8 files changed, 214 insertions(+), 27 deletions(-) diff --git a/src/build.h.in b/src/build.h.in index 0bd2ca64..0f8741e0 100644 --- a/src/build.h.in +++ b/src/build.h.in @@ -118,6 +118,13 @@ #define REORDER_TIME 10000 +/** Minimal discovery interval */ +#define MIN_DISCOVERY_INTERVAL 1000 /* 1 second */ + +/** Default discovery interval */ +#define DEFAULT_DISCOVERY_INTERVAL 5000 /* 5 seconds */ + + /** The minimum time that must pass between two on-verify calls on the same peer */ #define MIN_VERIFY_INTERVAL 10000 /* 10 seconds */ diff --git a/src/config.c b/src/config.c index 9ba05ad2..23e765da 100644 --- a/src/config.c +++ b/src/config.c @@ -26,6 +26,7 @@ #include "peer_group.h" #include +#include #include #include #include @@ -119,34 +120,123 @@ void fastd_config_mac(const char *name, const char *impl) { impl, name, name); } -/** Handles the configuration of a bind address */ -void fastd_config_bind_address(const fastd_peer_address_t *address, const char *bindtodev, unsigned flags) { -#ifndef USE_BINDTODEVICE - if (bindtodev && !fastd_peer_address_is_v6_ll(address)) - exit_error("config error: device bind configuration not supported on this system"); -#endif +/** Normalization of a bind address, initializing the target address structure */ +static void normalize_address(fastd_peer_address_t *address, const fastd_peer_address_t *config, const char *bindtodev) { + if (config->sa.sa_family == AF_INET6 && config->in6.sin6_scope_id) { + if (!bindtodev) + exit_error("config error: need interface to bind to resolve ll6 address"); + + struct ifaddrs *ifaddrs; + if (getifaddrs(&ifaddrs)) + exit_error("config error: failed to resolve interface addresses"); + + struct ifaddrs *ifa; + for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { + if (strcmp(ifa->ifa_name, bindtodev) || !ifa->ifa_addr) + continue; + + const fastd_peer_address_t *addr = (const fastd_peer_address_t *)ifa->ifa_addr; + if (!fastd_peer_address_is_v6_ll(addr)) + continue; + address->in6 = addr->in6; + address->in6.sin6_port = config->in6.sin6_port; + break; + } + + freeifaddrs(ifaddrs); + if (!ifa) + exit_error("config error: failed to resolve any ll6 address for interface"); + } else { + *address = *config; + if (bindtodev && fastd_peer_address_is_v6_ll(address)) { + char *end; + address->in6.sin6_scope_id = strtoul(bindtodev, &end, 10); + + if (*end || !address->in6.sin6_scope_id) + address->in6.sin6_scope_id = if_nametoindex(bindtodev); + + if (!address->in6.sin6_scope_id) + exit_error("config error: failed to resolve IPv6 LL scope interface"); + } else + fastd_peer_address_simplify(address); + } +} + +/** Handles the configuration of a bind address */ +void fastd_config_bind_address( + const fastd_peer_address_t *address, const char *bindtodev, const fastd_peer_address_t *sourceaddr, + fastd_timeout_t interval, unsigned flags) { #ifndef USE_MULTIAF_BIND if (address->sa.sa_family == AF_UNSPEC) { fastd_peer_address_t addr4 = { .in = { .sin_family = AF_INET, .sin_port = address->in.sin_port } }; fastd_peer_address_t addr6 = { .in6 = { .sin6_family = AF_INET6, .sin6_port = address->in.sin_port } }; - fastd_config_bind_address(&addr4, bindtodev, flags); - fastd_config_bind_address(&addr6, bindtodev, flags); + if (sourceaddr->sa.sa_family != AF_INET6) + fastd_config_bind_address(&addr4, bindtodev, sourceaddr, interval, flags); + if (sourceaddr->sa.sa_family != AF_INET) + fastd_config_bind_address(&addr6, bindtodev, sourceaddr, interval, flags); return; } #endif fastd_bind_address_t *addr = fastd_new(fastd_bind_address_t); + + normalize_address(&addr->addr, address, bindtodev); + normalize_address(&addr->sourceaddr, sourceaddr, bindtodev); + + if (fastd_peer_address_is_multicast(&addr->addr)) { + if (addr->addr.sa.sa_family == AF_INET) { + if (!addr->addr.in.sin_port) + exit_error("config error: multicast bind requires port specification"); +#ifndef USE_PKTINFO + exit_error("config error: multicast IPv4 requires PKTINFO support"); +#endif + } else if (!addr->addr.in6.sin6_port) + exit_error("config error: multicast bind with no interface requires source address"); + + if (!bindtodev) { + if (addr->addr.sa.sa_family == AF_INET6) + exit_error("config error: multicast IPv6 bind requires bind interface"); + if (addr->sourceaddr.sa.sa_family == AF_UNSPEC) + exit_error("config error: multicast IPv4 bind requires bind interface or source address"); + } + + if (addr->sourceaddr.sa.sa_family != AF_UNSPEC && addr->addr.sa.sa_family != addr->sourceaddr.sa.sa_family) + exit_error("config error: family of source address does not match family of multicast address"); + + if (interval != FASTD_TIMEOUT_INV) { + if (interval && interval < MIN_DISCOVERY_INTERVAL) + exit_error("config error: discovery interval is smaller than minimum 1 second"); + } else + interval = DEFAULT_DISCOVERY_INTERVAL; + } else { +#ifndef USE_BINDTODEVICE + if (bindtodev && !fastd_peer_address_is_v6_ll(&addr->addr)) + exit_error("config error: device bind configuration not supported on this system"); +#endif + + if (addr->addr.sa.sa_family != AF_UNSPEC && addr->sourceaddr.sa.sa_family != AF_UNSPEC) { + if (addr->addr.sa.sa_family != addr->sourceaddr.sa.sa_family) + exit_error("config error: family of source address does not match family of bind address"); + if (!fastd_peer_address_is_any(&addr->addr) && !fastd_peer_address_is_host_equal(&addr->addr, &addr->sourceaddr)) + exit_error("config error: source address is different from explicitly bound address"); + } + + if (interval != FASTD_TIMEOUT_INV) + exit_error("config error: discovery interval set on non-discovery enabled socket"); + } + + if (addr->sourceaddr.sa.sa_family != AF_UNSPEC && fastd_peer_address_is_multicast(&addr->sourceaddr)) + exit_error("config error: source address is a multicast address"); + addr->next = conf.bind_addrs; conf.bind_addrs = addr; conf.n_bind_addrs++; - addr->addr = *address; addr->flags = flags; addr->bindtodev = fastd_strdup(bindtodev); - - fastd_peer_address_simplify(&addr->addr); + addr->interval = interval; if (addr->addr.sa.sa_family != AF_INET6 && ((flags & FASTD_BIND_DEFAULT_IPV4) || !conf.bind_addr_default_v4)) conf.bind_addr_default_v4 = addr; diff --git a/src/config.h b/src/config.h index 8f6d4666..8b261dc4 100644 --- a/src/config.h +++ b/src/config.h @@ -31,7 +31,9 @@ void fastd_config_method(fastd_peer_group_t *group, const char *name); bool fastd_config_ifname(fastd_peer_t *peer, const char *ifname); void fastd_config_cipher(const char *name, const char *impl); void fastd_config_mac(const char *name, const char *impl); -void fastd_config_bind_address(const fastd_peer_address_t *address, const char *bindtodev, unsigned flags); +void fastd_config_bind_address( + const fastd_peer_address_t *address, const char *bindtodev, const fastd_peer_address_t *sourceaddr, + fastd_timeout_t interval, unsigned flags); void fastd_config_release(void); void fastd_config_handle_options(int argc, char *const argv[]); void fastd_config_verify(void); diff --git a/src/config.y b/src/config.y index a3a03fb2..38c9daf3 100644 --- a/src/config.y +++ b/src/config.y @@ -74,12 +74,14 @@ %token TOK_INCLUDE %token TOK_INFO %token TOK_INTERFACE +%token TOK_INTERVAL %token TOK_IP %token TOK_IPV4 %token TOK_IPV6 %token TOK_KEY %token TOK_LEVEL %token TOK_LIMIT +%token TOK_LL6 %token TOK_LOG %token TOK_MAC %token TOK_MARK @@ -102,6 +104,7 @@ %token TOK_SECRET %token TOK_SECURE %token TOK_SOCKET +%token TOK_SOURCE %token TOK_STATUS %token TOK_STDERR %token TOK_SYNC @@ -126,7 +129,8 @@ #include static void fastd_config_handle_bind_address( - fastd_peer_address_t address, int64_t maybe_port, const char *bindtodevice, unsigned bind_default); + fastd_peer_address_t address, int64_t maybe_port, const char *bindtodevice, fastd_peer_address_t source, + fastd_timeout_t interval, unsigned bind_default); static void fastd_config_error(YYLTYPE *loc, fastd_parser_state_t *state, const char *s); } @@ -142,6 +146,8 @@ %type maybe_af %type bind_address %type maybe_bind_interface +%type maybe_source_address +%type maybe_interval %type maybe_bind_default %type bind_default %type drop_capabilities_enabled @@ -306,12 +312,12 @@ interface: TOK_STRING { } ; -bind: bind_address maybe_bind_port maybe_bind_interface maybe_bind_default { - fastd_config_handle_bind_address($1, $2, $3 ? $3->str : NULL, $4); +bind: bind_address maybe_bind_port maybe_bind_interface maybe_source_address maybe_interval maybe_bind_default { + fastd_config_handle_bind_address($1, $2, $3 ? $3->str : NULL, $4, $5, $6); } - | TOK_ADDR6_SCOPED maybe_bind_port maybe_bind_default { + | TOK_ADDR6_SCOPED maybe_bind_port maybe_source_address maybe_interval maybe_bind_default { fastd_peer_address_t addr = { .in6 = { .sin6_family = AF_INET6, .sin6_addr = $1.addr } }; - fastd_config_handle_bind_address(addr, $2, $1.ifname, $3); + fastd_config_handle_bind_address(addr, $2, $1.ifname, $3, $4, $5); } ; @@ -322,8 +328,11 @@ bind_address: | TOK_ADDR6 { $$ = (fastd_peer_address_t){ .in6 = { .sin6_family = AF_INET6, .sin6_addr = $1 } }; } + | TOK_LL6 { + $$ = (fastd_peer_address_t){ .in6 = { .sin6_family = AF_INET6, .sin6_scope_id = -1 } }; + } | TOK_ANY { - $$ = (fastd_peer_address_t){ .in = { .sin_family = AF_UNSPEC } }; + $$ = (fastd_peer_address_t){ .sa = { .sa_family = AF_UNSPEC } }; } ; @@ -336,6 +345,30 @@ maybe_bind_interface: } ; +maybe_source_address: + TOK_SOURCE TOK_ADDR4 { + $$ = (fastd_peer_address_t){ .in = { .sin_family = AF_INET, .sin_addr = $2 } }; + } + | TOK_SOURCE TOK_ADDR6 { + $$ = (fastd_peer_address_t){ .in6 = { .sin6_family = AF_INET6, .sin6_addr = $2 } }; + } + | TOK_SOURCE TOK_LL6 { + $$ = (fastd_peer_address_t){ .in6 = { .sin6_family = AF_INET6, .sin6_scope_id = -1 } }; + } + | { + $$ = (fastd_peer_address_t){ .sa = { .sa_family = AF_UNSPEC } }; + } + ; + +maybe_interval: + TOK_INTERVAL TOK_UINT { + $$ = $2; + } + | { + $$ = FASTD_TIMEOUT_INV; + } + ; + maybe_bind_default: TOK_DEFAULT bind_default { $$ = $2; @@ -671,7 +704,8 @@ bind_port: colon_or_port TOK_UINT { %% static void fastd_config_handle_bind_address( - fastd_peer_address_t address, int64_t maybe_port, const char *bindtodevice, unsigned bind_default) { + fastd_peer_address_t address, int64_t maybe_port, const char *bindtodevice, + fastd_peer_address_t source, fastd_timeout_t interval, unsigned bind_default) { unsigned flags = bind_default; uint16_t port = 0; @@ -686,7 +720,10 @@ static void fastd_config_handle_bind_address( else address.in6.sin6_port = port; - fastd_config_bind_address(&address, bindtodevice, flags); + if (interval != FASTD_TIMEOUT_INV) + interval *= 1000; + + fastd_config_bind_address(&address, bindtodevice, &source, interval, flags); } static void fastd_config_error(YYLTYPE *loc, fastd_parser_state_t *state, const char *s) { diff --git a/src/fastd.h b/src/fastd.h index a4caf488..3bc45895 100644 --- a/src/fastd.h +++ b/src/fastd.h @@ -140,10 +140,12 @@ union fastd_peer_address { /** A linked list of addresses to bind to */ struct fastd_bind_address { - fastd_bind_address_t *next; /**< The next address in the list */ - fastd_peer_address_t addr; /**< The address to bind to */ - unsigned flags; /**< FASTD_BIND_* flags */ - char *bindtodev; /**< May contain an interface name to limit the bind to */ + fastd_bind_address_t *next; /**< The next address in the list */ + fastd_peer_address_t addr; /**< The address to bind to */ + fastd_peer_address_t sourceaddr; /**< Source address to use for packets */ + unsigned flags; /**< FASTD_BIND_* flags */ + char *bindtodev; /**< May contain an interface name to limit the bind to */ + fastd_timeout_t interval; /**< Discovery interval if discovery enabled, or FASTD_TIMEOUT_INV */ }; /** A socket descriptor */ @@ -473,12 +475,57 @@ static inline fastd_eth_addr_t fastd_buffer_dest_address(const fastd_buffer_t *b return ret; } +/** Checks if a fastd_peer_address_t is the IPv4 any address */ +static inline bool fastd_peer_address_is_v4_any(const fastd_peer_address_t *addr) { + return addr->sa.sa_family == AF_INET && addr->in.sin_addr.s_addr == INADDR_ANY; +} + +/** Checks if a fastd_peer_address_t is an IPv4 multicast address */ +static inline bool fastd_peer_address_is_v4_multicast(const fastd_peer_address_t *addr) { + return addr->sa.sa_family == AF_INET && IN_MULTICAST(ntohl(addr->in.sin_addr.s_addr)); +} + +/** Checks if host parts of two IPv4 fastd_peer_address_t are equal */ +static inline bool fastd_peer_address_is_v4_host_equal(const fastd_peer_address_t *addr1, const fastd_peer_address_t *addr2) { + return addr1->sa.sa_family == AF_INET && addr2->sa.sa_family == AF_INET && addr1->in.sin_addr.s_addr == addr2->in.sin_addr.s_addr; +} + +/** Checks if a fastd_peer_address_t is the IPv6 any address */ +static inline bool fastd_peer_address_is_v6_any(const fastd_peer_address_t *addr) { + return addr->sa.sa_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&addr->in6.sin6_addr); +} /** Checks if a fastd_peer_address_t is an IPv6 link-local address */ static inline bool fastd_peer_address_is_v6_ll(const fastd_peer_address_t *addr) { - return (addr->sa.sa_family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&addr->in6.sin6_addr)); + return addr->sa.sa_family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&addr->in6.sin6_addr); +} + +/** Checks if a fastd_peer_address_t is an IPv6 multicast address */ +static inline bool fastd_peer_address_is_v6_multicast(const fastd_peer_address_t *addr) { + return addr->sa.sa_family == AF_INET6 && IN6_IS_ADDR_MULTICAST(&addr->in6.sin6_addr); } +/** Checks if host parts of two IPv6 fastd_peer_address_t are equal */ +static inline bool fastd_peer_address_is_v6_host_equal(const fastd_peer_address_t *addr1, const fastd_peer_address_t *addr2) { + return addr1->sa.sa_family == AF_INET6 && addr2->sa.sa_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&addr1->in6.sin6_addr, &addr2->in6.sin6_addr); +} + +/** Checks if the fastd_peer_address_t represents the IPv4 or IPv6 any address */ +static inline bool fastd_peer_address_is_any(const fastd_peer_address_t *addr) { + return fastd_peer_address_is_v4_any(addr) || fastd_peer_address_is_v6_any(addr); +} + +/** Checks if the fastd_peer_address_t represents an IPv4 or IPv6 multicast address */ +static inline bool fastd_peer_address_is_multicast(const fastd_peer_address_t *addr) { + return fastd_peer_address_is_v4_multicast(addr) || fastd_peer_address_is_v6_multicast(addr); +} + +/** Checks if host parts of two fastd_peer_address_t are equal */ +static inline bool fastd_peer_address_is_host_equal(const fastd_peer_address_t *addr1, const fastd_peer_address_t *addr2) { + return fastd_peer_address_is_v4_host_equal(addr1, addr2) || fastd_peer_address_is_v6_host_equal(addr1, addr2); +} + + /** Duplicates a string, creating a one-element string stack */ static inline fastd_string_stack_t *fastd_string_stack_dup(const char *str) { size_t str_len = strlen(str); diff --git a/src/lex.c b/src/lex.c index 769c29d1..80ef4bd5 100644 --- a/src/lex.c +++ b/src/lex.c @@ -71,12 +71,14 @@ static const keyword_t keywords[] = { { "include", TOK_INCLUDE }, { "info", TOK_INFO }, { "interface", TOK_INTERFACE }, + { "interval", TOK_INTERVAL }, { "ip", TOK_IP }, { "ipv4", TOK_IPV4 }, { "ipv6", TOK_IPV6 }, { "key", TOK_KEY }, { "level", TOK_LEVEL }, { "limit", TOK_LIMIT }, + { "ll6", TOK_LL6 }, { "log", TOK_LOG }, { "mac", TOK_MAC }, { "mark", TOK_MARK }, @@ -99,6 +101,7 @@ static const keyword_t keywords[] = { { "secret", TOK_SECRET }, { "secure", TOK_SECURE }, { "socket", TOK_SOCKET }, + { "source", TOK_SOURCE }, { "status", TOK_STATUS }, { "stderr", TOK_STDERR }, { "sync", TOK_SYNC }, diff --git a/src/options.c b/src/options.c index b5350665..b611acb2 100644 --- a/src/options.c +++ b/src/options.c @@ -287,7 +287,8 @@ static void option_bind(const char *arg) { free(addrstr); - fastd_config_bind_address(&addr, ifname, flags); + fastd_peer_address_t sourceaddr = { .sa = { .sa_family = AF_UNSPEC } }; + fastd_config_bind_address(&addr, ifname, &sourceaddr, FASTD_TIMEOUT_INV, flags); } /** Handles the --protocol option */ diff --git a/src/socket.c b/src/socket.c index e65f7edb..1b50e282 100644 --- a/src/socket.c +++ b/src/socket.c @@ -199,7 +199,7 @@ void fastd_socket_bind_all(void) { /** Opens a single socket bound to a random port for the given address family */ fastd_socket_t *fastd_socket_open(fastd_peer_t *peer, int af) { - const fastd_bind_address_t any_address = { .addr.sa.sa_family = af }; + const fastd_bind_address_t any_address = { .addr.sa.sa_family = af, .sourceaddr.sa.sa_family = AF_UNSPEC, .interval = FASTD_TIMEOUT_INV }; const fastd_bind_address_t *bind_address; From 1933b6287f79e577d66574261347a4b3e405d460 Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Thu, 5 Nov 2020 20:19:52 +0100 Subject: [PATCH 02/15] Split socket options into IPv4/IPv6 applied options and fix IPv6-LL addressing 1) Socket options that are set in src/socket.c are now explicitly selected for IPv4 or IPv6. IP_FREEBIND works - normally - for both socket families, but as the newer Linux and glibc versions also implement the specific IPV6_FREEBIND socket option, defer to this if available. 2) Socket options that are only relevant on an AF_INET/AF_INET6 socket are correspondingly only set anymore for the specific socket type. 3) Resolution of the IPv6 scope identifier in the actual socket bind is no longer necessary, as it's resolved in the configuration setup for the bind address anyway (normalize_address()). --- src/meson.build | 1 + src/socket.c | 66 ++++++++++++++++++++++--------------------------- 2 files changed, 30 insertions(+), 37 deletions(-) diff --git a/src/meson.build b/src/meson.build index f6909ba5..5d7dab4a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -194,6 +194,7 @@ conf_data.set('USE_BINDTODEVICE', is_android or is_linux) conf_data.set('USE_EPOLL', is_android or is_linux) conf_data.set('USE_SELECT', is_darwin) conf_data.set('USE_FREEBIND', is_android or is_linux) +conf_data.set('USE_FREEBIND6', cc.has_header_symbol('netinet/in.h', 'IPV6_FREEBIND')) conf_data.set('USE_PMTU', is_android or is_linux) conf_data.set('USE_PKTINFO', is_android or is_linux) conf_data.set('USE_PACKET_MARK', is_linux) diff --git a/src/socket.c b/src/socket.c index 1b50e282..f4924448 100644 --- a/src/socket.c +++ b/src/socket.c @@ -22,8 +22,11 @@ \return The new socket's file descriptor */ static int bind_socket(const fastd_bind_address_t *addr) { + const int zero = 0; + const int one = 1; int fd = -1; int af = AF_UNSPEC; + fastd_peer_address_t bind_address = addr->addr; if (addr->addr.sa.sa_family != AF_INET) { fd = socket(PF_INET6, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); @@ -52,68 +55,57 @@ static int bind_socket(const fastd_bind_address_t *addr) { fastd_setnonblock(fd); #endif - int one = 1; - #ifdef USE_PKTINFO - if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one))) { + if (af == AF_INET && setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one))) { pr_error_errno("setsockopt: unable to set IP_PKTINFO"); goto error; } #endif + if (af == AF_INET6 && setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one))) { + pr_error_errno("setsockopt: unable to set IPV6_RECVPKTINFO"); + goto error; + } + #ifdef USE_FREEBIND +#ifdef USE_FREEBIND6 + if (af == AF_INET && setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one))) + pr_warn_errno("setsockopt: unable to set IP_FREEBIND"); + if (af == AF_INET6 && setsockopt(fd, IPPROTO_IPV6, IPV6_FREEBIND, &one, sizeof(one))) + pr_warn_errno("setsockopt: unable to set IPV6_FREEBIND"); +#else if (setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one))) pr_warn_errno("setsockopt: unable to set IP_FREEBIND"); #endif - - if (af == AF_INET6) { - if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one))) { - pr_error_errno("setsockopt: unable to set IPV6_RECVPKTINFO"); - goto error; - } - } +#endif #ifdef USE_BINDTODEVICE - if (addr->bindtodev && !fastd_peer_address_is_v6_ll(&addr->addr)) { - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, addr->bindtodev, strlen(addr->bindtodev))) { - pr_warn_errno("setsockopt: unable to bind to device"); - goto error; - } + if (addr->bindtodev && !fastd_peer_address_is_v6_ll(&addr->addr) && + setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, addr->bindtodev, strlen(addr->bindtodev))) { + pr_warn_errno("setsockopt: unable to bind to device"); + goto error; } #endif #ifdef USE_PMTU - int pmtu = IP_PMTUDISC_DONT; - if (setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu))) { + const int pmtu = IP_PMTUDISC_DONT; + if (af == AF_INET && setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu))) { + pr_error_errno("setsockopt: unable to disable PMTU discovery"); + goto error; + } + if (af == AF_INET6 && setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &pmtu, sizeof(pmtu))) { pr_error_errno("setsockopt: unable to disable PMTU discovery"); goto error; } #endif #ifdef USE_PACKET_MARK - if (conf.packet_mark) { - if (setsockopt(fd, SOL_SOCKET, SO_MARK, &conf.packet_mark, sizeof(conf.packet_mark))) { - pr_error_errno("setsockopt: unable to set packet mark"); - goto error; - } + if (conf.packet_mark && setsockopt(fd, SOL_SOCKET, SO_MARK, &conf.packet_mark, sizeof(conf.packet_mark))) { + pr_error_errno("setsockopt: unable to set packet mark"); + goto error; } #endif - fastd_peer_address_t bind_address = addr->addr; - - if (fastd_peer_address_is_v6_ll(&addr->addr) && addr->bindtodev) { - char *end; - bind_address.in6.sin6_scope_id = strtoul(addr->bindtodev, &end, 10); - - if (*end) - bind_address.in6.sin6_scope_id = if_nametoindex(addr->bindtodev); - - if (!bind_address.in6.sin6_scope_id) { - pr_warn_errno("if_nametoindex"); - goto error; - } - } - if (bind_address.sa.sa_family == AF_UNSPEC) { memset(&bind_address, 0, sizeof(bind_address)); bind_address.sa.sa_family = af; From 8a8ca9eb8511a4a2bdb89debe2eb23473692154c Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Thu, 5 Nov 2020 21:05:58 +0100 Subject: [PATCH 03/15] Update socket binding framework to handle multicast addresses. 1) Update socket binding function to handle IPv4/IPv6 multicast addresses by binding to the corresponding address family any address (otherwise, at least on Linux, you won't see the multicast packets on the socket), and setting up the corresponding multicast join for the socket. This does not need to be cleaned up, as the multicast bind is automatically left when the socket is closed. 2) Preferrably, use IPv6 sockets, even for IPv4 addresses. This means that IPv4 binds and also IPv4 any binds are always done on IPv6 sockets, with the corresponding option IPV6_ONLY not set and the address widened for the bind. As the underlying socket, even when it's only used for IPv4 traffic, supports all of the IPv6 socket functionality, means that IPV6_PKTINFO is also available, even when receiving only IPv4 traffic on the socket, so removes the reliance on IP_PKTINFO. This is relevant on some older Linux releases. 3) Multicast IPv4 sockets still require to be bound on an IPv4 socket, as they explicitly use socket options that are IPv4-socket only. This is the only class of addresses remaining which initializes an AF_INET socket explicitly. TODO: Especially 2) needs to be checked. Linux behaves correctly (i.e., when binding a widened IPv4 address on the IPv6 socket, the socket behaves just like an IPv4 only socket, except that all addresses are wide), and AFAIK this is also required behavior for the corresponding IPv6 sockets API. But, possibly, there are some other implementations around which do not properly map the IPv4 address space for IPv6 sockets. Needs to be checked. --- src/socket.c | 112 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 83 insertions(+), 29 deletions(-) diff --git a/src/socket.c b/src/socket.c index f4924448..f3dee751 100644 --- a/src/socket.c +++ b/src/socket.c @@ -11,6 +11,7 @@ */ #include "fastd.h" +#include "peer.h" #include "polling.h" #include @@ -28,28 +29,45 @@ static int bind_socket(const fastd_bind_address_t *addr) { int af = AF_UNSPEC; fastd_peer_address_t bind_address = addr->addr; - if (addr->addr.sa.sa_family != AF_INET) { + if (!fastd_peer_address_is_v4_multicast(&bind_address)) { fd = socket(PF_INET6, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); if (fd >= 0) { af = AF_INET6; - int val = (addr->addr.sa.sa_family == AF_INET6); + const int val = bind_address.sa.sa_family == AF_INET6 || addr->sourceaddr.sa.sa_family == AF_INET6; if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val))) { - pr_warn_errno("setsockopt"); + pr_warn_errno("setsockopt: unable to set socket for IPv6 only"); goto error; } } } - if (fd < 0 && addr->addr.sa.sa_family != AF_INET6) { + + if (fd < 0 && bind_address.sa.sa_family != AF_INET6 && addr->sourceaddr.sa.sa_family != AF_INET6) { fd = socket(PF_INET, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); - if (fd < 0) - exit_errno("unable to create socket"); - else + if (fd >= 0) af = AF_INET; } - if (fd < 0) + if (fd < 0) { + pr_error_errno("socket: unable to create socket"); goto error; + } + + if ((bind_address.sa.sa_family == AF_UNSPEC && (af == AF_INET || addr->sourceaddr.sa.sa_family == AF_INET)) || + fastd_peer_address_is_v4_multicast(&bind_address)) { + bind_address.in.sin_family = AF_INET; + bind_address.in.sin_addr.s_addr = INADDR_ANY; + + if (af == AF_INET6) + fastd_peer_address_widen(&bind_address); + } else if (bind_address.sa.sa_family == AF_UNSPEC || fastd_peer_address_is_v6_multicast(&bind_address)) { + bind_address.in6.sin6_family = AF_INET6; + bind_address.in6.sin6_addr = in6addr_any; + + if (addr->addr.sa.sa_family == AF_UNSPEC) + bind_address.in6.sin6_port = addr->addr.in.sin_port; + } else if (af == AF_INET6) + fastd_peer_address_widen(&bind_address); #ifdef NO_HAVE_SOCK_NONBLOCK fastd_setnonblock(fd); @@ -80,7 +98,7 @@ static int bind_socket(const fastd_bind_address_t *addr) { #endif #ifdef USE_BINDTODEVICE - if (addr->bindtodev && !fastd_peer_address_is_v6_ll(&addr->addr) && + if (addr->bindtodev && !fastd_peer_address_is_v6_ll(&bind_address) && setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, addr->bindtodev, strlen(addr->bindtodev))) { pr_warn_errno("setsockopt: unable to bind to device"); goto error; @@ -106,20 +124,58 @@ static int bind_socket(const fastd_bind_address_t *addr) { } #endif - if (bind_address.sa.sa_family == AF_UNSPEC) { - memset(&bind_address, 0, sizeof(bind_address)); - bind_address.sa.sa_family = af; - - if (af == AF_INET6) - bind_address.in6.sin6_port = addr->addr.in.sin_port; - else - bind_address.in.sin_port = addr->addr.in.sin_port; + if (bind(fd, &bind_address.sa, af == AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))) { + pr_warn_errno("bind: unable to bind socket"); + goto error; } - if (bind(fd, &bind_address.sa, - bind_address.sa.sa_family == AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))) { - pr_warn_errno("bind"); - goto error; + if (fastd_peer_address_is_v4_multicast(&addr->addr)) { + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &zero, sizeof(zero))) { + pr_error_errno("setsockopt: unable to disable IPv4 multicast loop"); + goto error; + } + + struct ip_mreqn mreq = { .imr_multiaddr = addr->addr.in.sin_addr }; + if (addr->sourceaddr.sa.sa_family != AF_UNSPEC) + mreq.imr_address = addr->sourceaddr.in.sin_addr; + if (addr->bindtodev) { + mreq.imr_ifindex = if_nametoindex(addr->bindtodev); + if (!mreq.imr_ifindex) { + pr_error_errno("if_nametoindex: failed to resolve IPv4 multicast device"); + goto error; + } + } + + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &mreq, sizeof(mreq))) { + pr_error_errno("setsockopt: unable to set ip IPv4 multicast interface binding"); + goto error; + } + + if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))) { + pr_error_errno("setsockopt: failed to join IPv4 multicast group"); + goto error; + } + } else if (fastd_peer_address_is_v6_multicast(&addr->addr)) { + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero, sizeof(zero))) { + pr_error_errno("setsockopt: unable to disable IPv6 multicast loop"); + goto error; + } + + struct ipv6_mreq mreq = { .ipv6mr_multiaddr = addr->addr.in6.sin6_addr, .ipv6mr_interface = if_nametoindex(addr->bindtodev) }; + if (!mreq.ipv6mr_interface) { + pr_error_errno("if_nametoindex: failed to resolve IPv6 multicast device"); + goto error; + } + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &mreq.ipv6mr_interface, sizeof(mreq.ipv6mr_interface))) { + pr_error_errno("setsockopt: unable to set up IPv6 multicast interface binding"); + goto error; + } + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq))) { + pr_error_errno("setsockopt: failed to join IPv6 multicast group"); + goto error; + } } #ifdef __ANDROID__ @@ -132,18 +188,16 @@ static int bind_socket(const fastd_bind_address_t *addr) { return fd; error: - if (fd >= 0) { - if (close(fd)) - pr_error_errno("close"); - } + if (fd >= 0 && close(fd)) + pr_error_errno("close"); if (addr->bindtodev) pr_error( - fastd_peer_address_is_v6_ll(&addr->addr) ? "unable to bind to %L" - : "unable to bind to %B on `%s'", - &addr->addr, addr->bindtodev); + fastd_peer_address_is_v6_ll(&bind_address) ? "unable to bind to %L" + : "unable to bind to %B on `%s'", + &bind_address, addr->bindtodev); else - pr_error("unable to bind to %B", &addr->addr); + pr_error("unable to bind to %B", &bind_address); return -1; } From 8de0f23980278906c1f016287cefb264ec2c2472 Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Thu, 5 Nov 2020 21:34:34 +0100 Subject: [PATCH 04/15] Use bound_addr in socket structure to represent unicast processing address, not just bind. 1) bound_addr in the socket structure was a pointer to an allocated structure, which, as it's always set up anyway, simply not required to be a dynamic pointer. Change this to be a member structure of the socket structure. 2) Use the host part of sourceaddr if that is set up as the bound address for the socket, as this is the address that is supposed to be processed for unicast packets and also used as the source address for outgoing packets. This means that bound_addr does not explicitly represent the actual bind anymore in some places, but that's not required by any of the code that currently uses bound_addr. --- src/fastd.h | 4 +-- src/protocols/ec25519_fhmqvc/handshake.c | 2 +- src/receive.c | 4 +-- src/send.c | 2 +- src/socket.c | 41 +++++++++++------------- 5 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/fastd.h b/src/fastd.h index 3bc45895..2ca6c171 100644 --- a/src/fastd.h +++ b/src/fastd.h @@ -152,8 +152,8 @@ struct fastd_bind_address { struct fastd_socket { fastd_poll_fd_t fd; /**< The file descriptor for the socket */ const fastd_bind_address_t *addr; /**< The address this socket is supposed to be bound to (or NULL) */ - fastd_peer_address_t *bound_addr; /**< The actual address that was bound to (may differ from addr when addr has - a random port) */ + fastd_peer_address_t bound_addr; /**< The actual address that was bound to (may differ from addr when addr has + a random port) and also the source address to require for incoming packets */ fastd_peer_t *peer; /**< If the socket belongs to a single peer (as it was create dynamically when sending a handshake), contains that peer */ }; diff --git a/src/protocols/ec25519_fhmqvc/handshake.c b/src/protocols/ec25519_fhmqvc/handshake.c index f9b9c183..ed324c5c 100644 --- a/src/protocols/ec25519_fhmqvc/handshake.c +++ b/src/protocols/ec25519_fhmqvc/handshake.c @@ -474,7 +474,7 @@ void fastd_protocol_ec25519_fhmqvc_handshake_init( if (!peer || !fastd_peer_is_established(peer)) { const fastd_shell_command_t *on_connect = fastd_peer_group_lookup_peer_shell_command(peer, on_connect); fastd_peer_exec_shell_command( - on_connect, peer, (local_addr && local_addr->sa.sa_family) ? local_addr : sock->bound_addr, + on_connect, peer, (local_addr && local_addr->sa.sa_family) ? local_addr : &sock->bound_addr, remote_addr, false); } diff --git a/src/receive.c b/src/receive.c index 6bca9f48..e0e056e2 100644 --- a/src/receive.c +++ b/src/receive.c @@ -43,7 +43,7 @@ handle_socket_control(struct msghdr *message, const fastd_socket_t *sock, fastd_ local_addr->in.sin_family = AF_INET; local_addr->in.sin_addr = pktinfo.ipi_addr; - local_addr->in.sin_port = fastd_peer_address_get_port(sock->bound_addr); + local_addr->in.sin_port = fastd_peer_address_get_port(&sock->bound_addr); return; } @@ -59,7 +59,7 @@ handle_socket_control(struct msghdr *message, const fastd_socket_t *sock, fastd_ local_addr->in6.sin6_family = AF_INET6; local_addr->in6.sin6_addr = pktinfo.ipi6_addr; - local_addr->in6.sin6_port = fastd_peer_address_get_port(sock->bound_addr); + local_addr->in6.sin6_port = fastd_peer_address_get_port(&sock->bound_addr); if (IN6_IS_ADDR_LINKLOCAL(&local_addr->in6.sin6_addr)) local_addr->in6.sin6_scope_id = pktinfo.ipi6_ifindex; diff --git a/src/send.c b/src/send.c index a3e06309..5db289cf 100644 --- a/src/send.c +++ b/src/send.c @@ -91,7 +91,7 @@ void fastd_send( exit_bug("unsupported address family"); } - if (sock->bound_addr->sa.sa_family == AF_INET6) { + if (sock->bound_addr.sa.sa_family == AF_INET6) { remote_addr6 = *remote_addr; fastd_peer_address_widen(&remote_addr6); diff --git a/src/socket.c b/src/socket.c index f3dee751..f336e441 100644 --- a/src/socket.c +++ b/src/socket.c @@ -208,10 +208,18 @@ static void set_bound_address(fastd_socket_t *sock) { socklen_t len = sizeof(addr); if (getsockname(sock->fd.fd, &addr.sa, &len) < 0) - exit_errno("getsockname"); - - sock->bound_addr = fastd_new(fastd_peer_address_t); - *sock->bound_addr = addr; + exit_errno("getsockname: failed to resolve bound address for socket"); + + if (sock->addr && sock->addr->sourceaddr.sa.sa_family != AF_UNSPEC) { + sock->bound_addr = sock->addr->sourceaddr; + + if (addr.sa.sa_family == AF_INET6) { + fastd_peer_address_widen(&sock->bound_addr); + sock->bound_addr.in6.sin6_port = addr.in6.sin6_port; + } else + sock->bound_addr.in.sin_port = addr.in.sin_port; + } else + sock->bound_addr = addr; } /** Tries to initialize sockets for all configured bind addresses */ @@ -230,14 +238,10 @@ void fastd_socket_bind_all(void) { set_bound_address(sock); - fastd_peer_address_t bound_addr = *sock->bound_addr; - if (!sock->addr->addr.sa.sa_family) - bound_addr.sa.sa_family = AF_UNSPEC; - - if (sock->addr->bindtodev && !fastd_peer_address_is_v6_ll(&bound_addr)) - pr_info("bound to %B on `%s'", &bound_addr, sock->addr->bindtodev); + if (sock->addr->bindtodev && !fastd_peer_address_is_v6_ll(&sock->bound_addr)) + pr_info("bound to %B on `%s'", &sock->bound_addr, sock->addr->bindtodev); else - pr_info("bound to %B", &bound_addr); + pr_info("bound to %B", &sock->bound_addr); fastd_poll_fd_register(&sock->fd); } @@ -287,21 +291,12 @@ void fastd_socket_close(fastd_socket_t *sock) { sock->fd.fd = -1; } - - if (sock->bound_addr) { - free(sock->bound_addr); - sock->bound_addr = NULL; - } } /** Handles an error that occured on a socket */ void fastd_socket_error(fastd_socket_t *sock) { - fastd_peer_address_t bound_addr = *sock->bound_addr; - if (!sock->addr->addr.sa.sa_family) - bound_addr.sa.sa_family = AF_UNSPEC; - - if (sock->addr->bindtodev && !fastd_peer_address_is_v6_ll(&bound_addr)) - exit_error("error on socket bound to %B on `%s'", &sock->addr->addr, sock->addr->bindtodev); + if (sock->addr->bindtodev && !fastd_peer_address_is_v6_ll(&sock->bound_addr)) + exit_error("error on socket bound to %B on `%s'", &sock->bound_addr, sock->addr->bindtodev); else - exit_error("error on socket bound to %B", &sock->addr->addr); + exit_error("error on socket bound to %B", &sock->bound_addr); } From 4f4aeaf297a27f8d585b3ff05896b17edfe881c1 Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Thu, 5 Nov 2020 22:18:30 +0100 Subject: [PATCH 05/15] Implement socket task framework for periodic discovery events on sockets. 1) Move socket and peer address structures out to a separate header file which defines the corresponding structures. Add a task structure to the socket structure to represent the task pqueue element of the socket. 2) Implement a new task type (TASK_TYPE_SOCKET), which is dispatched to a corresponding function in socket.c for later implementing the actual muticast discovery packet send. This function currently just reschedules the task in the configured interval. 3) Initialize the socket task when creating the socket by checking whether the bind address has an interval set up (i.e., non-zero and not FASTD_TIMEOUT_INV), in which case the socket task is immediately started. When no interval set up (for non-multicast sockets) or for ephemeral sockets (for an individual peer), don't set up a socket task, as it is useless. 4) Make sure to remove the socket task from the task queue when a socket is closed. --- src/fastd.c | 2 +- src/fastd.h | 68 +-------------------------------------- src/socket.c | 18 ++++++++++- src/socket.h | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/task.c | 5 +++ src/types.h | 1 + 6 files changed, 115 insertions(+), 69 deletions(-) create mode 100644 src/socket.h diff --git a/src/fastd.c b/src/fastd.c index 0d62f9fa..c3ccf69d 100644 --- a/src/fastd.c +++ b/src/fastd.c @@ -139,7 +139,7 @@ static void init_sockets(void) { if (ctx.ioctl_sock < 0) exit_errno("unable to create ioctl socket"); - ctx.socks = fastd_new_array(conf.n_bind_addrs, fastd_socket_t); + ctx.socks = fastd_new0_array(conf.n_bind_addrs, fastd_socket_t); size_t i; fastd_bind_address_t *addr = conf.bind_addrs; diff --git a/src/fastd.h b/src/fastd.h index 2ca6c171..05b7f1ba 100644 --- a/src/fastd.h +++ b/src/fastd.h @@ -22,6 +22,7 @@ #include "polling.h" #include "sem.h" #include "shell.h" +#include "socket.h" #include "task.h" #include "util.h" #include "vector.h" @@ -127,13 +128,6 @@ struct fastd_protocol { bool (*describe_peer)(const fastd_peer_t *peer, char *buf, size_t len); }; -/** An union storing an IPv4 or IPv6 address */ -union fastd_peer_address { - struct sockaddr sa; /**< A sockaddr field (for access to sa_family) */ - struct sockaddr_in in; /**< An IPv4 address */ - struct sockaddr_in6 in6; /**< An IPv6 address */ -}; - #define FASTD_BIND_DEFAULT_IPV4 (1U << 1) #define FASTD_BIND_DEFAULT_IPV6 (1U << 2) #define FASTD_BIND_DYNAMIC (1U << 3) @@ -148,15 +142,6 @@ struct fastd_bind_address { fastd_timeout_t interval; /**< Discovery interval if discovery enabled, or FASTD_TIMEOUT_INV */ }; -/** A socket descriptor */ -struct fastd_socket { - fastd_poll_fd_t fd; /**< The file descriptor for the socket */ - const fastd_bind_address_t *addr; /**< The address this socket is supposed to be bound to (or NULL) */ - fastd_peer_address_t bound_addr; /**< The actual address that was bound to (may differ from addr when addr has - a random port) and also the source address to require for incoming packets */ - fastd_peer_t *peer; /**< If the socket belongs to a single peer (as it was create dynamically when sending a - handshake), contains that peer */ -}; /** A TUN/TAP interface */ struct fastd_iface { @@ -475,57 +460,6 @@ static inline fastd_eth_addr_t fastd_buffer_dest_address(const fastd_buffer_t *b return ret; } -/** Checks if a fastd_peer_address_t is the IPv4 any address */ -static inline bool fastd_peer_address_is_v4_any(const fastd_peer_address_t *addr) { - return addr->sa.sa_family == AF_INET && addr->in.sin_addr.s_addr == INADDR_ANY; -} - -/** Checks if a fastd_peer_address_t is an IPv4 multicast address */ -static inline bool fastd_peer_address_is_v4_multicast(const fastd_peer_address_t *addr) { - return addr->sa.sa_family == AF_INET && IN_MULTICAST(ntohl(addr->in.sin_addr.s_addr)); -} - -/** Checks if host parts of two IPv4 fastd_peer_address_t are equal */ -static inline bool fastd_peer_address_is_v4_host_equal(const fastd_peer_address_t *addr1, const fastd_peer_address_t *addr2) { - return addr1->sa.sa_family == AF_INET && addr2->sa.sa_family == AF_INET && addr1->in.sin_addr.s_addr == addr2->in.sin_addr.s_addr; -} - -/** Checks if a fastd_peer_address_t is the IPv6 any address */ -static inline bool fastd_peer_address_is_v6_any(const fastd_peer_address_t *addr) { - return addr->sa.sa_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&addr->in6.sin6_addr); -} - -/** Checks if a fastd_peer_address_t is an IPv6 link-local address */ -static inline bool fastd_peer_address_is_v6_ll(const fastd_peer_address_t *addr) { - return addr->sa.sa_family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&addr->in6.sin6_addr); -} - -/** Checks if a fastd_peer_address_t is an IPv6 multicast address */ -static inline bool fastd_peer_address_is_v6_multicast(const fastd_peer_address_t *addr) { - return addr->sa.sa_family == AF_INET6 && IN6_IS_ADDR_MULTICAST(&addr->in6.sin6_addr); -} - -/** Checks if host parts of two IPv6 fastd_peer_address_t are equal */ -static inline bool fastd_peer_address_is_v6_host_equal(const fastd_peer_address_t *addr1, const fastd_peer_address_t *addr2) { - return addr1->sa.sa_family == AF_INET6 && addr2->sa.sa_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&addr1->in6.sin6_addr, &addr2->in6.sin6_addr); -} - -/** Checks if the fastd_peer_address_t represents the IPv4 or IPv6 any address */ -static inline bool fastd_peer_address_is_any(const fastd_peer_address_t *addr) { - return fastd_peer_address_is_v4_any(addr) || fastd_peer_address_is_v6_any(addr); -} - -/** Checks if the fastd_peer_address_t represents an IPv4 or IPv6 multicast address */ -static inline bool fastd_peer_address_is_multicast(const fastd_peer_address_t *addr) { - return fastd_peer_address_is_v4_multicast(addr) || fastd_peer_address_is_v6_multicast(addr); -} - -/** Checks if host parts of two fastd_peer_address_t are equal */ -static inline bool fastd_peer_address_is_host_equal(const fastd_peer_address_t *addr1, const fastd_peer_address_t *addr2) { - return fastd_peer_address_is_v4_host_equal(addr1, addr2) || fastd_peer_address_is_v6_host_equal(addr1, addr2); -} - - /** Duplicates a string, creating a one-element string stack */ static inline fastd_string_stack_t *fastd_string_stack_dup(const char *str) { size_t str_len = strlen(str); diff --git a/src/socket.c b/src/socket.c index f336e441..23895754 100644 --- a/src/socket.c +++ b/src/socket.c @@ -13,6 +13,7 @@ #include "fastd.h" #include "peer.h" #include "polling.h" +#include "socket.h" #include @@ -244,6 +245,11 @@ void fastd_socket_bind_all(void) { pr_info("bound to %B", &sock->bound_addr); fastd_poll_fd_register(&sock->fd); + + if (sock->addr->interval && sock->addr->interval != FASTD_TIMEOUT_INV) { + fastd_task_schedule(&sock->task, TASK_TYPE_SOCKET, ctx.now); + pr_info("scheduled discovery task for socket bound to %B", &sock->bound_addr); + } } } @@ -270,7 +276,7 @@ fastd_socket_t *fastd_socket_open(fastd_peer_t *peer, int af) { if (fd < 0) return NULL; - fastd_socket_t *sock = fastd_new(fastd_socket_t); + fastd_socket_t *sock = fastd_new0(fastd_socket_t); sock->fd = FASTD_POLL_FD(POLL_TYPE_SOCKET, fd); sock->addr = NULL; @@ -291,6 +297,8 @@ void fastd_socket_close(fastd_socket_t *sock) { sock->fd.fd = -1; } + + fastd_task_unschedule(&sock->task); } /** Handles an error that occured on a socket */ @@ -300,3 +308,11 @@ void fastd_socket_error(fastd_socket_t *sock) { else exit_error("error on socket bound to %B", &sock->bound_addr); } + +/** Handles socket task periodically dispatched on a socket */ +void fastd_socket_handle_task(fastd_task_t *task) { + fastd_socket_t *sock = container_of(task, fastd_socket_t, task); + + pr_debug2("rescheduling socket task for socket bound to %B", &sock->bound_addr); + fastd_task_reschedule_relative(task, sock->addr->interval); +} diff --git a/src/socket.h b/src/socket.h new file mode 100644 index 00000000..0152bd30 --- /dev/null +++ b/src/socket.h @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + Copyright (c) 2020, Heiko Wundram + All rights reserved. +*/ + +/** + \file + + Socket structures. +*/ + +#pragma once + +#include "task.h" + +#include + + +/** An union storing an IPv4 or IPv6 address */ +union fastd_peer_address { + struct sockaddr sa; /**< A sockaddr field (for access to sa_family) */ + struct sockaddr_in in; /**< An IPv4 address */ + struct sockaddr_in6 in6; /**< An IPv6 address */ +}; + +/** A socket descriptor */ +struct fastd_socket { + fastd_poll_fd_t fd; /**< The file descriptor for the socket */ + const fastd_bind_address_t *addr; /**< The address this socket is supposed to be bound to (or NULL) */ + fastd_peer_address_t bound_addr; /**< The actual address that was bound to (may differ from addr when addr has + a random port) and also the source address to require for incoming packets */ + fastd_peer_t *peer; /**< If the socket belongs to a single peer (as it was create dynamically when sending a + handshake), contains that peer */ + fastd_task_t task; /**< Task handle for the socket task which is added and dispatched on multicast + sockets which have a discovery interval */ +}; + +/** Checks if a fastd_peer_address_t is the IPv4 any address */ +static inline bool fastd_peer_address_is_v4_any(const fastd_peer_address_t *addr) { + return addr->sa.sa_family == AF_INET && addr->in.sin_addr.s_addr == INADDR_ANY; +} + +/** Checks if a fastd_peer_address_t is an IPv4 multicast address */ +static inline bool fastd_peer_address_is_v4_multicast(const fastd_peer_address_t *addr) { + return addr->sa.sa_family == AF_INET && IN_MULTICAST(ntohl(addr->in.sin_addr.s_addr)); +} + +/** Checks if host parts of two IPv4 fastd_peer_address_t are equal */ +static inline bool fastd_peer_address_is_v4_host_equal(const fastd_peer_address_t *addr1, const fastd_peer_address_t *addr2) { + return addr1->sa.sa_family == AF_INET && addr2->sa.sa_family == AF_INET && addr1->in.sin_addr.s_addr == addr2->in.sin_addr.s_addr; +} + +/** Checks if a fastd_peer_address_t is the IPv6 any address */ +static inline bool fastd_peer_address_is_v6_any(const fastd_peer_address_t *addr) { + return addr->sa.sa_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&addr->in6.sin6_addr); +} + +/** Checks if a fastd_peer_address_t is an IPv6 link-local address */ +static inline bool fastd_peer_address_is_v6_ll(const fastd_peer_address_t *addr) { + return addr->sa.sa_family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&addr->in6.sin6_addr); +} + +/** Checks if a fastd_peer_address_t is an IPv6 multicast address */ +static inline bool fastd_peer_address_is_v6_multicast(const fastd_peer_address_t *addr) { + return addr->sa.sa_family == AF_INET6 && IN6_IS_ADDR_MULTICAST(&addr->in6.sin6_addr); +} + +/** Checks if host parts of two IPv6 fastd_peer_address_t are equal */ +static inline bool fastd_peer_address_is_v6_host_equal(const fastd_peer_address_t *addr1, const fastd_peer_address_t *addr2) { + return addr1->sa.sa_family == AF_INET6 && addr2->sa.sa_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&addr1->in6.sin6_addr, &addr2->in6.sin6_addr); +} + +/** Checks if the fastd_peer_address_t represents the IPv4 or IPv6 any address */ +static inline bool fastd_peer_address_is_any(const fastd_peer_address_t *addr) { + return fastd_peer_address_is_v4_any(addr) || fastd_peer_address_is_v6_any(addr); +} + +/** Checks if the fastd_peer_address_t represents an IPv4 or IPv6 multicast address */ +static inline bool fastd_peer_address_is_multicast(const fastd_peer_address_t *addr) { + return fastd_peer_address_is_v4_multicast(addr) || fastd_peer_address_is_v6_multicast(addr); +} + +/** Checks if host parts of two fastd_peer_address_t are equal */ +static inline bool fastd_peer_address_is_host_equal(const fastd_peer_address_t *addr1, const fastd_peer_address_t *addr2) { + return fastd_peer_address_is_v4_host_equal(addr1, addr2) || fastd_peer_address_is_v6_host_equal(addr1, addr2); +} + + +void fastd_socket_handle_task(fastd_task_t *task); diff --git a/src/task.c b/src/task.c index 85f88cb9..e7dd86a2 100644 --- a/src/task.c +++ b/src/task.c @@ -12,6 +12,7 @@ #include "task.h" #include "peer.h" +#include "socket.h" /** Performs periodic maintenance tasks */ @@ -34,6 +35,10 @@ static void handle_task(void) { fastd_peer_handle_task(task); break; + case TASK_TYPE_SOCKET: + fastd_socket_handle_task(task); + break; + default: exit_bug("unknown task type"); } diff --git a/src/types.h b/src/types.h index cf46da55..a774b5be 100644 --- a/src/types.h +++ b/src/types.h @@ -73,6 +73,7 @@ typedef enum fastd_task_type { TASK_TYPE_UNSPEC = 0, /**< Unspecified task type */ TASK_TYPE_MAINTENANCE, /**< Scheduled maintenance */ TASK_TYPE_PEER, /**< Peer maintenance (handshake, reset, keepalive) */ + TASK_TYPE_SOCKET, /**< Socket task for sending out discovery packets */ } fastd_task_type_t; From a8881fa4292646b391eba8682e59113b42e99053 Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Mon, 9 Nov 2020 11:17:38 +0100 Subject: [PATCH 06/15] Implement multicast packet support for the handshake 1) Don't send out error results for handshakes that have been received on a multicast local address. 2) Use the bound address (which is the source address by a previous patch) to reply to multicast packets, or otherwise use the default socket bind address for packets received on a multicast socket. 3) Don't signal a connect event for multicast discovery packets. 4) Handle multicast discovery packets only for type 1 handshake packets (the actual discovery) and ignore multicast discovery from peers where we already have an established connection. --- src/handshake.c | 5 +++++ src/protocols/ec25519_fhmqvc/handshake.c | 21 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/handshake.c b/src/handshake.c index e7663528..0ed90311 100644 --- a/src/handshake.c +++ b/src/handshake.c @@ -275,6 +275,11 @@ static void print_error( void fastd_handshake_send_error( fastd_socket_t *sock, const fastd_peer_address_t *local_addr, const fastd_peer_address_t *remote_addr, fastd_peer_t *peer, const fastd_handshake_t *handshake, uint8_t reply_code, uint16_t error_detail) { + if (local_addr && fastd_peer_address_is_multicast(local_addr)) { + pr_debug("ignoring handshake error from multicast packet on %I", local_addr); + return; + } + print_error("sending", peer, remote_addr, reply_code, error_detail); fastd_buffer_t *buffer = fastd_buffer_alloc( diff --git a/src/protocols/ec25519_fhmqvc/handshake.c b/src/protocols/ec25519_fhmqvc/handshake.c index ed324c5c..dbb8749c 100644 --- a/src/protocols/ec25519_fhmqvc/handshake.c +++ b/src/protocols/ec25519_fhmqvc/handshake.c @@ -290,6 +290,13 @@ static void respond_handshake( fastd_handshake_tlv_len(buffer)); memcpy(mac, hmacbuf.b, HASHBYTES); + if (local_addr && fastd_peer_address_is_multicast(local_addr)) { + if (sock->addr && sock->addr->sourceaddr.sa.sa_family != AF_UNSPEC) + local_addr = &sock->bound_addr; + else + local_addr = NULL; + } + fastd_send(sock, local_addr, remote_addr, peer, buffer, 0); } @@ -471,10 +478,10 @@ void fastd_protocol_ec25519_fhmqvc_handshake_init( fastd_handshake_add( buffer, RECORD_SENDER_HANDSHAKE_KEY, PUBLICKEYBYTES, &ctx.protocol_state->handshake_key.key.public); - if (!peer || !fastd_peer_is_established(peer)) { + if (!fastd_peer_address_is_multicast(remote_addr) && (!peer || !fastd_peer_is_established(peer))) { const fastd_shell_command_t *on_connect = fastd_peer_group_lookup_peer_shell_command(peer, on_connect); fastd_peer_exec_shell_command( - on_connect, peer, (local_addr && local_addr->sa.sa_family) ? local_addr : &sock->bound_addr, + on_connect, peer, (local_addr && local_addr->sa.sa_family != AF_UNSPEC) ? local_addr : &sock->bound_addr, remote_addr, false); } @@ -617,6 +624,11 @@ void fastd_protocol_ec25519_fhmqvc_handshake_handle( return; } + if (fastd_peer_address_is_multicast(local_addr) && handshake->type != 1) { + pr_warn("received uunknown multicast handshake type %u from %I", handshake->type, remote_addr); + return; + } + peer = match_sender_key(sock, remote_addr, peer, handshake->records[RECORD_SENDER_KEY].data); if (!peer) { switch (errno) { @@ -638,6 +650,11 @@ void fastd_protocol_ec25519_fhmqvc_handshake_handle( } } + if (fastd_peer_address_is_multicast(local_addr) && fastd_peer_is_established(peer)) { + pr_debug("ignoring multicast handshake discovery packet from peer %P[%I]", peer, remote_addr); + return; + } + if (!fastd_handshake_check_mtu(sock, local_addr, remote_addr, peer, handshake)) return; From c4053feec16fa1191ad92fac99dbf84b993096f5 Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Mon, 9 Nov 2020 11:32:57 +0100 Subject: [PATCH 07/15] Remove unnecessary copies of in*_pktinfo structures 1) Remove copying of the in(6)_pktinfo structure from the cmsg headers on receive path. The data contents of the fastd_peer_address_t are filled from the fields directly pointed to in the CMSG_DATA section. 2) Remove copying of the in(6)_pktinfo structure to the cmsg headers on the send path. The underlying buffer is initialized to 1024 bytes of zeros in the send function, so zeroing the buffer contents is not required and the fields can be immediately referenced in the corresponding pktinfo helper. 3) Resize buffers on send and receive for ancilliary messages; the ancilliary messages that are received are a lot smaller than the currently allocated 1kb, so that stack usage (and zeroing in the send path) can be restricted to smaller sizes. --- src/receive.c | 28 +++++++++------------------- src/send.c | 24 +++++++++++------------- 2 files changed, 20 insertions(+), 32 deletions(-) diff --git a/src/receive.c b/src/receive.c index e0e056e2..75019b7d 100644 --- a/src/receive.c +++ b/src/receive.c @@ -23,8 +23,6 @@ /** Handles the ancillary control messages of received packets */ static inline void handle_socket_control(struct msghdr *message, const fastd_socket_t *sock, fastd_peer_address_t *local_addr) { - memset(local_addr, 0, sizeof(fastd_peer_address_t)); - const uint8_t *end = (const uint8_t *)message->msg_control + message->msg_controllen; struct cmsghdr *cmsg; @@ -34,15 +32,12 @@ handle_socket_control(struct msghdr *message, const fastd_socket_t *sock, fastd_ #ifdef USE_PKTINFO if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { - struct in_pktinfo pktinfo; - - if ((const uint8_t *)CMSG_DATA(cmsg) + sizeof(pktinfo) > end) + const struct in_pktinfo *pktinfo = (const struct in_pktinfo *)CMSG_DATA(cmsg); + if ((const uint8_t *)CMSG_DATA(cmsg) + sizeof(*pktinfo) > end) return; - memcpy(&pktinfo, CMSG_DATA(cmsg), sizeof(pktinfo)); - local_addr->in.sin_family = AF_INET; - local_addr->in.sin_addr = pktinfo.ipi_addr; + local_addr->in.sin_addr = pktinfo->ipi_addr; local_addr->in.sin_port = fastd_peer_address_get_port(&sock->bound_addr); return; @@ -50,19 +45,14 @@ handle_socket_control(struct msghdr *message, const fastd_socket_t *sock, fastd_ #endif if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { - struct in6_pktinfo pktinfo; - - if ((uint8_t *)CMSG_DATA(cmsg) + sizeof(pktinfo) > end) + const struct in6_pktinfo *pktinfo = (const struct in6_pktinfo *)CMSG_DATA(cmsg); + if ((const uint8_t *)CMSG_DATA(cmsg) + sizeof(*pktinfo) > end) return; - memcpy(&pktinfo, CMSG_DATA(cmsg), sizeof(pktinfo)); - local_addr->in6.sin6_family = AF_INET6; - local_addr->in6.sin6_addr = pktinfo.ipi6_addr; + local_addr->in6.sin6_addr = pktinfo->ipi6_addr; local_addr->in6.sin6_port = fastd_peer_address_get_port(&sock->bound_addr); - - if (IN6_IS_ADDR_LINKLOCAL(&local_addr->in6.sin6_addr)) - local_addr->in6.sin6_scope_id = pktinfo.ipi6_ifindex; + local_addr->in6.sin6_scope_id = IN6_IS_ADDR_LINKLOCAL(&local_addr->in6.sin6_addr) ? pktinfo->ipi6_ifindex : 0; return; } @@ -239,10 +229,10 @@ static inline void handle_socket_receive( void fastd_receive(fastd_socket_t *sock) { size_t max_len = max_size_t(fastd_max_payload(ctx.max_mtu) + conf.overhead, MAX_HANDSHAKE_SIZE); fastd_buffer_t *buffer = fastd_buffer_alloc(max_len, conf.decrypt_headroom); - fastd_peer_address_t local_addr; + fastd_peer_address_t local_addr = {}; fastd_peer_address_t recvaddr; struct iovec buffer_vec = { .iov_base = buffer->data, .iov_len = buffer->len }; - uint8_t cbuf[1024] __attribute__((aligned(8))); + uint8_t cbuf[128] __attribute__((aligned(8))); struct msghdr message = { .msg_name = &recvaddr, diff --git a/src/send.c b/src/send.c index 5db289cf..adeb3176 100644 --- a/src/send.c +++ b/src/send.c @@ -35,33 +35,31 @@ static inline void add_pktinfo(struct msghdr *msg, const fastd_peer_address_t *l #ifdef USE_PKTINFO if (local_addr->sa.sa_family == AF_INET) { + struct in_pktinfo *pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); + cmsg->cmsg_level = IPPROTO_IP; cmsg->cmsg_type = IP_PKTINFO; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo)); msg->msg_controllen += cmsg->cmsg_len; - struct in_pktinfo pktinfo = {}; - pktinfo.ipi_spec_dst = local_addr->in.sin_addr; - memcpy(CMSG_DATA(cmsg), &pktinfo, sizeof(pktinfo)); + pktinfo->ipi_spec_dst = local_addr->in.sin_addr; + return; } #endif if (local_addr->sa.sa_family == AF_INET6) { + struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); + cmsg->cmsg_level = IPPROTO_IPV6; cmsg->cmsg_type = IPV6_PKTINFO; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo)); msg->msg_controllen += cmsg->cmsg_len; - struct in6_pktinfo pktinfo = {}; - pktinfo.ipi6_addr = local_addr->in6.sin6_addr; - - if (IN6_IS_ADDR_LINKLOCAL(&local_addr->in6.sin6_addr)) - pktinfo.ipi6_ifindex = local_addr->in6.sin6_scope_id; - - memcpy(CMSG_DATA(cmsg), &pktinfo, sizeof(pktinfo)); + pktinfo->ipi6_addr = local_addr->in6.sin6_addr; + pktinfo->ipi6_ifindex = IN6_IS_ADDR_LINKLOCAL(&local_addr->in6.sin6_addr) ? local_addr->in6.sin6_scope_id : 0; } } @@ -73,7 +71,7 @@ void fastd_send( exit_bug("send: sock == NULL"); struct msghdr msg = {}; - uint8_t cbuf[1024] __attribute__((aligned(8))) = {}; + uint8_t cbuf[128] __attribute__((aligned(8))) = {}; fastd_peer_address_t remote_addr6; switch (remote_addr->sa.sa_family) { From 7a6a4e5221dfc5b66b8f1dc35ada1fb65f8419b9 Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Mon, 9 Nov 2020 11:41:40 +0100 Subject: [PATCH 08/15] Remove send retry without control message structure 1) Retrying a send on a socket without the corresponding control message attached if required for choosing the local packet source works counter to having sourceaddr respected. In case sourceaddr is not set up, the local_addr in the send will have been NULLed or have sa_family as AF_UNSPEC, so that no ancilliary message is attached at all. --- src/send.c | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/send.c b/src/send.c index adeb3176..638e82e3 100644 --- a/src/send.c +++ b/src/send.c @@ -69,6 +69,8 @@ void fastd_send( fastd_peer_t *peer, fastd_buffer_t *buffer, size_t stat_size) { if (!sock) exit_bug("send: sock == NULL"); + if (local_addr && fastd_peer_address_is_multicast(local_addr)) + exit_bug("send: local address is multicast"); struct msghdr msg = {}; uint8_t cbuf[128] __attribute__((aligned(8))) = {}; @@ -109,25 +111,7 @@ void fastd_send( if (!msg.msg_controllen) msg.msg_control = NULL; - int ret = sendmsg(sock->fd.fd, &msg, 0); - - if (ret < 0 && msg.msg_controllen) { - switch (errno) { - case EINVAL: - case ENETUNREACH: - pr_debug2("sendmsg: %s (trying again without pktinfo)", strerror(errno)); - - if (peer && !fastd_peer_handshake_scheduled(peer)) - fastd_peer_schedule_handshake_default(peer); - - msg.msg_control = NULL; - msg.msg_controllen = 0; - - ret = sendmsg(sock->fd.fd, &msg, 0); - } - } - - if (ret < 0) { + if (sendmsg(sock->fd.fd, &msg, 0) < 0) { switch (errno) { case EAGAIN: #if EAGAIN != EWOULDBLOCK From c8b5f4bcffdb5baef31c4faeb63685547ea8a9ab Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Mon, 9 Nov 2020 12:14:03 +0100 Subject: [PATCH 09/15] Set up uniform exit for the handle_socket_receive_* functions 1) Socket receive functions which don't dispatch the buffer to a corresponding protocol handler need to free the data buffer on exit. This implements a common error: label on exit, which can be jumped to from any part of the function to ensure buffer cleanup. Additional tests which also drop the packet then simply need to jump to error to have proper handling. --- src/receive.c | 58 ++++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/receive.c b/src/receive.c index 75019b7d..a0a440b7 100644 --- a/src/receive.c +++ b/src/receive.c @@ -135,36 +135,35 @@ static bool backoff_unknown(const fastd_peer_address_t *addr) { static inline void handle_socket_receive_known( fastd_socket_t *sock, const fastd_peer_address_t *local_addr, const fastd_peer_address_t *remote_addr, fastd_peer_t *peer, fastd_buffer_t *buffer) { - if (!fastd_peer_may_connect(peer)) { - fastd_buffer_free(buffer); - return; - } + if (!fastd_peer_may_connect(peer)) + goto error; const uint8_t *packet_type = buffer->data; switch (*packet_type) { case PACKET_DATA: if (!fastd_peer_is_established(peer) || !fastd_peer_address_equal(&peer->local_address, local_addr)) { - fastd_buffer_free(buffer); - if (!backoff_unknown(remote_addr)) { pr_debug("unexpectedly received payload data from %P[%I]", peer, remote_addr); conf.protocol->handshake_init(sock, local_addr, remote_addr, NULL); } - return; + + goto error; } conf.protocol->handle_recv(peer, buffer); - break; + return; case PACKET_HANDSHAKE: fastd_handshake_handle(sock, local_addr, remote_addr, peer, buffer); - break; + return; default: - fastd_buffer_free(buffer); pr_debug("received packet with invalid type from %P[%I]", peer, remote_addr); } + +error: + fastd_buffer_free(buffer); } /** Determines if packets from known addresses are accepted */ @@ -180,22 +179,23 @@ static inline void handle_socket_receive_unknown( switch (*packet_type) { case PACKET_DATA: - fastd_buffer_free(buffer); - if (!backoff_unknown(remote_addr)) { pr_debug("unexpectedly received payload data from unknown address %I", remote_addr); conf.protocol->handshake_init(sock, local_addr, remote_addr, NULL); } - break; + + goto error; case PACKET_HANDSHAKE: fastd_handshake_handle(sock, local_addr, remote_addr, NULL, buffer); - break; + return; default: - fastd_buffer_free(buffer); pr_debug("received packet with invalid type from unknown address %I", remote_addr); } + +error: + fastd_buffer_free(buffer); } /** Handles a packet read from a socket */ @@ -205,11 +205,8 @@ static inline void handle_socket_receive( fastd_peer_t *peer = NULL; if (sock->peer) { - if (!fastd_peer_address_equal(&sock->peer->address, remote_addr)) { - fastd_buffer_free(buffer); - return; - } - + if (!fastd_peer_address_equal(&sock->peer->address, remote_addr)) + goto error; peer = sock->peer; } else { peer = fastd_peer_hashtable_lookup(remote_addr); @@ -217,12 +214,16 @@ static inline void handle_socket_receive( if (peer) { handle_socket_receive_known(sock, local_addr, remote_addr, peer, buffer); + return; } else if (allow_unknown_peers()) { handle_socket_receive_unknown(sock, local_addr, remote_addr, buffer); - } else { - pr_debug("received packet from unknown peer %I", remote_addr); - fastd_buffer_free(buffer); + return; } + + pr_debug("received packet from unknown peer %I", remote_addr); + +error: + fastd_buffer_free(buffer); } /** Reads a packet from a socket */ @@ -247,9 +248,7 @@ void fastd_receive(fastd_socket_t *sock) { if (len <= 0) { if (len < 0) pr_warn_errno("recvmsg"); - - fastd_buffer_free(buffer); - return; + goto error; } buffer->len = len; @@ -259,8 +258,7 @@ void fastd_receive(fastd_socket_t *sock) { #ifdef USE_PKTINFO if (!local_addr.sa.sa_family) { pr_error("received packet without packet info"); - fastd_buffer_free(buffer); - return; + goto error; } #endif @@ -268,6 +266,10 @@ void fastd_receive(fastd_socket_t *sock) { fastd_peer_address_simplify(&recvaddr); handle_socket_receive(sock, &local_addr, &recvaddr, buffer); + return; + +error: + fastd_buffer_free(buffer); } /** Handles a received and decrypted payload packet */ From 121b86f1363f6c2d86820661057c8b134d96dbcb Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Mon, 9 Nov 2020 15:14:03 +0100 Subject: [PATCH 10/15] Sanity check incoming packet addresses 1) Sanity check incoming packets for proper multicast packet type (only handshake possible) and for received address (when listening on the any address with an explicit source). 2) Sanity check PKTINFO data for IPv6 protocol sockets even if USE_PKTINFO is off, as IPV6_PKTINFO should always be on. 3) Don't simplify local address for the packet, due to the fact that sock->bound_addr is also not simplified but rather specified in the socket type address format. --- src/receive.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/receive.c b/src/receive.c index a0a440b7..dd7d81c2 100644 --- a/src/receive.c +++ b/src/receive.c @@ -142,6 +142,11 @@ static inline void handle_socket_receive_known( switch (*packet_type) { case PACKET_DATA: + if (fastd_peer_address_is_multicast(local_addr)) { + pr_warn("unexpected multicast data packet from peer %P[%I]", peer, remote_addr); + goto error; + } + if (!fastd_peer_is_established(peer) || !fastd_peer_address_equal(&peer->local_address, local_addr)) { if (!backoff_unknown(remote_addr)) { pr_debug("unexpectedly received payload data from %P[%I]", peer, remote_addr); @@ -179,6 +184,11 @@ static inline void handle_socket_receive_unknown( switch (*packet_type) { case PACKET_DATA: + if (fastd_peer_address_is_multicast(local_addr)) { + pr_warn("unexpected multicast data packet from address %I", remote_addr); + goto error; + } + if (!backoff_unknown(remote_addr)) { pr_debug("unexpectedly received payload data from unknown address %I", remote_addr); conf.protocol->handshake_init(sock, local_addr, remote_addr, NULL); @@ -204,6 +214,22 @@ static inline void handle_socket_receive( fastd_buffer_t *buffer) { fastd_peer_t *peer = NULL; + if (fastd_peer_address_is_multicast(local_addr)) { + if (!sock->addr || sock->addr->interval == FASTD_TIMEOUT_INV) { + pr_debug("received multicast packet from %I on non-discovery-enabled socket", remote_addr); + goto error; + } + + if (!fastd_peer_address_is_host_equal(&sock->addr->addr, local_addr)) { + pr_debug("received multicast packet on different multicat address %I", local_addr); + goto error; + } + } else if (sock->addr && local_addr->sa.sa_family != AF_UNSPEC && sock->addr->sourceaddr.sa.sa_family != AF_UNSPEC && + !fastd_peer_address_is_host_equal(&sock->bound_addr, local_addr)) { + pr_debug("received packet on non-source address %I, expected %I", local_addr, &sock->bound_addr); + goto error; + } + if (sock->peer) { if (!fastd_peer_address_equal(&sock->peer->address, remote_addr)) goto error; @@ -256,13 +282,14 @@ void fastd_receive(fastd_socket_t *sock) { handle_socket_control(&message, sock, &local_addr); #ifdef USE_PKTINFO - if (!local_addr.sa.sa_family) { + if (local_addr.sa.sa_family == AF_UNSPEC) { +#else + if (sock->bound_addr.sa.sa_family == AF_INET6 && local_addr.sa.sa_family == AF_UNSPEC) { +#endif pr_error("received packet without packet info"); goto error; } -#endif - fastd_peer_address_simplify(&local_addr); fastd_peer_address_simplify(&recvaddr); handle_socket_receive(sock, &local_addr, &recvaddr, buffer); From 89138834eda8d7efd439c87d4cf153cba4372145 Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Mon, 9 Nov 2020 15:33:38 +0100 Subject: [PATCH 11/15] Send out socket discovery 1) Send out socket discovery packets (handshake type 1 messages without peer binding) from the socket task in intervals specified by sock->addr->interval. --- src/socket.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/socket.c b/src/socket.c index 23895754..42463bcd 100644 --- a/src/socket.c +++ b/src/socket.c @@ -313,6 +313,12 @@ void fastd_socket_error(fastd_socket_t *sock) { void fastd_socket_handle_task(fastd_task_t *task) { fastd_socket_t *sock = container_of(task, fastd_socket_t, task); + pr_debug("dispatching discovery packet to multicast address %B", &sock->addr->addr); + if (sock->addr->sourceaddr.sa.sa_family != AF_UNSPEC) + conf.protocol->handshake_init(sock, &sock->bound_addr, &sock->addr->addr, NULL); + else + conf.protocol->handshake_init(sock, NULL, &sock->addr->addr, NULL); + pr_debug2("rescheduling socket task for socket bound to %B", &sock->bound_addr); fastd_task_reschedule_relative(task, sock->addr->interval); } From 9924f756a1c4a56a1fe8f9eb7c8c49b199dc0ee3 Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Tue, 10 Nov 2020 08:23:17 +0100 Subject: [PATCH 12/15] Test interface flags for resolved LL6 address if on Linux 1) The resolved LL6 address of an interface for automatic configuration of an interface may be tentative (privacy extensions, etc.) and/or have failed DAD. Make sure to evaluate the address flags at least on Linux where this is possible. --- src/build.h.in | 3 +++ src/config.c | 8 ++++++++ src/meson.build | 1 + 3 files changed, 12 insertions(+) diff --git a/src/build.h.in b/src/build.h.in index 0f8741e0..b3a948fe 100644 --- a/src/build.h.in +++ b/src/build.h.in @@ -52,6 +52,9 @@ /** Defined if the platform supports IP_PKTINFO */ #mesondefine USE_PKTINFO +/** Defined if the platform support IFA_F_* for address types */ +#mesondefine USE_IFAFLAGS + /** Defined if the platform supports SO_MARK */ #mesondefine USE_PACKET_MARK diff --git a/src/config.c b/src/config.c index 23e765da..f26d75a8 100644 --- a/src/config.c +++ b/src/config.c @@ -37,6 +37,10 @@ #include +#ifdef USE_IFAFLAGS +#include +#endif + /** The global configuration */ fastd_config_t conf = {}; @@ -138,6 +142,10 @@ static void normalize_address(fastd_peer_address_t *address, const fastd_peer_ad const fastd_peer_address_t *addr = (const fastd_peer_address_t *)ifa->ifa_addr; if (!fastd_peer_address_is_v6_ll(addr)) continue; +#ifdef USE_IFAFLAGS + if ((ifa->ifa_flags & IFA_F_DEPRECATED) || (ifa->ifa_flags & IFA_F_DADFAILED)) + continue; +#endif address->in6 = addr->in6; address->in6.sin6_port = config->in6.sin6_port; diff --git a/src/meson.build b/src/meson.build index 5d7dab4a..f225737e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -192,6 +192,7 @@ conf_data.set('HAVE_SETRESGID', conf_data.set('USE_BINDTODEVICE', is_android or is_linux) conf_data.set('USE_EPOLL', is_android or is_linux) +conf_data.set('USE_IFAFLAGS', is_linux) conf_data.set('USE_SELECT', is_darwin) conf_data.set('USE_FREEBIND', is_android or is_linux) conf_data.set('USE_FREEBIND6', cc.has_header_symbol('netinet/in.h', 'IPV6_FREEBIND')) From c39a8a11d1be3b07df0f8863458acb332f726908 Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Tue, 10 Nov 2020 08:24:57 +0100 Subject: [PATCH 13/15] Fix missing meson define for USE_FREEBIND6 1) Fix an oversight while implementing the SO_FREEBIND6 support in src/socket.c where a corresponding configure test was defined, but the configure test never made it to the set of feature macros. --- src/build.h.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/build.h.in b/src/build.h.in index b3a948fe..41e13b4b 100644 --- a/src/build.h.in +++ b/src/build.h.in @@ -46,6 +46,9 @@ /** Defined if the platform supports SO_FREEBIND */ #mesondefine USE_FREEBIND +/** Defined if the platform supports SO_FREEBIND6 */ +#mesondefine USE_FREEBIND6 + /** Defined if the platform supports IP_MTU_DISCOVER */ #mesondefine USE_PMTU From 66136b8f540cbc3ee4d37e9ffec714dff2cc9685 Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Tue, 10 Nov 2020 08:37:14 +0100 Subject: [PATCH 14/15] Set IPPROTO_IP socket options for IPv6 sockets using IPv4 mapped addresses 1) Partially revert a change that set up socket options only for IPPROTO_IPV6 on IPv6 connected sockets; some socket options apply to both protocols (independent of where they are set), some don't, so simply set all in case the socket is dual address family. --- src/socket.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/socket.c b/src/socket.c index 42463bcd..498face6 100644 --- a/src/socket.c +++ b/src/socket.c @@ -30,13 +30,13 @@ static int bind_socket(const fastd_bind_address_t *addr) { int af = AF_UNSPEC; fastd_peer_address_t bind_address = addr->addr; + const int v6only = bind_address.sa.sa_family == AF_INET6 || addr->sourceaddr.sa.sa_family == AF_INET6; if (!fastd_peer_address_is_v4_multicast(&bind_address)) { fd = socket(PF_INET6, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); if (fd >= 0) { af = AF_INET6; - const int val = bind_address.sa.sa_family == AF_INET6 || addr->sourceaddr.sa.sa_family == AF_INET6; - if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val))) { + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only))) { pr_warn_errno("setsockopt: unable to set socket for IPv6 only"); goto error; } @@ -75,22 +75,22 @@ static int bind_socket(const fastd_bind_address_t *addr) { #endif #ifdef USE_PKTINFO - if (af == AF_INET && setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one))) { + if ((af == AF_INET || !v6only) && setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one))) { pr_error_errno("setsockopt: unable to set IP_PKTINFO"); goto error; } #endif - if (af == AF_INET6 && setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one))) { + if (af != AF_INET && setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one))) { pr_error_errno("setsockopt: unable to set IPV6_RECVPKTINFO"); goto error; } #ifdef USE_FREEBIND #ifdef USE_FREEBIND6 - if (af == AF_INET && setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one))) + if ((af == AF_INET || !v6only) && setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one))) pr_warn_errno("setsockopt: unable to set IP_FREEBIND"); - if (af == AF_INET6 && setsockopt(fd, IPPROTO_IPV6, IPV6_FREEBIND, &one, sizeof(one))) + if (af != AF_INET && setsockopt(fd, IPPROTO_IPV6, IPV6_FREEBIND, &one, sizeof(one))) pr_warn_errno("setsockopt: unable to set IPV6_FREEBIND"); #else if (setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one))) @@ -108,11 +108,11 @@ static int bind_socket(const fastd_bind_address_t *addr) { #ifdef USE_PMTU const int pmtu = IP_PMTUDISC_DONT; - if (af == AF_INET && setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu))) { + if ((af == AF_INET || !v6only) && setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu))) { pr_error_errno("setsockopt: unable to disable PMTU discovery"); goto error; } - if (af == AF_INET6 && setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &pmtu, sizeof(pmtu))) { + if (af != AF_INET && setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &pmtu, sizeof(pmtu))) { pr_error_errno("setsockopt: unable to disable PMTU discovery"); goto error; } From 116a476f4038daa3a633f5fb3adf10f2d43bea3b Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Tue, 10 Nov 2020 08:53:27 +0100 Subject: [PATCH 15/15] Implement trigger network interface support for OpenWRT example init file 1) Due to the fact that multicasting requires interface binding (and possibly other binding configurations also use interface binding), make sure to include config trigger support for OpenWRT configuration file to initiate a start/reload/reconnect on interface change for specific interfaces. --- doc/examples/openwrt/fastd.init | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/doc/examples/openwrt/fastd.init b/doc/examples/openwrt/fastd.init index c95ab980..29362366 100644 --- a/doc/examples/openwrt/fastd.init +++ b/doc/examples/openwrt/fastd.init @@ -327,8 +327,8 @@ reload_instance() { update_peer_groups "$s" true - rc_procd start_service "$s" - procd_send_signal fastd "$s" + rc_procd start_service "$s" + procd_send_signal fastd "$s" } start_service() { @@ -402,3 +402,23 @@ generate_key() { generate_key_instance "$instance" } + +service_triggers_instance() { + local s="$1" + + section_enabled "$s" || return 0 + + config_get triggers "$s" triggers + if [ -n "$triggers" ] + then + for trigger in $triggers + do + procd_add_interface_trigger "interface.*" $trigger /etc/init.d/fastd restart "$s" + done + fi +} + +service_triggers() { + config_foreach service_triggers_instance 'fastd' + procd_add_reload_trigger 'fastd' +}