Skip to content

100% CPU hang with nghttp3 HTTP/3 and low MTU #20440

Closed

Description

@danog

I did this

When making HTTP/3 requests over interfaces with a low MTU (starting at 1470), curl gets stuck with 100% CPU usage.

It's especially an issue when using VPN connections or tunnels of another kind.

To easily reproduce, set the MTU to any value lower than or equal to 1470, for example:

ip link set mtu 1420 dev enp47s0

Then run:

curl --http3 https://google.com/

I expected the following

No hang

curl/libcurl version

curl 8.18.0 (x86_64-pc-linux-gnu) libcurl/8.18.0 OpenSSL/3.6.0 zlib/1.3.1.zlib-ng brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 libssh2/1.11.1 nghttp2/1.68.0 ngtcp2/1.20.0 nghttp3/1.15.0 librtmp/2.3 mit-krb5/1.21.3
Release-Date: 2026-01-07
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns mqtt pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp ws wss
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

operating system

Arch linux, Linux arch-lenovo 6.18.7-1-cachyos #1 SMP PREEMPT_DYNAMIC Fri, 23 Jan 2026 23:19:52 +0000 x86_64 GNU/Linux

Activity

  1. danog commented on Jan 26, 2026

    @danog
    Author

    Looking in more detail, strace shows a busy loop due to lack of handling of an EMSGSIZE returned by sendmsg:

    sendmsg(5, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="_\342\334\253o\253\3220\215\r\377$\\\37\353\340\27N\333\30\273\343g\201\256Y\332\17\310\0036T"..., iov_len=1444}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = -1 EMSGSIZE (Message too long)
    ... repeated endlessly
    

    Relevant -vvv output:

    16:23:46.856820 [0-0] => Send header, 72 bytes (0x48)
    0000: GET / HTTP/3
    000e: Host: google.com
    0020: User-Agent: curl/8.18.0
    0039: Accept: */*
    0046:
    16:23:46.856912 [0-0] * Request completely sent off
    16:23:46.856962 [0-0] * [HTTP/3] ingress, recvmmsg -> EAGAIN
    16:23:46.856998 [0-0] * [HTTP/3] egress, collect and send packets, quantum=12000
    16:23:46.857047 [0-0] * [HTTP/3] [0] cf_recv(blen=102400) -> 81, 0
    16:23:46.858250 [0-0] * [HTTP/3] ingress, recvmmsg -> EAGAIN
    16:23:46.858308 [0-0] * [HTTP/3] egress, collect and send packets, quantum=12000
    16:23:46.858363 [0-0] * [HTTP/3] [0] cf_recv(blen=102400) -> 81, 0
    16:23:46.865527 [0-0] * [HTTP/3] ingress, recvmmsg -> EAGAIN
    16:23:46.865556 [0-0] * [HTTP/3] egress, collect and send packets, quantum=12000
    16:23:46.865593 [0-0] * [HTTP/3] [0] cf_recv(blen=102400) -> 81, 0
    16:23:46.873329 [0-0] * [HTTP/3] ingress, recvmmsg -> EAGAIN
    16:23:46.873342 [0-0] * [HTTP/3] vquic_recvmmsg(len=851, packets=3, calls=1) -> 0
    16:23:46.873354 [0-0] * [HTTP/3] egress, collect and send packets, quantum=14060
    16:23:46.873372 [0-0] * [HTTP/3] vquic_send_tail_split: [1444 gso=1444][42 gso=42]
    16:23:46.873386 [0-0] * [HTTP/3] vquic_sendmsg(len=1444, gso=1444, calls=1) -> 0, sent=0
    16:23:46.873400 [0-0] * [HTTP/3] vquic_sendmsg(len=1444, gso=1444, calls=1) -> 0, sent=0
    ...
    
  2. eworm-de commented on Jan 26, 2026

    @eworm-de
    Contributor

    Just for reference... This is the downstream report: https://gitlab.archlinux.org/archlinux/packaging/packages/curl/-/issues/14

  3. eworm-de commented on Jan 26, 2026

    @eworm-de
    Contributor

    Hmm, it loops in vquic_flush():

    curl/lib/vquic/vquic.c

    Lines 300 to 320 in 08b31b1

    while(Curl_bufq_peek(&qctx->sendbuf, &buf, &blen)) {
    gsolen = qctx->gsolen;
    if(qctx->split_len) {
    gsolen = qctx->split_gsolen;
    if(blen > qctx->split_len)
    blen = qctx->split_len;
    }
    result = vquic_send_packets(cf, data, qctx, buf, blen, gsolen, &sent);
    if(result) {
    if(result == CURLE_AGAIN) {
    Curl_bufq_skip(&qctx->sendbuf, sent);
    if(qctx->split_len)
    qctx->split_len -= sent;
    }
    return result;
    }
    Curl_bufq_skip(&qctx->sendbuf, sent);
    if(qctx->split_len)
    qctx->split_len -= sent;
    }

  4. eworm-de commented on Jan 26, 2026

    @eworm-de
    Contributor

    It calls into vquic_send_packets() (that's where message regarding "vquic_sendmsg()" is given), then do_sendmsg(). There it hits this code:

    curl/lib/vquic/vquic.c

    Lines 169 to 171 in 08b31b1

    case SOCKEMSGSIZE:
    /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */
    break;

    Should it set a result there?

  5. eworm-de commented on Jan 26, 2026

    @eworm-de
    Contributor

    This makes it succeed...

    --- a/lib/vquic/vquic.c
    +++ b/lib/vquic/vquic.c
    @@ -168,7 +168,7 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf,
           return CURLE_AGAIN;
         case SOCKEMSGSIZE:
           /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */
    -      break;
    +      return CURLE_SEND_ERROR;
         case EIO:
           if(pktlen > gsolen) {
             /* GSO failure */
    

    Not sure it is correct, though. :-p

  6. added a commit that references this issue on Jan 27, 2026
  7. eworm-de commented on Jan 27, 2026

    @eworm-de
    Contributor

    Opened a pull request in #20445 to feed the CI.

  8. added a commit that references this issue on Jan 27, 2026
  9. icing commented on Jan 27, 2026

    @icing
    Contributor

    I propose #20448 to handle MTU probing without erroring the connection. Could you try that in your setup? Thanks!

  10. eworm-de commented on Jan 27, 2026

    @eworm-de
    Contributor

    Works for me. Anybody else wants to give it a try? @danog? @christian-heusel?

  11. danog commented on Jan 27, 2026

    @danog
    Author

    #20448 seems to be working fine

  12. added a commit that references this issue on Jan 28, 2026
    cf4164f
  13. eworm-de commented on Jan 28, 2026

    @eworm-de
    Contributor

    Thanks a lot!

  14. danog commented on Jan 28, 2026

    @danog
    Author

    Thanks, awesome!

  15. added a commit that references this issue on Mar 5, 2026
    8df342d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

HTTP/3h3 or quic related

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions