Coverage Report

Created: 2026-01-25 06:18

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
26
#ifdef HAVE_NETINET_UDP_H
27
#include <netinet/udp.h>
28
#endif
29
#ifdef USE_NGHTTP3
30
#include <nghttp3/nghttp3.h>
31
#endif
32
#include "../urldata.h"
33
#include "../bufq.h"
34
#include "../curlx/dynbuf.h"
35
#include "../curlx/fopen.h"
36
#include "../cfilters.h"
37
#include "../curl_trc.h"
38
#include "curl_ngtcp2.h"
39
#include "curl_quiche.h"
40
#include "../multiif.h"
41
#include "../progress.h"
42
#include "../rand.h"
43
#include "vquic.h"
44
#include "vquic_int.h"
45
#include "../curlx/strerr.h"
46
#include "../curlx/strparse.h"
47
48
49
#if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3)
50
51
#define NW_CHUNK_SIZE     (64 * 1024)
52
#define NW_SEND_CHUNKS    1
53
54
int Curl_vquic_init(void)
55
{
56
#if defined(USE_NGTCP2) && defined(OPENSSL_QUIC_API2)
57
  if(ngtcp2_crypto_ossl_init())
58
    return 0;
59
#endif
60
61
  return 1;
62
}
63
64
void Curl_quic_ver(char *p, size_t len)
65
{
66
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
67
  Curl_ngtcp2_ver(p, len);
68
#elif defined(USE_QUICHE)
69
  Curl_quiche_ver(p, len);
70
#endif
71
}
72
73
CURLcode vquic_ctx_init(struct Curl_easy *data,
74
                        struct cf_quic_ctx *qctx)
75
{
76
  Curl_bufq_init2(&qctx->sendbuf, NW_CHUNK_SIZE, NW_SEND_CHUNKS,
77
                  BUFQ_OPT_SOFT_LIMIT);
78
#if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG)
79
  qctx->no_gso = FALSE;
80
#else
81
  qctx->no_gso = TRUE;
82
#endif
83
#ifdef DEBUGBUILD
84
  {
85
    const char *p = getenv("CURL_DBG_QUIC_WBLOCK");
86
    if(p) {
87
      curl_off_t l;
88
      if(!curlx_str_number(&p, &l, 100))
89
        qctx->wblock_percent = (int)l;
90
    }
91
  }
92
#endif
93
  vquic_ctx_set_time(qctx, Curl_pgrs_now(data));
94
95
  return CURLE_OK;
96
}
97
98
void vquic_ctx_free(struct cf_quic_ctx *qctx)
99
{
100
  Curl_bufq_free(&qctx->sendbuf);
101
}
102
103
void vquic_ctx_set_time(struct cf_quic_ctx *qctx,
104
                        const struct curltime *pnow)
105
{
106
  qctx->last_op = *pnow;
107
}
108
109
void vquic_ctx_update_time(struct cf_quic_ctx *qctx,
110
                           const struct curltime *pnow)
111
{
112
  qctx->last_op = *pnow;
113
}
114
115
static CURLcode send_packet_no_gso(struct Curl_cfilter *cf,
116
                                   struct Curl_easy *data,
117
                                   struct cf_quic_ctx *qctx,
118
                                   const uint8_t *pkt, size_t pktlen,
119
                                   size_t gsolen, size_t *psent);
120
121
static CURLcode do_sendmsg(struct Curl_cfilter *cf,
122
                           struct Curl_easy *data,
123
                           struct cf_quic_ctx *qctx,
124
                           const uint8_t *pkt, size_t pktlen, size_t gsolen,
125
                           size_t *psent)
126
{
127
  CURLcode result = CURLE_OK;
128
#ifdef HAVE_SENDMSG
129
  struct iovec msg_iov;
130
  struct msghdr msg = { 0 };
131
  ssize_t rv;
132
#if defined(__linux__) && defined(UDP_SEGMENT)
133
  uint8_t msg_ctrl[32];
134
  struct cmsghdr *cm;
135
#endif
136
137
  *psent = 0;
138
  msg_iov.iov_base = (uint8_t *)CURL_UNCONST(pkt);
139
  msg_iov.iov_len = pktlen;
140
  msg.msg_iov = &msg_iov;
141
  msg.msg_iovlen = 1;
142
143
#if defined(__linux__) && defined(UDP_SEGMENT)
144
  if(pktlen > gsolen) {
145
    /* Only set this, when we need it. macOS, for example,
146
     * does not seem to like a msg_control of length 0. */
147
    memset(msg_ctrl, 0, sizeof(msg_ctrl));
148
    msg.msg_control = msg_ctrl;
149
    assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(int)));
150
    msg.msg_controllen = CMSG_SPACE(sizeof(int));
151
    cm = CMSG_FIRSTHDR(&msg);
152
    cm->cmsg_level = SOL_UDP;
153
    cm->cmsg_type = UDP_SEGMENT;
154
    cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
155
    *(uint16_t *)(void *)CMSG_DATA(cm) = gsolen & 0xffff;
156
  }
157
#endif
158
159
  while((rv = sendmsg(qctx->sockfd, &msg, 0)) == -1 && SOCKERRNO == SOCKEINTR)
160
    ;
161
162
  if(!curlx_sztouz(rv, psent)) {
163
    switch(SOCKERRNO) {
164
    case EAGAIN:
165
#if EAGAIN != SOCKEWOULDBLOCK
166
    case SOCKEWOULDBLOCK:
167
#endif
168
      return CURLE_AGAIN;
169
    case SOCKEMSGSIZE:
170
      /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */
171
      break;
172
    case EIO:
173
      if(pktlen > gsolen) {
174
        /* GSO failure */
175
        infof(data, "sendmsg() returned %zd (errno %d); disable GSO", rv,
176
              SOCKERRNO);
177
        qctx->no_gso = TRUE;
178
        return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent);
179
      }
180
      FALLTHROUGH();
181
    default:
182
      failf(data, "sendmsg() returned %zd (errno %d)", rv, SOCKERRNO);
183
      result = CURLE_SEND_ERROR;
184
      goto out;
185
    }
186
  }
187
  else if(pktlen != *psent) {
188
    failf(data, "sendmsg() sent only %zu/%zu bytes", *psent, pktlen);
189
    result = CURLE_SEND_ERROR;
190
    goto out;
191
  }
192
#else
193
  ssize_t rv;
194
  (void)gsolen;
195
196
  *psent = 0;
197
198
  while((rv = send(qctx->sockfd, (const char *)pkt,
199
                   (SEND_TYPE_ARG3)pktlen, 0)) == -1 &&
200
        SOCKERRNO == SOCKEINTR)
201
    ;
202
203
  if(!curlx_sztouz(rv, psent)) {
204
    if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) {
205
      result = CURLE_AGAIN;
206
      goto out;
207
    }
208
    else {
209
      failf(data, "send() returned %zd (errno %d)", rv, SOCKERRNO);
210
      if(SOCKERRNO != SOCKEMSGSIZE) {
211
        result = CURLE_SEND_ERROR;
212
        goto out;
213
      }
214
      /* UDP datagram is too large; caused by PMTUD. Just let it be
215
         lost. */
216
    }
217
  }
218
#endif
219
  (void)cf;
220
221
out:
222
  return result;
223
}
224
225
#ifdef HAVE_SENDMSG
226
#define VQUIC_SEND_METHOD   "sendmsg"
227
#else
228
#define VQUIC_SEND_METHOD   "send"
229
#endif
230
231
static CURLcode send_packet_no_gso(struct Curl_cfilter *cf,
232
                                   struct Curl_easy *data,
233
                                   struct cf_quic_ctx *qctx,
234
                                   const uint8_t *pkt, size_t pktlen,
235
                                   size_t gsolen, size_t *psent)
236
{
237
  const uint8_t *p, *end = pkt + pktlen;
238
  size_t sent, len;
239
  CURLcode result = CURLE_OK;
240
  VERBOSE(size_t calls = 0);
241
242
  *psent = 0;
243
244
  for(p = pkt; p < end; p += gsolen) {
245
    len = CURLMIN(gsolen, (size_t)(end - p));
246
    result = do_sendmsg(cf, data, qctx, p, len, len, &sent);
247
    if(result)
248
      goto out;
249
    *psent += sent;
250
    VERBOSE(++calls);
251
  }
252
out:
253
  CURL_TRC_CF(data, cf, "vquic_%s(len=%zu, gso=%zu, calls=%zu)"
254
              " -> %d, sent=%zu",
255
              VQUIC_SEND_METHOD, pktlen, gsolen, calls, result, *psent);
256
  return result;
257
}
258
259
static CURLcode vquic_send_packets(struct Curl_cfilter *cf,
260
                                   struct Curl_easy *data,
261
                                   struct cf_quic_ctx *qctx,
262
                                   const uint8_t *pkt, size_t pktlen,
263
                                   size_t gsolen, size_t *psent)
264
{
265
  CURLcode result;
266
#ifdef DEBUGBUILD
267
  /* simulate network blocking/partial writes */
268
  if(qctx->wblock_percent > 0) {
269
    unsigned char c;
270
    *psent = 0;
271
    Curl_rand(data, &c, 1);
272
    if(c >= ((100 - qctx->wblock_percent) * 256 / 100)) {
273
      CURL_TRC_CF(data, cf, "vquic_flush() simulate EWOULDBLOCK");
274
      return CURLE_AGAIN;
275
    }
276
  }
277
#endif
278
  if(qctx->no_gso && pktlen > gsolen) {
279
    result = send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent);
280
  }
281
  else {
282
    result = do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent);
283
    CURL_TRC_CF(data, cf, "vquic_%s(len=%zu, gso=%zu, calls=1)"
284
                " -> %d, sent=%zu",
285
                VQUIC_SEND_METHOD, pktlen, gsolen, result, *psent);
286
  }
287
  if(!result)
288
    qctx->last_io = qctx->last_op;
289
  return result;
290
}
291
292
CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data,
293
                     struct cf_quic_ctx *qctx)
294
{
295
  const unsigned char *buf;
296
  size_t blen, sent;
297
  CURLcode result;
298
  size_t gsolen;
299
300
  while(Curl_bufq_peek(&qctx->sendbuf, &buf, &blen)) {
301
    gsolen = qctx->gsolen;
302
    if(qctx->split_len) {
303
      gsolen = qctx->split_gsolen;
304
      if(blen > qctx->split_len)
305
        blen = qctx->split_len;
306
    }
307
308
    result = vquic_send_packets(cf, data, qctx, buf, blen, gsolen, &sent);
309
    if(result) {
310
      if(result == CURLE_AGAIN) {
311
        Curl_bufq_skip(&qctx->sendbuf, sent);
312
        if(qctx->split_len)
313
          qctx->split_len -= sent;
314
      }
315
      return result;
316
    }
317
    Curl_bufq_skip(&qctx->sendbuf, sent);
318
    if(qctx->split_len)
319
      qctx->split_len -= sent;
320
  }
321
  return CURLE_OK;
322
}
323
324
CURLcode vquic_send(struct Curl_cfilter *cf, struct Curl_easy *data,
325
                    struct cf_quic_ctx *qctx, size_t gsolen)
326
{
327
  qctx->gsolen = gsolen;
328
  return vquic_flush(cf, data, qctx);
329
}
330
331
CURLcode vquic_send_tail_split(struct Curl_cfilter *cf, struct Curl_easy *data,
332
                               struct cf_quic_ctx *qctx, size_t gsolen,
333
                               size_t tail_len, size_t tail_gsolen)
334
{
335
  DEBUGASSERT(Curl_bufq_len(&qctx->sendbuf) > tail_len);
336
  qctx->split_len = Curl_bufq_len(&qctx->sendbuf) - tail_len;
337
  qctx->split_gsolen = gsolen;
338
  qctx->gsolen = tail_gsolen;
339
  CURL_TRC_CF(data, cf, "vquic_send_tail_split: [%zu gso=%zu][%zu gso=%zu]",
340
              qctx->split_len, qctx->split_gsolen, tail_len, qctx->gsolen);
341
  return vquic_flush(cf, data, qctx);
342
}
343
344
#if defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG)
345
static size_t vquic_msghdr_get_udp_gro(struct msghdr *msg)
346
{
347
  int gso_size = 0;
348
#if defined(__linux__) && defined(UDP_GRO)
349
  struct cmsghdr *cmsg;
350
351
  /* Workaround musl CMSG_NXTHDR issue */
352
#if defined(__clang__) && !defined(__GLIBC__)
353
#pragma clang diagnostic push
354
#pragma clang diagnostic ignored "-Wsign-compare"
355
#pragma clang diagnostic ignored "-Wcast-align"
356
#endif
357
  for(cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
358
#if defined(__clang__) && !defined(__GLIBC__)
359
#pragma clang diagnostic pop
360
#endif
361
    if(cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) {
362
      memcpy(&gso_size, CMSG_DATA(cmsg), sizeof(gso_size));
363
364
      break;
365
    }
366
  }
367
#endif
368
  (void)msg;
369
370
  return (size_t)gso_size;
371
}
372
#endif
373
374
#ifdef HAVE_SENDMMSG
375
static CURLcode recvmmsg_packets(struct Curl_cfilter *cf,
376
                                 struct Curl_easy *data,
377
                                 struct cf_quic_ctx *qctx,
378
                                 size_t max_pkts,
379
                                 vquic_recv_pkts_cb *recv_cb, void *userp)
380
{
381
#if defined(__linux__) && defined(UDP_GRO)
382
#define MMSG_NUM  16
383
#define UDP_GRO_CNT_MAX  64
384
#else
385
#define MMSG_NUM  64
386
#define UDP_GRO_CNT_MAX  1
387
#endif
388
#define MSG_BUF_SIZE  (UDP_GRO_CNT_MAX * 1500)
389
  struct iovec msg_iov[MMSG_NUM];
390
  struct mmsghdr mmsg[MMSG_NUM];
391
  uint8_t msg_ctrl[MMSG_NUM * CMSG_SPACE(sizeof(int))];
392
  struct sockaddr_storage remote_addr[MMSG_NUM];
393
  size_t total_nread = 0, pkts = 0, calls = 0;
394
  int mcount, i, n;
395
  char errstr[STRERROR_LEN];
396
  CURLcode result = CURLE_OK;
397
  size_t gso_size;
398
  char *sockbuf = NULL;
399
  uint8_t (*bufs)[MSG_BUF_SIZE] = NULL;
400
401
  DEBUGASSERT(max_pkts > 0);
402
  result = Curl_multi_xfer_sockbuf_borrow(data, MMSG_NUM * MSG_BUF_SIZE,
403
                                          &sockbuf);
404
  if(result)
405
    goto out;
406
  bufs = (uint8_t (*)[MSG_BUF_SIZE])sockbuf;
407
408
  total_nread = 0;
409
  while(pkts < max_pkts) {
410
    n = (int)CURLMIN(CURLMIN(MMSG_NUM, IOV_MAX), max_pkts);
411
    memset(&mmsg, 0, sizeof(mmsg));
412
    for(i = 0; i < n; ++i) {
413
      msg_iov[i].iov_base = bufs[i];
414
      msg_iov[i].iov_len = (int)sizeof(bufs[i]);
415
      mmsg[i].msg_hdr.msg_iov = &msg_iov[i];
416
      mmsg[i].msg_hdr.msg_iovlen = 1;
417
      mmsg[i].msg_hdr.msg_name = &remote_addr[i];
418
      mmsg[i].msg_hdr.msg_namelen = sizeof(remote_addr[i]);
419
      mmsg[i].msg_hdr.msg_control = &msg_ctrl[i * CMSG_SPACE(sizeof(int))];
420
      mmsg[i].msg_hdr.msg_controllen = CMSG_SPACE(sizeof(int));
421
    }
422
423
    while((mcount = recvmmsg(qctx->sockfd, mmsg, n, 0, NULL)) == -1 &&
424
          (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE))
425
      ;
426
    if(mcount == -1) {
427
      if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) {
428
        CURL_TRC_CF(data, cf, "ingress, recvmmsg -> EAGAIN");
429
        goto out;
430
      }
431
      if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) {
432
        struct ip_quadruple ip;
433
        if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip))
434
          failf(data, "QUIC: connection to %s port %u refused",
435
                ip.remote_ip, ip.remote_port);
436
        result = CURLE_COULDNT_CONNECT;
437
        goto out;
438
      }
439
      curlx_strerror(SOCKERRNO, errstr, sizeof(errstr));
440
      failf(data, "QUIC: recvmmsg() unexpectedly returned %d (errno=%d; %s)",
441
                  mcount, SOCKERRNO, errstr);
442
      result = CURLE_RECV_ERROR;
443
      goto out;
444
    }
445
446
    ++calls;
447
    for(i = 0; i < mcount; ++i) {
448
      /* A zero-length UDP packet is no QUIC packet. Ignore. */
449
      if(!mmsg[i].msg_len)
450
        continue;
451
      total_nread += mmsg[i].msg_len;
452
453
      gso_size = vquic_msghdr_get_udp_gro(&mmsg[i].msg_hdr);
454
      if(gso_size == 0)
455
        gso_size = mmsg[i].msg_len;
456
457
      result = recv_cb(bufs[i], mmsg[i].msg_len, gso_size,
458
                       mmsg[i].msg_hdr.msg_name,
459
                       mmsg[i].msg_hdr.msg_namelen, 0, userp);
460
      if(result)
461
        goto out;
462
      pkts += (mmsg[i].msg_len + gso_size - 1) / gso_size;
463
    }
464
  }
465
466
out:
467
  if(total_nread || result)
468
    CURL_TRC_CF(data, cf, "vquic_recvmmsg(len=%zu, packets=%zu, calls=%zu)"
469
                " -> %d", total_nread, pkts, calls, result);
470
  Curl_multi_xfer_sockbuf_release(data, sockbuf);
471
  return result;
472
}
473
474
#elif defined(HAVE_SENDMSG)
475
static CURLcode recvmsg_packets(struct Curl_cfilter *cf,
476
                                struct Curl_easy *data,
477
                                struct cf_quic_ctx *qctx,
478
                                size_t max_pkts,
479
                                vquic_recv_pkts_cb *recv_cb, void *userp)
480
{
481
  struct iovec msg_iov;
482
  struct msghdr msg;
483
  uint8_t buf[64 * 1024];
484
  struct sockaddr_storage remote_addr;
485
  size_t total_nread, pkts, calls;
486
  ssize_t rc;
487
  size_t nread;
488
  char errstr[STRERROR_LEN];
489
  CURLcode result = CURLE_OK;
490
  uint8_t msg_ctrl[CMSG_SPACE(sizeof(int))];
491
  size_t gso_size;
492
493
  DEBUGASSERT(max_pkts > 0);
494
  for(pkts = 0, total_nread = 0, calls = 0; pkts < max_pkts;) {
495
    /* fully initialise this on each call to `recvmsg()`. There seem to
496
     * operating systems out there that mess with `msg_iov.iov_len`. */
497
    memset(&msg, 0, sizeof(msg));
498
    msg_iov.iov_base = buf;
499
    msg_iov.iov_len = (int)sizeof(buf);
500
    msg.msg_iov = &msg_iov;
501
    msg.msg_iovlen = 1;
502
    msg.msg_control = msg_ctrl;
503
    msg.msg_name = &remote_addr;
504
    msg.msg_namelen = sizeof(remote_addr);
505
    msg.msg_controllen = sizeof(msg_ctrl);
506
507
    while((rc = recvmsg(qctx->sockfd, &msg, 0)) == -1 &&
508
          (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE))
509
      ;
510
    if(!curlx_sztouz(rc, &nread)) {
511
      if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) {
512
        goto out;
513
      }
514
      if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) {
515
        struct ip_quadruple ip;
516
        if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip))
517
          failf(data, "QUIC: connection to %s port %u refused",
518
                ip.remote_ip, ip.remote_port);
519
        result = CURLE_COULDNT_CONNECT;
520
        goto out;
521
      }
522
      curlx_strerror(SOCKERRNO, errstr, sizeof(errstr));
523
      failf(data, "QUIC: recvmsg() unexpectedly returned %zd (errno=%d; %s)",
524
            rc, SOCKERRNO, errstr);
525
      result = CURLE_RECV_ERROR;
526
      goto out;
527
    }
528
529
    total_nread += nread;
530
    ++calls;
531
532
    /* A 0-length UDP packet is no QUIC packet */
533
    if(!nread)
534
      continue;
535
536
    gso_size = vquic_msghdr_get_udp_gro(&msg);
537
    if(gso_size == 0)
538
      gso_size = nread;
539
540
    result = recv_cb(buf, nread, gso_size,
541
                     msg.msg_name, msg.msg_namelen, 0, userp);
542
    if(result)
543
      goto out;
544
    pkts += (nread + gso_size - 1) / gso_size;
545
  }
546
547
out:
548
  if(total_nread || result)
549
    CURL_TRC_CF(data, cf, "vquic_recvmsg(len=%zu, packets=%zu, calls=%zu)"
550
                " -> %d", total_nread, pkts, calls, result);
551
  return result;
552
}
553
554
#else /* HAVE_SENDMMSG || HAVE_SENDMSG */
555
static CURLcode recvfrom_packets(struct Curl_cfilter *cf,
556
                                 struct Curl_easy *data,
557
                                 struct cf_quic_ctx *qctx,
558
                                 size_t max_pkts,
559
                                 vquic_recv_pkts_cb *recv_cb, void *userp)
560
{
561
  uint8_t buf[64 * 1024];
562
  int bufsize = (int)sizeof(buf);
563
  struct sockaddr_storage remote_addr;
564
  socklen_t remote_addrlen = sizeof(remote_addr);
565
  size_t total_nread, pkts, calls = 0, nread;
566
  ssize_t rv;
567
  char errstr[STRERROR_LEN];
568
  CURLcode result = CURLE_OK;
569
570
  DEBUGASSERT(max_pkts > 0);
571
  for(pkts = 0, total_nread = 0; pkts < max_pkts;) {
572
    while((rv = recvfrom(qctx->sockfd, (char *)buf, bufsize, 0,
573
                         (struct sockaddr *)&remote_addr,
574
                         &remote_addrlen)) == -1 &&
575
          (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE))
576
      ;
577
    if(!curlx_sztouz(rv, &nread)) {
578
      if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) {
579
        CURL_TRC_CF(data, cf, "ingress, recvfrom -> EAGAIN");
580
        goto out;
581
      }
582
      if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) {
583
        struct ip_quadruple ip;
584
        if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip))
585
          failf(data, "QUIC: connection to %s port %u refused",
586
                ip.remote_ip, ip.remote_port);
587
        result = CURLE_COULDNT_CONNECT;
588
        goto out;
589
      }
590
      curlx_strerror(SOCKERRNO, errstr, sizeof(errstr));
591
      failf(data, "QUIC: recvfrom() unexpectedly returned %zd (errno=%d; %s)",
592
            rv, SOCKERRNO, errstr);
593
      result = CURLE_RECV_ERROR;
594
      goto out;
595
    }
596
597
    ++pkts;
598
    ++calls;
599
600
    /* A 0-length UDP packet is no QUIC packet */
601
    if(!nread)
602
      continue;
603
604
    total_nread += nread;
605
    result = recv_cb(buf, nread, nread, &remote_addr, remote_addrlen,
606
                     0, userp);
607
    if(result)
608
      goto out;
609
  }
610
611
out:
612
  if(total_nread || result)
613
    CURL_TRC_CF(data, cf, "vquic_recvfrom(len=%zu, packets=%zu, calls=%zu)"
614
                " -> %d", total_nread, pkts, calls, result);
615
  return result;
616
}
617
#endif /* !HAVE_SENDMMSG && !HAVE_SENDMSG */
618
619
CURLcode vquic_recv_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
  CURLcode result;
626
#ifdef HAVE_SENDMMSG
627
  result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp);
628
#elif defined(HAVE_SENDMSG)
629
  result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp);
630
#else
631
  result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp);
632
#endif
633
  if(!result) {
634
    if(!qctx->got_first_byte) {
635
      qctx->got_first_byte = TRUE;
636
      qctx->first_byte_at = qctx->last_op;
637
    }
638
    qctx->last_io = qctx->last_op;
639
  }
640
  return result;
641
}
642
643
/*
644
 * If the QLOGDIR environment variable is set, open and return a file
645
 * descriptor to write the log to.
646
 *
647
 * This function returns error if something failed outside of failing to
648
 * create the file. Open file success is deemed by seeing if the returned fd
649
 * is != -1.
650
 */
651
CURLcode Curl_qlogdir(struct Curl_easy *data,
652
                      unsigned char *scid,
653
                      size_t scidlen,
654
                      int *qlogfdp)
655
{
656
  char *qlog_dir = curl_getenv("QLOGDIR");
657
  *qlogfdp = -1;
658
  if(qlog_dir) {
659
    struct dynbuf fname;
660
    CURLcode result;
661
    unsigned int i;
662
    curlx_dyn_init(&fname, DYN_QLOG_NAME);
663
    result = curlx_dyn_add(&fname, qlog_dir);
664
    if(!result)
665
      result = curlx_dyn_add(&fname, "/");
666
    for(i = 0; (i < scidlen) && !result; i++) {
667
      char hex[3];
668
      curl_msnprintf(hex, 3, "%02x", scid[i]);
669
      result = curlx_dyn_add(&fname, hex);
670
    }
671
    if(!result)
672
      result = curlx_dyn_add(&fname, ".sqlog");
673
674
    if(!result) {
675
      int qlogfd = curlx_open(curlx_dyn_ptr(&fname),
676
                              O_WRONLY | O_CREAT | CURL_O_BINARY,
677
                              data->set.new_file_perms
678
#ifdef _WIN32
679
                              & (_S_IREAD | _S_IWRITE)
680
#endif
681
                              );
682
      if(qlogfd != -1)
683
        *qlogfdp = qlogfd;
684
    }
685
    curlx_dyn_free(&fname);
686
    curlx_free(qlog_dir);
687
    if(result)
688
      return result;
689
  }
690
691
  return CURLE_OK;
692
}
693
694
CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
695
                             struct Curl_easy *data,
696
                             struct connectdata *conn,
697
                             const struct Curl_addrinfo *ai,
698
                             uint8_t transport)
699
{
700
  (void)transport;
701
  DEBUGASSERT(transport == TRNSPRT_QUIC);
702
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
703
  return Curl_cf_ngtcp2_create(pcf, data, conn, ai);
704
#elif defined(USE_QUICHE)
705
  return Curl_cf_quiche_create(pcf, data, conn, ai);
706
#else
707
  *pcf = NULL;
708
  (void)data;
709
  (void)conn;
710
  (void)ai;
711
  return CURLE_NOT_BUILT_IN;
712
#endif
713
}
714
715
CURLcode Curl_conn_may_http3(struct Curl_easy *data,
716
                             const struct connectdata *conn,
717
                             unsigned char transport)
718
{
719
  if(transport == TRNSPRT_UNIX) {
720
    /* cannot do QUIC over a Unix domain socket */
721
    return CURLE_QUIC_CONNECT_ERROR;
722
  }
723
  if(!(conn->scheme->flags & PROTOPT_SSL)) {
724
    failf(data, "HTTP/3 requested for non-HTTPS URL");
725
    return CURLE_URL_MALFORMAT;
726
  }
727
#ifndef CURL_DISABLE_PROXY
728
  if(conn->bits.socksproxy) {
729
    failf(data, "HTTP/3 is not supported over a SOCKS proxy");
730
    return CURLE_URL_MALFORMAT;
731
  }
732
  if(conn->bits.httpproxy && conn->bits.tunnel_proxy) {
733
    failf(data, "HTTP/3 is not supported over an HTTP proxy");
734
    return CURLE_URL_MALFORMAT;
735
  }
736
#endif
737
738
  return CURLE_OK;
739
}
740
741
#ifdef CURLVERBOSE
742
const char *vquic_h3_err_str(uint64_t error_code)
743
{
744
  if(error_code <= UINT_MAX) {
745
    switch((unsigned int)error_code) {
746
    case CURL_H3_ERR_NO_ERROR:
747
      return "NO_ERROR";
748
    case CURL_H3_ERR_GENERAL_PROTOCOL_ERROR:
749
      return "GENERAL_PROTOCOL_ERROR";
750
    case CURL_H3_ERR_INTERNAL_ERROR:
751
      return "INTERNAL_ERROR";
752
    case CURL_H3_ERR_STREAM_CREATION_ERROR:
753
      return "STREAM_CREATION_ERROR";
754
    case CURL_H3_ERR_CLOSED_CRITICAL_STREAM:
755
      return "CLOSED_CRITICAL_STREAM";
756
    case CURL_H3_ERR_FRAME_UNEXPECTED:
757
      return "FRAME_UNEXPECTED";
758
    case CURL_H3_ERR_FRAME_ERROR:
759
      return "FRAME_ERROR";
760
    case CURL_H3_ERR_EXCESSIVE_LOAD:
761
      return "EXCESSIVE_LOAD";
762
    case CURL_H3_ERR_ID_ERROR:
763
      return "ID_ERROR";
764
    case CURL_H3_ERR_SETTINGS_ERROR:
765
      return "SETTINGS_ERROR";
766
    case CURL_H3_ERR_MISSING_SETTINGS:
767
      return "MISSING_SETTINGS";
768
    case CURL_H3_ERR_REQUEST_REJECTED:
769
      return "REQUEST_REJECTED";
770
    case CURL_H3_ERR_REQUEST_CANCELLED:
771
      return "REQUEST_CANCELLED";
772
    case CURL_H3_ERR_REQUEST_INCOMPLETE:
773
      return "REQUEST_INCOMPLETE";
774
    case CURL_H3_ERR_MESSAGE_ERROR:
775
      return "MESSAGE_ERROR";
776
    case CURL_H3_ERR_CONNECT_ERROR:
777
      return "CONNECT_ERROR";
778
    case CURL_H3_ERR_VERSION_FALLBACK:
779
      return "VERSION_FALLBACK";
780
    default:
781
      break;
782
    }
783
  }
784
  /* RFC 9114 ch. 8.1 + 9, reserved future error codes that are NO_ERROR */
785
  if((error_code >= 0x21) && !((error_code - 0x21) % 0x1f))
786
    return "NO_ERROR";
787
  return "unknown";
788
}
789
#endif /* CURLVERBOSE */
790
791
#if defined(USE_NGTCP2) || defined(USE_NGHTTP3)
792
793
static void *vquic_ngtcp2_malloc(size_t size, void *user_data)
794
{
795
  (void)user_data;
796
  return Curl_cmalloc(size);
797
}
798
799
static void vquic_ngtcp2_free(void *ptr, void *user_data)
800
{
801
  (void)user_data;
802
  Curl_cfree(ptr);
803
}
804
805
static void *vquic_ngtcp2_calloc(size_t nmemb, size_t size, void *user_data)
806
{
807
  (void)user_data;
808
  return Curl_ccalloc(nmemb, size);
809
}
810
811
static void *vquic_ngtcp2_realloc(void *ptr, size_t size, void *user_data)
812
{
813
  (void)user_data;
814
  return Curl_crealloc(ptr, size);
815
}
816
817
#ifdef USE_NGTCP2
818
static struct ngtcp2_mem vquic_ngtcp2_mem = {
819
  NULL,
820
  vquic_ngtcp2_malloc,
821
  vquic_ngtcp2_free,
822
  vquic_ngtcp2_calloc,
823
  vquic_ngtcp2_realloc
824
};
825
struct ngtcp2_mem *Curl_ngtcp2_mem(void)
826
{
827
  return &vquic_ngtcp2_mem;
828
}
829
#endif
830
831
#ifdef USE_NGHTTP3
832
static struct nghttp3_mem vquic_nghttp3_mem = {
833
  NULL,
834
  vquic_ngtcp2_malloc,
835
  vquic_ngtcp2_free,
836
  vquic_ngtcp2_calloc,
837
  vquic_ngtcp2_realloc
838
};
839
struct nghttp3_mem *Curl_nghttp3_mem(void)
840
{
841
  return &vquic_nghttp3_mem;
842
}
843
#endif
844
845
#endif /* USE_NGTCP2 || USE_NGHTTP3 */
846
847
#else /* CURL_DISABLE_HTTP || !USE_HTTP3 */
848
849
CURLcode Curl_conn_may_http3(struct Curl_easy *data,
850
                             const struct connectdata *conn,
851
                             unsigned char transport)
852
0
{
853
0
  (void)data;
854
0
  (void)conn;
855
0
  (void)transport;
856
0
  DEBUGF(infof(data, "QUIC is not supported in this build"));
857
0
  return CURLE_NOT_BUILT_IN;
858
0
}
859
860
#endif /* !CURL_DISABLE_HTTP && USE_HTTP3 */