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 USE_QUICHE
28#include <quiche.h>
29#include <openssl/err.h>
30#include <openssl/ssl.h>
31#include "bufq.h"
32#include "urldata.h"
33#include "cfilters.h"
34#include "cf-socket.h"
35#include "sendf.h"
36#include "strdup.h"
37#include "rand.h"
38#include "strcase.h"
39#include "multiif.h"
40#include "connect.h"
41#include "progress.h"
42#include "strerror.h"
43#include "http1.h"
44#include "vquic.h"
45#include "vquic_int.h"
46#include "curl_quiche.h"
47#include "transfer.h"
48#include "inet_pton.h"
49#include "vtls/openssl.h"
50#include "vtls/keylog.h"
51#include "vtls/vtls.h"
52
53/* The last 3 #include files should be in this order */
54#include "curl_printf.h"
55#include "curl_memory.h"
56#include "memdebug.h"
57
58/* #define DEBUG_QUICHE */
59
60#define QUIC_MAX_STREAMS (100)
61#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */
62
63#define H3_STREAM_WINDOW_SIZE (128 * 1024)
64#define H3_STREAM_CHUNK_SIZE (16 * 1024)
65/* The pool keeps spares around and half of a full stream windows
66 * seems good. More does not seem to improve performance.
67 * The benefit of the pool is that stream buffer to not keep
68 * spares. So memory consumption goes down when streams run empty,
69 * have a large upload done, etc. */
70#define H3_STREAM_POOL_SPARES \
71 (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE ) / 2
72/* Receive and Send max number of chunks just follows from the
73 * chunk size and window size */
74#define H3_STREAM_RECV_CHUNKS \
75 (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE)
76#define H3_STREAM_SEND_CHUNKS \
77 (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE)
78
79/*
80 * Store quiche version info in this buffer.
81 */
82void Curl_quiche_ver(char *p, size_t len)
83{
84 (void)msnprintf(p, len, "quiche/%s", quiche_version());
85}
86
87static void keylog_callback(const SSL *ssl, const char *line)
88{
89 (void)ssl;
90 Curl_tls_keylog_write_line(line);
91}
92
93struct cf_quiche_ctx {
94 struct cf_quic_ctx q;
95 quiche_conn *qconn;
96 quiche_config *cfg;
97 quiche_h3_conn *h3c;
98 quiche_h3_config *h3config;
99 uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
100 SSL_CTX *sslctx;
101 SSL *ssl;
102 struct curltime started_at; /* time the current attempt started */
103 struct curltime handshake_at; /* time connect handshake finished */
104 struct curltime first_byte_at; /* when first byte was recvd */
105 struct curltime reconnect_at; /* time the next attempt should start */
106 struct bufc_pool stream_bufcp; /* chunk pool for streams */
107 curl_off_t data_recvd;
108 size_t sends_on_hold; /* # of streams with SEND_HOLD set */
109 BIT(goaway); /* got GOAWAY from server */
110 BIT(got_first_byte); /* if first byte was received */
111 BIT(x509_store_setup); /* if x509 store has been set up */
112};
113
114#ifdef DEBUG_QUICHE
115static void quiche_debug_log(const char *line, void *argp)
116{
117 (void)argp;
118 fprintf(stderr, "%s\n", line);
119}
120#endif
121
122static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx)
123{
124 if(ctx) {
125 vquic_ctx_free(&ctx->q);
126 if(ctx->qconn)
127 quiche_conn_free(ctx->qconn);
128 if(ctx->h3config)
129 quiche_h3_config_free(ctx->h3config);
130 if(ctx->h3c)
131 quiche_h3_conn_free(ctx->h3c);
132 if(ctx->cfg)
133 quiche_config_free(ctx->cfg);
134 Curl_bufcp_free(&ctx->stream_bufcp);
135 memset(ctx, 0, sizeof(*ctx));
136 }
137}
138
139static CURLcode quic_x509_store_setup(struct Curl_cfilter *cf,
140 struct Curl_easy *data)
141{
142 struct cf_quiche_ctx *ctx = cf->ctx;
143
144 if(!ctx->x509_store_setup) {
145 if(cf->conn->ssl_config.verifypeer) {
146 const char * const ssl_cafile = cf->conn->ssl_config.CAfile;
147 const char * const ssl_capath = cf->conn->ssl_config.CApath;
148 if(ssl_cafile || ssl_capath) {
149 SSL_CTX_set_verify(ctx->sslctx, SSL_VERIFY_PEER, NULL);
150 /* tell OpenSSL where to find CA certificates that are used to verify
151 the server's certificate. */
152 if(!SSL_CTX_load_verify_locations(ctx->sslctx, ssl_cafile,
153 ssl_capath)) {
154 /* Fail if we insist on successfully verifying the server. */
155 failf(data, "error setting certificate verify locations:"
156 " CAfile: %s CApath: %s",
157 ssl_cafile ? ssl_cafile : "none",
158 ssl_capath ? ssl_capath : "none");
159 return CURLE_SSL_CACERT_BADFILE;
160 }
161 infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
162 infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
163 }
164#ifdef CURL_CA_FALLBACK
165 else {
166 /* verifying the peer without any CA certificates won't work so
167 use openssl's built-in default as fallback */
168 SSL_CTX_set_default_verify_paths(ctx->sslctx);
169 }
170#endif
171 }
172 ctx->x509_store_setup = TRUE;
173 }
174 return CURLE_OK;
175}
176
177static CURLcode quic_ssl_setup(struct Curl_cfilter *cf, struct Curl_easy *data)
178{
179 struct cf_quiche_ctx *ctx = cf->ctx;
180 unsigned char checkip[16];
181 struct connectdata *conn = data->conn;
182 const char *curves = conn->ssl_config.curves;
183
184 DEBUGASSERT(!ctx->sslctx);
185 ctx->sslctx = SSL_CTX_new(TLS_method());
186 if(!ctx->sslctx)
187 return CURLE_OUT_OF_MEMORY;
188
189 SSL_CTX_set_alpn_protos(ctx->sslctx,
190 (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL,
191 sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1);
192
193 SSL_CTX_set_default_verify_paths(ctx->sslctx);
194
195 /* Open the file if a TLS or QUIC backend has not done this before. */
196 Curl_tls_keylog_open();
197 if(Curl_tls_keylog_enabled()) {
198 SSL_CTX_set_keylog_callback(ctx->sslctx, keylog_callback);
199 }
200
201 if(curves && !SSL_CTX_set1_curves_list(ctx->sslctx, curves)) {
202 failf(data, "failed setting curves list for QUIC: '%s'", curves);
203 return CURLE_SSL_CIPHER;
204 }
205
206 ctx->ssl = SSL_new(ctx->sslctx);
207 if(!ctx->ssl)
208 return CURLE_QUIC_CONNECT_ERROR;
209
210 SSL_set_app_data(ctx->ssl, cf);
211
212 if((0 == Curl_inet_pton(AF_INET, cf->conn->host.name, checkip))
213#ifdef ENABLE_IPV6
214 && (0 == Curl_inet_pton(AF_INET6, cf->conn->host.name, checkip))
215#endif
216 ) {
217 char *snihost = Curl_ssl_snihost(data, cf->conn->host.name, NULL);
218 if(!snihost || !SSL_set_tlsext_host_name(ctx->ssl, snihost)) {
219 failf(data, "Failed set SNI");
220 SSL_free(ctx->ssl);
221 ctx->ssl = NULL;
222 return CURLE_QUIC_CONNECT_ERROR;
223 }
224 }
225
226 return CURLE_OK;
227}
228
229/**
230 * All about the H3 internals of a stream
231 */
232struct stream_ctx {
233 int64_t id; /* HTTP/3 protocol stream identifier */
234 struct bufq recvbuf; /* h3 response */
235 struct h1_req_parser h1; /* h1 request parsing */
236 uint64_t error3; /* HTTP/3 stream error code */
237 curl_off_t upload_left; /* number of request bytes left to upload */
238 bool closed; /* TRUE on stream close */
239 bool reset; /* TRUE on stream reset */
240 bool send_closed; /* stream is locally closed */
241 bool resp_hds_complete; /* complete, final response has been received */
242 bool resp_got_header; /* TRUE when h3 stream has recvd some HEADER */
243};
244
245#define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \
246 ((struct HTTP *)(d)->req.p.http)->h3_ctx \
247 : NULL))
248#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx
249#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \
250 H3_STREAM_CTX(d)->id : -2)
251
252static bool stream_send_is_suspended(struct Curl_easy *data)
253{
254 return (data->req.keepon & KEEP_SEND_HOLD);
255}
256
257static void stream_send_suspend(struct Curl_cfilter *cf,
258 struct Curl_easy *data)
259{
260 struct cf_quiche_ctx *ctx = cf->ctx;
261
262 if((data->req.keepon & KEEP_SENDBITS) == KEEP_SEND) {
263 data->req.keepon |= KEEP_SEND_HOLD;
264 ++ctx->sends_on_hold;
265 if(H3_STREAM_ID(data) >= 0)
266 CURL_TRC_CF(data, cf, "[%"PRId64"] suspend sending",
267 H3_STREAM_ID(data));
268 else
269 CURL_TRC_CF(data, cf, "[%s] suspend sending", data->state.url);
270 }
271}
272
273static void stream_send_resume(struct Curl_cfilter *cf,
274 struct Curl_easy *data)
275{
276 struct cf_quiche_ctx *ctx = cf->ctx;
277
278 if(stream_send_is_suspended(data)) {
279 data->req.keepon &= ~KEEP_SEND_HOLD;
280 --ctx->sends_on_hold;
281 if(H3_STREAM_ID(data) >= 0)
282 CURL_TRC_CF(data, cf, "[%"PRId64"] resume sending",
283 H3_STREAM_ID(data));
284 else
285 CURL_TRC_CF(data, cf, "[%s] resume sending", data->state.url);
286 Curl_expire(data, 0, EXPIRE_RUN_NOW);
287 }
288}
289
290static void check_resumes(struct Curl_cfilter *cf,
291 struct Curl_easy *data)
292{
293 struct cf_quiche_ctx *ctx = cf->ctx;
294 struct Curl_easy *sdata;
295
296 if(ctx->sends_on_hold) {
297 DEBUGASSERT(data->multi);
298 for(sdata = data->multi->easyp;
299 sdata && ctx->sends_on_hold; sdata = sdata->next) {
300 if(stream_send_is_suspended(sdata)) {
301 stream_send_resume(cf, sdata);
302 }
303 }
304 }
305}
306
307static CURLcode h3_data_setup(struct Curl_cfilter *cf,
308 struct Curl_easy *data)
309{
310 struct cf_quiche_ctx *ctx = cf->ctx;
311 struct stream_ctx *stream = H3_STREAM_CTX(data);
312
313 if(stream)
314 return CURLE_OK;
315
316 stream = calloc(1, sizeof(*stream));
317 if(!stream)
318 return CURLE_OUT_OF_MEMORY;
319
320 H3_STREAM_LCTX(data) = stream;
321 stream->id = -1;
322 Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp,
323 H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
324 Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
325 return CURLE_OK;
326}
327
328static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
329{
330 struct cf_quiche_ctx *ctx = cf->ctx;
331 struct stream_ctx *stream = H3_STREAM_CTX(data);
332
333 (void)cf;
334 if(stream) {
335 CURL_TRC_CF(data, cf, "[%"PRId64"] easy handle is done", stream->id);
336 if(stream_send_is_suspended(data)) {
337 data->req.keepon &= ~KEEP_SEND_HOLD;
338 --ctx->sends_on_hold;
339 }
340 Curl_bufq_free(&stream->recvbuf);
341 Curl_h1_req_parse_free(&stream->h1);
342 free(stream);
343 H3_STREAM_LCTX(data) = NULL;
344 }
345}
346
347static void drain_stream(struct Curl_cfilter *cf,
348 struct Curl_easy *data)
349{
350 struct stream_ctx *stream = H3_STREAM_CTX(data);
351 unsigned char bits;
352
353 (void)cf;
354 bits = CURL_CSELECT_IN;
355 if(stream && !stream->send_closed && stream->upload_left)
356 bits |= CURL_CSELECT_OUT;
357 if(data->state.dselect_bits != bits) {
358 data->state.dselect_bits = bits;
359 Curl_expire(data, 0, EXPIRE_RUN_NOW);
360 }
361}
362
363static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf,
364 struct Curl_easy *data,
365 int64_t stream3_id)
366{
367 struct Curl_easy *sdata;
368
369 (void)cf;
370 if(H3_STREAM_ID(data) == stream3_id) {
371 return data;
372 }
373 else {
374 DEBUGASSERT(data->multi);
375 for(sdata = data->multi->easyp; sdata; sdata = sdata->next) {
376 if((sdata->conn == data->conn) && H3_STREAM_ID(sdata) == stream3_id) {
377 return sdata;
378 }
379 }
380 }
381 return NULL;
382}
383
384/*
385 * write_resp_raw() copies response data in raw format to the `data`'s
386 * receive buffer. If not enough space is available, it appends to the
387 * `data`'s overflow buffer.
388 */
389static CURLcode write_resp_raw(struct Curl_cfilter *cf,
390 struct Curl_easy *data,
391 const void *mem, size_t memlen)
392{
393 struct stream_ctx *stream = H3_STREAM_CTX(data);
394 CURLcode result = CURLE_OK;
395 ssize_t nwritten;
396
397 (void)cf;
398 if(!stream)
399 return CURLE_RECV_ERROR;
400 nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result);
401 if(nwritten < 0)
402 return result;
403
404 if((size_t)nwritten < memlen) {
405 /* This MUST not happen. Our recbuf is dimensioned to hold the
406 * full max_stream_window and then some for this very reason. */
407 DEBUGASSERT(0);
408 return CURLE_RECV_ERROR;
409 }
410 return result;
411}
412
413struct cb_ctx {
414 struct Curl_cfilter *cf;
415 struct Curl_easy *data;
416};
417
418static int cb_each_header(uint8_t *name, size_t name_len,
419 uint8_t *value, size_t value_len,
420 void *argp)
421{
422 struct cb_ctx *x = argp;
423 struct stream_ctx *stream = H3_STREAM_CTX(x->data);
424 CURLcode result;
425
426 if(!stream)
427 return CURLE_OK;
428
429 if((name_len == 7) && !strncmp(HTTP_PSEUDO_STATUS, (char *)name, 7)) {
430 CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] status: %.*s",
431 stream->id, (int)value_len, value);
432 result = write_resp_raw(x->cf, x->data, "HTTP/3 ", sizeof("HTTP/3 ") - 1);
433 if(!result)
434 result = write_resp_raw(x->cf, x->data, value, value_len);
435 if(!result)
436 result = write_resp_raw(x->cf, x->data, " \r\n", 3);
437 }
438 else {
439 CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] header: %.*s: %.*s",
440 stream->id, (int)name_len, name,
441 (int)value_len, value);
442 result = write_resp_raw(x->cf, x->data, name, name_len);
443 if(!result)
444 result = write_resp_raw(x->cf, x->data, ": ", 2);
445 if(!result)
446 result = write_resp_raw(x->cf, x->data, value, value_len);
447 if(!result)
448 result = write_resp_raw(x->cf, x->data, "\r\n", 2);
449 }
450 if(result) {
451 CURL_TRC_CF(x->data, x->cf, "[%"PRId64"] on header error %d",
452 stream->id, result);
453 }
454 return result;
455}
456
457static ssize_t stream_resp_read(void *reader_ctx,
458 unsigned char *buf, size_t len,
459 CURLcode *err)
460{
461 struct cb_ctx *x = reader_ctx;
462 struct cf_quiche_ctx *ctx = x->cf->ctx;
463 struct stream_ctx *stream = H3_STREAM_CTX(x->data);
464 ssize_t nread;
465
466 if(!stream) {
467 *err = CURLE_RECV_ERROR;
468 return -1;
469 }
470
471 nread = quiche_h3_recv_body(ctx->h3c, ctx->qconn, stream->id,
472 buf, len);
473 if(nread >= 0) {
474 *err = CURLE_OK;
475 return nread;
476 }
477 else {
478 *err = CURLE_AGAIN;
479 return -1;
480 }
481}
482
483static CURLcode cf_recv_body(struct Curl_cfilter *cf,
484 struct Curl_easy *data)
485{
486 struct stream_ctx *stream = H3_STREAM_CTX(data);
487 ssize_t nwritten;
488 struct cb_ctx cb_ctx;
489 CURLcode result = CURLE_OK;
490
491 if(!stream)
492 return CURLE_RECV_ERROR;
493
494 if(!stream->resp_hds_complete) {
495 result = write_resp_raw(cf, data, "\r\n", 2);
496 if(result)
497 return result;
498 stream->resp_hds_complete = TRUE;
499 }
500
501 cb_ctx.cf = cf;
502 cb_ctx.data = data;
503 nwritten = Curl_bufq_slurp(&stream->recvbuf,
504 stream_resp_read, &cb_ctx, &result);
505
506 if(nwritten < 0 && result != CURLE_AGAIN) {
507 CURL_TRC_CF(data, cf, "[%"PRId64"] recv_body error %zd",
508 stream->id, nwritten);
509 failf(data, "Error %d in HTTP/3 response body for stream[%"PRId64"]",
510 result, stream->id);
511 stream->closed = TRUE;
512 stream->reset = TRUE;
513 stream->send_closed = TRUE;
514 streamclose(cf->conn, "Reset of stream");
515 return result;
516 }
517 return CURLE_OK;
518}
519
520#ifdef DEBUGBUILD
521static const char *cf_ev_name(quiche_h3_event *ev)
522{
523 switch(quiche_h3_event_type(ev)) {
524 case QUICHE_H3_EVENT_HEADERS:
525 return "HEADERS";
526 case QUICHE_H3_EVENT_DATA:
527 return "DATA";
528 case QUICHE_H3_EVENT_RESET:
529 return "RESET";
530 case QUICHE_H3_EVENT_FINISHED:
531 return "FINISHED";
532 case QUICHE_H3_EVENT_GOAWAY:
533 return "GOAWAY";
534 default:
535 return "Unknown";
536 }
537}
538#else
539#define cf_ev_name(x) ""
540#endif
541
542static CURLcode h3_process_event(struct Curl_cfilter *cf,
543 struct Curl_easy *data,
544 int64_t stream3_id,
545 quiche_h3_event *ev)
546{
547 struct stream_ctx *stream = H3_STREAM_CTX(data);
548 struct cb_ctx cb_ctx;
549 CURLcode result = CURLE_OK;
550 int rc;
551
552 if(!stream)
553 return CURLE_OK;
554 DEBUGASSERT(stream3_id == stream->id);
555 switch(quiche_h3_event_type(ev)) {
556 case QUICHE_H3_EVENT_HEADERS:
557 stream->resp_got_header = TRUE;
558 cb_ctx.cf = cf;
559 cb_ctx.data = data;
560 rc = quiche_h3_event_for_each_header(ev, cb_each_header, &cb_ctx);
561 if(rc) {
562 failf(data, "Error %d in HTTP/3 response header for stream[%"PRId64"]",
563 rc, stream3_id);
564 return CURLE_RECV_ERROR;
565 }
566 CURL_TRC_CF(data, cf, "[%"PRId64"] <- [HEADERS]", stream3_id);
567 break;
568
569 case QUICHE_H3_EVENT_DATA:
570 if(!stream->closed) {
571 result = cf_recv_body(cf, data);
572 }
573 break;
574
575 case QUICHE_H3_EVENT_RESET:
576 CURL_TRC_CF(data, cf, "[%"PRId64"] RESET", stream3_id);
577 stream->closed = TRUE;
578 stream->reset = TRUE;
579 stream->send_closed = TRUE;
580 streamclose(cf->conn, "Reset of stream");
581 break;
582
583 case QUICHE_H3_EVENT_FINISHED:
584 CURL_TRC_CF(data, cf, "[%"PRId64"] CLOSED", stream3_id);
585 if(!stream->resp_hds_complete) {
586 result = write_resp_raw(cf, data, "\r\n", 2);
587 if(result)
588 return result;
589 stream->resp_hds_complete = TRUE;
590 }
591 stream->closed = TRUE;
592 streamclose(cf->conn, "End of stream");
593 data->req.keepon &= ~KEEP_SEND_HOLD;
594 break;
595
596 case QUICHE_H3_EVENT_GOAWAY:
597 CURL_TRC_CF(data, cf, "[%"PRId64"] <- [GOAWAY]", stream3_id);
598 break;
599
600 default:
601 CURL_TRC_CF(data, cf, "[%"PRId64"] recv, unhandled event %d",
602 stream3_id, quiche_h3_event_type(ev));
603 break;
604 }
605 return result;
606}
607
608static CURLcode cf_poll_events(struct Curl_cfilter *cf,
609 struct Curl_easy *data)
610{
611 struct cf_quiche_ctx *ctx = cf->ctx;
612 struct stream_ctx *stream = H3_STREAM_CTX(data);
613 struct Curl_easy *sdata;
614 quiche_h3_event *ev;
615 CURLcode result;
616
617 /* Take in the events and distribute them to the transfers. */
618 while(ctx->h3c) {
619 int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev);
620 if(stream3_id == QUICHE_H3_ERR_DONE) {
621 break;
622 }
623 else if(stream3_id < 0) {
624 CURL_TRC_CF(data, cf, "[%"PRId64"] error poll: %"PRId64,
625 stream? stream->id : -1, stream3_id);
626 return CURLE_HTTP3;
627 }
628
629 sdata = get_stream_easy(cf, data, stream3_id);
630 if(!sdata) {
631 CURL_TRC_CF(data, cf, "[%"PRId64"] discard event %s for "
632 "unknown [%"PRId64"]",
633 stream? stream->id : -1, cf_ev_name(ev), stream3_id);
634 }
635 else {
636 result = h3_process_event(cf, sdata, stream3_id, ev);
637 drain_stream(cf, sdata);
638 if(result) {
639 CURL_TRC_CF(data, cf, "[%"PRId64"] error processing event %s "
640 "for [%"PRId64"] -> %d",
641 stream? stream->id : -1, cf_ev_name(ev),
642 stream3_id, result);
643 if(data == sdata) {
644 /* Only report this error to the caller if it is about the
645 * transfer we were called with. Otherwise we fail a transfer
646 * due to a problem in another one. */
647 quiche_h3_event_free(ev);
648 return result;
649 }
650 }
651 quiche_h3_event_free(ev);
652 }
653 }
654 return CURLE_OK;
655}
656
657struct recv_ctx {
658 struct Curl_cfilter *cf;
659 struct Curl_easy *data;
660 int pkts;
661};
662
663static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen,
664 struct sockaddr_storage *remote_addr,
665 socklen_t remote_addrlen, int ecn,
666 void *userp)
667{
668 struct recv_ctx *r = userp;
669 struct cf_quiche_ctx *ctx = r->cf->ctx;
670 quiche_recv_info recv_info;
671 ssize_t nread;
672
673 (void)ecn;
674 ++r->pkts;
675
676 recv_info.to = (struct sockaddr *)&ctx->q.local_addr;
677 recv_info.to_len = ctx->q.local_addrlen;
678 recv_info.from = (struct sockaddr *)remote_addr;
679 recv_info.from_len = remote_addrlen;
680
681 nread = quiche_conn_recv(ctx->qconn, (unsigned char *)pkt, pktlen,
682 &recv_info);
683 if(nread < 0) {
684 if(QUICHE_ERR_DONE == nread) {
685 CURL_TRC_CF(r->data, r->cf, "ingress, quiche is DONE");
686 return CURLE_OK;
687 }
688 else if(QUICHE_ERR_TLS_FAIL == nread) {
689 long verify_ok = SSL_get_verify_result(ctx->ssl);
690 if(verify_ok != X509_V_OK) {
691 failf(r->data, "SSL certificate problem: %s",
692 X509_verify_cert_error_string(verify_ok));
693 return CURLE_PEER_FAILED_VERIFICATION;
694 }
695 }
696 else {
697 failf(r->data, "quiche_conn_recv() == %zd", nread);
698 return CURLE_RECV_ERROR;
699 }
700 }
701 else if((size_t)nread < pktlen) {
702 CURL_TRC_CF(r->data, r->cf, "ingress, quiche only read %zd/%zu bytes",
703 nread, pktlen);
704 }
705
706 return CURLE_OK;
707}
708
709static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
710 struct Curl_easy *data)
711{
712 struct cf_quiche_ctx *ctx = cf->ctx;
713 struct recv_ctx rctx;
714 CURLcode result;
715
716 DEBUGASSERT(ctx->qconn);
717 result = quic_x509_store_setup(cf, data);
718 if(result)
719 return result;
720
721 rctx.cf = cf;
722 rctx.data = data;
723 rctx.pkts = 0;
724
725 result = vquic_recv_packets(cf, data, &ctx->q, 1000, recv_pkt, &rctx);
726 if(result)
727 return result;
728
729 if(rctx.pkts > 0) {
730 /* quiche digested ingress packets. It might have opened flow control
731 * windows again. */
732 check_resumes(cf, data);
733 }
734 return cf_poll_events(cf, data);
735}
736
737struct read_ctx {
738 struct Curl_cfilter *cf;
739 struct Curl_easy *data;
740 quiche_send_info send_info;
741};
742
743static ssize_t read_pkt_to_send(void *userp,
744 unsigned char *buf, size_t buflen,
745 CURLcode *err)
746{
747 struct read_ctx *x = userp;
748 struct cf_quiche_ctx *ctx = x->cf->ctx;
749 ssize_t nwritten;
750
751 nwritten = quiche_conn_send(ctx->qconn, buf, buflen, &x->send_info);
752 if(nwritten == QUICHE_ERR_DONE) {
753 *err = CURLE_AGAIN;
754 return -1;
755 }
756
757 if(nwritten < 0) {
758 failf(x->data, "quiche_conn_send returned %zd", nwritten);
759 *err = CURLE_SEND_ERROR;
760 return -1;
761 }
762 *err = CURLE_OK;
763 return nwritten;
764}
765
766/*
767 * flush_egress drains the buffers and sends off data.
768 * Calls failf() on errors.
769 */
770static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
771 struct Curl_easy *data)
772{
773 struct cf_quiche_ctx *ctx = cf->ctx;
774 ssize_t nread;
775 CURLcode result;
776 int64_t expiry_ns;
777 int64_t timeout_ns;
778 struct read_ctx readx;
779 size_t pkt_count, gsolen;
780
781 expiry_ns = quiche_conn_timeout_as_nanos(ctx->qconn);
782 if(!expiry_ns) {
783 quiche_conn_on_timeout(ctx->qconn);
784 if(quiche_conn_is_closed(ctx->qconn)) {
785 failf(data, "quiche_conn_on_timeout closed the connection");
786 return CURLE_SEND_ERROR;
787 }
788 }
789
790 result = vquic_flush(cf, data, &ctx->q);
791 if(result) {
792 if(result == CURLE_AGAIN) {
793 Curl_expire(data, 1, EXPIRE_QUIC);
794 return CURLE_OK;
795 }
796 return result;
797 }
798
799 readx.cf = cf;
800 readx.data = data;
801 memset(&readx.send_info, 0, sizeof(readx.send_info));
802 pkt_count = 0;
803 gsolen = quiche_conn_max_send_udp_payload_size(ctx->qconn);
804 for(;;) {
805 /* add the next packet to send, if any, to our buffer */
806 nread = Curl_bufq_sipn(&ctx->q.sendbuf, 0,
807 read_pkt_to_send, &readx, &result);
808 if(nread < 0) {
809 if(result != CURLE_AGAIN)
810 return result;
811 /* Nothing more to add, flush and leave */
812 result = vquic_send(cf, data, &ctx->q, gsolen);
813 if(result) {
814 if(result == CURLE_AGAIN) {
815 Curl_expire(data, 1, EXPIRE_QUIC);
816 return CURLE_OK;
817 }
818 return result;
819 }
820 goto out;
821 }
822
823 ++pkt_count;
824 if((size_t)nread < gsolen || pkt_count >= MAX_PKT_BURST) {
825 result = vquic_send(cf, data, &ctx->q, gsolen);
826 if(result) {
827 if(result == CURLE_AGAIN) {
828 Curl_expire(data, 1, EXPIRE_QUIC);
829 return CURLE_OK;
830 }
831 goto out;
832 }
833 pkt_count = 0;
834 }
835 }
836
837out:
838 timeout_ns = quiche_conn_timeout_as_nanos(ctx->qconn);
839 if(timeout_ns % 1000000)
840 timeout_ns += 1000000;
841 /* expire resolution is milliseconds */
842 Curl_expire(data, (timeout_ns / 1000000), EXPIRE_QUIC);
843 return result;
844}
845
846static ssize_t recv_closed_stream(struct Curl_cfilter *cf,
847 struct Curl_easy *data,
848 CURLcode *err)
849{
850 struct stream_ctx *stream = H3_STREAM_CTX(data);
851 ssize_t nread = -1;
852
853 DEBUGASSERT(stream);
854 if(stream->reset) {
855 failf(data,
856 "HTTP/3 stream %" PRId64 " reset by server", stream->id);
857 *err = stream->resp_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
858 CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv, was reset -> %d",
859 stream->id, *err);
860 }
861 else if(!stream->resp_got_header) {
862 failf(data,
863 "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting"
864 " all response header fields, treated as error",
865 stream->id);
866 /* *err = CURLE_PARTIAL_FILE; */
867 *err = CURLE_RECV_ERROR;
868 CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv, closed incomplete"
869 " -> %d", stream->id, *err);
870 }
871 else {
872 *err = CURLE_OK;
873 nread = 0;
874 }
875 return nread;
876}
877
878static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
879 char *buf, size_t len, CURLcode *err)
880{
881 struct cf_quiche_ctx *ctx = cf->ctx;
882 struct stream_ctx *stream = H3_STREAM_CTX(data);
883 ssize_t nread = -1;
884 CURLcode result;
885
886 if(!stream) {
887 *err = CURLE_RECV_ERROR;
888 return -1;
889 }
890
891 if(!Curl_bufq_is_empty(&stream->recvbuf)) {
892 nread = Curl_bufq_read(&stream->recvbuf,
893 (unsigned char *)buf, len, err);
894 CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) "
895 "-> %zd, %d", stream->id, len, nread, *err);
896 if(nread < 0)
897 goto out;
898 }
899
900 if(cf_process_ingress(cf, data)) {
901 CURL_TRC_CF(data, cf, "cf_recv, error on ingress");
902 *err = CURLE_RECV_ERROR;
903 nread = -1;
904 goto out;
905 }
906
907 /* recvbuf had nothing before, maybe after progressing ingress? */
908 if(nread < 0 && !Curl_bufq_is_empty(&stream->recvbuf)) {
909 nread = Curl_bufq_read(&stream->recvbuf,
910 (unsigned char *)buf, len, err);
911 CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) "
912 "-> %zd, %d", stream->id, len, nread, *err);
913 if(nread < 0)
914 goto out;
915 }
916
917 if(nread > 0) {
918 if(stream->closed)
919 drain_stream(cf, data);
920 }
921 else {
922 if(stream->closed) {
923 nread = recv_closed_stream(cf, data, err);
924 goto out;
925 }
926 else if(quiche_conn_is_draining(ctx->qconn)) {
927 failf(data, "QUIC connection is draining");
928 *err = CURLE_HTTP3;
929 nread = -1;
930 goto out;
931 }
932 *err = CURLE_AGAIN;
933 nread = -1;
934 }
935
936out:
937 result = cf_flush_egress(cf, data);
938 if(result) {
939 CURL_TRC_CF(data, cf, "cf_recv, flush egress failed");
940 *err = result;
941 nread = -1;
942 }
943 if(nread > 0)
944 ctx->data_recvd += nread;
945 CURL_TRC_CF(data, cf, "[%"PRId64"] cf_recv(total=%"
946 CURL_FORMAT_CURL_OFF_T ") -> %zd, %d",
947 stream->id, ctx->data_recvd, nread, *err);
948 return nread;
949}
950
951/* Index where :authority header field will appear in request header
952 field list. */
953#define AUTHORITY_DST_IDX 3
954
955static ssize_t h3_open_stream(struct Curl_cfilter *cf,
956 struct Curl_easy *data,
957 const void *buf, size_t len,
958 CURLcode *err)
959{
960 struct cf_quiche_ctx *ctx = cf->ctx;
961 struct stream_ctx *stream = H3_STREAM_CTX(data);
962 size_t nheader, i;
963 int64_t stream3_id;
964 struct dynhds h2_headers;
965 quiche_h3_header *nva = NULL;
966 ssize_t nwritten;
967
968 if(!stream) {
969 *err = h3_data_setup(cf, data);
970 if(*err) {
971 return -1;
972 }
973 stream = H3_STREAM_CTX(data);
974 DEBUGASSERT(stream);
975 }
976
977 Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
978
979 DEBUGASSERT(stream);
980 nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err);
981 if(nwritten < 0)
982 goto out;
983 if(!stream->h1.done) {
984 /* need more data */
985 goto out;
986 }
987 DEBUGASSERT(stream->h1.req);
988
989 *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data);
990 if(*err) {
991 nwritten = -1;
992 goto out;
993 }
994 /* no longer needed */
995 Curl_h1_req_parse_free(&stream->h1);
996
997 nheader = Curl_dynhds_count(&h2_headers);
998 nva = malloc(sizeof(quiche_h3_header) * nheader);
999 if(!nva) {
1000 *err = CURLE_OUT_OF_MEMORY;
1001 nwritten = -1;
1002 goto out;
1003 }
1004
1005 for(i = 0; i < nheader; ++i) {
1006 struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
1007 nva[i].name = (unsigned char *)e->name;
1008 nva[i].name_len = e->namelen;
1009 nva[i].value = (unsigned char *)e->value;
1010 nva[i].value_len = e->valuelen;
1011 }
1012
1013 switch(data->state.httpreq) {
1014 case HTTPREQ_POST:
1015 case HTTPREQ_POST_FORM:
1016 case HTTPREQ_POST_MIME:
1017 case HTTPREQ_PUT:
1018 if(data->state.infilesize != -1)
1019 stream->upload_left = data->state.infilesize;
1020 else
1021 /* data sending without specifying the data amount up front */
1022 stream->upload_left = -1; /* unknown */
1023 break;
1024 default:
1025 stream->upload_left = 0; /* no request body */
1026 break;
1027 }
1028
1029 if(stream->upload_left == 0)
1030 stream->send_closed = TRUE;
1031
1032 stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader,
1033 stream->send_closed);
1034 if(stream3_id < 0) {
1035 if(QUICHE_H3_ERR_STREAM_BLOCKED == stream3_id) {
1036 /* quiche seems to report this error if the connection window is
1037 * exhausted. Which happens frequently and intermittent. */
1038 CURL_TRC_CF(data, cf, "send_request(%s) rejected with BLOCKED",
1039 data->state.url);
1040 stream_send_suspend(cf, data);
1041 *err = CURLE_AGAIN;
1042 nwritten = -1;
1043 goto out;
1044 }
1045 else {
1046 CURL_TRC_CF(data, cf, "send_request(%s) -> %" PRId64,
1047 data->state.url, stream3_id);
1048 }
1049 *err = CURLE_SEND_ERROR;
1050 nwritten = -1;
1051 goto out;
1052 }
1053
1054 DEBUGASSERT(stream->id == -1);
1055 *err = CURLE_OK;
1056 stream->id = stream3_id;
1057 stream->closed = FALSE;
1058 stream->reset = FALSE;
1059
1060 if(Curl_trc_is_verbose(data)) {
1061 infof(data, "[HTTP/3] [%" PRId64 "] OPENED stream for %s",
1062 stream->id, data->state.url);
1063 for(i = 0; i < nheader; ++i) {
1064 infof(data, "[HTTP/3] [%" PRId64 "] [%.*s: %.*s]", stream->id,
1065 (int)nva[i].name_len, nva[i].name,
1066 (int)nva[i].value_len, nva[i].value);
1067 }
1068 }
1069
1070out:
1071 free(nva);
1072 Curl_dynhds_free(&h2_headers);
1073 return nwritten;
1074}
1075
1076static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data,
1077 const void *buf, size_t len, CURLcode *err)
1078{
1079 struct cf_quiche_ctx *ctx = cf->ctx;
1080 struct stream_ctx *stream = H3_STREAM_CTX(data);
1081 CURLcode result;
1082 ssize_t nwritten;
1083
1084 *err = cf_process_ingress(cf, data);
1085 if(*err) {
1086 nwritten = -1;
1087 goto out;
1088 }
1089
1090 if(!stream || stream->id < 0) {
1091 nwritten = h3_open_stream(cf, data, buf, len, err);
1092 if(nwritten < 0)
1093 goto out;
1094 stream = H3_STREAM_CTX(data);
1095 }
1096 else {
1097 bool eof = (stream->upload_left >= 0 &&
1098 (curl_off_t)len >= stream->upload_left);
1099 nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->id,
1100 (uint8_t *)buf, len, eof);
1101 if(nwritten == QUICHE_H3_ERR_DONE || (nwritten == 0 && len > 0)) {
1102 /* TODO: we seem to be blocked on flow control and should HOLD
1103 * sending. But when do we open again? */
1104 if(!quiche_conn_stream_writable(ctx->qconn, stream->id, len)) {
1105 CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) "
1106 "-> window exhausted", stream->id, len);
1107 stream_send_suspend(cf, data);
1108 }
1109 *err = CURLE_AGAIN;
1110 nwritten = -1;
1111 goto out;
1112 }
1113 else if(nwritten == QUICHE_H3_TRANSPORT_ERR_INVALID_STREAM_STATE &&
1114 stream->closed && stream->resp_hds_complete) {
1115 /* sending request body on a stream that has been closed by the
1116 * server. If the server has send us a final response, we should
1117 * silently discard the send data.
1118 * This happens for example on redirects where the server, instead
1119 * of reading the full request body just closed the stream after
1120 * sending the 30x response.
1121 * This is sort of a race: had the transfer loop called recv first,
1122 * it would see the response and stop/discard sending on its own- */
1123 CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data"
1124 "on closed stream with response", stream->id);
1125 *err = CURLE_OK;
1126 nwritten = (ssize_t)len;
1127 goto out;
1128 }
1129 else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) {
1130 CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) "
1131 "-> exceeds size", stream->id, len);
1132 *err = CURLE_SEND_ERROR;
1133 nwritten = -1;
1134 goto out;
1135 }
1136 else if(nwritten < 0) {
1137 CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) "
1138 "-> quiche err %zd", stream->id, len, nwritten);
1139 *err = CURLE_SEND_ERROR;
1140 nwritten = -1;
1141 goto out;
1142 }
1143 else {
1144 /* quiche accepted all or at least a part of the buf */
1145 if(stream->upload_left > 0) {
1146 stream->upload_left = (nwritten < stream->upload_left)?
1147 (stream->upload_left - nwritten) : 0;
1148 }
1149 if(stream->upload_left == 0)
1150 stream->send_closed = TRUE;
1151
1152 CURL_TRC_CF(data, cf, "[%" PRId64 "] send body(len=%zu, "
1153 "left=%" CURL_FORMAT_CURL_OFF_T ") -> %zd",
1154 stream->id, len, stream->upload_left, nwritten);
1155 *err = CURLE_OK;
1156 }
1157 }
1158
1159out:
1160 result = cf_flush_egress(cf, data);
1161 if(result) {
1162 *err = result;
1163 nwritten = -1;
1164 }
1165 CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %zd, %d",
1166 stream? stream->id : -1, len, nwritten, *err);
1167 return nwritten;
1168}
1169
1170static bool stream_is_writeable(struct Curl_cfilter *cf,
1171 struct Curl_easy *data)
1172{
1173 struct cf_quiche_ctx *ctx = cf->ctx;
1174 struct stream_ctx *stream = H3_STREAM_CTX(data);
1175
1176 return stream &&
1177 quiche_conn_stream_writable(ctx->qconn, (uint64_t)stream->id, 1);
1178}
1179
1180static int cf_quiche_get_select_socks(struct Curl_cfilter *cf,
1181 struct Curl_easy *data,
1182 curl_socket_t *socks)
1183{
1184 struct cf_quiche_ctx *ctx = cf->ctx;
1185 struct SingleRequest *k = &data->req;
1186 int rv = GETSOCK_BLANK;
1187
1188 socks[0] = ctx->q.sockfd;
1189
1190 /* in an HTTP/3 connection we can basically always get a frame so we should
1191 always be ready for one */
1192 rv |= GETSOCK_READSOCK(0);
1193
1194 /* we're still uploading or the HTTP/3 layer wants to send data */
1195 if(((k->keepon & KEEP_SENDBITS) == KEEP_SEND)
1196 && stream_is_writeable(cf, data))
1197 rv |= GETSOCK_WRITESOCK(0);
1198
1199 return rv;
1200}
1201
1202/*
1203 * Called from transfer.c:data_pending to know if we should keep looping
1204 * to receive more data from the connection.
1205 */
1206static bool cf_quiche_data_pending(struct Curl_cfilter *cf,
1207 const struct Curl_easy *data)
1208{
1209 const struct stream_ctx *stream = H3_STREAM_CTX(data);
1210 (void)cf;
1211 return stream && !Curl_bufq_is_empty(&stream->recvbuf);
1212}
1213
1214static CURLcode h3_data_pause(struct Curl_cfilter *cf,
1215 struct Curl_easy *data,
1216 bool pause)
1217{
1218 /* TODO: there seems right now no API in quiche to shrink/enlarge
1219 * the streams windows. As we do in HTTP/2. */
1220 if(!pause) {
1221 drain_stream(cf, data);
1222 Curl_expire(data, 0, EXPIRE_RUN_NOW);
1223 }
1224 return CURLE_OK;
1225}
1226
1227static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf,
1228 struct Curl_easy *data,
1229 int event, int arg1, void *arg2)
1230{
1231 CURLcode result = CURLE_OK;
1232
1233 (void)arg1;
1234 (void)arg2;
1235 switch(event) {
1236 case CF_CTRL_DATA_SETUP:
1237 break;
1238 case CF_CTRL_DATA_PAUSE:
1239 result = h3_data_pause(cf, data, (arg1 != 0));
1240 break;
1241 case CF_CTRL_DATA_DONE: {
1242 h3_data_done(cf, data);
1243 break;
1244 }
1245 case CF_CTRL_DATA_DONE_SEND: {
1246 struct stream_ctx *stream = H3_STREAM_CTX(data);
1247 if(stream && !stream->send_closed) {
1248 unsigned char body[1];
1249 ssize_t sent;
1250
1251 stream->send_closed = TRUE;
1252 stream->upload_left = 0;
1253 body[0] = 'X';
1254 sent = cf_quiche_send(cf, data, body, 0, &result);
1255 CURL_TRC_CF(data, cf, "[%"PRId64"] DONE_SEND -> %zd, %d",
1256 stream->id, sent, result);
1257 }
1258 break;
1259 }
1260 case CF_CTRL_DATA_IDLE: {
1261 struct stream_ctx *stream = H3_STREAM_CTX(data);
1262 if(stream && !stream->closed) {
1263 result = cf_flush_egress(cf, data);
1264 if(result)
1265 CURL_TRC_CF(data, cf, "data idle, flush egress -> %d", result);
1266 }
1267 break;
1268 }
1269 default:
1270 break;
1271 }
1272 return result;
1273}
1274
1275static CURLcode cf_verify_peer(struct Curl_cfilter *cf,
1276 struct Curl_easy *data)
1277{
1278 struct cf_quiche_ctx *ctx = cf->ctx;
1279 CURLcode result = CURLE_OK;
1280
1281 cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
1282 cf->conn->httpversion = 30;
1283 cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
1284
1285 if(cf->conn->ssl_config.verifyhost) {
1286 X509 *server_cert;
1287 server_cert = SSL_get_peer_certificate(ctx->ssl);
1288 if(!server_cert) {
1289 result = CURLE_PEER_FAILED_VERIFICATION;
1290 goto out;
1291 }
1292 result = Curl_ossl_verifyhost(data, cf->conn, server_cert);
1293 X509_free(server_cert);
1294 if(result)
1295 goto out;
1296 }
1297 else
1298 CURL_TRC_CF(data, cf, "Skipped certificate verification");
1299
1300 ctx->h3config = quiche_h3_config_new();
1301 if(!ctx->h3config) {
1302 result = CURLE_OUT_OF_MEMORY;
1303 goto out;
1304 }
1305
1306 /* Create a new HTTP/3 connection on the QUIC connection. */
1307 ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config);
1308 if(!ctx->h3c) {
1309 result = CURLE_OUT_OF_MEMORY;
1310 goto out;
1311 }
1312 if(data->set.ssl.certinfo)
1313 /* asked to gather certificate info */
1314 (void)Curl_ossl_certchain(data, ctx->ssl);
1315
1316out:
1317 if(result) {
1318 if(ctx->h3config) {
1319 quiche_h3_config_free(ctx->h3config);
1320 ctx->h3config = NULL;
1321 }
1322 if(ctx->h3c) {
1323 quiche_h3_conn_free(ctx->h3c);
1324 ctx->h3c = NULL;
1325 }
1326 }
1327 return result;
1328}
1329
1330static CURLcode cf_connect_start(struct Curl_cfilter *cf,
1331 struct Curl_easy *data)
1332{
1333 struct cf_quiche_ctx *ctx = cf->ctx;
1334 int rv;
1335 CURLcode result;
1336 const struct Curl_sockaddr_ex *sockaddr;
1337
1338 DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD);
1339
1340#ifdef DEBUG_QUICHE
1341 /* initialize debug log callback only once */
1342 static int debug_log_init = 0;
1343 if(!debug_log_init) {
1344 quiche_enable_debug_logging(quiche_debug_log, NULL);
1345 debug_log_init = 1;
1346 }
1347#endif
1348 Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
1349 H3_STREAM_POOL_SPARES);
1350 ctx->data_recvd = 0;
1351
1352 result = vquic_ctx_init(&ctx->q);
1353 if(result)
1354 return result;
1355
1356 ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
1357 if(!ctx->cfg) {
1358 failf(data, "can't create quiche config");
1359 return CURLE_FAILED_INIT;
1360 }
1361 quiche_config_enable_pacing(ctx->cfg, false);
1362 quiche_config_set_max_idle_timeout(ctx->cfg, QUIC_IDLE_TIMEOUT);
1363 quiche_config_set_initial_max_data(ctx->cfg, (1 * 1024 * 1024)
1364 /* (QUIC_MAX_STREAMS/2) * H3_STREAM_WINDOW_SIZE */);
1365 quiche_config_set_initial_max_streams_bidi(ctx->cfg, QUIC_MAX_STREAMS);
1366 quiche_config_set_initial_max_streams_uni(ctx->cfg, QUIC_MAX_STREAMS);
1367 quiche_config_set_initial_max_stream_data_bidi_local(ctx->cfg,
1368 H3_STREAM_WINDOW_SIZE);
1369 quiche_config_set_initial_max_stream_data_bidi_remote(ctx->cfg,
1370 H3_STREAM_WINDOW_SIZE);
1371 quiche_config_set_initial_max_stream_data_uni(ctx->cfg,
1372 H3_STREAM_WINDOW_SIZE);
1373 quiche_config_set_disable_active_migration(ctx->cfg, TRUE);
1374
1375 quiche_config_set_max_connection_window(ctx->cfg,
1376 10 * QUIC_MAX_STREAMS * H3_STREAM_WINDOW_SIZE);
1377 quiche_config_set_max_stream_window(ctx->cfg, 10 * H3_STREAM_WINDOW_SIZE);
1378 quiche_config_set_application_protos(ctx->cfg,
1379 (uint8_t *)
1380 QUICHE_H3_APPLICATION_PROTOCOL,
1381 sizeof(QUICHE_H3_APPLICATION_PROTOCOL)
1382 - 1);
1383
1384 DEBUGASSERT(!ctx->ssl);
1385 DEBUGASSERT(!ctx->sslctx);
1386 result = quic_ssl_setup(cf, data);
1387 if(result)
1388 return result;
1389
1390 result = Curl_rand(data, ctx->scid, sizeof(ctx->scid));
1391 if(result)
1392 return result;
1393
1394 Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd,
1395 &sockaddr, NULL, NULL, NULL, NULL);
1396 ctx->q.local_addrlen = sizeof(ctx->q.local_addr);
1397 rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr,
1398 &ctx->q.local_addrlen);
1399 if(rv == -1)
1400 return CURLE_QUIC_CONNECT_ERROR;
1401
1402 ctx->qconn = quiche_conn_new_with_tls((const uint8_t *)ctx->scid,
1403 sizeof(ctx->scid), NULL, 0,
1404 (struct sockaddr *)&ctx->q.local_addr,
1405 ctx->q.local_addrlen,
1406 &sockaddr->sa_addr, sockaddr->addrlen,
1407 ctx->cfg, ctx->ssl, false);
1408 if(!ctx->qconn) {
1409 failf(data, "can't create quiche connection");
1410 return CURLE_OUT_OF_MEMORY;
1411 }
1412
1413 /* Known to not work on Windows */
1414#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD)
1415 {
1416 int qfd;
1417 (void)Curl_qlogdir(data, ctx->scid, sizeof(ctx->scid), &qfd);
1418 if(qfd != -1)
1419 quiche_conn_set_qlog_fd(ctx->qconn, qfd,
1420 "qlog title", "curl qlog");
1421 }
1422#endif
1423
1424 result = cf_flush_egress(cf, data);
1425 if(result)
1426 return result;
1427
1428 {
1429 unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL;
1430 unsigned alpn_len, offset = 0;
1431
1432 /* Replace each ALPN length prefix by a comma. */
1433 while(offset < sizeof(alpn_protocols) - 1) {
1434 alpn_len = alpn_protocols[offset];
1435 alpn_protocols[offset] = ',';
1436 offset += 1 + alpn_len;
1437 }
1438
1439 CURL_TRC_CF(data, cf, "Sent QUIC client Initial, ALPN: %s",
1440 alpn_protocols + 1);
1441 }
1442
1443 return CURLE_OK;
1444}
1445
1446static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
1447 struct Curl_easy *data,
1448 bool blocking, bool *done)
1449{
1450 struct cf_quiche_ctx *ctx = cf->ctx;
1451 CURLcode result = CURLE_OK;
1452 struct curltime now;
1453
1454 if(cf->connected) {
1455 *done = TRUE;
1456 return CURLE_OK;
1457 }
1458
1459 /* Connect the UDP filter first */
1460 if(!cf->next->connected) {
1461 result = Curl_conn_cf_connect(cf->next, data, blocking, done);
1462 if(result || !*done)
1463 return result;
1464 }
1465
1466 *done = FALSE;
1467 now = Curl_now();
1468
1469 if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) {
1470 /* Not time yet to attempt the next connect */
1471 CURL_TRC_CF(data, cf, "waiting for reconnect time");
1472 goto out;
1473 }
1474
1475 if(!ctx->qconn) {
1476 result = cf_connect_start(cf, data);
1477 if(result)
1478 goto out;
1479 ctx->started_at = now;
1480 result = cf_flush_egress(cf, data);
1481 /* we do not expect to be able to recv anything yet */
1482 goto out;
1483 }
1484
1485 result = cf_process_ingress(cf, data);
1486 if(result)
1487 goto out;
1488
1489 result = cf_flush_egress(cf, data);
1490 if(result)
1491 goto out;
1492
1493 if(quiche_conn_is_established(ctx->qconn)) {
1494 CURL_TRC_CF(data, cf, "handshake complete after %dms",
1495 (int)Curl_timediff(now, ctx->started_at));
1496 ctx->handshake_at = now;
1497 result = cf_verify_peer(cf, data);
1498 if(!result) {
1499 CURL_TRC_CF(data, cf, "peer verified");
1500 cf->connected = TRUE;
1501 cf->conn->alpn = CURL_HTTP_VERSION_3;
1502 *done = TRUE;
1503 connkeep(cf->conn, "HTTP/3 default");
1504 }
1505 }
1506 else if(quiche_conn_is_draining(ctx->qconn)) {
1507 /* When a QUIC server instance is shutting down, it may send us a
1508 * CONNECTION_CLOSE right away. Our connection then enters the DRAINING
1509 * state.
1510 * This may be a stopping of the service or it may be that the server
1511 * is reloading and a new instance will start serving soon.
1512 * In any case, we tear down our socket and start over with a new one.
1513 * We re-open the underlying UDP cf right now, but do not start
1514 * connecting until called again.
1515 */
1516 int reconn_delay_ms = 200;
1517
1518 CURL_TRC_CF(data, cf, "connect, remote closed, reconnect after %dms",
1519 reconn_delay_ms);
1520 Curl_conn_cf_close(cf->next, data);
1521 cf_quiche_ctx_clear(ctx);
1522 result = Curl_conn_cf_connect(cf->next, data, FALSE, done);
1523 if(!result && *done) {
1524 *done = FALSE;
1525 ctx->reconnect_at = Curl_now();
1526 ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000;
1527 Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC);
1528 result = CURLE_OK;
1529 }
1530 }
1531
1532out:
1533#ifndef CURL_DISABLE_VERBOSE_STRINGS
1534 if(result && result != CURLE_AGAIN) {
1535 const char *r_ip;
1536 int r_port;
1537
1538 Curl_cf_socket_peek(cf->next, data, NULL, NULL,
1539 &r_ip, &r_port, NULL, NULL);
1540 infof(data, "connect to %s port %u failed: %s",
1541 r_ip, r_port, curl_easy_strerror(result));
1542 }
1543#endif
1544 return result;
1545}
1546
1547static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data)
1548{
1549 struct cf_quiche_ctx *ctx = cf->ctx;
1550
1551 if(ctx) {
1552 if(ctx->qconn) {
1553 (void)quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0);
1554 /* flushing the egress is not a failsafe way to deliver all the
1555 outstanding packets, but we also don't want to get stuck here... */
1556 (void)cf_flush_egress(cf, data);
1557 }
1558 cf_quiche_ctx_clear(ctx);
1559 }
1560}
1561
1562static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
1563{
1564 struct cf_quiche_ctx *ctx = cf->ctx;
1565
1566 (void)data;
1567 cf_quiche_ctx_clear(ctx);
1568 free(ctx);
1569 cf->ctx = NULL;
1570}
1571
1572static CURLcode cf_quiche_query(struct Curl_cfilter *cf,
1573 struct Curl_easy *data,
1574 int query, int *pres1, void *pres2)
1575{
1576 struct cf_quiche_ctx *ctx = cf->ctx;
1577
1578 switch(query) {
1579 case CF_QUERY_MAX_CONCURRENT: {
1580 uint64_t max_streams = CONN_INUSE(cf->conn);
1581 if(!ctx->goaway) {
1582 max_streams += quiche_conn_peer_streams_left_bidi(ctx->qconn);
1583 }
1584 *pres1 = (max_streams > INT_MAX)? INT_MAX : (int)max_streams;
1585 CURL_TRC_CF(data, cf, "query: MAX_CONCURRENT -> %d", *pres1);
1586 return CURLE_OK;
1587 }
1588 case CF_QUERY_CONNECT_REPLY_MS:
1589 if(ctx->got_first_byte) {
1590 timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at);
1591 *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX;
1592 }
1593 else
1594 *pres1 = -1;
1595 return CURLE_OK;
1596 case CF_QUERY_TIMER_CONNECT: {
1597 struct curltime *when = pres2;
1598 if(ctx->got_first_byte)
1599 *when = ctx->first_byte_at;
1600 return CURLE_OK;
1601 }
1602 case CF_QUERY_TIMER_APPCONNECT: {
1603 struct curltime *when = pres2;
1604 if(cf->connected)
1605 *when = ctx->handshake_at;
1606 return CURLE_OK;
1607 }
1608 default:
1609 break;
1610 }
1611 return cf->next?
1612 cf->next->cft->query(cf->next, data, query, pres1, pres2) :
1613 CURLE_UNKNOWN_OPTION;
1614}
1615
1616static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf,
1617 struct Curl_easy *data,
1618 bool *input_pending)
1619{
1620 bool alive = TRUE;
1621
1622 *input_pending = FALSE;
1623 if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
1624 return FALSE;
1625
1626 if(*input_pending) {
1627 /* This happens before we've sent off a request and the connection is
1628 not in use by any other transfer, there shouldn't be any data here,
1629 only "protocol frames" */
1630 *input_pending = FALSE;
1631 if(cf_process_ingress(cf, data))
1632 alive = FALSE;
1633 else {
1634 alive = TRUE;
1635 }
1636 }
1637
1638 return alive;
1639}
1640
1641struct Curl_cftype Curl_cft_http3 = {
1642 "HTTP/3",
1643 CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
1644 0,
1645 cf_quiche_destroy,
1646 cf_quiche_connect,
1647 cf_quiche_close,
1648 Curl_cf_def_get_host,
1649 cf_quiche_get_select_socks,
1650 cf_quiche_data_pending,
1651 cf_quiche_send,
1652 cf_quiche_recv,
1653 cf_quiche_data_event,
1654 cf_quiche_conn_is_alive,
1655 Curl_cf_def_conn_keep_alive,
1656 cf_quiche_query,
1657};
1658
1659CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf,
1660 struct Curl_easy *data,
1661 struct connectdata *conn,
1662 const struct Curl_addrinfo *ai)
1663{
1664 struct cf_quiche_ctx *ctx = NULL;
1665 struct Curl_cfilter *cf = NULL, *udp_cf = NULL;
1666 CURLcode result;
1667
1668 (void)data;
1669 (void)conn;
1670 ctx = calloc(sizeof(*ctx), 1);
1671 if(!ctx) {
1672 result = CURLE_OUT_OF_MEMORY;
1673 goto out;
1674 }
1675
1676 result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
1677 if(result)
1678 goto out;
1679
1680 result = Curl_cf_udp_create(&udp_cf, data, conn, ai, TRNSPRT_QUIC);
1681 if(result)
1682 goto out;
1683
1684 udp_cf->conn = cf->conn;
1685 udp_cf->sockindex = cf->sockindex;
1686 cf->next = udp_cf;
1687
1688out:
1689 *pcf = (!result)? cf : NULL;
1690 if(result) {
1691 if(udp_cf)
1692 Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE);
1693 Curl_safefree(cf);
1694 Curl_safefree(ctx);
1695 }
1696
1697 return result;
1698}
1699
1700bool Curl_conn_is_quiche(const struct Curl_easy *data,
1701 const struct connectdata *conn,
1702 int sockindex)
1703{
1704 struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
1705
1706 (void)data;
1707 for(; cf; cf = cf->next) {
1708 if(cf->cft == &Curl_cft_http3)
1709 return TRUE;
1710 if(cf->cft->flags & CF_TYPE_IP_CONNECT)
1711 return FALSE;
1712 }
1713 return FALSE;
1714}
1715
1716#endif
1717