From patchwork Wed Apr 29 17:00:48 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?UGV0ciBNZW7FocOtaw==?= X-Patchwork-Id: 134165 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id A9BF04BBC0E1 for ; Wed, 29 Apr 2026 17:02:20 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org A9BF04BBC0E1 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Vwq6gKzD X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id B28B14B99F6A for ; Wed, 29 Apr 2026 17:01:43 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org B28B14B99F6A Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org B28B14B99F6A Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1777482103; cv=none; b=k+1Ndy+DpKfExD2dyPGI2icfssiToGEYNHgfkTgGdoTueCRVDqarS4TsAniOl/V9JGQTNoe2nZBUz8MRlzJwJJzKbG3+j1/Yi9+mH5lMHsw8w/EeVs+BDhs+esttb2v2jJ009aqdvFqzyfA0IvLm8UykSSpN+PPAt729WKhNPrQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1777482103; c=relaxed/simple; bh=EoKe8t1cSvH1kddFayS8oIzWU/V8tAUlMlYpKmc1a60=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=RfewVTib++sJ/2lTiLP21ZgyYArI8l+ZKkamF3yy376EujE6wXGRUQT79MdElgQXbbeDqOuxVRk1Tql2UpDXHfhQZgvXs3ZE4djhrWKCkKaPivURRth4IQ9ZIrghHoLvRxx3jm6aNvqxDusuIG5gPHwOsvenXSbE6+UAUSBlDyE= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org B28B14B99F6A DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1777482103; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=8rfA1XjOizkFQW0Lk0leTkdM0UiDBeeyI2gR5MgAasA=; b=Vwq6gKzDzmK6ZU59MW41wzLH1ynM9ZMitaP3gZ7+L4Ac+e/7EAUGRm/dYUoDVMlbWwceNF 3N15l4rP/99+QIkj5+j2+Pwa3+67uH+bAaegsuv+gdXFIAbEljgpRin4JWjk/gMZYgQaxK ThT5bEOZ/w0XWHAIikwpBj/L2sHQNTw= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-633-9Du4TpWRMmW3rq8E7aqLFQ-1; Wed, 29 Apr 2026 13:01:41 -0400 X-MC-Unique: 9Du4TpWRMmW3rq8E7aqLFQ-1 X-Mimecast-MFC-AGG-ID: 9Du4TpWRMmW3rq8E7aqLFQ_1777482100 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 9751A18005B6 for ; Wed, 29 Apr 2026 17:01:40 +0000 (UTC) Received: from pemensik-tp1g4 (pemensik-tp1g4.brq.redhat.com [10.43.2.231]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id C1CEF1800345; Wed, 29 Apr 2026 17:01:39 +0000 (UTC) From: =?utf-8?b?UGV0ciBNZW7FocOtaw==?= To: libc-alpha@sourceware.org Cc: =?utf-8?b?UGV0ciBNZW7FocOtaw==?= Subject: [PATCH v7 1/2] resolv: implement ipv4+ipv6 flags in resolv.conf (bug 30544) Date: Wed, 29 Apr 2026 19:00:48 +0200 Message-ID: <20260429170104.2378474-1-pemensik@redhat.com> In-Reply-To: <20251020160017.1574993-4-pemensik@redhat.com> References: <20251020160017.1574993-4-pemensik@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: fep4sIyILL48sJuZSOjkGBto_vFlrcHkT6_8RctSbzQ_1777482100 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.0 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_PASS, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org Add separate flags to mark support of address family. Its intention is to react dynamically to changing network conditions. If the host has IPv4 connectivity only, it would set ipv4 option. If it has also IPv6 connectivity, it would set ipv6 option. If it connects IPv6-only network, then it set ipv6 only. For backward compatibility it will keep old behaviour when both ipv4 and ipv6 flags are missing. In this case it behaves the same way as if both were present. This is quite useful when default AF_UNSPEC is used in hints, but the host is on network with just single AF used. Unlike no-aaaa option it would change just AF_UNSPEC behaviour, ie. getent ahosts example.com. getent ahostsv4 and getent ahostsv6 would remain unmodified. Signed-off-by: Petr Menšík --- resolv/nss_dns/dns-host.c | 50 +++++++++++++++++---------- resolv/res_debug.c | 2 ++ resolv/res_init.c | 2 ++ resolv/resolv.h | 2 ++ resolv/tst-resolv-res_init-skeleton.c | 2 ++ 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c index 728dae615d..16e45ce48d 100644 --- a/resolv/nss_dns/dns-host.c +++ b/resolv/nss_dns/dns-host.c @@ -379,6 +379,7 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat, { enum nss_status status = check_name (name, herrnop); char tmp[NS_MAXDNAME]; + if (status != NSS_STATUS_SUCCESS) return status; struct resolv_context *ctx = __resolv_context_get (); @@ -412,26 +413,39 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat, int olderr = errno; int n; + unsigned long res_options = ctx->resp->options & (RES_NOAAAA|RES_IPV4|RES_IPV6); + int qtype; - if ((ctx->resp->options & RES_NOAAAA) == 0) + switch (res_options) { - n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA, - dns_packet_buffer, sizeof (dns_packet_buffer), - &alt_dns_packet_buffer, &ans2p, &nans2p, - &resplen2, &ans2p_malloced); - if (n >= 0) - status = gaih_getanswer (alt_dns_packet_buffer, n, ans2p, resplen2, - &abuf, pat, errnop, herrnop, ttlp); - } - else - { - n = __res_context_search (ctx, name, C_IN, T_A, - dns_packet_buffer, sizeof (dns_packet_buffer), - &alt_dns_packet_buffer, NULL, NULL, NULL, NULL); - if (n >= 0) - status = gaih_getanswer_noaaaa (alt_dns_packet_buffer, n, - &abuf, pat, errnop, herrnop, ttlp); - } + case RES_IPV4: + case RES_IPV4|RES_NOAAAA: + case RES_NOAAAA: + case RES_IPV6|RES_NOAAAA: /*< this combination should never be used. */ + case RES_IPV4|RES_IPV6|RES_NOAAAA: /*< this does not make sense. */ + case RES_IPV6: /*< oh we want AAAA, but not A here. code is the same. */ + qtype = (res_options == RES_IPV6 ? T_AAAA : T_A); + n = __res_context_search (ctx, name, C_IN, qtype, + dns_packet_buffer, sizeof (dns_packet_buffer), + &alt_dns_packet_buffer, NULL, NULL, NULL, NULL); + if (n >= 0) + status = gaih_getanswer_noaaaa (alt_dns_packet_buffer, n, + &abuf, pat, errnop, herrnop, ttlp); + break; + + case 0: + case RES_IPV4|RES_IPV6: + default: + n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA, + dns_packet_buffer, sizeof (dns_packet_buffer), + &alt_dns_packet_buffer, &ans2p, &nans2p, + &resplen2, &ans2p_malloced); + if (n >= 0) + status = gaih_getanswer (alt_dns_packet_buffer, n, ans2p, resplen2, + &abuf, pat, errnop, herrnop, ttlp); + break; + } + if (n < 0) { switch (errno) diff --git a/resolv/res_debug.c b/resolv/res_debug.c index 73af0c72fe..f73bccd7bd 100644 --- a/resolv/res_debug.c +++ b/resolv/res_debug.c @@ -613,6 +613,8 @@ p_option(u_long option) { case RES_NORELOAD: return "no-reload"; case RES_TRUSTAD: return "trust-ad"; case RES_NOAAAA: return "no-aaaa"; + case RES_IPV4: return "ipv4"; + case RES_IPV6: return "ipv6"; /* XXX nonreentrant */ default: sprintf(nbuf, "?0x%lx?", (u_long)option); return (nbuf); diff --git a/resolv/res_init.c b/resolv/res_init.c index 683ae9e9b2..d5f0311372 100644 --- a/resolv/res_init.c +++ b/resolv/res_init.c @@ -696,6 +696,8 @@ res_setoptions (struct resolv_conf_parser *parser, const char *options) { STRnLEN ("trust-ad"), RES_TRUSTAD }, { STRnLEN ("no-aaaa"), RES_NOAAAA }, { STRnLEN ("strict-error"), RES_STRICTERR }, + { STRnLEN ("ipv4"), RES_IPV4 }, + { STRnLEN ("ipv6"), RES_IPV6 }, }; #define noptions (sizeof (options) / sizeof (options[0])) bool negate_option = *cp == '-'; diff --git a/resolv/resolv.h b/resolv/resolv.h index b8a0f66a5f..2459083fee 100644 --- a/resolv/resolv.h +++ b/resolv/resolv.h @@ -134,6 +134,8 @@ struct res_sym { #define RES_TRUSTAD 0x04000000 /* Request AD bit, keep it in responses. */ #define RES_NOAAAA 0x08000000 /* Suppress AAAA queries. */ #define RES_STRICTERR 0x10000000 /* Report more DNS errors as errors. */ +#define RES_IPV4 0x20000000 /* Query A records on PF_UNSPEC hints. */ +#define RES_IPV6 0x40000000 /* Query AAAA records on PF_UNSPEC hints. */ #define RES_DEFAULT (RES_RECURSE|RES_DEFNAMES|RES_DNSRCH) diff --git a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c index 1e4c59dac9..aec63656a9 100644 --- a/resolv/tst-resolv-res_init-skeleton.c +++ b/resolv/tst-resolv-res_init-skeleton.c @@ -130,6 +130,8 @@ print_resp (FILE *fp, res_state resp) print_option_flag (fp, &options, RES_TRUSTAD, "trust-ad"); print_option_flag (fp, &options, RES_NOAAAA, "no-aaaa"); print_option_flag (fp, &options, RES_STRICTERR, "strict-error"); + print_option_flag (fp, &options, RES_IPV4, "ipv4"); + print_option_flag (fp, &options, RES_IPV6, "ipv6"); fputc ('\n', fp); if (options != 0) fprintf (fp, "; error: unresolved option bits: 0x%x\n", options); From patchwork Wed Apr 29 17:00:49 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?UGV0ciBNZW7FocOtaw==?= X-Patchwork-Id: 134166 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 48FFF4BBC0CB for ; Wed, 29 Apr 2026 17:02:39 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 48FFF4BBC0CB Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=C8fMlEA5 X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 83F6F4BBCD92 for ; Wed, 29 Apr 2026 17:01:50 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 83F6F4BBCD92 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 83F6F4BBCD92 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1777482110; cv=none; b=Rsrdm+drFpAVyZCo9o91viybf7prnmwjPlO5inS4kTbXA8vVgLaPjuXkkHZ+VDbdc+Nf68DfSQv19qVahWBf7F28oqys0oK3ZO/WZwZKmEKKuxicN1hXgE5RQsnWvGkWQZU5bXxj2w6UBUXbt9W4EXbbq/srnyc7MVJ6ZNXLXjk= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1777482110; c=relaxed/simple; bh=Y3X/6Cn1mK7UU2E7XjP6Kx205Hyq7ZtH1pKtKIpaSzw=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=IcR26A1FX7MgmIWjivwgK1rNYgsTrCCwU2KmPbdsfZzK7scbqL1qUqDHYr/IQEaFEJYcWMzMv6EoAHLSbkalKtxbsm289TIGj/XLE9KOdGZlR8FF7JKqBP05OtoW7nAyk8RTv4j6uB1hHLvE3Y1FRrUUZ50t4eva11WQqzXgLTI= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 83F6F4BBCD92 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1777482110; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Mu8iunUgFc40aXMxAqB0zfGRGwneRl151iTmDbUw5Ts=; b=C8fMlEA5pERQbTSzqndp8nXfdgEKmHCvu0cPIAI6ZwMot8s82oRdrTBKrbo/c/TCioNukQ 4oU2RCIzr6+0aWCcWUUbMk1kuxNgQTyD00KkLjATQkyc+9o8PoHKX6lWsiv21keJkqHOs6 EUMieQAyXh4eRYoIcy/FIYul41VzXU8= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-297-8smnobTHM2esBdBdTZlGsw-1; Wed, 29 Apr 2026 13:01:48 -0400 X-MC-Unique: 8smnobTHM2esBdBdTZlGsw-1 X-Mimecast-MFC-AGG-ID: 8smnobTHM2esBdBdTZlGsw_1777482108 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 996551800652 for ; Wed, 29 Apr 2026 17:01:47 +0000 (UTC) Received: from pemensik-tp1g4 (pemensik-tp1g4.brq.redhat.com [10.43.2.231]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 889221800349; Wed, 29 Apr 2026 17:01:46 +0000 (UTC) From: =?utf-8?b?UGV0ciBNZW7FocOtaw==?= To: libc-alpha@sourceware.org Cc: =?utf-8?b?UGV0ciBNZW7FocOtaw==?= Subject: [PATCH v7 2/2] resolv: Create test for ipv4 and ipv6 resolv.conf flags Date: Wed, 29 Apr 2026 19:00:49 +0200 Message-ID: <20260429170104.2378474-2-pemensik@redhat.com> In-Reply-To: <20260429170104.2378474-1-pemensik@redhat.com> References: <20251020160017.1574993-4-pemensik@redhat.com> <20260429170104.2378474-1-pemensik@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: wpG7RViWVuh-9ejK9X8-SsWr93qHJ1RyhB7--XJmSq8_1777482108 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.0 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_PASS, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org Test newly added resolv.conf options ipv4 and ipv6. Derived from noaaa test. Count query counts after the test, because it varies depending on used flags. Fixed ai re-initialization on each attempt. Signed-off-by: Petr Menšík --- resolv/Makefile | 2 + resolv/tst-resolv-ipv4-ipv6.c | 639 ++++++++++++++++++++++++++++++++++ 2 files changed, 641 insertions(+) create mode 100644 resolv/tst-resolv-ipv4-ipv6.c diff --git a/resolv/Makefile b/resolv/Makefile index 971608eff5..841720cf25 100644 --- a/resolv/Makefile +++ b/resolv/Makefile @@ -116,6 +116,7 @@ tests += \ tst-resolv-byaddr \ tst-resolv-dns-section \ tst-resolv-edns \ + tst-resolv-ipv4-ipv6 \ tst-resolv-invalid-cname \ tst-resolv-invalid-ptr \ tst-resolv-network \ @@ -311,6 +312,7 @@ $(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-ipv4-ipv6: $(objpfx)libresolv.so $(shared-thread-library) $(objpfx)tst-resolv-invalid-cname: $(objpfx)libresolv.so \ $(shared-thread-library) $(objpfx)tst-resolv-invalid-ptr: $(objpfx)libresolv.so \ diff --git a/resolv/tst-resolv-ipv4-ipv6.c b/resolv/tst-resolv-ipv4-ipv6.c new file mode 100644 index 0000000000..d375a0989e --- /dev/null +++ b/resolv/tst-resolv-ipv4-ipv6.c @@ -0,0 +1,639 @@ +/* Test RES_IPV4 and RES_IPV6 resolver option. + 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 +#include +#include +#include + +/* Used to keep track of the number of queries. */ +static volatile unsigned int queries; + +static void +response (const struct resolv_response_context *ctx, + struct resolv_response_builder *b, + const char *qname, uint16_t qclass, uint16_t qtype) +{ + /* Each test should only send one query. */ + ++queries; + if (queries < 1 || queries > 2) + FAIL_EXIT1 ("Unexpected number of queries %d on %s\n", queries, qname); + TEST_COMPARE (qclass, C_IN); + + /* The only other query type besides A is PTR. */ + if (qtype != T_A && qtype != T_AAAA) + TEST_COMPARE (qtype, T_PTR); + + int an, ns, ar; + char *tail; + if (sscanf (qname, "an%d.ns%d.ar%d.%ms", &an, &ns, &ar, &tail) != 4) + FAIL_EXIT1 ("invalid QNAME: %s\n", qname); + TEST_COMPARE_STRING (tail, "example"); + free (tail); + + if (an < 0 || ns < 0 || ar < 0) + { + struct resolv_response_flags flags = { .rcode = NXDOMAIN, }; + resolv_response_init (b, flags); + resolv_response_add_question (b, qname, qclass, qtype); + return; + } + + struct resolv_response_flags flags = {}; + resolv_response_init (b, flags); + resolv_response_add_question (b, qname, qclass, qtype); + + resolv_response_section (b, ns_s_an); + for (int i = 0; i < an; ++i) + { + resolv_response_open_record (b, qname, qclass, qtype, 60); + switch (qtype) + { + case T_A: + { + char ipv4[4] = {192, 0, 2, i + 1}; + resolv_response_add_data (b, &ipv4, sizeof (ipv4)); + } + break; + + case T_AAAA: + { + char addr_ipv6[16] = + { 0x20, 1, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, i + 1 }; + resolv_response_add_data (b, addr_ipv6, sizeof (addr_ipv6)); + } + break; + + case T_PTR: + { + char *name = xasprintf ("ptr-%d", i); + resolv_response_add_name (b, name); + free (name); + } + break; + } + resolv_response_close_record (b); + } + + resolv_response_section (b, ns_s_ns); + for (int i = 0; i < ns; ++i) + { + resolv_response_open_record (b, qname, qclass, T_NS, 60); + char *name = xasprintf ("ns%d.example.net", i); + resolv_response_add_name (b, name); + free (name); + resolv_response_close_record (b); + } + + resolv_response_section (b, ns_s_ar); + int addr = 1; + for (int i = 0; i < ns; ++i) + { + char *name = xasprintf ("ns%d.example.net", i); + for (int j = 0; j < ar; ++j) + { + resolv_response_open_record (b, name, qclass, T_A, 60); + char ipv4[4] = {192, 0, 2, addr}; + resolv_response_add_data (b, &ipv4, sizeof (ipv4)); + resolv_response_close_record (b); + + resolv_response_open_record (b, name, qclass, T_AAAA, 60); + char ipv6[16] + = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, addr}; + resolv_response_add_data (b, &ipv6, sizeof (ipv6)); + resolv_response_close_record (b); + + ++addr; + } + free (name); + } +} + +/* Number of modes. Lowest bit encodes *n* function vs implicit _res + argument. The mode numbers themselves are arbitrary. */ +enum { mode_count = 8 }; + +/* res_send-like modes do not perform error translation. */ +enum { first_send_mode = 6 }; + +static int +libresolv_query (unsigned int mode, const char *qname, uint16_t qtype, + unsigned char *buf, size_t buflen) +{ + int saved_errno = errno; + + TEST_VERIFY_EXIT (mode < mode_count); + + switch (mode) + { + case 0: + return res_query (qname, C_IN, qtype, buf, buflen); + case 1: + return res_nquery (&_res, qname, C_IN, qtype, buf, buflen); + case 2: + return res_search (qname, C_IN, qtype, buf, buflen); + case 3: + return res_nsearch (&_res, qname, C_IN, qtype, buf, buflen); + case 4: + return res_querydomain (qname, "", C_IN, qtype, buf, buflen); + case 5: + return res_nquerydomain (&_res, qname, "", C_IN, qtype, buf, buflen); + case 6: + { + unsigned char querybuf[512]; + int ret = res_mkquery (QUERY, qname, C_IN, qtype, + NULL, 0, NULL, querybuf, sizeof (querybuf)); + TEST_VERIFY_EXIT (ret > 0); + errno = saved_errno; + return res_send (querybuf, ret, buf, buflen); + } + case 7: + { + unsigned char querybuf[512]; + int ret = res_nmkquery (&_res, QUERY, qname, C_IN, qtype, + NULL, 0, NULL, querybuf, sizeof (querybuf)); + TEST_VERIFY_EXIT (ret > 0); + errno = saved_errno; + return res_nsend (&_res, querybuf, ret, buf, buflen); + } + } + __builtin_unreachable (); +} + +/* Common parts should not change behaviour whatever flag is used. */ +static int +do_test_common(void) +{ + queries = 0; + + /* getaddrinfo checks with one address. */ + struct addrinfo *ai = NULL; + int ret; + ret = getaddrinfo ("an1.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an1.ns2.ar1.example (AF_INET)", ai, ret, + "address: STREAM/TCP 192.0.2.1 80\n"); + freeaddrinfo (ai); + ai = NULL; + TEST_COMPARE (queries, 1); + + queries = 0; + ret = getaddrinfo ("an1.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_INET6, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an1.ns2.ar1.example (AF_INET6)", ai, ret, + "address: STREAM/TCP 2001:db8::1 80\n"); + freeaddrinfo (ai); + ai = NULL; + TEST_COMPARE (queries, 1); + queries = 0; + + /* getaddrinfo checks with three addresses. */ + ret = getaddrinfo ("an3.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an3.ns2.ar1.example (AF_INET)", ai, ret, + "address: STREAM/TCP 192.0.2.1 80\n" + "address: STREAM/TCP 192.0.2.2 80\n" + "address: STREAM/TCP 192.0.2.3 80\n"); + freeaddrinfo (ai); + ai = NULL; + TEST_COMPARE (queries, 1); + queries = 0; + ret = getaddrinfo ("an3.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_INET6, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an3.ns2.ar1.example (AF_INET6)", ai, ret, + "address: STREAM/TCP 2001:db8::1 80\n" + "address: STREAM/TCP 2001:db8::2 80\n" + "address: STREAM/TCP 2001:db8::3 80\n"); + freeaddrinfo (ai); + ai = NULL; + TEST_COMPARE (queries, 1); + queries = 0; + + return ret; +} + +/* test IPV4 flag */ +static int +do_test_ipv4 (int options) +{ + int ret; + struct addrinfo *ai = NULL; + + _res.options = options; + queries = 0; + + ret = do_test_common(); + + /* getaddrinfo checks with one address. */ + ret = getaddrinfo ("an1.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an1.ns2.ar1.example (AF_UNSPEC) ipv4", ai, ret, + "address: STREAM/TCP 192.0.2.1 80\n"); + freeaddrinfo (ai); + ai = NULL; + TEST_COMPARE (queries, 1); + + queries = 0; + ret = getaddrinfo ("an3.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an3.ns2.ar1.example (AF_UNSPEC) ipv4", ai, ret, + "address: STREAM/TCP 192.0.2.1 80\n" + "address: STREAM/TCP 192.0.2.2 80\n" + "address: STREAM/TCP 192.0.2.3 80\n"); + freeaddrinfo (ai); + ai = NULL; + TEST_COMPARE (queries, 1); + queries = 0; + + return ret; +} + +static int +do_test_ipv6 (int options) +{ + int ret; + struct addrinfo *ai = NULL; + + _res.options = options; + queries = 0; + + ret = do_test_common(); + + /* getaddrinfo checks with one address. */ + ret = getaddrinfo ("an1.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an1.ns2.ar1.example (AF_UNSPEC) ipv6", ai, ret, + "address: STREAM/TCP 2001:db8::1 80\n"); + freeaddrinfo (ai); + ai = NULL; + TEST_COMPARE (queries, 1); + queries = 0; + + /* getaddrinfo checks with three addresses. */ + ret = getaddrinfo ("an3.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an3.ns2.ar1.example (AF_UNSPEC) ipv6", ai, ret, + "address: STREAM/TCP 2001:db8::1 80\n" + "address: STREAM/TCP 2001:db8::2 80\n" + "address: STREAM/TCP 2001:db8::3 80\n"); + freeaddrinfo (ai); + ai = NULL; + TEST_COMPARE (queries, 1); + queries = 0; + + return ret; +} + +/* When both flags are applied */ +static int +do_test_ipv4_ipv6 (int options) +{ + int ret; + struct addrinfo *ai = NULL; + + _res.options = options; + queries = 0; + + ret = do_test_common(); + + /* getaddrinfo checks with one address. */ + ret = getaddrinfo ("an1.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an1.ns2.ar1.example (AF_UNSPEC) ipv4 ipv6", ai, ret, + "address: STREAM/TCP 192.0.2.1 80\n" + "address: STREAM/TCP 2001:db8::1 80\n"); + freeaddrinfo (ai); + ai = NULL; + TEST_COMPARE (queries, 2); + queries = 0; + + /* getaddrinfo checks with three addresses. */ + ret = getaddrinfo ("an3.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an3.ns2.ar1.example (AF_UNSPEC) ipv4 ipv6", ai, ret, + "address: STREAM/TCP 192.0.2.1 80\n" + "address: STREAM/TCP 192.0.2.2 80\n" + "address: STREAM/TCP 192.0.2.3 80\n" + "address: STREAM/TCP 2001:db8::1 80\n" + "address: STREAM/TCP 2001:db8::2 80\n" + "address: STREAM/TCP 2001:db8::3 80\n"); + freeaddrinfo (ai); + ai = NULL; + TEST_COMPARE (queries, 2); + queries = 0; + + return ret; +} + +static int +do_test (void) +{ + int baseoptions; + int ret; + struct addrinfo *ai = NULL; + struct resolv_test *obj = resolv_test_start + ((struct resolv_redirect_config) + { + .response_callback = response + }); + + baseoptions = _res.options; + + ret = do_test_ipv4(baseoptions | RES_IPV4); + ret = do_test_ipv6(baseoptions | RES_IPV6); + ret = do_test_ipv4_ipv6(baseoptions); + ret = do_test_ipv4_ipv6(baseoptions | RES_IPV4 | RES_IPV6); + + /* getaddrinfo checks with no address. */ + ret = getaddrinfo ("an0.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an0.ns2.ar1.example (AF_INET)", ai, ret, + "error: No address associated with hostname\n"); + freeaddrinfo (ai); + ai = NULL; + queries = 0; + ret = getaddrinfo ("an0.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_INET6, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an0.ns2.ar1.example (AF_INET6)", ai, ret, + "error: No address associated with hostname\n"); + freeaddrinfo (ai); + ai = NULL; + queries = 0; + ret = getaddrinfo ("an0.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an-1.ns2.ar1.example (AF_UNSPEC)", ai, ret, + "error: No address associated with hostname\n"); + freeaddrinfo (ai); + ai = NULL; + queries = 0; + + /* getaddrinfo checks with NXDOMAIN. */ + ret = getaddrinfo ("an-1.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an-1.ns2.ar1.example (AF_INET)", ai, ret, + "error: Name or service not known\n"); + freeaddrinfo (ai); + ai = NULL; + queries = 0; + ret = getaddrinfo ("an-1.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_INET6, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an-1.ns2.ar1.example (AF_INET6)", ai, ret, + "error: Name or service not known\n"); + freeaddrinfo (ai); + ai = NULL; + queries = 0; + ret = getaddrinfo ("an-1.ns2.ar1.example", "80", + &(struct addrinfo) + { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }, &ai); + check_addrinfo ("an-1.ns2.ar1.example (AF_UNSPEC)", ai, ret, + "error: Name or service not known\n"); + freeaddrinfo (ai); + ai = NULL; + queries = 0; + + for (unsigned int mode = 0; mode < mode_count; ++mode) + { + unsigned char *buf; + int ret; + + /* Response for A. */ + buf = malloc (512); + ret = libresolv_query (mode, "an1.ns2.ar1.example", T_A, buf, 512); + TEST_VERIFY_EXIT (ret > 0); + check_dns_packet ("an1.ns2.ar1.example A", buf, ret, + "name: an1.ns2.ar1.example\n" + "address: 192.0.2.1\n"); + free (buf); + queries = 0; + + /* NODATA response for A. */ + buf = malloc (512); + errno = 0; + ret = libresolv_query (mode, "an0.ns2.ar1.example", T_A, buf, 512); + if (mode < first_send_mode) + { + TEST_COMPARE (ret, -1); + TEST_COMPARE (errno, 0); + TEST_COMPARE (h_errno, NO_ADDRESS); + } + else + { + TEST_VERIFY_EXIT (ret > 0); + TEST_COMPARE (((HEADER *)buf)->rcode, 0); + check_dns_packet ("an1.ns2.ar1.example A", buf, ret, + "name: an0.ns2.ar1.example\n"); + } + free (buf); + queries = 0; + + /* NXDOMAIN response for A. */ + buf = malloc (512); + errno = 0; + ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_A, buf, 512); + if (mode < first_send_mode) + { + TEST_COMPARE (ret, -1); + TEST_COMPARE (errno, 0); + TEST_COMPARE (h_errno, HOST_NOT_FOUND); + } + else + { + TEST_VERIFY_EXIT (ret > 0); + TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN); + check_dns_packet ("an1.ns2.ar1.example A", buf, ret, + "name: an-1.ns2.ar1.example\n"); + } + free (buf); + queries = 0; + + /* Response for PTR. */ + buf = malloc (512); + ret = libresolv_query (mode, "an1.ns2.ar1.example", T_PTR, buf, 512); + TEST_VERIFY_EXIT (ret > 0); + check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret, + "name: an1.ns2.ar1.example\n" + "data: an1.ns2.ar1.example PTR ptr-0\n"); + free (buf); + queries = 0; + + /* NODATA response for PTR. */ + buf = malloc (512); + errno = 0; + ret = libresolv_query (mode, "an0.ns2.ar1.example", T_PTR, buf, 512); + if (mode < first_send_mode) + { + TEST_COMPARE (ret, -1); + TEST_COMPARE (errno, 0); + TEST_COMPARE (h_errno, NO_ADDRESS); + } + else + { + TEST_VERIFY_EXIT (ret > 0); + TEST_COMPARE (((HEADER *)buf)->rcode, 0); + check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret, + "name: an0.ns2.ar1.example\n"); + } + free (buf); + queries = 0; + + /* NXDOMAIN response for PTR. */ + buf = malloc (512); + errno = 0; + ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_PTR, buf, 512); + if (mode < first_send_mode) + { + TEST_COMPARE (ret, -1); + TEST_COMPARE (errno, 0); + TEST_COMPARE (h_errno, HOST_NOT_FOUND); + } + else + { + TEST_VERIFY_EXIT (ret > 0); + TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN); + check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret, + "name: an-1.ns2.ar1.example\n"); + } + free (buf); + queries = 0; + + /* NODATA response for AAAA. */ + buf = malloc (512); + errno = 0; + ret = libresolv_query (mode, "an1.ns2.ar1.example", T_AAAA, buf, 512); + TEST_VERIFY_EXIT (ret > 0); + check_dns_packet ("an1.ns2.ar1.example AAAA", buf, ret, + "name: an1.ns2.ar1.example\n" + "address: 2001:db8::1\n"); + TEST_VERIFY_EXIT (ret > 0); + free (buf); + queries = 0; + + /* NODATA response for AAAA (original is already NODATA). */ + buf = malloc (512); + errno = 0; + ret = libresolv_query (mode, "an0.ns2.ar1.example", T_AAAA, buf, 512); + if (mode < first_send_mode) + { + TEST_COMPARE (ret, -1); + TEST_COMPARE (errno, 0); + TEST_COMPARE (h_errno, NO_ADDRESS); + } + else + { + TEST_VERIFY_EXIT (ret > 0); + TEST_COMPARE (((HEADER *)buf)->rcode, 0); + check_dns_packet ("an0.ns2.ar1.example A", buf, ret, + "name: an0.ns2.ar1.example\n"); + } + free (buf); + queries = 0; + + /* NXDOMAIN response. */ + buf = malloc (512); + errno = 0; + ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_AAAA, buf, 512); + if (mode < first_send_mode) + { + TEST_COMPARE (ret, -1); + TEST_COMPARE (errno, 0); + TEST_COMPARE (h_errno, HOST_NOT_FOUND); + } + else + { + TEST_VERIFY_EXIT (ret > 0); + TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN); + check_dns_packet ("an-1.ns2.ar1.example A", buf, ret, + "name: an-1.ns2.ar1.example\n"); + } + free (buf); + queries = 0; + } + + resolv_test_end (obj); + + return 0; +} + +#include