asyn-thrdd: retry link-local ipv6 if missing scope id

When the threaded resolver gets AAAA results that carry a link-local
address without scope-id, it now re-queues a query with AF_UNSPEC and
strips ipv4 addresses from that result. Whatever the resulting addresses
and scope-ids are, this becomes the result of the resolve.

Fixes #22330
Reported-by: Bartel Sielski
Closes #22368
This commit is contained in:
Stefan Eissing 2026-07-21 13:28:59 +02:00 committed by Daniel Stenberg
parent 489a4c1b48
commit 545cdd4b50
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
8 changed files with 194 additions and 66 deletions

View file

@ -26,6 +26,7 @@
#
import logging
import os
import re
from datetime import timedelta
from typing import Generator
@ -283,6 +284,30 @@ class TestResolve:
r.check_stats(count=1, http_status=0, exitcode=6)
assert r.duration < timedelta(seconds=20), f'{r}'
# dnsd with one answer for AAAA, delayed one for A
@pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override")
@pytest.mark.skipif(condition=not Env.curl_has_feature('IPv6'), reason="no IPv6")
@pytest.mark.skipif(condition=not Env.curl_resolv_threaded(), reason="no threaded resolver")
def test_21_16_dnsd_link_local(self, env: Env, httpd, dnsd):
dnsd.set_answers(addr_aaaa=['[fe80::1]'])
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env['CURL_QUICK_EXIT'] = '1'
run_env['CURL_DEBUG'] = 'dns'
curl = CurlClient(env=env, run_env=run_env, force_resolv=False)
url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json'
r = curl.http_download(urls=[url], with_stats=True, extra_args=[
'--connect-timeout', '1'
])
# should fail with CURLE_OPERATION_TIMEOUT or COULDNT_CONNECT
assert r.exit_code in (7, 28), f'{r.dump_logs()}'
af_unspec_resolves = [line for line in r.trace_lines if
re.match(r'.* \[DNS] re-queueing query .+ for AF_UNSPEC resolve', line)]
assert len(af_unspec_resolves) == 1, f'{r.dump_logs()}'
aaaa_resolves = [line for line in r.trace_lines if
re.match(r'.* \* IPv6: fe80::1', line)]
assert len(aaaa_resolves) == 1, f'{r.dump_logs()}'
def _clean_files(self, files):
for file in files:
if os.path.exists(file):

View file

@ -88,7 +88,7 @@ static CURLcode test_unit3301(const char *arg)
/* create and teardown queue */
memset(&ctx, 0, sizeof(ctx));
result = Curl_thrdq_create(&tqueue, "unit3301-a", 0, 0, 2, 1,
result = Curl_thrdq_create(&tqueue, "unit3301-a", 0, 2, 1,
unit3301_item_free, unit3301_process,
unit3301_event, &ctx);
fail_unless(!result, "queue-a create");
@ -99,7 +99,7 @@ static CURLcode test_unit3301(const char *arg)
/* create queue, have it process `count` items */
count = 10;
memset(&ctx, 0, sizeof(ctx));
result = Curl_thrdq_create(&tqueue, "unit3301-b", 0, 0, 2, 1,
result = Curl_thrdq_create(&tqueue, "unit3301-b", 0, 2, 1,
unit3301_item_free, unit3301_process,
unit3301_event, &ctx);
fail_unless(!result, "queue-b create");

View file

@ -76,7 +76,7 @@ static CURLcode test_unit3306(const char *arg)
fail_unless(getenv("CURL_DBG_THRDPOOL_FAIL_STARTS"),
"CURL_DBG_THRDPOOL_FAIL_STARTS must be set for this test");
result = Curl_thrdq_create(&tqueue, "unit3306", 0, 0, 2, 1,
result = Curl_thrdq_create(&tqueue, "unit3306", 0, 2, 1,
unit3306_item_free, unit3306_process,
NULL, NULL);
fail_unless(!result, "queue create");