Coverage Report

Created: 2026-02-09 06:05

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
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_osslq.h"
40
#include "curl_quiche.h"
41
#include "../multiif.h"
42
#include "../progress.h"
43
#include "../rand.h"
44
#include "vquic.h"
45
#include "vquic_int.h"
46
#include "../curlx/strerr.h"
47
#include "../curlx/strparse.h"
48
49
50
#if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3)
51
52
#define NW_CHUNK_SIZE     (64 * 1024)
53
#define NW_SEND_CHUNKS    1
54
55
int Curl_vquic_init(void)
56
{
57
#if defined(USE_NGTCP2) && defined(OPENSSL_QUIC_API2)
58
  if(ngtcp2_crypto_ossl_init())
59
    return 0;
60
#endif
61
62
  return 1;
63
}
64
65
void Curl_quic_ver(char *p, size_t len)
66
{
67
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
68
  Curl_ngtcp2_ver(p, len);
69
#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3)
70
  Curl_osslq_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. Just let it be lost. */
174
      break;
175
    case EIO:
176
      if(pktlen > gsolen) {
177
        /* GSO failure */
178
        infof(data, "sendmsg() returned %zd (errno %d); disable GSO", rv,
179
              SOCKERRNO);
180
        qctx->no_gso = TRUE;
181
        return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent);
182
      }
183
      FALLTHROUGH();
184
    default:
185
      failf(data, "sendmsg() returned %zd (errno %d)", rv, SOCKERRNO);
186
      result = CURLE_SEND_ERROR;
187
      goto out;
188
    }
189
  }
190
  else if(pktlen != *psent) {
191
    failf(data, "sendmsg() sent only %zu/%zu bytes", *psent, pktlen);
192
    result = CURLE_SEND_ERROR;
193
    goto out;
194
  }
195
#else
196
  ssize_t rv;
197
  (void)gsolen;
198
199
  *psent = 0;
200
201
  while((rv = send(qctx->sockfd, (const char *)pkt,
202
                   (SEND_TYPE_ARG3)pktlen, 0)) == -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
      failf(data, "send() returned %zd (errno %d)", rv, SOCKERRNO);
213
      if(SOCKERRNO != SOCKEMSGSIZE) {
214
        result = CURLE_SEND_ERROR;
215
        goto out;
216
      }
217
      /* UDP datagram is too large; caused by PMTUD. Just let it be
218
         lost. */
219
    }
220
  }
221
#endif
222
  (void)cf;
223
224
out:
225
  return result;
226
}
227
228
#ifdef HAVE_SENDMSG
229
#define VQUIC_SEND_METHOD   "sendmsg"
230
#else
231
#define VQUIC_SEND_METHOD   "send"
232
#endif
233
234
static CURLcode send_packet_no_gso(struct Curl_cfilter *cf,
235
                                   struct Curl_easy *data,
236
                                   struct cf_quic_ctx *qctx,
237
                                   const uint8_t *pkt, size_t pktlen,
238
                                   size_t gsolen, size_t *psent)
239
{
240
  const uint8_t *p, *end = pkt + pktlen;
241
  size_t sent, len, calls = 0;
242
  CURLcode result = CURLE_OK;
243
244
  *psent = 0;
245
246
  for(p = pkt; p < end; p += gsolen) {
247
    len = CURLMIN(gsolen, (size_t)(end - p));
248
    result = do_sendmsg(cf, data, qctx, p, len, len, &sent);
249
    if(result)
250
      goto out;
251
    *psent += sent;
252
    ++calls;
253
  }
254
out:
255
  CURL_TRC_CF(data, cf, "vquic_%s(len=%zu, gso=%zu, calls=%zu)"
256
              " -> %d, sent=%zu",
257
              VQUIC_SEND_METHOD, pktlen, gsolen, calls, result, *psent);
258
  return result;
259
}
260
261
static CURLcode vquic_send_packets(struct Curl_cfilter *cf,
262
                                   struct Curl_easy *data,
263
                                   struct cf_quic_ctx *qctx,
264
                                   const uint8_t *pkt, size_t pktlen,
265
                                   size_t gsolen, size_t *psent)
266
{
267
  CURLcode result;
268
#ifdef DEBUGBUILD
269
  /* simulate network blocking/partial writes */
270
  if(qctx->wblock_percent > 0) {
271
    unsigned char c;
272
    *psent = 0;
273
    Curl_rand(data, &c, 1);
274
    if(c >= ((100 - qctx->wblock_percent) * 256 / 100)) {
275
      CURL_TRC_CF(data, cf, "vquic_flush() simulate EWOULDBLOCK");
276
      return CURLE_AGAIN;
277
    }
278
  }
279
#endif
280
  if(qctx->no_gso && pktlen > gsolen) {
281
    result = send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent);
282
  }
283
  else {
284
    result = do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent);
285
    CURL_TRC_CF(data, cf, "vquic_%s(len=%zu, gso=%zu, calls=1)"
286
                " -> %d, sent=%zu",
287
                VQUIC_SEND_METHOD, pktlen, gsolen, result, *psent);
288
  }
289
  if(!result)
290
    qctx->last_io = qctx->last_op;
291
  return result;
292
}
293
294
CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data,
295
                     struct cf_quic_ctx *qctx)
296
{
297
  const unsigned char *buf;
298
  size_t blen, sent;
299
  CURLcode result;
300
  size_t gsolen;
301
302
  while(Curl_bufq_peek(&qctx->sendbuf, &buf, &blen)) {
303
    gsolen = qctx->gsolen;
304
    if(qctx->split_len) {
305
      gsolen = qctx->split_gsolen;
306
      if(blen > qctx->split_len)
307
        blen = qctx->split_len;
308
    }
309
310
    result = vquic_send_packets(cf, data, qctx, buf, blen, gsolen, &sent);
311
    if(result) {
312
      if(result == CURLE_AGAIN) {
313
        Curl_bufq_skip(&qctx->sendbuf, sent);
314
        if(qctx->split_len)
315
          qctx->split_len -= sent;
316
      }
317
      return result;
318
    }
319
    Curl_bufq_skip(&qctx->sendbuf, sent);
320
    if(qctx->split_len)
321
      qctx->split_len -= sent;
322
  }
323
  return CURLE_OK;
324
}
325
326
CURLcode vquic_send(struct Curl_cfilter *cf, struct Curl_easy *data,
327
                    struct cf_quic_ctx *qctx, size_t gsolen)
328
{
329
  qctx->gsolen = gsolen;
330
  return vquic_flush(cf, data, qctx);
331
}
332
333
CURLcode vquic_send_tail_split(struct Curl_cfilter *cf, struct Curl_easy *data,
334
                               struct cf_quic_ctx *qctx, size_t gsolen,
335
                               size_t tail_len, size_t tail_gsolen)
336
{
337
  DEBUGASSERT(Curl_bufq_len(&qctx->sendbuf) > tail_len);
338
  qctx->split_len = Curl_bufq_len(&qctx->sendbuf) - tail_len;
339
  qctx->split_gsolen = gsolen;
340
  qctx->gsolen = tail_gsolen;
341
  CURL_TRC_CF(data, cf, "vquic_send_tail_split: [%zu gso=%zu][%zu gso=%zu]",
342
              qctx->split_len, qctx->split_gsolen, tail_len, qctx->gsolen);
343
  return vquic_flush(cf, data, qctx);
344
}
345
346
#if defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG)
347
static size_t vquic_msghdr_get_udp_gro(struct msghdr *msg)
348
{
349
  int gso_size = 0;
350
#if defined(__linux__) && defined(UDP_GRO)
351
  struct cmsghdr *cmsg;
352
353
  /* Workaround musl CMSG_NXTHDR issue */
354
#if defined(__clang__) && !defined(__GLIBC__)
355
#pragma clang diagnostic push
356
#pragma clang diagnostic ignored "-Wsign-compare"
357
#pragma clang diagnostic ignored "-Wcast-align"
358
#endif
359
  for(cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
360
#if defined(__clang__) && !defined(__GLIBC__)
361
#pragma clang diagnostic pop
362
#endif
363
    if(cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) {
364
      memcpy(&gso_size, CMSG_DATA(cmsg), sizeof(gso_size));
365
366
      break;
367
    }
368
  }
369
#endif
370
  (void)msg;
371
372
  return (size_t)gso_size;
373
}
374
#endif
375
376
#ifdef HAVE_SENDMMSG
377
static CURLcode recvmmsg_packets(struct Curl_cfilter *cf,
378
                                 struct Curl_easy *data,
379
                                 struct cf_quic_ctx *qctx,
380
                                 size_t max_pkts,
381
                                 vquic_recv_pkts_cb *recv_cb, void *userp)
382
{
383
#if defined(__linux__) && defined(UDP_GRO)
384
#define MMSG_NUM  16
385
#define UDP_GRO_CNT_MAX  64
386
#else
387
#define MMSG_NUM  64
388
#define UDP_GRO_CNT_MAX  1
389
#endif
390
#define MSG_BUF_SIZE  (UDP_GRO_CNT_MAX * 1500)
391
  struct iovec msg_iov[MMSG_NUM];
392
  struct mmsghdr mmsg[MMSG_NUM];
393
  uint8_t msg_ctrl[MMSG_NUM * CMSG_SPACE(sizeof(int))];
394
  struct sockaddr_storage remote_addr[MMSG_NUM];
395
  size_t total_nread = 0, pkts = 0, calls = 0;
396
  int mcount, i, n;
397
  char errstr[STRERROR_LEN];
398
  CURLcode result = CURLE_OK;
399
  size_t gso_size;
400
  char *sockbuf = NULL;
401
  uint8_t (*bufs)[MSG_BUF_SIZE] = NULL;
402
403
  DEBUGASSERT(max_pkts > 0);
404
  result = Curl_multi_xfer_sockbuf_borrow(data, MMSG_NUM * MSG_BUF_SIZE,
405
                                          &sockbuf);
406
  if(result)
407
    goto out;
408
  bufs = (uint8_t (*)[MSG_BUF_SIZE])sockbuf;
409
410
  total_nread = 0;
411
  while(pkts < max_pkts) {
412
    n = (int)CURLMIN(CURLMIN(MMSG_NUM, IOV_MAX), max_pkts);
413
    memset(&mmsg, 0, sizeof(mmsg));
414
    for(i = 0; i < n; ++i) {
415
      msg_iov[i].iov_base = bufs[i];
416
      msg_iov[i].iov_len = (int)sizeof(bufs[i]);
417
      mmsg[i].msg_hdr.msg_iov = &msg_iov[i];
418
      mmsg[i].msg_hdr.msg_iovlen = 1;
419
      mmsg[i].msg_hdr.msg_name = &remote_addr[i];
420
      mmsg[i].msg_hdr.msg_namelen = sizeof(remote_addr[i]);
421
      mmsg[i].msg_hdr.msg_control = &msg_ctrl[i * CMSG_SPACE(sizeof(int))];
422
      mmsg[i].msg_hdr.msg_controllen = CMSG_SPACE(sizeof(int));
423
    }
424
425
    while((mcount = recvmmsg(qctx->sockfd, mmsg, n, 0, NULL)) == -1 &&
426
          (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE))
427
      ;
428
    if(mcount == -1) {
429
      if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) {
430
        CURL_TRC_CF(data, cf, "ingress, recvmmsg -> EAGAIN");
431
        goto out;
432
      }
433
      if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) {
434
        struct ip_quadruple ip;
435
        if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip))
436
          failf(data, "QUIC: connection to %s port %u refused",
437
                ip.remote_ip, ip.remote_port);
438
        result = CURLE_COULDNT_CONNECT;
439
        goto out;
440
      }
441
      curlx_strerror(SOCKERRNO, errstr, sizeof(errstr));
442
      failf(data, "QUIC: recvmmsg() unexpectedly returned %d (errno=%d; %s)",
443
                  mcount, SOCKERRNO, errstr);
444
      result = CURLE_RECV_ERROR;
445
      goto out;
446
    }
447
448
    ++calls;
449
    for(i = 0; i < mcount; ++i) {
450
      /* A zero-length UDP packet is no QUIC packet. Ignore. */
451
      if(!mmsg[i].msg_len)
452
        continue;
453
      total_nread += mmsg[i].msg_len;
454
455
      gso_size = vquic_msghdr_get_udp_gro(&mmsg[i].msg_hdr);
456
      if(gso_size == 0)
457
        gso_size = mmsg[i].msg_len;
458
459
      result = recv_cb(bufs[i], mmsg[i].msg_len, gso_size,
460
                       mmsg[i].msg_hdr.msg_name,
461
                       mmsg[i].msg_hdr.msg_namelen, 0, userp);
462
      if(result)
463
        goto out;
464
      pkts += (mmsg[i].msg_len + gso_size - 1) / gso_size;
465
    }
466
  }
467
468
out:
469
  if(total_nread || result)
470
    CURL_TRC_CF(data, cf, "vquic_recvmmsg(len=%zu, packets=%zu, calls=%zu)"
471
                " -> %d", total_nread, pkts, calls, result);
472
  Curl_multi_xfer_sockbuf_release(data, sockbuf);
473
  return result;
474
}
475
476
#elif defined(HAVE_SENDMSG)
477
static CURLcode recvmsg_packets(struct Curl_cfilter *cf,
478
                                struct Curl_easy *data,
479
                                struct cf_quic_ctx *qctx,
480
                                size_t max_pkts,
481
                                vquic_recv_pkts_cb *recv_cb, void *userp)
482
{
483
  struct iovec msg_iov;
484
  struct msghdr msg;
485
  uint8_t buf[64 * 1024];
486
  struct sockaddr_storage remote_addr;
487
  size_t total_nread, pkts, calls;
488
  ssize_t rc;
489
  size_t nread;
490
  char errstr[STRERROR_LEN];
491
  CURLcode result = CURLE_OK;
492
  uint8_t msg_ctrl[CMSG_SPACE(sizeof(int))];
493
  size_t gso_size;
494
495
  DEBUGASSERT(max_pkts > 0);
496
  for(pkts = 0, total_nread = 0, calls = 0; pkts < max_pkts;) {
497
    /* fully initialise this on each call to `recvmsg()`. There seem to
498
     * operating systems out there that mess with `msg_iov.iov_len`. */
499
    memset(&msg, 0, sizeof(msg));
500
    msg_iov.iov_base = buf;
501
    msg_iov.iov_len = (int)sizeof(buf);
502
    msg.msg_iov = &msg_iov;
503
    msg.msg_iovlen = 1;
504
    msg.msg_control = msg_ctrl;
505
    msg.msg_name = &remote_addr;
506
    msg.msg_namelen = sizeof(remote_addr);
507
    msg.msg_controllen = sizeof(msg_ctrl);
508
509
    while((rc = recvmsg(qctx->sockfd, &msg, 0)) == -1 &&
510
          (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE))
511
      ;
512
    if(!curlx_sztouz(rc, &nread)) {
513
      if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) {
514
        goto out;
515
      }
516
      if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) {
517
        struct ip_quadruple ip;
518
        if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip))
519
          failf(data, "QUIC: connection to %s port %u refused",
520
                ip.remote_ip, ip.remote_port);
521
        result = CURLE_COULDNT_CONNECT;
522
        goto out;
523
      }
524
      curlx_strerror(SOCKERRNO, errstr, sizeof(errstr));
525
      failf(data, "QUIC: recvmsg() unexpectedly returned %zd (errno=%d; %s)",
526
            rc, SOCKERRNO, errstr);
527
      result = CURLE_RECV_ERROR;
528
      goto out;
529
    }
530
531
    total_nread += nread;
532
    ++calls;
533
534
    /* A 0-length UDP packet is no QUIC packet */
535
    if(!nread)
536
      continue;
537
538
    gso_size = vquic_msghdr_get_udp_gro(&msg);
539
    if(gso_size == 0)
540
      gso_size = nread;
541
542
    result = recv_cb(buf, nread, gso_size,
543
                     msg.msg_name, msg.msg_namelen, 0, userp);
544
    if(result)
545
      goto out;
546
    pkts += (nread + gso_size - 1) / gso_size;
547
  }
548
549
out:
550
  if(total_nread || result)
551
    CURL_TRC_CF(data, cf, "vquic_recvmsg(len=%zu, packets=%zu, calls=%zu)"
552
                " -> %d", total_nread, pkts, calls, result);
553
  return result;
554
}
555
556
#else /* HAVE_SENDMMSG || HAVE_SENDMSG */
557
static CURLcode recvfrom_packets(struct Curl_cfilter *cf,
558
                                 struct Curl_easy *data,
559
                                 struct cf_quic_ctx *qctx,
560
                                 size_t max_pkts,
561
                                 vquic_recv_pkts_cb *recv_cb, void *userp)
562
{
563
  uint8_t buf[64 * 1024];
564
  int bufsize = (int)sizeof(buf);
565
  struct sockaddr_storage remote_addr;
566
  socklen_t remote_addrlen = sizeof(remote_addr);
567
  size_t total_nread, pkts, calls = 0, nread;
568
  ssize_t rv;
569
  char errstr[STRERROR_LEN];
570
  CURLcode result = CURLE_OK;
571
572
  DEBUGASSERT(max_pkts > 0);
573
  for(pkts = 0, total_nread = 0; pkts < max_pkts;) {
574
    while((rv = recvfrom(qctx->sockfd, (char *)buf, bufsize, 0,
575
                         (struct sockaddr *)&remote_addr,
576
                         &remote_addrlen)) == -1 &&
577
          (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE))
578
      ;
579
    if(!curlx_sztouz(rv, &nread)) {
580
      if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) {
581
        CURL_TRC_CF(data, cf, "ingress, recvfrom -> EAGAIN");
582
        goto out;
583
      }
584
      if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) {
585
        struct ip_quadruple ip;
586
        if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip))
587
          failf(data, "QUIC: connection to %s port %u refused",
588
                ip.remote_ip, ip.remote_port);
589
        result = CURLE_COULDNT_CONNECT;
590
        goto out;
591
      }
592
      curlx_strerror(SOCKERRNO, errstr, sizeof(errstr));
593
      failf(data, "QUIC: recvfrom() unexpectedly returned %zd (errno=%d; %s)",
594
            rv, SOCKERRNO, errstr);
595
      result = CURLE_RECV_ERROR;
596
      goto out;
597
    }
598
599
    ++pkts;
600
    ++calls;
601
602
    /* A 0-length UDP packet is no QUIC packet */
603
    if(!nread)
604
      continue;
605
606
    total_nread += nread;
607
    result = recv_cb(buf, nread, nread, &remote_addr, remote_addrlen,
608
                     0, userp);
609
    if(result)
610
      goto out;
611
  }
612
613
out:
614
  if(total_nread || result)
615
    CURL_TRC_CF(data, cf, "vquic_recvfrom(len=%zu, packets=%zu, calls=%zu)"
616
                " -> %d", total_nread, pkts, calls, result);
617
  return result;
618
}
619
#endif /* !HAVE_SENDMMSG && !HAVE_SENDMSG */
620
621
CURLcode vquic_recv_packets(struct Curl_cfilter *cf,
622
                            struct Curl_easy *data,
623
                            struct cf_quic_ctx *qctx,
624
                            size_t max_pkts,
625
                            vquic_recv_pkts_cb *recv_cb, void *userp)
626
{
627
  CURLcode result;
628
#ifdef HAVE_SENDMMSG
629
  result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp);
630
#elif defined(HAVE_SENDMSG)
631
  result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp);
632
#else
633
  result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp);
634
#endif
635
  if(!result) {
636
    if(!qctx->got_first_byte) {
637
      qctx->got_first_byte = TRUE;
638
      qctx->first_byte_at = qctx->last_op;
639
    }
640
    qctx->last_io = qctx->last_op;
641
  }
642
  return result;
643
}
644
645
/*
646
 * If the QLOGDIR environment variable is set, open and return a file
647
 * descriptor to write the log to.
648
 *
649
 * This function returns error if something failed outside of failing to
650
 * create the file. Open file success is deemed by seeing if the returned fd
651
 * is != -1.
652
 */
653
CURLcode Curl_qlogdir(struct Curl_easy *data,
654
                      unsigned char *scid,
655
                      size_t scidlen,
656
                      int *qlogfdp)
657
{
658
  char *qlog_dir = curl_getenv("QLOGDIR");
659
  *qlogfdp = -1;
660
  if(qlog_dir) {
661
    struct dynbuf fname;
662
    CURLcode result;
663
    unsigned int i;
664
    curlx_dyn_init(&fname, DYN_QLOG_NAME);
665
    result = curlx_dyn_add(&fname, qlog_dir);
666
    if(!result)
667
      result = curlx_dyn_add(&fname, "/");
668
    for(i = 0; (i < scidlen) && !result; i++) {
669
      char hex[3];
670
      curl_msnprintf(hex, 3, "%02x", scid[i]);
671
      result = curlx_dyn_add(&fname, hex);
672
    }
673
    if(!result)
674
      result = curlx_dyn_add(&fname, ".sqlog");
675
676
    if(!result) {
677
      int qlogfd = curlx_open(curlx_dyn_ptr(&fname),
678
                              O_WRONLY | O_CREAT | CURL_O_BINARY,
679
                              data->set.new_file_perms
680
#ifdef _WIN32
681
                              & (_S_IREAD | _S_IWRITE)
682
#endif
683
                              );
684
      if(qlogfd != -1)
685
        *qlogfdp = qlogfd;
686
    }
687
    curlx_dyn_free(&fname);
688
    curlx_free(qlog_dir);
689
    if(result)
690
      return result;
691
  }
692
693
  return CURLE_OK;
694
}
695
696
CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
697
                             struct Curl_easy *data,
698
                             struct connectdata *conn,
699
                             const struct Curl_addrinfo *ai,
700
                             uint8_t transport)
701
{
702
  (void)transport;
703
  DEBUGASSERT(transport == TRNSPRT_QUIC);
704
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
705
  return Curl_cf_ngtcp2_create(pcf, data, conn, ai);
706
#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3)
707
  return Curl_cf_osslq_create(pcf, data, conn, ai);
708
#elif defined(USE_QUICHE)
709
  return Curl_cf_quiche_create(pcf, data, conn, ai);
710
#else
711
  *pcf = NULL;
712
  (void)data;
713
  (void)conn;
714
  (void)ai;
715
  return CURLE_NOT_BUILT_IN;
716
#endif
717
}
718
719
CURLcode Curl_conn_may_http3(struct Curl_easy *data,
720
                             const struct connectdata *conn,
721
                             unsigned char transport)
722
{
723
  if(transport == TRNSPRT_UNIX) {
724
    /* cannot do QUIC over a Unix domain socket */
725
    return CURLE_QUIC_CONNECT_ERROR;
726
  }
727
  if(!(conn->handler->flags & PROTOPT_SSL)) {
728
    failf(data, "HTTP/3 requested for non-HTTPS URL");
729
    return CURLE_URL_MALFORMAT;
730
  }
731
#ifndef CURL_DISABLE_PROXY
732
  if(conn->bits.socksproxy) {
733
    failf(data, "HTTP/3 is not supported over a SOCKS proxy");
734
    return CURLE_URL_MALFORMAT;
735
  }
736
  if(conn->bits.httpproxy && conn->bits.tunnel_proxy) {
737
    failf(data, "HTTP/3 is not supported over an HTTP proxy");
738
    return CURLE_URL_MALFORMAT;
739
  }
740
#endif
741
742
  return CURLE_OK;
743
}
744
745
#if defined(USE_NGTCP2) || defined(USE_NGHTTP3)
746
747
static void *vquic_ngtcp2_malloc(size_t size, void *user_data)
748
{
749
  (void)user_data;
750
  return Curl_cmalloc(size);
751
}
752
753
static void vquic_ngtcp2_free(void *ptr, void *user_data)
754
{
755
  (void)user_data;
756
  Curl_cfree(ptr);
757
}
758
759
static void *vquic_ngtcp2_calloc(size_t nmemb, size_t size, void *user_data)
760
{
761
  (void)user_data;
762
  return Curl_ccalloc(nmemb, size);
763
}
764
765
static void *vquic_ngtcp2_realloc(void *ptr, size_t size, void *user_data)
766
{
767
  (void)user_data;
768
  return Curl_crealloc(ptr, size);
769
}
770
771
#ifdef USE_NGTCP2
772
static struct ngtcp2_mem vquic_ngtcp2_mem = {
773
  NULL,
774
  vquic_ngtcp2_malloc,
775
  vquic_ngtcp2_free,
776
  vquic_ngtcp2_calloc,
777
  vquic_ngtcp2_realloc
778
};
779
struct ngtcp2_mem *Curl_ngtcp2_mem(void)
780
{
781
  return &vquic_ngtcp2_mem;
782
}
783
#endif
784
785
#ifdef USE_NGHTTP3
786
static struct nghttp3_mem vquic_nghttp3_mem = {
787
  NULL,
788
  vquic_ngtcp2_malloc,
789
  vquic_ngtcp2_free,
790
  vquic_ngtcp2_calloc,
791
  vquic_ngtcp2_realloc
792
};
793
struct nghttp3_mem *Curl_nghttp3_mem(void)
794
{
795
  return &vquic_nghttp3_mem;
796
}
797
#endif
798
799
#endif /* USE_NGTCP2 || USE_NGHTTP3 */
800
801
#else /* CURL_DISABLE_HTTP || !USE_HTTP3 */
802
803
CURLcode Curl_conn_may_http3(struct Curl_easy *data,
804
                             const struct connectdata *conn,
805
                             unsigned char transport)
806
0
{
807
0
  (void)conn;
808
0
  (void)data;
809
0
  (void)transport;
810
0
  DEBUGF(infof(data, "QUIC is not supported in this build"));
811
0
  return CURLE_NOT_BUILT_IN;
812
0
}
813
814
#endif /* !CURL_DISABLE_HTTP && USE_HTTP3 */