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