From e6f6eb24b1945f3c271f0b49fd60b9944e85439d Mon Sep 17 00:00:00 2001 From: David Gibson Date: Tue, 2 Dec 2025 15:02:10 +1100 Subject: util: Fix setting of IPV6_V6ONLY socket option Currently we only call setsockopt() on IPV6_V6ONLY when we want to set it to 1, which we typically do on all IPv6 sockets except those explicitly for dual stack listening. That's not quite right in two ways: * Although IPV6_V6ONLY==0 is normally the default on Linux, that can be changed with the net.ipv6.bindv6only sysctl. It may also have different defaults on other OSes if we ever support them. We know we need it off for dual stack sockets, so explicitly set it to 0 in that case. * At the same time setting IPV6_V6ONLY to 1 for IPv6 sockets bound to a specific address is harmless but pointless. Don't set the option at all in this case, saving a syscall. Signed-off-by: David Gibson Signed-off-by: Stefano Brivio --- util.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/util.c b/util.c index 6cb1d38..491e7ff 100644 --- a/util.c +++ b/util.c @@ -45,13 +45,13 @@ * @type: epoll type * @sa: Socket address to bind to * @ifname: Interface for binding, NULL for any - * @v6only: Set IPV6_V6ONLY socket option + * @v6only: If >= 0, set IPV6_V6ONLY socket option to this value * * Return: newly created socket, negative error code on failure */ static int sock_l4_(const struct ctx *c, enum epoll_type type, const union sockaddr_inany *sa, const char *ifname, - bool v6only) + int v6only) { sa_family_t af = sa->sa_family; bool freebind = false; @@ -95,9 +95,13 @@ static int sock_l4_(const struct ctx *c, enum epoll_type type, return -EBADF; } - if (v6only) - if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &y, sizeof(y))) - debug("Failed to set IPV6_V6ONLY on socket %i", fd); + if (v6only >= 0) { + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, + &v6only, sizeof(v6only))) { + debug("Failed to set IPV6_V6ONLY to %d on socket %i", + v6only, fd); + } + } if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &y, sizeof(y))) debug("Failed to set SO_REUSEADDR on socket %i", fd); @@ -181,7 +185,16 @@ static int sock_l4_(const struct ctx *c, enum epoll_type type, int sock_l4(const struct ctx *c, enum epoll_type type, const union sockaddr_inany *sa, const char *ifname) { - return sock_l4_(c, type, sa, ifname, sa->sa_family == AF_INET6); + int v6only = -1; + + /* The option doesn't exist for IPv4 sockets, and we don't care about it + * for IPv6 sockets with a non-wildcard address. + */ + if (sa->sa_family == AF_INET6 && + IN6_IS_ADDR_UNSPECIFIED(&sa->sa6.sin6_addr)) + v6only = 1; + + return sock_l4_(c, type, sa, ifname, v6only); } /** @@ -204,6 +217,10 @@ int sock_l4_dualstack(const struct ctx *c, enum epoll_type type, .sa6.sin6_port = htons(port), }; + /* Dual stack sockets require IPV6_V6ONLY == 0. Usually that's the + * default, but sysctl net.ipv6.bindv6only can change that, so set the + * sockopt explicitly. + */ return sock_l4_(c, type, &sa, ifname, 0); } -- cgit v1.2.3