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