From: Willy Tarreau Date: Fri, 9 May 2025 13:23:10 +0000 (+0200) Subject: MEDIUM: sock-inet: re-check IPv6 connectivity every 30s X-Git-Tag: v3.2-dev16~48 X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=8a96216847757b053a435818b2e64b69703bad84;p=thirdparty%2Fhaproxy.git MEDIUM: sock-inet: re-check IPv6 connectivity every 30s IPv6 connectivity might start off (e.g. network not fully up when haproxy starts), so for features like resolvers, it would be nice to periodically recheck. With this change, instead of having the resolvers code rely on a variable indicating connectivity, it will now call a function that will check for how long a connectivity check hasn't been run, and will perform a new one if needed. The age was set to 30s which seems reasonable considering that the DNS will cache results anyway. There's no saving in spacing it more since the syscall is very check (just a connect() without any packet being emitted). The variables remain exported so that we could present them in show info or anywhere else. This way, "dns-accept-family auto" will now stay up to date. Warning though, it does perform some caching so even with a refreshed IPv6 connectivity, an older record may be returned anyway. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 3acf5c15e..89cededa0 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -2193,6 +2193,7 @@ dns-accept-family [,...] - "ipv4": query and accept IPv4 addresses ("A" records) - "ipv6": query and accept IPv6 addresses ("AAAA" records) - "auto": use IPv4, and IPv6 if the system has a default gateway for it. + The result of the last check is cached for 30 seconds. When a single family is used, no request will be sent to resolvers for the other family, and any response for the othe family will be ignored. The diff --git a/include/haproxy/defaults.h b/include/haproxy/defaults.h index c18b2f079..7eba11ef6 100644 --- a/include/haproxy/defaults.h +++ b/include/haproxy/defaults.h @@ -349,6 +349,11 @@ #define SRV_CHK_INTER_THRES 1000 #endif +/* INET6 connectivity caching interval (in ms) */ +#ifndef INET6_CONNECTIVITY_CACHE_TIME +#define INET6_CONNECTIVITY_CACHE_TIME 30000 +#endif + /* Specifies the string used to report the version and release date on the * statistics page. May be defined to the empty string ("") to permanently * disable the feature. diff --git a/include/haproxy/sock_inet.h b/include/haproxy/sock_inet.h index 1d4a8b7a0..ad756558d 100644 --- a/include/haproxy/sock_inet.h +++ b/include/haproxy/sock_inet.h @@ -31,6 +31,7 @@ extern int sock_inet6_v6only_default; extern int sock_inet_tcp_maxseg_default; extern int sock_inet6_tcp_maxseg_default; extern int sock_inet6_seems_reachable; +extern uint last_inet6_check; #ifdef HA_HAVE_MPTCP extern int sock_inet_mptcp_maxseg_default; @@ -54,5 +55,6 @@ int sock_inet_is_foreign(int fd, sa_family_t family); int sock_inet4_make_foreign(int fd); int sock_inet6_make_foreign(int fd); int sock_inet_bind_receiver(struct receiver *rx, char **errmsg); +int is_inet6_reachable(void); #endif /* _HAPROXY_SOCK_INET_H */ diff --git a/src/resolvers.c b/src/resolvers.c index f8378a18b..d52303381 100644 --- a/src/resolvers.c +++ b/src/resolvers.c @@ -225,7 +225,7 @@ static inline int resolv_active_families(void) { if (resolv_accept_families & RSLV_AUTO_FAMILY) { /* Let's adjust our default resolver families based on apparent IPv6 connectivity */ - if (sock_inet6_seems_reachable) + if (is_inet6_reachable()) return RSLV_ACCEPT_IPV4 | RSLV_ACCEPT_IPV6; else return RSLV_ACCEPT_IPV4; diff --git a/src/sock_inet.c b/src/sock_inet.c index cf60c5a1c..abe99b360 100644 --- a/src/sock_inet.c +++ b/src/sock_inet.c @@ -81,6 +81,7 @@ int sock_inet6_tcp_maxseg_default = -1; /* indicate whether v6 looks reachable (this is only a hint) */ int sock_inet6_seems_reachable = 0; +uint last_inet6_check = TICK_ETERNITY; /* Default MPTCPv4/MPTCPv6 MSS settings. -1=unknown. */ #ifdef HA_HAVE_MPTCP @@ -472,6 +473,45 @@ int sock_inet_bind_receiver(struct receiver *rx, char **errmsg) goto bind_return; } + +/* Detects IPv6 reachability: for this we perform a UDP connect to address + * 2001:: on port 53. No packet will be sent, it will just check the routing + * table towards this prefix for the majority of public addresses. In case of + * error we assume no IPv6 connectivity. + * + * Returns non-zero if inet6 looks reachable, otherwise zero. This considers + * the last result if it ages less than 30s, otherwise triggers a new test + * which updates and . + */ +int is_inet6_reachable(void) +{ + uint last_check = HA_ATOMIC_LOAD(&last_inet6_check); + struct sockaddr_in6 dest = { }; + int ret = 0; + int fd; + + if (tick_isset(last_check) && + !tick_is_expired(tick_add(last_check, INET6_CONNECTIVITY_CACHE_TIME), HA_ATOMIC_LOAD(&global_now_ms))) + return HA_ATOMIC_LOAD(&sock_inet6_seems_reachable); + + /* update the test date to ensure nobody else does it in parallel */ + HA_ATOMIC_STORE(&last_inet6_check, HA_ATOMIC_LOAD(&global_now_ms)); + + fd = socket(AF_INET6, SOCK_DGRAM, 0); + if (fd >= 0) { + dest.sin6_family = AF_INET6; + dest.sin6_addr.s6_addr[0] = 0x20; + dest.sin6_addr.s6_addr[1] = 0x01; + dest.sin6_port = htons(53); + if (connect(fd, (struct sockaddr*)&dest, sizeof(dest)) == 0) + ret = 1; + close(fd); + } + + HA_ATOMIC_STORE(&sock_inet6_seems_reachable, ret); + return ret; +} + static void sock_inet_prepare() { int fd, val; @@ -529,24 +569,6 @@ static void sock_inet_prepare() close(fd); } #endif - - /* detect IPv6 reachability: for this we perform a UDP connect to - * address 2001:: on port 53. No packet will be sent, it will just - * check the routing table towards this prefix for the majority of - * public addresses. In case of error we assume no IPv6 connectivity. - */ - fd = socket(AF_INET6, SOCK_DGRAM, 0); - if (fd >= 0) { - struct sockaddr_in6 dest = { }; - - dest.sin6_family = AF_INET6; - dest.sin6_addr.s6_addr[0] = 0x20; - dest.sin6_addr.s6_addr[1] = 0x01; - dest.sin6_port = htons(53); - if (connect(fd, (struct sockaddr*)&dest, sizeof(dest)) == 0) - sock_inet6_seems_reachable = 1; - close(fd); - } } INITCALL0(STG_PREPARE, sock_inet_prepare);