Coverage Report

Created: 2026-07-14 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Utilities/cmcurl/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/cf-ngtcp2.h"
45
#include "vquic/cf-ngtcp2-cmn.h"
46
#include "vquic/cf-ngtcp2-proxy.h"
47
#include "vquic/cf-quiche.h"
48
#include "multiif.h"
49
#include "progress.h"
50
#include "rand.h"
51
#include "vquic/vquic_int.h"
52
#include "curlx/strerr.h"
53
#include "curlx/strparse.h"
54
55
56
#define NW_CHUNK_SIZE     (64 * 1024)
57
#define NW_SEND_CHUNKS    1
58
59
int Curl_vquic_init(void)
60
{
61
#if defined(USE_NGTCP2) && defined(OPENSSL_QUIC_API2)
62
  if(ngtcp2_crypto_ossl_init())
63
    return 0;
64
#endif
65
66
  return 1;
67
}
68
69
void Curl_quic_ver(char *p, size_t len)
70
{
71
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
72
  Curl_ngtcp2_ver(p, len);
73
#elif defined(USE_QUICHE)
74
  Curl_quiche_ver(p, len);
75
#endif
76
}
77
78
CURLcode vquic_ctx_init(struct Curl_easy *data,
79
                        struct cf_quic_ctx *qctx)
80
{
81
  Curl_bufq_init2(&qctx->sendbuf, NW_CHUNK_SIZE, NW_SEND_CHUNKS,
82
                  BUFQ_OPT_SOFT_LIMIT);
83
#if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG)
84
  qctx->no_gso = FALSE;
85
#else
86
  qctx->no_gso = TRUE;
87
#endif
88
#ifdef DEBUGBUILD
89
  {
90
    const char *p = getenv("CURL_DBG_QUIC_WBLOCK");
91
    if(p) {
92
      curl_off_t l;
93
      if(!curlx_str_number(&p, &l, 100))
94
        qctx->wblock_percent = (int)l;
95
    }
96
  }
97
#endif
98
  vquic_ctx_set_time(qctx, Curl_pgrs_now(data));
99
100
  return CURLE_OK;
101
}
102
103
void vquic_ctx_free(struct cf_quic_ctx *qctx)
104
{
105
  Curl_bufq_free(&qctx->sendbuf);
106
}
107
108
void vquic_ctx_set_time(struct cf_quic_ctx *qctx,
109
                        const struct curltime *pnow)
110
{
111
  qctx->last_op = *pnow;
112
}
113
114
void vquic_ctx_update_time(struct cf_quic_ctx *qctx,
115
                           const struct curltime *pnow)
116
{
117
  qctx->last_op = *pnow;
118
}
119
120
static CURLcode send_packet_no_gso(struct Curl_cfilter *cf,
121
                                   struct Curl_easy *data,
122
                                   struct cf_quic_ctx *qctx,
123
                                   const uint8_t *pkt, size_t pktlen,
124
                                   size_t gsolen, size_t *psent);
125
126
static CURLcode do_sendmsg(struct Curl_cfilter *cf,
127
                           struct Curl_easy *data,
128
                           struct cf_quic_ctx *qctx,
129
                           const uint8_t *pkt, size_t pktlen, size_t gsolen,
130
                           size_t *psent)
131
{
132
  CURLcode result = CURLE_OK;
133
#ifdef HAVE_SENDMSG
134
  struct iovec msg_iov;
135
  struct msghdr msg = { 0 };
136
  ssize_t rv;
137
#if defined(__linux__) && defined(UDP_SEGMENT)
138
  uint8_t msg_ctrl[32];
139
  struct cmsghdr *cm;
140
#endif
141
142
  *psent = 0;
143
  msg_iov.iov_base = (uint8_t *)CURL_UNCONST(pkt);
144
  msg_iov.iov_len = pktlen;
145
  msg.msg_iov = &msg_iov;
146
  msg.msg_iovlen = 1;
147
148
#if defined(__linux__) && defined(UDP_SEGMENT)
149
  if(pktlen > gsolen) {
150
    /* Only set this, when we need it. macOS, for example,
151
     * does not seem to like a msg_control of length 0. */
152
    memset(msg_ctrl, 0, sizeof(msg_ctrl));
153
    msg.msg_control = msg_ctrl;
154
    assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(int)));
155
    msg.msg_controllen = CMSG_SPACE(sizeof(int));
156
    cm = CMSG_FIRSTHDR(&msg);
157
    cm->cmsg_level = SOL_UDP;
158
    cm->cmsg_type = UDP_SEGMENT;
159
    cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
160
    *(uint16_t *)(void *)CMSG_DATA(cm) = gsolen & 0xffff;
161
  }
162
#endif
163
164
  while((rv = sendmsg(qctx->sockfd, &msg, 0)) == -1 && SOCKERRNO == SOCKEINTR)
165
    ;
166
167
  if(!curlx_sztouz(rv, psent)) {
168
    int sockerr = SOCKERRNO;
169
    if(SOCK_EAGAIN(sockerr))
170
      return CURLE_AGAIN;
171
    switch(sockerr) {
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
              sockerr);
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, sockerr);
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(SOCK_EAGAIN(SOCKERRNO)) {
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,
259
              "vquic_%s(len=%zu, gso=%zu, calls=%zu) -> %d, sent=%zu",
260
              VQUIC_SEND_METHOD, pktlen, gsolen, calls, (int)result, *psent);
261
  return result;
262
}
263
264
/* Split QUIC payload by datagram (gso) boundaries when sending over a
265
 * non-UDP lower filter (for example CONNECT-UDP proxy tunnel). */
266
static CURLcode send_packet_no_gso_cf(struct Curl_cfilter *cf,
267
                                      struct Curl_easy *data,
268
                                      const uint8_t *pkt, size_t pktlen,
269
                                      size_t gsolen, size_t *psent)
270
{
271
  const uint8_t *p, *end = pkt + pktlen;
272
  size_t sent, len;
273
  CURLcode result = CURLE_OK;
274
  VERBOSE(size_t calls = 0);
275
276
  *psent = 0;
277
278
  /* Send one datagram-sized chunk per call into the lower filter. */
279
  for(p = pkt; p < end; p += len) {
280
    len = CURLMIN(gsolen, (size_t)(end - p));
281
    result = Curl_conn_cf_send(cf->next, data, p, len, FALSE, &sent);
282
    /* Report forward progress even if we return CURLE_AGAIN later. */
283
    VERBOSE(++calls);
284
    /* Preserve lower-filter errors (including CURLE_AGAIN). */
285
    if(result)
286
      goto out;
287
288
    if(sent != len) {
289
      /* We can only send the complete datagram, not parts. */
290
      result = CURLE_SEND_ERROR;
291
      goto out;
292
    }
293
    *psent += sent;
294
  }
295
296
out:
297
  CURL_TRC_CF(data, cf,
298
              "vquic_cf_send(len=%zu, gso=%zu, calls=%zu) -> %d, sent=%zu",
299
              pktlen, gsolen, calls, (int)result, *psent);
300
  return result;
301
}
302
303
static CURLcode vquic_send_packets(struct Curl_cfilter *cf,
304
                                   struct Curl_easy *data,
305
                                   struct cf_quic_ctx *qctx,
306
                                   const uint8_t *pkt, size_t pktlen,
307
                                   size_t gsolen, size_t *psent)
308
{
309
  CURLcode result;
310
#ifdef DEBUGBUILD
311
  /* simulate network blocking/partial writes */
312
  if(qctx->wblock_percent > 0) {
313
    unsigned char c;
314
    *psent = 0;
315
    Curl_rand(data, &c, 1);
316
    if(c >= ((100 - qctx->wblock_percent) * 256 / 100)) {
317
      CURL_TRC_CF(data, cf, "vquic_flush() simulate EWOULDBLOCK");
318
      return CURLE_AGAIN;
319
    }
320
  }
321
#endif
322
  if(qctx->no_gso && pktlen > gsolen) {
323
    result = send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent);
324
  }
325
  else {
326
    result = do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent);
327
    CURL_TRC_CF(data, cf,
328
                "vquic_%s(len=%zu, gso=%zu, calls=1) -> %d, sent=%zu",
329
                VQUIC_SEND_METHOD, pktlen, gsolen, (int)result, *psent);
330
  }
331
  if(!result)
332
    qctx->last_io = qctx->last_op;
333
  return result;
334
}
335
336
CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data,
337
                     struct cf_quic_ctx *qctx)
338
{
339
  const unsigned char *buf;
340
  size_t blen, sent;
341
  CURLcode result;
342
  size_t gsolen;
343
344
  while(Curl_bufq_peek(&qctx->sendbuf, &buf, &blen)) {
345
    gsolen = qctx->gsolen;
346
    if(qctx->split_len) {
347
      gsolen = qctx->split_gsolen;
348
      if(blen > qctx->split_len)
349
        blen = qctx->split_len;
350
    }
351
352
    if(qctx->sockfd != CURL_SOCKET_BAD) {
353
      /* Direct UDP socket (via happy eyeballs) */
354
      result = vquic_send_packets(cf, data, qctx, buf, blen, gsolen, &sent);
355
    }
356
    else {
357
      /* Tunneled QUIC (CONNECT-UDP through proxy) */
358
      if(gsolen && (blen > gsolen)) {
359
        /* Send one datagram at a time to preserve packet boundaries. */
360
        result = send_packet_no_gso_cf(cf, data, buf, blen, gsolen, &sent);
361
      }
362
      else {
363
        /* No GSO aggregate to split, regular lower-filter send is enough. */
364
        result = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &sent);
365
      }
366
    }
367
368
    if(result) {
369
      if(result == CURLE_AGAIN) {
370
        Curl_bufq_skip(&qctx->sendbuf, sent);
371
        if(qctx->split_len)
372
          qctx->split_len -= sent;
373
      }
374
      return result;
375
    }
376
    Curl_bufq_skip(&qctx->sendbuf, sent);
377
    if(qctx->split_len)
378
      qctx->split_len -= sent;
379
  }
380
  return CURLE_OK;
381
}
382
383
CURLcode vquic_send(struct Curl_cfilter *cf, struct Curl_easy *data,
384
                    struct cf_quic_ctx *qctx, size_t gsolen)
385
{
386
  qctx->gsolen = gsolen;
387
  return vquic_flush(cf, data, qctx);
388
}
389
390
CURLcode vquic_send_tail_split(struct Curl_cfilter *cf, struct Curl_easy *data,
391
                               struct cf_quic_ctx *qctx, size_t gsolen,
392
                               size_t tail_len, size_t tail_gsolen)
393
{
394
  DEBUGASSERT(Curl_bufq_len(&qctx->sendbuf) > tail_len);
395
  qctx->split_len = Curl_bufq_len(&qctx->sendbuf) - tail_len;
396
  qctx->split_gsolen = gsolen;
397
  qctx->gsolen = tail_gsolen;
398
  CURL_TRC_CF(data, cf, "vquic_send_tail_split: [%zu gso=%zu][%zu gso=%zu]",
399
              qctx->split_len, qctx->split_gsolen, tail_len, qctx->gsolen);
400
  return vquic_flush(cf, data, qctx);
401
}
402
403
#if defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG)
404
static size_t vquic_msghdr_get_udp_gro(struct msghdr *msg)
405
{
406
  int gso_size = 0;
407
#if defined(__linux__) && defined(UDP_GRO)
408
  struct cmsghdr *cmsg;
409
410
  /* Workaround musl CMSG_NXTHDR issue */
411
#if defined(__clang__) && !defined(__GLIBC__)
412
#pragma clang diagnostic push
413
#pragma clang diagnostic ignored "-Wsign-compare"
414
#pragma clang diagnostic ignored "-Wcast-align"
415
#endif
416
  for(cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
417
#if defined(__clang__) && !defined(__GLIBC__)
418
#pragma clang diagnostic pop
419
#endif
420
    if(cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) {
421
      memcpy(&gso_size, CMSG_DATA(cmsg), sizeof(gso_size));
422
423
      break;
424
    }
425
  }
426
#endif
427
  (void)msg;
428
429
  return (size_t)gso_size;
430
}
431
#endif
432
433
#ifdef HAVE_SENDMMSG
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 = 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(SOCK_EAGAIN(SOCKERRNO)) {
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) {
512
        ++pkts;
513
        continue;
514
      }
515
      total_nread += mmsg[i].msg_len;
516
517
      gso_size = vquic_msghdr_get_udp_gro(&mmsg[i].msg_hdr);
518
      if(gso_size == 0)
519
        gso_size = mmsg[i].msg_len;
520
521
      result = recv_cb(bufs[i], mmsg[i].msg_len, gso_size,
522
                       mmsg[i].msg_hdr.msg_name,
523
                       mmsg[i].msg_hdr.msg_namelen, 0, userp);
524
      if(result)
525
        goto out;
526
      pkts += (mmsg[i].msg_len + gso_size - 1) / gso_size;
527
    }
528
  }
529
530
out:
531
  if(total_nread || result)
532
    CURL_TRC_CF(data, cf,
533
                "vquic_recvmmsg(len=%zu, packets=%zu, calls=%zu) -> %d",
534
                total_nread, pkts, calls, (int)result);
535
  Curl_multi_xfer_sockbuf_release(data, sockbuf);
536
  return result;
537
}
538
539
#elif defined(HAVE_SENDMSG)
540
static CURLcode recvmsg_packets(struct Curl_cfilter *cf,
541
                                struct Curl_easy *data,
542
                                struct cf_quic_ctx *qctx,
543
                                size_t max_pkts,
544
                                vquic_recv_pkts_cb *recv_cb, void *userp)
545
{
546
  struct iovec msg_iov;
547
  struct msghdr msg;
548
  uint8_t buf[64 * 1024];
549
  struct sockaddr_storage remote_addr;
550
  size_t total_nread, pkts, calls;
551
  ssize_t rc;
552
  size_t nread;
553
  char errstr[STRERROR_LEN];
554
  CURLcode result = CURLE_OK;
555
  uint8_t msg_ctrl[CMSG_SPACE(sizeof(int))];
556
  size_t gso_size;
557
558
  DEBUGASSERT(max_pkts > 0);
559
  for(pkts = 0, total_nread = 0, calls = 0; pkts < max_pkts;) {
560
    /* fully initialize this on each call to `recvmsg()`. There seem to
561
     * operating systems out there that mess with `msg_iov.iov_len`. */
562
    memset(&msg, 0, sizeof(msg));
563
    msg_iov.iov_base = buf;
564
    msg_iov.iov_len = sizeof(buf);
565
    msg.msg_iov = &msg_iov;
566
    msg.msg_iovlen = 1;
567
    msg.msg_control = msg_ctrl;
568
    msg.msg_name = &remote_addr;
569
    msg.msg_namelen = sizeof(remote_addr);
570
    msg.msg_controllen = sizeof(msg_ctrl);
571
572
    while((rc = recvmsg(qctx->sockfd, &msg, 0)) == -1 &&
573
          (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE))
574
      ;
575
    if(!curlx_sztouz(rc, &nread)) {
576
      if(SOCK_EAGAIN(SOCKERRNO)) {
577
        goto out;
578
      }
579
      if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) {
580
        struct ip_quadruple ip;
581
        if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip))
582
          failf(data, "QUIC: connection to %s port %u refused",
583
                ip.remote_ip, ip.remote_port);
584
        result = CURLE_COULDNT_CONNECT;
585
        goto out;
586
      }
587
      curlx_strerror(SOCKERRNO, errstr, sizeof(errstr));
588
      failf(data, "QUIC: recvmsg() unexpectedly returned %zd (errno=%d; %s)",
589
            rc, SOCKERRNO, errstr);
590
      result = CURLE_RECV_ERROR;
591
      goto out;
592
    }
593
594
    total_nread += nread;
595
    ++calls;
596
597
    /* A 0-length UDP packet is no QUIC packet */
598
    if(!nread) {
599
      ++pkts;
600
      continue;
601
    }
602
603
    gso_size = vquic_msghdr_get_udp_gro(&msg);
604
    if(gso_size == 0)
605
      gso_size = nread;
606
607
    result = recv_cb(buf, nread, gso_size,
608
                     msg.msg_name, msg.msg_namelen, 0, userp);
609
    if(result)
610
      goto out;
611
    pkts += (nread + gso_size - 1) / gso_size;
612
  }
613
614
out:
615
  if(total_nread || result)
616
    CURL_TRC_CF(data, cf,
617
                "vquic_recvmsg(len=%zu, packets=%zu, calls=%zu) -> %d",
618
                total_nread, pkts, calls, (int)result);
619
  return result;
620
}
621
622
#else /* HAVE_SENDMMSG || HAVE_SENDMSG */
623
static CURLcode recvfrom_packets(struct Curl_cfilter *cf,
624
                                 struct Curl_easy *data,
625
                                 struct cf_quic_ctx *qctx,
626
                                 size_t max_pkts,
627
                                 vquic_recv_pkts_cb *recv_cb, void *userp)
628
{
629
  uint8_t buf[64 * 1024];
630
  int bufsize = (int)sizeof(buf);
631
  struct sockaddr_storage remote_addr;
632
  socklen_t remote_addrlen = sizeof(remote_addr);
633
  size_t total_nread, pkts, calls = 0, nread;
634
  ssize_t rv;
635
  char errstr[STRERROR_LEN];
636
  CURLcode result = CURLE_OK;
637
638
  DEBUGASSERT(max_pkts > 0);
639
  for(pkts = 0, total_nread = 0; pkts < max_pkts;) {
640
    while((rv = recvfrom(qctx->sockfd, (char *)buf, bufsize, 0,
641
                         (struct sockaddr *)&remote_addr,
642
                         &remote_addrlen)) == -1 &&
643
          (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE))
644
      ;
645
    if(!curlx_sztouz(rv, &nread)) {
646
      if(SOCK_EAGAIN(SOCKERRNO)) {
647
        CURL_TRC_CF(data, cf, "ingress, recvfrom -> EAGAIN");
648
        goto out;
649
      }
650
      if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) {
651
        struct ip_quadruple ip;
652
        if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip))
653
          failf(data, "QUIC: connection to %s port %u refused",
654
                ip.remote_ip, ip.remote_port);
655
        result = CURLE_COULDNT_CONNECT;
656
        goto out;
657
      }
658
      curlx_strerror(SOCKERRNO, errstr, sizeof(errstr));
659
      failf(data, "QUIC: recvfrom() unexpectedly returned %zd (errno=%d; %s)",
660
            rv, SOCKERRNO, errstr);
661
      result = CURLE_RECV_ERROR;
662
      goto out;
663
    }
664
665
    ++pkts;
666
    ++calls;
667
668
    /* A 0-length UDP packet is no QUIC packet */
669
    if(!nread)
670
      continue;
671
672
    total_nread += nread;
673
    result = recv_cb(buf, nread, nread, &remote_addr, remote_addrlen,
674
                     0, userp);
675
    if(result)
676
      goto out;
677
  }
678
679
out:
680
  if(total_nread || result)
681
    CURL_TRC_CF(data, cf,
682
                "vquic_recvfrom(len=%zu, packets=%zu, calls=%zu) -> %d",
683
                total_nread, pkts, calls, (int)result);
684
  return result;
685
}
686
#endif /* !HAVE_SENDMMSG && !HAVE_SENDMSG */
687
688
CURLcode vquic_recv_packets(struct Curl_cfilter *cf,
689
                            struct Curl_easy *data,
690
                            struct cf_quic_ctx *qctx,
691
                            size_t max_pkts,
692
                            vquic_recv_pkts_cb *recv_cb, void *userp)
693
{
694
  CURLcode result;
695
#ifdef HAVE_SENDMMSG
696
  result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp);
697
#elif defined(HAVE_SENDMSG)
698
  result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp);
699
#else
700
  result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp);
701
#endif
702
  if(!result) {
703
    if(!qctx->got_first_byte) {
704
      qctx->got_first_byte = TRUE;
705
      qctx->first_byte_at = qctx->last_op;
706
    }
707
    qctx->last_io = qctx->last_op;
708
  }
709
  return result;
710
}
711
712
/*
713
 * If the QLOGDIR environment variable is set, open and return a file
714
 * descriptor to write the log to.
715
 *
716
 * This function returns error if something failed outside of failing to
717
 * create the file. Open file success is deemed by seeing if the returned fd
718
 * is != -1.
719
 */
720
CURLcode Curl_qlogdir(struct Curl_easy *data,
721
                      unsigned char *scid,
722
                      size_t scidlen,
723
                      int *qlogfdp)
724
{
725
  char *qlog_dir = curl_getenv("QLOGDIR");
726
  *qlogfdp = -1;
727
  if(qlog_dir) {
728
    struct dynbuf fname;
729
    CURLcode result;
730
    unsigned int i;
731
    curlx_dyn_init(&fname, DYN_QLOG_NAME);
732
    result = curlx_dyn_add(&fname, qlog_dir);
733
    if(!result)
734
      result = curlx_dyn_add(&fname, "/");
735
    for(i = 0; (i < scidlen) && !result; i++) {
736
      char hex[3];
737
      curl_msnprintf(hex, 3, "%02x", scid[i]);
738
      result = curlx_dyn_add(&fname, hex);
739
    }
740
    if(!result)
741
      result = curlx_dyn_add(&fname, ".sqlog");
742
743
    if(!result) {
744
      int qlogfd = curlx_open(curlx_dyn_ptr(&fname),
745
                              O_WRONLY | O_CREAT | CURL_O_BINARY,
746
                              data->set.new_file_perms
747
#ifdef _WIN32
748
                              & (_S_IREAD | _S_IWRITE)
749
#endif
750
                              );
751
      if(qlogfd != -1)
752
        *qlogfdp = qlogfd;
753
    }
754
    curlx_dyn_free(&fname);
755
    curlx_free(qlog_dir);
756
    if(result)
757
      return result;
758
  }
759
760
  return CURLE_OK;
761
}
762
763
CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at,
764
                                   struct Curl_peer *origin,
765
                                   struct Curl_peer *peer)
766
{
767
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
768
  return Curl_cf_ngtcp2_insert_after(cf_at, origin, peer);
769
#elif defined(USE_QUICHE)
770
  return Curl_cf_quiche_insert_after(cf_at, origin, peer);
771
#else
772
  (void)cf_at;
773
  (void)origin;
774
  (void)peer;
775
  return CURLE_NOT_BUILT_IN;
776
#endif
777
}
778
779
CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
780
                             struct Curl_easy *data,
781
                             struct Curl_peer *origin,
782
                             struct Curl_peer *peer,
783
                             uint8_t transport_peer,
784
                             struct connectdata *conn,
785
                             struct Curl_sockaddr_ex *addr,
786
                             struct Curl_peer *tunnel_peer,
787
                             uint8_t tunnel_transport)
788
{
789
  (void)transport_peer;
790
  (void)tunnel_transport;
791
  (void)tunnel_peer;
792
  DEBUGASSERT(transport_peer == TRNSPRT_QUIC);
793
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
794
  return Curl_cf_ngtcp2_create(pcf, data, origin, peer, conn, addr);
795
#elif defined(USE_QUICHE)
796
  return Curl_cf_quiche_create(pcf, data, origin, peer, conn, addr);
797
#else
798
  *pcf = NULL;
799
  (void)data;
800
  (void)origin;
801
  (void)peer;
802
  (void)conn;
803
  (void)addr;
804
  (void)tunnel_peer;
805
  (void)tunnel_transport;
806
  return CURLE_NOT_BUILT_IN;
807
#endif
808
}
809
810
#if !defined(CURL_DISABLE_PROXY) && defined(USE_PROXY_HTTP3)
811
812
CURLcode Curl_cf_h3_proxy_insert_after(struct Curl_cfilter *cf_at,
813
                                       struct Curl_easy *data,
814
                                       struct Curl_peer *origin,
815
                                       struct Curl_peer *peer,
816
                                       struct Curl_peer *tunnel_peer,
817
                                       uint8_t tunnel_transport)
818
{
819
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
820
  return Curl_cf_ngtcp2_proxy_insert_after(cf_at, data, origin, peer,
821
                                           tunnel_peer, tunnel_transport);
822
#else
823
  (void)cf_at;
824
  (void)data;
825
  (void)origin;
826
  (void)peer;
827
  (void)tunnel_peer;
828
  (void)tunnel_transport;
829
  return CURLE_NOT_BUILT_IN;
830
#endif
831
}
832
833
CURLcode Curl_cf_h3_proxy_create(struct Curl_cfilter **pcf,
834
                                 struct Curl_easy *data,
835
                                 struct Curl_peer *origin,
836
                                 struct Curl_peer *peer,
837
                                 uint8_t transport_peer,
838
                                 struct connectdata *conn,
839
                                 struct Curl_sockaddr_ex *addr,
840
                                 struct Curl_peer *tunnel_peer,
841
                                 uint8_t tunnel_transport)
842
{
843
  DEBUGASSERT(transport_peer == TRNSPRT_QUIC);
844
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
845
  return Curl_cf_ngtcp2_proxy_create(pcf, data, origin, peer, transport_peer,
846
                                     conn, addr,
847
                                     tunnel_peer, tunnel_transport);
848
#else
849
  *pcf = NULL;
850
  (void)data;
851
  (void)conn;
852
  (void)addr;
853
  (void)peer;
854
  (void)transport_peer;
855
  (void)tunnel_peer;
856
  (void)tunnel_transport;
857
  return CURLE_NOT_BUILT_IN;
858
#endif
859
}
860
861
#endif /* !CURL_DISABLE_PROXY && USE_PROXY_HTTP3 */
862
863
CURLcode Curl_conn_may_http3(struct Curl_easy *data,
864
                             const struct connectdata *conn,
865
                             unsigned char transport)
866
{
867
  if(transport == TRNSPRT_UNIX) {
868
    failf(data, "HTTP/3 cannot be used over UNIX domain sockets");
869
    return CURLE_QUIC_CONNECT_ERROR;
870
  }
871
  if(!(data->state.origin->scheme->flags & PROTOPT_SSL)) {
872
    failf(data, "HTTP/3 requested for non-HTTPS URL");
873
    return CURLE_URL_MALFORMAT;
874
  }
875
#ifndef CURL_DISABLE_PROXY
876
  if(conn->socks_proxy.peer) {
877
    failf(data, "HTTP/3 is not supported over a SOCKS proxy");
878
    return CURLE_URL_MALFORMAT;
879
  }
880
#else
881
  (void)conn;
882
#endif
883
884
  return CURLE_OK;
885
}
886
887
#ifdef CURLVERBOSE
888
const char *vquic_h3_err_str(uint64_t error_code)
889
{
890
  if(error_code <= UINT_MAX) {
891
    switch((unsigned int)error_code) {
892
    case CURL_H3_ERR_NO_ERROR:
893
      return "NO_ERROR";
894
    case CURL_H3_ERR_GENERAL_PROTOCOL_ERROR:
895
      return "GENERAL_PROTOCOL_ERROR";
896
    case CURL_H3_ERR_INTERNAL_ERROR:
897
      return "INTERNAL_ERROR";
898
    case CURL_H3_ERR_STREAM_CREATION_ERROR:
899
      return "STREAM_CREATION_ERROR";
900
    case CURL_H3_ERR_CLOSED_CRITICAL_STREAM:
901
      return "CLOSED_CRITICAL_STREAM";
902
    case CURL_H3_ERR_FRAME_UNEXPECTED:
903
      return "FRAME_UNEXPECTED";
904
    case CURL_H3_ERR_FRAME_ERROR:
905
      return "FRAME_ERROR";
906
    case CURL_H3_ERR_EXCESSIVE_LOAD:
907
      return "EXCESSIVE_LOAD";
908
    case CURL_H3_ERR_ID_ERROR:
909
      return "ID_ERROR";
910
    case CURL_H3_ERR_SETTINGS_ERROR:
911
      return "SETTINGS_ERROR";
912
    case CURL_H3_ERR_MISSING_SETTINGS:
913
      return "MISSING_SETTINGS";
914
    case CURL_H3_ERR_REQUEST_REJECTED:
915
      return "REQUEST_REJECTED";
916
    case CURL_H3_ERR_REQUEST_CANCELLED:
917
      return "REQUEST_CANCELLED";
918
    case CURL_H3_ERR_REQUEST_INCOMPLETE:
919
      return "REQUEST_INCOMPLETE";
920
    case CURL_H3_ERR_MESSAGE_ERROR:
921
      return "MESSAGE_ERROR";
922
    case CURL_H3_ERR_CONNECT_ERROR:
923
      return "CONNECT_ERROR";
924
    case CURL_H3_ERR_VERSION_FALLBACK:
925
      return "VERSION_FALLBACK";
926
    default:
927
      break;
928
    }
929
  }
930
  /* RFC 9114 ch. 8.1 + 9, reserved future error codes that are NO_ERROR */
931
  if((error_code >= 0x21) && !((error_code - 0x21) % 0x1f))
932
    return "NO_ERROR";
933
  return "unknown";
934
}
935
#endif /* CURLVERBOSE */
936
937
#if defined(USE_NGTCP2) || defined(USE_NGHTTP3)
938
939
static void *vquic_ngtcp2_malloc(size_t size, void *user_data)
940
{
941
  (void)user_data;
942
  return Curl_cmalloc(size);
943
}
944
945
static void vquic_ngtcp2_free(void *ptr, void *user_data)
946
{
947
  (void)user_data;
948
  Curl_cfree(ptr);
949
}
950
951
static void *vquic_ngtcp2_calloc(size_t nmemb, size_t size, void *user_data)
952
{
953
  (void)user_data;
954
  return Curl_ccalloc(nmemb, size);
955
}
956
957
static void *vquic_ngtcp2_realloc(void *ptr, size_t size, void *user_data)
958
{
959
  (void)user_data;
960
  return Curl_crealloc(ptr, size);
961
}
962
963
#ifdef USE_NGTCP2
964
static struct ngtcp2_mem vquic_ngtcp2_mem = {
965
  NULL,
966
  vquic_ngtcp2_malloc,
967
  vquic_ngtcp2_free,
968
  vquic_ngtcp2_calloc,
969
  vquic_ngtcp2_realloc
970
};
971
struct ngtcp2_mem *Curl_ngtcp2_mem(void)
972
{
973
  return &vquic_ngtcp2_mem;
974
}
975
#endif
976
977
#ifdef USE_NGHTTP3
978
static struct nghttp3_mem vquic_nghttp3_mem = {
979
  NULL,
980
  vquic_ngtcp2_malloc,
981
  vquic_ngtcp2_free,
982
  vquic_ngtcp2_calloc,
983
  vquic_ngtcp2_realloc
984
};
985
struct nghttp3_mem *Curl_nghttp3_mem(void)
986
{
987
  return &vquic_nghttp3_mem;
988
}
989
#endif
990
991
#endif /* USE_NGTCP2 || USE_NGHTTP3 */
992
993
#else /* CURL_DISABLE_HTTP || !USE_HTTP3 */
994
995
CURLcode Curl_conn_may_http3(struct Curl_easy *data,
996
                             const struct connectdata *conn,
997
                             unsigned char transport)
998
0
{
999
0
  (void)data;
1000
0
  (void)conn;
1001
0
  (void)transport;
1002
0
  DEBUGF(infof(data, "QUIC is not supported in this build"));
1003
0
  return CURLE_NOT_BUILT_IN;
1004
0
}
1005
1006
#endif /* !CURL_DISABLE_HTTP && USE_HTTP3 */