/src/h2o/lib/http3/server.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2018 Fastly, Kazuho Oku |
3 | | * |
4 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | | * of this software and associated documentation files (the "Software"), to |
6 | | * deal in the Software without restriction, including without limitation the |
7 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
8 | | * sell copies of the Software, and to permit persons to whom the Software is |
9 | | * furnished to do so, subject to the following conditions: |
10 | | * |
11 | | * The above copyright notice and this permission notice shall be included in |
12 | | * all copies or substantial portions of the Software. |
13 | | * |
14 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
20 | | * IN THE SOFTWARE. |
21 | | */ |
22 | | #include <sys/socket.h> |
23 | | #include "khash.h" |
24 | | #include "h2o/absprio.h" |
25 | | #include "h2o/http3_common.h" |
26 | | #include "h2o/http3_server.h" |
27 | | #include "h2o/http3_internal.h" |
28 | | #include "./../probes_.h" |
29 | | |
30 | | /** |
31 | | * the scheduler |
32 | | */ |
33 | | struct st_h2o_http3_req_scheduler_t { |
34 | | struct { |
35 | | struct { |
36 | | h2o_linklist_t high; |
37 | | h2o_linklist_t low; |
38 | | } urgencies[H2O_ABSPRIO_NUM_URGENCY_LEVELS]; |
39 | | size_t smallest_urgency; |
40 | | } active; |
41 | | h2o_linklist_t conn_blocked; |
42 | | }; |
43 | | |
44 | | /** |
45 | | * |
46 | | */ |
47 | | struct st_h2o_http3_req_scheduler_node_t { |
48 | | h2o_linklist_t link; |
49 | | h2o_absprio_t priority; |
50 | | uint64_t call_cnt; |
51 | | }; |
52 | | |
53 | | /** |
54 | | * callback used to compare precedence of the entries within the same urgency level (e.g., by comparing stream IDs) |
55 | | */ |
56 | | typedef int (*h2o_http3_req_scheduler_compare_cb)(struct st_h2o_http3_req_scheduler_t *sched, |
57 | | const struct st_h2o_http3_req_scheduler_node_t *x, |
58 | | const struct st_h2o_http3_req_scheduler_node_t *y); |
59 | | |
60 | | /** |
61 | | * Once the size of the request body being received exceeds thit limit, streaming mode will be used (if possible), and the |
62 | | * concurrency of such requests would be limited to one per connection. This is set to 1 to avoid blocking requests that send |
63 | | * small payloads without a FIN as well as to have parity with http2. |
64 | | */ |
65 | 0 | #define H2O_HTTP3_REQUEST_BODY_MIN_BYTES_TO_BLOCK 1 |
66 | | |
67 | | enum h2o_http3_server_stream_state { |
68 | | /** |
69 | | * receiving headers |
70 | | */ |
71 | | H2O_HTTP3_SERVER_STREAM_STATE_RECV_HEADERS, |
72 | | /** |
73 | | * receiving request body (runs concurrently) |
74 | | */ |
75 | | H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_BEFORE_BLOCK, |
76 | | /** |
77 | | * blocked, waiting to be unblocked one by one (either in streaming mode or in non-streaming mode) |
78 | | */ |
79 | | H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_BLOCKED, |
80 | | /** |
81 | | * in non-streaming mode, receiving body |
82 | | */ |
83 | | H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_UNBLOCKED, |
84 | | /** |
85 | | * in non-streaming mode, waiting for the request to be processed |
86 | | */ |
87 | | H2O_HTTP3_SERVER_STREAM_STATE_REQ_PENDING, |
88 | | /** |
89 | | * request has been processed, waiting for the response headers |
90 | | */ |
91 | | H2O_HTTP3_SERVER_STREAM_STATE_SEND_HEADERS, |
92 | | /** |
93 | | * sending body (the generator MAY have closed, but the transmission to the client is still ongoing) |
94 | | */ |
95 | | H2O_HTTP3_SERVER_STREAM_STATE_SEND_BODY, |
96 | | /** |
97 | | * all data has been sent and ACKed, waiting for the transport stream to close (req might be disposed when entering this state) |
98 | | */ |
99 | | H2O_HTTP3_SERVER_STREAM_STATE_CLOSE_WAIT |
100 | | }; |
101 | | |
102 | | struct st_h2o_http3_server_stream_t; |
103 | | KHASH_MAP_INIT_INT64(stream, struct st_h2o_http3_server_stream_t *) |
104 | | |
105 | | struct st_h2o_http3_server_conn_t { |
106 | | h2o_conn_t super; |
107 | | h2o_http3_conn_t h3; |
108 | | ptls_handshake_properties_t handshake_properties; |
109 | | /** |
110 | | * link-list of pending requests using st_h2o_http3_server_stream_t::link |
111 | | */ |
112 | | struct { |
113 | | /** |
114 | | * holds streams in RECV_BODY_BLOCKED state. They are promoted one by one to the POST_BLOCK State. |
115 | | */ |
116 | | h2o_linklist_t recv_body_blocked; |
117 | | /** |
118 | | * holds streams that are in request streaming mode. |
119 | | */ |
120 | | h2o_linklist_t req_streaming; |
121 | | /** |
122 | | * holds streams in REQ_PENDING state or RECV_BODY_POST_BLOCK state (that is using streaming; i.e., write_req.cb != NULL). |
123 | | */ |
124 | | h2o_linklist_t pending; |
125 | | /** |
126 | | * holds streams in RECV_HEADERS state whose header blocks are blocked by QPACK dynamic table references. |
127 | | */ |
128 | | h2o_linklist_t qpack_blocked; |
129 | | } delayed_streams; |
130 | | /** |
131 | | * number of streams currently on `delayed_streams.qpack_blocked`; checked against the decoder's max_blocked. |
132 | | */ |
133 | | uint64_t num_qpack_blocked; |
134 | | /** |
135 | | * responses blocked by SETTINGS frame yet to arrive (e.g., CONNECT-UDP requests waiting for SETTINGS to see if |
136 | | * datagram-flow-id can be sent). There is no separate state for streams linked here, because these streams are techincally |
137 | | * indifferent from those that are currently queued by the filters after `h2o_send` is called. |
138 | | */ |
139 | | h2o_linklist_t streams_resp_settings_blocked; |
140 | | /** |
141 | | * next application-level timeout |
142 | | */ |
143 | | h2o_timer_t timeout; |
144 | | /** |
145 | | * counter (the order MUST match that of h2o_http3_server_stream_state; it is accessed by index via the use of counters[]) |
146 | | */ |
147 | | union { |
148 | | struct { |
149 | | uint32_t recv_headers; |
150 | | uint32_t recv_body_before_block; |
151 | | uint32_t recv_body_blocked; |
152 | | uint32_t recv_body_unblocked; |
153 | | uint32_t req_pending; |
154 | | uint32_t send_headers; |
155 | | uint32_t send_body; |
156 | | uint32_t close_wait; |
157 | | }; |
158 | | uint32_t counters[1]; |
159 | | } num_streams; |
160 | | /** |
161 | | * Number of streams that is request streaming. The state can be in either one of SEND_HEADERS, SEND_BODY, CLOSE_WAIT. |
162 | | */ |
163 | | uint32_t num_streams_req_streaming; |
164 | | /** |
165 | | * number of streams in tunneling mode |
166 | | */ |
167 | | uint32_t num_streams_tunnelling; |
168 | | /** |
169 | | * aggregate of request stream statistics |
170 | | */ |
171 | | struct { |
172 | | /** |
173 | | * number of request streams handled on this connection |
174 | | */ |
175 | | uint64_t num_requests; |
176 | | struct { |
177 | | uint64_t stream_bytes; |
178 | | uint64_t headers_frame_bytes; |
179 | | uint64_t body_bytes; |
180 | | h2o_qpack_section_stats_t qpack; |
181 | | } req, resp; |
182 | | } stats; |
183 | | /** |
184 | | * scheduler |
185 | | */ |
186 | | struct { |
187 | | /** |
188 | | * States for request streams. |
189 | | */ |
190 | | struct st_h2o_http3_req_scheduler_t reqs; |
191 | | /** |
192 | | * States for unidirectional streams. Each element is a bit vector where slot for each stream is defined as: 1 << stream_id. |
193 | | */ |
194 | | struct { |
195 | | uint16_t active; |
196 | | uint16_t conn_blocked; |
197 | | } uni; |
198 | | } scheduler; |
199 | | /** |
200 | | * stream map used for datagram flows |
201 | | * TODO: Get rid of this structure once we drop support for masque draft-03; RFC 9297 uses quater stream ID instead of |
202 | | * dynamically mapping streams with flow IDs. |
203 | | */ |
204 | | khash_t(stream) * datagram_flows; |
205 | | /** |
206 | | * the earliest moment (in terms of max_data.sent) when the next resumption token can be sent |
207 | | */ |
208 | | uint64_t skip_jumpstart_token_until; |
209 | | /** |
210 | | * timeout entry used for graceful shutdown |
211 | | */ |
212 | | h2o_timer_t _graceful_shutdown_timeout; |
213 | | }; |
214 | | |
215 | | /** |
216 | | * sendvec, with additional field that contains the starting offset of the content |
217 | | */ |
218 | | struct st_h2o_http3_server_sendvec_t { |
219 | | h2o_sendvec_t vec; |
220 | | /** |
221 | | * Starting offset of the content carried by the vector, or UINT64_MAX if it is not carrying body |
222 | | */ |
223 | | uint64_t entity_offset; |
224 | | }; |
225 | | |
226 | | struct st_h2o_http3_server_stream_t { |
227 | | quicly_stream_t *quic; |
228 | | struct { |
229 | | h2o_buffer_t *buf; |
230 | | quicly_error_t (*handle_input)(struct st_h2o_http3_server_stream_t *stream, const uint8_t **src, const uint8_t *src_end, |
231 | | int in_generator, const char **err_desc); |
232 | | uint64_t bytes_left_in_data_frame; |
233 | | } recvbuf; |
234 | | struct { |
235 | | H2O_VECTOR(struct st_h2o_http3_server_sendvec_t) vecs; |
236 | | size_t off_within_first_vec; |
237 | | size_t min_index_to_addref; |
238 | | uint64_t final_size, final_body_size; |
239 | | uint8_t data_frame_header_buf[9]; |
240 | | } sendbuf; |
241 | | enum h2o_http3_server_stream_state state; |
242 | | /** |
243 | | * Non-zero if the stream is in RECV_HEADERS state waiting for the QPACK decoder's insert count to reach this value. |
244 | | */ |
245 | | uint64_t qpack_blocked_ref; |
246 | | h2o_linklist_t link; |
247 | | h2o_linklist_t link_resp_settings_blocked; |
248 | | h2o_ostream_t ostr_final; |
249 | | struct st_h2o_http3_req_scheduler_node_t scheduler; |
250 | | /** |
251 | | * if read is blocked |
252 | | */ |
253 | | uint8_t read_blocked : 1; |
254 | | /** |
255 | | * if h2o_proceed_response has been invoked, or if the invocation has been requested |
256 | | */ |
257 | | uint8_t proceed_requested : 1; |
258 | | /** |
259 | | * this flag is set by on_send_emit, triggers the invocation h2o_proceed_response in scheduler_do_send, used by do_send to |
260 | | * take different actions based on if it has been called while scheduler_do_send is running. |
261 | | */ |
262 | | uint8_t proceed_while_sending : 1; |
263 | | /** |
264 | | * if a PRIORITY_UPDATE frame has been received |
265 | | */ |
266 | | uint8_t received_priority_update : 1; |
267 | | /** |
268 | | * used in CLOSE_WAIT state to determine if h2o_dispose_request has been called |
269 | | */ |
270 | | uint8_t req_disposed : 1; |
271 | | /** |
272 | | * indicates if the request is in streaming mode |
273 | | */ |
274 | | uint8_t req_streaming : 1; |
275 | | /** |
276 | | * indicates if the request has ever been QPACK-blocked. |
277 | | */ |
278 | | uint8_t qpack_blocked_ever : 1; |
279 | | /** |
280 | | * if request streaming EOS has been delivered to the handler by calling write_req with is_end_stream set |
281 | | */ |
282 | | uint8_t req_streaming_eos_delivered : 1; |
283 | | /** |
284 | | * buffer to hold the request body (or a chunk of, if in streaming mode), or CONNECT payload |
285 | | */ |
286 | | h2o_buffer_t *req_body; |
287 | | /** |
288 | | * flow ID used by masque over H3_DATAGRAMS |
289 | | */ |
290 | | uint64_t datagram_flow_id; |
291 | | /** |
292 | | * per-stream statistics |
293 | | */ |
294 | | struct { |
295 | | struct { |
296 | | uint64_t headers_frame_bytes; |
297 | | h2o_qpack_section_stats_t qpack; |
298 | | } req, resp; |
299 | | } stats; |
300 | | /** |
301 | | * the request. Placed at the end, as it holds the pool. |
302 | | */ |
303 | | h2o_req_t req; |
304 | | }; |
305 | | |
306 | | static int foreach_request(h2o_conn_t *_conn, int (*cb)(h2o_req_t *req, void *cbdata), void *cbdata); |
307 | | static void initiate_graceful_shutdown(h2o_conn_t *_conn); |
308 | | static void close_idle_connection(h2o_conn_t *_conn); |
309 | | static void on_stream_destroy(quicly_stream_t *qs, quicly_error_t err); |
310 | | static void record_stream_stats(struct st_h2o_http3_server_stream_t *stream); |
311 | | static quicly_error_t handle_input_post_trailers(struct st_h2o_http3_server_stream_t *stream, const uint8_t **src, |
312 | | const uint8_t *src_end, int in_generator, const char **err_desc); |
313 | | static quicly_error_t handle_input_expect_data(struct st_h2o_http3_server_stream_t *stream, const uint8_t **src, |
314 | | const uint8_t *src_end, int in_generator, const char **err_desc); |
315 | | |
316 | | static const h2o_sendvec_callbacks_t self_allocated_vec_callbacks = {h2o_sendvec_read_raw, NULL}, |
317 | | immutable_vec_callbacks = {h2o_sendvec_read_raw, NULL}; |
318 | | |
319 | | static int sendvec_size_is_for_recycle(size_t size) |
320 | 1.47k | { |
321 | 1.47k | if (h2o_socket_ssl_buffer_allocator.conf->memsize / 2 <= size && size <= h2o_socket_ssl_buffer_allocator.conf->memsize) |
322 | 0 | return 1; |
323 | 1.47k | return 0; |
324 | 1.47k | } |
325 | | |
326 | | static void dispose_sendvec(struct st_h2o_http3_server_sendvec_t *vec) |
327 | 4.51k | { |
328 | 4.51k | if (vec->vec.callbacks == &self_allocated_vec_callbacks) { |
329 | 736 | if (sendvec_size_is_for_recycle(vec->vec.len)) { |
330 | 0 | h2o_mem_free_recycle(&h2o_socket_ssl_buffer_allocator, vec->vec.raw); |
331 | 736 | } else { |
332 | 736 | free(vec->vec.raw); |
333 | 736 | } |
334 | 736 | } |
335 | 4.51k | } |
336 | | |
337 | | static void req_scheduler_init(struct st_h2o_http3_req_scheduler_t *sched) |
338 | 6.28k | { |
339 | 6.28k | size_t i; |
340 | | |
341 | 56.6k | for (i = 0; i < H2O_ABSPRIO_NUM_URGENCY_LEVELS; ++i) { |
342 | 50.3k | h2o_linklist_init_anchor(&sched->active.urgencies[i].high); |
343 | 50.3k | h2o_linklist_init_anchor(&sched->active.urgencies[i].low); |
344 | 50.3k | } |
345 | 6.28k | sched->active.smallest_urgency = i; |
346 | 6.28k | h2o_linklist_init_anchor(&sched->conn_blocked); |
347 | 6.28k | } |
348 | | |
349 | | static void req_scheduler_activate(struct st_h2o_http3_req_scheduler_t *sched, struct st_h2o_http3_req_scheduler_node_t *node, |
350 | | h2o_http3_req_scheduler_compare_cb comp) |
351 | 1.59k | { |
352 | | /* unlink if necessary */ |
353 | 1.59k | if (h2o_linklist_is_linked(&node->link)) |
354 | 4 | h2o_linklist_unlink(&node->link); |
355 | | |
356 | 1.59k | if (!node->priority.incremental || node->call_cnt == 0) { |
357 | | /* non-incremental streams and the first emission of incremental streams go in strict order */ |
358 | 1.59k | h2o_linklist_t *anchor = &sched->active.urgencies[node->priority.urgency].high, *pos; |
359 | 1.59k | for (pos = anchor->prev; pos != anchor; pos = pos->prev) { |
360 | 0 | struct st_h2o_http3_req_scheduler_node_t *node_at_pos = |
361 | 0 | H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_req_scheduler_node_t, link, pos); |
362 | 0 | if (comp(sched, node_at_pos, node) < 0) |
363 | 0 | break; |
364 | 0 | } |
365 | 1.59k | h2o_linklist_insert(pos->next, &node->link); |
366 | 1.59k | } else { |
367 | | /* once sent, incremental streams go into a lower list */ |
368 | 4 | h2o_linklist_insert(&sched->active.urgencies[node->priority.urgency].low, &node->link); |
369 | 4 | } |
370 | | |
371 | | /* book keeping */ |
372 | 1.59k | if (node->priority.urgency < sched->active.smallest_urgency) |
373 | 1.59k | sched->active.smallest_urgency = node->priority.urgency; |
374 | 1.59k | } |
375 | | |
376 | | static void req_scheduler_update_smallest_urgency_post_removal(struct st_h2o_http3_req_scheduler_t *sched, size_t changed) |
377 | 8.53k | { |
378 | 8.53k | if (sched->active.smallest_urgency < changed) |
379 | 0 | return; |
380 | | |
381 | | /* search from the location that *might* have changed */ |
382 | 8.53k | sched->active.smallest_urgency = changed; |
383 | 42.5k | while (h2o_linklist_is_empty(&sched->active.urgencies[sched->active.smallest_urgency].high) && |
384 | 42.5k | h2o_linklist_is_empty(&sched->active.urgencies[sched->active.smallest_urgency].low)) { |
385 | 42.5k | ++sched->active.smallest_urgency; |
386 | 42.5k | if (sched->active.smallest_urgency >= H2O_ABSPRIO_NUM_URGENCY_LEVELS) |
387 | 8.53k | break; |
388 | 42.5k | } |
389 | 8.53k | } |
390 | | |
391 | | static void req_scheduler_deactivate(struct st_h2o_http3_req_scheduler_t *sched, struct st_h2o_http3_req_scheduler_node_t *node) |
392 | 8.53k | { |
393 | 8.53k | if (h2o_linklist_is_linked(&node->link)) |
394 | 1.59k | h2o_linklist_unlink(&node->link); |
395 | | |
396 | 8.53k | req_scheduler_update_smallest_urgency_post_removal(sched, node->priority.urgency); |
397 | 8.53k | } |
398 | | |
399 | | static void req_scheduler_setup_for_next(struct st_h2o_http3_req_scheduler_t *sched, struct st_h2o_http3_req_scheduler_node_t *node, |
400 | | h2o_http3_req_scheduler_compare_cb comp) |
401 | 368 | { |
402 | 368 | assert(h2o_linklist_is_linked(&node->link)); |
403 | | |
404 | | /* reschedule to achieve round-robin behavior */ |
405 | 368 | if (node->priority.incremental) |
406 | 4 | req_scheduler_activate(sched, node, comp); |
407 | 368 | } |
408 | | |
409 | | static void req_scheduler_conn_blocked(struct st_h2o_http3_req_scheduler_t *sched, struct st_h2o_http3_req_scheduler_node_t *node) |
410 | 0 | { |
411 | 0 | if (h2o_linklist_is_linked(&node->link)) |
412 | 0 | h2o_linklist_unlink(&node->link); |
413 | |
|
414 | 0 | h2o_linklist_insert(&sched->conn_blocked, &node->link); |
415 | |
|
416 | 0 | req_scheduler_update_smallest_urgency_post_removal(sched, node->priority.urgency); |
417 | 0 | } |
418 | | |
419 | | static void req_scheduler_unblock_conn_blocked(struct st_h2o_http3_req_scheduler_t *sched, h2o_http3_req_scheduler_compare_cb comp) |
420 | 0 | { |
421 | 0 | while (!h2o_linklist_is_empty(&sched->conn_blocked)) { |
422 | 0 | struct st_h2o_http3_req_scheduler_node_t *node = |
423 | 0 | H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_req_scheduler_node_t, link, sched->conn_blocked.next); |
424 | 0 | req_scheduler_activate(sched, node, comp); |
425 | 0 | } |
426 | 0 | } |
427 | | |
428 | | static int req_scheduler_compare_stream_id(struct st_h2o_http3_req_scheduler_t *sched, |
429 | | const struct st_h2o_http3_req_scheduler_node_t *x, |
430 | | const struct st_h2o_http3_req_scheduler_node_t *y) |
431 | 0 | { |
432 | 0 | struct st_h2o_http3_server_stream_t *sx = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, scheduler, x), |
433 | 0 | *sy = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, scheduler, y); |
434 | 0 | if (sx->quic->stream_id < sy->quic->stream_id) { |
435 | 0 | return -1; |
436 | 0 | } else if (sx->quic->stream_id > sy->quic->stream_id) { |
437 | 0 | return 1; |
438 | 0 | } else { |
439 | 0 | return 0; |
440 | 0 | } |
441 | 0 | } |
442 | | |
443 | | static struct st_h2o_http3_server_conn_t *get_conn(struct st_h2o_http3_server_stream_t *stream) |
444 | 92.5k | { |
445 | 92.5k | return (void *)stream->req.conn; |
446 | 92.5k | } |
447 | | |
448 | | static uint32_t *get_state_counter(struct st_h2o_http3_server_conn_t *conn, enum h2o_http3_server_stream_state state) |
449 | 26.3k | { |
450 | 26.3k | return conn->num_streams.counters + (size_t)state; |
451 | 26.3k | } |
452 | | |
453 | | static void handle_priority_change(struct st_h2o_http3_server_stream_t *stream, const char *value, size_t len, h2o_absprio_t base) |
454 | 0 | { |
455 | 0 | int reactivate = 0; |
456 | |
|
457 | 0 | if (h2o_linklist_is_linked(&stream->scheduler.link)) { |
458 | 0 | req_scheduler_deactivate(&get_conn(stream)->scheduler.reqs, &stream->scheduler); |
459 | 0 | reactivate = 1; |
460 | 0 | } |
461 | | |
462 | | /* update priority, using provided value as the base */ |
463 | 0 | stream->scheduler.priority = base; |
464 | 0 | h2o_absprio_parse_priority(value, len, &stream->scheduler.priority); |
465 | |
|
466 | 0 | if (reactivate) |
467 | 0 | req_scheduler_activate(&get_conn(stream)->scheduler.reqs, &stream->scheduler, req_scheduler_compare_stream_id); |
468 | 0 | } |
469 | | |
470 | | static void tunnel_on_udp_read(h2o_req_t *_req, h2o_iovec_t *datagrams, size_t num_datagrams) |
471 | 0 | { |
472 | 0 | struct st_h2o_http3_server_stream_t *stream = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, req, _req); |
473 | 0 | h2o_http3_send_h3_datagrams(&get_conn(stream)->h3, stream->datagram_flow_id, datagrams, num_datagrams); |
474 | 0 | } |
475 | | |
476 | | static void request_run_delayed(struct st_h2o_http3_server_conn_t *conn) |
477 | 783 | { |
478 | 783 | if (!h2o_timer_is_linked(&conn->timeout)) |
479 | 783 | h2o_timer_link(conn->super.ctx->loop, 0, &conn->timeout); |
480 | 783 | } |
481 | | |
482 | | static void check_run_blocked(struct st_h2o_http3_server_conn_t *conn) |
483 | 0 | { |
484 | 0 | if (conn->num_streams.recv_body_unblocked + conn->num_streams_req_streaming < |
485 | 0 | conn->super.ctx->globalconf->http3.max_concurrent_streaming_requests_per_connection && |
486 | 0 | !h2o_linklist_is_empty(&conn->delayed_streams.recv_body_blocked)) |
487 | 0 | request_run_delayed(conn); |
488 | 0 | } |
489 | | |
490 | | static void pre_dispose_request(struct st_h2o_http3_server_stream_t *stream) |
491 | 6.28k | { |
492 | 6.28k | struct st_h2o_http3_server_conn_t *conn = get_conn(stream); |
493 | 6.28k | size_t i; |
494 | | |
495 | | /* release vectors */ |
496 | 8.42k | for (i = 0; i != stream->sendbuf.vecs.size; ++i) |
497 | 2.13k | dispose_sendvec(stream->sendbuf.vecs.entries + i); |
498 | | |
499 | | /* dispose request body buffer */ |
500 | 6.28k | if (stream->req_body != NULL) |
501 | 434 | h2o_buffer_dispose(&stream->req_body); |
502 | | |
503 | | /* clean up request streaming */ |
504 | 6.28k | if (stream->req_streaming && !stream->req.is_tunnel_req) { |
505 | 0 | assert(conn->num_streams_req_streaming != 0); |
506 | 0 | stream->req_streaming = 0; |
507 | 0 | --conn->num_streams_req_streaming; |
508 | 0 | check_run_blocked(conn); |
509 | 0 | } |
510 | | |
511 | | /* remove stream from datagram flow list */ |
512 | 6.28k | if (stream->datagram_flow_id != UINT64_MAX) { |
513 | 671 | khiter_t iter = kh_get(stream, conn->datagram_flows, stream->datagram_flow_id); |
514 | | /* it's possible the tunnel wasn't established yet */ |
515 | 671 | if (iter != kh_end(conn->datagram_flows)) |
516 | 0 | kh_del(stream, conn->datagram_flows, iter); |
517 | 671 | } |
518 | | |
519 | 6.28k | if (stream->req.is_tunnel_req) |
520 | 233 | --get_conn(stream)->num_streams_tunnelling; |
521 | 6.28k | } |
522 | | |
523 | | static void set_state(struct st_h2o_http3_server_stream_t *stream, enum h2o_http3_server_stream_state state, int in_generator) |
524 | 6.86k | { |
525 | 6.86k | struct st_h2o_http3_server_conn_t *conn = get_conn(stream); |
526 | 6.86k | enum h2o_http3_server_stream_state old_state = stream->state; |
527 | | |
528 | 6.86k | H2O_PROBE_CONN(H3S_STREAM_SET_STATE, &conn->super, stream->quic->stream_id, (unsigned)state); |
529 | | |
530 | 6.86k | assert(stream->qpack_blocked_ref == 0 && "QPACK-blocked streams must be unblocked or cancelled before changing stream state"); |
531 | | |
532 | 6.86k | --*get_state_counter(conn, old_state); |
533 | 6.86k | stream->state = state; |
534 | 6.86k | ++*get_state_counter(conn, stream->state); |
535 | | |
536 | 6.86k | switch (state) { |
537 | 0 | case H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_BLOCKED: |
538 | 0 | assert(conn->delayed_streams.recv_body_blocked.prev == &stream->link || !"stream is not registered to the recv_body list?"); |
539 | 0 | break; |
540 | 1.87k | case H2O_HTTP3_SERVER_STREAM_STATE_CLOSE_WAIT: { |
541 | 1.87k | if (h2o_linklist_is_linked(&stream->link)) |
542 | 0 | h2o_linklist_unlink(&stream->link); |
543 | 1.87k | pre_dispose_request(stream); |
544 | 1.87k | if (!in_generator) { |
545 | 1.87k | record_stream_stats(stream); |
546 | 1.87k | h2o_dispose_request(&stream->req); |
547 | 1.87k | stream->req_disposed = 1; |
548 | 1.87k | } |
549 | 1.87k | static const quicly_stream_callbacks_t close_wait_callbacks = {on_stream_destroy, |
550 | 1.87k | quicly_stream_noop_on_send_shift, |
551 | 1.87k | quicly_stream_noop_on_send_emit, |
552 | 1.87k | quicly_stream_noop_on_send_stop, |
553 | 1.87k | quicly_stream_noop_on_receive, |
554 | 1.87k | quicly_stream_noop_on_receive_reset}; |
555 | 1.87k | stream->quic->callbacks = &close_wait_callbacks; |
556 | 1.87k | } break; |
557 | 4.99k | default: |
558 | 4.99k | break; |
559 | 6.86k | } |
560 | 6.86k | } |
561 | | |
562 | | static void cancel_qpack_decoder(struct st_h2o_http3_server_stream_t *stream) |
563 | 1.87k | { |
564 | 1.87k | struct st_h2o_http3_server_conn_t *conn = get_conn(stream); |
565 | 1.87k | if (stream->qpack_blocked_ref != 0) { |
566 | 0 | assert(conn->num_qpack_blocked > 0); |
567 | 0 | --conn->num_qpack_blocked; |
568 | 0 | stream->qpack_blocked_ref = 0; |
569 | 0 | } |
570 | 1.87k | h2o_http3_qpack_cancel_stream(&conn->h3, stream->quic->stream_id); |
571 | 1.87k | } |
572 | | |
573 | | /** |
574 | | * Shutdowns a stream. Note that a request stream should not be shut down until receiving some QUIC frame that refers to that |
575 | | * stream, but we might might have created stream state due to receiving a PRIORITY_UPDATE frame prior to that (see |
576 | | * handle_priority_update_frame). |
577 | | */ |
578 | | static void shutdown_stream(struct st_h2o_http3_server_stream_t *stream, quicly_error_t stop_sending_code, |
579 | | quicly_error_t reset_code, int in_generator, int reset_only_if_open) |
580 | 996 | { |
581 | 996 | assert(stream->state < H2O_HTTP3_SERVER_STREAM_STATE_CLOSE_WAIT); |
582 | 996 | if (quicly_stream_has_receive_side(0, stream->quic->stream_id)) { |
583 | | /* send STOP_SENDING unless RESET_STREAM was received; we send STOP_SENDING even if all data up to EOS have been received, |
584 | | * as it is allowed and might be beneficial in case ACKs are lost */ |
585 | 996 | if (!(quicly_recvstate_transfer_complete(&stream->quic->recvstate) && stream->quic->recvstate.eos == UINT64_MAX)) |
586 | 996 | quicly_request_stop(stream->quic, stop_sending_code); |
587 | 996 | cancel_qpack_decoder(stream); |
588 | 996 | if (h2o_linklist_is_linked(&stream->link)) |
589 | 0 | h2o_linklist_unlink(&stream->link); |
590 | 996 | } |
591 | 996 | if (reset_only_if_open && quicly_stream_has_send_side(0, stream->quic->stream_id) && |
592 | 0 | !quicly_sendstate_is_open(&stream->quic->sendstate)) { |
593 | 0 | if (stream->state < H2O_HTTP3_SERVER_STREAM_STATE_SEND_BODY) |
594 | 0 | set_state(stream, H2O_HTTP3_SERVER_STREAM_STATE_SEND_BODY, in_generator); |
595 | 996 | } else { |
596 | 996 | if (quicly_stream_has_send_side(0, stream->quic->stream_id) && |
597 | 996 | !quicly_sendstate_transfer_complete(&stream->quic->sendstate)) |
598 | 996 | quicly_reset_stream(stream->quic, reset_code); |
599 | 996 | set_state(stream, H2O_HTTP3_SERVER_STREAM_STATE_CLOSE_WAIT, in_generator); |
600 | 996 | } |
601 | 996 | } |
602 | | |
603 | | static socklen_t get_sockname(h2o_conn_t *_conn, struct sockaddr *sa) |
604 | 0 | { |
605 | 0 | struct st_h2o_http3_server_conn_t *conn = (void *)_conn; |
606 | 0 | struct sockaddr *src = quicly_get_sockname(conn->h3.super.quic); |
607 | 0 | socklen_t len = src->sa_family == AF_UNSPEC ? sizeof(struct sockaddr) : quicly_get_socklen(src); |
608 | 0 | memcpy(sa, src, len); |
609 | 0 | return len; |
610 | 0 | } |
611 | | |
612 | | static socklen_t get_peername(h2o_conn_t *_conn, struct sockaddr *sa) |
613 | 409 | { |
614 | 409 | struct st_h2o_http3_server_conn_t *conn = (void *)_conn; |
615 | 409 | struct sockaddr *src = quicly_get_peername(conn->h3.super.quic); |
616 | 409 | socklen_t len = quicly_get_socklen(src); |
617 | 409 | memcpy(sa, src, len); |
618 | 409 | return len; |
619 | 409 | } |
620 | | |
621 | | static ptls_t *get_ptls(h2o_conn_t *_conn) |
622 | 409 | { |
623 | 409 | struct st_h2o_http3_server_conn_t *conn = (void *)_conn; |
624 | 409 | return quicly_get_tls(conn->h3.super.quic); |
625 | 409 | } |
626 | | |
627 | | static const char *get_ssl_server_name(h2o_conn_t *conn) |
628 | 0 | { |
629 | 0 | ptls_t *ptls = get_ptls(conn); |
630 | 0 | return ptls_get_server_name(ptls); |
631 | 0 | } |
632 | | |
633 | | static ptls_log_conn_state_t *log_state(h2o_conn_t *conn) |
634 | 0 | { |
635 | 0 | ptls_t *ptls = get_ptls(conn); |
636 | 0 | return ptls_get_log_state(ptls); |
637 | 0 | } |
638 | | |
639 | | static uint64_t get_req_id(h2o_req_t *req) |
640 | 0 | { |
641 | 0 | struct st_h2o_http3_server_stream_t *stream = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, req, req); |
642 | 0 | return stream->quic->stream_id; |
643 | 0 | } |
644 | | |
645 | | static uint32_t num_reqs_inflight(h2o_conn_t *_conn) |
646 | 0 | { |
647 | 0 | struct st_h2o_http3_server_conn_t *conn = (void *)_conn; |
648 | 0 | return quicly_num_streams_by_group(conn->h3.super.quic, 0, 0); |
649 | 0 | } |
650 | | |
651 | | static quicly_tracer_t *get_tracer(h2o_conn_t *_conn) |
652 | 0 | { |
653 | 0 | struct st_h2o_http3_server_conn_t *conn = (void *)_conn; |
654 | 0 | return quicly_get_tracer(conn->h3.super.quic); |
655 | 0 | } |
656 | | |
657 | | static h2o_iovec_t log_extensible_priorities(h2o_req_t *_req) |
658 | 0 | { |
659 | 0 | struct st_h2o_http3_server_stream_t *stream = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, req, _req); |
660 | 0 | char *buf = h2o_mem_alloc_pool(&stream->req.pool, char, sizeof("u=" H2O_UINT8_LONGEST_STR ",i=?1")); |
661 | 0 | int len = |
662 | 0 | sprintf(buf, "u=%" PRIu8 "%s", stream->scheduler.priority.urgency, stream->scheduler.priority.incremental ? ",i=?1" : ""); |
663 | 0 | return h2o_iovec_init(buf, len); |
664 | 0 | } |
665 | | |
666 | | #define DEFINE_NUMERIC_LOGGER(name, fmt, value) \ |
667 | | static h2o_iovec_t log_##name(h2o_req_t *_req) \ |
668 | 0 | { \ |
669 | 0 | struct st_h2o_http3_server_stream_t *stream = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, req, _req); \ |
670 | 0 | char *buf = h2o_mem_alloc_pool(&stream->req.pool, char, sizeof(H2O_INT64_LONGEST_STR)); \ |
671 | 0 | return h2o_iovec_init(buf, sprintf(buf, fmt, value)); \ |
672 | 0 | } |
673 | | |
674 | 0 | DEFINE_NUMERIC_LOGGER(request_header_bytes, "%" PRIu64, stream->stats.req.headers_frame_bytes) |
675 | 0 | DEFINE_NUMERIC_LOGGER(request_header_text_bytes, "%zu", stream->stats.req.qpack.text_bytes) |
676 | 0 | DEFINE_NUMERIC_LOGGER(request_header_count, "%zu", stream->stats.req.qpack.count) |
677 | 0 | DEFINE_NUMERIC_LOGGER(response_header_text_bytes, "%zu", stream->stats.resp.qpack.text_bytes) |
678 | 0 | DEFINE_NUMERIC_LOGGER(response_header_count, "%zu", stream->stats.resp.qpack.count) |
679 | | |
680 | | static h2o_iovec_t log_cc_name(h2o_req_t *req) |
681 | 0 | { |
682 | 0 | struct st_h2o_http3_server_conn_t *conn = (struct st_h2o_http3_server_conn_t *)req->conn; |
683 | 0 | quicly_stats_t stats; |
684 | |
|
685 | 0 | if (quicly_get_stats(conn->h3.super.quic, &stats) == 0) |
686 | 0 | return h2o_iovec_init(stats.cc.type->name, strlen(stats.cc.type->name)); |
687 | 0 | return h2o_iovec_init(NULL, 0); |
688 | 0 | } |
689 | | |
690 | | static h2o_iovec_t log_delivery_rate(h2o_req_t *req) |
691 | 0 | { |
692 | 0 | struct st_h2o_http3_server_conn_t *conn = (struct st_h2o_http3_server_conn_t *)req->conn; |
693 | 0 | quicly_rate_t rate; |
694 | |
|
695 | 0 | if (quicly_get_delivery_rate(conn->h3.super.quic, &rate) == 0 && rate.latest != 0) { |
696 | 0 | char *buf = h2o_mem_alloc_pool(&req->pool, char, sizeof(H2O_UINT64_LONGEST_STR)); |
697 | 0 | size_t len = sprintf(buf, "%" PRIu64, rate.latest); |
698 | 0 | return h2o_iovec_init(buf, len); |
699 | 0 | } |
700 | | |
701 | 0 | return h2o_iovec_init(NULL, 0); |
702 | 0 | } |
703 | | |
704 | | static h2o_iovec_t log_tls_protocol_version(h2o_req_t *_req) |
705 | 0 | { |
706 | 0 | return h2o_iovec_init(H2O_STRLIT("TLSv1.3")); |
707 | 0 | } |
708 | | |
709 | | static h2o_iovec_t log_session_reused(h2o_req_t *req) |
710 | 0 | { |
711 | 0 | struct st_h2o_http3_server_conn_t *conn = (struct st_h2o_http3_server_conn_t *)req->conn; |
712 | 0 | ptls_t *tls = quicly_get_tls(conn->h3.super.quic); |
713 | 0 | return ptls_is_psk_handshake(tls) ? h2o_iovec_init(H2O_STRLIT("1")) : h2o_iovec_init(H2O_STRLIT("0")); |
714 | 0 | } |
715 | | |
716 | | static h2o_iovec_t log_cipher(h2o_req_t *req) |
717 | 0 | { |
718 | 0 | struct st_h2o_http3_server_conn_t *conn = (struct st_h2o_http3_server_conn_t *)req->conn; |
719 | 0 | ptls_t *tls = quicly_get_tls(conn->h3.super.quic); |
720 | 0 | ptls_cipher_suite_t *cipher = ptls_get_cipher(tls); |
721 | 0 | return cipher != NULL ? h2o_iovec_init(cipher->name, strlen(cipher->name)) : h2o_iovec_init(NULL, 0); |
722 | 0 | } |
723 | | |
724 | | static h2o_iovec_t log_cipher_bits(h2o_req_t *req) |
725 | 0 | { |
726 | 0 | struct st_h2o_http3_server_conn_t *conn = (struct st_h2o_http3_server_conn_t *)req->conn; |
727 | 0 | ptls_t *tls = quicly_get_tls(conn->h3.super.quic); |
728 | 0 | ptls_cipher_suite_t *cipher = ptls_get_cipher(tls); |
729 | 0 | if (cipher == NULL) |
730 | 0 | return h2o_iovec_init(NULL, 0); |
731 | | |
732 | 0 | char *buf = h2o_mem_alloc_pool(&req->pool, char, sizeof(H2O_UINT16_LONGEST_STR)); |
733 | 0 | return h2o_iovec_init(buf, sprintf(buf, "%" PRIu16, (uint16_t)(cipher->aead->key_size * 8))); |
734 | 0 | } |
735 | | |
736 | | static h2o_iovec_t log_session_id(h2o_req_t *_req) |
737 | 0 | { |
738 | | /* FIXME */ |
739 | 0 | return h2o_iovec_init(NULL, 0); |
740 | 0 | } |
741 | | |
742 | | static h2o_iovec_t log_negotiated_protocol(h2o_req_t *req) |
743 | 0 | { |
744 | 0 | struct st_h2o_http3_server_conn_t *conn = (struct st_h2o_http3_server_conn_t *)req->conn; |
745 | 0 | ptls_t *tls = quicly_get_tls(conn->h3.super.quic); |
746 | 0 | const char *proto = ptls_get_negotiated_protocol(tls); |
747 | 0 | return proto != NULL ? h2o_iovec_init(proto, strlen(proto)) : h2o_iovec_init(NULL, 0); |
748 | 0 | } |
749 | | |
750 | | static h2o_iovec_t log_ech_config_id(h2o_req_t *req) |
751 | 0 | { |
752 | 0 | struct st_h2o_http3_server_conn_t *conn = (struct st_h2o_http3_server_conn_t *)req->conn; |
753 | 0 | ptls_t *tls = quicly_get_tls(conn->h3.super.quic); |
754 | 0 | uint8_t config_id; |
755 | |
|
756 | 0 | if (ptls_is_ech_handshake(tls, &config_id, NULL, NULL)) { |
757 | 0 | char *s = h2o_mem_alloc_pool(&req->pool, char, sizeof(H2O_UINT8_LONGEST_STR)); |
758 | 0 | size_t len = sprintf(s, "%" PRIu8, config_id); |
759 | 0 | return h2o_iovec_init(s, len); |
760 | 0 | } else { |
761 | 0 | return h2o_iovec_init(NULL, 0); |
762 | 0 | } |
763 | 0 | } |
764 | | |
765 | | static h2o_iovec_t log_ech_kem(h2o_req_t *req) |
766 | 0 | { |
767 | 0 | struct st_h2o_http3_server_conn_t *conn = (struct st_h2o_http3_server_conn_t *)req->conn; |
768 | 0 | ptls_t *tls = quicly_get_tls(conn->h3.super.quic); |
769 | 0 | ptls_hpke_kem_t *kem; |
770 | |
|
771 | 0 | if (ptls_is_ech_handshake(tls, NULL, &kem, NULL)) { |
772 | 0 | return h2o_iovec_init(kem->keyex->name, strlen(kem->keyex->name)); |
773 | 0 | } else { |
774 | 0 | return h2o_iovec_init(NULL, 0); |
775 | 0 | } |
776 | 0 | } |
777 | | |
778 | | static h2o_iovec_t log_ech_cipher(h2o_req_t *req) |
779 | 0 | { |
780 | 0 | struct st_h2o_http3_server_conn_t *conn = (struct st_h2o_http3_server_conn_t *)req->conn; |
781 | 0 | ptls_t *tls = quicly_get_tls(conn->h3.super.quic); |
782 | 0 | ptls_hpke_cipher_suite_t *cipher; |
783 | |
|
784 | 0 | if (ptls_is_ech_handshake(tls, NULL, NULL, &cipher)) { |
785 | 0 | return h2o_iovec_init(cipher->name, strlen(cipher->name)); |
786 | 0 | } else { |
787 | 0 | return h2o_iovec_init(NULL, 0); |
788 | 0 | } |
789 | 0 | } |
790 | | |
791 | | static h2o_iovec_t log_ech_cipher_bits(h2o_req_t *req) |
792 | 0 | { |
793 | 0 | struct st_h2o_http3_server_conn_t *conn = (struct st_h2o_http3_server_conn_t *)req->conn; |
794 | 0 | ptls_t *tls = quicly_get_tls(conn->h3.super.quic); |
795 | 0 | ptls_hpke_cipher_suite_t *cipher; |
796 | |
|
797 | 0 | if (ptls_is_ech_handshake(tls, NULL, NULL, &cipher)) { |
798 | 0 | uint16_t bits = (uint16_t)(cipher->aead->key_size * 8); |
799 | 0 | char *s = h2o_mem_alloc_pool(&req->pool, char, sizeof(H2O_UINT16_LONGEST_STR)); |
800 | 0 | size_t len = sprintf(s, "%" PRIu16, bits); |
801 | 0 | return h2o_iovec_init(s, len); |
802 | 0 | } else { |
803 | 0 | return h2o_iovec_init(NULL, 0); |
804 | 0 | } |
805 | 0 | } |
806 | | |
807 | 0 | DEFINE_NUMERIC_LOGGER(stream_id, "%" PRIu64, stream->quic->stream_id) |
808 | | |
809 | | static h2o_iovec_t log_qpack_blocked(h2o_req_t *_req) |
810 | 0 | { |
811 | 0 | struct st_h2o_http3_server_stream_t *stream = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, req, _req); |
812 | 0 | return stream->qpack_blocked_ever ? h2o_iovec_init(H2O_STRLIT("1")) : h2o_iovec_init(H2O_STRLIT("0")); |
813 | 0 | } |
814 | | |
815 | | static h2o_iovec_t log_quic_stats(h2o_req_t *req) |
816 | 0 | { |
817 | 0 | #define PUSH_FIELD(field, name) \ |
818 | 0 | do { \ |
819 | 0 | len += snprintf(buf + len, bufsize - len, name "=%" PRIu64 ",", (uint64_t)stats.field); \ |
820 | 0 | if (len + 1 > bufsize) { \ |
821 | 0 | bufsize = bufsize * 3 / 2; \ |
822 | 0 | goto Redo; \ |
823 | 0 | } \ |
824 | 0 | } while (0); |
825 | |
|
826 | 0 | struct st_h2o_http3_server_conn_t *conn = (struct st_h2o_http3_server_conn_t *)req->conn; |
827 | 0 | quicly_stats_t stats; |
828 | |
|
829 | 0 | if (quicly_get_stats(conn->h3.super.quic, &stats) != 0) |
830 | 0 | return h2o_iovec_init(H2O_STRLIT("-")); |
831 | | |
832 | 0 | char *buf; |
833 | 0 | size_t len; |
834 | 0 | static __thread size_t bufsize = 100; /* this value grows by 1.5x to find adequete value, and is remembered for future |
835 | | * invocations */ |
836 | 0 | Redo: |
837 | 0 | buf = h2o_mem_alloc_pool(&req->pool, char, bufsize); |
838 | 0 | len = 0; |
839 | |
|
840 | 0 | QUICLY_STATS_FOREACH(PUSH_FIELD); |
841 | |
|
842 | 0 | return h2o_iovec_init(buf, len - 1); |
843 | |
|
844 | 0 | #undef PUSH_FIELD |
845 | 0 | } |
846 | | |
847 | 0 | DEFINE_NUMERIC_LOGGER(quic_version, "%" PRIu32, quicly_get_protocol_version(stream->quic->conn)) |
848 | | |
849 | | static uint64_t get_request_stream_size(struct st_h2o_http3_server_stream_t *stream) |
850 | 6.28k | { |
851 | 6.28k | if (!quicly_recvstate_transfer_complete(&stream->quic->recvstate)) |
852 | 0 | return stream->quic->recvstate.received.ranges[0].end; |
853 | | |
854 | | /* On reset, recvstate has no final size and clears received ranges. In that case, data_off is the best available contiguous |
855 | | * byte count. */ |
856 | 6.28k | if (stream->quic->recvstate.eos == UINT64_MAX) |
857 | 0 | return stream->quic->recvstate.data_off; |
858 | | |
859 | 6.28k | return stream->quic->recvstate.eos; |
860 | 6.28k | } |
861 | | |
862 | 0 | DEFINE_NUMERIC_LOGGER(request_stream_bytes, "%" PRIu64, get_request_stream_size(stream)) |
863 | 0 | DEFINE_NUMERIC_LOGGER(response_stream_bytes, "%" PRIu64, stream->quic->sendstate.size_inflight) |
864 | | |
865 | | #undef DEFINE_NUMERIC_LOGGER |
866 | | |
867 | | static void record_stream_stats(struct st_h2o_http3_server_stream_t *stream) |
868 | 6.28k | { |
869 | 6.28k | struct st_h2o_http3_server_conn_t *conn = get_conn(stream); |
870 | 6.28k | uint64_t request_stream_bytes = get_request_stream_size(stream); |
871 | | |
872 | 6.28k | ++conn->stats.num_requests; |
873 | 6.28k | conn->stats.req.stream_bytes += request_stream_bytes; |
874 | 6.28k | conn->stats.req.headers_frame_bytes += stream->stats.req.headers_frame_bytes; |
875 | 6.28k | conn->stats.req.body_bytes += stream->req.req_body_bytes_received; |
876 | 6.28k | conn->stats.req.qpack.count += stream->stats.req.qpack.count; |
877 | 6.28k | conn->stats.req.qpack.text_bytes += stream->stats.req.qpack.text_bytes; |
878 | 6.28k | conn->stats.resp.stream_bytes += stream->quic->sendstate.size_inflight; |
879 | 6.28k | conn->stats.resp.headers_frame_bytes += stream->stats.resp.headers_frame_bytes; |
880 | 6.28k | conn->stats.resp.body_bytes += stream->req.bytes_sent; |
881 | 6.28k | conn->stats.resp.qpack.count += stream->stats.resp.qpack.count; |
882 | 6.28k | conn->stats.resp.qpack.text_bytes += stream->stats.resp.qpack.text_bytes; |
883 | | |
884 | 6.28k | H2O_PROBE_CONN(H3S_STREAM_STATS, &conn->super, stream->quic->stream_id, request_stream_bytes, |
885 | 6.28k | stream->stats.req.headers_frame_bytes, stream->req.req_body_bytes_received, stream->stats.req.qpack.count, |
886 | 6.28k | stream->stats.req.qpack.text_bytes, stream->quic->sendstate.size_inflight, |
887 | 6.28k | stream->stats.resp.headers_frame_bytes, stream->req.bytes_sent, stream->stats.resp.qpack.count, |
888 | 6.28k | stream->stats.resp.qpack.text_bytes); |
889 | 6.28k | H2O_LOG_CONN(h3s_stream_stats, &conn->super, { |
890 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(stream_id, stream->quic->stream_id); |
891 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(request_stream_bytes, request_stream_bytes); |
892 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(request_header_bytes, stream->stats.req.headers_frame_bytes); |
893 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(request_body_bytes, stream->req.req_body_bytes_received); |
894 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(request_header_count, stream->stats.req.qpack.count); |
895 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(request_header_text_bytes, stream->stats.req.qpack.text_bytes); |
896 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(response_stream_bytes, stream->quic->sendstate.size_inflight); |
897 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(response_header_bytes, stream->stats.resp.headers_frame_bytes); |
898 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(response_body_bytes, stream->req.bytes_sent); |
899 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(response_header_count, stream->stats.resp.qpack.count); |
900 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(response_header_text_bytes, stream->stats.resp.qpack.text_bytes); |
901 | 6.28k | }); |
902 | 6.28k | } |
903 | | |
904 | | void on_stream_destroy(quicly_stream_t *qs, quicly_error_t err) |
905 | 6.28k | { |
906 | 6.28k | struct st_h2o_http3_server_stream_t *stream = qs->data; |
907 | 6.28k | struct st_h2o_http3_server_conn_t *conn = get_conn(stream); |
908 | | |
909 | | /* Unless the stream is closed as part of connection teardown, it is already in CLOSE_WAIT when destroyed; state cleanup has |
910 | | * already been performed. In contrast, when the QUIC connection is closed, streams can be destroyed in any state. In that case, |
911 | | * consistency within the connection itself does not always need to be preserved, as the connection is being discarded; however, |
912 | | * state external to the connection still needs to be maintained. */ |
913 | 6.28k | --*get_state_counter(conn, stream->state); |
914 | | |
915 | 6.28k | req_scheduler_deactivate(&conn->scheduler.reqs, &stream->scheduler); |
916 | | |
917 | 6.28k | if (h2o_linklist_is_linked(&stream->link)) |
918 | 0 | h2o_linklist_unlink(&stream->link); |
919 | 6.28k | if (stream->qpack_blocked_ref != 0) { |
920 | 0 | assert(conn->num_qpack_blocked > 0); |
921 | 0 | --conn->num_qpack_blocked; |
922 | 0 | } |
923 | 6.28k | if (h2o_linklist_is_linked(&stream->link_resp_settings_blocked)) |
924 | 0 | h2o_linklist_unlink(&stream->link_resp_settings_blocked); |
925 | 6.28k | if (stream->state != H2O_HTTP3_SERVER_STREAM_STATE_CLOSE_WAIT) |
926 | 4.41k | pre_dispose_request(stream); |
927 | 6.28k | if (!stream->req_disposed) { |
928 | 4.41k | record_stream_stats(stream); |
929 | 4.41k | h2o_dispose_request(&stream->req); |
930 | 4.41k | } |
931 | | /* in case the stream is destroyed before the buffer is fully consumed */ |
932 | 6.28k | h2o_buffer_dispose(&stream->recvbuf.buf); |
933 | | |
934 | 6.28k | free(stream); |
935 | | |
936 | 6.28k | uint32_t num_req_streams_incl_self = quicly_num_streams_by_group(conn->h3.super.quic, 0, 0); |
937 | 6.28k | assert(num_req_streams_incl_self > 0 && |
938 | 6.28k | "during the invocation of the destroy callback, stream count should include the number of the stream being destroyed"); |
939 | 6.28k | if (num_req_streams_incl_self == 1) |
940 | 6.28k | h2o_conn_set_state(&conn->super, H2O_CONN_STATE_IDLE); |
941 | 6.28k | } |
942 | | |
943 | | /** |
944 | | * Converts vectors owned by the generator to ones owned by the HTTP/3 implementation, as the former becomes inaccessible once we |
945 | | * call `do_proceed`. |
946 | | */ |
947 | | static int retain_sendvecs(struct st_h2o_http3_server_stream_t *stream) |
948 | 368 | { |
949 | 1.47k | for (; stream->sendbuf.min_index_to_addref != stream->sendbuf.vecs.size; ++stream->sendbuf.min_index_to_addref) { |
950 | 1.10k | struct st_h2o_http3_server_sendvec_t *vec = stream->sendbuf.vecs.entries + stream->sendbuf.min_index_to_addref; |
951 | 1.10k | assert(vec->vec.callbacks->read_ == h2o_sendvec_read_raw); |
952 | 1.10k | if (!(vec->vec.callbacks == &self_allocated_vec_callbacks || vec->vec.callbacks == &immutable_vec_callbacks)) { |
953 | 736 | size_t off_within_vec = stream->sendbuf.min_index_to_addref == 0 ? stream->sendbuf.off_within_first_vec : 0, |
954 | 736 | newlen = vec->vec.len - off_within_vec; |
955 | 736 | void *newbuf = sendvec_size_is_for_recycle(newlen) ? h2o_mem_alloc_recycle(&h2o_socket_ssl_buffer_allocator) |
956 | 736 | : h2o_mem_alloc(newlen); |
957 | 736 | memcpy(newbuf, vec->vec.raw + off_within_vec, newlen); |
958 | 736 | vec->vec = (h2o_sendvec_t){&self_allocated_vec_callbacks, newlen, {newbuf}}; |
959 | 736 | if (stream->sendbuf.min_index_to_addref == 0) |
960 | 0 | stream->sendbuf.off_within_first_vec = 0; |
961 | 736 | } |
962 | 1.10k | } |
963 | | |
964 | 368 | return 1; |
965 | 368 | } |
966 | | |
967 | | static void collect_quic_performance_metrics(struct st_h2o_http3_server_stream_t *stream) |
968 | 877 | { |
969 | 877 | struct st_h2o_http3_server_conn_t *conn = get_conn(stream); |
970 | | |
971 | | /* If the request is the first request, log method, content-length, and ttlb so that the performance of HTTP/3 can be evaluated. |
972 | | * Note, to compare the performance of connections using different CC parameters, only the numbers from connections that served |
973 | | * requests capable of fulfilling the CWND regardless of CC behavior (i.e, pre-built local objects) can be used. To extract such |
974 | | * connections, properties other than method and content-length might be needed. */ |
975 | 877 | if (stream->quic->stream_id == 0) { |
976 | 877 | #define EMIT_STATS_FIELD(fld, lit) PTLS_LOG__DO_ELEMENT_UNSIGNED(lit, stats.fld); |
977 | 877 | H2O_LOG_CONN(h3s_stream0_ttlb, &conn->super, { |
978 | 877 | PTLS_LOG_ELEMENT_UNSAFESTR(method, stream->req.method.base, stream->req.method.len); |
979 | 877 | PTLS_LOG_ELEMENT_UNSIGNED(content_length, stream->req.res.content_length); |
980 | 877 | struct timeval now = h2o_gettimeofday(conn->super.ctx->loop); |
981 | 877 | int64_t ttlb = (h2o_timeval_subtract(&stream->req.timestamps.request_begin_at, &now) + 500) / 1000; |
982 | 877 | if (ttlb < 0) |
983 | 877 | ttlb = 0; |
984 | 877 | PTLS_LOG_ELEMENT_SIGNED(ttlb, ttlb); |
985 | 877 | quicly_stats_t stats; |
986 | 877 | if (quicly_get_stats(conn->h3.super.quic, &stats) == 0) { |
987 | 877 | QUICLY_STATS_FOREACH(EMIT_STATS_FIELD); /* if this is too heavyweight, we can hexdump `stats` instead */ |
988 | 877 | } |
989 | 877 | }); |
990 | 877 | #undef EMIT_STATS_FIELD |
991 | 877 | } |
992 | 877 | } |
993 | | |
994 | | static void on_send_shift(quicly_stream_t *qs, size_t delta) |
995 | 877 | { |
996 | 877 | struct st_h2o_http3_server_stream_t *stream = qs->data; |
997 | 877 | size_t i; |
998 | | |
999 | 877 | assert(H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_BEFORE_BLOCK <= stream->state && |
1000 | 877 | stream->state <= H2O_HTTP3_SERVER_STREAM_STATE_SEND_BODY); |
1001 | 877 | assert(delta != 0); |
1002 | 877 | assert(stream->sendbuf.vecs.size != 0); |
1003 | | |
1004 | 877 | size_t bytes_avail_in_first_vec = stream->sendbuf.vecs.entries[0].vec.len - stream->sendbuf.off_within_first_vec; |
1005 | 877 | if (delta < bytes_avail_in_first_vec) { |
1006 | 0 | stream->sendbuf.off_within_first_vec += delta; |
1007 | 0 | return; |
1008 | 0 | } |
1009 | 877 | delta -= bytes_avail_in_first_vec; |
1010 | 877 | stream->sendbuf.off_within_first_vec = 0; |
1011 | 877 | dispose_sendvec(&stream->sendbuf.vecs.entries[0]); |
1012 | | |
1013 | 2.37k | for (i = 1; delta != 0; ++i) { |
1014 | 1.50k | assert(i < stream->sendbuf.vecs.size); |
1015 | 1.50k | if (delta < stream->sendbuf.vecs.entries[i].vec.len) { |
1016 | 0 | stream->sendbuf.off_within_first_vec = delta; |
1017 | 0 | break; |
1018 | 0 | } |
1019 | 1.50k | delta -= stream->sendbuf.vecs.entries[i].vec.len; |
1020 | 1.50k | dispose_sendvec(&stream->sendbuf.vecs.entries[i]); |
1021 | 1.50k | } |
1022 | 877 | memmove(stream->sendbuf.vecs.entries, stream->sendbuf.vecs.entries + i, |
1023 | 877 | (stream->sendbuf.vecs.size - i) * sizeof(stream->sendbuf.vecs.entries[0])); |
1024 | 877 | stream->sendbuf.vecs.size -= i; |
1025 | 877 | if (stream->sendbuf.min_index_to_addref <= i) { |
1026 | 877 | stream->sendbuf.min_index_to_addref = 0; |
1027 | 877 | } else { |
1028 | 0 | stream->sendbuf.min_index_to_addref -= i; |
1029 | 0 | } |
1030 | | |
1031 | 877 | if (stream->sendbuf.vecs.size == 0) { |
1032 | 877 | if (quicly_sendstate_is_open(&stream->quic->sendstate)) { |
1033 | 0 | assert((H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_BEFORE_BLOCK <= stream->state && |
1034 | 0 | stream->state <= H2O_HTTP3_SERVER_STREAM_STATE_SEND_HEADERS) || |
1035 | 0 | stream->proceed_requested); |
1036 | 877 | } else { |
1037 | 877 | collect_quic_performance_metrics(stream); |
1038 | 877 | if (quicly_stream_has_receive_side(0, stream->quic->stream_id)) { |
1039 | 877 | quicly_request_stop(stream->quic, H2O_HTTP3_ERROR_EARLY_RESPONSE); |
1040 | 877 | cancel_qpack_decoder(stream); |
1041 | 877 | } |
1042 | 877 | set_state(stream, H2O_HTTP3_SERVER_STREAM_STATE_CLOSE_WAIT, 0); |
1043 | 877 | } |
1044 | 877 | } |
1045 | 877 | } |
1046 | | |
1047 | | static void on_send_emit(quicly_stream_t *qs, size_t off, void *_dst, size_t *len, int *wrote_all) |
1048 | 1.24k | { |
1049 | 1.24k | struct st_h2o_http3_server_stream_t *stream = qs->data; |
1050 | | |
1051 | 1.24k | assert(H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_BEFORE_BLOCK <= stream->state && |
1052 | 1.24k | stream->state <= H2O_HTTP3_SERVER_STREAM_STATE_SEND_BODY); |
1053 | | |
1054 | 1.24k | uint8_t *dst = _dst, *dst_end = dst + *len; |
1055 | 1.24k | size_t vec_index = 0; |
1056 | | |
1057 | | /* find the start position identified by vec_index and off */ |
1058 | 1.24k | off += stream->sendbuf.off_within_first_vec; |
1059 | 1.24k | while (off != 0) { |
1060 | 0 | assert(vec_index < stream->sendbuf.vecs.size); |
1061 | 0 | if (off < stream->sendbuf.vecs.entries[vec_index].vec.len) |
1062 | 0 | break; |
1063 | 0 | off -= stream->sendbuf.vecs.entries[vec_index].vec.len; |
1064 | 0 | ++vec_index; |
1065 | 0 | } |
1066 | 1.24k | assert(vec_index < stream->sendbuf.vecs.size); |
1067 | | |
1068 | | /* write */ |
1069 | 1.24k | *wrote_all = 0; |
1070 | 3.48k | do { |
1071 | 3.48k | struct st_h2o_http3_server_sendvec_t *this_vec = stream->sendbuf.vecs.entries + vec_index; |
1072 | 3.48k | size_t sz = this_vec->vec.len - off; |
1073 | 3.48k | if (dst_end - dst < sz) |
1074 | 0 | sz = dst_end - dst; |
1075 | | /* convert vector into raw form, the first time it's being sent (TODO use ssl_buffer_recyle) */ |
1076 | 3.48k | if (this_vec->vec.callbacks->read_ != h2o_sendvec_read_raw) { |
1077 | 0 | size_t newlen = this_vec->vec.len; |
1078 | 0 | void *newbuf = sendvec_size_is_for_recycle(newlen) ? h2o_mem_alloc_recycle(&h2o_socket_ssl_buffer_allocator) |
1079 | 0 | : h2o_mem_alloc(newlen); |
1080 | 0 | if (!this_vec->vec.callbacks->read_(&this_vec->vec, newbuf, newlen)) { |
1081 | 0 | free(newbuf); |
1082 | 0 | goto Error; |
1083 | 0 | } |
1084 | 0 | this_vec->vec = (h2o_sendvec_t){&self_allocated_vec_callbacks, newlen, {newbuf}}; |
1085 | 0 | } |
1086 | | /* copy payload */ |
1087 | 3.48k | memcpy(dst, this_vec->vec.raw + off, sz); |
1088 | | /* adjust offsets */ |
1089 | 3.48k | if (this_vec->entity_offset != UINT64_MAX && stream->req.bytes_sent < this_vec->entity_offset + off + sz) |
1090 | 1.11k | stream->req.bytes_sent = this_vec->entity_offset + off + sz; |
1091 | 3.48k | dst += sz; |
1092 | 3.48k | off += sz; |
1093 | | /* when reaching the end of the current vector, update vec_index, wrote_all */ |
1094 | 3.48k | if (off == this_vec->vec.len) { |
1095 | 3.48k | off = 0; |
1096 | 3.48k | ++vec_index; |
1097 | 3.48k | if (vec_index == stream->sendbuf.vecs.size) { |
1098 | 1.24k | *wrote_all = 1; |
1099 | 1.24k | break; |
1100 | 1.24k | } |
1101 | 3.48k | } |
1102 | 3.48k | } while (dst != dst_end); |
1103 | | |
1104 | 1.24k | *len = dst - (uint8_t *)_dst; |
1105 | | |
1106 | | /* retain the payload of response body before calling `h2o_proceed_request`, as the generator might discard the buffer */ |
1107 | 1.24k | if (stream->state == H2O_HTTP3_SERVER_STREAM_STATE_SEND_BODY && *wrote_all && |
1108 | 1.24k | quicly_sendstate_is_open(&stream->quic->sendstate) && !stream->proceed_requested) { |
1109 | 368 | if (!retain_sendvecs(stream)) |
1110 | 0 | goto Error; |
1111 | 368 | stream->proceed_requested = 1; |
1112 | 368 | stream->proceed_while_sending = 1; |
1113 | 368 | } |
1114 | | |
1115 | 1.24k | return; |
1116 | 1.24k | Error: |
1117 | 0 | *len = 0; |
1118 | 0 | *wrote_all = 1; |
1119 | 0 | shutdown_stream(stream, H2O_HTTP3_ERROR_EARLY_RESPONSE, H2O_HTTP3_ERROR_INTERNAL, 0, 0); |
1120 | 0 | } |
1121 | | |
1122 | | static void on_send_stop(quicly_stream_t *qs, quicly_error_t err) |
1123 | 0 | { |
1124 | 0 | struct st_h2o_http3_server_stream_t *stream = qs->data; |
1125 | |
|
1126 | 0 | shutdown_stream(stream, H2O_HTTP3_ERROR_REQUEST_CANCELLED, err, 0, 0); |
1127 | 0 | } |
1128 | | |
1129 | | static void handle_buffered_input(struct st_h2o_http3_server_stream_t *stream, int in_generator) |
1130 | 6.28k | { |
1131 | 6.28k | struct st_h2o_http3_server_conn_t *conn = get_conn(stream); |
1132 | | |
1133 | 6.28k | if (stream->state >= H2O_HTTP3_SERVER_STREAM_STATE_CLOSE_WAIT) |
1134 | 0 | return; |
1135 | 6.28k | if (stream->qpack_blocked_ref != 0) |
1136 | 0 | return; |
1137 | | |
1138 | 6.28k | { /* Process contiguous bytes in the receive buffer until one of the following conditions are reached: |
1139 | | * a) connection- or stream-level error (i.e., state advanced to CLOSE_WAIT) is detected - in which case we exit, |
1140 | | * b) incomplete frame is detected - wait for more (if the stream is open) or raise a connection error, or |
1141 | | * c) all bytes are processed or read_blocked / qpack_blocked_ref is set synchronously - exit the loop. */ |
1142 | 6.28k | size_t bytes_available = quicly_recvstate_bytes_available(&stream->quic->recvstate); |
1143 | 6.28k | assert(bytes_available <= stream->recvbuf.buf->size); |
1144 | 6.28k | if (bytes_available != 0) { |
1145 | 6.28k | const uint8_t *src = (const uint8_t *)stream->recvbuf.buf->bytes, *src_end = src + bytes_available; |
1146 | 24.5k | do { |
1147 | 24.5k | quicly_error_t err; |
1148 | 24.5k | const char *err_desc = NULL; |
1149 | 24.5k | if ((err = stream->recvbuf.handle_input(stream, &src, src_end, in_generator, &err_desc)) != 0) { |
1150 | 4.04k | if (err == H2O_HTTP3_ERROR_INCOMPLETE) { |
1151 | 184 | if (!quicly_recvstate_transfer_complete(&stream->quic->recvstate)) |
1152 | 0 | break; |
1153 | 184 | err = H2O_HTTP3_ERROR_GENERAL_PROTOCOL; |
1154 | 184 | err_desc = "incomplete frame"; |
1155 | 184 | } |
1156 | 4.04k | h2o_quic_close_connection(&conn->h3.super, err, err_desc); |
1157 | 4.04k | return; |
1158 | 20.4k | } else if (stream->state >= H2O_HTTP3_SERVER_STREAM_STATE_CLOSE_WAIT) { |
1159 | 374 | return; |
1160 | 374 | } |
1161 | 24.5k | } while (src != src_end && !stream->read_blocked && stream->qpack_blocked_ref == 0 && |
1162 | 18.2k | !quicly_stop_requested(stream->quic)); |
1163 | | /* Processed zero or more bytes without noticing an error; shift the bytes that have been processed as frames. */ |
1164 | 1.86k | size_t bytes_consumed = src - (const uint8_t *)stream->recvbuf.buf->bytes; |
1165 | 1.86k | h2o_buffer_consume(&stream->recvbuf.buf, bytes_consumed); |
1166 | 1.86k | quicly_stream_sync_recvbuf(stream->quic, bytes_consumed); |
1167 | 1.86k | if (stream->read_blocked || stream->qpack_blocked_ref != 0) |
1168 | 233 | return; |
1169 | 1.86k | } |
1170 | 6.28k | } |
1171 | | |
1172 | 1.63k | if (quicly_recvstate_transfer_complete(&stream->quic->recvstate)) { |
1173 | 1.63k | if (stream->recvbuf.buf->size == 0 && (stream->recvbuf.handle_input == handle_input_expect_data || |
1174 | 1.19k | stream->recvbuf.handle_input == handle_input_post_trailers)) { |
1175 | | /* have complete request, advance the state and process the request */ |
1176 | 1.19k | if (stream->req.content_length != SIZE_MAX && stream->req.content_length != stream->req.req_body_bytes_received) { |
1177 | | /* the request terminated abruptly; reset the stream as we do for HTTP/2 */ |
1178 | 186 | shutdown_stream(stream, H2O_HTTP3_ERROR_NONE /* ignored */, |
1179 | 186 | stream->req.req_body_bytes_received < stream->req.content_length |
1180 | 186 | ? H2O_HTTP3_ERROR_REQUEST_INCOMPLETE |
1181 | 186 | : H2O_HTTP3_ERROR_GENERAL_PROTOCOL, |
1182 | 186 | in_generator, 0); |
1183 | 1.01k | } else { |
1184 | 1.01k | if (stream->req.write_req.cb != NULL) { |
1185 | 0 | if (!h2o_linklist_is_linked(&stream->link)) |
1186 | 0 | h2o_linklist_insert(&conn->delayed_streams.req_streaming, &stream->link); |
1187 | 0 | request_run_delayed(conn); |
1188 | 1.01k | } else if (!stream->req.process_called && stream->state < H2O_HTTP3_SERVER_STREAM_STATE_SEND_HEADERS) { |
1189 | | /* process the request, if we haven't called h2o_process_request nor send an error response */ |
1190 | 783 | switch (stream->state) { |
1191 | 0 | case H2O_HTTP3_SERVER_STREAM_STATE_RECV_HEADERS: |
1192 | 783 | case H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_BEFORE_BLOCK: |
1193 | 783 | case H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_UNBLOCKED: |
1194 | 783 | break; |
1195 | 0 | default: |
1196 | 0 | assert(!"unexpected state"); |
1197 | 0 | break; |
1198 | 783 | } |
1199 | 783 | set_state(stream, H2O_HTTP3_SERVER_STREAM_STATE_REQ_PENDING, in_generator); |
1200 | 783 | h2o_linklist_insert(&conn->delayed_streams.pending, &stream->link); |
1201 | 783 | request_run_delayed(conn); |
1202 | 783 | } |
1203 | 1.01k | } |
1204 | 1.19k | } else { |
1205 | | /* request stream closed with an incomplete request, send error */ |
1206 | 436 | shutdown_stream(stream, H2O_HTTP3_ERROR_NONE /* ignored */, H2O_HTTP3_ERROR_REQUEST_INCOMPLETE, in_generator, 0); |
1207 | 436 | } |
1208 | 1.63k | } else { |
1209 | 0 | if (stream->state == H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_BEFORE_BLOCK && stream->req_body != NULL && |
1210 | 0 | stream->req_body->size >= H2O_HTTP3_REQUEST_BODY_MIN_BYTES_TO_BLOCK) { |
1211 | | /* switch to blocked state if the request body is becoming large (this limits the concurrency to the backend) */ |
1212 | 0 | stream->read_blocked = 1; |
1213 | 0 | h2o_linklist_insert(&conn->delayed_streams.recv_body_blocked, &stream->link); |
1214 | 0 | set_state(stream, H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_BLOCKED, in_generator); |
1215 | 0 | check_run_blocked(conn); |
1216 | 0 | } else if (stream->req.write_req.cb != NULL && stream->req_body->size != 0) { |
1217 | | /* in streaming mode, let the run_delayed invoke write_req */ |
1218 | 0 | if (!h2o_linklist_is_linked(&stream->link)) |
1219 | 0 | h2o_linklist_insert(&conn->delayed_streams.req_streaming, &stream->link); |
1220 | 0 | request_run_delayed(conn); |
1221 | 0 | } |
1222 | 0 | } |
1223 | 1.63k | } |
1224 | | |
1225 | | static void on_receive(quicly_stream_t *qs, size_t off, const void *input, size_t len) |
1226 | 6.28k | { |
1227 | 6.28k | struct st_h2o_http3_server_stream_t *stream = qs->data; |
1228 | | |
1229 | | /* save received data (FIXME avoid copying if possible; see hqclient.c) */ |
1230 | 6.28k | h2o_http3_update_recvbuf(&stream->recvbuf.buf, off, input, len); |
1231 | | |
1232 | 6.28k | if (stream->read_blocked || quicly_stop_requested(stream->quic)) |
1233 | 0 | return; |
1234 | | |
1235 | | /* handle input (FIXME propage err_desc) */ |
1236 | 6.28k | handle_buffered_input(stream, 0); |
1237 | 6.28k | } |
1238 | | |
1239 | | static void on_receive_reset(quicly_stream_t *qs, quicly_error_t err) |
1240 | 0 | { |
1241 | 0 | struct st_h2o_http3_server_stream_t *stream = qs->data; |
1242 | |
|
1243 | 0 | shutdown_stream(stream, H2O_HTTP3_ERROR_NONE /* ignored */, |
1244 | 0 | stream->state == H2O_HTTP3_SERVER_STREAM_STATE_RECV_HEADERS ? H2O_HTTP3_ERROR_REQUEST_REJECTED |
1245 | 0 | : H2O_HTTP3_ERROR_REQUEST_CANCELLED, |
1246 | 0 | 0, 1); |
1247 | 0 | } |
1248 | | |
1249 | | static void close_request_streaming(struct st_h2o_http3_server_stream_t *stream) |
1250 | 0 | { |
1251 | 0 | stream->req.write_req.cb = NULL; |
1252 | 0 | stream->req.write_req.ctx = NULL; |
1253 | 0 | stream->req.forward_datagram.write_ = NULL; |
1254 | 0 | stream->req.proceed_req = NULL; |
1255 | 0 | stream->req_streaming = 0; |
1256 | 0 | if (!stream->req.is_tunnel_req) |
1257 | 0 | --get_conn(stream)->num_streams_req_streaming; |
1258 | 0 | check_run_blocked(get_conn(stream)); |
1259 | 0 | } |
1260 | | |
1261 | | static void proceed_request_streaming(h2o_req_t *_req, const char *errstr) |
1262 | 0 | { |
1263 | 0 | struct st_h2o_http3_server_stream_t *stream = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, req, _req); |
1264 | 0 | struct st_h2o_http3_server_conn_t *conn = get_conn(stream); |
1265 | |
|
1266 | 0 | assert(stream->req_body != NULL); |
1267 | 0 | assert(errstr != NULL || !h2o_linklist_is_linked(&stream->link)); |
1268 | 0 | assert(conn->num_streams_req_streaming != 0 || stream->req.is_tunnel_req); |
1269 | | |
1270 | 0 | int reset_received = quicly_recvstate_transfer_complete(&stream->quic->recvstate) && stream->quic->recvstate.eos == UINT64_MAX; |
1271 | 0 | if (errstr != NULL || reset_received) { |
1272 | 0 | close_request_streaming(stream); |
1273 | 0 | shutdown_stream(stream, H2O_HTTP3_ERROR_INTERNAL, H2O_HTTP3_ERROR_INTERNAL, 1, 1); |
1274 | 0 | return; |
1275 | 0 | } |
1276 | | |
1277 | | /* remove the bytes from the request body buffer */ |
1278 | 0 | assert(stream->req.entity.len == stream->req_body->size); |
1279 | 0 | h2o_buffer_consume(&stream->req_body, stream->req_body->size); |
1280 | 0 | stream->req.entity = h2o_iovec_init(NULL, 0); |
1281 | | |
1282 | | /* unblock read until the next invocation of write_req, or after the final invocation */ |
1283 | 0 | stream->read_blocked = 0; |
1284 | |
|
1285 | 0 | if (stream->req_streaming_eos_delivered) { |
1286 | 0 | close_request_streaming(stream); |
1287 | 0 | return; |
1288 | 0 | } |
1289 | | |
1290 | | /* handle input in the receive buffer */ |
1291 | 0 | handle_buffered_input(stream, 1); |
1292 | 0 | } |
1293 | | |
1294 | | static void run_delayed(h2o_timer_t *timer) |
1295 | 783 | { |
1296 | 783 | struct st_h2o_http3_server_conn_t *conn = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_conn_t, timeout, timer); |
1297 | 783 | int made_progress; |
1298 | | |
1299 | 1.56k | do { |
1300 | 1.56k | made_progress = 0; |
1301 | | |
1302 | | /* promote blocked stream to unblocked state, if possible */ |
1303 | 1.56k | if (conn->num_streams.recv_body_unblocked + conn->num_streams_req_streaming < |
1304 | 1.56k | conn->super.ctx->globalconf->http3.max_concurrent_streaming_requests_per_connection && |
1305 | 1.56k | !h2o_linklist_is_empty(&conn->delayed_streams.recv_body_blocked)) { |
1306 | 0 | struct st_h2o_http3_server_stream_t *stream = |
1307 | 0 | H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, link, conn->delayed_streams.recv_body_blocked.next); |
1308 | 0 | assert(stream->state == H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_BLOCKED); |
1309 | 0 | assert(stream->read_blocked); |
1310 | 0 | h2o_linklist_unlink(&stream->link); |
1311 | 0 | made_progress = 1; |
1312 | 0 | quicly_stream_set_receive_window(stream->quic, conn->super.ctx->globalconf->http3.active_stream_window_size); |
1313 | 0 | if (h2o_req_can_stream_request(&stream->req)) { |
1314 | | /* use streaming mode */ |
1315 | 0 | stream->req_streaming = 1; |
1316 | 0 | ++conn->num_streams_req_streaming; |
1317 | 0 | stream->req.proceed_req = proceed_request_streaming; |
1318 | 0 | set_state(stream, H2O_HTTP3_SERVER_STREAM_STATE_SEND_HEADERS, 0); |
1319 | 0 | h2o_process_request(&stream->req); |
1320 | 0 | } else { |
1321 | | /* unblock, read the bytes in receive buffer */ |
1322 | 0 | stream->read_blocked = 0; |
1323 | 0 | set_state(stream, H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_UNBLOCKED, 0); |
1324 | 0 | handle_buffered_input(stream, 0); |
1325 | 0 | if (quicly_get_state(conn->h3.super.quic) >= QUICLY_STATE_CLOSING) |
1326 | 0 | return; |
1327 | 0 | } |
1328 | 0 | } |
1329 | | |
1330 | | /* process streams using request streaming, that have new data to submit */ |
1331 | 1.56k | while (!h2o_linklist_is_empty(&conn->delayed_streams.req_streaming)) { |
1332 | 0 | struct st_h2o_http3_server_stream_t *stream = |
1333 | 0 | H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, link, conn->delayed_streams.req_streaming.next); |
1334 | 0 | int is_end_stream = quicly_recvstate_transfer_complete(&stream->quic->recvstate); |
1335 | 0 | assert(stream->req.process_called); |
1336 | 0 | assert(stream->req.write_req.cb != NULL); |
1337 | 0 | assert(stream->req_body != NULL); |
1338 | 0 | assert(stream->req_body->size != 0 || is_end_stream); |
1339 | 0 | assert(!stream->read_blocked); |
1340 | 0 | h2o_linklist_unlink(&stream->link); |
1341 | 0 | stream->read_blocked = 1; |
1342 | 0 | if (is_end_stream) |
1343 | 0 | stream->req_streaming_eos_delivered = 1; |
1344 | 0 | made_progress = 1; |
1345 | 0 | assert(stream->req.entity.len == stream->req_body->size && |
1346 | 0 | (stream->req.entity.len == 0 || stream->req.entity.base == stream->req_body->bytes)); |
1347 | 0 | if (stream->req.write_req.cb(stream->req.write_req.ctx, is_end_stream) != 0) |
1348 | 0 | shutdown_stream(stream, H2O_HTTP3_ERROR_INTERNAL, H2O_HTTP3_ERROR_INTERNAL, 0, 1); |
1349 | 0 | } |
1350 | | |
1351 | | /* process the requests (not in streaming mode); TODO cap concurrency? */ |
1352 | 2.34k | while (!h2o_linklist_is_empty(&conn->delayed_streams.pending)) { |
1353 | 783 | struct st_h2o_http3_server_stream_t *stream = |
1354 | 783 | H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, link, conn->delayed_streams.pending.next); |
1355 | 783 | assert(stream->state == H2O_HTTP3_SERVER_STREAM_STATE_REQ_PENDING); |
1356 | 783 | assert(!stream->req.process_called); |
1357 | 783 | assert(!stream->read_blocked); |
1358 | 783 | h2o_linklist_unlink(&stream->link); |
1359 | 783 | made_progress = 1; |
1360 | 783 | set_state(stream, H2O_HTTP3_SERVER_STREAM_STATE_SEND_HEADERS, 0); |
1361 | 783 | h2o_process_request(&stream->req); |
1362 | 783 | } |
1363 | | |
1364 | 1.56k | } while (made_progress); |
1365 | 783 | } |
1366 | | |
1367 | | quicly_error_t handle_input_post_trailers(struct st_h2o_http3_server_stream_t *stream, const uint8_t **src, const uint8_t *src_end, |
1368 | | int in_generator, const char **err_desc) |
1369 | 377 | { |
1370 | 377 | h2o_http3_read_frame_t frame; |
1371 | 377 | quicly_error_t ret; |
1372 | | |
1373 | | /* read and ignore unknown frames */ |
1374 | 377 | if ((ret = h2o_http3_read_frame(&frame, 0, H2O_HTTP3_STREAM_TYPE_REQUEST, get_conn(stream)->h3.max_frame_payload_size, src, |
1375 | 377 | src_end, err_desc)) != 0) |
1376 | 15 | return ret; |
1377 | 362 | switch (frame.type) { |
1378 | 1 | case H2O_HTTP3_FRAME_TYPE_HEADERS: |
1379 | 5 | case H2O_HTTP3_FRAME_TYPE_DATA: |
1380 | 5 | return H2O_HTTP3_ERROR_FRAME_UNEXPECTED; |
1381 | 357 | default: |
1382 | 357 | break; |
1383 | 362 | } |
1384 | | |
1385 | 357 | return 0; |
1386 | 362 | } |
1387 | | |
1388 | | static quicly_error_t handle_input_expect_data_payload(struct st_h2o_http3_server_stream_t *stream, const uint8_t **src, |
1389 | | const uint8_t *src_end, int in_generator, const char **err_desc) |
1390 | 1.72k | { |
1391 | 1.72k | size_t bytes_avail = src_end - *src; |
1392 | | |
1393 | | /* append data to body buffer */ |
1394 | 1.72k | if (bytes_avail > stream->recvbuf.bytes_left_in_data_frame) |
1395 | 1.55k | bytes_avail = stream->recvbuf.bytes_left_in_data_frame; |
1396 | 1.72k | if (stream->req_body == NULL) |
1397 | 201 | h2o_buffer_init(&stream->req_body, &h2o_socket_buffer_prototype); |
1398 | 1.72k | if (!h2o_buffer_try_append(&stream->req_body, *src, bytes_avail)) |
1399 | 0 | return H2O_HTTP3_ERROR_INTERNAL; |
1400 | 1.72k | stream->req.entity = h2o_iovec_init(stream->req_body->bytes, stream->req_body->size); |
1401 | 1.72k | stream->req.req_body_bytes_received += bytes_avail; |
1402 | 1.72k | stream->recvbuf.bytes_left_in_data_frame -= bytes_avail; |
1403 | 1.72k | *src += bytes_avail; |
1404 | | |
1405 | 1.72k | if (stream->recvbuf.bytes_left_in_data_frame == 0) |
1406 | 1.57k | stream->recvbuf.handle_input = handle_input_expect_data; |
1407 | | |
1408 | 1.72k | return 0; |
1409 | 1.72k | } |
1410 | | |
1411 | | quicly_error_t handle_input_expect_data(struct st_h2o_http3_server_stream_t *stream, const uint8_t **src, const uint8_t *src_end, |
1412 | | int in_generator, const char **err_desc) |
1413 | 15.3k | { |
1414 | 15.3k | h2o_http3_read_frame_t frame; |
1415 | 15.3k | quicly_error_t ret; |
1416 | | |
1417 | | /* read frame */ |
1418 | 15.3k | if ((ret = h2o_http3_read_frame(&frame, 0, H2O_HTTP3_STREAM_TYPE_REQUEST, get_conn(stream)->h3.max_frame_payload_size, src, |
1419 | 15.3k | src_end, err_desc)) != 0) |
1420 | 64 | return ret; |
1421 | 15.2k | switch (frame.type) { |
1422 | 79 | case H2O_HTTP3_FRAME_TYPE_HEADERS: |
1423 | | /* when in tunnel mode, trailers forbidden */ |
1424 | 79 | if (stream->req.is_tunnel_req) { |
1425 | 0 | *err_desc = "unexpected frame type"; |
1426 | 0 | return H2O_HTTP3_ERROR_FRAME_UNEXPECTED; |
1427 | 0 | } |
1428 | | /* trailers, ignore but disallow succeeding DATA or HEADERS frame */ |
1429 | 79 | stream->recvbuf.handle_input = handle_input_post_trailers; |
1430 | 79 | return 0; |
1431 | 14.9k | case H2O_HTTP3_FRAME_TYPE_DATA: |
1432 | 14.9k | if (stream->req.content_length != SIZE_MAX && |
1433 | 364 | stream->req.content_length - stream->req.req_body_bytes_received < frame.length) { |
1434 | | /* The only viable option here is to reset the stream, as we might have already started streaming the request body |
1435 | | * upstream. This behavior is consistent with what we do in HTTP/2. */ |
1436 | 62 | shutdown_stream(stream, H2O_HTTP3_ERROR_EARLY_RESPONSE, H2O_HTTP3_ERROR_GENERAL_PROTOCOL, in_generator, 0); |
1437 | 62 | return 0; |
1438 | 62 | } |
1439 | 14.8k | break; |
1440 | 14.8k | default: |
1441 | 270 | return 0; |
1442 | 15.2k | } |
1443 | | |
1444 | | /* got a DATA frame */ |
1445 | 14.8k | if (frame.length != 0) { |
1446 | 1.82k | if (h2o_timeval_is_null(&stream->req.timestamps.request_body_begin_at)) |
1447 | 298 | stream->req.timestamps.request_body_begin_at = h2o_gettimeofday(get_conn(stream)->super.ctx->loop); |
1448 | 1.82k | stream->recvbuf.handle_input = handle_input_expect_data_payload; |
1449 | 1.82k | stream->recvbuf.bytes_left_in_data_frame = frame.length; |
1450 | 1.82k | } |
1451 | | |
1452 | 14.8k | return 0; |
1453 | 15.2k | } |
1454 | | |
1455 | | static int handle_input_expect_headers_send_http_error(struct st_h2o_http3_server_stream_t *stream, |
1456 | | void (*sendfn)(h2o_req_t *, const char *, const char *, int), |
1457 | | const char *reason, const char *body, const char **err_desc) |
1458 | 577 | { |
1459 | 577 | if (!quicly_recvstate_transfer_complete(&stream->quic->recvstate)) { |
1460 | 0 | quicly_request_stop(stream->quic, H2O_HTTP3_ERROR_EARLY_RESPONSE); |
1461 | 0 | cancel_qpack_decoder(stream); |
1462 | 0 | } |
1463 | | |
1464 | 577 | set_state(stream, H2O_HTTP3_SERVER_STREAM_STATE_SEND_HEADERS, 0); |
1465 | 577 | sendfn(&stream->req, reason, body, 0); |
1466 | 577 | *err_desc = NULL; |
1467 | | |
1468 | 577 | return 0; |
1469 | 577 | } |
1470 | | |
1471 | | static int handle_input_expect_headers_process_request_immediately(struct st_h2o_http3_server_stream_t *stream, |
1472 | | const char **err_desc) |
1473 | 233 | { |
1474 | 233 | h2o_buffer_init(&stream->req_body, &h2o_socket_buffer_prototype); |
1475 | 233 | stream->req.entity = h2o_iovec_init("", 0); |
1476 | 233 | stream->read_blocked = 1; |
1477 | 233 | stream->req.proceed_req = proceed_request_streaming; |
1478 | 233 | set_state(stream, H2O_HTTP3_SERVER_STREAM_STATE_SEND_HEADERS, 0); |
1479 | 233 | quicly_stream_set_receive_window(stream->quic, get_conn(stream)->super.ctx->globalconf->http3.active_stream_window_size); |
1480 | 233 | h2o_process_request(&stream->req); |
1481 | | |
1482 | 233 | return 0; |
1483 | 233 | } |
1484 | | |
1485 | | static int handle_input_expect_headers_process_connect(struct st_h2o_http3_server_stream_t *stream, uint64_t datagram_flow_id, |
1486 | | const char **err_desc) |
1487 | 432 | { |
1488 | 432 | if (stream->req.content_length != SIZE_MAX) |
1489 | 199 | return handle_input_expect_headers_send_http_error(stream, h2o_send_error_400, "Invalid Request", |
1490 | 199 | "CONNECT request cannot have request body", err_desc); |
1491 | | |
1492 | 233 | stream->req.is_tunnel_req = 1; |
1493 | 233 | stream->datagram_flow_id = datagram_flow_id; |
1494 | 233 | ++get_conn(stream)->num_streams_tunnelling; |
1495 | | |
1496 | 233 | return handle_input_expect_headers_process_request_immediately(stream, err_desc); |
1497 | 432 | } |
1498 | | |
1499 | | static quicly_error_t handle_input_expect_headers(struct st_h2o_http3_server_stream_t *stream, const uint8_t **src, |
1500 | | const uint8_t *src_end, int in_generator, const char **err_desc) |
1501 | 7.10k | { |
1502 | 7.10k | assert(!in_generator); /* this function is processing headers (before generators get assigned), not trailers */ |
1503 | | |
1504 | 7.10k | struct st_h2o_http3_server_conn_t *conn = get_conn(stream); |
1505 | 7.10k | h2o_http3_read_frame_t frame; |
1506 | 7.10k | int header_exists_map = 0; |
1507 | 7.10k | h2o_iovec_t expect = h2o_iovec_init(NULL, 0); |
1508 | 7.10k | h2o_iovec_t datagram_flow_id_field = {}; |
1509 | 7.10k | uint64_t datagram_flow_id = UINT64_MAX; |
1510 | 7.10k | uint8_t header_ack[H2O_HPACK_ENCODE_INT_MAX_LENGTH]; |
1511 | 7.10k | size_t header_ack_len; |
1512 | 7.10k | quicly_error_t ret; |
1513 | | |
1514 | 7.10k | if (h2o_timeval_is_null(&stream->req.timestamps.request_begin_at)) |
1515 | 6.28k | stream->req.timestamps.request_begin_at = h2o_gettimeofday(conn->super.ctx->loop); |
1516 | | |
1517 | | /* read the HEADERS frame (or a frame that precedes that) */ |
1518 | 7.10k | const uint8_t *frame_start = *src; |
1519 | 7.10k | if ((ret = h2o_http3_read_frame(&frame, 0, H2O_HTTP3_STREAM_TYPE_REQUEST, get_conn(stream)->h3.max_frame_payload_size, src, |
1520 | 7.10k | src_end, err_desc)) != 0) { |
1521 | 296 | if (*err_desc == h2o_http3_err_frame_too_large && frame.type == H2O_HTTP3_FRAME_TYPE_HEADERS) { |
1522 | 52 | shutdown_stream(stream, H2O_HTTP3_ERROR_REQUEST_REJECTED, H2O_HTTP3_ERROR_REQUEST_REJECTED, 0, 0); |
1523 | 52 | return 0; |
1524 | 244 | } else { |
1525 | 244 | return ret; |
1526 | 244 | } |
1527 | 296 | } |
1528 | 6.80k | if (frame.type != H2O_HTTP3_FRAME_TYPE_HEADERS) { |
1529 | 1.01k | switch (frame.type) { |
1530 | 7 | case H2O_HTTP3_FRAME_TYPE_DATA: |
1531 | 7 | return H2O_HTTP3_ERROR_FRAME_UNEXPECTED; |
1532 | 1.00k | default: |
1533 | 1.00k | break; |
1534 | 1.01k | } |
1535 | 1.00k | return 0; |
1536 | 1.01k | } |
1537 | | |
1538 | | /* parse the headers */ |
1539 | 5.79k | stream->stats.req.headers_frame_bytes += frame.length; |
1540 | 5.79k | if ((ret = h2o_qpack_parse_request(&stream->req.pool, get_conn(stream)->h3.qpack.dec, stream->quic->stream_id, |
1541 | 5.79k | &stream->req.input.method, &stream->req.input.scheme, &stream->req.input.authority, |
1542 | 5.79k | &stream->req.input.path, &stream->req.upgrade, &stream->req.headers, &header_exists_map, |
1543 | 5.79k | &stream->req.content_length, &expect, NULL /* TODO cache-digests */, &datagram_flow_id_field, |
1544 | 5.79k | conn->num_qpack_blocked, &stream->qpack_blocked_ref, &stream->stats.req.qpack, header_ack, |
1545 | 5.79k | &header_ack_len, frame.payload, frame.length, err_desc)) != 0 && |
1546 | 4.02k | ret != H2O_HTTP2_ERROR_INVALID_HEADER_CHAR) { |
1547 | 3.71k | return ret; |
1548 | 3.71k | } |
1549 | | |
1550 | | /* if the decoding of the frame is blocked by QPACK, return, preserving the HEADERS frame in the receive buffer */ |
1551 | 2.08k | if (stream->qpack_blocked_ref != 0) { |
1552 | 0 | stream->qpack_blocked_ever = 1; |
1553 | 0 | ++conn->num_qpack_blocked; |
1554 | 0 | h2o_linklist_insert(&conn->delayed_streams.qpack_blocked, &stream->link); |
1555 | 0 | *src = frame_start; |
1556 | 0 | return 0; |
1557 | 0 | } |
1558 | | |
1559 | | /* now that the header section have been read and decoded, update the receiver callback and emit an ACK */ |
1560 | 2.08k | stream->recvbuf.handle_input = handle_input_expect_data; |
1561 | 2.08k | if (header_ack_len != 0) |
1562 | 0 | h2o_http3_send_qpack_header_ack(&conn->h3, header_ack, header_ack_len); |
1563 | | |
1564 | 2.08k | h2o_probe_log_request(&stream->req, stream->quic->stream_id); |
1565 | | |
1566 | 2.08k | if (stream->req.input.scheme == NULL) |
1567 | 786 | stream->req.input.scheme = &H2O_URL_SCHEME_HTTPS; |
1568 | | |
1569 | 2.08k | int is_connect, must_exist_map, may_exist_map; |
1570 | 2.08k | const int can_receive_datagrams = |
1571 | 2.08k | quicly_get_context(get_conn(stream)->h3.super.quic)->transport_params.max_datagram_frame_size != 0; |
1572 | 2.08k | if (h2o_memis(stream->req.input.method.base, stream->req.input.method.len, H2O_STRLIT("CONNECT"))) { |
1573 | 575 | is_connect = 1; |
1574 | 575 | must_exist_map = H2O_HPACK_PARSE_HEADERS_METHOD_EXISTS | H2O_HPACK_PARSE_HEADERS_AUTHORITY_EXISTS; |
1575 | 575 | may_exist_map = 0; |
1576 | | /* extended connect looks like an ordinary request plus an upgrade token (:protocol) */ |
1577 | 575 | if ((header_exists_map & H2O_HPACK_PARSE_HEADERS_PROTOCOL_EXISTS) != 0) { |
1578 | 1 | must_exist_map |= H2O_HPACK_PARSE_HEADERS_SCHEME_EXISTS | H2O_HPACK_PARSE_HEADERS_PATH_EXISTS | |
1579 | 1 | H2O_HPACK_PARSE_HEADERS_PROTOCOL_EXISTS; |
1580 | 1 | if (can_receive_datagrams) |
1581 | 1 | datagram_flow_id = stream->quic->stream_id / 4; |
1582 | 1 | } |
1583 | 1.50k | } else if (h2o_memis(stream->req.input.method.base, stream->req.input.method.len, H2O_STRLIT("CONNECT-UDP"))) { |
1584 | | /* Handling of masque draft-03. Method is CONNECT-UDP and :protocol is not used, so we set `:protocol` to "connect-udp" to |
1585 | | * make it look like an upgrade. The method is preserved and can be used to distinguish between RFC 9298 version which uses |
1586 | | * "CONNECT". The draft requires "masque" in `:scheme` but we need to support clients that put "https" there instead. */ |
1587 | 14 | if (!((header_exists_map & H2O_HPACK_PARSE_HEADERS_PROTOCOL_EXISTS) == 0 && |
1588 | 13 | h2o_memis(stream->req.input.path.base, stream->req.input.path.len, H2O_STRLIT("/")))) { |
1589 | 11 | shutdown_stream(stream, H2O_HTTP3_ERROR_GENERAL_PROTOCOL, H2O_HTTP3_ERROR_GENERAL_PROTOCOL, 0, 0); |
1590 | 11 | return 0; |
1591 | 11 | } |
1592 | 3 | if (datagram_flow_id_field.base != NULL) { |
1593 | 0 | if (!can_receive_datagrams) { |
1594 | 0 | *err_desc = "unexpected h3 datagram"; |
1595 | 0 | return H2O_HTTP3_ERROR_GENERAL_PROTOCOL; |
1596 | 0 | } |
1597 | 0 | datagram_flow_id = 0; |
1598 | 0 | for (const char *p = datagram_flow_id_field.base; p != datagram_flow_id_field.base + datagram_flow_id_field.len; ++p) { |
1599 | 0 | if (!('0' <= *p && *p <= '9')) |
1600 | 0 | break; |
1601 | 0 | datagram_flow_id = datagram_flow_id * 10 + *p - '0'; |
1602 | 0 | } |
1603 | 0 | } |
1604 | 3 | assert(stream->req.upgrade.base == NULL); /* otherwise PROTOCOL_EXISTS will be set */ |
1605 | 3 | is_connect = 1; |
1606 | 3 | must_exist_map = H2O_HPACK_PARSE_HEADERS_METHOD_EXISTS | H2O_HPACK_PARSE_HEADERS_AUTHORITY_EXISTS | |
1607 | 3 | H2O_HPACK_PARSE_HEADERS_SCHEME_EXISTS | H2O_HPACK_PARSE_HEADERS_PATH_EXISTS; |
1608 | 3 | may_exist_map = 0; |
1609 | 1.49k | } else { |
1610 | | /* normal request */ |
1611 | 1.49k | is_connect = 0; |
1612 | 1.49k | must_exist_map = |
1613 | 1.49k | H2O_HPACK_PARSE_HEADERS_METHOD_EXISTS | H2O_HPACK_PARSE_HEADERS_SCHEME_EXISTS | H2O_HPACK_PARSE_HEADERS_PATH_EXISTS; |
1614 | 1.49k | may_exist_map = H2O_HPACK_PARSE_HEADERS_AUTHORITY_EXISTS; |
1615 | 1.49k | } |
1616 | | |
1617 | | /* check that all MUST pseudo headers exist, and that there are no other pseudo headers than MUST or MAY */ |
1618 | 2.07k | if (!((header_exists_map & must_exist_map) == must_exist_map && (header_exists_map & ~(must_exist_map | may_exist_map)) == 0)) { |
1619 | 249 | shutdown_stream(stream, H2O_HTTP3_ERROR_GENERAL_PROTOCOL, H2O_HTTP3_ERROR_GENERAL_PROTOCOL, 0, 0); |
1620 | 249 | return 0; |
1621 | 249 | } |
1622 | | |
1623 | | /* send a 400 error when observing an invalid header character */ |
1624 | 1.82k | if (ret == H2O_HTTP2_ERROR_INVALID_HEADER_CHAR) |
1625 | 217 | return handle_input_expect_headers_send_http_error(stream, h2o_send_error_400, "Invalid Request", *err_desc, err_desc); |
1626 | | |
1627 | | /* validate semantic requirement */ |
1628 | 1.60k | if (!h2o_req_validate_pseudo_headers(&stream->req)) { |
1629 | 2 | *err_desc = "invalid pseudo headers"; |
1630 | 2 | return H2O_HTTP3_ERROR_GENERAL_PROTOCOL; |
1631 | 2 | } |
1632 | | |
1633 | | /* check if content-length is within the permitted bounds */ |
1634 | 1.60k | if (stream->req.content_length != SIZE_MAX && stream->req.content_length > conn->super.ctx->globalconf->max_request_entity_size) |
1635 | 150 | return handle_input_expect_headers_send_http_error(stream, h2o_send_error_413, "Request Entity Too Large", |
1636 | 150 | "request entity is too large", err_desc); |
1637 | | |
1638 | | /* set priority */ |
1639 | 1.60k | assert(!h2o_linklist_is_linked(&stream->scheduler.link)); |
1640 | 1.45k | if (!stream->received_priority_update) { |
1641 | 1.45k | ssize_t index; |
1642 | 1.45k | if ((index = h2o_find_header(&stream->req.headers, H2O_TOKEN_PRIORITY, -1)) != -1) { |
1643 | 278 | h2o_iovec_t *value = &stream->req.headers.entries[index].value; |
1644 | 278 | h2o_absprio_parse_priority(value->base, value->len, &stream->scheduler.priority); |
1645 | 1.17k | } else if (is_connect) { |
1646 | 234 | stream->scheduler.priority.incremental = 1; |
1647 | 234 | } |
1648 | 1.45k | } |
1649 | | |
1650 | | /* special handling of CONNECT method */ |
1651 | 1.45k | if (is_connect) |
1652 | 432 | return handle_input_expect_headers_process_connect(stream, datagram_flow_id, err_desc); |
1653 | | |
1654 | | /* change state */ |
1655 | 1.02k | set_state(stream, H2O_HTTP3_SERVER_STREAM_STATE_RECV_BODY_BEFORE_BLOCK, 0); |
1656 | | |
1657 | | /* handle expect: 100-continue */ |
1658 | 1.02k | if (expect.base != NULL) { |
1659 | 11 | if (!h2o_lcstris(expect.base, expect.len, H2O_STRLIT("100-continue"))) { |
1660 | 11 | return handle_input_expect_headers_send_http_error(stream, h2o_send_error_417, "Expectation Failed", |
1661 | 11 | "unknown expectation", err_desc); |
1662 | 11 | } |
1663 | 0 | if (h2o_req_should_forward_expect(&stream->req)) { |
1664 | 0 | h2o_add_header(&stream->req.pool, &stream->req.headers, H2O_TOKEN_EXPECT, NULL, expect.base, expect.len); |
1665 | 0 | stream->req_streaming = 1; |
1666 | 0 | ++conn->num_streams_req_streaming; |
1667 | 0 | return handle_input_expect_headers_process_request_immediately(stream, err_desc); |
1668 | 0 | } else { |
1669 | 0 | stream->req.res.status = 100; |
1670 | 0 | h2o_send_informational(&stream->req); |
1671 | 0 | } |
1672 | 0 | } |
1673 | | |
1674 | 1.01k | return 0; |
1675 | 1.02k | } |
1676 | | |
1677 | | static void write_response(struct st_h2o_http3_server_stream_t *stream, h2o_iovec_t datagram_flow_id) |
1678 | 1.59k | { |
1679 | 1.59k | size_t serialized_header_len = 0; |
1680 | 1.59k | h2o_iovec_t frame = h2o_qpack_flatten_response( |
1681 | 1.59k | get_conn(stream)->h3.qpack.enc, &stream->req.pool, stream->quic->stream_id, NULL, stream->req.res.status, |
1682 | 1.59k | stream->req.res.headers.entries, stream->req.res.headers.size, &get_conn(stream)->super.ctx->globalconf->server_name, |
1683 | 1.59k | stream->req.res.content_length, datagram_flow_id, &stream->stats.resp.qpack, &serialized_header_len); |
1684 | 1.59k | stream->req.header_bytes_sent += serialized_header_len; |
1685 | 1.59k | stream->stats.resp.headers_frame_bytes += serialized_header_len; |
1686 | | |
1687 | 1.59k | h2o_vector_reserve(&stream->req.pool, &stream->sendbuf.vecs, stream->sendbuf.vecs.size + 1); |
1688 | 1.59k | struct st_h2o_http3_server_sendvec_t *vec = stream->sendbuf.vecs.entries + stream->sendbuf.vecs.size++; |
1689 | 1.59k | vec->vec = (h2o_sendvec_t){&immutable_vec_callbacks, frame.len, {frame.base}}; |
1690 | 1.59k | vec->entity_offset = UINT64_MAX; |
1691 | 1.59k | stream->sendbuf.final_size += frame.len; |
1692 | 1.59k | } |
1693 | | |
1694 | | static size_t flatten_data_frame_header(struct st_h2o_http3_server_stream_t *stream, struct st_h2o_http3_server_sendvec_t *dst, |
1695 | | size_t payload_size) |
1696 | 1.46k | { |
1697 | 1.46k | size_t header_size = 0; |
1698 | | |
1699 | | /* build header */ |
1700 | 1.46k | stream->sendbuf.data_frame_header_buf[header_size++] = H2O_HTTP3_FRAME_TYPE_DATA; |
1701 | 1.46k | header_size = |
1702 | 1.46k | quicly_encodev(stream->sendbuf.data_frame_header_buf + header_size, payload_size) - stream->sendbuf.data_frame_header_buf; |
1703 | | |
1704 | | /* initilaize the vector */ |
1705 | 1.46k | h2o_sendvec_init_raw(&dst->vec, stream->sendbuf.data_frame_header_buf, header_size); |
1706 | 1.46k | dst->entity_offset = UINT64_MAX; |
1707 | | |
1708 | 1.46k | return header_size; |
1709 | 1.46k | } |
1710 | | |
1711 | | static void shutdown_by_generator(struct st_h2o_http3_server_stream_t *stream) |
1712 | 1.59k | { |
1713 | 1.59k | quicly_sendstate_shutdown(&stream->quic->sendstate, stream->sendbuf.final_size); |
1714 | 1.59k | if (stream->sendbuf.vecs.size == 0) { |
1715 | 0 | if (quicly_stream_has_receive_side(0, stream->quic->stream_id)) { |
1716 | 0 | quicly_request_stop(stream->quic, H2O_HTTP3_ERROR_EARLY_RESPONSE); |
1717 | 0 | cancel_qpack_decoder(stream); |
1718 | 0 | } |
1719 | 0 | set_state(stream, H2O_HTTP3_SERVER_STREAM_STATE_CLOSE_WAIT, 1); |
1720 | 0 | } |
1721 | 1.59k | } |
1722 | | |
1723 | | /** |
1724 | | * returns boolean indicating if the response is ready to be sent, building the value of datagram-flow-id header field |
1725 | | */ |
1726 | | static int finalize_do_send_setup_udp_tunnel(struct st_h2o_http3_server_stream_t *stream, h2o_send_state_t send_state, |
1727 | | h2o_iovec_t *datagram_flow_id) |
1728 | 1.59k | { |
1729 | 1.59k | *datagram_flow_id = h2o_iovec_init(NULL, 0); |
1730 | | |
1731 | | /* TODO Convert H3_DATAGRAMs to capsules either here or inside the proxy handler. At the moment, the connect handler provides |
1732 | | * `h2o_req_t::forward_datagram` callbacks but the proxy handler does not. As support for H3_DATAGRAMs are advertised at the |
1733 | | * connection level, we need to support forwarding datagrams also when the proxy handler in use. |
1734 | | * Until then, connect-udp requests on H3 are refused to be tunneled by the proxy handler, see `h2o__proxy_process_request`. |
1735 | | * Also, as an abundance of caution, we drop the datagrams associated to requests that do not provide the forwarding hooks, by |
1736 | | * not registering such streams to `datagram_flows`. */ |
1737 | 1.59k | if (!((200 <= stream->req.res.status && stream->req.res.status <= 299) && stream->req.forward_datagram.write_ != NULL) || |
1738 | 1.59k | send_state != H2O_SEND_STATE_IN_PROGRESS) { |
1739 | 1.59k | stream->datagram_flow_id = UINT64_MAX; |
1740 | 1.59k | return 1; |
1741 | 1.59k | } |
1742 | | |
1743 | | /* Register the flow id to the connection so that datagram frames being received from the client would be dispatched to |
1744 | | * `req->forward_datagram.write_`. */ |
1745 | 0 | if (stream->datagram_flow_id != UINT64_MAX) { |
1746 | 0 | struct st_h2o_http3_server_conn_t *conn = get_conn(stream); |
1747 | 0 | int r; |
1748 | 0 | khiter_t iter = kh_put(stream, conn->datagram_flows, stream->datagram_flow_id, &r); |
1749 | 0 | assert(iter != kh_end(conn->datagram_flows)); |
1750 | 0 | kh_val(conn->datagram_flows, iter) = stream; |
1751 | 0 | } |
1752 | | |
1753 | | /* If the client sent a `datagram-flow-id` request header field and: |
1754 | | * a) if the peer is willing to accept datagrams as well, use the same flow ID for sending datagrams from us, |
1755 | | * b) if the peer did not send H3_DATAGRAM Settings, use the stream, or |
1756 | | * c) if H3 SETTINGS hasn't been received yet, wait for it, then call `do_send` again. We might drop some packets from origin |
1757 | | * that arrive before H3 SETTINGS from the client, in the rare occasion of packet carrying H3 SETTINGS getting lost while |
1758 | | * those carrying CONNECT-UDP request and the UDP datagram to be forwarded to the origin arrive. */ |
1759 | 0 | if (stream->datagram_flow_id != UINT64_MAX) { |
1760 | 0 | struct st_h2o_http3_server_conn_t *conn = get_conn(stream); |
1761 | 0 | if (!h2o_http3_has_received_settings(&conn->h3)) { |
1762 | 0 | h2o_linklist_insert(&conn->streams_resp_settings_blocked, &stream->link_resp_settings_blocked); |
1763 | 0 | return 0; |
1764 | 0 | } |
1765 | 0 | if (conn->h3.peer_settings.h3_datagram) { |
1766 | | /* register the route that would be used by the CONNECT handler for forwarding datagrams */ |
1767 | 0 | stream->req.forward_datagram.read_ = tunnel_on_udp_read; |
1768 | | /* if the request type is draft-03, build and return the value of datagram-flow-id header field */ |
1769 | 0 | if (stream->req.input.method.len == sizeof("CONNECT-UDP") - 1) { |
1770 | 0 | datagram_flow_id->base = h2o_mem_alloc_pool(&stream->req.pool, char, sizeof(H2O_UINT64_LONGEST_STR)); |
1771 | 0 | datagram_flow_id->len = sprintf(datagram_flow_id->base, "%" PRIu64, stream->datagram_flow_id); |
1772 | 0 | } |
1773 | 0 | } |
1774 | 0 | } |
1775 | | |
1776 | 0 | return 1; |
1777 | 0 | } |
1778 | | |
1779 | | static void finalize_do_send(struct st_h2o_http3_server_stream_t *stream) |
1780 | 1.96k | { |
1781 | 1.96k | quicly_stream_sync_sendbuf(stream->quic, 1); |
1782 | 1.96k | if (!stream->proceed_while_sending) |
1783 | 1.59k | h2o_quic_schedule_timer(&get_conn(stream)->h3.super); |
1784 | 1.96k | } |
1785 | | |
1786 | | static void do_send(h2o_ostream_t *_ostr, h2o_req_t *_req, h2o_sendvec_t *bufs, size_t bufcnt, h2o_send_state_t send_state) |
1787 | 1.96k | { |
1788 | 1.96k | struct st_h2o_http3_server_stream_t *stream = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, ostr_final, _ostr); |
1789 | 1.96k | int empty_payload_allowed = |
1790 | 1.96k | stream->state == H2O_HTTP3_SERVER_STREAM_STATE_SEND_HEADERS || send_state != H2O_SEND_STATE_IN_PROGRESS; |
1791 | | |
1792 | 1.96k | assert(&stream->req == _req); |
1793 | | |
1794 | 1.96k | stream->proceed_requested = 0; |
1795 | | |
1796 | 1.96k | switch (stream->state) { |
1797 | 1.59k | case H2O_HTTP3_SERVER_STREAM_STATE_SEND_HEADERS: { |
1798 | 1.59k | h2o_iovec_t datagram_flow_id; |
1799 | 1.59k | ssize_t priority_header_index; |
1800 | 1.59k | if (stream->req.send_server_timing != 0) |
1801 | 0 | h2o_add_server_timing_header(&stream->req, 0 /* TODO add support for trailers; it's going to be a little complex as we |
1802 | 0 | * need to build trailers the moment they are emitted onto wire */); |
1803 | 1.59k | if (!finalize_do_send_setup_udp_tunnel(stream, send_state, &datagram_flow_id)) |
1804 | 0 | return; |
1805 | 1.59k | stream->req.timestamps.response_start_at = h2o_gettimeofday(get_conn(stream)->super.ctx->loop); |
1806 | 1.59k | write_response(stream, datagram_flow_id); |
1807 | 1.59k | h2o_probe_log_response(&stream->req, stream->quic->stream_id); |
1808 | 1.59k | set_state(stream, H2O_HTTP3_SERVER_STREAM_STATE_SEND_BODY, 1); |
1809 | 1.59k | if ((priority_header_index = h2o_find_header(&stream->req.res.headers, H2O_TOKEN_PRIORITY, -1)) != -1) { |
1810 | 0 | const h2o_header_t *header = &stream->req.res.headers.entries[priority_header_index]; |
1811 | 0 | handle_priority_change( |
1812 | 0 | stream, header->value.base, header->value.len, |
1813 | 0 | stream->scheduler.priority /* omission of a parameter is disinterest to change (RFC 9218 Section 8) */); |
1814 | 0 | } |
1815 | 1.59k | break; |
1816 | 1.59k | } |
1817 | 368 | case H2O_HTTP3_SERVER_STREAM_STATE_SEND_BODY: |
1818 | 368 | assert(quicly_sendstate_is_open(&stream->quic->sendstate)); |
1819 | 368 | break; |
1820 | 368 | case H2O_HTTP3_SERVER_STREAM_STATE_CLOSE_WAIT: |
1821 | | /* This protocol handler transitions to CLOSE_WAIT when the request side is being reset by the origin. But our client-side |
1822 | | * implementations are capable of handling uni-directional close, therefore `do_send` might be invoked. The handler swallows |
1823 | | * the input, and relies on eventual destruction of `h2o_req_t` to discard the generator. */ |
1824 | 0 | return; |
1825 | 0 | default: |
1826 | 0 | h2o_fatal("logic flaw"); |
1827 | 0 | break; |
1828 | 1.96k | } |
1829 | | |
1830 | | /* If vectors carrying response body are being provided, copy them, incrementing the reference count if possible (for future |
1831 | | * retransmissions), as well as prepending a DATA frame header */ |
1832 | 1.96k | h2o_vector_reserve(&stream->req.pool, &stream->sendbuf.vecs, stream->sendbuf.vecs.size + 1 + bufcnt); |
1833 | 1.96k | size_t dst_slot = stream->sendbuf.vecs.size + 1 /* reserve slot for DATA frame header */, payload_size = 0; |
1834 | 3.42k | for (size_t i = 0; i != bufcnt; ++i) { |
1835 | 1.46k | if (bufs[i].len == 0) |
1836 | 0 | continue; |
1837 | | /* copy one body vector */ |
1838 | 1.46k | payload_size += bufs[i].len; |
1839 | 1.46k | stream->sendbuf.vecs.entries[dst_slot++] = (struct st_h2o_http3_server_sendvec_t){ |
1840 | 1.46k | .vec = bufs[i], |
1841 | 1.46k | .entity_offset = stream->sendbuf.final_body_size, |
1842 | 1.46k | }; |
1843 | 1.46k | } |
1844 | 1.96k | if (payload_size != 0) { |
1845 | | /* build DATA frame header */ |
1846 | 1.46k | size_t header_size = |
1847 | 1.46k | flatten_data_frame_header(stream, stream->sendbuf.vecs.entries + stream->sendbuf.vecs.size, payload_size); |
1848 | | /* update properties */ |
1849 | 1.46k | stream->sendbuf.vecs.size = dst_slot; |
1850 | 1.46k | stream->sendbuf.final_body_size += payload_size; |
1851 | 1.46k | stream->sendbuf.final_size += header_size + payload_size; |
1852 | 1.46k | } else { |
1853 | 500 | assert(empty_payload_allowed || !"h2o_data must only be called when there is progress"); |
1854 | 500 | } |
1855 | | |
1856 | 1.96k | switch (send_state) { |
1857 | 368 | case H2O_SEND_STATE_IN_PROGRESS: |
1858 | 368 | break; |
1859 | 1.59k | case H2O_SEND_STATE_FINAL: |
1860 | 1.59k | case H2O_SEND_STATE_ERROR: |
1861 | | /* TODO consider how to forward error, pending resolution of https://github.com/quicwg/base-drafts/issues/3300 */ |
1862 | 1.59k | shutdown_by_generator(stream); |
1863 | 1.59k | break; |
1864 | 1.96k | } |
1865 | | |
1866 | 1.96k | finalize_do_send(stream); |
1867 | 1.96k | } |
1868 | | |
1869 | | static void do_send_informational(h2o_ostream_t *_ostr, h2o_req_t *_req) |
1870 | 0 | { |
1871 | 0 | struct st_h2o_http3_server_stream_t *stream = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, ostr_final, _ostr); |
1872 | 0 | assert(&stream->req == _req); |
1873 | | |
1874 | 0 | write_response(stream, h2o_iovec_init(NULL, 0)); |
1875 | |
|
1876 | 0 | finalize_do_send(stream); |
1877 | 0 | } |
1878 | | |
1879 | | static quicly_error_t handle_priority_update_frame(struct st_h2o_http3_server_conn_t *conn, |
1880 | | const h2o_http3_priority_update_frame_t *frame) |
1881 | 0 | { |
1882 | 0 | if (frame->element_is_push) |
1883 | 0 | return H2O_HTTP3_ERROR_GENERAL_PROTOCOL; |
1884 | | |
1885 | | /* obtain the stream being referred to (creating one if necessary), or return if the stream has been closed already */ |
1886 | 0 | quicly_stream_t *qs; |
1887 | 0 | if (quicly_get_or_open_stream(conn->h3.super.quic, frame->element, &qs) != 0) |
1888 | 0 | return H2O_HTTP3_ERROR_ID; |
1889 | 0 | if (qs == NULL) |
1890 | 0 | return 0; |
1891 | | |
1892 | | /* apply the changes */ |
1893 | 0 | struct st_h2o_http3_server_stream_t *stream = qs->data; |
1894 | 0 | assert(stream != NULL); |
1895 | 0 | stream->received_priority_update = 1; |
1896 | |
|
1897 | 0 | handle_priority_change(stream, frame->value.base, frame->value.len, |
1898 | 0 | h2o_absprio_default /* the frame communicates a complete set of parameters; RFC 9218 Section 7 */); |
1899 | |
|
1900 | 0 | return 0; |
1901 | 0 | } |
1902 | | |
1903 | | static void handle_control_stream_frame(h2o_http3_conn_t *_conn, uint64_t type, const uint8_t *payload, size_t len) |
1904 | 0 | { |
1905 | 0 | struct st_h2o_http3_server_conn_t *conn = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_conn_t, h3, _conn); |
1906 | 0 | quicly_error_t err; |
1907 | 0 | const char *err_desc = NULL; |
1908 | |
|
1909 | 0 | if (!h2o_http3_has_received_settings(&conn->h3)) { |
1910 | 0 | if (type != H2O_HTTP3_FRAME_TYPE_SETTINGS) { |
1911 | 0 | err = H2O_HTTP3_ERROR_MISSING_SETTINGS; |
1912 | 0 | goto Fail; |
1913 | 0 | } |
1914 | 0 | if ((err = h2o_http3_handle_settings_frame(&conn->h3, payload, len, &err_desc)) != 0) |
1915 | 0 | goto Fail; |
1916 | 0 | assert(h2o_http3_has_received_settings(&conn->h3)); |
1917 | 0 | while (!h2o_linklist_is_empty(&conn->streams_resp_settings_blocked)) { |
1918 | 0 | struct st_h2o_http3_server_stream_t *stream = H2O_STRUCT_FROM_MEMBER( |
1919 | 0 | struct st_h2o_http3_server_stream_t, link_resp_settings_blocked, conn->streams_resp_settings_blocked.next); |
1920 | 0 | h2o_linklist_unlink(&stream->link_resp_settings_blocked); |
1921 | 0 | do_send(&stream->ostr_final, &stream->req, NULL, 0, H2O_SEND_STATE_IN_PROGRESS); |
1922 | 0 | } |
1923 | 0 | } else { |
1924 | 0 | switch (type) { |
1925 | 0 | case H2O_HTTP3_FRAME_TYPE_SETTINGS: |
1926 | 0 | err = H2O_HTTP3_ERROR_FRAME_UNEXPECTED; |
1927 | 0 | err_desc = "unexpected SETTINGS frame"; |
1928 | 0 | goto Fail; |
1929 | 0 | case H2O_HTTP3_FRAME_TYPE_PRIORITY_UPDATE_REQUEST: |
1930 | 0 | case H2O_HTTP3_FRAME_TYPE_PRIORITY_UPDATE_PUSH: { |
1931 | 0 | h2o_http3_priority_update_frame_t frame; |
1932 | 0 | if ((err = h2o_http3_decode_priority_update_frame(&frame, type == H2O_HTTP3_FRAME_TYPE_PRIORITY_UPDATE_PUSH, payload, |
1933 | 0 | len, &err_desc)) != 0) |
1934 | 0 | goto Fail; |
1935 | 0 | if ((err = handle_priority_update_frame(conn, &frame)) != 0) { |
1936 | 0 | err_desc = "invalid PRIORITY_UPDATE frame"; |
1937 | 0 | goto Fail; |
1938 | 0 | } |
1939 | 0 | } break; |
1940 | 0 | default: |
1941 | 0 | break; |
1942 | 0 | } |
1943 | 0 | } |
1944 | | |
1945 | 0 | return; |
1946 | 0 | Fail: |
1947 | 0 | h2o_quic_close_connection(&conn->h3.super, err, err_desc); |
1948 | 0 | } |
1949 | | |
1950 | | static void qpack_unblock_streams(h2o_http3_conn_t *_conn, uint64_t insert_count) |
1951 | 0 | { |
1952 | 0 | struct st_h2o_http3_server_conn_t *conn = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_conn_t, h3, _conn); |
1953 | |
|
1954 | 0 | h2o_linklist_t *node = conn->delayed_streams.qpack_blocked.next; |
1955 | 0 | while (node != &conn->delayed_streams.qpack_blocked) { |
1956 | 0 | struct st_h2o_http3_server_stream_t *stream = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, link, node); |
1957 | 0 | node = node->next; |
1958 | 0 | if (stream->qpack_blocked_ref > insert_count) |
1959 | 0 | continue; |
1960 | 0 | h2o_linklist_unlink(&stream->link); |
1961 | 0 | stream->qpack_blocked_ref = 0; |
1962 | 0 | assert(conn->num_qpack_blocked > 0); |
1963 | 0 | --conn->num_qpack_blocked; |
1964 | 0 | handle_buffered_input(stream, 0); |
1965 | 0 | } |
1966 | 0 | } |
1967 | | |
1968 | | static quicly_error_t stream_open_cb(quicly_stream_open_t *self, quicly_stream_t *qs) |
1969 | 25.1k | { |
1970 | 25.1k | static const quicly_stream_callbacks_t callbacks = {on_stream_destroy, on_send_shift, on_send_emit, |
1971 | 25.1k | on_send_stop, on_receive, on_receive_reset}; |
1972 | | |
1973 | | /* handling of unidirectional streams is not server-specific */ |
1974 | 25.1k | if (quicly_stream_is_unidirectional(qs->stream_id)) { |
1975 | 18.8k | h2o_http3_on_create_unidirectional_stream(qs); |
1976 | 18.8k | return 0; |
1977 | 18.8k | } |
1978 | | |
1979 | 25.1k | assert(quicly_stream_is_client_initiated(qs->stream_id)); |
1980 | | |
1981 | 6.28k | struct st_h2o_http3_server_conn_t *conn = |
1982 | 6.28k | H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_conn_t, h3, *quicly_get_data(qs->conn)); |
1983 | | |
1984 | | /* create new stream and start handling the request */ |
1985 | 6.28k | struct st_h2o_http3_server_stream_t *stream = h2o_mem_alloc(sizeof(*stream)); |
1986 | 6.28k | stream->quic = qs; |
1987 | 6.28k | h2o_buffer_init(&stream->recvbuf.buf, &h2o_socket_buffer_prototype); |
1988 | 6.28k | stream->recvbuf.handle_input = handle_input_expect_headers; |
1989 | 6.28k | memset(&stream->sendbuf, 0, sizeof(stream->sendbuf)); |
1990 | 6.28k | stream->state = H2O_HTTP3_SERVER_STREAM_STATE_RECV_HEADERS; |
1991 | 6.28k | stream->link = (h2o_linklist_t){NULL}; |
1992 | 6.28k | stream->link_resp_settings_blocked = (h2o_linklist_t){NULL}; |
1993 | 6.28k | stream->ostr_final = (h2o_ostream_t){ |
1994 | 6.28k | NULL, do_send, NULL, |
1995 | 6.28k | conn->super.ctx->globalconf->send_informational_mode == H2O_SEND_INFORMATIONAL_MODE_NONE ? NULL : do_send_informational}; |
1996 | 6.28k | stream->scheduler.link = (h2o_linklist_t){NULL}; |
1997 | 6.28k | stream->scheduler.priority = h2o_absprio_default; |
1998 | 6.28k | stream->scheduler.call_cnt = 0; |
1999 | | |
2000 | 6.28k | stream->read_blocked = 0; |
2001 | 6.28k | stream->proceed_requested = 0; |
2002 | 6.28k | stream->proceed_while_sending = 0; |
2003 | 6.28k | stream->received_priority_update = 0; |
2004 | 6.28k | stream->req_disposed = 0; |
2005 | 6.28k | stream->req_streaming = 0; |
2006 | 6.28k | stream->qpack_blocked_ref = 0; |
2007 | 6.28k | stream->qpack_blocked_ever = 0; |
2008 | 6.28k | stream->req_streaming_eos_delivered = 0; |
2009 | 6.28k | stream->req_body = NULL; |
2010 | 6.28k | memset(&stream->stats, 0, sizeof(stream->stats)); |
2011 | | |
2012 | 6.28k | h2o_init_request(&stream->req, &conn->super, NULL); |
2013 | 6.28k | stream->req.version = 0x0300; |
2014 | 6.28k | stream->req._ostr_top = &stream->ostr_final; |
2015 | | |
2016 | 6.28k | stream->quic->data = stream; |
2017 | 6.28k | stream->quic->callbacks = &callbacks; |
2018 | | |
2019 | 6.28k | ++*get_state_counter(get_conn(stream), stream->state); |
2020 | 6.28k | h2o_conn_set_state(&get_conn(stream)->super, H2O_CONN_STATE_ACTIVE); |
2021 | | |
2022 | 6.28k | return 0; |
2023 | 6.28k | } |
2024 | | |
2025 | | static quicly_stream_open_t on_stream_open = {stream_open_cb}; |
2026 | | |
2027 | | static void unblock_conn_blocked_streams(struct st_h2o_http3_server_conn_t *conn) |
2028 | 0 | { |
2029 | 0 | conn->scheduler.uni.active |= conn->scheduler.uni.conn_blocked; |
2030 | 0 | conn->scheduler.uni.conn_blocked = 0; |
2031 | 0 | req_scheduler_unblock_conn_blocked(&conn->scheduler.reqs, req_scheduler_compare_stream_id); |
2032 | 0 | } |
2033 | | |
2034 | | static int scheduler_can_send(quicly_stream_scheduler_t *sched, quicly_conn_t *qc, int conn_is_saturated) |
2035 | 0 | { |
2036 | 0 | struct st_h2o_http3_server_conn_t *conn = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_conn_t, h3, *quicly_get_data(qc)); |
2037 | |
|
2038 | 0 | if (!conn_is_saturated) { |
2039 | | /* not saturated, activate streams marked as being conn-blocked */ |
2040 | 0 | unblock_conn_blocked_streams(conn); |
2041 | 0 | } else { |
2042 | | /* TODO lazily move the active request and unidirectional streams to conn_blocked. Not doing so results in at most one |
2043 | | * spurious call to quicly_send. */ |
2044 | 0 | } |
2045 | |
|
2046 | 0 | if (conn->scheduler.uni.active != 0) |
2047 | 0 | return 1; |
2048 | 0 | if (conn->scheduler.reqs.active.smallest_urgency < H2O_ABSPRIO_NUM_URGENCY_LEVELS) |
2049 | 0 | return 1; |
2050 | | |
2051 | 0 | return 0; |
2052 | 0 | } |
2053 | | |
2054 | | static quicly_error_t scheduler_do_send(quicly_stream_scheduler_t *sched, quicly_conn_t *qc, quicly_send_context_t *s) |
2055 | 17.6k | { |
2056 | 17.6k | #define HAS_DATA_TO_SEND() \ |
2057 | 26.1k | (conn->scheduler.uni.active != 0 || conn->scheduler.reqs.active.smallest_urgency < H2O_ABSPRIO_NUM_URGENCY_LEVELS) |
2058 | | |
2059 | 17.6k | struct st_h2o_http3_server_conn_t *conn = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_conn_t, h3, *quicly_get_data(qc)); |
2060 | 17.6k | int had_data_to_send = HAS_DATA_TO_SEND(); |
2061 | 17.6k | quicly_error_t ret = 0; |
2062 | | |
2063 | 39.1k | while (quicly_can_send_data(conn->h3.super.quic, s)) { |
2064 | | /* The strategy is: |
2065 | | * |
2066 | | * 1. dequeue the first active stream |
2067 | | * 2. link the stream to the conn_blocked list, if nothing can be sent for the stream due to the connection being capped |
2068 | | * 3. otherwise, send |
2069 | | * 4. enqueue to the appropriate place |
2070 | | */ |
2071 | 39.1k | if (conn->scheduler.uni.active != 0) { |
2072 | 19.8k | static const ptrdiff_t stream_offsets[] = { |
2073 | 19.8k | offsetof(struct st_h2o_http3_server_conn_t, h3._control_streams.egress.control), |
2074 | 19.8k | offsetof(struct st_h2o_http3_server_conn_t, h3._control_streams.egress.qpack_encoder), |
2075 | 19.8k | offsetof(struct st_h2o_http3_server_conn_t, h3._control_streams.egress.qpack_decoder)}; |
2076 | | /* 1. obtain pointer to the offending stream */ |
2077 | 19.8k | struct st_h2o_http3_egress_unistream_t *stream = NULL; |
2078 | 19.8k | size_t i; |
2079 | 40.7k | for (i = 0; i != sizeof(stream_offsets) / sizeof(stream_offsets[0]); ++i) { |
2080 | 40.7k | stream = *(void **)((char *)conn + stream_offsets[i]); |
2081 | 40.7k | if ((conn->scheduler.uni.active & (1 << stream->quic->stream_id)) != 0) |
2082 | 19.8k | break; |
2083 | 40.7k | } |
2084 | 19.8k | assert(i != sizeof(stream_offsets) / sizeof(stream_offsets[0]) && "we should have found one stream"); |
2085 | | /* 2. move to the conn_blocked list if necessary */ |
2086 | 19.8k | if (quicly_is_blocked(conn->h3.super.quic) && !quicly_stream_can_send(stream->quic, 0)) { |
2087 | 0 | conn->scheduler.uni.active &= ~(1 << stream->quic->stream_id); |
2088 | 0 | conn->scheduler.uni.conn_blocked |= 1 << stream->quic->stream_id; |
2089 | 0 | continue; |
2090 | 0 | } |
2091 | | /* 3. send */ |
2092 | 19.8k | if ((ret = quicly_send_stream(stream->quic, s)) != 0) |
2093 | 0 | goto Exit; |
2094 | | /* 4. update scheduler state */ |
2095 | 19.8k | conn->scheduler.uni.active &= ~(1 << stream->quic->stream_id); |
2096 | 19.8k | if (quicly_stream_can_send(stream->quic, 1)) { |
2097 | 0 | uint16_t *slot = &conn->scheduler.uni.active; |
2098 | 0 | if (quicly_is_blocked(conn->h3.super.quic) && !quicly_stream_can_send(stream->quic, 0)) |
2099 | 0 | slot = &conn->scheduler.uni.conn_blocked; |
2100 | 0 | *slot |= 1 << stream->quic->stream_id; |
2101 | 0 | } |
2102 | 19.8k | } else if (conn->scheduler.reqs.active.smallest_urgency < H2O_ABSPRIO_NUM_URGENCY_LEVELS) { |
2103 | | /* 1. obtain pointer to the offending stream */ |
2104 | 1.61k | h2o_linklist_t *anchor = &conn->scheduler.reqs.active.urgencies[conn->scheduler.reqs.active.smallest_urgency].high; |
2105 | 1.61k | if (h2o_linklist_is_empty(anchor)) { |
2106 | 4 | anchor = &conn->scheduler.reqs.active.urgencies[conn->scheduler.reqs.active.smallest_urgency].low; |
2107 | 4 | assert(!h2o_linklist_is_empty(anchor)); |
2108 | 4 | } |
2109 | 1.61k | struct st_h2o_http3_server_stream_t *stream = |
2110 | 1.61k | H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_stream_t, scheduler.link, anchor->next); |
2111 | | /* 1. link to the conn_blocked list if necessary */ |
2112 | 1.61k | if (quicly_is_blocked(conn->h3.super.quic) && !quicly_stream_can_send(stream->quic, 0)) { |
2113 | 0 | req_scheduler_conn_blocked(&conn->scheduler.reqs, &stream->scheduler); |
2114 | 0 | continue; |
2115 | 0 | } |
2116 | | /* 3. send */ |
2117 | 1.61k | if ((ret = quicly_send_stream(stream->quic, s)) != 0) |
2118 | 0 | goto Exit; |
2119 | 1.61k | ++stream->scheduler.call_cnt; |
2120 | 1.61k | if (stream->quic->sendstate.size_inflight == stream->quic->sendstate.final_size && |
2121 | 1.24k | h2o_timeval_is_null(&stream->req.timestamps.response_end_at)) { |
2122 | 1.24k | stream->req.timestamps.response_end_at = h2o_gettimeofday(stream->req.conn->ctx->loop); |
2123 | 1.24k | if (h2o_timeval_is_null(&stream->req.timestamps.response_start_at)) { |
2124 | 0 | stream->req.timestamps.response_start_at = stream->req.timestamps.response_end_at; |
2125 | 0 | } |
2126 | 1.24k | } |
2127 | | /* 4. invoke h2o_proceed_request synchronously, so that we could obtain additional data for the current (i.e. highest) |
2128 | | * stream. */ |
2129 | 1.61k | if (stream->proceed_while_sending) { |
2130 | 368 | assert(stream->proceed_requested); |
2131 | 368 | h2o_proceed_response(&stream->req); |
2132 | 368 | stream->proceed_while_sending = 0; |
2133 | 368 | } |
2134 | | /* 5. prepare for next */ |
2135 | 1.61k | if (quicly_stream_can_send(stream->quic, 1)) { |
2136 | 368 | if (quicly_is_blocked(conn->h3.super.quic) && !quicly_stream_can_send(stream->quic, 0)) { |
2137 | | /* capped by connection-level flow control, move the stream to conn-blocked */ |
2138 | 0 | req_scheduler_conn_blocked(&conn->scheduler.reqs, &stream->scheduler); |
2139 | 368 | } else { |
2140 | | /* schedule for next emission */ |
2141 | 368 | req_scheduler_setup_for_next(&conn->scheduler.reqs, &stream->scheduler, req_scheduler_compare_stream_id); |
2142 | 368 | } |
2143 | 1.24k | } else { |
2144 | | /* nothing to send at this moment */ |
2145 | 1.24k | req_scheduler_deactivate(&conn->scheduler.reqs, &stream->scheduler); |
2146 | 1.24k | } |
2147 | 17.6k | } else { |
2148 | 17.6k | break; |
2149 | 17.6k | } |
2150 | 39.1k | } |
2151 | | |
2152 | 17.6k | Exit: |
2153 | | /* Send a resumption token if we've sent all available data and there is still room to send something, but not too frequently. |
2154 | | * We send a token every 200KB at most; the threshold has been chosen so that the additional overhead would be ~0.1% assuming a |
2155 | | * token size of 200 bytes (in reality, one NEW_TOKEN frame will uses 56 bytes). */ |
2156 | 17.6k | if (ret == 0 && had_data_to_send && !HAS_DATA_TO_SEND()) { |
2157 | 8.53k | uint64_t max_data_sent; |
2158 | 8.53k | quicly_get_max_data(conn->h3.super.quic, NULL, &max_data_sent, NULL, NULL); |
2159 | 8.53k | if (max_data_sent >= conn->skip_jumpstart_token_until) { |
2160 | 6.28k | quicly_send_resumption_token(conn->h3.super.quic); |
2161 | 6.28k | conn->skip_jumpstart_token_until = max_data_sent + 200000; |
2162 | 6.28k | } |
2163 | 8.53k | } |
2164 | | |
2165 | 17.6k | return ret; |
2166 | | |
2167 | 17.6k | #undef HAS_DATA_TO_SEND |
2168 | 17.6k | } |
2169 | | |
2170 | | static void scheduler_update_state(struct st_quicly_stream_scheduler_t *sched, quicly_stream_t *qs) |
2171 | 23.6k | { |
2172 | 23.6k | struct st_h2o_http3_server_conn_t *conn = |
2173 | 23.6k | H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_conn_t, h3, *quicly_get_data(qs->conn)); |
2174 | 23.6k | enum { DEACTIVATE, ACTIVATE, CONN_BLOCKED } new_state; |
2175 | | |
2176 | 23.6k | if (quicly_stream_can_send(qs, 1)) { |
2177 | 22.7k | if (quicly_is_blocked(conn->h3.super.quic) && !quicly_stream_can_send(qs, 0)) { |
2178 | 0 | new_state = CONN_BLOCKED; |
2179 | 22.7k | } else { |
2180 | 22.7k | new_state = ACTIVATE; |
2181 | 22.7k | } |
2182 | 22.7k | } else { |
2183 | 996 | new_state = DEACTIVATE; |
2184 | 996 | } |
2185 | | |
2186 | 23.6k | if (quicly_stream_is_unidirectional(qs->stream_id)) { |
2187 | 20.7k | assert(qs->stream_id < sizeof(uint16_t) * 8); |
2188 | 20.7k | uint16_t mask = (uint16_t)1 << qs->stream_id; |
2189 | 20.7k | switch (new_state) { |
2190 | 0 | case DEACTIVATE: |
2191 | 0 | conn->scheduler.uni.active &= ~mask; |
2192 | 0 | conn->scheduler.uni.conn_blocked &= ~mask; |
2193 | 0 | break; |
2194 | 20.7k | case ACTIVATE: |
2195 | 20.7k | conn->scheduler.uni.active |= mask; |
2196 | 20.7k | conn->scheduler.uni.conn_blocked &= ~mask; |
2197 | 20.7k | break; |
2198 | 0 | case CONN_BLOCKED: |
2199 | 0 | conn->scheduler.uni.active &= ~mask; |
2200 | 0 | conn->scheduler.uni.conn_blocked |= mask; |
2201 | 0 | break; |
2202 | 20.7k | } |
2203 | 20.7k | } else { |
2204 | 2.95k | struct st_h2o_http3_server_stream_t *stream = qs->data; |
2205 | 2.95k | if (stream->proceed_while_sending) |
2206 | 368 | return; |
2207 | 2.58k | switch (new_state) { |
2208 | 996 | case DEACTIVATE: |
2209 | 996 | req_scheduler_deactivate(&conn->scheduler.reqs, &stream->scheduler); |
2210 | 996 | break; |
2211 | 1.59k | case ACTIVATE: |
2212 | 1.59k | req_scheduler_activate(&conn->scheduler.reqs, &stream->scheduler, req_scheduler_compare_stream_id); |
2213 | 1.59k | break; |
2214 | 0 | case CONN_BLOCKED: |
2215 | 0 | req_scheduler_conn_blocked(&conn->scheduler.reqs, &stream->scheduler); |
2216 | 0 | break; |
2217 | 2.58k | } |
2218 | 2.58k | } |
2219 | 23.6k | } |
2220 | | |
2221 | | static quicly_stream_scheduler_t scheduler = {scheduler_can_send, scheduler_do_send, scheduler_update_state}; |
2222 | | |
2223 | | static void datagram_frame_receive_cb(quicly_receive_datagram_frame_t *self, quicly_conn_t *quic, ptls_iovec_t datagram) |
2224 | 0 | { |
2225 | 0 | struct st_h2o_http3_server_conn_t *conn = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_conn_t, h3, *quicly_get_data(quic)); |
2226 | 0 | uint64_t flow_id; |
2227 | 0 | h2o_iovec_t payload; |
2228 | | |
2229 | | /* decode */ |
2230 | 0 | if ((flow_id = h2o_http3_decode_h3_datagram(&payload, datagram.base, datagram.len)) == UINT64_MAX) { |
2231 | 0 | h2o_quic_close_connection(&conn->h3.super, H2O_HTTP3_ERROR_GENERAL_PROTOCOL, "invalid DATAGRAM frame"); |
2232 | 0 | return; |
2233 | 0 | } |
2234 | | |
2235 | | /* find stream */ |
2236 | 0 | khiter_t iter = kh_get(stream, conn->datagram_flows, flow_id); |
2237 | 0 | if (iter == kh_end(conn->datagram_flows)) |
2238 | 0 | return; |
2239 | 0 | struct st_h2o_http3_server_stream_t *stream = kh_val(conn->datagram_flows, iter); |
2240 | 0 | assert(stream->req.forward_datagram.write_ != NULL); |
2241 | | |
2242 | | /* drop this datagram if req.forward_datagram.write_ has been reset */ |
2243 | 0 | if (stream->req.forward_datagram.write_ == NULL) |
2244 | 0 | return; |
2245 | | |
2246 | | /* forward */ |
2247 | 0 | stream->req.forward_datagram.write_(&stream->req, &payload, 1); |
2248 | 0 | } |
2249 | | |
2250 | | static quicly_receive_datagram_frame_t on_receive_datagram_frame = {datagram_frame_receive_cb}; |
2251 | | |
2252 | | static void on_h3_destroy(h2o_quic_conn_t *h3_) |
2253 | 6.28k | { |
2254 | 6.28k | h2o_http3_conn_t *h3 = (h2o_http3_conn_t *)h3_; |
2255 | 6.28k | struct st_h2o_http3_server_conn_t *conn = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_conn_t, h3, h3); |
2256 | 6.28k | quicly_stats_t stats; |
2257 | | |
2258 | | /* some attributes are available only via h2olog, as DTrace probes have an upper limit on the number of arguments */ |
2259 | 6.28k | H2O_PROBE_CONN(H3S_DESTROY, &conn->super, conn->stats.num_requests, conn->stats.req.stream_bytes, |
2260 | 6.28k | conn->stats.req.headers_frame_bytes, conn->stats.req.body_bytes, conn->stats.req.qpack.count, |
2261 | 6.28k | conn->stats.req.qpack.text_bytes, conn->stats.resp.stream_bytes, conn->stats.resp.headers_frame_bytes, |
2262 | 6.28k | conn->stats.resp.body_bytes, conn->stats.resp.qpack.count, conn->stats.resp.qpack.text_bytes); |
2263 | 6.28k | H2O_LOG_CONN(h3s_destroy, &conn->super, { |
2264 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(num_requests, conn->stats.num_requests); |
2265 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(request_stream_bytes, conn->stats.req.stream_bytes); |
2266 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(request_header_bytes, conn->stats.req.headers_frame_bytes); |
2267 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(request_body_bytes, conn->stats.req.body_bytes); |
2268 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(request_header_count, conn->stats.req.qpack.count); |
2269 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(request_header_text_bytes, conn->stats.req.qpack.text_bytes); |
2270 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(response_stream_bytes, conn->stats.resp.stream_bytes); |
2271 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(response_header_bytes, conn->stats.resp.headers_frame_bytes); |
2272 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(response_body_bytes, conn->stats.resp.body_bytes); |
2273 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(response_header_count, conn->stats.resp.qpack.count); |
2274 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(response_header_text_bytes, conn->stats.resp.qpack.text_bytes); |
2275 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(control_stream_bytes_received, h3->stats.bytes_received.control_stream); |
2276 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(qpack_encoder_bytes_received, h3->stats.bytes_received.qpack_encoder); |
2277 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(qpack_decoder_bytes_received, h3->stats.bytes_received.qpack_decoder); |
2278 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(control_stream_bytes_sent, h3->stats.bytes_sent.control_stream); |
2279 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(qpack_encoder_bytes_sent, h3->stats.bytes_sent.qpack_encoder); |
2280 | 6.28k | PTLS_LOG_ELEMENT_UNSIGNED(qpack_decoder_bytes_sent, h3->stats.bytes_sent.qpack_decoder); |
2281 | 6.28k | }); |
2282 | | |
2283 | 6.28k | if (quicly_get_stats(h3_->quic, &stats) == 0) { |
2284 | 597k | #define ACC(fld, _unused) conn->super.ctx->quic_stats.quicly.fld += stats.fld; |
2285 | 597k | QUICLY_STATS_FOREACH_COUNTERS(ACC); |
2286 | 6.28k | #undef ACC |
2287 | 6.28k | if (conn->super.ctx->quic_stats.num_sentmap_packets_largest < stats.num_sentmap_packets_largest) |
2288 | 0 | conn->super.ctx->quic_stats.num_sentmap_packets_largest = stats.num_sentmap_packets_largest; |
2289 | 6.28k | } |
2290 | | |
2291 | | /* unlink and dispose */ |
2292 | 6.28k | if (h2o_timer_is_linked(&conn->timeout)) |
2293 | 0 | h2o_timer_unlink(&conn->timeout); |
2294 | 6.28k | if (h2o_timer_is_linked(&conn->_graceful_shutdown_timeout)) |
2295 | 0 | h2o_timer_unlink(&conn->_graceful_shutdown_timeout); |
2296 | 6.28k | h2o_http3_dispose_conn(&conn->h3); |
2297 | 6.28k | kh_destroy(stream, conn->datagram_flows); |
2298 | | |
2299 | | /* check consistency post-disposal */ |
2300 | 6.28k | assert(conn->num_streams.recv_headers == 0); |
2301 | 6.28k | assert(conn->num_streams.req_pending == 0); |
2302 | 6.28k | assert(conn->num_streams.send_headers == 0); |
2303 | 6.28k | assert(conn->num_streams.send_body == 0); |
2304 | 6.28k | assert(conn->num_streams.close_wait == 0); |
2305 | 6.28k | assert(conn->num_streams_req_streaming == 0); |
2306 | 6.28k | assert(conn->num_streams_tunnelling == 0); |
2307 | 6.28k | assert(h2o_linklist_is_empty(&conn->delayed_streams.recv_body_blocked)); |
2308 | 6.28k | assert(h2o_linklist_is_empty(&conn->delayed_streams.req_streaming)); |
2309 | 6.28k | assert(h2o_linklist_is_empty(&conn->delayed_streams.pending)); |
2310 | 6.28k | assert(h2o_linklist_is_empty(&conn->delayed_streams.qpack_blocked)); |
2311 | 6.28k | assert(conn->num_qpack_blocked == 0); |
2312 | 6.28k | assert(h2o_linklist_is_empty(&conn->streams_resp_settings_blocked)); |
2313 | 6.28k | assert(conn->scheduler.reqs.active.smallest_urgency >= H2O_ABSPRIO_NUM_URGENCY_LEVELS); |
2314 | 6.28k | assert(h2o_linklist_is_empty(&conn->scheduler.reqs.conn_blocked)); |
2315 | | |
2316 | | /* free memory */ |
2317 | 6.28k | h2o_destroy_connection(&conn->super); |
2318 | 6.28k | } |
2319 | | |
2320 | | void h2o_http3_server_init_context(h2o_context_t *h2o, h2o_quic_ctx_t *ctx, h2o_loop_t *loop, h2o_socket_t *sock, |
2321 | | h2o_socket_t *sock_alt_family, quicly_context_t *quic, quicly_cid_plaintext_t *next_cid, |
2322 | | h2o_quic_accept_cb acceptor, h2o_quic_notify_connection_update_cb notify_conn_update, |
2323 | | uint8_t use_gso) |
2324 | 0 | { |
2325 | 0 | return h2o_quic_init_context(ctx, loop, sock, sock_alt_family, quic, next_cid, acceptor, notify_conn_update, use_gso, |
2326 | 0 | &h2o->quic_stats); |
2327 | 0 | } |
2328 | | |
2329 | | h2o_http3_conn_t *h2o_http3_server_accept(h2o_http3_server_ctx_t *ctx, quicly_address_t *destaddr, quicly_address_t *srcaddr, |
2330 | | quicly_decoded_packet_t *packet, quicly_address_token_plaintext_t *address_token, |
2331 | | const h2o_http3_conn_callbacks_t *h3_callbacks) |
2332 | 6.28k | { |
2333 | 6.28k | static const h2o_conn_callbacks_t conn_callbacks = { |
2334 | 6.28k | .get_sockname = get_sockname, |
2335 | 6.28k | .get_peername = get_peername, |
2336 | 6.28k | .get_ptls = get_ptls, |
2337 | 6.28k | .get_ssl_server_name = get_ssl_server_name, |
2338 | 6.28k | .log_state = log_state, |
2339 | 6.28k | .get_req_id = get_req_id, |
2340 | 6.28k | .close_idle_connection = close_idle_connection, |
2341 | 6.28k | .foreach_request = foreach_request, |
2342 | 6.28k | .request_shutdown = initiate_graceful_shutdown, |
2343 | 6.28k | .num_reqs_inflight = num_reqs_inflight, |
2344 | 6.28k | .get_tracer = get_tracer, |
2345 | 6.28k | .log_ = {{ |
2346 | 6.28k | .extensible_priorities = log_extensible_priorities, |
2347 | 6.28k | .request_header_bytes = log_request_header_bytes, |
2348 | 6.28k | .request_header_text_bytes = log_request_header_text_bytes, |
2349 | 6.28k | .request_header_count = log_request_header_count, |
2350 | 6.28k | .response_header_text_bytes = log_response_header_text_bytes, |
2351 | 6.28k | .response_header_count = log_response_header_count, |
2352 | 6.28k | .transport = |
2353 | 6.28k | { |
2354 | 6.28k | .cc_name = log_cc_name, |
2355 | 6.28k | .delivery_rate = log_delivery_rate, |
2356 | 6.28k | }, |
2357 | 6.28k | .ssl = |
2358 | 6.28k | { |
2359 | 6.28k | .protocol_version = log_tls_protocol_version, |
2360 | 6.28k | .session_reused = log_session_reused, |
2361 | 6.28k | .cipher = log_cipher, |
2362 | 6.28k | .cipher_bits = log_cipher_bits, |
2363 | 6.28k | .session_id = log_session_id, |
2364 | 6.28k | .negotiated_protocol = log_negotiated_protocol, |
2365 | 6.28k | .ech_config_id = log_ech_config_id, |
2366 | 6.28k | .ech_kem = log_ech_kem, |
2367 | 6.28k | .ech_cipher = log_ech_cipher, |
2368 | 6.28k | .ech_cipher_bits = log_ech_cipher_bits, |
2369 | 6.28k | }, |
2370 | 6.28k | .http3 = |
2371 | 6.28k | { |
2372 | 6.28k | .stream_id = log_stream_id, |
2373 | 6.28k | .quic_stats = log_quic_stats, |
2374 | 6.28k | .quic_version = log_quic_version, |
2375 | 6.28k | .qpack_blocked = log_qpack_blocked, |
2376 | 6.28k | .request_stream_bytes = log_request_stream_bytes, |
2377 | 6.28k | .response_stream_bytes = log_response_stream_bytes, |
2378 | 6.28k | }, |
2379 | 6.28k | }}, |
2380 | 6.28k | }; |
2381 | | |
2382 | | /* setup the structure */ |
2383 | 6.28k | struct st_h2o_http3_server_conn_t *conn = (void *)h2o_create_connection( |
2384 | 6.28k | sizeof(*conn), ctx->accept_ctx->ctx, ctx->accept_ctx->hosts, h2o_gettimeofday(ctx->accept_ctx->ctx->loop), &conn_callbacks); |
2385 | 6.28k | memset((char *)conn + sizeof(conn->super), 0, sizeof(*conn) - sizeof(conn->super)); |
2386 | | |
2387 | 6.28k | h2o_http3_init_conn(&conn->h3, &ctx->super, h3_callbacks, &ctx->qpack, H2O_MAX_REQLEN); |
2388 | 6.28k | conn->handshake_properties = (ptls_handshake_properties_t){{{{NULL}}}}; |
2389 | 6.28k | h2o_linklist_init_anchor(&conn->delayed_streams.recv_body_blocked); |
2390 | 6.28k | h2o_linklist_init_anchor(&conn->delayed_streams.req_streaming); |
2391 | 6.28k | h2o_linklist_init_anchor(&conn->delayed_streams.pending); |
2392 | 6.28k | h2o_linklist_init_anchor(&conn->delayed_streams.qpack_blocked); |
2393 | 6.28k | h2o_linklist_init_anchor(&conn->streams_resp_settings_blocked); |
2394 | 6.28k | h2o_timer_init(&conn->timeout, run_delayed); |
2395 | 6.28k | memset(&conn->num_streams, 0, sizeof(conn->num_streams)); |
2396 | 6.28k | conn->num_streams_req_streaming = 0; |
2397 | 6.28k | conn->num_streams_tunnelling = 0; |
2398 | 6.28k | req_scheduler_init(&conn->scheduler.reqs); |
2399 | 6.28k | conn->scheduler.uni.active = 0; |
2400 | 6.28k | conn->scheduler.uni.conn_blocked = 0; |
2401 | 6.28k | conn->datagram_flows = kh_init(stream); |
2402 | 6.28k | conn->skip_jumpstart_token_until = |
2403 | 6.28k | quicly_cc_calc_initial_cwnd(ctx->super.quic->initcwnd_packets, ctx->super.quic->transport_params.max_udp_payload_size) * |
2404 | 6.28k | 4; /* sending jumpstart token is meaningless until CWND has grown 2x of IW, which translates to 4x data being sent */ |
2405 | | |
2406 | 6.28k | assert(ctx->super.next_cid != NULL && "to set next_cid, h2o_quic_set_context_identifier must be called"); |
2407 | | |
2408 | | /* accept connection */ |
2409 | 6.28k | ptls_log_conn_state_t log_state_override; |
2410 | 6.28k | ptls_log_init_conn_state(&log_state_override, ctx->super.quic->tls->random_bytes, conn->super.id, &srcaddr->sa); |
2411 | 6.28k | ptls_log_conn_state_override = &log_state_override; |
2412 | 6.28k | quicly_conn_t *qconn; |
2413 | 6.28k | quicly_error_t accept_ret = quicly_accept( |
2414 | 6.28k | &qconn, ctx->super.quic, &destaddr->sa, &srcaddr->sa, packet, address_token, ctx->super.next_cid, |
2415 | 6.28k | &conn->handshake_properties, |
2416 | 6.28k | &conn->h3 /* back pointer is set up here so that callbacks being called while parsing ClientHello can refer to `conn` */); |
2417 | 6.28k | ptls_log_conn_state_override = NULL; |
2418 | 6.28k | if (accept_ret != 0) { |
2419 | 0 | h2o_http3_conn_t *ret = NULL; |
2420 | 0 | if (accept_ret == QUICLY_ERROR_DECRYPTION_FAILED) |
2421 | 0 | ret = (h2o_http3_conn_t *)&h2o_quic_accept_conn_decryption_failed; |
2422 | 0 | h2o_http3_dispose_conn(&conn->h3); |
2423 | 0 | kh_destroy(stream, conn->datagram_flows); |
2424 | 0 | h2o_destroy_connection(&conn->super); |
2425 | 0 | return ret; |
2426 | 0 | } |
2427 | 6.28k | if (ctx->super.quic_stats != NULL) { |
2428 | 0 | ++ctx->super.quic_stats->packet_processed; |
2429 | 0 | } |
2430 | 6.28k | ++ctx->super.next_cid->master_id; /* FIXME check overlap */ |
2431 | 6.28k | h2o_http3_setup(&conn->h3, qconn); |
2432 | | |
2433 | 6.28k | H2O_PROBE_CONN(H3S_ACCEPT, &conn->super, &conn->super, conn->h3.super.quic, h2o_conn_get_uuid(&conn->super)); |
2434 | 6.28k | H2O_LOG_CONN(h3s_accept, &conn->super, { |
2435 | 6.28k | PTLS_LOG_ELEMENT_PTR(conn, &conn->super); |
2436 | 6.28k | PTLS_LOG_ELEMENT_PTR(quic, conn->h3.super.quic); |
2437 | 6.28k | PTLS_LOG_ELEMENT_SAFESTR(conn_uuid, h2o_conn_get_uuid(&conn->super)); |
2438 | 6.28k | }); |
2439 | | |
2440 | 6.28k | if (!h2o_quic_send(&conn->h3.super)) { |
2441 | | /* When `h2o_quic_send` fails, it destroys the connection object. */ |
2442 | 0 | return &h2o_http3_accept_conn_closed; |
2443 | 0 | } |
2444 | | |
2445 | 6.28k | return &conn->h3; |
2446 | 6.28k | } |
2447 | | |
2448 | | void h2o_http3_server_amend_quicly_context(h2o_globalconf_t *conf, quicly_context_t *quic) |
2449 | 1 | { |
2450 | 1 | quic->transport_params.max_data = |
2451 | 1 | conf->http3.active_stream_window_size; /* set to a size that does not block the unblocked request stream */ |
2452 | 1 | quic->transport_params.max_streams_uni = 10; |
2453 | 1 | quic->transport_params.max_stream_data.bidi_remote = h2o_http3_calc_min_flow_control_size(H2O_MAX_REQLEN); |
2454 | 1 | quic->transport_params.max_stream_data.uni = h2o_http3_calc_min_flow_control_size(H2O_MAX_REQLEN); |
2455 | 1 | quic->transport_params.max_idle_timeout = conf->http3.idle_timeout; |
2456 | 1 | quic->transport_params.min_ack_delay_usec = conf->http3.allow_delayed_ack ? 0 : UINT64_MAX; |
2457 | 1 | quic->ack_frequency = conf->http3.ack_frequency; |
2458 | 1 | quic->transport_params.max_datagram_frame_size = 1500; /* accept DATAGRAM frames; let the sender determine MTU, instead of being |
2459 | | * potentially too restrictive */ |
2460 | 1 | quic->stream_open = &on_stream_open; |
2461 | 1 | quic->stream_scheduler = &scheduler; |
2462 | 1 | quic->receive_datagram_frame = &on_receive_datagram_frame; |
2463 | | |
2464 | 4 | for (size_t i = 0; quic->tls->cipher_suites[i] != NULL; ++i) |
2465 | 3 | assert(quic->tls->cipher_suites[i]->aead->ctr_cipher != NULL && |
2466 | 1 | "for header protection, QUIC ciphers MUST provide CTR mode"); |
2467 | 1 | } |
2468 | | |
2469 | | h2o_conn_t *h2o_http3_get_connection(quicly_conn_t *quic) |
2470 | 0 | { |
2471 | 0 | struct st_h2o_http3_server_conn_t *conn = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_conn_t, h3, *quicly_get_data(quic)); |
2472 | | |
2473 | | /* this assertion is most likely to fire if the provided QUIC connection does not represent a server-side HTTP connection */ |
2474 | 0 | assert(conn->h3.super.quic == NULL || conn->h3.super.quic == quic); |
2475 | | |
2476 | 0 | return &conn->super; |
2477 | 0 | } |
2478 | | |
2479 | | static void graceful_shutdown_close_straggler(h2o_timer_t *entry) |
2480 | 0 | { |
2481 | 0 | struct st_h2o_http3_server_conn_t *conn = |
2482 | 0 | H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_conn_t, _graceful_shutdown_timeout, entry); |
2483 | | |
2484 | | /* We've sent two GOAWAY frames, close the remaining connections */ |
2485 | 0 | h2o_quic_close_connection(&conn->h3.super, 0, "shutting down"); |
2486 | |
|
2487 | 0 | conn->_graceful_shutdown_timeout.cb = NULL; |
2488 | 0 | } |
2489 | | |
2490 | | static void graceful_shutdown_resend_goaway(h2o_timer_t *entry) |
2491 | 0 | { |
2492 | 0 | struct st_h2o_http3_server_conn_t *conn = |
2493 | 0 | H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3_server_conn_t, _graceful_shutdown_timeout, entry); |
2494 | | |
2495 | | /* HTTP/3 draft section 5.2.8 -- |
2496 | | * "After allowing time for any in-flight requests or pushes to arrive, the endpoint can send another GOAWAY frame |
2497 | | * indicating which requests or pushes it might accept before the end of the connection. |
2498 | | * This ensures that a connection can be cleanly shut down without losing requests. */ |
2499 | |
|
2500 | 0 | if (conn->h3.state < H2O_HTTP3_CONN_STATE_HALF_CLOSED && quicly_get_state(conn->h3.super.quic) == QUICLY_STATE_CONNECTED) { |
2501 | 0 | quicly_stream_id_t next_stream_id = quicly_get_remote_next_stream_id(conn->h3.super.quic, 0 /* == bidi */); |
2502 | | /* Section 5.2-1: "This identifier MAY be zero if no requests or pushes were processed."" */ |
2503 | 0 | quicly_stream_id_t max_stream_id = next_stream_id < 4 ? 0 /* we haven't received any stream yet */ : next_stream_id - 4; |
2504 | 0 | h2o_http3_send_goaway_frame(&conn->h3, max_stream_id); |
2505 | 0 | conn->h3.state = H2O_HTTP3_CONN_STATE_HALF_CLOSED; |
2506 | | /* After waiting a second, we still have an active connection. If configured, wait one |
2507 | | * final timeout before closing the connection */ |
2508 | 0 | if (conn->super.ctx->globalconf->http3.graceful_shutdown_timeout > 0) { |
2509 | 0 | conn->_graceful_shutdown_timeout.cb = graceful_shutdown_close_straggler; |
2510 | 0 | h2o_timer_link(conn->super.ctx->loop, conn->super.ctx->globalconf->http3.graceful_shutdown_timeout, |
2511 | 0 | &conn->_graceful_shutdown_timeout); |
2512 | 0 | } else { |
2513 | 0 | conn->_graceful_shutdown_timeout.cb = NULL; |
2514 | 0 | } |
2515 | 0 | } |
2516 | 0 | } |
2517 | | |
2518 | | static void close_idle_connection(h2o_conn_t *_conn) |
2519 | 0 | { |
2520 | 0 | initiate_graceful_shutdown(_conn); |
2521 | 0 | } |
2522 | | |
2523 | | static void initiate_graceful_shutdown(h2o_conn_t *_conn) |
2524 | 0 | { |
2525 | 0 | h2o_conn_set_state(_conn, H2O_CONN_STATE_SHUTDOWN); |
2526 | |
|
2527 | 0 | struct st_h2o_http3_server_conn_t *conn = (void *)_conn; |
2528 | 0 | assert(conn->_graceful_shutdown_timeout.cb == NULL); |
2529 | 0 | conn->_graceful_shutdown_timeout.cb = graceful_shutdown_resend_goaway; |
2530 | |
|
2531 | 0 | h2o_http3_send_shutdown_goaway_frame(&conn->h3); |
2532 | |
|
2533 | 0 | h2o_timer_link(conn->super.ctx->loop, 1000, &conn->_graceful_shutdown_timeout); |
2534 | 0 | } |
2535 | | |
2536 | | struct foreach_request_ctx { |
2537 | | int (*cb)(h2o_req_t *req, void *cbdata); |
2538 | | void *cbdata; |
2539 | | }; |
2540 | | |
2541 | | static int64_t foreach_request_per_conn(void *_ctx, quicly_stream_t *qs) |
2542 | 0 | { |
2543 | 0 | struct foreach_request_ctx *ctx = _ctx; |
2544 | | |
2545 | | /* skip if the stream is not a request stream (TODO handle push?) */ |
2546 | 0 | if (!(quicly_stream_is_client_initiated(qs->stream_id) && !quicly_stream_is_unidirectional(qs->stream_id))) |
2547 | 0 | return 0; |
2548 | | |
2549 | 0 | struct st_h2o_http3_server_stream_t *stream = qs->data; |
2550 | 0 | assert(stream->quic == qs); |
2551 | | |
2552 | 0 | if (stream->state == H2O_HTTP3_SERVER_STREAM_STATE_CLOSE_WAIT) |
2553 | 0 | return 0; |
2554 | 0 | return ctx->cb(&stream->req, ctx->cbdata); |
2555 | 0 | } |
2556 | | |
2557 | | static int foreach_request(h2o_conn_t *_conn, int (*cb)(h2o_req_t *req, void *cbdata), void *cbdata) |
2558 | 0 | { |
2559 | 0 | struct foreach_request_ctx foreach_ctx = {.cb = cb, .cbdata = cbdata}; |
2560 | |
|
2561 | 0 | struct st_h2o_http3_server_conn_t *conn = (void *)_conn; |
2562 | 0 | quicly_foreach_stream(conn->h3.super.quic, &foreach_ctx, foreach_request_per_conn); |
2563 | 0 | return 0; |
2564 | 0 | } |
2565 | | |
2566 | | const h2o_http3_conn_callbacks_t H2O_HTTP3_CONN_CALLBACKS = { |
2567 | | {on_h3_destroy}, |
2568 | | handle_control_stream_frame, |
2569 | | qpack_unblock_streams, |
2570 | | }; |