Coverage Report

Created: 2026-04-12 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/vquic/vquic.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
#include "urldata.h"
26
#include "vquic/vquic.h"
27
28
#include "curl_trc.h"
29
30
#if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3)
31
32
#ifdef HAVE_NETINET_UDP_H
33
#include <netinet/udp.h>
34
#endif
35
36
#ifdef USE_NGHTTP3
37
#include <nghttp3/nghttp3.h>
38
#endif
39
40
#include "bufq.h"
41
#include "curlx/dynbuf.h"
42
#include "curlx/fopen.h"
43
#include "cfilters.h"
44
#include "vquic/curl_ngtcp2.h"
45
#include "vquic/curl_quiche.h"
46
#include "multiif.h"
47
#include "progress.h"
48
#include "rand.h"
49
#include "vquic/vquic_int.h"
50
#include "curlx/strerr.h"
51
#include "curlx/strparse.h"
52
53
54
#define NW_CHUNK_SIZE     (64 * 1024)
55
#define NW_SEND_CHUNKS    1
56
57
int Curl_vquic_init(void)
58
{
59
#if defined(USE_NGTCP2) && defined(OPENSSL_QUIC_API2)
60
  if(ngtcp2_crypto_ossl_init())
61
    return 0;
62
#endif
63
64
  return 1;
65
}
66
67
void Curl_quic_ver(char *p, size_t len)
68
{
69
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
70
  Curl_ngtcp2_ver(p, len);
71
#elif defined(USE_QUICHE)
72
  Curl_quiche_ver(p, len);
73
#endif
74
}
75
76
CURLcode vquic_ctx_init(struct Curl_easy *data,
77
                        struct cf_quic_ctx *qctx)
78
{
79
  Curl_bufq_init2(&qctx->sendbuf, NW_CHUNK_SIZE, NW_SEND_CHUNKS,
80
                  BUFQ_OPT_SOFT_LIMIT);
81
#if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG)
82
  qctx->no_gso = FALSE;
83
#else
84
  qctx->no_gso = TRUE;
85
#endif
86
#ifdef DEBUGBUILD
87
  {
88
    const char *p = getenv("CURL_DBG_QUIC_WBLOCK");
89
    if(p) {
90
      curl_off_t l;
91
      if(!curlx_str_number(&p, &l, 100))
92
        qctx->wblock_percent = (int)l;
93
    }
94
  }
95
#endif
96
  vquic_ctx_set_time(qctx, Curl_pgrs_now(data));
97
98
  return CURLE_OK;
99
}
100
101
void vquic_ctx_free(struct cf_quic_ctx *qctx)
102
{
103
  Curl_bufq_free(&qctx->sendbuf);
104
}
105
106
void vquic_ctx_set_time(struct cf_quic_ctx *qctx,
107
                        const struct curltime *pnow)
108
{
109
  qctx->last_op = *pnow;
110
}
111
112
void vquic_ctx_update_time(struct cf_quic_ctx *qctx,
113
                           const struct curltime *pnow)
114
{
115
  qctx->last_op = *pnow;
116
}
117
118
static CURLcode send_packet_no_gso(struct Curl_cfilter *cf,
119
                                   struct Curl_easy *data,
120
                                   struct cf_quic_ctx *qctx,
121
                                   const uint8_t *pkt, size_t pktlen,
122
                                   size_t gsolen, size_t *psent);
123
124
static CURLcode do_sendmsg(struct Curl_cfilter *cf,
125
                           struct Curl_easy *data,
126
                           struct cf_quic_ctx *qctx,
127
                           const uint8_t *pkt, size_t pktlen, size_t gsolen,
128
                           size_t *psent)
129
{
130
  CURLcode result = CURLE_OK;
131
#ifdef HAVE_SENDMSG
132
  struct iovec msg_iov;
133
  struct msghdr msg = { 0 };
134
  ssize_t rv;
135
#if defined(__linux__) && defined(UDP_SEGMENT)
136
  uint8_t msg_ctrl[32];
137
  struct cmsghdr *cm;
138
#endif
139
140
  *psent = 0;
141
  msg_iov.iov_base = (uint8_t *)CURL_UNCONST(pkt);
142
  msg_iov.iov_len = pktlen;
143
  msg.msg_iov = &msg_iov;
144
  msg.msg_iovlen = 1;
145
146
#if defined(__linux__) && defined(UDP_SEGMENT)
147
  if(pktlen > gsolen) {
148
    /* Only set this, when we need it. macOS, for example,
149
     * does not seem to like a msg_control of length 0. */
150
    memset(msg_ctrl, 0, sizeof(msg_ctrl));
151
    msg.msg_control = msg_ctrl;
152
    assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(int)));
153
    msg.msg_controllen = CMSG_SPACE(sizeof(int));
154
    cm = CMSG_FIRSTHDR(&msg);
155
    cm->cmsg_level = SOL_UDP;
156
    cm->cmsg_type = UDP_SEGMENT;
157
    cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
158
    *(uint16_t *)(void *)CMSG_DATA(cm) = gsolen & 0xffff;
159
  }
160
#endif
161
162
  while((rv = sendmsg(qctx->sockfd, &msg, 0)) == -1 && SOCKERRNO == SOCKEINTR)
163
    ;
164
165
  if(!curlx_sztouz(rv, psent)) {
166
    switch(SOCKERRNO) {
167
    case EAGAIN:
168
#if EAGAIN != SOCKEWOULDBLOCK
169
    case SOCKEWOULDBLOCK:
170
#endif
171
      return CURLE_AGAIN;
172
    case SOCKEMSGSIZE:
173
      /* UDP datagram is too large; caused by PMTUD. Let it be lost. */
174
      *psent = pktlen;
175
      break;
176
    case EIO:
177
      if(pktlen > gsolen) {
178
        /* GSO failure */
179
        infof(data, "sendmsg() returned %zd (errno %d); disable GSO", rv,
180
              SOCKERRNO);
181
        qctx->no_gso = TRUE;
182
        return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent);
183
      }
184
      FALLTHROUGH();
185
    default:
186
      failf(data, "sendmsg() returned %zd (errno %d)", rv, SOCKERRNO);
187
      result = CURLE_SEND_ERROR;
188
      goto out;
189
    }
190
  }
191
  else if(pktlen != *psent) {
192
    failf(data, "sendmsg() sent only %zu/%zu bytes", *psent, pktlen);
193
    result = CURLE_SEND_ERROR;
194
    goto out;
195
  }
196
#else
197
  ssize_t rv;
198
  (void)gsolen;
199
200
  *psent = 0;
201
202
  while((rv = swrite(qctx->sockfd, pkt, pktlen)) == -1 &&
203
        SOCKERRNO == SOCKEINTR)
204
    ;
205
206
  if(!curlx_sztouz(rv, psent)) {
207
    if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) {
208
      result = CURLE_AGAIN;
209
      goto out;
210
    }
211
    else {
212
      if(SOCKERRNO != SOCKEMSGSIZE) {
213
        failf(data, "send() returned %zd (errno %d)", rv, SOCKERRNO);
214
        result = CURLE_SEND_ERROR;
215
        goto out;
216
      }
217
      /* UDP datagram is too large; caused by PMTUD. Let it be lost. */
218
      *psent = pktlen;
219
    }
220
  }
221
#endif
222
  (void)cf;
223
224
out:
225
  return result;
226
}
227
228
#ifdef CURLVERBOSE
229
#ifdef HAVE_SENDMSG
230
#define VQUIC_SEND_METHOD   "sendmsg"
231
#else
232
#define VQUIC_SEND_METHOD   "send"
233
#endif
234
#endif
235
236
static CURLcode send_packet_no_gso(struct Curl_cfilter *cf,
237
                                   struct Curl_easy *data,
238
                                   struct cf_quic_ctx *qctx,
239
                                   const uint8_t *pkt, size_t pktlen,
240
                                   size_t gsolen, size_t *psent)
241
{
242
  const uint8_t *p, *end = pkt + pktlen;
243
  size_t sent, len;
244
  CURLcode result = CURLE_OK;
245
  VERBOSE(size_t calls = 0);
246
247
  *psent = 0;
248
249
  for(p = pkt; p < end; p += gsolen) {
250
    len = CURLMIN(gsolen, (size_t)(end - p));
251
    result = do_sendmsg(cf, data, qctx, p, len, len, &sent);
252
    if(result)
253
      goto out;
254
    *psent += sent;
255
    VERBOSE(++calls);
256
  }
257
out:
258
  CURL_TRC_CF(data, cf, "vquic_%s(len=%zu, gso=%zu, calls=%zu)"
259
              " -> %d, sent=%zu",
260
              VQUIC_SEND_METHOD, pktlen, gsolen, calls, result, *psent);
261
  return result;
262
}
263
264
static CURLcode vquic_send_packets(struct Curl_cfilter *cf,
265
                                   struct Curl_easy *data,
266
                                   struct cf_quic_ctx *qctx,
267
                                   const uint8_t *pkt, size_t pktlen,
268
                                   size_t gsolen, size_t *psent)
269
{
270
  CURLcode result;
271
#ifdef DEBUGBUILD
272
  /* simulate network blocking/partial writes */
273
  if(qctx->wblock_percent > 0) {
274
    unsigned char c;
275
    *psent = 0;
276
    Curl_rand(data, &c, 1);
277
    if(c >= ((100 - qctx->wblock_percent) * 256 / 100)) {
278
      CURL_TRC_CF(data, cf, "vquic_flush() simulate EWOULDBLOCK");
279
      return CURLE_AGAIN;
280
    }
281
  }
282
#endif
283
  if(qctx->no_gso && pktlen > gsolen) {
284
    result = send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent);
285
  }
286
  else {
287
    result = do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent);
288
    CURL_TRC_CF(data, cf, "vquic_%s(len=%zu, gso=%zu, calls=1)"
289
                " -> %d, sent=%zu",
290
                VQUIC_SEND_METHOD, pktlen, gsolen, result, *psent);
291
  }
292
  if(!result)
293
    qctx->last_io = qctx->last_op;
294
  return result;
295
}
296
297
CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data,
298
                     struct cf_quic_ctx *qctx)
299
{
300
  const unsigned char *buf;
301
  size_t blen, sent;
302
  CURLcode result;
303
  size_t gsolen;
304
305
  while(Curl_bufq_peek(&qctx->sendbuf, &buf, &blen)) {
306
    gsolen = qctx->gsolen;
307
    if(qctx->split_len) {
308
      gsolen = qctx->split_gsolen;
309
      if(blen > qctx->split_len)
310
        blen = qctx->split_len;
311
    }
312
313
    result = vquic_send_packets(cf, data, qctx, buf, blen, gsolen, &sent);
314
    if(result) {
315
      if(result == CURLE_AGAIN) {
316
        Curl_bufq_skip(&qctx->sendbuf, sent);
317
        if(qctx->split_len)
318
          qctx->split_len -= sent;
319
      }
320
      return result;
321
    }
322
    Curl_bufq_skip(&qctx->sendbuf, sent);
323
    if(qctx->split_len)
324
      qctx->split_len -= sent;
325
  }
326
  return CURLE_OK;
327
}
328
329
CURLcode vquic_send(struct Curl_cfilter *cf, struct Curl_easy *data,
330
                    struct cf_quic_ctx *qctx, size_t gsolen)
331
{
332
  qctx->gsolen = gsolen;
333
  return vquic_flush(cf, data, qctx);
334
}
335
336
CURLcode vquic_send_tail_split(struct Curl_cfilter *cf, struct Curl_easy *data,
337
                               struct cf_quic_ctx *qctx, size_t gsolen,
338
                               size_t tail_len, size_t tail_gsolen)
339
{
340
  DEBUGASSERT(Curl_bufq_len(&qctx->sendbuf) > tail_len);
341
  qctx->split_len = Curl_bufq_len(&qctx->sendbuf) - tail_len;
342
  qctx->split_gsolen = gsolen;
343
  qctx->gsolen = tail_gsolen;
344
  CURL_TRC_CF(data, cf, "vquic_send_tail_split: [%zu gso=%zu][%zu gso=%zu]",
345
              qctx->split_len, qctx->split_gsolen, tail_len, qctx->gsolen);
346
  return vquic_flush(cf, data, qctx);
347
}
348
349
#if defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG)
350
static size_t vquic_msghdr_get_udp_gro(struct msghdr *msg)
351
{
352
  int gso_size = 0;
353
#if defined(__linux__) && defined(UDP_GRO)
354
  struct cmsghdr *cmsg;
355
356
  /* Workaround musl CMSG_NXTHDR issue */
357
#if defined(__clang__) && !defined(__GLIBC__)
358
#pragma clang diagnostic push
359
#pragma clang diagnostic ignored "-Wsign-compare"
360
#pragma clang diagnostic ignored "-Wcast-align"
361
#endif
362
  for(cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
363
#if defined(__clang__) && !defined(__GLIBC__)
364
#pragma clang diagnostic pop
365
#endif
366
    if(cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) {
367
      memcpy(&gso_size, CMSG_DATA(cmsg), sizeof(gso_size));
368
369
      break;
370
    }
371
  }
372
#endif
373
  (void)msg;
374
375
  return (size_t)gso_size;
376
}
377
#endif
378
379
#ifdef HAVE_SENDMMSG
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
}
481
482
#elif defined(HAVE_SENDMSG)
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
}
561
562
#else /* HAVE_SENDMMSG || HAVE_SENDMSG */
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
}
625
#endif /* !HAVE_SENDMMSG && !HAVE_SENDMSG */
626
627
CURLcode vquic_recv_packets(struct Curl_cfilter *cf,
628
                            struct Curl_easy *data,
629
                            struct cf_quic_ctx *qctx,
630
                            size_t max_pkts,
631
                            vquic_recv_pkts_cb *recv_cb, void *userp)
632
{
633
  CURLcode result;
634
#ifdef HAVE_SENDMMSG
635
  result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp);
636
#elif defined(HAVE_SENDMSG)
637
  result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp);
638
#else
639
  result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp);
640
#endif
641
  if(!result) {
642
    if(!qctx->got_first_byte) {
643
      qctx->got_first_byte = TRUE;
644
      qctx->first_byte_at = qctx->last_op;
645
    }
646
    qctx->last_io = qctx->last_op;
647
  }
648
  return result;
649
}
650
651
/*
652
 * If the QLOGDIR environment variable is set, open and return a file
653
 * descriptor to write the log to.
654
 *
655
 * This function returns error if something failed outside of failing to
656
 * create the file. Open file success is deemed by seeing if the returned fd
657
 * is != -1.
658
 */
659
CURLcode Curl_qlogdir(struct Curl_easy *data,
660
                      unsigned char *scid,
661
                      size_t scidlen,
662
                      int *qlogfdp)
663
{
664
  char *qlog_dir = curl_getenv("QLOGDIR");
665
  *qlogfdp = -1;
666
  if(qlog_dir) {
667
    struct dynbuf fname;
668
    CURLcode result;
669
    unsigned int i;
670
    curlx_dyn_init(&fname, DYN_QLOG_NAME);
671
    result = curlx_dyn_add(&fname, qlog_dir);
672
    if(!result)
673
      result = curlx_dyn_add(&fname, "/");
674
    for(i = 0; (i < scidlen) && !result; i++) {
675
      char hex[3];
676
      curl_msnprintf(hex, 3, "%02x", scid[i]);
677
      result = curlx_dyn_add(&fname, hex);
678
    }
679
    if(!result)
680
      result = curlx_dyn_add(&fname, ".sqlog");
681
682
    if(!result) {
683
      int qlogfd = curlx_open(curlx_dyn_ptr(&fname),
684
                              O_WRONLY | O_CREAT | CURL_O_BINARY,
685
                              data->set.new_file_perms
686
#ifdef _WIN32
687
                              & (_S_IREAD | _S_IWRITE)
688
#endif
689
                              );
690
      if(qlogfd != -1)
691
        *qlogfdp = qlogfd;
692
    }
693
    curlx_dyn_free(&fname);
694
    curlx_free(qlog_dir);
695
    if(result)
696
      return result;
697
  }
698
699
  return CURLE_OK;
700
}
701
702
CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
703
                             struct Curl_easy *data,
704
                             struct connectdata *conn,
705
                             struct Curl_sockaddr_ex *addr,
706
                             uint8_t transport)
707
{
708
  (void)transport;
709
  DEBUGASSERT(transport == TRNSPRT_QUIC);
710
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
711
  return Curl_cf_ngtcp2_create(pcf, data, conn, addr);
712
#elif defined(USE_QUICHE)
713
  return Curl_cf_quiche_create(pcf, data, conn, addr);
714
#else
715
  *pcf = NULL;
716
  (void)data;
717
  (void)conn;
718
  (void)addr;
719
  return CURLE_NOT_BUILT_IN;
720
#endif
721
}
722
723
CURLcode Curl_conn_may_http3(struct Curl_easy *data,
724
                             const struct connectdata *conn,
725
                             unsigned char transport)
726
{
727
  if(transport == TRNSPRT_UNIX) {
728
    failf(data, "HTTP/3 cannot be used over UNIX domain sockets");
729
    return CURLE_QUIC_CONNECT_ERROR;
730
  }
731
  if(!(conn->scheme->flags & PROTOPT_SSL)) {
732
    failf(data, "HTTP/3 requested for non-HTTPS URL");
733
    return CURLE_URL_MALFORMAT;
734
  }
735
#ifndef CURL_DISABLE_PROXY
736
  if(conn->bits.socksproxy) {
737
    failf(data, "HTTP/3 is not supported over a SOCKS proxy");
738
    return CURLE_URL_MALFORMAT;
739
  }
740
  if(conn->bits.httpproxy && conn->bits.tunnel_proxy) {
741
    failf(data, "HTTP/3 is not supported over an HTTP proxy");
742
    return CURLE_URL_MALFORMAT;
743
  }
744
#endif
745
746
  return CURLE_OK;
747
}
748
749
#ifdef CURLVERBOSE
750
const char *vquic_h3_err_str(uint64_t error_code)
751
{
752
  if(error_code <= UINT_MAX) {
753
    switch((unsigned int)error_code) {
754
    case CURL_H3_ERR_NO_ERROR:
755
      return "NO_ERROR";
756
    case CURL_H3_ERR_GENERAL_PROTOCOL_ERROR:
757
      return "GENERAL_PROTOCOL_ERROR";
758
    case CURL_H3_ERR_INTERNAL_ERROR:
759
      return "INTERNAL_ERROR";
760
    case CURL_H3_ERR_STREAM_CREATION_ERROR:
761
      return "STREAM_CREATION_ERROR";
762
    case CURL_H3_ERR_CLOSED_CRITICAL_STREAM:
763
      return "CLOSED_CRITICAL_STREAM";
764
    case CURL_H3_ERR_FRAME_UNEXPECTED:
765
      return "FRAME_UNEXPECTED";
766
    case CURL_H3_ERR_FRAME_ERROR:
767
      return "FRAME_ERROR";
768
    case CURL_H3_ERR_EXCESSIVE_LOAD:
769
      return "EXCESSIVE_LOAD";
770
    case CURL_H3_ERR_ID_ERROR:
771
      return "ID_ERROR";
772
    case CURL_H3_ERR_SETTINGS_ERROR:
773
      return "SETTINGS_ERROR";
774
    case CURL_H3_ERR_MISSING_SETTINGS:
775
      return "MISSING_SETTINGS";
776
    case CURL_H3_ERR_REQUEST_REJECTED:
777
      return "REQUEST_REJECTED";
778
    case CURL_H3_ERR_REQUEST_CANCELLED:
779
      return "REQUEST_CANCELLED";
780
    case CURL_H3_ERR_REQUEST_INCOMPLETE:
781
      return "REQUEST_INCOMPLETE";
782
    case CURL_H3_ERR_MESSAGE_ERROR:
783
      return "MESSAGE_ERROR";
784
    case CURL_H3_ERR_CONNECT_ERROR:
785
      return "CONNECT_ERROR";
786
    case CURL_H3_ERR_VERSION_FALLBACK:
787
      return "VERSION_FALLBACK";
788
    default:
789
      break;
790
    }
791
  }
792
  /* RFC 9114 ch. 8.1 + 9, reserved future error codes that are NO_ERROR */
793
  if((error_code >= 0x21) && !((error_code - 0x21) % 0x1f))
794
    return "NO_ERROR";
795
  return "unknown";
796
}
797
#endif /* CURLVERBOSE */
798
799
#if defined(USE_NGTCP2) || defined(USE_NGHTTP3)
800
801
static void *vquic_ngtcp2_malloc(size_t size, void *user_data)
802
{
803
  (void)user_data;
804
  return Curl_cmalloc(size);
805
}
806
807
static void vquic_ngtcp2_free(void *ptr, void *user_data)
808
{
809
  (void)user_data;
810
  Curl_cfree(ptr);
811
}
812
813
static void *vquic_ngtcp2_calloc(size_t nmemb, size_t size, void *user_data)
814
{
815
  (void)user_data;
816
  return Curl_ccalloc(nmemb, size);
817
}
818
819
static void *vquic_ngtcp2_realloc(void *ptr, size_t size, void *user_data)
820
{
821
  (void)user_data;
822
  return Curl_crealloc(ptr, size);
823
}
824
825
#ifdef USE_NGTCP2
826
static struct ngtcp2_mem vquic_ngtcp2_mem = {
827
  NULL,
828
  vquic_ngtcp2_malloc,
829
  vquic_ngtcp2_free,
830
  vquic_ngtcp2_calloc,
831
  vquic_ngtcp2_realloc
832
};
833
struct ngtcp2_mem *Curl_ngtcp2_mem(void)
834
{
835
  return &vquic_ngtcp2_mem;
836
}
837
#endif
838
839
#ifdef USE_NGHTTP3
840
static struct nghttp3_mem vquic_nghttp3_mem = {
841
  NULL,
842
  vquic_ngtcp2_malloc,
843
  vquic_ngtcp2_free,
844
  vquic_ngtcp2_calloc,
845
  vquic_ngtcp2_realloc
846
};
847
struct nghttp3_mem *Curl_nghttp3_mem(void)
848
{
849
  return &vquic_nghttp3_mem;
850
}
851
#endif
852
853
#endif /* USE_NGTCP2 || USE_NGHTTP3 */
854
855
#else /* CURL_DISABLE_HTTP || !USE_HTTP3 */
856
857
CURLcode Curl_conn_may_http3(struct Curl_easy *data,
858
                             const struct connectdata *conn,
859
                             unsigned char transport)
860
0
{
861
0
  (void)data;
862
0
  (void)conn;
863
0
  (void)transport;
864
0
  DEBUGF(infof(data, "QUIC is not supported in this build"));
865
0
  return CURLE_NOT_BUILT_IN;
866
0
}
867
868
#endif /* !CURL_DISABLE_HTTP && USE_HTTP3 */