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