VirtualBox

source: vbox/trunk/src/libs/curl-8.0.1/lib/vquic/curl_quiche.c@ 100906

Last change on this file since 100906 was 99459, checked in by vboxsync, 2 years ago

setting missed svn:sync-process property on curl-8.0.1 files

File size: 41.5 KB
Line 
1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <[email protected]>, 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 USE_QUICHE
28#include <quiche.h>
29#include <openssl/err.h>
30#include <openssl/ssl.h>
31#include "urldata.h"
32#include "cfilters.h"
33#include "cf-socket.h"
34#include "sendf.h"
35#include "strdup.h"
36#include "rand.h"
37#include "strcase.h"
38#include "multiif.h"
39#include "connect.h"
40#include "progress.h"
41#include "strerror.h"
42#include "vquic.h"
43#include "vquic_int.h"
44#include "curl_quiche.h"
45#include "transfer.h"
46#include "h2h3.h"
47#include "vtls/openssl.h"
48#include "vtls/keylog.h"
49
50/* The last 3 #include files should be in this order */
51#include "curl_printf.h"
52#include "curl_memory.h"
53#include "memdebug.h"
54
55
56#define QUIC_MAX_STREAMS (256*1024)
57#define QUIC_MAX_DATA (1*1024*1024)
58#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */
59
60/* how many UDP packets to send max in one call */
61#define MAX_PKT_BURST 10
62#define MAX_UDP_PAYLOAD_SIZE 1452
63
64/*
65 * Store quiche version info in this buffer.
66 */
67void Curl_quiche_ver(char *p, size_t len)
68{
69 (void)msnprintf(p, len, "quiche/%s", quiche_version());
70}
71
72static void keylog_callback(const SSL *ssl, const char *line)
73{
74 (void)ssl;
75 Curl_tls_keylog_write_line(line);
76}
77
78static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
79{
80 SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
81
82 SSL_CTX_set_alpn_protos(ssl_ctx,
83 (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL,
84 sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1);
85
86 SSL_CTX_set_default_verify_paths(ssl_ctx);
87
88 /* Open the file if a TLS or QUIC backend has not done this before. */
89 Curl_tls_keylog_open();
90 if(Curl_tls_keylog_enabled()) {
91 SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
92 }
93
94 {
95 struct connectdata *conn = data->conn;
96 if(conn->ssl_config.verifypeer) {
97 const char * const ssl_cafile = conn->ssl_config.CAfile;
98 const char * const ssl_capath = conn->ssl_config.CApath;
99 if(ssl_cafile || ssl_capath) {
100 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
101 /* tell OpenSSL where to find CA certificates that are used to verify
102 the server's certificate. */
103 if(!SSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) {
104 /* Fail if we insist on successfully verifying the server. */
105 failf(data, "error setting certificate verify locations:"
106 " CAfile: %s CApath: %s",
107 ssl_cafile ? ssl_cafile : "none",
108 ssl_capath ? ssl_capath : "none");
109 return NULL;
110 }
111 infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
112 infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
113 }
114#ifdef CURL_CA_FALLBACK
115 else {
116 /* verifying the peer without any CA certificates won't work so
117 use openssl's built-in default as fallback */
118 SSL_CTX_set_default_verify_paths(ssl_ctx);
119 }
120#endif
121 }
122 }
123 return ssl_ctx;
124}
125
126struct quic_handshake {
127 char *buf; /* pointer to the buffer */
128 size_t alloclen; /* size of allocation */
129 size_t len; /* size of content in buffer */
130 size_t nread; /* how many bytes have been read */
131};
132
133struct h3_event_node {
134 struct h3_event_node *next;
135 quiche_h3_event *ev;
136};
137
138struct cf_quiche_ctx {
139 struct cf_quic_ctx q;
140 quiche_conn *qconn;
141 quiche_config *cfg;
142 quiche_h3_conn *h3c;
143 quiche_h3_config *h3config;
144 uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
145 SSL_CTX *sslctx;
146 SSL *ssl;
147 struct curltime started_at; /* time the current attempt started */
148 struct curltime handshake_at; /* time connect handshake finished */
149 struct curltime first_byte_at; /* when first byte was recvd */
150 struct curltime reconnect_at; /* time the next attempt should start */
151 BIT(goaway); /* got GOAWAY from server */
152 BIT(got_first_byte); /* if first byte was received */
153};
154
155
156#ifdef DEBUG_QUICHE
157static void quiche_debug_log(const char *line, void *argp)
158{
159 (void)argp;
160 fprintf(stderr, "%s\n", line);
161}
162#endif
163
164static void h3_clear_pending(struct Curl_easy *data)
165{
166 struct HTTP *stream = data->req.p.http;
167
168 if(stream->pending) {
169 struct h3_event_node *node, *next;
170 for(node = stream->pending; node; node = next) {
171 next = node->next;
172 quiche_h3_event_free(node->ev);
173 free(node);
174 }
175 stream->pending = NULL;
176 }
177}
178
179static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx)
180{
181 if(ctx) {
182 vquic_ctx_free(&ctx->q);
183 if(ctx->qconn)
184 quiche_conn_free(ctx->qconn);
185 if(ctx->h3config)
186 quiche_h3_config_free(ctx->h3config);
187 if(ctx->h3c)
188 quiche_h3_conn_free(ctx->h3c);
189 if(ctx->cfg)
190 quiche_config_free(ctx->cfg);
191 memset(ctx, 0, sizeof(*ctx));
192 }
193}
194
195static void notify_drain(struct Curl_cfilter *cf,
196 struct Curl_easy *data)
197{
198 (void)cf;
199 data->state.drain = 1;
200 Curl_expire(data, 0, EXPIRE_RUN_NOW);
201}
202
203static CURLcode h3_add_event(struct Curl_cfilter *cf,
204 struct Curl_easy *data,
205 int64_t stream3_id, quiche_h3_event *ev)
206{
207 struct Curl_easy *mdata;
208 struct h3_event_node *node, **pnext;
209
210 DEBUGASSERT(data->multi);
211 for(mdata = data->multi->easyp; mdata; mdata = mdata->next) {
212 if(mdata->req.p.http && mdata->req.p.http->stream3_id == stream3_id) {
213 break;
214 }
215 }
216
217 if(!mdata) {
218 DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] event discarded, easy handle "
219 "not found", stream3_id));
220 quiche_h3_event_free(ev);
221 return CURLE_OK;
222 }
223
224 node = calloc(sizeof(*node), 1);
225 if(!node) {
226 quiche_h3_event_free(ev);
227 return CURLE_OUT_OF_MEMORY;
228 }
229 node->ev = ev;
230 /* append to process them in order of arrival */
231 pnext = &mdata->req.p.http->pending;
232 while(*pnext) {
233 pnext = &((*pnext)->next);
234 }
235 *pnext = node;
236 notify_drain(cf, mdata);
237 return CURLE_OK;
238}
239
240struct h3h1header {
241 char *dest;
242 size_t destlen; /* left to use */
243 size_t nlen; /* used */
244};
245
246static int cb_each_header(uint8_t *name, size_t name_len,
247 uint8_t *value, size_t value_len,
248 void *argp)
249{
250 struct h3h1header *headers = (struct h3h1header *)argp;
251 size_t olen = 0;
252
253 if((name_len == 7) && !strncmp(H2H3_PSEUDO_STATUS, (char *)name, 7)) {
254 msnprintf(headers->dest,
255 headers->destlen, "HTTP/3 %.*s \r\n",
256 (int) value_len, value);
257 }
258 else if(!headers->nlen) {
259 return CURLE_HTTP3;
260 }
261 else {
262 msnprintf(headers->dest,
263 headers->destlen, "%.*s: %.*s\r\n",
264 (int)name_len, name, (int) value_len, value);
265 }
266 olen = strlen(headers->dest);
267 headers->destlen -= olen;
268 headers->nlen += olen;
269 headers->dest += olen;
270 return 0;
271}
272
273static ssize_t cf_recv_body(struct Curl_cfilter *cf,
274 struct Curl_easy *data,
275 char *buf, size_t len,
276 CURLcode *err)
277{
278 struct cf_quiche_ctx *ctx = cf->ctx;
279 struct HTTP *stream = data->req.p.http;
280 ssize_t nread;
281 size_t offset = 0;
282
283 if(!stream->firstbody) {
284 /* add a header-body separator CRLF */
285 offset = 2;
286 }
287 nread = quiche_h3_recv_body(ctx->h3c, ctx->qconn, stream->stream3_id,
288 (unsigned char *)buf + offset, len - offset);
289 if(nread >= 0) {
290 DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][DATA] len=%zd",
291 stream->stream3_id, nread));
292 if(!stream->firstbody) {
293 stream->firstbody = TRUE;
294 buf[0] = '\r';
295 buf[1] = '\n';
296 nread += offset;
297 }
298 }
299 else if(nread == -1) {
300 *err = CURLE_AGAIN;
301 stream->h3_recving_data = FALSE;
302 }
303 else {
304 failf(data, "Error %zd in HTTP/3 response body for stream[%"PRId64"]",
305 nread, stream->stream3_id);
306 stream->closed = TRUE;
307 stream->reset = TRUE;
308 streamclose(cf->conn, "Reset of stream");
309 stream->h3_recving_data = FALSE;
310 nread = -1;
311 *err = stream->h3_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
312 }
313 return nread;
314}
315
316#ifdef DEBUGBUILD
317static const char *cf_ev_name(quiche_h3_event *ev)
318{
319 switch(quiche_h3_event_type(ev)) {
320 case QUICHE_H3_EVENT_HEADERS:
321 return "HEADERS";
322 case QUICHE_H3_EVENT_DATA:
323 return "DATA";
324 case QUICHE_H3_EVENT_RESET:
325 return "RESET";
326 case QUICHE_H3_EVENT_FINISHED:
327 return "FINISHED";
328 case QUICHE_H3_EVENT_GOAWAY:
329 return "GOAWAY";
330 default:
331 return "Unknown";
332 }
333}
334#else
335#define cf_ev_name(x) ""
336#endif
337
338static ssize_t h3_process_event(struct Curl_cfilter *cf,
339 struct Curl_easy *data,
340 char *buf, size_t len,
341 int64_t stream3_id,
342 quiche_h3_event *ev,
343 CURLcode *err)
344{
345 struct HTTP *stream = data->req.p.http;
346 ssize_t recvd = 0;
347 int rc;
348 struct h3h1header headers;
349
350 DEBUGASSERT(stream3_id == stream->stream3_id);
351
352 *err = CURLE_OK;
353 switch(quiche_h3_event_type(ev)) {
354 case QUICHE_H3_EVENT_HEADERS:
355 stream->h3_got_header = TRUE;
356 headers.dest = buf;
357 headers.destlen = len;
358 headers.nlen = 0;
359 rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers);
360 if(rc) {
361 failf(data, "Error %d in HTTP/3 response header for stream[%"PRId64"]",
362 rc, stream3_id);
363 *err = CURLE_RECV_ERROR;
364 recvd = -1;
365 break;
366 }
367 recvd = headers.nlen;
368 DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][HEADERS] len=%zd",
369 stream3_id, recvd));
370 break;
371
372 case QUICHE_H3_EVENT_DATA:
373 DEBUGASSERT(!stream->closed);
374 stream->h3_recving_data = TRUE;
375 recvd = cf_recv_body(cf, data, buf, len, err);
376 if(recvd < 0) {
377 if(*err != CURLE_AGAIN)
378 return -1;
379 recvd = 0;
380 }
381 break;
382
383 case QUICHE_H3_EVENT_RESET:
384 DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][RESET]", stream3_id));
385 stream->closed = TRUE;
386 stream->reset = TRUE;
387 /* streamclose(cf->conn, "Reset of stream");*/
388 stream->h3_recving_data = FALSE;
389 break;
390
391 case QUICHE_H3_EVENT_FINISHED:
392 DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][FINISHED]", stream3_id));
393 stream->closed = TRUE;
394 /* streamclose(cf->conn, "End of stream");*/
395 stream->h3_recving_data = FALSE;
396 break;
397
398 case QUICHE_H3_EVENT_GOAWAY:
399 DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][GOAWAY]", stream3_id));
400 break;
401
402 default:
403 DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] recv, unhandled event %d",
404 stream3_id, quiche_h3_event_type(ev)));
405 break;
406 }
407 return recvd;
408}
409
410static ssize_t h3_process_pending(struct Curl_cfilter *cf,
411 struct Curl_easy *data,
412 char *buf, size_t len,
413 CURLcode *err)
414{
415 struct HTTP *stream = data->req.p.http;
416 struct h3_event_node *node = stream->pending, **pnext = &stream->pending;
417 ssize_t recvd = 0, erecvd;
418
419 *err = CURLE_OK;
420 DEBUGASSERT(stream);
421 while(node && len) {
422 erecvd = h3_process_event(cf, data, buf, len,
423 stream->stream3_id, node->ev, err);
424 quiche_h3_event_free(node->ev);
425 *pnext = node->next;
426 free(node);
427 node = *pnext;
428 if(erecvd < 0) {
429 DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] process event -> %d",
430 stream->stream3_id, *err));
431 return erecvd;
432 }
433 recvd += erecvd;
434 *err = CURLE_OK;
435 buf += erecvd;
436 len -= erecvd;
437 }
438 return recvd;
439}
440
441static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
442 struct Curl_easy *data)
443{
444 struct cf_quiche_ctx *ctx = cf->ctx;
445 int64_t stream3_id = data->req.p.http? data->req.p.http->stream3_id : -1;
446 uint8_t buf[65536];
447 int bufsize = (int)sizeof(buf);
448 struct sockaddr_storage remote_addr;
449 socklen_t remote_addrlen;
450 quiche_recv_info recv_info;
451 ssize_t recvd, nread;
452 ssize_t total = 0, pkts = 0;
453
454 DEBUGASSERT(ctx->qconn);
455
456 /* in case the timeout expired */
457 quiche_conn_on_timeout(ctx->qconn);
458
459 do {
460 remote_addrlen = sizeof(remote_addr);
461 while((recvd = recvfrom(ctx->q.sockfd, (char *)buf, bufsize, 0,
462 (struct sockaddr *)&remote_addr,
463 &remote_addrlen)) == -1 &&
464 SOCKERRNO == EINTR)
465 ;
466 if(recvd < 0) {
467 if((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK)) {
468 break;
469 }
470 if(SOCKERRNO == ECONNREFUSED) {
471 const char *r_ip;
472 int r_port;
473 Curl_cf_socket_peek(cf->next, data, NULL, NULL,
474 &r_ip, &r_port, NULL, NULL);
475 failf(data, "quiche: connection to %s:%u refused",
476 r_ip, r_port);
477 return CURLE_COULDNT_CONNECT;
478 }
479 failf(data, "quiche: recvfrom() unexpectedly returned %zd "
480 "(errno: %d, socket %d)", recvd, SOCKERRNO, ctx->q.sockfd);
481 return CURLE_RECV_ERROR;
482 }
483
484 total += recvd;
485 ++pkts;
486 if(recvd > 0 && !ctx->got_first_byte) {
487 ctx->first_byte_at = Curl_now();
488 ctx->got_first_byte = TRUE;
489 }
490 recv_info.from = (struct sockaddr *) &remote_addr;
491 recv_info.from_len = remote_addrlen;
492 recv_info.to = (struct sockaddr *) &ctx->q.local_addr;
493 recv_info.to_len = ctx->q.local_addrlen;
494
495 nread = quiche_conn_recv(ctx->qconn, buf, recvd, &recv_info);
496 if(nread < 0) {
497 if(QUICHE_ERR_DONE == nread) {
498 DEBUGF(LOG_CF(data, cf, "ingress, quiche is DONE"));
499 return CURLE_OK;
500 }
501 else if(QUICHE_ERR_TLS_FAIL == nread) {
502 long verify_ok = SSL_get_verify_result(ctx->ssl);
503 if(verify_ok != X509_V_OK) {
504 failf(data, "SSL certificate problem: %s",
505 X509_verify_cert_error_string(verify_ok));
506 return CURLE_PEER_FAILED_VERIFICATION;
507 }
508 }
509 else {
510 failf(data, "quiche_conn_recv() == %zd", nread);
511 return CURLE_RECV_ERROR;
512 }
513 }
514 else if(nread < recvd) {
515 DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] ingress, quiche only "
516 "accepted %zd/%zd bytes",
517 stream3_id, nread, recvd));
518 }
519
520 } while(pkts < 1000); /* arbitrary */
521
522 DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] ingress, recvd %zd bytes "
523 "in %zd packets", stream3_id, total, pkts));
524 return CURLE_OK;
525}
526
527/*
528 * flush_egress drains the buffers and sends off data.
529 * Calls failf() on errors.
530 */
531static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
532 struct Curl_easy *data)
533{
534 struct cf_quiche_ctx *ctx = cf->ctx;
535 int64_t stream3_id = data->req.p.http? data->req.p.http->stream3_id : -1;
536 quiche_send_info send_info;
537 ssize_t outlen, total_len = 0;
538 size_t max_udp_payload_size =
539 quiche_conn_max_send_udp_payload_size(ctx->qconn);
540 size_t gsolen = max_udp_payload_size;
541 size_t sent, pktcnt = 0;
542 CURLcode result;
543 int64_t timeout_ns;
544
545 ctx->q.no_gso = TRUE;
546 if(ctx->q.num_blocked_pkt) {
547 result = vquic_send_blocked_pkt(cf, data, &ctx->q);
548 if(result) {
549 if(result == CURLE_AGAIN) {
550 DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] egress, still not "
551 "able to send blocked packet", stream3_id));
552 Curl_expire(data, 1, EXPIRE_QUIC);
553 return CURLE_OK;
554 }
555 goto out;
556 }
557 }
558
559 for(;;) {
560 outlen = quiche_conn_send(ctx->qconn, ctx->q.pktbuf, max_udp_payload_size,
561 &send_info);
562 if(outlen == QUICHE_ERR_DONE) {
563 result = CURLE_OK;
564 goto out;
565 }
566
567 if(outlen < 0) {
568 failf(data, "quiche_conn_send returned %zd", outlen);
569 result = CURLE_SEND_ERROR;
570 goto out;
571 }
572
573 /* send the pktbuf *before* the last addition */
574 result = vquic_send_packet(cf, data, &ctx->q, ctx->q.pktbuf,
575 outlen, gsolen, &sent);
576 ++pktcnt;
577 total_len += outlen;
578 if(result) {
579 if(result == CURLE_AGAIN) {
580 /* blocked, add the pktbuf *before* and *at* the last addition
581 * separately to the blocked packages */
582 DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] egress, pushing blocked "
583 "packet with %zd bytes", stream3_id, outlen));
584 vquic_push_blocked_pkt(cf, &ctx->q, ctx->q.pktbuf, outlen, gsolen);
585 Curl_expire(data, 1, EXPIRE_QUIC);
586 return CURLE_OK;
587 }
588 goto out;
589 }
590 }
591
592out:
593 timeout_ns = quiche_conn_timeout_as_nanos(ctx->qconn);
594 if(timeout_ns % 1000000)
595 timeout_ns += 1000000;
596 /* expire resolution is milliseconds */
597 Curl_expire(data, (timeout_ns / 1000000), EXPIRE_QUIC);
598 if(pktcnt)
599 DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] egress, sent %zd packets "
600 "with %zd bytes", stream3_id, pktcnt, total_len));
601 return result;
602}
603
604static ssize_t recv_closed_stream(struct Curl_cfilter *cf,
605 struct Curl_easy *data,
606 CURLcode *err)
607{
608 struct HTTP *stream = data->req.p.http;
609 ssize_t nread = -1;
610
611 if(stream->reset) {
612 failf(data,
613 "HTTP/3 stream %" PRId64 " reset by server", stream->stream3_id);
614 *err = stream->h3_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
615 DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, was reset -> %d",
616 stream->stream3_id, *err));
617 goto out;
618 }
619
620 if(!stream->h3_got_header) {
621 failf(data,
622 "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting"
623 " all response header fields, treated as error",
624 stream->stream3_id);
625 /* *err = CURLE_PARTIAL_FILE; */
626 *err = CURLE_RECV_ERROR;
627 DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed incomplete"
628 " -> %d", stream->stream3_id, *err));
629 goto out;
630 }
631 else {
632 DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed ok"
633 " -> %d", stream->stream3_id, *err));
634 }
635 *err = CURLE_OK;
636 nread = 0;
637
638out:
639 return nread;
640}
641
642static CURLcode cf_poll_events(struct Curl_cfilter *cf,
643 struct Curl_easy *data)
644{
645 struct cf_quiche_ctx *ctx = cf->ctx;
646 struct HTTP *stream = data->req.p.http;
647 quiche_h3_event *ev;
648
649 /* Take in the events and distribute them to the transfers. */
650 while(1) {
651 int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev);
652 if(stream3_id < 0) {
653 /* nothing more to do */
654 break;
655 }
656 DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] recv, queue event %s "
657 "for [h3sid=%"PRId64"]",
658 stream? stream->stream3_id : -1, cf_ev_name(ev),
659 stream3_id));
660 if(h3_add_event(cf, data, stream3_id, ev) != CURLE_OK) {
661 return CURLE_OUT_OF_MEMORY;
662 }
663 }
664 return CURLE_OK;
665}
666
667static ssize_t cf_recv_transfer_data(struct Curl_cfilter *cf,
668 struct Curl_easy *data,
669 char *buf, size_t len,
670 CURLcode *err)
671{
672 struct HTTP *stream = data->req.p.http;
673 ssize_t recvd = -1;
674 size_t offset = 0;
675
676 if(stream->h3_recving_data) {
677 /* try receiving body first */
678 recvd = cf_recv_body(cf, data, buf, len, err);
679 if(recvd < 0) {
680 if(*err != CURLE_AGAIN)
681 return -1;
682 recvd = 0;
683 }
684 if(recvd > 0) {
685 offset = recvd;
686 }
687 }
688
689 if(offset < len && stream->pending) {
690 /* process any pending events for `data` first. if there are,
691 * return so the transfer can handle those. We do not want to
692 * progress ingress while events are pending here. */
693 recvd = h3_process_pending(cf, data, buf + offset, len - offset, err);
694 if(recvd < 0) {
695 if(*err != CURLE_AGAIN)
696 return -1;
697 recvd = 0;
698 }
699 if(recvd > 0) {
700 offset += recvd;
701 }
702 }
703
704 if(offset) {
705 *err = CURLE_OK;
706 return offset;
707 }
708 *err = CURLE_AGAIN;
709 return 0;
710}
711
712static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
713 char *buf, size_t len, CURLcode *err)
714{
715 struct HTTP *stream = data->req.p.http;
716 ssize_t recvd = -1;
717
718 *err = CURLE_AGAIN;
719
720 recvd = cf_recv_transfer_data(cf, data, buf, len, err);
721 if(recvd)
722 goto out;
723 if(stream->closed) {
724 recvd = recv_closed_stream(cf, data, err);
725 goto out;
726 }
727
728 /* we did get nothing from the quiche buffers or pending events.
729 * Take in more data from the connection, any error is fatal */
730 if(cf_process_ingress(cf, data)) {
731 DEBUGF(LOG_CF(data, cf, "h3_stream_recv returns on ingress"));
732 *err = CURLE_RECV_ERROR;
733 recvd = -1;
734 goto out;
735 }
736 /* poll quiche and distribute the events to the transfers */
737 *err = cf_poll_events(cf, data);
738 if(*err) {
739 recvd = -1;
740 goto out;
741 }
742
743 /* try to receive again for this transfer */
744 recvd = cf_recv_transfer_data(cf, data, buf, len, err);
745 if(recvd)
746 goto out;
747 if(stream->closed) {
748 recvd = recv_closed_stream(cf, data, err);
749 goto out;
750 }
751 recvd = -1;
752 *err = CURLE_AGAIN;
753 data->state.drain = 0;
754
755out:
756 if(cf_flush_egress(cf, data)) {
757 DEBUGF(LOG_CF(data, cf, "cf_recv, flush egress failed"));
758 *err = CURLE_SEND_ERROR;
759 return -1;
760 }
761 DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] cf_recv -> %zd, err=%d",
762 stream->stream3_id, recvd, *err));
763 if(recvd > 0)
764 notify_drain(cf, data);
765 return recvd;
766}
767
768/* Index where :authority header field will appear in request header
769 field list. */
770#define AUTHORITY_DST_IDX 3
771
772static CURLcode cf_http_request(struct Curl_cfilter *cf,
773 struct Curl_easy *data,
774 const void *mem,
775 size_t len)
776{
777 struct cf_quiche_ctx *ctx = cf->ctx;
778 struct HTTP *stream = data->req.p.http;
779 size_t nheader;
780 int64_t stream3_id;
781 quiche_h3_header *nva = NULL;
782 CURLcode result = CURLE_OK;
783 struct h2h3req *hreq = NULL;
784
785 stream->h3req = TRUE; /* send off! */
786 stream->closed = FALSE;
787 stream->reset = FALSE;
788
789 result = Curl_pseudo_headers(data, mem, len, NULL, &hreq);
790 if(result)
791 goto fail;
792 nheader = hreq->entries;
793
794 nva = malloc(sizeof(quiche_h3_header) * nheader);
795 if(!nva) {
796 result = CURLE_OUT_OF_MEMORY;
797 goto fail;
798 }
799 else {
800 unsigned int i;
801 for(i = 0; i < nheader; i++) {
802 nva[i].name = (unsigned char *)hreq->header[i].name;
803 nva[i].name_len = hreq->header[i].namelen;
804 nva[i].value = (unsigned char *)hreq->header[i].value;
805 nva[i].value_len = hreq->header[i].valuelen;
806 }
807 }
808
809 switch(data->state.httpreq) {
810 case HTTPREQ_POST:
811 case HTTPREQ_POST_FORM:
812 case HTTPREQ_POST_MIME:
813 case HTTPREQ_PUT:
814 if(data->state.infilesize != -1)
815 stream->upload_left = data->state.infilesize;
816 else
817 /* data sending without specifying the data amount up front */
818 stream->upload_left = -1; /* unknown, but not zero */
819
820 stream->upload_done = !stream->upload_left;
821 stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader,
822 stream->upload_done);
823 break;
824 default:
825 stream->upload_left = 0;
826 stream->upload_done = TRUE;
827 stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader,
828 TRUE);
829 break;
830 }
831
832 Curl_safefree(nva);
833
834 if(stream3_id < 0) {
835 if(QUICHE_H3_ERR_STREAM_BLOCKED == stream3_id) {
836 DEBUGF(LOG_CF(data, cf, "send_request(%s, body_len=%ld) rejected "
837 "with H3_ERR_STREAM_BLOCKED",
838 data->state.url, (long)stream->upload_left));
839 result = CURLE_AGAIN;
840 goto fail;
841 }
842 else {
843 DEBUGF(LOG_CF(data, cf, "send_request(%s, body_len=%ld) -> %" PRId64,
844 data->state.url, (long)stream->upload_left, stream3_id));
845 }
846 result = CURLE_SEND_ERROR;
847 goto fail;
848 }
849
850 stream->stream3_id = stream3_id;
851 infof(data, "Using HTTP/3 Stream ID: %" PRId64 " (easy handle %p)",
852 stream3_id, (void *)data);
853 DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] opened for %s",
854 stream3_id, data->state.url));
855
856 Curl_pseudo_free(hreq);
857 return CURLE_OK;
858
859fail:
860 free(nva);
861 Curl_pseudo_free(hreq);
862 return result;
863}
864
865static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data,
866 const void *buf, size_t len, CURLcode *err)
867{
868 struct cf_quiche_ctx *ctx = cf->ctx;
869 struct HTTP *stream = data->req.p.http;
870 ssize_t nwritten;
871
872 DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_send(len=%zu) start",
873 stream->h3req? stream->stream3_id : -1, len));
874 *err = cf_process_ingress(cf, data);
875 if(*err)
876 return -1;
877
878 if(!stream->h3req) {
879 CURLcode result = cf_http_request(cf, data, buf, len);
880 if(result) {
881 *err = result;
882 return -1;
883 }
884 nwritten = len;
885 }
886 else {
887 nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->stream3_id,
888 (uint8_t *)buf, len, FALSE);
889 DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] send body(len=%zu) -> %zd",
890 stream->stream3_id, len, nwritten));
891 if(nwritten == QUICHE_H3_ERR_DONE) {
892 /* no error, nothing to do (flow control?) */
893 *err = CURLE_AGAIN;
894 nwritten = -1;
895 }
896 else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) {
897 DEBUGF(LOG_CF(data, cf, "send_body(len=%zu) -> exceeds size", len));
898 *err = CURLE_SEND_ERROR;
899 nwritten = -1;
900 }
901 else if(nwritten < 0) {
902 DEBUGF(LOG_CF(data, cf, "send_body(len=%zu) -> SEND_ERROR", len));
903 *err = CURLE_SEND_ERROR;
904 nwritten = -1;
905 }
906 else {
907 *err = CURLE_OK;
908 }
909 }
910
911 if(cf_flush_egress(cf, data)) {
912 *err = CURLE_SEND_ERROR;
913 return -1;
914 }
915
916 return nwritten;
917}
918
919static bool stream_is_writeable(struct Curl_cfilter *cf,
920 struct Curl_easy *data)
921{
922 struct cf_quiche_ctx *ctx = cf->ctx;
923 struct HTTP *stream = data->req.p.http;
924
925 /* surely, there must be a better way */
926 quiche_stream_iter *qiter = quiche_conn_writable(ctx->qconn);
927 if(qiter) {
928 uint64_t stream_id;
929 while(quiche_stream_iter_next(qiter, &stream_id)) {
930 if(stream_id == (uint64_t)stream->stream3_id)
931 return TRUE;
932 }
933 quiche_stream_iter_free(qiter);
934 }
935 return FALSE;
936}
937
938static int cf_quiche_get_select_socks(struct Curl_cfilter *cf,
939 struct Curl_easy *data,
940 curl_socket_t *socks)
941{
942 struct cf_quiche_ctx *ctx = cf->ctx;
943 struct SingleRequest *k = &data->req;
944 int rv = GETSOCK_BLANK;
945
946 socks[0] = ctx->q.sockfd;
947
948 /* in an HTTP/3 connection we can basically always get a frame so we should
949 always be ready for one */
950 rv |= GETSOCK_READSOCK(0);
951
952 /* we're still uploading or the HTTP/3 layer wants to send data */
953 if(((k->keepon & KEEP_SENDBITS) == KEEP_SEND)
954 && stream_is_writeable(cf, data))
955 rv |= GETSOCK_WRITESOCK(0);
956
957 return rv;
958}
959
960/*
961 * Called from transfer.c:data_pending to know if we should keep looping
962 * to receive more data from the connection.
963 */
964static bool cf_quiche_data_pending(struct Curl_cfilter *cf,
965 const struct Curl_easy *data)
966{
967 struct HTTP *stream = data->req.p.http;
968
969 if(stream->pending) {
970 DEBUGF(LOG_CF((struct Curl_easy *)data, cf,
971 "[h3sid=%"PRId64"] has event pending", stream->stream3_id));
972 return TRUE;
973 }
974 if(stream->h3_recving_data) {
975 DEBUGF(LOG_CF((struct Curl_easy *)data, cf,
976 "[h3sid=%"PRId64"] is receiving DATA", stream->stream3_id));
977 return TRUE;
978 }
979 if(data->state.drain) {
980 DEBUGF(LOG_CF((struct Curl_easy *)data, cf,
981 "[h3sid=%"PRId64"] is draining", stream->stream3_id));
982 return TRUE;
983 }
984 return FALSE;
985}
986
987static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf,
988 struct Curl_easy *data,
989 int event, int arg1, void *arg2)
990{
991 struct cf_quiche_ctx *ctx = cf->ctx;
992 CURLcode result = CURLE_OK;
993
994 (void)arg1;
995 (void)arg2;
996 switch(event) {
997 case CF_CTRL_DATA_DONE: {
998 struct HTTP *stream = data->req.p.http;
999 DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] easy handle is %s",
1000 stream->stream3_id, arg1? "cancelled" : "done"));
1001 h3_clear_pending(data);
1002 break;
1003 }
1004 case CF_CTRL_DATA_DONE_SEND: {
1005 struct HTTP *stream = data->req.p.http;
1006 ssize_t sent;
1007 stream->upload_done = TRUE;
1008 sent = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->stream3_id,
1009 NULL, 0, TRUE);
1010 DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] send_body FINISHED",
1011 stream->stream3_id));
1012 if(sent < 0)
1013 return CURLE_SEND_ERROR;
1014 break;
1015 }
1016 case CF_CTRL_DATA_IDLE:
1017 /* anything to do? */
1018 break;
1019 default:
1020 break;
1021 }
1022 return result;
1023}
1024
1025static CURLcode cf_verify_peer(struct Curl_cfilter *cf,
1026 struct Curl_easy *data)
1027{
1028 struct cf_quiche_ctx *ctx = cf->ctx;
1029 CURLcode result = CURLE_OK;
1030
1031 cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
1032 cf->conn->httpversion = 30;
1033 cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
1034
1035 if(cf->conn->ssl_config.verifyhost) {
1036 X509 *server_cert;
1037 server_cert = SSL_get_peer_certificate(ctx->ssl);
1038 if(!server_cert) {
1039 result = CURLE_PEER_FAILED_VERIFICATION;
1040 goto out;
1041 }
1042 result = Curl_ossl_verifyhost(data, cf->conn, server_cert);
1043 X509_free(server_cert);
1044 if(result)
1045 goto out;
1046 }
1047 else
1048 DEBUGF(LOG_CF(data, cf, "Skipped certificate verification"));
1049
1050 ctx->h3config = quiche_h3_config_new();
1051 if(!ctx->h3config) {
1052 result = CURLE_OUT_OF_MEMORY;
1053 goto out;
1054 }
1055
1056 /* Create a new HTTP/3 connection on the QUIC connection. */
1057 ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config);
1058 if(!ctx->h3c) {
1059 result = CURLE_OUT_OF_MEMORY;
1060 goto out;
1061 }
1062 if(data->set.ssl.certinfo)
1063 /* asked to gather certificate info */
1064 (void)Curl_ossl_certchain(data, ctx->ssl);
1065
1066out:
1067 if(result) {
1068 if(ctx->h3config) {
1069 quiche_h3_config_free(ctx->h3config);
1070 ctx->h3config = NULL;
1071 }
1072 if(ctx->h3c) {
1073 quiche_h3_conn_free(ctx->h3c);
1074 ctx->h3c = NULL;
1075 }
1076 }
1077 return result;
1078}
1079
1080static CURLcode cf_connect_start(struct Curl_cfilter *cf,
1081 struct Curl_easy *data)
1082{
1083 struct cf_quiche_ctx *ctx = cf->ctx;
1084 int rv;
1085 CURLcode result;
1086 const struct Curl_sockaddr_ex *sockaddr;
1087
1088 DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD);
1089
1090#ifdef DEBUG_QUICHE
1091 /* initialize debug log callback only once */
1092 static int debug_log_init = 0;
1093 if(!debug_log_init) {
1094 quiche_enable_debug_logging(quiche_debug_log, NULL);
1095 debug_log_init = 1;
1096 }
1097#endif
1098
1099 result = vquic_ctx_init(&ctx->q, MAX_UDP_PAYLOAD_SIZE * MAX_PKT_BURST);
1100 if(result)
1101 return result;
1102
1103 ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
1104 if(!ctx->cfg) {
1105 failf(data, "can't create quiche config");
1106 return CURLE_FAILED_INIT;
1107 }
1108 quiche_config_set_max_idle_timeout(ctx->cfg, QUIC_IDLE_TIMEOUT);
1109 quiche_config_set_initial_max_data(ctx->cfg, QUIC_MAX_DATA);
1110 quiche_config_set_initial_max_stream_data_bidi_local(
1111 ctx->cfg, QUIC_MAX_DATA);
1112 quiche_config_set_initial_max_stream_data_bidi_remote(
1113 ctx->cfg, QUIC_MAX_DATA);
1114 quiche_config_set_initial_max_stream_data_uni(ctx->cfg, QUIC_MAX_DATA);
1115 quiche_config_set_initial_max_streams_bidi(ctx->cfg, QUIC_MAX_STREAMS);
1116 quiche_config_set_initial_max_streams_uni(ctx->cfg, QUIC_MAX_STREAMS);
1117 quiche_config_set_application_protos(ctx->cfg,
1118 (uint8_t *)
1119 QUICHE_H3_APPLICATION_PROTOCOL,
1120 sizeof(QUICHE_H3_APPLICATION_PROTOCOL)
1121 - 1);
1122
1123 DEBUGASSERT(!ctx->ssl);
1124 DEBUGASSERT(!ctx->sslctx);
1125 ctx->sslctx = quic_ssl_ctx(data);
1126 if(!ctx->sslctx)
1127 return CURLE_QUIC_CONNECT_ERROR;
1128 ctx->ssl = SSL_new(ctx->sslctx);
1129 if(!ctx->ssl)
1130 return CURLE_QUIC_CONNECT_ERROR;
1131
1132 SSL_set_app_data(ctx->ssl, cf);
1133 SSL_set_tlsext_host_name(ctx->ssl, cf->conn->host.name);
1134
1135 result = Curl_rand(data, ctx->scid, sizeof(ctx->scid));
1136 if(result)
1137 return result;
1138
1139 Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd,
1140 &sockaddr, NULL, NULL, NULL, NULL);
1141 ctx->q.local_addrlen = sizeof(ctx->q.local_addr);
1142 rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr,
1143 &ctx->q.local_addrlen);
1144 if(rv == -1)
1145 return CURLE_QUIC_CONNECT_ERROR;
1146
1147 ctx->qconn = quiche_conn_new_with_tls((const uint8_t *)ctx->scid,
1148 sizeof(ctx->scid), NULL, 0,
1149 (struct sockaddr *)&ctx->q.local_addr,
1150 ctx->q.local_addrlen,
1151 &sockaddr->sa_addr, sockaddr->addrlen,
1152 ctx->cfg, ctx->ssl, false);
1153 if(!ctx->qconn) {
1154 failf(data, "can't create quiche connection");
1155 return CURLE_OUT_OF_MEMORY;
1156 }
1157
1158 /* Known to not work on Windows */
1159#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD)
1160 {
1161 int qfd;
1162 (void)Curl_qlogdir(data, ctx->scid, sizeof(ctx->scid), &qfd);
1163 if(qfd != -1)
1164 quiche_conn_set_qlog_fd(ctx->qconn, qfd,
1165 "qlog title", "curl qlog");
1166 }
1167#endif
1168
1169 result = cf_flush_egress(cf, data);
1170 if(result)
1171 return result;
1172
1173 {
1174 unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL;
1175 unsigned alpn_len, offset = 0;
1176
1177 /* Replace each ALPN length prefix by a comma. */
1178 while(offset < sizeof(alpn_protocols) - 1) {
1179 alpn_len = alpn_protocols[offset];
1180 alpn_protocols[offset] = ',';
1181 offset += 1 + alpn_len;
1182 }
1183
1184 DEBUGF(LOG_CF(data, cf, "Sent QUIC client Initial, ALPN: %s",
1185 alpn_protocols + 1));
1186 }
1187
1188 return CURLE_OK;
1189}
1190
1191static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
1192 struct Curl_easy *data,
1193 bool blocking, bool *done)
1194{
1195 struct cf_quiche_ctx *ctx = cf->ctx;
1196 CURLcode result = CURLE_OK;
1197 struct curltime now;
1198
1199 if(cf->connected) {
1200 *done = TRUE;
1201 return CURLE_OK;
1202 }
1203
1204 /* Connect the UDP filter first */
1205 if(!cf->next->connected) {
1206 result = Curl_conn_cf_connect(cf->next, data, blocking, done);
1207 if(result || !*done)
1208 return result;
1209 }
1210
1211 *done = FALSE;
1212 now = Curl_now();
1213
1214 if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) {
1215 /* Not time yet to attempt the next connect */
1216 DEBUGF(LOG_CF(data, cf, "waiting for reconnect time"));
1217 goto out;
1218 }
1219
1220 if(!ctx->qconn) {
1221 result = cf_connect_start(cf, data);
1222 if(result)
1223 goto out;
1224 ctx->started_at = now;
1225 result = cf_flush_egress(cf, data);
1226 /* we do not expect to be able to recv anything yet */
1227 goto out;
1228 }
1229
1230 result = cf_process_ingress(cf, data);
1231 if(result)
1232 goto out;
1233
1234 result = cf_flush_egress(cf, data);
1235 if(result)
1236 goto out;
1237
1238 if(quiche_conn_is_established(ctx->qconn)) {
1239 DEBUGF(LOG_CF(data, cf, "handshake complete after %dms",
1240 (int)Curl_timediff(now, ctx->started_at)));
1241 ctx->handshake_at = now;
1242 result = cf_verify_peer(cf, data);
1243 if(!result) {
1244 DEBUGF(LOG_CF(data, cf, "peer verified"));
1245 cf->connected = TRUE;
1246 cf->conn->alpn = CURL_HTTP_VERSION_3;
1247 *done = TRUE;
1248 connkeep(cf->conn, "HTTP/3 default");
1249 }
1250 }
1251 else if(quiche_conn_is_draining(ctx->qconn)) {
1252 /* When a QUIC server instance is shutting down, it may send us a
1253 * CONNECTION_CLOSE right away. Our connection then enters the DRAINING
1254 * state.
1255 * This may be a stopping of the service or it may be that the server
1256 * is reloading and a new instance will start serving soon.
1257 * In any case, we tear down our socket and start over with a new one.
1258 * We re-open the underlying UDP cf right now, but do not start
1259 * connecting until called again.
1260 */
1261 int reconn_delay_ms = 200;
1262
1263 DEBUGF(LOG_CF(data, cf, "connect, remote closed, reconnect after %dms",
1264 reconn_delay_ms));
1265 Curl_conn_cf_close(cf->next, data);
1266 cf_quiche_ctx_clear(ctx);
1267 result = Curl_conn_cf_connect(cf->next, data, FALSE, done);
1268 if(!result && *done) {
1269 *done = FALSE;
1270 ctx->reconnect_at = Curl_now();
1271 ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000;
1272 Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC);
1273 result = CURLE_OK;
1274 }
1275 }
1276
1277out:
1278#ifndef CURL_DISABLE_VERBOSE_STRINGS
1279 if(result && result != CURLE_AGAIN) {
1280 const char *r_ip;
1281 int r_port;
1282
1283 Curl_cf_socket_peek(cf->next, data, NULL, NULL,
1284 &r_ip, &r_port, NULL, NULL);
1285 infof(data, "connect to %s port %u failed: %s",
1286 r_ip, r_port, curl_easy_strerror(result));
1287 }
1288#endif
1289 return result;
1290}
1291
1292static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data)
1293{
1294 struct cf_quiche_ctx *ctx = cf->ctx;
1295
1296 (void)data;
1297 if(ctx) {
1298 if(ctx->qconn) {
1299 (void)quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0);
1300 /* flushing the egress is not a failsafe way to deliver all the
1301 outstanding packets, but we also don't want to get stuck here... */
1302 (void)cf_flush_egress(cf, data);
1303 }
1304 cf_quiche_ctx_clear(ctx);
1305 }
1306}
1307
1308static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
1309{
1310 struct cf_quiche_ctx *ctx = cf->ctx;
1311
1312 (void)data;
1313 cf_quiche_ctx_clear(ctx);
1314 free(ctx);
1315 cf->ctx = NULL;
1316}
1317
1318static CURLcode cf_quiche_query(struct Curl_cfilter *cf,
1319 struct Curl_easy *data,
1320 int query, int *pres1, void *pres2)
1321{
1322 struct cf_quiche_ctx *ctx = cf->ctx;
1323
1324 switch(query) {
1325 case CF_QUERY_MAX_CONCURRENT: {
1326 uint64_t max_streams = CONN_INUSE(cf->conn);
1327 if(!ctx->goaway) {
1328 max_streams += quiche_conn_peer_streams_left_bidi(ctx->qconn);
1329 }
1330 *pres1 = (max_streams > INT_MAX)? INT_MAX : (int)max_streams;
1331 DEBUGF(LOG_CF(data, cf, "query: MAX_CONCURRENT -> %d", *pres1));
1332 return CURLE_OK;
1333 }
1334 case CF_QUERY_CONNECT_REPLY_MS:
1335 if(ctx->got_first_byte) {
1336 timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at);
1337 *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX;
1338 }
1339 else
1340 *pres1 = -1;
1341 return CURLE_OK;
1342 case CF_QUERY_TIMER_CONNECT: {
1343 struct curltime *when = pres2;
1344 if(ctx->got_first_byte)
1345 *when = ctx->first_byte_at;
1346 return CURLE_OK;
1347 }
1348 case CF_QUERY_TIMER_APPCONNECT: {
1349 struct curltime *when = pres2;
1350 if(cf->connected)
1351 *when = ctx->handshake_at;
1352 return CURLE_OK;
1353 }
1354 default:
1355 break;
1356 }
1357 return cf->next?
1358 cf->next->cft->query(cf->next, data, query, pres1, pres2) :
1359 CURLE_UNKNOWN_OPTION;
1360}
1361
1362static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf,
1363 struct Curl_easy *data,
1364 bool *input_pending)
1365{
1366 bool alive = TRUE;
1367
1368 *input_pending = FALSE;
1369 if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
1370 return FALSE;
1371
1372 if(*input_pending) {
1373 /* This happens before we've sent off a request and the connection is
1374 not in use by any other transfer, there shouldn't be any data here,
1375 only "protocol frames" */
1376 *input_pending = FALSE;
1377 Curl_attach_connection(data, cf->conn);
1378 if(cf_process_ingress(cf, data))
1379 alive = FALSE;
1380 else {
1381 alive = TRUE;
1382 }
1383 Curl_detach_connection(data);
1384 }
1385
1386 return alive;
1387}
1388
1389struct Curl_cftype Curl_cft_http3 = {
1390 "HTTP/3",
1391 CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
1392 0,
1393 cf_quiche_destroy,
1394 cf_quiche_connect,
1395 cf_quiche_close,
1396 Curl_cf_def_get_host,
1397 cf_quiche_get_select_socks,
1398 cf_quiche_data_pending,
1399 cf_quiche_send,
1400 cf_quiche_recv,
1401 cf_quiche_data_event,
1402 cf_quiche_conn_is_alive,
1403 Curl_cf_def_conn_keep_alive,
1404 cf_quiche_query,
1405};
1406
1407CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf,
1408 struct Curl_easy *data,
1409 struct connectdata *conn,
1410 const struct Curl_addrinfo *ai)
1411{
1412 struct cf_quiche_ctx *ctx = NULL;
1413 struct Curl_cfilter *cf = NULL, *udp_cf = NULL;
1414 CURLcode result;
1415
1416 (void)data;
1417 (void)conn;
1418 ctx = calloc(sizeof(*ctx), 1);
1419 if(!ctx) {
1420 result = CURLE_OUT_OF_MEMORY;
1421 goto out;
1422 }
1423
1424 result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
1425 if(result)
1426 goto out;
1427
1428 result = Curl_cf_udp_create(&udp_cf, data, conn, ai, TRNSPRT_QUIC);
1429 if(result)
1430 goto out;
1431
1432 udp_cf->conn = cf->conn;
1433 udp_cf->sockindex = cf->sockindex;
1434 cf->next = udp_cf;
1435
1436out:
1437 *pcf = (!result)? cf : NULL;
1438 if(result) {
1439 if(udp_cf)
1440 Curl_conn_cf_discard(udp_cf, data);
1441 Curl_safefree(cf);
1442 Curl_safefree(ctx);
1443 }
1444
1445 return result;
1446}
1447
1448bool Curl_conn_is_quiche(const struct Curl_easy *data,
1449 const struct connectdata *conn,
1450 int sockindex)
1451{
1452 struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
1453
1454 (void)data;
1455 for(; cf; cf = cf->next) {
1456 if(cf->cft == &Curl_cft_http3)
1457 return TRUE;
1458 if(cf->cft->flags & CF_TYPE_IP_CONNECT)
1459 return FALSE;
1460 }
1461 return FALSE;
1462}
1463
1464#endif
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette