xref: /curl/lib/vquic/curl_quiche.c (revision fa0ccd9f)
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