CVE-2026-11352: QUIC zero-length UDP datagrams busy-loop
What the report claims
curl's QUIC UDP receive helper ignores zero-length UDP datagrams before counting them against the per-call packet budget, causing a busy-loop that can keep curl running past its timeout.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2026-11352 QUIC zero-length UDP datagrams busy-loop
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The code supports the reported flaw. A maintainer should review its impact and decide what to do next.
Why this finding?
- recvmsg_packets and recvmmsg_packets skip zero-length datagrams before incrementing pkts, defeating the per-call packet budget (lib/vquic/vquic.c lines 511-512, 596-597).
- The fallback recvfrom_packets increments pkts before the zero-length check (line 661 vs 665), confirming the intended safe accounting.
- vquic_recv_packets uses the vulnerable helpers on Linux (HAVE_SENDMMSG) and other platforms with HAVE_SENDMSG (lines 692, 694).
- A remote QUIC peer can send unlimited zero-length datagrams after handshake completion, keeping curl busy in vquic_recv_packets without returning to the event loop where --max-time is enforced.
- The project's own 'Busy-loops' policy states: 'If the problem bypasses the regular counter-measures and it causes a never-ending transfer, it might be a security problem.' This qualifies.
How this analysis ran 5 claims · 7 citations verified
Checked against curl master on 2026-06-05 (3d721a1d41a7). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Review a draft reply to the reporter
Edit and verify before sending.
Thank you for the detailed report and reproduction script. The code confirms that recvmsg_packets() and recvmmsg_packets() skip zero-length datagrams before incrementing the packet counter pkts (lib/vquic/vquic.c lines 511-512, 596-597), while the fallback recvfrom_packets() increments pkts first (line 661). This defeats the per-call max_pkts budget, allowing a remote QUIC peer to keep curl busy in vquic_recv_packets() indefinitely, bypassing --max-time. We will prepare a fix that moves the pkts increment before the zero-length check in both helpers.
Inspect all claims and source code 5 claims · 9 code excerpts
Report claims
recvmsg_packets() and recvmmsg_packets() skip zero-length datagrams without incrementing the packet counter, leading to a busy-loop when a peer sends many zero-length UDP datagrams.
Read the reasoning
Both helpers check for zero-length payloads before incrementing the packet counter pkts. The loop condition 'pkts < max_pkts' therefore makes no progress when only zero-length datagrams arrive.
The bug is in lib/vquic/vquic.c where vquic_recv_packets() uses these helpers to receive packets.
Read the reasoning
vquic_recv_packets dispatches to the two vulnerable helpers on Linux (HAVE_SENDMMSG) and other platforms with HAVE_SENDMSG.
A malicious HTTP/3/QUIC peer sends continuous zero-length UDP datagrams after completing the QUIC handshake.
Read the reasoning
The attack vector is plausible: a QUIC peer that completed the handshake can send zero-length UDP datagrams to the connected socket, which will be processed by vquic_recv_packets via the vulnerable helpers.
curl remains busy in vquic_recv_packets() and does not respect --max-time or other timeout settings, causing denial of service.
Read the reasoning
Because pkts never increments on zero-length datagrams, the per-call budget max_pkts is never exhausted. The function does not return, so application-level timeouts (--max-time, CURLOPT_TIMEOUT) that are checked in the caller's event loop never get a chance to fire.
The provided script reproduces the issue by running a real HTTP/3 server, completing the QUIC handshake, then sending zero-length datagrams until curl is killed after 30 seconds.
Read the reasoning
The reproduction script completes a real QUIC handshake, then floods zero-length datagrams via sendmmsg, and demonstrates curl --http3-only --max-time 2 running past 30 seconds.
Read the original report (excerpt)
Source code
| 538 | static CURLcode recvmsg_packets(struct Curl_cfilter *cf, |
| 539 | struct Curl_easy *data, |
| 540 | struct cf_quic_ctx *qctx, |
| 541 | size_t max_pkts, |
| 542 | vquic_recv_pkts_cb *recv_cb, void *userp) |
| 543 | { |
| 544 | struct iovec msg_iov; |
| 545 | struct msghdr msg; |
| 546 | uint8_t buf[64 * 1024]; |
| 547 | struct sockaddr_storage remote_addr; |
| 548 | size_t total_nread, pkts, calls; |
| 549 | ssize_t rc; |
| 550 | size_t nread; |
| 551 | char errstr[STRERROR_LEN]; |
| 552 | CURLcode result = CURLE_OK; |
| 553 | uint8_t msg_ctrl[CMSG_SPACE(sizeof(int))]; |
| 554 | size_t gso_size; |
| 555 | |
| 556 | DEBUGASSERT(max_pkts > 0); |
| 557 | for(pkts = 0, total_nread = 0, calls = 0; pkts < max_pkts;) { |
| 558 | /* fully initialize this on each call to `recvmsg()`. There seem to |
| 559 | * operating systems out there that mess with `msg_iov.iov_len`. */ |
| 560 | memset(&msg, 0, sizeof(msg)); |
| 561 | msg_iov.iov_base = buf; |
| 562 | msg_iov.iov_len = (int)sizeof(buf); |
| 563 | msg.msg_iov = &msg_iov; |
| 564 | msg.msg_iovlen = 1; |
| 565 | msg.msg_control = msg_ctrl; |
| 566 | msg.msg_name = &remote_addr; |
| 567 | msg.msg_namelen = sizeof(remote_addr); |
| 568 | msg.msg_controllen = sizeof(msg_ctrl); |
| 569 | |
| 570 | while((rc = recvmsg(qctx->sockfd, &msg, 0)) == -1 && |
| 571 | (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) |
| 572 | ; |
| 573 | if(!curlx_sztouz(rc, &nread)) { |
| 574 | if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { |
| 575 | goto out; |
| 576 | } |
| 577 | if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { |
| 578 | struct ip_quadruple ip; |
| 579 | if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip)) |
| 580 | failf(data, "QUIC: connection to %s port %u refused", |
| 581 | ip.remote_ip, ip.remote_port); |
| 582 | result = CURLE_COULDNT_CONNECT; |
| 583 | goto out; |
| 584 | } |
| 585 | curlx_strerror(SOCKERRNO, errstr, sizeof(errstr)); |
| 586 | failf(data, "QUIC: recvmsg() unexpectedly returned %zd (errno=%d; %s)", |
| 587 | rc, SOCKERRNO, errstr); |
| 588 | result = CURLE_RECV_ERROR; |
| 589 | goto out; |
| 590 | } |
| 591 | |
| 592 | total_nread += nread; |
| 593 | ++calls; |
| 594 | |
| 595 | /* A 0-length UDP packet is no QUIC packet */ |
| 596 | if(!nread) |
| C1: recvmsg_packets: 'if(!nread) continue;' at line 597 skips pkts increment at line 607 | |
| 597 | continue; |
| 598 | |
| 599 | gso_size = vquic_msghdr_get_udp_gro(&msg); |
| 600 | if(gso_size == 0) |
| 601 | gso_size = nread; |
| 602 | |
| 603 | result = recv_cb(buf, nread, gso_size, |
| 604 | msg.msg_name, msg.msg_namelen, 0, userp); |
| 605 | if(result) |
| 606 | goto out; |
| 607 | pkts += (nread + gso_size - 1) / gso_size; |
| 608 | } |
| 609 | |
| 610 | out: |
| 611 | if(total_nread || result) |
| 612 | CURL_TRC_CF(data, cf, |
| 613 | "vquic_recvmsg(len=%zu, packets=%zu, calls=%zu) -> %d", |
| 614 | total_nread, pkts, calls, result); |
| 615 | return result; |
| 616 | } |
| 434 | static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, |
| 435 | struct Curl_easy *data, |
| 436 | struct cf_quic_ctx *qctx, |
| 437 | size_t max_pkts, |
| 438 | vquic_recv_pkts_cb *recv_cb, void *userp) |
| 439 | { |
| 440 | #if defined(__linux__) && defined(UDP_GRO) |
| 441 | #define MMSG_NUM 16 |
| 442 | #define UDP_GRO_CNT_MAX 64 |
| 443 | #else |
| 444 | #define MMSG_NUM 64 |
| 445 | #define UDP_GRO_CNT_MAX 1 |
| 446 | #endif |
| 447 | #define MSG_BUF_SIZE (UDP_GRO_CNT_MAX * 1500) |
| 448 | struct iovec msg_iov[MMSG_NUM]; |
| 449 | struct mmsghdr mmsg[MMSG_NUM]; |
| 450 | uint8_t msg_ctrl[MMSG_NUM * CMSG_SPACE(sizeof(int))]; |
| 451 | struct sockaddr_storage remote_addr[MMSG_NUM]; |
| 452 | size_t total_nread = 0, pkts = 0; |
| 453 | #ifdef CURLVERBOSE |
| 454 | size_t calls = 0; |
| 455 | #endif |
| 456 | int mcount, i, n; |
| 457 | char errstr[STRERROR_LEN]; |
| 458 | CURLcode result = CURLE_OK; |
| 459 | size_t gso_size; |
| 460 | char *sockbuf = NULL; |
| 461 | uint8_t (*bufs)[MSG_BUF_SIZE] = NULL; |
| 462 | |
| 463 | DEBUGASSERT(max_pkts > 0); |
| 464 | result = Curl_multi_xfer_sockbuf_borrow(data, MMSG_NUM * MSG_BUF_SIZE, |
| 465 | &sockbuf); |
| 466 | if(result) |
| 467 | goto out; |
| 468 | bufs = (uint8_t (*)[MSG_BUF_SIZE])sockbuf; |
| 469 | |
| 470 | total_nread = 0; |
| 471 | while(pkts < max_pkts) { |
| 472 | n = (int)CURLMIN(CURLMIN(MMSG_NUM, IOV_MAX), max_pkts); |
| 473 | memset(&mmsg, 0, sizeof(mmsg)); |
| 474 | for(i = 0; i < n; ++i) { |
| 475 | msg_iov[i].iov_base = bufs[i]; |
| 476 | msg_iov[i].iov_len = (int)sizeof(bufs[i]); |
| 477 | mmsg[i].msg_hdr.msg_iov = &msg_iov[i]; |
| 478 | mmsg[i].msg_hdr.msg_iovlen = 1; |
| 479 | mmsg[i].msg_hdr.msg_name = &remote_addr[i]; |
| 480 | mmsg[i].msg_hdr.msg_namelen = sizeof(remote_addr[i]); |
| 481 | mmsg[i].msg_hdr.msg_control = &msg_ctrl[i * CMSG_SPACE(sizeof(int))]; |
| 482 | mmsg[i].msg_hdr.msg_controllen = CMSG_SPACE(sizeof(int)); |
| 483 | } |
| 484 | |
| 485 | while((mcount = recvmmsg(qctx->sockfd, mmsg, n, 0, NULL)) == -1 && |
| 486 | (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) |
| 487 | ; |
| 488 | if(mcount == -1) { |
| 489 | if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { |
| 490 | CURL_TRC_CF(data, cf, "ingress, recvmmsg -> EAGAIN"); |
| 491 | goto out; |
| 492 | } |
| 493 | if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { |
| 494 | struct ip_quadruple ip; |
| 495 | if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip)) |
| 496 | failf(data, "QUIC: connection to %s port %u refused", |
| 497 | ip.remote_ip, ip.remote_port); |
| 498 | result = CURLE_COULDNT_CONNECT; |
| 499 | goto out; |
| 500 | } |
| 501 | curlx_strerror(SOCKERRNO, errstr, sizeof(errstr)); |
| 502 | failf(data, "QUIC: recvmmsg() unexpectedly returned %d (errno=%d; %s)", |
| 503 | mcount, SOCKERRNO, errstr); |
| 504 | result = CURLE_RECV_ERROR; |
| 505 | goto out; |
| 506 | } |
| 507 | |
| 508 | VERBOSE(++calls); |
| 509 | for(i = 0; i < mcount; ++i) { |
| 510 | /* A zero-length UDP packet is no QUIC packet. Ignore. */ |
| 511 | if(!mmsg[i].msg_len) |
| C1: recvmmsg_packets: 'if(!mmsg[i].msg_len) continue;' at line 512 skips pkts increment at line 524 | |
| 512 | continue; |
| 513 | total_nread += mmsg[i].msg_len; |
| 514 | |
| 515 | gso_size = vquic_msghdr_get_udp_gro(&mmsg[i].msg_hdr); |
| 516 | if(gso_size == 0) |
| 517 | gso_size = mmsg[i].msg_len; |
| 518 | |
| 519 | result = recv_cb(bufs[i], mmsg[i].msg_len, gso_size, |
| 520 | mmsg[i].msg_hdr.msg_name, |
| 521 | mmsg[i].msg_hdr.msg_namelen, 0, userp); |
| 522 | if(result) |
| 523 | goto out; |
| 524 | pkts += (mmsg[i].msg_len + gso_size - 1) / gso_size; |
| 525 | } |
| 526 | } |
| 527 | |
| 528 | out: |
| 529 | if(total_nread || result) |
| 530 | CURL_TRC_CF(data, cf, |
| 531 | "vquic_recvmmsg(len=%zu, packets=%zu, calls=%zu) -> %d", |
| 532 | total_nread, pkts, calls, result); |
| 533 | Curl_multi_xfer_sockbuf_release(data, sockbuf); |
| 534 | return result; |
| 535 | } |
| 684 | CURLcode vquic_recv_packets(struct Curl_cfilter *cf, |
| C3: vquic_recv_packets is the entry point for receiving QUIC packets from the peer | |
| C4: vquic_recv_packets runs the busy loop; control does not return to the caller until the loop exits | |
| 685 | struct Curl_easy *data, |
| 686 | struct cf_quic_ctx *qctx, |
| 687 | size_t max_pkts, |
| 688 | vquic_recv_pkts_cb *recv_cb, void *userp) |
| 689 | { |
| 690 | CURLcode result; |
| 691 | #ifdef HAVE_SENDMMSG |
| 692 | result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); |
| C2: vquic_recv_packets calls recvmmsg_packets when HAVE_SENDMMSG is defined | |
| 693 | #elif defined(HAVE_SENDMSG) |
| 694 | result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); |
| C2: vquic_recv_packets calls recvmsg_packets when HAVE_SENDMSG is defined | |
| 695 | #else |
| 696 | result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp); |
| 697 | #endif |
| 698 | if(!result) { |
| 699 | if(!qctx->got_first_byte) { |
| 700 | qctx->got_first_byte = TRUE; |
| 701 | qctx->first_byte_at = qctx->last_op; |
| 702 | } |
| 703 | qctx->last_io = qctx->last_op; |
| 704 | } |
| 705 | return result; |
| 706 | } |
| 63 | #define time(x) altsvc_debugtime(x) |
| C4: time() macro shows timeout handling is at a higher level that only runs when control returns |
Show 78 lines (not cited by the judge)
| 483 | static CURLcode recvmsg_packets(struct Curl_cfilter *cf, |
| 484 | struct Curl_easy *data, |
| 485 | struct cf_quic_ctx *qctx, |
| 486 | size_t max_pkts, |
| 487 | vquic_recv_pkts_cb *recv_cb, void *userp) |
| 488 | { |
| 489 | struct iovec msg_iov; |
| 490 | struct msghdr msg; |
| 491 | uint8_t buf[64 * 1024]; |
| 492 | struct sockaddr_storage remote_addr; |
| 493 | size_t total_nread, pkts, calls; |
| 494 | ssize_t rc; |
| 495 | size_t nread; |
| 496 | char errstr[STRERROR_LEN]; |
| 497 | CURLcode result = CURLE_OK; |
| 498 | uint8_t msg_ctrl[CMSG_SPACE(sizeof(int))]; |
| 499 | size_t gso_size; |
| 500 | |
| 501 | DEBUGASSERT(max_pkts > 0); |
| 502 | for(pkts = 0, total_nread = 0, calls = 0; pkts < max_pkts;) { |
| 503 | /* fully initialise this on each call to `recvmsg()`. There seem to |
| 504 | * operating systems out there that mess with `msg_iov.iov_len`. */ |
| 505 | memset(&msg, 0, sizeof(msg)); |
| 506 | msg_iov.iov_base = buf; |
| 507 | msg_iov.iov_len = (int)sizeof(buf); |
| 508 | msg.msg_iov = &msg_iov; |
| 509 | msg.msg_iovlen = 1; |
| 510 | msg.msg_control = msg_ctrl; |
| 511 | msg.msg_name = &remote_addr; |
| 512 | msg.msg_namelen = sizeof(remote_addr); |
| 513 | msg.msg_controllen = sizeof(msg_ctrl); |
| 514 | |
| 515 | while((rc = recvmsg(qctx->sockfd, &msg, 0)) == -1 && |
| 516 | (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) |
| 517 | ; |
| 518 | if(!curlx_sztouz(rc, &nread)) { |
| 519 | if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { |
| 520 | goto out; |
| 521 | } |
| 522 | if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { |
| 523 | struct ip_quadruple ip; |
| 524 | if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip)) |
| 525 | failf(data, "QUIC: connection to %s port %u refused", |
| 526 | ip.remote_ip, ip.remote_port); |
| 527 | result = CURLE_COULDNT_CONNECT; |
| 528 | goto out; |
| 529 | } |
| 530 | curlx_strerror(SOCKERRNO, errstr, sizeof(errstr)); |
| 531 | failf(data, "QUIC: recvmsg() unexpectedly returned %zd (errno=%d; %s)", |
| 532 | rc, SOCKERRNO, errstr); |
| 533 | result = CURLE_RECV_ERROR; |
| 534 | goto out; |
| 535 | } |
| 536 | |
| 537 | total_nread += nread; |
| 538 | ++calls; |
| 539 | |
| 540 | /* A 0-length UDP packet is no QUIC packet */ |
| 541 | if(!nread) |
| 542 | continue; |
| 543 | |
| 544 | gso_size = vquic_msghdr_get_udp_gro(&msg); |
| 545 | if(gso_size == 0) |
| 546 | gso_size = nread; |
| 547 | |
| 548 | result = recv_cb(buf, nread, gso_size, |
| 549 | msg.msg_name, msg.msg_namelen, 0, userp); |
| 550 | if(result) |
| 551 | goto out; |
| 552 | pkts += (nread + gso_size - 1) / gso_size; |
| 553 | } |
| 554 | |
| 555 | out: |
| 556 | if(total_nread || result) |
| 557 | CURL_TRC_CF(data, cf, "vquic_recvmsg(len=%zu, packets=%zu, calls=%zu)" |
| 558 | " -> %d", total_nread, pkts, calls, result); |
| 559 | return result; |
| 560 | } |
Show 101 lines (not cited by the judge)
| 380 | static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, |
| 381 | struct Curl_easy *data, |
| 382 | struct cf_quic_ctx *qctx, |
| 383 | size_t max_pkts, |
| 384 | vquic_recv_pkts_cb *recv_cb, void *userp) |
| 385 | { |
| 386 | #if defined(__linux__) && defined(UDP_GRO) |
| 387 | #define MMSG_NUM 16 |
| 388 | #define UDP_GRO_CNT_MAX 64 |
| 389 | #else |
| 390 | #define MMSG_NUM 64 |
| 391 | #define UDP_GRO_CNT_MAX 1 |
| 392 | #endif |
| 393 | #define MSG_BUF_SIZE (UDP_GRO_CNT_MAX * 1500) |
| 394 | struct iovec msg_iov[MMSG_NUM]; |
| 395 | struct mmsghdr mmsg[MMSG_NUM]; |
| 396 | uint8_t msg_ctrl[MMSG_NUM * CMSG_SPACE(sizeof(int))]; |
| 397 | struct sockaddr_storage remote_addr[MMSG_NUM]; |
| 398 | size_t total_nread = 0, pkts = 0; |
| 399 | #ifdef CURLVERBOSE |
| 400 | size_t calls = 0; |
| 401 | #endif |
| 402 | int mcount, i, n; |
| 403 | char errstr[STRERROR_LEN]; |
| 404 | CURLcode result = CURLE_OK; |
| 405 | size_t gso_size; |
| 406 | char *sockbuf = NULL; |
| 407 | uint8_t (*bufs)[MSG_BUF_SIZE] = NULL; |
| 408 | |
| 409 | DEBUGASSERT(max_pkts > 0); |
| 410 | result = Curl_multi_xfer_sockbuf_borrow(data, MMSG_NUM * MSG_BUF_SIZE, |
| 411 | &sockbuf); |
| 412 | if(result) |
| 413 | goto out; |
| 414 | bufs = (uint8_t (*)[MSG_BUF_SIZE])sockbuf; |
| 415 | |
| 416 | total_nread = 0; |
| 417 | while(pkts < max_pkts) { |
| 418 | n = (int)CURLMIN(CURLMIN(MMSG_NUM, IOV_MAX), max_pkts); |
| 419 | memset(&mmsg, 0, sizeof(mmsg)); |
| 420 | for(i = 0; i < n; ++i) { |
| 421 | msg_iov[i].iov_base = bufs[i]; |
| 422 | msg_iov[i].iov_len = (int)sizeof(bufs[i]); |
| 423 | mmsg[i].msg_hdr.msg_iov = &msg_iov[i]; |
| 424 | mmsg[i].msg_hdr.msg_iovlen = 1; |
| 425 | mmsg[i].msg_hdr.msg_name = &remote_addr[i]; |
| 426 | mmsg[i].msg_hdr.msg_namelen = sizeof(remote_addr[i]); |
| 427 | mmsg[i].msg_hdr.msg_control = &msg_ctrl[i * CMSG_SPACE(sizeof(int))]; |
| 428 | mmsg[i].msg_hdr.msg_controllen = CMSG_SPACE(sizeof(int)); |
| 429 | } |
| 430 | |
| 431 | while((mcount = recvmmsg(qctx->sockfd, mmsg, n, 0, NULL)) == -1 && |
| 432 | (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) |
| 433 | ; |
| 434 | if(mcount == -1) { |
| 435 | if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { |
| 436 | CURL_TRC_CF(data, cf, "ingress, recvmmsg -> EAGAIN"); |
| 437 | goto out; |
| 438 | } |
| 439 | if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { |
| 440 | struct ip_quadruple ip; |
| 441 | if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip)) |
| 442 | failf(data, "QUIC: connection to %s port %u refused", |
| 443 | ip.remote_ip, ip.remote_port); |
| 444 | result = CURLE_COULDNT_CONNECT; |
| 445 | goto out; |
| 446 | } |
| 447 | curlx_strerror(SOCKERRNO, errstr, sizeof(errstr)); |
| 448 | failf(data, "QUIC: recvmmsg() unexpectedly returned %d (errno=%d; %s)", |
| 449 | mcount, SOCKERRNO, errstr); |
| 450 | result = CURLE_RECV_ERROR; |
| 451 | goto out; |
| 452 | } |
| 453 | |
| 454 | VERBOSE(++calls); |
| 455 | for(i = 0; i < mcount; ++i) { |
| 456 | /* A zero-length UDP packet is no QUIC packet. Ignore. */ |
| 457 | if(!mmsg[i].msg_len) |
| 458 | continue; |
| 459 | total_nread += mmsg[i].msg_len; |
| 460 | |
| 461 | gso_size = vquic_msghdr_get_udp_gro(&mmsg[i].msg_hdr); |
| 462 | if(gso_size == 0) |
| 463 | gso_size = mmsg[i].msg_len; |
| 464 | |
| 465 | result = recv_cb(bufs[i], mmsg[i].msg_len, gso_size, |
| 466 | mmsg[i].msg_hdr.msg_name, |
| 467 | mmsg[i].msg_hdr.msg_namelen, 0, userp); |
| 468 | if(result) |
| 469 | goto out; |
| 470 | pkts += (mmsg[i].msg_len + gso_size - 1) / gso_size; |
| 471 | } |
| 472 | } |
| 473 | |
| 474 | out: |
| 475 | if(total_nread || result) |
| 476 | CURL_TRC_CF(data, cf, "vquic_recvmmsg(len=%zu, packets=%zu, calls=%zu)" |
| 477 | " -> %d", total_nread, pkts, calls, result); |
| 478 | Curl_multi_xfer_sockbuf_release(data, sockbuf); |
| 479 | return result; |
| 480 | } |
Show 63 lines (not cited by the judge)
| 619 | static CURLcode recvfrom_packets(struct Curl_cfilter *cf, |
| 620 | struct Curl_easy *data, |
| 621 | struct cf_quic_ctx *qctx, |
| 622 | size_t max_pkts, |
| 623 | vquic_recv_pkts_cb *recv_cb, void *userp) |
| 624 | { |
| 625 | uint8_t buf[64 * 1024]; |
| 626 | int bufsize = (int)sizeof(buf); |
| 627 | struct sockaddr_storage remote_addr; |
| 628 | socklen_t remote_addrlen = sizeof(remote_addr); |
| 629 | size_t total_nread, pkts, calls = 0, nread; |
| 630 | ssize_t rv; |
| 631 | char errstr[STRERROR_LEN]; |
| 632 | CURLcode result = CURLE_OK; |
| 633 | |
| 634 | DEBUGASSERT(max_pkts > 0); |
| 635 | for(pkts = 0, total_nread = 0; pkts < max_pkts;) { |
| 636 | while((rv = recvfrom(qctx->sockfd, (char *)buf, bufsize, 0, |
| 637 | (struct sockaddr *)&remote_addr, |
| 638 | &remote_addrlen)) == -1 && |
| 639 | (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) |
| 640 | ; |
| 641 | if(!curlx_sztouz(rv, &nread)) { |
| 642 | if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { |
| 643 | CURL_TRC_CF(data, cf, "ingress, recvfrom -> EAGAIN"); |
| 644 | goto out; |
| 645 | } |
| 646 | if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { |
| 647 | struct ip_quadruple ip; |
| 648 | if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip)) |
| 649 | failf(data, "QUIC: connection to %s port %u refused", |
| 650 | ip.remote_ip, ip.remote_port); |
| 651 | result = CURLE_COULDNT_CONNECT; |
| 652 | goto out; |
| 653 | } |
| 654 | curlx_strerror(SOCKERRNO, errstr, sizeof(errstr)); |
| 655 | failf(data, "QUIC: recvfrom() unexpectedly returned %zd (errno=%d; %s)", |
| 656 | rv, SOCKERRNO, errstr); |
| 657 | result = CURLE_RECV_ERROR; |
| 658 | goto out; |
| 659 | } |
| 660 | |
| 661 | ++pkts; |
| 662 | ++calls; |
| 663 | |
| 664 | /* A 0-length UDP packet is no QUIC packet */ |
| 665 | if(!nread) |
| 666 | continue; |
| 667 | |
| 668 | total_nread += nread; |
| 669 | result = recv_cb(buf, nread, nread, &remote_addr, remote_addrlen, |
| 670 | 0, userp); |
| 671 | if(result) |
| 672 | goto out; |
| 673 | } |
| 674 | |
| 675 | out: |
| 676 | if(total_nread || result) |
| 677 | CURL_TRC_CF(data, cf, |
| 678 | "vquic_recvfrom(len=%zu, packets=%zu, calls=%zu) -> %d", |
| 679 | total_nread, pkts, calls, result); |
| 680 | return result; |
| 681 | } |
Show 62 lines (not cited by the judge)
| 563 | static CURLcode recvfrom_packets(struct Curl_cfilter *cf, |
| 564 | struct Curl_easy *data, |
| 565 | struct cf_quic_ctx *qctx, |
| 566 | size_t max_pkts, |
| 567 | vquic_recv_pkts_cb *recv_cb, void *userp) |
| 568 | { |
| 569 | uint8_t buf[64 * 1024]; |
| 570 | int bufsize = (int)sizeof(buf); |
| 571 | struct sockaddr_storage remote_addr; |
| 572 | socklen_t remote_addrlen = sizeof(remote_addr); |
| 573 | size_t total_nread, pkts, calls = 0, nread; |
| 574 | ssize_t rv; |
| 575 | char errstr[STRERROR_LEN]; |
| 576 | CURLcode result = CURLE_OK; |
| 577 | |
| 578 | DEBUGASSERT(max_pkts > 0); |
| 579 | for(pkts = 0, total_nread = 0; pkts < max_pkts;) { |
| 580 | while((rv = recvfrom(qctx->sockfd, (char *)buf, bufsize, 0, |
| 581 | (struct sockaddr *)&remote_addr, |
| 582 | &remote_addrlen)) == -1 && |
| 583 | (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) |
| 584 | ; |
| 585 | if(!curlx_sztouz(rv, &nread)) { |
| 586 | if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { |
| 587 | CURL_TRC_CF(data, cf, "ingress, recvfrom -> EAGAIN"); |
| 588 | goto out; |
| 589 | } |
| 590 | if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { |
| 591 | struct ip_quadruple ip; |
| 592 | if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip)) |
| 593 | failf(data, "QUIC: connection to %s port %u refused", |
| 594 | ip.remote_ip, ip.remote_port); |
| 595 | result = CURLE_COULDNT_CONNECT; |
| 596 | goto out; |
| 597 | } |
| 598 | curlx_strerror(SOCKERRNO, errstr, sizeof(errstr)); |
| 599 | failf(data, "QUIC: recvfrom() unexpectedly returned %zd (errno=%d; %s)", |
| 600 | rv, SOCKERRNO, errstr); |
| 601 | result = CURLE_RECV_ERROR; |
| 602 | goto out; |
| 603 | } |
| 604 | |
| 605 | ++pkts; |
| 606 | ++calls; |
| 607 | |
| 608 | /* A 0-length UDP packet is no QUIC packet */ |
| 609 | if(!nread) |
| 610 | continue; |
| 611 | |
| 612 | total_nread += nread; |
| 613 | result = recv_cb(buf, nread, nread, &remote_addr, remote_addrlen, |
| 614 | 0, userp); |
| 615 | if(result) |
| 616 | goto out; |
| 617 | } |
| 618 | |
| 619 | out: |
| 620 | if(total_nread || result) |
| 621 | CURL_TRC_CF(data, cf, "vquic_recvfrom(len=%zu, packets=%zu, calls=%zu)" |
| 622 | " -> %d", total_nread, pkts, calls, result); |
| 623 | return result; |
| 624 | } |
Show 42 lines (not cited by the judge)
| 84 | while(<CURL_H>) { |
| 85 | my $l = $_; |
| 86 | if($fl) { |
| 87 | # continued deprecation |
| 88 | if($l =~ /(.*)\),/) { |
| 89 | $fl .= $1; |
| 90 | |
| 91 | # the end |
| 92 | my @p=split(/, */, $fl); |
| 93 | add($p[0], $p[1], $p[2]); |
| 94 | undef $fl; |
| 95 | } |
| 96 | else { |
| 97 | # another line to append |
| 98 | chomp $l; |
| 99 | $fl .= $l; |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | if(/^ *CURLOPTDEPRECATED\((.*)/) { |
| 104 | $fl = $1; |
| 105 | chomp $fl; |
| 106 | } |
| 107 | |
| 108 | if(/^ *CURLOPT\(([^,]*), ([^,]*), (\d+)\)/) { |
| 109 | my($opt, $type, $num)=($1,$2,$3); |
| 110 | add($opt, $type, $num); |
| 111 | } |
| 112 | |
| 113 | # alias for an older option |
| 114 | # old = new |
| 115 | if(/^#define (CURLOPT_[^ ]*) *(CURLOPT_\S*)/) { |
| 116 | my ($o, $n)=($1, $2); |
| 117 | # skip obsolete ones |
| 118 | if(($n !~ /OBSOLETE/) && ($o !~ /OBSOLETE/)) { |
| 119 | $o =~ s/^CURLOPT_//; |
| 120 | $n =~ s/^CURLOPT_//; |
| 121 | $alias{$o} = $n; |
| 122 | push @names, $o; |
| 123 | } |
| 124 | } |
| 125 | } |