From a12076cc5288b6e509c69fd9f4d57fdc9d1f90c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Witold=20Kr=C4=99cicki?= Date: Tue, 21 Jul 2020 13:29:14 +0200 Subject: [PATCH 1/2] netmgr: retry binding with IP_FREEBIND when EADDRNOTAVAIL is returned. When a new IPv6 interface/address appears it's first in a tentative state - in which we cannot bind to it, yet it's already being reported by the route socket. Because of that BIND9 is unable to listen on any newly detected IPv6 addresses. Fix it by setting IP_FREEBIND option (or equivalent option on other OSes) and then retrying bind() call. (cherry picked from commit a0f7d28967f95c904614bbe44efde789e430a80e) --- lib/isc/netmgr/netmgr-int.h | 6 +++++ lib/isc/netmgr/netmgr.c | 51 +++++++++++++++++++++++++++++++++++++ lib/isc/netmgr/tcp.c | 13 ++++++++++ lib/isc/netmgr/udp.c | 14 ++++++++++ 4 files changed, 84 insertions(+) diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h index cb3da7fc71..5876bba328 100644 --- a/lib/isc/netmgr/netmgr-int.h +++ b/lib/isc/netmgr/netmgr-int.h @@ -793,3 +793,9 @@ isc__nm_decstats(isc_nm_t *mgr, isc_statscounter_t counterid); /*%< * Decrement socket-related statistics counters. */ + +isc_result_t +isc__nm_socket_freebind(const uv_handle_t *handle); +/*%< + * Set the IP_FREEBIND (or equivalent) socket option on the uv_handle + */ diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c index 8ac42822c2..a40af49ba6 100644 --- a/lib/isc/netmgr/netmgr.c +++ b/lib/isc/netmgr/netmgr.c @@ -1474,3 +1474,54 @@ isc__nm_decstats(isc_nm_t *mgr, isc_statscounter_t counterid) { isc_stats_decrement(mgr->stats, counterid); } } + +#define setsockopt_on(socket, level, name) \ + setsockopt(socket, level, name, &(int){ 1 }, sizeof(int)) + +isc_result_t +isc__nm_socket_freebind(const uv_handle_t *handle) { + /* + * Set the IP_FREEBIND (or equivalent option) on the uv_handle. + */ + isc_result_t result = ISC_R_SUCCESS; + uv_os_fd_t fd; + if (uv_fileno(handle, &fd) != 0) { + return (ISC_R_FAILURE); + } +#ifdef IP_FREEBIND + if (setsockopt_on(fd, IPPROTO_IP, IP_FREEBIND) == -1) { + return (ISC_R_FAILURE); + } +#elif defined(IP_BINDANY) || defined(IPV6_BINDANY) + struct sockaddr_in sockfd; + + if (getsockname(fd, (struct sockaddr *)&sockfd, + &(socklen_t){ sizeof(sockfd) }) == -1) + { + return (ISC_R_FAILURE); + } +#if defined(IP_BINDANY) + if (sockfd.sin_family == AF_INET) { + if (setsockopt_on(fd, IPPROTO_IP, IP_BINDANY) == -1) { + return (ISC_R_FAILURE); + } + } +#endif +#if defined(IPV6_BINDANY) + if (sockfd.sin_family == AF_INET6) { + if (setsockopt_on(fd, IPPROTO_IPV6, IPV6_BINDANY) == -1) { + return (ISC_R_FAILURE); + } + } +#endif +#elif defined(SO_BINDANY) + if (setsockopt_on(fd, SOL_SOCKET, SO_BINDANY) == -1) { + return (ISC_R_FAILURE); + } +#else + UNUSED(handle); + UNUSED(fd); + result = ISC_R_NOTIMPLEMENTED; +#endif + return (result); +} diff --git a/lib/isc/netmgr/tcp.c b/lib/isc/netmgr/tcp.c index c572777662..7e9644e4ff 100644 --- a/lib/isc/netmgr/tcp.c +++ b/lib/isc/netmgr/tcp.c @@ -249,6 +249,19 @@ isc__nm_async_tcplisten(isc__networker_t *worker, isc__netievent_t *ev0) { r = uv_tcp_bind(&sock->uv_handle.tcp, &sock->iface->addr.type.sa, flags); + if (r == UV_EADDRNOTAVAIL && + isc__nm_socket_freebind(&sock->uv_handle.handle) == ISC_R_SUCCESS) + { + /* + * Retry binding with IP_FREEBIND (or equivalent option) if the + * address is not available. This helps with IPv6 tentative + * addresses which are reported by the route socket, although + * named is not yet able to properly bind to them. + */ + r = uv_tcp_bind(&sock->uv_handle.tcp, + &sock->iface->addr.type.sa, flags); + } + if (r != 0) { isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]); uv_close(&sock->uv_handle.handle, tcp_close_cb); diff --git a/lib/isc/netmgr/udp.c b/lib/isc/netmgr/udp.c index 6e2d2098cf..0d130453ff 100644 --- a/lib/isc/netmgr/udp.c +++ b/lib/isc/netmgr/udp.c @@ -167,6 +167,20 @@ isc__nm_async_udplisten(isc__networker_t *worker, isc__netievent_t *ev0) { r = uv_udp_bind(&sock->uv_handle.udp, &sock->parent->iface->addr.type.sa, uv_bind_flags); + if (r == UV_EADDRNOTAVAIL && + isc__nm_socket_freebind(&sock->uv_handle.handle) == ISC_R_SUCCESS) + { + /* + * Retry binding with IP_FREEBIND (or equivalent option) if the + * address is not available. This helps with IPv6 tentative + * addresses which are reported by the route socket, although + * named is not yet able to properly bind to them. + */ + r = uv_udp_bind(&sock->uv_handle.udp, + &sock->parent->iface->addr.type.sa, + uv_bind_flags); + } + if (r < 0) { isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]); } From 95fb38619b988a59ed9f90d74705277155be07ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Witold=20Kr=C4=99cicki?= Date: Tue, 21 Jul 2020 14:56:45 +0200 Subject: [PATCH 2/2] Add CHANGES and release note for GL #2038 (cherry picked from commit 94eda43ab28383c59df21280d6c06fc84260cc4f) --- CHANGES | 5 +++++ doc/notes/notes-current.rst | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGES b/CHANGES index 335d9e5461..b49104509f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +5481. [bug] BIND 9 would fail to bind to IPv6 addresses in a + tentative state when a new IPv6 address was added to the + system, but the Duplicate Address Detection (DAD) + mechanism had not yet finished. [GL #2038] + 5477. [bug] The idle timeout for connected TCP sockets is now derived from the client query processing timeout configured for a resolver. [GL #2024] diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index ee6e8ca7ef..d59f0794d4 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -42,3 +42,12 @@ Bug Fixes - The introduction of KASP support broke whether the second field of sig-validity-interval was treated as days or hours. (Thanks to Tony Finch.) [GL !3735] + +- The IPv6 Duplicate Address Detection (DAD) mechanism could cause the operating + system to report the new IPv6 addresses to the applications via the + getifaddrs() API in a tentative (DAD not yet finished) or duplicate (DAD + failed) state. Such addresses cannot be bound by an application, and named + failed to listen on IPv6 addresses after the DAD mechanism finished. It is + possible to work around the issue by setting the IP_FREEBIND option on the + socket and trying to bind() to the IPv6 address again if the first bind() call + fails with EADDRNOTAVAIL. [GL #2038]