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] 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]); }