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.
This commit is contained in:
Heiko Wundram 2020-11-05 21:05:58 +01:00
parent 1933b6287f
commit 8a8ca9eb85

View file

@ -11,6 +11,7 @@
*/ */
#include "fastd.h" #include "fastd.h"
#include "peer.h"
#include "polling.h" #include "polling.h"
#include <net/if.h> #include <net/if.h>
@ -28,28 +29,45 @@ static int bind_socket(const fastd_bind_address_t *addr) {
int af = AF_UNSPEC; int af = AF_UNSPEC;
fastd_peer_address_t bind_address = addr->addr; 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); fd = socket(PF_INET6, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP);
if (fd >= 0) { if (fd >= 0) {
af = AF_INET6; 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))) { 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; 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); fd = socket(PF_INET, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP);
if (fd < 0) if (fd >= 0)
exit_errno("unable to create socket");
else
af = AF_INET; af = AF_INET;
} }
if (fd < 0) if (fd < 0) {
pr_error_errno("socket: unable to create socket");
goto error; 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 #ifdef NO_HAVE_SOCK_NONBLOCK
fastd_setnonblock(fd); fastd_setnonblock(fd);
@ -80,7 +98,7 @@ static int bind_socket(const fastd_bind_address_t *addr) {
#endif #endif
#ifdef USE_BINDTODEVICE #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))) { setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, addr->bindtodev, strlen(addr->bindtodev))) {
pr_warn_errno("setsockopt: unable to bind to device"); pr_warn_errno("setsockopt: unable to bind to device");
goto error; goto error;
@ -106,20 +124,58 @@ static int bind_socket(const fastd_bind_address_t *addr) {
} }
#endif #endif
if (bind_address.sa.sa_family == AF_UNSPEC) { if (bind(fd, &bind_address.sa, af == AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))) {
memset(&bind_address, 0, sizeof(bind_address)); pr_warn_errno("bind: unable to bind socket");
bind_address.sa.sa_family = af; goto error;
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, if (fastd_peer_address_is_v4_multicast(&addr->addr)) {
bind_address.sa.sa_family == AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))) { if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &zero, sizeof(zero))) {
pr_warn_errno("bind"); pr_error_errno("setsockopt: unable to disable IPv4 multicast loop");
goto error; 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__ #ifdef __ANDROID__
@ -132,18 +188,16 @@ static int bind_socket(const fastd_bind_address_t *addr) {
return fd; return fd;
error: error:
if (fd >= 0) { if (fd >= 0 && close(fd))
if (close(fd)) pr_error_errno("close");
pr_error_errno("close");
}
if (addr->bindtodev) if (addr->bindtodev)
pr_error( pr_error(
fastd_peer_address_is_v6_ll(&addr->addr) ? "unable to bind to %L" fastd_peer_address_is_v6_ll(&bind_address) ? "unable to bind to %L"
: "unable to bind to %B on `%s'", : "unable to bind to %B on `%s'",
&addr->addr, addr->bindtodev); &bind_address, addr->bindtodev);
else else
pr_error("unable to bind to %B", &addr->addr); pr_error("unable to bind to %B", &bind_address);
return -1; return -1;
} }