← Commit list

f71efc7a8b

Joshua Rogers
Joshua Rogers
2026-09-22
SUCCESS Duration: 390s

Subject

vdns: announce resolver start before the HTTPS RR query is sent

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0

—

Build Log

(log yok)

Commit Diff GitHub →

docs/libcurl/opts/CURLOPT_RESOLVER_START_FUNCTION.md+4 -1
@@ -34,7 +34,10 @@ Pass a pointer to your callback function, which should match the prototype
shown above.
This callback function gets called by libcurl every time before a new resolve
-request is started.
+request is started. A single transfer can trigger more than one such
+request, for example an HTTPS RR lookup is a separate request from the
+regular A/AAAA lookup, and each gets its own call to this callback before it
+is started.
*resolver_state* points to a backend-specific resolver state. Currently only
the ares resolver backend has a resolver state. It can be used to set up any
lib/vdns/asyn-thrdd.c+35 -18
@@ -212,39 +212,48 @@ static void async_thrdd_rr_done(void *user_data, ares_status_t status,
async->result = Curl_httpsrr_from_ares(dnsrec, &async->httpsrr);
}
-static CURLcode async_rr_start(struct Curl_easy *data,
- struct Curl_resolv_async *async)
+/* Initialize the c-ares channel used for the HTTPS RR side-query, without
+ * submitting any query on it yet. */
+static CURLcode async_rr_init(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
struct async_thrdd_ctx *thrdd = &async->thrdd;
- char *https_name = NULL;
int status;
- CURLcode result = CURLE_OK;
+ (void)data;
DEBUGASSERT(!thrdd->rr.channel);
- if(async->peer->port != 443) {
- https_name = curl_maprintf("_%u._https.%s",
- async->peer->port, async->peer->hostname);
- if(!https_name) {
- result = CURLE_OUT_OF_MEMORY;
- goto out;
- }
- }
status = ares_init_options(&thrdd->rr.channel, NULL, 0);
if(status != ARES_SUCCESS) {
thrdd->rr.channel = NULL;
- result = CURLE_FAILED_INIT;
- goto out;
+ return CURLE_FAILED_INIT;
}
#ifdef DEBUGBUILD
if(getenv("CURL_DNS_SERVER")) {
const char *servers = getenv("CURL_DNS_SERVER");
status = ares_set_servers_ports_csv(thrdd->rr.channel, servers);
- if(status) {
- result = CURLE_FAILED_INIT;
+ if(status)
+ return CURLE_FAILED_INIT;
+ }
+#endif
+ return CURLE_OK;
+}
+
+/* Submit the HTTPS RR query on the already initialized channel. */
+static CURLcode async_rr_start(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
+{
+ struct async_thrdd_ctx *thrdd = &async->thrdd;
+ char *https_name = NULL;
+ CURLcode result = CURLE_OK;
+
+ if(async->peer->port != 443) {
+ https_name = curl_maprintf("_%u._https.%s",
+ async->peer->port, async->peer->hostname);
+ if(!https_name) {
+ result = CURLE_OUT_OF_MEMORY;
goto out;
}
}
-#endif
async->queries_ongoing++;
ares_query_dnsrec(thrdd->rr.channel,
@@ -639,7 +648,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data,
#ifdef USE_HTTPSRR_ARES
DEBUGASSERT(!async->thrdd.rr.channel);
if((async->dns_queries & CURL_DNSQ_HTTPS) && !async->is_ipaddr) {
- result = async_rr_start(data, async);
+ result = async_rr_init(data, async);
if(result)
goto out;
resolver = async->thrdd.rr.channel;
@@ -650,6 +659,14 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data,
if(result)
return result;
+#ifdef USE_HTTPSRR_ARES
+ if(resolver) {
+ result = async_rr_start(data, async);
+ if(result)
+ goto out;
+ }
+#endif
+
#ifdef CURLRES_IPV6
/* Do not start an AAAA query for an IPv4 address when
* we will start an A query for it. */
tests/data/Makefile.am+1 -0
@@ -486,6 +486,7 @@ TESTCASES = \
test461 \
test462 \
test463 \
+ test464 \
test467 \
test468 \
test469 \
tests/data/test464+32 -0
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<testcase>
+<info>
+<keywords>
+DNS
+HTTPS RR
+libtest
+</keywords>
+</info>
+
+# Client-side
+<client>
+<features>
+Debug
+asyn-rr
+</features>
+<tool>
+lib%TESTNUMBER
+</tool>
+<command>
+https://localhost:%NOLISTENPORT/464
+</command>
+
+<name>
+resolver start callback vetoes the HTTPS RR query before it is sent
+</name>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+</verify>
+</testcase>
tests/libtest/Makefile.inc+1 -0
@@ -68,6 +68,7 @@ TESTS_C = \
cli_ws_pingpong.c \
cli_ws_write_err.c \
\
+ lib464.c \
lib500.c \
lib501.c \
lib502.c \
tests/libtest/lib464.c+175 -0
@@ -0,0 +1,175 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <[email protected]>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+/*
+ * Verify that the resolver-start callback (CURLOPT_RESOLVER_START_FUNCTION)
+ * is announced, and can veto the resolve, before the HTTPS RR side-query is
+ * sent out on the wire. This binds a UDP socket of its own, points the
+ * c-ares HTTPS RR channel at it via the CURL_DNS_SERVER debug hook, and
+ * makes the callback abort the moment it sees the HTTPS RR resolver state.
+ * If the query had already been dispatched, this local "DNS server" would
+ * have received it regardless of the callback's veto.
+ */
+
+#include "first.h"
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+static int t464_cb_count = 0;
+static int t464_https_seen = 0;
+
+static int t464_resolver_start_cb(void *resolver_state, void *reserved,
+ void *userdata)
+{
+ (void)reserved;
+ (void)userdata;
+ t464_cb_count++;
+ if(resolver_state) {
+ /* This is the HTTPS RR side-channel announce: veto it. */
+ t464_https_seen = 1;
+ return 1;
+ }
+ /* This is the regular A/AAAA announce: let it proceed. */
+ return 0;
+}
+
+static CURLcode test_lib464(const char *URL)
+{
+ CURL *curl = NULL;
+ CURLcode result = CURLE_OK;
+ curl_socket_t dnssock = CURL_SOCKET_BAD;
+ struct sockaddr_in sa;
+ curl_socklen_t salen = sizeof(sa);
+ char envbuf[64];
+ char inbuf[512];
+ fd_set fds;
+ struct timeval tv;
+ int port;
+ int gotpacket = 0;
+ bool global_inited = FALSE;
+
+ dnssock = CURL_SOCKET(AF_INET, SOCK_DGRAM, 0);
+ if(dnssock == CURL_SOCKET_BAD) {
+ curl_mfprintf(stderr, "socket creation error\n");
+ return TEST_ERR_MAJOR_BAD;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sa.sin_port = 0;
+
+ if(bind(dnssock, (struct sockaddr *)&sa, sizeof(sa))) {
+ curl_mfprintf(stderr, "bind() failed\n");
+ result = TEST_ERR_MAJOR_BAD;
+ goto test_cleanup;
+ }
+ if(getsockname(dnssock, (struct sockaddr *)&sa, &salen)) {
+ curl_mfprintf(stderr, "getsockname() failed\n");
+ result = TEST_ERR_MAJOR_BAD;
+ goto test_cleanup;
+ }
+ port = ntohs(sa.sin_port);
+ curl_msnprintf(envbuf, sizeof(envbuf), "127.0.0.1:%d", port);
+#ifdef _WIN32
+ _putenv_s("CURL_DNS_SERVER", envbuf);
+#else
+ setenv("CURL_DNS_SERVER", envbuf, 1);
+#endif
+
+ if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
+ curl_mfprintf(stderr, "curl_global_init() failed\n");
+ result = TEST_ERR_MAJOR_BAD;
+ goto test_cleanup;
+ }
+ global_inited = TRUE;
+ curl = curl_easy_init();
+ if(!curl) {
+ curl_mfprintf(stderr, "curl_easy_init() failed\n");
+ result = TEST_ERR_MAJOR_BAD;
+ goto test_cleanup;
+ }
+
+ easy_setopt(curl, CURLOPT_URL, URL);
+ easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+ easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
+ easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
+ easy_setopt(curl, CURLOPT_RESOLVER_START_FUNCTION, t464_resolver_start_cb);
+
+ /* Vetoing the HTTPS RR side-query only drops that optional lookup, it
+ * does not fail the transfer: the plain A/AAAA resolve still succeeds
+ * and curl goes on to (fail to) connect, since nothing listens on the
+ * target port. */
+ result = curl_easy_perform(curl);
+ if(result != CURLE_COULDNT_CONNECT) {
+ curl_mfprintf(stderr, "curl_easy_perform should have returned "
+ "CURLE_COULDNT_CONNECT but instead returned error %d\n",
+ (int)result);
+ if(result == CURLE_OK)
+ result = TEST_ERR_FAILURE;
+ goto test_cleanup;
+ }
+ result = CURLE_OK;
+
+ if(!t464_https_seen) {
+ curl_mfprintf(stderr, "the HTTPS RR resolver-start announce never "
+ "happened, this test needs asyn-rr support\n");
+ result = TEST_ERR_FAILURE;
+ goto test_cleanup;
+ }
+ if(t464_cb_count != 2) {
+ curl_mfprintf(stderr, "Unexpected number of callbacks: %d\n",
+ t464_cb_count);
+ result = TEST_ERR_FAILURE;
+ goto test_cleanup;
+ }
+
+ FD_ZERO(&fds);
+ FD_SET(dnssock, &fds);
+ tv.tv_sec = 0;
+ tv.tv_usec = 200000;
+ if(select((int)dnssock + 1, &fds, NULL, NULL, &tv) > 0)
+ gotpacket = (recvfrom(dnssock, inbuf, sizeof(inbuf), 0, NULL, NULL) > 0);
+
+ if(gotpacket) {
+ curl_mfprintf(stderr, "the HTTPS RR query was sent on the wire before "
+ "the resolver-start callback could veto it\n");
+ result = TEST_ERR_FAILURE;
+ goto test_cleanup;
+ }
+
+test_cleanup:
+ curl_easy_cleanup(curl);
+ if(global_inited)
+ curl_global_cleanup();
+ if(dnssock != CURL_SOCKET_BAD)
+ sclose(dnssock);
+
+ return result;
+}