Orange-OpenSource / hurl

Hurl, run and test HTTP requests with plain text.
https://hurl.dev
Apache License 2.0
13.18k stars 493 forks source link

Sporadic failures/timeout on CI for Arch Linux / HTTP/3 #3088

Open jcamiel opened 4 months ago

jcamiel commented 4 months ago

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.

jcamiel commented 4 months ago

Update reproduced with libcurl only with this code:

#include <curl/curl.h>

static CURLcode sslctx_function(CURL *curl, void *sslctx, void *parm) {
    return CURLE_OK;
}

int main(int argc, char *argv[]) {
    CURLcode ret;
    CURL *hnd;

    char *data = "";

    hnd = curl_easy_init();

    curl_easy_reset(hnd);

    curl_easy_setopt(hnd, CURLOPT_URL, "https://google.com");
    curl_easy_setopt(hnd, CURLOPT_TIMEOUT, 20L);
    curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L);
    curl_easy_setopt(hnd, CURLOPT_NOBODY, 1L);
    curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L);
    curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_3);
    curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
    curl_easy_setopt(hnd, CURLOPT_SSL_CTX_FUNCTION, *sslctx_function);
    curl_easy_setopt(hnd, CURLOPT_SSL_CTX_DATA, data);
    ret = curl_easy_perform(hnd);

    curl_easy_cleanup(hnd);

    return (int)ret;
}

https://github.com/jcamiel/curlhttp3/actions/runs/10113188862/job/27968973290

See mail on libcurl mailing list https://curl.se/mail/lib-2024-07/0035.html