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