git: 8d98d889e1fc - stable/14 - libc/getaddrinfo(2): return EAI_AGAIN on nameserver timeout
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 19 Jun 2025 20:01:21 UTC
The branch stable/14 has been updated by glebius:
URL: https://cgit.FreeBSD.org/src/commit/?id=8d98d889e1fcdf38488b107b7191c38f3ea662d2
commit 8d98d889e1fcdf38488b107b7191c38f3ea662d2
Author: Gleb Smirnoff <glebius@FreeBSD.org>
AuthorDate: 2025-03-28 21:35:35 +0000
Commit: Gleb Smirnoff <glebius@FreeBSD.org>
CommitDate: 2025-06-19 20:00:19 +0000
libc/getaddrinfo(2): return EAI_AGAIN on nameserver timeout
A nameserver timeout is a soft failure, future attempts may succeed.
Returning EAI_AGAIN is crucial for API users to tell a soft name
resolution failure from negative resolution result.
Before the change we would return EAI_ADDRFAMILY, which I believe, is a
regression from 144361386696, and before that revision we used to return
EAI_NONAME in most of the cases.
Reviewed by: kib
Differential Revision: https://reviews.freebsd.org/D49411
(cherry picked from commit d803854bccb9ea527c1769ac403e011ff0e121e5)
---
lib/libc/net/getaddrinfo.c | 58 ++++++++++++++++++++++++++++++++++------------
1 file changed, 43 insertions(+), 15 deletions(-)
diff --git a/lib/libc/net/getaddrinfo.c b/lib/libc/net/getaddrinfo.c
index 2b9499d5099b..b8af23ebe8da 100644
--- a/lib/libc/net/getaddrinfo.c
+++ b/lib/libc/net/getaddrinfo.c
@@ -2341,9 +2341,14 @@ _dns_getaddrinfo(void *rv, void *cb_data, va_list ap)
if (res_searchN(hostname, &q, res) < 0) {
free(buf);
free(buf2);
- if (res->res_h_errno == NO_DATA)
+ switch (res->res_h_errno) {
+ case NO_DATA:
return (NS_ADDRFAMILY);
- return (NS_NOTFOUND);
+ case TRY_AGAIN:
+ return (NS_TRYAGAIN);
+ default:
+ return (NS_NOTFOUND);
+ }
}
/* prefer IPv6 */
if (q.next) {
@@ -2705,9 +2710,18 @@ res_queryN(const char *name, struct res_target *target, res_state res)
int n;
u_int oflags;
struct res_target *t;
- int rcode;
+ u_int rcode;
int ancount;
+ /*
+ * Extend rcode values in the scope of this function. The DNS header
+ * rcode we use in this function (hp->rcode) is limited by 4 bits, so
+ * anything starting from 16 is safe wrt aliasing. However, nameser.h
+ * already has extended enum __ns_rcode, so for future safety let's use
+ * even larger values.
+ */
+#define RCODE_UNREACH 32
+#define RCODE_TIMEDOUT 33
rcode = NOERROR;
ancount = 0;
@@ -2768,7 +2782,29 @@ again:
printf(";; res_nquery: retry without EDNS0\n");
goto again;
}
- rcode = hp->rcode; /* record most recent error */
+ /*
+ * Historically if a DNS server replied with ICMP port
+ * unreach res_nsend() would signal that with
+ * ECONNREFUSED and the upper layers would convert that
+ * into TRY_AGAIN. See 3a0b3b673936b and deeper.
+ * Also, res_nsend() may set errno to ECONNREFUSED due
+ * to internal failures. This may not be intentional,
+ * but we also treat that as soft failures.
+ *
+ * A more practical case is when a DNS server(s) were
+ * queried and didn't respond anything, which usually
+ * indicates a soft network failure.
+ */
+ switch (errno) {
+ case ECONNREFUSED:
+ rcode = RCODE_UNREACH;
+ break;
+ case ETIMEDOUT:
+ rcode = RCODE_TIMEDOUT;
+ break;
+ default:
+ rcode = hp->rcode;
+ }
#ifdef DEBUG
if (res->options & RES_DEBUG)
printf(";; res_query: send error\n");
@@ -2800,6 +2836,8 @@ again:
case NXDOMAIN:
RES_SET_H_ERRNO(res, HOST_NOT_FOUND);
break;
+ case RCODE_UNREACH:
+ case RCODE_TIMEDOUT:
case SERVFAIL:
RES_SET_H_ERRNO(res, TRY_AGAIN);
break;
@@ -2862,10 +2900,6 @@ res_searchN(const char *name, struct res_target *target, res_state res)
ret = res_querydomainN(name, NULL, target, res);
if (ret > 0 || trailing_dot)
return (ret);
- if (errno == ECONNREFUSED) {
- RES_SET_H_ERRNO(res, TRY_AGAIN);
- return (-1);
- }
switch (res->res_h_errno) {
case NO_DATA:
case HOST_NOT_FOUND:
@@ -2906,7 +2940,6 @@ res_searchN(const char *name, struct res_target *target, res_state res)
ret = res_querydomainN(name, *domain, target, res);
if (ret > 0)
return (ret);
-
/*
* If no server present, give up.
* If name isn't found in this domain,
@@ -2920,11 +2953,6 @@ res_searchN(const char *name, struct res_target *target, res_state res)
* but try the input name below in case it's
* fully-qualified.
*/
- if (errno == ECONNREFUSED) {
- RES_SET_H_ERRNO(res, TRY_AGAIN);
- return (-1);
- }
-
switch (res->res_h_errno) {
case NO_DATA:
got_nodata++;
@@ -2933,8 +2961,8 @@ res_searchN(const char *name, struct res_target *target, res_state res)
/* keep trying */
break;
case TRY_AGAIN:
- got_servfail++;
if (hp->rcode == SERVFAIL) {
+ got_servfail++;
/* try next search element, if any */
break;
}