Coverage Report

Created: 2025-10-10 06:09

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