From a0f7d28967f95c904614bbe44efde789e430a80e 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. --- 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 277601d5ab..b5593a7f59 100644 --- a/lib/isc/netmgr/netmgr-int.h +++ b/lib/isc/netmgr/netmgr-int.h @@ -821,3 +821,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 0cdf5b3e9c..30260afb29 100644 --- a/lib/isc/netmgr/netmgr.c +++ b/lib/isc/netmgr/netmgr.c @@ -1519,3 +1519,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 430616355b..558b685caf 100644 --- a/lib/isc/netmgr/tcp.c +++ b/lib/isc/netmgr/tcp.c @@ -340,6 +340,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 0e782f5cde..544a967ae9 100644 --- a/lib/isc/netmgr/udp.c +++ b/lib/isc/netmgr/udp.c @@ -168,6 +168,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 94eda43ab28383c59df21280d6c06fc84260cc4f 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 --- CHANGES | 5 ++++- doc/notes/notes-current.rst | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 7bfcbc1ab3..05f3252f0d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,7 @@ -5481. [placeholder] +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] 5480. [placeholder] diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index f315ffe670..0480bcbf6b 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -60,3 +60,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]