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.
This commit is contained in:
Heiko Wundram 2020-11-05 09:46:03 +01:00
parent 2ce6095b27
commit 0a939bbc36
8 changed files with 214 additions and 27 deletions

View file

@ -118,6 +118,13 @@
#define REORDER_TIME 10000 #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 */ /** The minimum time that must pass between two on-verify calls on the same peer */
#define MIN_VERIFY_INTERVAL 10000 /* 10 seconds */ #define MIN_VERIFY_INTERVAL 10000 /* 10 seconds */

View file

@ -26,6 +26,7 @@
#include "peer_group.h" #include "peer_group.h"
#include <dirent.h> #include <dirent.h>
#include <ifaddrs.h>
#include <grp.h> #include <grp.h>
#include <libgen.h> #include <libgen.h>
#include <pwd.h> #include <pwd.h>
@ -119,34 +120,123 @@ void fastd_config_mac(const char *name, const char *impl) {
impl, name, name); impl, name, name);
} }
/** Handles the configuration of a bind address */ /** Normalization of a bind address, initializing the target address structure */
void fastd_config_bind_address(const fastd_peer_address_t *address, const char *bindtodev, unsigned flags) { static void normalize_address(fastd_peer_address_t *address, const fastd_peer_address_t *config, const char *bindtodev) {
#ifndef USE_BINDTODEVICE if (config->sa.sa_family == AF_INET6 && config->in6.sin6_scope_id) {
if (bindtodev && !fastd_peer_address_is_v6_ll(address)) if (!bindtodev)
exit_error("config error: device bind configuration not supported on this system"); exit_error("config error: need interface to bind to resolve ll6 address");
#endif
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 #ifndef USE_MULTIAF_BIND
if (address->sa.sa_family == AF_UNSPEC) { 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 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_peer_address_t addr6 = { .in6 = { .sin6_family = AF_INET6, .sin6_port = address->in.sin_port } };
fastd_config_bind_address(&addr4, bindtodev, flags); if (sourceaddr->sa.sa_family != AF_INET6)
fastd_config_bind_address(&addr6, bindtodev, flags); 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; return;
} }
#endif #endif
fastd_bind_address_t *addr = fastd_new(fastd_bind_address_t); 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; addr->next = conf.bind_addrs;
conf.bind_addrs = addr; conf.bind_addrs = addr;
conf.n_bind_addrs++; conf.n_bind_addrs++;
addr->addr = *address;
addr->flags = flags; addr->flags = flags;
addr->bindtodev = fastd_strdup(bindtodev); addr->bindtodev = fastd_strdup(bindtodev);
addr->interval = interval;
fastd_peer_address_simplify(&addr->addr);
if (addr->addr.sa.sa_family != AF_INET6 && ((flags & FASTD_BIND_DEFAULT_IPV4) || !conf.bind_addr_default_v4)) if (addr->addr.sa.sa_family != AF_INET6 && ((flags & FASTD_BIND_DEFAULT_IPV4) || !conf.bind_addr_default_v4))
conf.bind_addr_default_v4 = addr; conf.bind_addr_default_v4 = addr;

View file

@ -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); bool fastd_config_ifname(fastd_peer_t *peer, const char *ifname);
void fastd_config_cipher(const char *name, const char *impl); void fastd_config_cipher(const char *name, const char *impl);
void fastd_config_mac(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_release(void);
void fastd_config_handle_options(int argc, char *const argv[]); void fastd_config_handle_options(int argc, char *const argv[]);
void fastd_config_verify(void); void fastd_config_verify(void);

View file

@ -74,12 +74,14 @@
%token TOK_INCLUDE %token TOK_INCLUDE
%token TOK_INFO %token TOK_INFO
%token TOK_INTERFACE %token TOK_INTERFACE
%token TOK_INTERVAL
%token TOK_IP %token TOK_IP
%token TOK_IPV4 %token TOK_IPV4
%token TOK_IPV6 %token TOK_IPV6
%token TOK_KEY %token TOK_KEY
%token TOK_LEVEL %token TOK_LEVEL
%token TOK_LIMIT %token TOK_LIMIT
%token TOK_LL6
%token TOK_LOG %token TOK_LOG
%token TOK_MAC %token TOK_MAC
%token TOK_MARK %token TOK_MARK
@ -102,6 +104,7 @@
%token TOK_SECRET %token TOK_SECRET
%token TOK_SECURE %token TOK_SECURE
%token TOK_SOCKET %token TOK_SOCKET
%token TOK_SOURCE
%token TOK_STATUS %token TOK_STATUS
%token TOK_STDERR %token TOK_STDERR
%token TOK_SYNC %token TOK_SYNC
@ -126,7 +129,8 @@
#include <limits.h> #include <limits.h>
static void fastd_config_handle_bind_address( 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); static void fastd_config_error(YYLTYPE *loc, fastd_parser_state_t *state, const char *s);
} }
@ -142,6 +146,8 @@
%type <uint64> maybe_af %type <uint64> maybe_af
%type <addr> bind_address %type <addr> bind_address
%type <str> maybe_bind_interface %type <str> maybe_bind_interface
%type <addr> maybe_source_address
%type <int64> maybe_interval
%type <uint64> maybe_bind_default %type <uint64> maybe_bind_default
%type <uint64> bind_default %type <uint64> bind_default
%type <uint64> drop_capabilities_enabled %type <uint64> drop_capabilities_enabled
@ -306,12 +312,12 @@ interface: TOK_STRING {
} }
; ;
bind: bind_address maybe_bind_port maybe_bind_interface maybe_bind_default { 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); 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_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 { | TOK_ADDR6 {
$$ = (fastd_peer_address_t){ .in6 = { .sin6_family = AF_INET6, .sin6_addr = $1 } }; $$ = (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 { | 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: maybe_bind_default:
TOK_DEFAULT bind_default { TOK_DEFAULT bind_default {
$$ = $2; $$ = $2;
@ -671,7 +704,8 @@ bind_port: colon_or_port TOK_UINT {
%% %%
static void fastd_config_handle_bind_address( 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; unsigned flags = bind_default;
uint16_t port = 0; uint16_t port = 0;
@ -686,7 +720,10 @@ static void fastd_config_handle_bind_address(
else else
address.in6.sin6_port = port; 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) { static void fastd_config_error(YYLTYPE *loc, fastd_parser_state_t *state, const char *s) {

View file

@ -142,8 +142,10 @@ union fastd_peer_address {
struct fastd_bind_address { struct fastd_bind_address {
fastd_bind_address_t *next; /**< The next address in the list */ 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 addr; /**< The address to bind to */
fastd_peer_address_t sourceaddr; /**< Source address to use for packets */
unsigned flags; /**< FASTD_BIND_* flags */ unsigned flags; /**< FASTD_BIND_* flags */
char *bindtodev; /**< May contain an interface name to limit the bind to */ 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 */ /** A socket descriptor */
@ -473,12 +475,57 @@ static inline fastd_eth_addr_t fastd_buffer_dest_address(const fastd_buffer_t *b
return ret; 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 */ /** 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) { 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 */ /** Duplicates a string, creating a one-element string stack */
static inline fastd_string_stack_t *fastd_string_stack_dup(const char *str) { static inline fastd_string_stack_t *fastd_string_stack_dup(const char *str) {
size_t str_len = strlen(str); size_t str_len = strlen(str);

View file

@ -71,12 +71,14 @@ static const keyword_t keywords[] = {
{ "include", TOK_INCLUDE }, { "include", TOK_INCLUDE },
{ "info", TOK_INFO }, { "info", TOK_INFO },
{ "interface", TOK_INTERFACE }, { "interface", TOK_INTERFACE },
{ "interval", TOK_INTERVAL },
{ "ip", TOK_IP }, { "ip", TOK_IP },
{ "ipv4", TOK_IPV4 }, { "ipv4", TOK_IPV4 },
{ "ipv6", TOK_IPV6 }, { "ipv6", TOK_IPV6 },
{ "key", TOK_KEY }, { "key", TOK_KEY },
{ "level", TOK_LEVEL }, { "level", TOK_LEVEL },
{ "limit", TOK_LIMIT }, { "limit", TOK_LIMIT },
{ "ll6", TOK_LL6 },
{ "log", TOK_LOG }, { "log", TOK_LOG },
{ "mac", TOK_MAC }, { "mac", TOK_MAC },
{ "mark", TOK_MARK }, { "mark", TOK_MARK },
@ -99,6 +101,7 @@ static const keyword_t keywords[] = {
{ "secret", TOK_SECRET }, { "secret", TOK_SECRET },
{ "secure", TOK_SECURE }, { "secure", TOK_SECURE },
{ "socket", TOK_SOCKET }, { "socket", TOK_SOCKET },
{ "source", TOK_SOURCE },
{ "status", TOK_STATUS }, { "status", TOK_STATUS },
{ "stderr", TOK_STDERR }, { "stderr", TOK_STDERR },
{ "sync", TOK_SYNC }, { "sync", TOK_SYNC },

View file

@ -287,7 +287,8 @@ static void option_bind(const char *arg) {
free(addrstr); 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 */ /** Handles the --protocol option */

View file

@ -199,7 +199,7 @@ void fastd_socket_bind_all(void) {
/** Opens a single socket bound to a random port for the given address family */ /** 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) { 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; const fastd_bind_address_t *bind_address;