From d58e05d599e91bf2d7597da217208b2ef4c92fc1 Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Mon, 23 Mar 2026 16:53:51 +0100 Subject: [PATCH] Avoid duplicate DNS query if search list contains '.' Resolves: RHEL-153056 --- glibc-RHEL-153056-1.patch | 249 ++++++++++++++++++++++++++++++++++++ glibc-RHEL-153056-2.patch | 258 ++++++++++++++++++++++++++++++++++++++ glibc-RHEL-153056-3.patch | 98 +++++++++++++++ 3 files changed, 605 insertions(+) create mode 100644 glibc-RHEL-153056-1.patch create mode 100644 glibc-RHEL-153056-2.patch create mode 100644 glibc-RHEL-153056-3.patch diff --git a/glibc-RHEL-153056-1.patch b/glibc-RHEL-153056-1.patch new file mode 100644 index 0000000..bf93cf7 --- /dev/null +++ b/glibc-RHEL-153056-1.patch @@ -0,0 +1,249 @@ +commit 8ca2fe7e96c0ccf04d32d7002d7a6d9edcb9f8ee +Author: Sergey Kolosov +Date: Fri Oct 10 17:15:27 2025 +0200 + + resolv: Add tests for getaddrinfo returning EAI_AGAIN [BZ #16849] + + This patch adds two tests that verify correct behavior of getaddrinfo + when DNS resolution fails with a temporary error. Both tests ensure + that getaddrinfo returns EAI_AGAIN in cases where no valid address can + be resolved due to network or resolver failure. + + * tst-getaddrinfo-eai-again.c + Runs inside the glibc test-container without any DNS server + configured. The test performs queries using AF_INET, AF_INET6, + and AF_UNSPEC and verifies that getaddrinfo returns EAI_AGAIN + when resolution fails. + + * tst-getaddrinfo-eai-again-timeout.c + Runs outside of the container but uses the resolv_test framework + to simulate network failures. The test covers two failure modes: + - No response from the server (resolv_response_drop) + - Zero-length reply from the server + In both cases, getaddrinfo is expected to return EAI_AGAIN. + + Reviewed-by: Florian Weimer + +diff --git a/resolv/Makefile b/resolv/Makefile +index f3f5c260d0b7471f..cb1ff182dfdd4572 100644 +--- a/resolv/Makefile ++++ b/resolv/Makefile +@@ -80,7 +80,10 @@ routines_no_fortify += \ + # routines_no_fortify + + tests = tst-aton tst-leaks tst-inet_ntop +-tests-container = tst-leaks2 ++tests-container += \ ++ tst-getaddrinfo-eai-again \ ++ tst-leaks2 \ ++ # tests-container + + tests-internal += tst-inet_aton_exact + +@@ -136,6 +139,7 @@ tests-static += tst-ns_rr_cursor + # These tests need libdl. + ifeq (yes,$(build-shared)) + tests += \ ++ tst-getaddrinfo-eai-again-timeout \ + tst-resolv-ai_idn \ + tst-resolv-ai_idn-latin1 \ + tst-resolv-ai_idn-nolibidn2 \ +@@ -268,6 +272,8 @@ $(objpfx)mtrace-tst-resolv-res_ninit.out: $(objpfx)tst-resolv-res_ninit.out + + $(objpfx)tst-bug18665-tcp: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-bug18665: $(objpfx)libresolv.so $(shared-thread-library) ++$(objpfx)tst-getaddrinfo-eai-again-timeout: \ ++ $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-resolv-ai_idn: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-resolv-ai_idn-latin1: \ + $(objpfx)libresolv.so $(shared-thread-library) +diff --git a/resolv/tst-getaddrinfo-eai-again-timeout.c b/resolv/tst-getaddrinfo-eai-again-timeout.c +new file mode 100644 +index 0000000000000000..ec4a6563b7b5ae51 +--- /dev/null ++++ b/resolv/tst-getaddrinfo-eai-again-timeout.c +@@ -0,0 +1,122 @@ ++/* Test for BZ #16849. Verify that getaddrinfo correctly returns ++ EAI_AGAIN when DNS resolution fails due to timeout or malformed ++ responses. ++ ++ This test uses two simulated failure modes: ++ - The DNS server does not respond at all (resolv_response_drop). ++ - The DNS server responds with a zero-length packet. ++ ++ Copyright (C) 2025 Free Software Foundation, Inc. ++ This file is part of the GNU C Library. ++ ++ The GNU C Library is free software; you can redistribute it and/or ++ modify it under the terms of the GNU Lesser General Public ++ License as published by the Free Software Foundation; either ++ version 2.1 of the License, or (at your option) any later version. ++ ++ The GNU C Library is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ Lesser General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public ++ License along with the GNU C Library; if not, see ++ . */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++/* Track whether the callbacks were actually invoked. */ ++static volatile bool response_called_drop = false; ++static volatile bool response_called_zero_len = false; ++ ++/* Simulate a DNS server that sends a zero-length response. */ ++static void ++response_zero_len (const struct resolv_response_context *ctx, ++ struct resolv_response_builder *b, ++ const char *qname, uint16_t qclass, uint16_t qtype) ++{ ++ response_called_zero_len = true; ++ /* Do nothing — zero-length reply. */ ++} ++ ++/* Simulate a DNS server that drops the request. */ ++static void ++response_drop (const struct resolv_response_context *ctx, ++ struct resolv_response_builder *b, ++ const char *qname, uint16_t qclass, uint16_t qtype) ++{ ++ response_called_drop = true; ++ resolv_response_drop (b); ++} ++ ++/* Query getaddrinfo for multiple families and expect EAI_AGAIN. */ ++static void ++query_host (const char *host_name) ++{ ++ int family[] = { AF_INET, AF_INET6, AF_UNSPEC }; ++ const char *family_names[] = { "AF_INET", "AF_INET6", "AF_UNSPEC" }; ++ ++ for (int i = 0; i < 3; i++) ++ { ++ struct addrinfo hints = ++ { ++ .ai_socktype = 0, ++ .ai_protocol = 0, ++ .ai_family = family[i], ++ .ai_flags = 0, ++ }; ++ struct addrinfo *result; ++ int res = getaddrinfo (host_name, NULL, &hints, &result); ++ if (res != EAI_AGAIN) ++ FAIL_EXIT1 ("getaddrinfo (%s, %s) returned %s, expected EAI_AGAIN", ++ host_name, family_names[i], gai_strerror (res)); ++ } ++} ++ ++/* Simulate DNS server dropping all queries. */ ++static void ++test_drop (void) ++{ ++ struct resolv_test *aux = resolv_test_start ++ ((struct resolv_redirect_config) ++ { ++ .response_callback = response_drop, ++ }); ++ /* Reduce default timeout to make the test run faster. */ ++ _res.retrans = 1; ++ _res.retry = 1; ++ query_host ("site.example"); ++ resolv_test_end (aux); ++} ++ ++/* Simulate DNS server sending zero-length responses. */ ++static void ++test_zero_len_packet (void) ++{ ++ struct resolv_test *aux = resolv_test_start ++ ((struct resolv_redirect_config) ++ { ++ .response_callback = response_zero_len, ++ }); ++ query_host ("site.example"); ++ resolv_test_end (aux); ++} ++ ++static int ++do_test (void) ++{ ++ test_drop (); ++ test_zero_len_packet (); ++ ++ if (!response_called_drop) ++ FAIL_EXIT1 ("response_drop callback was not called"); ++ if (!response_called_zero_len) ++ FAIL_EXIT1 ("response_zero_len callback was not called"); ++ return 0; ++} ++ ++#include +diff --git a/resolv/tst-getaddrinfo-eai-again.c b/resolv/tst-getaddrinfo-eai-again.c +new file mode 100644 +index 0000000000000000..21daa6c1682d1156 +--- /dev/null ++++ b/resolv/tst-getaddrinfo-eai-again.c +@@ -0,0 +1,56 @@ ++/* Test for BZ #16849. Verify that getaddrinfo correctly returns ++ EAI_AGAIN error code if DNS query fails due to a network failure. ++ ++ Copyright (C) 2025 Free Software Foundation, Inc. ++ This file is part of the GNU C Library. ++ ++ The GNU C Library is free software; you can redistribute it and/or ++ modify it under the terms of the GNU Lesser General Public ++ License as published by the Free Software Foundation; either ++ version 2.1 of the License, or (at your option) any later version. ++ ++ The GNU C Library is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ Lesser General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public ++ License along with the GNU C Library; if not, see ++ . */ ++ ++#include ++#include ++ ++/* Query getaddrinfo with various address families and verify that ++ it returns EAI_AGAIN when DNS resolution fails. */ ++static void ++query_host (const char *host_name) ++{ ++ int family[] = { AF_INET, AF_INET6, AF_UNSPEC }; ++ const char *family_names[] = { "AF_INET", "AF_INET6", "AF_UNSPEC" }; ++ ++ for (int i = 0; i < 3; i++) ++ { ++ struct addrinfo hints = ++ { ++ .ai_socktype = 0, ++ .ai_protocol = 0, ++ .ai_family = family[i], ++ .ai_flags = 0, ++ }; ++ struct addrinfo *result; ++ int res = getaddrinfo (host_name, NULL, &hints, &result); ++ if (res != EAI_AGAIN) ++ FAIL_EXIT1 ("getaddrinfo (%s, %s) returned %s, expected EAI_AGAIN", ++ host_name, family_names[i], gai_strerror (res)); ++ } ++} ++ ++static int ++do_test (void) ++{ ++ query_host ("site.example"); ++ return 0; ++} ++ ++#include diff --git a/glibc-RHEL-153056-2.patch b/glibc-RHEL-153056-2.patch new file mode 100644 index 0000000..df43536 --- /dev/null +++ b/glibc-RHEL-153056-2.patch @@ -0,0 +1,258 @@ +commit c995686e2cbe2a3ab2a11877a61c14a2e1fc35cb +Author: Florian Weimer +Date: Tue Mar 3 18:48:47 2026 +0100 + + support: no_override_resolv_conf_search flag for resolver test framework + + It is required to test "search ." in /etc/resolv.conf files. The + default is to override the search path isolate from unexpected + settings in the test execution environment. + + Reviewed-by: Carlos O'Donell + +Conflicts: + resolv/Makefile + (tst-resolv-invalid-cname dependencies were added + in a different place on the 2.34 upstream branch) + +diff --git a/resolv/Makefile b/resolv/Makefile +index cb1ff182dfdd4572..116ab6a1574eb1ff 100644 +--- a/resolv/Makefile ++++ b/resolv/Makefile +@@ -83,6 +83,7 @@ tests = tst-aton tst-leaks tst-inet_ntop + tests-container += \ + tst-getaddrinfo-eai-again \ + tst-leaks2 \ ++ tst-resolv-no-search \ + # tests-container + + tests-internal += tst-inet_aton_exact +@@ -294,10 +295,11 @@ $(objpfx)tst-resolv-res_init-multi: $(objpfx)libresolv.so \ + $(shared-thread-library) + $(objpfx)tst-resolv-res_init-thread: $(objpfx)libresolv.so \ + $(shared-thread-library) +-$(objpfx)tst-resolv-noaaaa: $(objpfx)libresolv.so $(shared-thread-library) +-$(objpfx)tst-resolv-noaaaa-vc: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-resolv-invalid-cname: $(objpfx)libresolv.so \ + $(shared-thread-library) ++$(objpfx)tst-resolv-no-search: $(objpfx)libresolv.so $(shared-thread-library) ++$(objpfx)tst-resolv-noaaaa: $(objpfx)libresolv.so $(shared-thread-library) ++$(objpfx)tst-resolv-noaaaa-vc: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-resolv-nondecimal: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-resolv-rotate: $(objpfx)libresolv.so $(shared-thread-library) +diff --git a/resolv/tst-resolv-no-search.c b/resolv/tst-resolv-no-search.c +new file mode 100644 +index 0000000000000000..29701d4772507507 +--- /dev/null ++++ b/resolv/tst-resolv-no-search.c +@@ -0,0 +1,174 @@ ++/* Test using "search ." in /etc/resolv.conf. ++ Copyright (C) 2026 Free Software Foundation, Inc. ++ This file is part of the GNU C Library. ++ ++ The GNU C Library is free software; you can redistribute it and/or ++ modify it under the terms of the GNU Lesser General Public ++ License as published by the Free Software Foundation; either ++ version 2.1 of the License, or (at your option) any later version. ++ ++ The GNU C Library is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ Lesser General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public ++ License along with the GNU C Library; if not, see ++ . */ ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Check that plain res_init loads the configuration as expected. */ ++static void ++test_res_init (void *ignored) ++{ ++ res_init (); ++ TEST_COMPARE_STRING (_res.dnsrch[0], "."); ++ TEST_COMPARE_STRING (_res.dnsrch[1], NULL); ++} ++ ++static void ++response (const struct resolv_response_context *ctx, ++ struct resolv_response_builder *b, ++ const char *qname, uint16_t qclass, uint16_t qtype) ++{ ++ TEST_VERIFY_EXIT (qclass == C_IN); ++ TEST_COMPARE (ctx->server_index, 0); ++ ++ if (strncmp (qname, "does-not-exist", strlen ("does-not-exist")) == 0) ++ { ++ resolv_response_init (b, (struct resolv_response_flags) ++ { .rcode = ns_r_nxdomain }); ++ resolv_response_add_question (b, qname, qclass, qtype); ++ return; ++ } ++ ++ resolv_response_init (b, (struct resolv_response_flags) { }); ++ resolv_response_add_question (b, qname, qclass, qtype); ++ resolv_response_section (b, ns_s_an); ++ ++ resolv_response_open_record (b, qname, qclass, qtype, 0); ++ switch (qtype) ++ { ++ case T_A: ++ { ++ char ipv4[4] = {192, 0, 2, 17}; ++ resolv_response_add_data (b, &ipv4, sizeof (ipv4)); ++ } ++ break; ++ case T_AAAA: ++ { ++ char ipv6[16] ++ = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; ++ resolv_response_add_data (b, &ipv6, sizeof (ipv6)); ++ } ++ break; ++ } ++ resolv_response_close_record (b); ++} ++ ++static void ++check_h (const char *name, int family, const char *expected) ++{ ++ if (family == AF_INET) ++ { ++ char *query = xasprintf ("gethostbyname (\"%s\")", name); ++ check_hostent (query, gethostbyname (name), expected); ++ free (query); ++ } ++ { ++ char *query = xasprintf ("gethostbyname2 (\"%s\", %d)", name, family); ++ check_hostent (query, gethostbyname2 (name, family), expected); ++ free (query); ++ } ++} ++ ++static void ++check_ai (const char *name, int family, const char *expected) ++{ ++ struct addrinfo hints = { .ai_family = family, .ai_socktype = SOCK_STREAM, }; ++ struct addrinfo *ai; ++ char *query = xasprintf ("%s:80 [%d]", name, hints.ai_family); ++ int ret = getaddrinfo (name, "80", &hints, &ai); ++ check_addrinfo (query, ai, ret, expected); ++ if (ret == 0) ++ freeaddrinfo (ai); ++ free (query); ++} ++ ++static int ++do_test (void) ++{ ++ support_isolate_in_subprocess (test_res_init, NULL); ++ ++ struct resolv_test *aux = resolv_test_start ++ ((struct resolv_redirect_config) ++ { ++ .response_callback = response, ++ .no_override_resolv_conf_search = true, ++ }); ++ ++ check_h ("www.example", AF_INET, ++ "name: www.example\n" ++ "address: 192.0.2.17\n"); ++ check_h ("www.example", AF_INET6, ++ "name: www.example\n" ++ "address: 2001:db8::1\n"); ++ check_ai ("www.example", AF_UNSPEC, ++ "address: STREAM/TCP 192.0.2.17 80\n" ++ "address: STREAM/TCP 2001:db8::1 80\n"); ++ check_ai ("www.example", AF_INET, ++ "address: STREAM/TCP 192.0.2.17 80\n"); ++ check_ai ("www.example", AF_INET6, ++ "address: STREAM/TCP 2001:db8::1 80\n"); ++ check_h ("does-not-exist.example", AF_INET, ++ "error: HOST_NOT_FOUND\n"); ++ check_h ("does-not-exist.example", AF_INET6, ++ "error: HOST_NOT_FOUND\n"); ++ check_ai ("does-not-exist.example", AF_UNSPEC, ++ "error: Name or service not known\n"); ++ check_ai ("does-not-exist.example", AF_INET, ++ "error: Name or service not known\n"); ++ check_ai ("does-not-exist.example", AF_INET6, ++ "error: Name or service not known\n"); ++ ++ /* With trailing dot. */ ++ check_h ("www.example.", AF_INET, ++ "name: www.example\n" ++ "address: 192.0.2.17\n"); ++ check_h ("www.example.", AF_INET6, ++ "name: www.example\n" ++ "address: 2001:db8::1\n"); ++ check_ai ("www.example.", AF_UNSPEC, ++ "address: STREAM/TCP 192.0.2.17 80\n" ++ "address: STREAM/TCP 2001:db8::1 80\n"); ++ check_ai ("www.example.", AF_INET, ++ "address: STREAM/TCP 192.0.2.17 80\n"); ++ check_ai ("www.example.", AF_INET6, ++ "address: STREAM/TCP 2001:db8::1 80\n"); ++ check_h ("does-not-exist.example.", AF_INET, ++ "error: HOST_NOT_FOUND\n"); ++ check_h ("does-not-exist.example.", AF_INET6, ++ "error: HOST_NOT_FOUND\n"); ++ check_ai ("does-not-exist.example.", AF_UNSPEC, ++ "error: Name or service not known\n"); ++ check_ai ("does-not-exist.example.", AF_INET, ++ "error: Name or service not known\n"); ++ check_ai ("does-not-exist.example.", AF_INET6, ++ "error: Name or service not known\n"); ++ ++ resolv_test_end (aux); ++ ++ return 0; ++} ++ ++#include +diff --git a/resolv/tst-resolv-no-search.root/etc/resolv.conf b/resolv/tst-resolv-no-search.root/etc/resolv.conf +new file mode 100644 +index 0000000000000000..5ace648869a31edb +--- /dev/null ++++ b/resolv/tst-resolv-no-search.root/etc/resolv.conf +@@ -0,0 +1 @@ ++search . +diff --git a/support/resolv_test.c b/support/resolv_test.c +index 73d20572f3b843c7..7272334b78fc85f1 100644 +--- a/support/resolv_test.c ++++ b/support/resolv_test.c +@@ -1098,6 +1098,9 @@ resolv_test_init (void) + static void + set_search_path (struct resolv_redirect_config config) + { ++ if (config.no_override_resolv_conf_search) ++ return; ++ + memset (_res.defdname, 0, sizeof (_res.defdname)); + memset (_res.dnsrch, 0, sizeof (_res.dnsrch)); + +diff --git a/support/resolv_test.h b/support/resolv_test.h +index ddf967449e398710..e0a17ebf23c34880 100644 +--- a/support/resolv_test.h ++++ b/support/resolv_test.h +@@ -96,6 +96,9 @@ struct resolv_redirect_config + domain name as well. */ + const char *search[7]; + ++ /* If true, do not override the search path loaded from /etc/resolv.conf. */ ++ bool no_override_resolv_conf_search; ++ + /* Number of servers to activate in resolv. 0 means the default, + resolv_max_test_servers. */ + int nscount; diff --git a/glibc-RHEL-153056-3.patch b/glibc-RHEL-153056-3.patch new file mode 100644 index 0000000..ed5efd8 --- /dev/null +++ b/glibc-RHEL-153056-3.patch @@ -0,0 +1,98 @@ +commit dc9ca785a5fe2059a9b04ab336520d463d9a715b +Author: Carlos Peón Costa +Date: Tue Mar 3 18:48:47 2026 +0100 + + resolv: Avoid duplicate query if search list contains '.' (bug 33804) + + Co-authored-by: Florian Weimer + Signed-off-by: Florian Weimer + Reviewed-by: Carlos O'Donell + +diff --git a/resolv/res_query.c b/resolv/res_query.c +index 1d2c81737bc889c9..bae7b3c23d778a7d 100644 +--- a/resolv/res_query.c ++++ b/resolv/res_query.c +@@ -358,7 +358,7 @@ __res_context_search (struct resolv_context *ctx, + char tmp[NS_MAXDNAME]; + u_int dots; + int trailing_dot, ret, saved_herrno; +- int got_nodata = 0, got_servfail = 0, root_on_list = 0; ++ int got_nodata = 0, got_servfail = 0; + int tried_as_is = 0; + int searched = 0; + +@@ -437,8 +437,11 @@ __res_context_search (struct resolv_context *ctx, + domain. */ + if (dname[0] == '.') + dname++; +- if (dname[0] == '\0') +- root_on_list++; ++ if (dname[0] == '\0') { ++ if (tried_as_is) ++ continue; ++ tried_as_is++; ++ } + + ret = __res_context_querydomain + (ctx, name, dname, class, type, +@@ -510,7 +513,7 @@ __res_context_search (struct resolv_context *ctx, + * unless RES_NOTLDQUERY is set and there were no dots. + */ + if ((dots || !searched || (statp->options & RES_NOTLDQUERY) == 0) +- && !(tried_as_is || root_on_list)) { ++ && !tried_as_is) { + ret = __res_context_querydomain + (ctx, name, NULL, class, type, + answer, anslen, answerp, answerp2, nanswerp2, +diff --git a/resolv/tst-resolv-no-search.c b/resolv/tst-resolv-no-search.c +index 29701d4772507507..7d78d4044cf50e48 100644 +--- a/resolv/tst-resolv-no-search.c ++++ b/resolv/tst-resolv-no-search.c +@@ -27,6 +27,11 @@ + #include + #include + ++/* Used to check for duplicated queries (bug 33804). POSIX does not ++ explicitly say that socket calls (as used in the resolver tests) ++ provide synchronization. */ ++static _Atomic unsigned int query_count; ++ + /* Check that plain res_init loads the configuration as expected. */ + static void + test_res_init (void *ignored) +@@ -43,6 +48,7 @@ response (const struct resolv_response_context *ctx, + { + TEST_VERIFY_EXIT (qclass == C_IN); + TEST_COMPARE (ctx->server_index, 0); ++ ++query_count; + + if (strncmp (qname, "does-not-exist", strlen ("does-not-exist")) == 0) + { +@@ -82,12 +88,16 @@ check_h (const char *name, int family, const char *expected) + if (family == AF_INET) + { + char *query = xasprintf ("gethostbyname (\"%s\")", name); ++ query_count = 0; + check_hostent (query, gethostbyname (name), expected); ++ TEST_COMPARE (query_count, 1); + free (query); + } + { + char *query = xasprintf ("gethostbyname2 (\"%s\", %d)", name, family); ++ query_count = 0; + check_hostent (query, gethostbyname2 (name, family), expected); ++ TEST_COMPARE (query_count, 1); + free (query); + } + } +@@ -98,8 +108,10 @@ check_ai (const char *name, int family, const char *expected) + struct addrinfo hints = { .ai_family = family, .ai_socktype = SOCK_STREAM, }; + struct addrinfo *ai; + char *query = xasprintf ("%s:80 [%d]", name, hints.ai_family); ++ query_count = 0; + int ret = getaddrinfo (name, "80", &hints, &ai); + check_addrinfo (query, ai, ret, expected); ++ TEST_COMPARE (query_count, family == AF_UNSPEC ? 2 : 1); + if (ret == 0) + freeaddrinfo (ai); + free (query);