Open
Description
On our CI, we have sporadic failure when doing a HEAD request to https://google.com on ArchLinux, for HTTP/3.
$ curl --version
curl 8.8.0 (x86_64-pc-linux-gnu) libcurl/8.8.0 OpenSSL/3.3.1 zlib/1.3.1 brotli/1.1.0 zstd/1.5.6 libidn2/2.3.7 libpsl/0.21.5 libssh2/1.11.0 nghttp2/1.62.1 nghttp3/1.4.0
Release-Date: 2024-05-22
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns mqtt pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTP3 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM PSL SPNEGO SSL threadsafe TLS-SRP UnixSockets zstd
error: HTTP connection
--> tests_ok/http_version_3_option.hurl:6:6
|
6 | HEAD https://google.com/
| ^^^^^^^^^^^^^^^^^^ (95) HTTP/3 stream 0 reset by server
Hurl file:
HEAD https://google.com
[Options]
http3: true
HTTP/3 *
curl command:
$ curl --http3 --head https://google.com
Testing many times the curl command, we can't reproduce it.
With a libcurl sample, we can't reproduce the timeout:
int main(int argc, char *argv[])
{
CURLcode ret;
CURL *hnd;
hnd = curl_easy_init();
curl_easy_reset(hnd);
curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, 102400L);
curl_easy_setopt(hnd, CURLOPT_URL, "https://google.com");
curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(hnd, CURLOPT_NOBODY, 1L);
curl_easy_setopt(hnd, CURLOPT_USERAGENT, "curl/8.8.0");
curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L);
curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_3);
curl_easy_setopt(hnd, CURLOPT_FILETIME, 1L);
curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(hnd, CURLOPT_FTP_SKIP_PASV_IP, 1L);
curl_easy_setopt(hnd, CURLOPT_COOKIEFILE, "");
curl_easy_setopt(hnd, CURLOPT_CERTINFO, 1L);
curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 1L);
ret = curl_easy_perform(hnd);
curl_easy_cleanup(hnd);
return (int)ret;
}
With Easy Rust interface, we can reproduce it:
pub fn perform_head(url: &str) -> Result<(), Error> {
let mut handle = Easy::new();
handle.url(url)?;
handle.nobody(true)?;
handle.http_version(HttpVersion::V3)?;
handle.verbose(true)?;
handle.timeout(Duration::from_secs(20))?;
let transfer = handle.transfer();
transfer.perform()?;
Ok(())
}
With Rust bindings to libcurl, we can't reproduc it:
pub fn perform_head(url: &str) -> Result<(), Error> {
unsafe {
curl_sys::curl_global_init(curl_sys::CURL_GLOBAL_ALL);
let handle = curl_sys::curl_easy_init();
let url = CString::new(url).unwrap();
conv(curl_sys::curl_easy_setopt(
handle,
curl_sys::CURLOPT_URL,
url.as_ptr(),
))?;
conv(curl_sys::curl_easy_setopt(
handle,
curl_sys::CURLOPT_NOBODY,
1 as c_long,
))?;
conv(curl_sys::curl_easy_setopt(
handle,
curl_sys::CURLOPT_HTTP_VERSION,
curl_sys::CURL_HTTP_VERSION_3 as c_long,
))?;
conv(curl_sys::curl_easy_setopt(
handle,
curl_sys::CURLOPT_VERBOSE,
1 as c_long,
))?;
conv(curl_sys::curl_easy_setopt(
handle,
curl_sys::CURLOPT_TIMEOUT_MS,
20 * 1000 as c_long,
))?;
conv(curl_sys::curl_easy_perform(handle))?;
Ok(())
}
}
fn conv(code: CURLcode) -> Result<(), Error> {
if code == CURLE_OK {
Ok(())
} else {
Err(Error(code as i32))
}
}
In my analysis, I've the impression that the Easy curl-rust wrapper do something additional that can trigger this defect.