easy: reset errorbuf on eyeballing success

Any failf() that fill the errorbuf need to be forgotten once happy
eyeballing finds a succssful winner. Because the errorbuf, once set, is
not overwritten with future error information.

Adds test_05_05 to verify.

Reported-by: Tim Friedrich Brüggemann
Fixes #20608

Closes #20613
This commit is contained in:
Stefan Eissing 2026-02-17 09:50:25 +01:00 committed by Viktor Szakats
parent 36c2c7626f
commit f1c9d5e484
No known key found for this signature in database
7 changed files with 47 additions and 0 deletions

View file

@ -58,6 +58,10 @@ only send 800.
The percentage of send() calls that should be answered with EAGAIN at random.
QUIC only.
## `CURL_DBG_SOCK_FAIL_IPV6`
Fail opening of sockets for the IPv6 address family.
## `CURL_DEBUG`
Trace logging behavior as an alternative to calling curl_global_trace(3).

View file

@ -222,6 +222,8 @@ static CURLcode baller_connected(struct Curl_cfilter *cf,
/* install the winning filter below this one. */
cf->next = winner->cf;
winner->cf = NULL;
/* whatever errors where reported by ballers, clear our errorbuf */
Curl_reset_fail(data);
#ifdef USE_NGHTTP2
{

View file

@ -427,6 +427,10 @@ evaluate:
addr = cf_ai_iter_next(&bs->addr_iter);
ai_family = bs->addr_iter.ai_family;
}
/* We are (re-)starting attempts. We are not interested in
* keeping old failure information. The new attempt will either
* succeed or persist new failure. */
Curl_reset_fail(data);
if(addr) { /* try another address */
result = cf_ip_attempt_new(&a, cf, data, addr, ai_family,
@ -793,6 +797,8 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf,
ctx->ballers.winner->cf = NULL;
cf_ip_happy_ctx_clear(cf, data);
Curl_expire_done(data, EXPIRE_HAPPY_EYEBALLS);
/* whatever errors where reported by ballers, clear our errorbuf */
Curl_reset_fail(data);
if(cf->conn->scheme->protocol & PROTO_FAMILY_SSH)
Curl_pgrsTime(data, TIMER_APPCONNECT); /* we are connected already */

View file

@ -332,6 +332,12 @@ static CURLcode socket_open(struct Curl_easy *data,
}
else {
/* opensocket callback not set, so simply create the socket now */
#ifdef DEBUGBUILD
if((addr->family == AF_INET6) && getenv("CURL_DBG_SOCK_FAIL_IPV6")) {
failf(data, "CURL_DBG_SOCK_FAIL_IPV6: failed to open socket");
return CURLE_COULDNT_CONNECT;
}
#endif
*sockfd = CURL_SOCKET(addr->family, addr->socktype, addr->protocol);
if((*sockfd == CURL_SOCKET_BAD) && (SOCKERRNO == SOCKENOMEM))
return CURLE_OUT_OF_MEMORY;

View file

@ -194,6 +194,13 @@ void Curl_failf(struct Curl_easy *data, const char *fmt, ...)
}
}
void Curl_reset_fail(struct Curl_easy *data)
{
if(data->set.errorbuffer)
data->set.errorbuffer[0] = 0;
data->state.errorbuf = FALSE;
}
#ifdef CURLVERBOSE
struct curl_trc_feat Curl_trc_feat_multi = {
"MULTI",

View file

@ -61,6 +61,11 @@ void Curl_failf(struct Curl_easy *data,
#define failf Curl_failf
/* In case failf() reported into the errorbuf, clear it again.
* This is used to clear information from happy eyeballing attempts
* when ultimately a successful attempt was made. */
void Curl_reset_fail(struct Curl_easy *data);
#define CURL_LOG_LVL_NONE 0
#define CURL_LOG_LVL_INFO 1

View file

@ -25,6 +25,7 @@
###########################################################################
#
import logging
import os
import pytest
from testenv import Env, CurlClient
@ -123,3 +124,19 @@ class TestErrors:
else:
r.check_exit_code(0)
r.check_response(http_status=200, count=count)
# Make a connect with a fail for '::1' and wrong certificate for '127.0.0.1'
# The error message reported needs to be from the certificate.
# verifies issue #20608
@pytest.mark.parametrize("proto", Env.http_h1_h2_protos())
def test_05_05_failed_peer(self, env: Env, proto, httpd, nghttpx):
domain = f'invalid.{env.tld}'
url = f'https://{domain}:{env.port_for(proto)}/'
run_env = os.environ.copy()
run_env['CURL_DBG_SOCK_FAIL_IPV6'] = '1'
curl = CurlClient(env=env, run_env=run_env)
r = curl.http_download(urls=[url], alpn_proto=proto, extra_args=[
'--resolve', f'{domain}:{env.port_for(proto)}:::1,127.0.0.1',
])
assert r.exit_code == 60, f'{r}'
assert r.stats[0]['errormsg'] != 'CURL_DBG_SOCK_FAIL_IPV6: failed to open socket'