Coverage Report

Created: 2025-12-04 07:04

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