/src/wireshark/epan/dissectors/packet-http3.c
Line | Count | Source |
1 | | /* packet-http3.c |
2 | | * Routines for HTTP/3 dissection |
3 | | * Copyright 2019, Peter Wu <peter@lekensteyn.nl> |
4 | | * Copyright 2023, Omer Shapira <oesh@github.com> |
5 | | * |
6 | | * Wireshark - Network traffic analyzer |
7 | | * By Gerald Combs <gerald@wireshark.org> |
8 | | * Copyright 1998 Gerald Combs |
9 | | * |
10 | | * SPDX-License-Identifier: GPL-2.0-or-later |
11 | | */ |
12 | | |
13 | | /* |
14 | | * https://tools.ietf.org/html/draft-ietf-quic-http-29 |
15 | | * https://tools.ietf.org/html/draft-ietf-quic-qpack-16 |
16 | | * |
17 | | * Depends on the QUIC dissector for providing a reassembled stream of data, see |
18 | | * packet-quic.c for details about supported QUIC draft versions. |
19 | | * Depends on nghttp3 for HTTP header dissection. |
20 | | * Currently supported HTTP/3 versions: h3-23 up to and including h3-29. |
21 | | */ |
22 | | |
23 | | #include "config.h" |
24 | 0 | #define WS_LOG_DOMAIN "HTTP3" |
25 | | |
26 | | #include <stdint.h> |
27 | | #include <string.h> |
28 | | |
29 | | #include <epan/wmem_scopes.h> |
30 | | #include <epan/addr_resolv.h> |
31 | | #include <epan/charsets.h> |
32 | | #include <epan/conversation_table.h> |
33 | | #include <epan/decode_as.h> |
34 | | #include <epan/exceptions.h> |
35 | | #include <epan/expert.h> |
36 | | #include <epan/follow.h> |
37 | | #include <epan/packet.h> |
38 | | #include <epan/proto_data.h> |
39 | | #include <epan/reassemble.h> |
40 | | #include <epan/to_str.h> |
41 | | #include <epan/uat.h> |
42 | | |
43 | | #include <epan/dissectors/packet-http3.h> |
44 | | #include <epan/dissectors/packet-http.h> /* for getting status reason-phrase */ |
45 | | #include <epan/dissectors/packet-media-type.h> |
46 | | #include <epan/dissectors/packet-quic.h> |
47 | | #include <epan/dissectors/packet-udp.h> |
48 | | |
49 | | #include <wsutil/pint.h> |
50 | | #include <wsutil/str_util.h> |
51 | | #include <wsutil/ws_assert.h> |
52 | | #include <wsutil/zlib_compat.h> |
53 | | |
54 | | #ifdef HAVE_NGHTTP3 |
55 | | #include <nghttp3/nghttp3.h> |
56 | | #include <epan/export_object.h> |
57 | | #endif |
58 | | |
59 | | void proto_reg_handoff_http3(void); |
60 | | void proto_register_http3(void); |
61 | | |
62 | | static dissector_handle_t http3_handle; |
63 | | static dissector_handle_t http3_datagram_handle; |
64 | | |
65 | | static int http3_follow_tap; |
66 | | |
67 | | #ifdef HAVE_NGHTTP3 |
68 | | static reassembly_table http3_body_reassembly_table; |
69 | | |
70 | | static dissector_table_t media_type_dissector_table; |
71 | | |
72 | | static dissector_handle_t media_handle; |
73 | | |
74 | | static int http_eo_tap; |
75 | | |
76 | | /* |
77 | | * Decompression of content-encoded entities. |
78 | | */ |
79 | | #if defined(HAVE_ZLIB) || defined(HAVE_ZLIBNG) || defined(HAVE_BROTLI) || defined(HAVE_ZSTD) |
80 | | static bool http3_decompress_body = true; |
81 | | #else |
82 | | static bool http3_decompress_body; |
83 | | #endif |
84 | | |
85 | | #endif /* HAVE_NGHTTP3 */ |
86 | | |
87 | | #define PROTO_DATA_KEY_HEADER 0 |
88 | | #define PROTO_DATA_KEY_QPACK 1 |
89 | | |
90 | | static int proto_http3; |
91 | | static int hf_http3_stream_uni; |
92 | | static int hf_http3_stream_uni_type; |
93 | | static int hf_http3_stream_bidi; |
94 | | static int hf_http3_stream_id; |
95 | | static int hf_http3_push_id; |
96 | | static int hf_http3_frame; |
97 | | static int hf_http3_frame_type; |
98 | | static int hf_http3_frame_length; |
99 | | static int hf_http3_frame_payload; |
100 | | |
101 | | static int hf_http3_time; |
102 | | static int hf_http3_request_in; |
103 | | static int hf_http3_response_in; |
104 | | |
105 | | static int hf_http3_data; |
106 | | static int hf_http3_encoded_entity; |
107 | | static int hf_http3_body_fragments; |
108 | | static int hf_http3_body_fragment; |
109 | | static int hf_http3_body_fragment_overlap; |
110 | | static int hf_http3_body_fragment_overlap_conflicts; |
111 | | static int hf_http3_body_fragment_multiple_tails; |
112 | | static int hf_http3_body_fragment_too_long_fragment; |
113 | | static int hf_http3_body_fragment_error; |
114 | | static int hf_http3_body_fragment_count; |
115 | | static int hf_http3_body_reassembled_in; |
116 | | static int hf_http3_body_reassembled_length; |
117 | | static int hf_http3_body_reassembled_data; |
118 | | |
119 | | static int hf_http3_headers_count; |
120 | | static int hf_http3_header; |
121 | | static int hf_http3_headers_decoded_length; |
122 | | static int hf_http3_header_name_length; |
123 | | static int hf_http3_header_name; |
124 | | static int hf_http3_header_value_length; |
125 | | static int hf_http3_header_value; |
126 | | static int hf_http3_header_request_full_uri; |
127 | | |
128 | | static int hf_http3_header_qpack_blocked; |
129 | | static int hf_http3_header_qpack_blocked_stream_rcint; |
130 | | static int hf_http3_header_qpack_blocked_decoder_wicnt; |
131 | | |
132 | | #ifdef HAVE_NGHTTP3 |
133 | | /* Static HTTP3 headers */ |
134 | | static int hf_http3_headers_status; |
135 | | static int hf_http3_headers_path; |
136 | | static int hf_http3_headers_protocol; |
137 | | static int hf_http3_headers_method; |
138 | | static int hf_http3_headers_scheme; |
139 | | static int hf_http3_headers_accept; |
140 | | static int hf_http3_headers_accept_charset; |
141 | | static int hf_http3_headers_accept_encoding; |
142 | | static int hf_http3_headers_accept_language; |
143 | | static int hf_http3_headers_accept_ranges; |
144 | | static int hf_http3_headers_access_control_allow_origin; |
145 | | static int hf_http3_headers_age; |
146 | | static int hf_http3_headers_allow; |
147 | | static int hf_http3_headers_authorization; |
148 | | static int hf_http3_headers_authority; |
149 | | static int hf_http3_headers_cache_control; |
150 | | static int hf_http3_headers_content_disposition; |
151 | | static int hf_http3_headers_content_encoding; |
152 | | static int hf_http3_headers_content_language; |
153 | | static int hf_http3_headers_content_length; |
154 | | static int hf_http3_headers_content_location; |
155 | | static int hf_http3_headers_content_range; |
156 | | static int hf_http3_headers_content_type; |
157 | | static int hf_http3_headers_cookie; |
158 | | static int hf_http3_headers_date; |
159 | | static int hf_http3_headers_etag; |
160 | | static int hf_http3_headers_expect; |
161 | | static int hf_http3_headers_expires; |
162 | | static int hf_http3_headers_from; |
163 | | static int hf_http3_headers_if_match; |
164 | | static int hf_http3_headers_if_modified_since; |
165 | | static int hf_http3_headers_if_none_match; |
166 | | static int hf_http3_headers_if_range; |
167 | | static int hf_http3_headers_if_unmodified_since; |
168 | | static int hf_http3_headers_last_modified; |
169 | | static int hf_http3_headers_link; |
170 | | static int hf_http3_headers_location; |
171 | | static int hf_http3_headers_max_forwards; |
172 | | static int hf_http3_headers_proxy_authenticate; |
173 | | static int hf_http3_headers_proxy_authorization; |
174 | | static int hf_http3_headers_range; |
175 | | static int hf_http3_headers_referer; |
176 | | static int hf_http3_headers_refresh; |
177 | | static int hf_http3_headers_retry_after; |
178 | | static int hf_http3_headers_server; |
179 | | static int hf_http3_headers_set_cookie; |
180 | | static int hf_http3_headers_strict_transport_security; |
181 | | static int hf_http3_headers_user_agent; |
182 | | static int hf_http3_headers_vary; |
183 | | static int hf_http3_headers_via; |
184 | | static int hf_http3_headers_www_authenticate; |
185 | | #endif |
186 | | |
187 | | static int hf_http3_qpack_encoder; |
188 | | static int hf_http3_qpack_encoder_icnt; |
189 | | static int hf_http3_qpack_encoder_icnt_inc; |
190 | | static int hf_http3_qpack_encoder_opcode_insert_indexed; |
191 | | static int hf_http3_qpack_encoder_opcode_insert_indexed_ref; |
192 | | static int hf_http3_qpack_encoder_opcode_insert_indexed_val; |
193 | | static int hf_http3_qpack_encoder_opcode_insert_indexed_hval; |
194 | | static int hf_http3_qpack_encoder_opcode_insert; |
195 | | static int hf_http3_qpack_encoder_opcode_insert_name; |
196 | | static int hf_http3_qpack_encoder_opcode_insert_hname; |
197 | | static int hf_http3_qpack_encoder_opcode_insert_val; |
198 | | static int hf_http3_qpack_encoder_opcode_insert_hval; |
199 | | static int hf_http3_qpack_encoder_opcode_duplicate; |
200 | | static int hf_http3_qpack_encoder_opcode_dtable_cap; |
201 | | static int hf_http3_qpack_encoder_opcode_dtable_cap_val; |
202 | | |
203 | | static int hf_http3_qpack_decoder; |
204 | | static int hf_http3_qpack_decoder_opcode_section_ack; |
205 | | static int hf_http3_qpack_decoder_opcode_section_ack_stream_id; |
206 | | static int hf_http3_qpack_decoder_opcode_cancel_stream; |
207 | | static int hf_http3_qpack_decoder_opcode_cancel_stream_id; |
208 | | static int hf_http3_qpack_decoder_opcode_icnt_increment; |
209 | | static int hf_http3_qpack_decoder_opcode_icnt_increment_value; |
210 | | |
211 | | static int hf_http3_settings; |
212 | | static int hf_http3_settings_identifier; |
213 | | static int hf_http3_settings_value; |
214 | | static int hf_http3_settings_qpack_max_table_capacity; |
215 | | static int hf_http3_settings_max_field_section_size; |
216 | | static int hf_http3_settings_qpack_blocked_streams; |
217 | | static int hf_http3_settings_extended_connect; |
218 | | static int hf_http3_settings_webtransport; |
219 | | static int hf_http3_settings_h3_datagram; |
220 | | static int hf_http3_settings_h3_datagram_draft04; |
221 | | static int hf_http3_priority_update_element_id; |
222 | | static int hf_http3_priority_update_field_value; |
223 | | |
224 | | static int hf_http3_datagram; |
225 | | static int hf_http3_datagram_quarter_stream_id; |
226 | | static int hf_http3_datagram_request_stream_id; |
227 | | static int hf_http3_datagram_payload; |
228 | | |
229 | | static expert_field ei_http3_qpack_failed; |
230 | | static expert_field ei_http3_prefix_int_failed; |
231 | | static expert_field ei_http3_huffman_failed; |
232 | | /* HTTP3 dissection EIs */ |
233 | | static expert_field ei_http3_unknown_stream_type; |
234 | | /* Encoded data EIs */ |
235 | | static expert_field ei_http3_header_encoded_state; |
236 | | /* HTTP3 header decoding EIs */ |
237 | | static expert_field ei_http3_header_decoding_failed; |
238 | | static expert_field ei_http3_header_decoding_blocked; |
239 | | static expert_field ei_http3_header_decoding_no_output; |
240 | | static expert_field ei_http3_header_size; |
241 | | static expert_field ei_http3_header_transfer_encoding; |
242 | | /* HTTP3 body decoding EIs */ |
243 | | static expert_field ei_http3_body_decompression_failed; |
244 | | /* HTTP3 datagram prefix EIs */ |
245 | | static expert_field ei_http3_datagram_invalid_stream_id; |
246 | | |
247 | | /* Initialize the subtree pointers */ |
248 | | static int ett_http3; |
249 | | static int ett_http3_stream_uni; |
250 | | static int ett_http3_stream_bidi; |
251 | | static int ett_http3_frame; |
252 | | static int ett_http3_body_fragment; |
253 | | static int ett_http3_body_fragments; |
254 | | static int ett_http3_encoded_entity; |
255 | | static int ett_http3_settings; |
256 | | static int ett_http3_headers; |
257 | | static int ett_http3_headers_qpack_blocked; |
258 | | static int ett_http3_qpack_update; |
259 | | static int ett_http3_qpack_opcode; |
260 | | static int ett_http3_datagram; |
261 | | static int ett_http3_datagram_stream_id; |
262 | | |
263 | | #ifdef HAVE_NGHTTP3 |
264 | | static const fragment_items http3_body_fragment_items = { |
265 | | /* Fragment subtrees */ |
266 | | &ett_http3_body_fragment, |
267 | | &ett_http3_body_fragments, |
268 | | /* Fragment fields */ |
269 | | &hf_http3_body_fragments, |
270 | | &hf_http3_body_fragment, |
271 | | &hf_http3_body_fragment_overlap, |
272 | | &hf_http3_body_fragment_overlap_conflicts, |
273 | | &hf_http3_body_fragment_multiple_tails, |
274 | | &hf_http3_body_fragment_too_long_fragment, |
275 | | &hf_http3_body_fragment_error, |
276 | | &hf_http3_body_fragment_count, |
277 | | &hf_http3_body_reassembled_in, |
278 | | &hf_http3_body_reassembled_length, |
279 | | &hf_http3_body_reassembled_data, |
280 | | "Body fragments" |
281 | | }; |
282 | | #endif |
283 | | |
284 | | /** |
285 | | * HTTP3 header constants. |
286 | | * The below constants are used for dissecting the |
287 | | * code. This is not an exhaustive list. |
288 | | */ |
289 | | #define HTTP3_HEADER_NAME_CONTENT_ENCODING "content-encoding" |
290 | | #define HTTP3_HEADER_NAME_CONTENT_TYPE "content-type" |
291 | | #define HTTP3_HEADER_NAME_CONTENT_LENGTH "content-length" |
292 | | #define HTTP3_HEADER_NAME_TRANSFER_ENCODING "transfer-encoding" |
293 | | #define HTTP3_HEADER_NAME_AUTHORITY ":authority" |
294 | | #define HTTP3_HEADER_NAME_METHOD ":method" |
295 | | #define HTTP3_HEADER_NAME_PATH ":path" |
296 | | #define HTTP3_HEADER_NAME_PROTOCOL ":protocol" |
297 | | #define HTTP3_HEADER_NAME_SCHEME ":scheme" |
298 | | #define HTTP3_HEADER_NAME_STATUS ":status" |
299 | | |
300 | | #define HTTP3_HEADER_METHOD_CONNECT "CONNECT" |
301 | | #define HTTP3_HEADER_STATUS_PARTIAL_CONTENT "206" |
302 | | |
303 | | #define HTTP3_HEADER_UNKNOWN "<unknown>" |
304 | | |
305 | | /** |
306 | | * Unidirectional stream types. |
307 | | * https://tools.ietf.org/html/draft-ietf-quic-http-29#section-6.2 |
308 | | * https://tools.ietf.org/html/draft-ietf-quic-qpack-16#section-4.2 |
309 | | */ |
310 | | enum http3_stream_type { |
311 | | HTTP3_STREAM_TYPE_CONTROL, |
312 | | HTTP3_STREAM_TYPE_PUSH, |
313 | | HTTP3_STREAM_TYPE_QPACK_ENCODER, |
314 | | HTTP3_STREAM_TYPE_QPACK_DECODER, |
315 | | HTTP3_STREAM_TYPE_WEBTRANSPORT = 0x54, // draft-ietf-webtrans-http3-03 |
316 | | }; |
317 | | |
318 | | /** |
319 | | * Unidirectional stream types (62-bit code space). |
320 | | * https://tools.ietf.org/html/draft-ietf-quic-http-29#section-11.2.4 |
321 | | */ |
322 | | // clang-format off |
323 | | static const val64_string http3_stream_types[] = { |
324 | | /* 0x00 - 0x3f Assigned via Standards Action or IESG Approval. */ |
325 | | { 0x00, "Control Stream" }, |
326 | | { 0x01, "Push Stream" }, |
327 | | { 0x02, "QPACK Encoder Stream" }, |
328 | | { 0x03, "QPACK Decoder Stream" }, |
329 | | { 0x54, "WebTransport Stream" }, |
330 | | /* 0x40 - 0x3FFFFFFFFFFFFFFF Assigned via Specification Required policy */ |
331 | | { 0, NULL } |
332 | | }; |
333 | | // clang-format on |
334 | | |
335 | | /** |
336 | | * Frame type codes (62-bit code space). |
337 | | * https://tools.ietf.org/html/draft-ietf-quic-http-29#section-11.2.1 |
338 | | */ |
339 | 0 | #define HTTP3_DATA 0x0 |
340 | 0 | #define HTTP3_HEADERS 0x1 |
341 | 0 | #define HTTP3_CANCEL_PUSH 0x3 |
342 | 0 | #define HTTP3_SETTINGS 0x4 |
343 | 0 | #define HTTP3_PUSH_PROMISE 0x5 |
344 | 0 | #define HTTP3_GOAWAY 0x7 |
345 | 0 | #define HTTP3_MAX_PUSH_ID 0xD |
346 | | #define HTTP3_WEBTRANSPORT_BISTREAM 0x41 |
347 | 0 | #define HTTP3_PRIORITY_UPDATE_REQUEST_STREAM 0xF0700 |
348 | 0 | #define HTTP3_PRIORITY_UPDATE_PUSH_STREAM 0xF0701 |
349 | | |
350 | | static const val64_string http3_frame_types[] = { |
351 | | /* 0x00 - 0x3f Assigned via Standards Action or IESG Approval. */ |
352 | | { HTTP3_DATA, "DATA" }, |
353 | | { HTTP3_HEADERS, "HEADERS" }, |
354 | | { 0x02, "Reserved" }, // "PRIORITY" in draft-22 and before |
355 | | { HTTP3_CANCEL_PUSH, "CANCEL_PUSH" }, |
356 | | { HTTP3_SETTINGS, "SETTINGS" }, |
357 | | { HTTP3_PUSH_PROMISE, "PUSH_PROMISE" }, |
358 | | { 0x06, "Reserved" }, |
359 | | { HTTP3_GOAWAY, "GOAWAY" }, |
360 | | { 0x08, "Reserved" }, |
361 | | { 0x09, "Reserved" }, |
362 | | { HTTP3_MAX_PUSH_ID, "MAX_PUSH_ID" }, |
363 | | { 0x0e, "Reserved" }, // "DUPLICATE_PUSH" in draft-26 and before |
364 | | /* 0x40 - 0x3FFFFFFFFFFFFFFF Assigned via Specification Required policy */ |
365 | | { HTTP3_WEBTRANSPORT_BISTREAM, "WEBTRANSPORT_BISTREAM" }, // draft-ietf-webtrans-http3-03 |
366 | | { HTTP3_PRIORITY_UPDATE_REQUEST_STREAM, "PRIORITY_UPDATE" }, // RFC 9218 |
367 | | { HTTP3_PRIORITY_UPDATE_PUSH_STREAM, "PRIORITY_UPDATE" }, // RFC 9218 |
368 | | { 0, NULL } |
369 | | }; |
370 | | |
371 | | /** |
372 | | * Settings parameter type codes (62-bit code space). |
373 | | * https://tools.ietf.org/html/draft-ietf-quic-http-29#name-http-2-settings-parameters |
374 | | */ |
375 | 0 | #define HTTP3_QPACK_MAX_TABLE_CAPACITY 0x01 |
376 | 0 | #define HTTP3_SETTINGS_MAX_FIELD_SECTION_SIZE 0x06 |
377 | 0 | #define HTTP3_QPACK_BLOCKED_STREAMS 0x07 |
378 | 0 | #define HTTP3_EXTENDED_CONNECT 0x08 /* https://datatracker.ietf.org/doc/draft-ietf-httpbis-h3-websockets */ |
379 | 0 | #define HTTP3_H3_DATAGRAM 0x33 /* rfc9297 */ |
380 | 0 | #define HTTP3_H3_DATAGRAM_DRAFT04 0xffd277 /* draft-ietf-masque-h3-datagram-04 */ |
381 | 0 | #define HTTP3_WEBTRANSPORT 0x2b603742 /* draft-ietf-webtrans-http3-03 */ |
382 | | |
383 | | static const val64_string http3_settings_vals[] = { |
384 | | { HTTP3_QPACK_MAX_TABLE_CAPACITY, "Max Table Capacity" }, |
385 | | { HTTP3_SETTINGS_MAX_FIELD_SECTION_SIZE, "Max Field Section Size" }, |
386 | | { HTTP3_QPACK_BLOCKED_STREAMS, "Blocked Streams" }, |
387 | | { HTTP3_EXTENDED_CONNECT, "Extended CONNECT" }, |
388 | | { HTTP3_WEBTRANSPORT, "Enable WebTransport" }, |
389 | | { HTTP3_H3_DATAGRAM, "Enable Datagram" }, |
390 | | { HTTP3_H3_DATAGRAM_DRAFT04, "Enable Datagram Draft04" }, |
391 | | { 0, NULL } |
392 | | }; |
393 | | |
394 | | /** |
395 | | * QPACK encoder stream opcodes. |
396 | | */ |
397 | 0 | #define QPACK_OPCODE_MASK 0xE0 |
398 | 0 | #define QPACK_OPCODE_INSERT_INDEXED 0x80 |
399 | 0 | #define QPACK_OPCODE_INSERT 0x40 |
400 | 0 | #define QPACK_OPCODE_SET_DTABLE_CAP 0x20 |
401 | 0 | #define QPACK_OPCODE_DUPLICATE 0x00 |
402 | | |
403 | | /** |
404 | | * QPACK decoder stream opcodes. |
405 | | */ |
406 | 0 | #define QPACK_OPCODE_SECTION_ACK 0x80 |
407 | 0 | #define QPACK_OPCODE_STREAM_CANCEL 0x40 |
408 | 0 | #define QPACK_OPCODE_ICNT_INCREMENT 0x00 |
409 | | |
410 | | #define QPACK_HUFFMAN_5_STRING 0x20 |
411 | | #define QPACK_HUFFMAN_6_STRING 0x40 |
412 | | #define QPACK_HUFFMAN_7_STRING q0x80 |
413 | | |
414 | | typedef enum _http3_stream_dir { |
415 | | FROM_CLIENT_TO_SERVER = 0, |
416 | | FROM_SERVER_TO_CLIENT = 1, |
417 | | } http3_stream_dir; |
418 | | |
419 | | /** |
420 | | * Essential data structures. |
421 | | */ |
422 | | |
423 | | /** |
424 | | * HTTP3 stream info - contains information about HTTP3 stream. |
425 | | * HTTP3 streams roughly correspond to QUIC streams, with the |
426 | | * HTTP3 Server Push being an exception to the rule. |
427 | | */ |
428 | | |
429 | | /* HTTP/3 pseudo-header fields. |
430 | | * |
431 | | * This is a convenience structure that is used |
432 | | * to collect the values of the HTTP/3 pseudo-headers |
433 | | * while constructing the protocol tree, |
434 | | * and to construct the column info afterwards. |
435 | | * Pseudo-header fields defined for requests MUST NOT appear in responses; |
436 | | * pseudo-header fields defined for responses MUST NOT appear in requests. |
437 | | * Pseudo-header fields MUST NOT appear in trailer sections. |
438 | | * https://www.rfc-editor.org/rfc/rfc9114.html#name-http-control-data |
439 | | * |
440 | | * Therefore, we can store these at the bidirectional stream level. |
441 | | */ |
442 | | typedef struct _http3_pseudo_header_fields { |
443 | | const char *authority; |
444 | | const char *method; |
445 | | const char *path; |
446 | | const char *protocol; |
447 | | const char *reason_phrase; /**< "pseudo" pseudo-header. */ |
448 | | const char *scheme; |
449 | | const char *status; |
450 | | } http3_pseudo_header_fields_t; |
451 | | |
452 | | #define HTTP3_PSEUDO_HEADERS_INITIALIZER (http3_pseudo_header_fields_t){ \ |
453 | | .authority = NULL, \ |
454 | | .method = NULL, \ |
455 | | .path = NULL, \ |
456 | | .protocol = NULL, \ |
457 | | .reason_phrase = NULL, \ |
458 | | .scheme = NULL, \ |
459 | | .status = NULL, \ |
460 | | } |
461 | | typedef struct _http3_stream_info { |
462 | | uint64_t id; /**< HTTP3 stream id */ |
463 | | uint64_t uni_stream_type; /**< Unidirectional stream type */ |
464 | | uint64_t broken_from_offset; /**< Unrecognized stream starting at offset (if non-zero). */ |
465 | | http3_stream_dir direction; |
466 | | wmem_list_t *request_header_data; /**< List of request header data */ |
467 | | wmem_list_t *response_header_data; /**< List of response header data */ |
468 | | const char *protocol; /**< Protocol from extended CONNECT */ |
469 | | dissector_handle_t next_handle; /**< Dissector for extended CONNECT protocol */ |
470 | | http_upgrade_info_t *upgrade_info; /**< Data for new protocol */ |
471 | | nstime_t request_ts; /**< Timestamp of request first HEADERS frame */ |
472 | | uint32_t request_frame_num; /**< Frame number of request first HEADERS frame */ |
473 | | uint32_t response_frame_num; /**< Frame number of response first HEADERS frame */ |
474 | | bool is_connect; /**< Method is CONNECT (plain or extended) */ |
475 | | http3_pseudo_header_fields_t pseudo_headers; |
476 | | } http3_stream_info_t; |
477 | | |
478 | | /** |
479 | | * HTTP3 session info - contains information about the HTTP3 session. |
480 | | * HTTP3 sessions roughly correspond to QUIC connections, at least |
481 | | * until the dissector will support connection migration and/or |
482 | | * Multipath QUIC. When that happens, a single HTTP3 session would |
483 | | * be mapped to multiple QUIC connections, or to multiple QUIC |
484 | | * paths (in the MP-QUIC terminology). |
485 | | */ |
486 | | |
487 | | typedef void *qpack_decoder_t; |
488 | | typedef void *qpack_decoder_ctx_t; |
489 | | typedef struct _http3_session_info { |
490 | | unsigned id; |
491 | | qpack_decoder_t qpack_decoder[2]; /**< Decoders for outgoing/incoming QPACK streams. */ |
492 | | http3_stream_info_t *current_stream; /**< Currently processed stream */ |
493 | | } http3_session_info_t; |
494 | | |
495 | | /** |
496 | | * Lookup or create new HTTP3 session object for the pinfo. |
497 | | */ |
498 | | static http3_session_info_t *http3_session_lookup_or_create(packet_info *pinfo); |
499 | | |
500 | | /** |
501 | | * HTTP3 Header dissection support. |
502 | | */ |
503 | | #define QPACK_MAX_DTABLE_SIZE 65536 /**< Max size of the QPACK dynamic table. */ |
504 | | #define QPACK_MAX_BLOCKED 512 /**< Upper limit on number of streams blocked on QPACK updates. */ |
505 | | |
506 | | /** |
507 | | * Limit the maximum header size to handle legitimate use cases while |
508 | | * protecting against hostile traffic (in practice, decompression bombs, |
509 | | * as libnghttp3 puts a limit on the compressed size of a single header.) |
510 | | * |
511 | | * Note that server limits tend to be lower than those supported by clients; |
512 | | * some clients have supported hundreds of MiB of headers (?!) at least in |
513 | | * the past. Chrome has had a consistent limit of 256 KiB. This is more than |
514 | | * enough, but still reasonably fast. We shouldn't need to limit the number |
515 | | * of headers after speeding up tvb_composite. |
516 | | * |
517 | | * https://stackoverflow.com/questions/686217/maximum-on-http-header-values |
518 | | * https://stackoverflow.com/questions/1097651/is-there-a-practical-http-header-length-limit/ |
519 | | * https://stackoverflow.com/questions/3326210/can-http-headers-be-too-big-for-browsers |
520 | | */ |
521 | | #define QPACK_MAX_HEADER_SIZE 1048576 /**< Max size of decompressed headers (1 MiB) */ |
522 | | |
523 | | /** |
524 | | * Header caching scheme |
525 | | * |
526 | | * The HTTP/3 headers are sent on the wire in QPACK-encoded form. |
527 | | * To dissect the headers, Wireshark needs to keep the decoded |
528 | | * header names and values in memory. |
529 | | * |
530 | | * To optimize dissection time, and to conserve memory |
531 | | * the HTTP/3 dissector keeps all *unique* combinations |
532 | | * of header-name, header-value in a cache. |
533 | | * |
534 | | * The cached values are stored in the "pstr" format: |
535 | | * name length (uint32_t) |
536 | | * name |
537 | | * value length (uint32_t) |
538 | | * value |
539 | | */ |
540 | | |
541 | | /** |
542 | | * HTTP3 header field. |
543 | | * |
544 | | * The header field contains two sections: |
545 | | * - encoded points to the location of the encoded field in the *original* packet TVB. |
546 | | * - decoded points to the formatted header string, which is allocated in a cache map, |
547 | | * to conserve memory. |
548 | | * The decoded fields are used to create an auxiliary TVB which will |
549 | | * be used for dissection of decoded header values. |
550 | | */ |
551 | | typedef struct _http3_header_field { |
552 | | struct { |
553 | | unsigned len; /**< Length of the encoded header field. */ |
554 | | unsigned offset; /**< Offset of the encoded header field in the decrypted TVB. */ |
555 | | } encoded; |
556 | | struct { |
557 | | const char *bytes; /**< Decoded header field bytes. */ |
558 | | unsigned len; /**< Length of the decoded header field. */ |
559 | | } decoded; |
560 | | } http3_header_field_t; |
561 | | |
562 | | /** |
563 | | * HTTP3 encoded header data block. |
564 | | * |
565 | | * This helper structure is used to support header dissection. |
566 | | */ |
567 | | typedef struct _header_block_encoded_iter { |
568 | | uint8_t *bytes; |
569 | | uint32_t len; |
570 | | uint32_t pos; |
571 | | } header_block_encoded_iter_t; |
572 | | |
573 | | #define HEADER_BLOCK_ENC_ITER_PTR(hdata) \ |
574 | | ((hdata)->encoded.bytes == NULL \ |
575 | | ? NULL \ |
576 | | : ((hdata)->encoded.pos == (hdata)->encoded.len) ? NULL : (hdata)->encoded.bytes + (hdata)->encoded.pos) |
577 | | |
578 | | #define HEADER_BLOCK_ENC_ITER_REMAINING(hdata) \ |
579 | | ((hdata)->encoded.bytes == NULL ? 0 : ((hdata)->encoded.len - (hdata)->encoded.pos)) |
580 | | |
581 | | #define HEADER_BLOCK_ENC_ITER_INC(hdata, nread) \ |
582 | | do { \ |
583 | | if ((hdata)) { \ |
584 | | (hdata)->encoded.pos += (nread); \ |
585 | | DISSECTOR_ASSERT((hdata)->encoded.pos <= (hdata)->encoded.len); \ |
586 | | } \ |
587 | | } while (0) |
588 | | |
589 | | #define HTTP3_HD_DECODER_BLOCKED 0x1 |
590 | | #define HTTP3_HD_DECODER_ERROR 0x2 |
591 | | /** |
592 | | * HTTP3 header data block. |
593 | | * |
594 | | * The data block corresponds to contents of a single HTTP3 HEADERS frame. |
595 | | * If a packet contains multiple HTTP3 HEADERS frames, |
596 | | * the corresponding blocks will be chained using the `next' |
597 | | * pointer. In this case, individual headers blocks |
598 | | * will be identified by the `offset' field. |
599 | | */ |
600 | | typedef struct _http3_header_data { |
601 | | #if 0 |
602 | | /* XXX - This doesn't seem to be used. Would this be any different |
603 | | * than encoded.len ? */ |
604 | | uint32_t len; /**< Length of the encoded headers block. */ |
605 | | #endif |
606 | | uint32_t offset; /**< Offset of the headers block in the pinfo TVB. */ |
607 | | int32_t ds_idx; /**< Index of the data source tvb in the pinfo. */ |
608 | | uint16_t state; /**< See HTTP3_HD_DECODER_XXX above */ |
609 | | int16_t error; /**< Decoding error code if any. */ |
610 | | wmem_array_t * header_fields; /**< List of header fields contained in the header block. */ |
611 | | header_block_encoded_iter_t encoded; /**< Used for dissection, not allocated. */ |
612 | | struct _http3_header_data * next; /**< Next pointer in the chain. */ |
613 | | bool header_size_exceeded; /**< True if decoding halted due to excessive size. */ |
614 | | } http3_header_data_t; |
615 | | |
616 | | |
617 | | #ifdef HAVE_NGHTTP3 |
618 | | #endif /* HAVE_NGHTTP3 */ |
619 | | |
620 | | /* HTTP3 QPACK encoder state |
621 | | * |
622 | | * Store information about how many entries a QPACK encoder stream |
623 | | * has inserted into the decoder at a particular point in the capture |
624 | | * file (both the number newly inserted in the portion of the stream |
625 | | * contained in the current QUIC packet and the total up to that point.) |
626 | | * If a capture frame contains multiple encoder stream segments, the |
627 | | * corresponding blocks will be chained using the 'next' pointer. In this |
628 | | * case, individual blocks will be identified by the data source index |
629 | | * of the tvb within the capture frame and the offset in the ds_tvb. |
630 | | * (Both are necessary for multiple QUIC packets coalesced in a single |
631 | | * UDP datagram with multiple stream segments within a QUIC packet.) |
632 | | */ |
633 | | typedef struct _http3_qpack_encoder_state { |
634 | | unsigned offset; /**< Offset of the headers block in the pinfo TVB. */ |
635 | | int32_t ds_idx; /**< Index of the data source tvb in the pinfo. */ |
636 | | uint32_t icnt_inc; /**< Number of insertions in this header segment. */ |
637 | | uint64_t icnt; /**< Total number of insertions up to this point. */ |
638 | | ptrdiff_t nread; /**< Number of bytes read; if negative, an error code. */ |
639 | | struct _http3_qpack_encoder_state * next; /**< Next pointer in the chain. */ |
640 | | } http3_qpack_encoder_state_t; |
641 | | |
642 | | /** |
643 | | * File-scoped context. |
644 | | * This data structure is used to maintain file-scoped |
645 | | * lookup tables. It is reset when the file-scoped |
646 | | * allocator is exited. |
647 | | */ |
648 | | typedef struct _http3_file_local_ctx { |
649 | | wmem_map_t *conn_info_map; |
650 | | #ifdef HAVE_NGHTTP3 |
651 | | wmem_map_t *hdr_cache_map; |
652 | | #endif |
653 | | } http3_file_local_ctx; |
654 | | |
655 | | /** |
656 | | * @function http3_get_file_local_ctx |
657 | | * @abstract Will create a new instance for the first time |
658 | | * the file is visited. |
659 | | * This function is not intended to be invoked directly, |
660 | | * but should be used via the `HTTP3_CONN_INFO_MAP` et. al. below. |
661 | | * @returns file-local context. |
662 | | */ |
663 | | static http3_file_local_ctx *http3_get_file_local_ctx(void); |
664 | | |
665 | 0 | #define HTTP3_CONN_INFO_MAP http3_get_file_local_ctx()->conn_info_map |
666 | | |
667 | | #ifdef HAVE_NGHTTP3 |
668 | | #define HTTP3_HEADER_CACHE http3_get_file_local_ctx()->hdr_cache_map |
669 | | #endif |
670 | | |
671 | | /** |
672 | | * Check whether the argument represents a reserved code point, |
673 | | * for Stream Type, Frame Type, Error Code, etc. |
674 | | */ |
675 | | static inline bool |
676 | | http3_is_reserved_code(uint64_t stream_type) |
677 | 0 | { |
678 | 0 | return (stream_type - 0x21) % 0x1f == 0; |
679 | 0 | } |
680 | | |
681 | | /** |
682 | | * Attempt to parse QUIC-encoded variable integer. |
683 | | */ |
684 | | static bool |
685 | | try_get_quic_varint(tvbuff_t *tvb, int offset, uint64_t *value, int *lenvar) |
686 | 0 | { |
687 | 0 | if (tvb_reported_length_remaining(tvb, offset) == 0) { |
688 | 0 | return false; |
689 | 0 | } |
690 | 0 | unsigned len = 1 << (tvb_get_uint8(tvb, offset) >> 6); |
691 | 0 | if (tvb_reported_length_remaining(tvb, offset) < len) { |
692 | 0 | return false; |
693 | 0 | } |
694 | 0 | *lenvar = len; |
695 | 0 | if (value) { |
696 | 0 | unsigned n = tvb_get_varint(tvb, offset, -1, value, ENC_VARINT_QUIC); |
697 | 0 | DISSECTOR_ASSERT_CMPINT(n, ==, len); |
698 | 0 | } |
699 | 0 | return true; |
700 | 0 | } |
701 | | |
702 | | /** |
703 | | * Return the size of entire HTTP/3 frame. |
704 | | */ |
705 | | static int |
706 | | get_http3_frame_size(tvbuff_t *tvb, int offset) |
707 | 0 | { |
708 | 0 | int type_size, length_size; |
709 | 0 | uint64_t frame_length; |
710 | |
|
711 | 0 | if (!try_get_quic_varint(tvb, offset, NULL, &type_size)) { |
712 | 0 | return 0; |
713 | 0 | } |
714 | 0 | offset += type_size; |
715 | |
|
716 | 0 | if (!try_get_quic_varint(tvb, offset, &frame_length, &length_size)) { |
717 | 0 | return 0; |
718 | 0 | } |
719 | | |
720 | 0 | uint64_t frame_size = type_size + length_size + frame_length; |
721 | 0 | if (frame_size > INT32_MAX) { |
722 | | // We do not support such large frames. |
723 | 0 | return 0; |
724 | 0 | } |
725 | 0 | return (int)frame_size; |
726 | 0 | } |
727 | | |
728 | | /** |
729 | | * Check whether the pinfo contains at least one whole HTTP3 frame, |
730 | | * and adjust the pinfo desegmentation settings for the lower |
731 | | * layer (QUIC, generally) to continue the desegmentation process. |
732 | | */ |
733 | | static bool |
734 | | http3_check_frame_size(tvbuff_t *tvb, packet_info *pinfo, int offset) |
735 | 0 | { |
736 | 0 | int frame_size = get_http3_frame_size(tvb, offset); |
737 | 0 | int remaining = tvb_reported_length_remaining(tvb, offset); |
738 | 0 | if (frame_size && frame_size <= remaining) { |
739 | 0 | return true; |
740 | 0 | } |
741 | | |
742 | 0 | pinfo->desegment_offset = offset; |
743 | 0 | pinfo->desegment_len = frame_size ? (frame_size - remaining) : DESEGMENT_ONE_MORE_SEGMENT; |
744 | 0 | return false; |
745 | 0 | } |
746 | | |
747 | | static inline http3_stream_dir |
748 | | http3_packet_get_direction(quic_stream_info *stream_info) |
749 | 0 | { |
750 | 0 | return stream_info->from_server |
751 | 0 | ? FROM_SERVER_TO_CLIENT |
752 | 0 | : FROM_CLIENT_TO_SERVER; |
753 | 0 | } |
754 | | |
755 | | uint64_t* |
756 | | http3_get_stream_id(packet_info *pinfo) |
757 | 0 | { |
758 | 0 | return p_get_proto_data(pinfo->pool, pinfo, hf_http3_stream_id, 0); |
759 | 0 | } |
760 | | |
761 | | static const char* |
762 | | http3_get_request_full_uri(packet_info *pinfo, http3_stream_info_t *http3_stream) |
763 | 0 | { |
764 | 0 | const char* uri = NULL; |
765 | 0 | if (http3_stream->pseudo_headers.authority) { |
766 | | /* "All HTTP/3 requests MUST include exactly one value for the :method, |
767 | | * :scheme, and :path pseudo-header fields, unless the request is a |
768 | | * CONNECT request[.]" |
769 | | * https://www.rfc-editor.org/rfc/rfc9114.html#section-4.3.1-3 |
770 | | */ |
771 | 0 | if (http3_stream->is_connect && !http3_stream->protocol) { |
772 | | /* Both plain CONNECT and CONNECT-UDP use only the `:authority' header. |
773 | | * https://www.rfc-editor.org/rfc/rfc9114.html#connect */ |
774 | 0 | uri = wmem_strdup(pinfo->pool, http3_stream->pseudo_headers.authority); |
775 | 0 | } else { |
776 | | /* The Extended CONNECT uses the standard URL construction |
777 | | * https://www.rfc-editor.org/rfc/rfc8441.html#section-4 */ |
778 | 0 | uri = wmem_strdup_printf(pinfo->pool, "%s://%s%s", |
779 | 0 | http3_stream->pseudo_headers.scheme, |
780 | 0 | http3_stream->pseudo_headers.authority, |
781 | 0 | http3_stream->pseudo_headers.path); |
782 | 0 | } |
783 | 0 | } |
784 | 0 | return uri; |
785 | 0 | } |
786 | | |
787 | | /** |
788 | | * Functions to support decompression of HTTP3 headers. |
789 | | */ |
790 | | #ifdef HAVE_NGHTTP3 |
791 | | /** |
792 | | * File-scoped callback to release resources allocated for the QPACK |
793 | | * decoder. |
794 | | */ |
795 | | static bool |
796 | | qpack_decoder_del_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event _U_, void *user_data) |
797 | | { |
798 | | nghttp3_qpack_decoder_del((nghttp3_qpack_decoder *)user_data); |
799 | | return false; |
800 | | } |
801 | | |
802 | | /** |
803 | | * Memory allocation callbacks for nghttp3_qpack functionality. |
804 | | */ |
805 | | static void * |
806 | | http3_nghttp3_malloc(size_t size, void *user_data _U_) |
807 | | { |
808 | | return wmem_alloc0(wmem_file_scope(), size); |
809 | | } |
810 | | |
811 | | static void |
812 | | http3_nghttp3_free(void *ptr, void *user_data _U_) |
813 | | { |
814 | | wmem_free(wmem_file_scope(), ptr); |
815 | | } |
816 | | |
817 | | static void * |
818 | | http3_nghttp3_calloc(size_t nmemb, size_t size, void *user_data _U_) |
819 | | { |
820 | | return wmem_alloc0(wmem_file_scope(), nmemb * size); |
821 | | } |
822 | | |
823 | | static void * |
824 | | http3_nghttp3_realloc(void *ptr, size_t size, void *user_data _U_) |
825 | | { |
826 | | return wmem_realloc(wmem_file_scope(), ptr, size); |
827 | | } |
828 | | |
829 | | static nghttp3_mem g_qpack_mem_allocator = { |
830 | | .malloc = http3_nghttp3_malloc, |
831 | | .free = http3_nghttp3_free, |
832 | | .calloc = http3_nghttp3_calloc, |
833 | | .realloc = http3_nghttp3_realloc, |
834 | | }; |
835 | | |
836 | | static nghttp3_mem * |
837 | | qpack_mem_allocator(wmem_allocator_t *allocator _U_, int debug _U_) |
838 | | { |
839 | | nghttp3_mem *mem; |
840 | | mem = &g_qpack_mem_allocator; |
841 | | return mem; |
842 | | } |
843 | | |
844 | | /** |
845 | | * Initialization routine for the http3_session object. |
846 | | * Invoked during the creation of the new http3_session. |
847 | | */ |
848 | | static void |
849 | | http3_initialize_qpack_decoders(http3_session_info_t *http3_session) |
850 | | { |
851 | | for (int dir = 0; dir < 2; dir++) { |
852 | | nghttp3_qpack_decoder **pdecoder = (nghttp3_qpack_decoder **)&(http3_session->qpack_decoder[dir]); |
853 | | nghttp3_qpack_decoder_new(pdecoder, QPACK_MAX_DTABLE_SIZE, QPACK_MAX_BLOCKED, |
854 | | qpack_mem_allocator(wmem_file_scope(), 1)); |
855 | | nghttp3_qpack_decoder_set_max_dtable_capacity(*pdecoder, QPACK_MAX_DTABLE_SIZE); |
856 | | wmem_register_callback(wmem_file_scope(), qpack_decoder_del_cb, *pdecoder); |
857 | | } |
858 | | } |
859 | | |
860 | | static GHashTable *header_fields_hash; |
861 | | |
862 | | static const char * |
863 | | cid_to_string(const quic_cid_t *cid, wmem_allocator_t *scope) |
864 | | { |
865 | | if (cid->len == 0) { |
866 | | return "(none)"; |
867 | | } |
868 | | char *str = (char *)wmem_alloc0(scope, 2 * cid->len + 1); |
869 | | bytes_to_hexstr(str, cid->cid, cid->len); |
870 | | return str; |
871 | | } |
872 | | |
873 | | static http3_header_data_t * |
874 | | http3_get_header_data(packet_info *pinfo, tvbuff_t *tvb, unsigned offset) |
875 | | { |
876 | | http3_header_data_t *data, *prev = NULL; |
877 | | |
878 | | unsigned raw_offset = tvb_raw_offset(tvb) + offset; |
879 | | /* The raw offset is relative to the original data source, which is |
880 | | * the decrypted QUIC packet. There can be multiple decrypted QUIC |
881 | | * packets in a single QUIC layer, so this guarantees the same raw |
882 | | * offset from different decrypted data gives different keys. |
883 | | */ |
884 | | int32_t ds_idx = get_data_source_index_by_tvb(pinfo, tvb_get_ds_tvb(tvb)); |
885 | | DISSECTOR_ASSERT(ds_idx >= 0); |
886 | | |
887 | | data = (http3_header_data_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_http3, PROTO_DATA_KEY_HEADER); |
888 | | |
889 | | /* |
890 | | * Attempt to find existing header data block. |
891 | | * In most cases, data will be `NULL' |
892 | | * and this loop won't be visited. |
893 | | */ |
894 | | while (data != NULL) { |
895 | | if (data->offset == raw_offset && data->ds_idx == ds_idx) { |
896 | | /* |
897 | | * We found the matching data. Return it. |
898 | | */ |
899 | | return data; |
900 | | } |
901 | | prev = data; |
902 | | data = data->next; |
903 | | } |
904 | | |
905 | | /* |
906 | | * We did not find header data matching the offset. |
907 | | * Allocate a new header data block, and initialize |
908 | | * the offset marker. |
909 | | */ |
910 | | data = wmem_new0(wmem_file_scope(), http3_header_data_t); |
911 | | data->offset = raw_offset; |
912 | | data->ds_idx = ds_idx; |
913 | | |
914 | | /* |
915 | | * Check whether the newly allocated data should be linked |
916 | | * to the tail of existing header block chain, or whether |
917 | | * it is the head of a new header block chain. |
918 | | */ |
919 | | if (prev != NULL) { |
920 | | prev->next = data; |
921 | | } else { |
922 | | p_add_proto_data(wmem_file_scope(), pinfo, proto_http3, PROTO_DATA_KEY_HEADER, data); |
923 | | } |
924 | | |
925 | | return data; |
926 | | } |
927 | | |
928 | | static http3_qpack_encoder_state_t * |
929 | | http3_get_qpack_encoder_state(packet_info *pinfo, tvbuff_t *tvb, unsigned offset) |
930 | | { |
931 | | http3_qpack_encoder_state_t *data, *prev = NULL; |
932 | | |
933 | | unsigned raw_offset = tvb_raw_offset(tvb) + offset; |
934 | | /* The raw offset is relative to the original data source, which is |
935 | | * the decrypted QUIC packet. There can be multiple decrypted QUIC |
936 | | * packets in a single QUIC layer, so this guarantees the same raw |
937 | | * offset from different decrypted data gives different keys. |
938 | | */ |
939 | | int32_t ds_idx = get_data_source_index_by_tvb(pinfo, tvb_get_ds_tvb(tvb)); |
940 | | DISSECTOR_ASSERT(ds_idx >= 0); |
941 | | |
942 | | data = (http3_qpack_encoder_state_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_http3, PROTO_DATA_KEY_QPACK); |
943 | | |
944 | | /* |
945 | | * Attempt to find existing header data block. |
946 | | * In most cases, data will be `NULL' |
947 | | * and this loop won't be visited. |
948 | | */ |
949 | | while (data != NULL) { |
950 | | if (data->offset == raw_offset && data->ds_idx == ds_idx) { |
951 | | /* |
952 | | * We found the matching data. Return it. |
953 | | */ |
954 | | return data; |
955 | | } |
956 | | prev = data; |
957 | | data = data->next; |
958 | | } |
959 | | |
960 | | /* |
961 | | * We did not find header data matching the offset. |
962 | | * Allocate a new header data block, and initialize |
963 | | * the offset marker. |
964 | | */ |
965 | | data = wmem_new0(wmem_file_scope(), http3_qpack_encoder_state_t); |
966 | | data->offset = raw_offset; |
967 | | data->ds_idx = ds_idx; |
968 | | |
969 | | /* |
970 | | * Check whether the newly allocated data should be linked |
971 | | * to the tail of existing header block chain, or whether |
972 | | * it is the head of a new header block chain. |
973 | | */ |
974 | | if (prev != NULL) { |
975 | | prev->next = data; |
976 | | } else { |
977 | | p_add_proto_data(wmem_file_scope(), pinfo, proto_http3, PROTO_DATA_KEY_QPACK, data); |
978 | | } |
979 | | |
980 | | return data; |
981 | | } |
982 | | |
983 | | static proto_item * |
984 | | try_add_named_header_field(proto_tree *tree, packet_info *pinfo, tvbuff_t *tvb, int offset, uint32_t length, |
985 | | const char *header_name, const char *header_value) |
986 | | { |
987 | | int hf_id; |
988 | | header_field_info *hfi; |
989 | | proto_item *ti = NULL; |
990 | | |
991 | | const int *entry = (const int *)g_hash_table_lookup(header_fields_hash, header_name); |
992 | | if (entry == NULL) { |
993 | | return NULL; |
994 | | } |
995 | | |
996 | | hf_id = *entry; |
997 | | |
998 | | hfi = proto_registrar_get_nth(hf_id); |
999 | | DISSECTOR_ASSERT(hfi != NULL); |
1000 | | |
1001 | | if (FT_IS_UINT32(hfi->type)) { |
1002 | | uint32_t value; |
1003 | | if (ws_strtou32(header_value, NULL, &value)) { |
1004 | | ti = proto_tree_add_uint(tree, hf_id, tvb, offset, length, value); |
1005 | | } |
1006 | | } else if (FT_IS_UINT(hfi->type)) { |
1007 | | uint64_t value; |
1008 | | if (ws_strtou64(header_value, NULL, &value)) { |
1009 | | ti = proto_tree_add_uint64(tree, hf_id, tvb, offset, length, value); |
1010 | | } |
1011 | | } else { |
1012 | | ti = proto_tree_add_item(tree, hf_id, tvb, offset, length, ENC_BIG_ENDIAN); |
1013 | | } |
1014 | | if (hf_id == hf_http3_headers_path) { |
1015 | | http_add_path_components_to_tree(tvb, pinfo, ti, offset, length); |
1016 | | } |
1017 | | return ti; |
1018 | | } |
1019 | | |
1020 | | static void |
1021 | | get_header_field_pstr(wmem_allocator_t *scratch, nghttp3_qpack_nv *header_nv, const char **outp, uint32_t *outlen) |
1022 | | { |
1023 | | char *pstr; /* The returned pstr, always from the cache. */ |
1024 | | uint32_t pstr_len; /* The length of `pstr'. */ |
1025 | | nghttp3_vec namev; /* Vector holding the bytes of the field's name. */ |
1026 | | char *name; /* Typed pointer to field's name. */ |
1027 | | uint32_t name_len; /* Field's name length. */ |
1028 | | nghttp3_vec valuev; /* Vector holding the bytes of the field's value. */ |
1029 | | char *value; /* Typed pointer to field's value. */ |
1030 | | uint32_t value_len; /* Field's value length. */ |
1031 | | |
1032 | | uint8_t *scratch_buffer; |
1033 | | |
1034 | | /* Extract the vectors from `header_nv'. */ |
1035 | | namev = nghttp3_rcbuf_get_buf(header_nv->name); |
1036 | | name = (char *)namev.base; |
1037 | | name_len = (uint32_t)namev.len; |
1038 | | valuev = nghttp3_rcbuf_get_buf(header_nv->value); |
1039 | | value = (char *)valuev.base; |
1040 | | value_len = (uint32_t)valuev.len; |
1041 | | |
1042 | | ws_debug("HTTP header: %.*s: %.*s", name_len, name, value_len, value); |
1043 | | |
1044 | | /* Construct the pstr in the scratch buffer. |
1045 | | * The pstr format is described in the "Header caching scheme" |
1046 | | * comment above. |
1047 | | */ |
1048 | | pstr_len = (4 + name_len) + (4 + value_len); |
1049 | | scratch_buffer = (uint8_t *)wmem_alloc(scratch, pstr_len); |
1050 | | phtonu32(&scratch_buffer[0], name_len); |
1051 | | memcpy(&scratch_buffer[4], name, name_len); |
1052 | | phtonu32(&scratch_buffer[4 + name_len], value_len); |
1053 | | memcpy(&scratch_buffer[4 + name_len + 4], value, value_len); |
1054 | | |
1055 | | /* Check whether the pstr is already in the cache, |
1056 | | * or allocate a new entry. */ |
1057 | | pstr = (char *)wmem_map_lookup(HTTP3_HEADER_CACHE, scratch_buffer); |
1058 | | if (pstr == NULL) { |
1059 | | pstr = (char *)wmem_memdup(wmem_file_scope(), scratch_buffer, pstr_len); |
1060 | | wmem_map_insert(HTTP3_HEADER_CACHE, pstr, pstr); |
1061 | | } |
1062 | | |
1063 | | /* Decrement `nv' reference counts to avoid memory leaks. */ |
1064 | | nghttp3_rcbuf_decref(header_nv->name); |
1065 | | nghttp3_rcbuf_decref(header_nv->value); |
1066 | | |
1067 | | *outp = pstr; |
1068 | | *outlen = pstr_len; |
1069 | | } |
1070 | | |
1071 | | static const char* |
1072 | | http3_get_header_value(packet_info *pinfo, const char* name, bool the_other_direction) { |
1073 | | wmem_list_t *header_data_list; |
1074 | | wmem_list_frame_t *frame; |
1075 | | http3_header_data_t *header_data; |
1076 | | http3_session_info_t *http3_session = http3_session_lookup_or_create(pinfo); |
1077 | | http3_stream_info_t *http3_stream = http3_session->current_stream; |
1078 | | |
1079 | | if (!http3_stream) { |
1080 | | return NULL; |
1081 | | } |
1082 | | |
1083 | | if ((http3_stream->direction && the_other_direction) || (!http3_stream->direction && !the_other_direction)) { |
1084 | | header_data_list = http3_stream->request_header_data; |
1085 | | } else { |
1086 | | header_data_list = http3_stream->response_header_data; |
1087 | | } |
1088 | | |
1089 | | if (!header_data_list) { |
1090 | | return NULL; |
1091 | | } |
1092 | | |
1093 | | for (frame = wmem_list_head(header_data_list); |
1094 | | frame; |
1095 | | frame = wmem_list_frame_next(frame)) |
1096 | | { |
1097 | | header_data = (http3_header_data_t*)wmem_list_frame_data(frame); |
1098 | | if (!header_data) { |
1099 | | continue; |
1100 | | } |
1101 | | for (unsigned i = 0; i < wmem_array_get_count(header_data->header_fields); ++i) { |
1102 | | http3_header_field_t *in; |
1103 | | uint32_t name_len; |
1104 | | in = (http3_header_field_t *)wmem_array_index(header_data->header_fields, i); |
1105 | | name_len = pntohu32(in->decoded.bytes); |
1106 | | if (strlen(name) == name_len && strncmp(in->decoded.bytes + 4, name, name_len) == 0) { |
1107 | | return (const char*)get_ascii_string(pinfo->pool, |
1108 | | (uint8_t*)in->decoded.bytes + 4 + name_len + 4, |
1109 | | pntohu32(in->decoded.bytes + 4 + name_len)); |
1110 | | } |
1111 | | } |
1112 | | } |
1113 | | return NULL; |
1114 | | } |
1115 | | |
1116 | | static void |
1117 | | populate_http3_header_tracking(packet_info *pinfo _U_, http3_stream_info_t *http3_stream, |
1118 | | const char *header_name, const char *header_value) |
1119 | | { |
1120 | | /* HTTP/3 header tracking is simpler than HTTP/2 in that the header |
1121 | | * section is sent as a single HEADERS frame, and the trailer section |
1122 | | * as a single HEADERS frame. |
1123 | | * https://www.rfc-editor.org/rfc/rfc9114.html#section-4.1-4 |
1124 | | * CONTINUATION frames do not exist: |
1125 | | * https://www.rfc-editor.org/rfc/rfc9114.html#appendix-A.2.5-1.20.1 |
1126 | | * Headers may also be carried on PUSH PROMISE frames, which reference |
1127 | | * a push ID instead of a server-initiated stream ID. If PUSH_PROMISE |
1128 | | * is used, if the same push ID occurs in multiple frames, the "header |
1129 | | * sets MUST contain the same fields in the same order, and both the |
1130 | | * name and the value in each field MUST be exact matches." Server push |
1131 | | * is not handled yet. |
1132 | | * |
1133 | | * Thus, we could do some more error checking regarding those conditions. |
1134 | | */ |
1135 | | |
1136 | | /* There are pseudo-header and header values we wish to save to the |
1137 | | * bidirectional stream for access across frames, and values we wish |
1138 | | * to save on a per-direction basis. Right now we don't do the latter, |
1139 | | * only retrieving them from the full header list, but it might be |
1140 | | * worth the optimization to pre-process here. |
1141 | | */ |
1142 | | if (strcmp(header_name, HTTP3_HEADER_NAME_METHOD) == 0) { |
1143 | | http3_stream->pseudo_headers.method = wmem_strdup(wmem_file_scope(), header_value); |
1144 | | if (strcmp(header_value, "CONNECT") == 0) { |
1145 | | /* This is a variant of CONNECT method. |
1146 | | * Supported variants: |
1147 | | * 1. "Plain CONNECT" |
1148 | | * https://www.rfc-editor.org/rfc/rfc7231#section-4.3.6 |
1149 | | * 2. "Extended CONNECT" |
1150 | | * https://www.rfc-editor.org/rfc/rfc9298.html#section-2 |
1151 | | * https://www.rfc-editor.org/rfc/rfc9298.html#section-3.4 |
1152 | | * https://www.rfc-editor.org/rfc/rfc8441.html#section-4 |
1153 | | * |
1154 | | * "Plain CONNECT" utilizes the `:authority' pseudo-header as |
1155 | | * the connection target. |
1156 | | * The "Extended CONNECT" uses the pseudo-headers |
1157 | | * in the same way as other HTTP methods. */ |
1158 | | http3_stream->is_connect = true; |
1159 | | } |
1160 | | } else if (strcmp(header_name, HTTP3_HEADER_NAME_PROTOCOL) == 0) { |
1161 | | http3_stream->pseudo_headers.protocol = wmem_strdup(wmem_file_scope(), header_value); |
1162 | | } else if (strcmp(header_name, HTTP3_HEADER_NAME_PATH) == 0) { |
1163 | | http3_stream->pseudo_headers.path = wmem_strdup(wmem_file_scope(), header_value); |
1164 | | } else if (strcmp(header_name, HTTP3_HEADER_NAME_AUTHORITY) == 0) { |
1165 | | http3_stream->pseudo_headers.authority = wmem_strdup(wmem_file_scope(), header_value); |
1166 | | } else if (strcmp(header_name, HTTP3_HEADER_NAME_SCHEME) == 0) { |
1167 | | http3_stream->pseudo_headers.scheme = wmem_strdup(wmem_file_scope(), header_value); |
1168 | | } else if (strcmp(header_name, HTTP3_HEADER_NAME_STATUS) == 0) { |
1169 | | unsigned status_code; |
1170 | | |
1171 | | status_code = (unsigned)strtoul(header_value, NULL, 10); |
1172 | | http3_stream->pseudo_headers.status = wmem_strdup(wmem_file_scope(), header_value); |
1173 | | http3_stream->pseudo_headers.reason_phrase = val_to_str_const(status_code, vals_http_status_code, "Unknown"); |
1174 | | } |
1175 | | } |
1176 | | |
1177 | | static int |
1178 | | decode_http3_headers(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, unsigned tvb_offset, unsigned offset, |
1179 | | quic_stream_info *stream_info, http3_stream_info_t *http3_stream) |
1180 | | { |
1181 | | http3_header_data_t *header_data; /* The decoded header data block; populated on the first pass. */ |
1182 | | http3_session_info_t *http3_session; /* The corresponding HTTP/3 session. */ |
1183 | | tvbuff_t *header_tvb; /* Composite TVB containing the decoded header fields. */ |
1184 | | unsigned header_len; /* Total length of the decoded header fields. */ |
1185 | | int hoffset; /* Offset of a decoded header in the decoded TVB */ |
1186 | | proto_item *ti; /* Temporary tree item; used in multiple ways when constructing proto trees. */ |
1187 | | http3_pseudo_header_fields_t pseudo_headers; /* Pseudo-header values; populated when building proto trees; used when creating column info. */ |
1188 | | |
1189 | | http3_session = http3_session_lookup_or_create(pinfo); |
1190 | | header_data = http3_get_header_data(pinfo, tvb, offset); |
1191 | | |
1192 | | ws_noisy("pdinfo visited=%d", PINFO_FD_VISITED(pinfo)); |
1193 | | |
1194 | | if (!PINFO_FD_VISITED(pinfo)) { |
1195 | | nghttp3_qpack_decoder *decoder; |
1196 | | int length = 0; |
1197 | | http3_stream_dir packet_direction; |
1198 | | |
1199 | | /* |
1200 | | * This packet has not been processed yet, which means this is |
1201 | | * the first linear scan. We do header decompression only |
1202 | | * once in linear scan and cache the result. If we don't |
1203 | | * cache, already processed data will be fed into decompressor |
1204 | | * again and again since dissector will be called randomly. |
1205 | | * This makes context out-of-sync. |
1206 | | */ |
1207 | | |
1208 | | length = tvb_reported_length_remaining(tvb, tvb_offset); |
1209 | | packet_direction = http3_packet_get_direction(stream_info); |
1210 | | decoder = http3_session->qpack_decoder[packet_direction]; |
1211 | | |
1212 | | DISSECTOR_ASSERT(decoder); |
1213 | | DISSECTOR_ASSERT(header_data); |
1214 | | DISSECTOR_ASSERT(header_data->encoded.bytes == NULL); |
1215 | | DISSECTOR_ASSERT(header_data->encoded.len == 0); |
1216 | | DISSECTOR_ASSERT(header_data->header_fields == NULL); |
1217 | | |
1218 | | header_data->encoded.bytes = tvb_memdup(wmem_file_scope(), tvb, tvb_offset, length); |
1219 | | header_data->encoded.pos = 0; |
1220 | | header_data->encoded.len = length; |
1221 | | |
1222 | | nghttp3_qpack_stream_context *sctx = NULL; |
1223 | | nghttp3_qpack_stream_context_new(&sctx, http3_stream->id, nghttp3_mem_default()); |
1224 | | |
1225 | | ws_debug("Header data: %p %d %d", header_data->encoded.bytes, header_data->encoded.pos, |
1226 | | header_data->encoded.len); |
1227 | | |
1228 | | header_len = 0; |
1229 | | |
1230 | | /* |
1231 | | * Attempt to decode headers. |
1232 | | * |
1233 | | * TODO: This may incorrectly put headers that were blocked |
1234 | | * for packet k in the past to this packet n. We will deal with this later |
1235 | | */ |
1236 | | while (HEADER_BLOCK_ENC_ITER_REMAINING(header_data)) { |
1237 | | nghttp3_qpack_nv nv; |
1238 | | uint8_t flags; |
1239 | | |
1240 | | if (header_len >= QPACK_MAX_HEADER_SIZE) { |
1241 | | header_data->header_size_exceeded = true; |
1242 | | break; |
1243 | | } |
1244 | | |
1245 | | ws_noisy("%p %p:%d decode decoder=%p sctx=%p", header_data->encoded.bytes, |
1246 | | HEADER_BLOCK_ENC_ITER_PTR(header_data), |
1247 | | HEADER_BLOCK_ENC_ITER_REMAINING(header_data), decoder, sctx); |
1248 | | |
1249 | | int32_t nread = (int32_t)nghttp3_qpack_decoder_read_request(decoder, sctx, &nv, &flags, |
1250 | | HEADER_BLOCK_ENC_ITER_PTR(header_data), |
1251 | | HEADER_BLOCK_ENC_ITER_REMAINING(header_data), 1); |
1252 | | /* |
1253 | | * Check for decoding errors. |
1254 | | */ |
1255 | | if (nread < 0) { |
1256 | | header_data->state = HTTP3_HD_DECODER_ERROR; |
1257 | | header_data->error = nread; |
1258 | | ws_debug("Early return nread=%d err=%s", nread, nghttp3_strerror(nread)); |
1259 | | break; |
1260 | | } |
1261 | | |
1262 | | /* |
1263 | | * Check whether the QPACK decoder is blocked on QPACK encoder stream. |
1264 | | */ |
1265 | | if (flags & NGHTTP3_QPACK_DECODE_FLAG_BLOCKED) { |
1266 | | uint64_t wicnt, ricnt; |
1267 | | |
1268 | | header_data->state = HTTP3_HD_DECODER_BLOCKED; |
1269 | | ricnt = nghttp3_qpack_stream_context_get_ricnt(sctx); |
1270 | | wicnt = nghttp3_qpack_decoder_get_icnt(decoder); |
1271 | | ws_debug("Early return nread=%d blocked=%" PRIu8 " ricnt=%" PRIu64 " wicnt=%" PRIu64, |
1272 | | nread, flags, ricnt, wicnt); |
1273 | | break; |
1274 | | } |
1275 | | |
1276 | | /* |
1277 | | * Check whether the decoder has emitted header data. |
1278 | | */ |
1279 | | if (flags & NGHTTP3_QPACK_DECODE_FLAG_EMIT) { |
1280 | | http3_header_field_t *out; |
1281 | | |
1282 | | ws_noisy("Emit nread=%d flags=%" PRIu8 "", nread, flags); |
1283 | | |
1284 | | /* Create an output field and add it to the headers array */ |
1285 | | out = wmem_new0(wmem_file_scope(), http3_header_field_t); |
1286 | | |
1287 | | /* Populate the `encoded' portion. */ |
1288 | | out->encoded.len = nread; |
1289 | | out->encoded.offset = header_data->encoded.pos; |
1290 | | |
1291 | | /* Populate the `decoded' portion. */ |
1292 | | get_header_field_pstr(pinfo->pool, &nv, &out->decoded.bytes, &out->decoded.len); |
1293 | | |
1294 | | /* Add the decoded header field to the header data. */ |
1295 | | if (header_data->header_fields == NULL) { |
1296 | | header_data->header_fields = wmem_array_new(wmem_file_scope(), sizeof(http3_header_field_t)); |
1297 | | } |
1298 | | wmem_array_append(header_data->header_fields, out, 1); |
1299 | | |
1300 | | header_len += out->decoded.len; |
1301 | | |
1302 | | } else { |
1303 | | proto_tree_add_expert_format(tree, pinfo, &ei_http3_header_decoding_no_output, tvb, tvb_offset, 0, |
1304 | | "QPACK - nothing emitted decoder %p ctx %p flags %" PRIu8 " error %d (%s)", |
1305 | | decoder, sctx, flags, nread, nghttp3_strerror((int)nread)); |
1306 | | } |
1307 | | |
1308 | | /* |
1309 | | * Check whether the QPACK decoder has finished. |
1310 | | */ |
1311 | | if (nread == 0 || (flags & NGHTTP3_QPACK_DECODE_FLAG_FINAL)) { |
1312 | | break; |
1313 | | } |
1314 | | |
1315 | | HEADER_BLOCK_ENC_ITER_INC(header_data, nread); |
1316 | | } |
1317 | | nghttp3_qpack_stream_context_del(sctx); |
1318 | | |
1319 | | if (stream_info->from_server) { |
1320 | | if (!http3_stream->response_header_data) { |
1321 | | http3_stream->response_header_data = wmem_list_new(wmem_file_scope()); |
1322 | | } |
1323 | | wmem_list_append(http3_stream->response_header_data, header_data); |
1324 | | } else { |
1325 | | if (!http3_stream->request_header_data) { |
1326 | | http3_stream->request_header_data = wmem_list_new(wmem_file_scope()); |
1327 | | } |
1328 | | wmem_list_append(http3_stream->request_header_data, header_data); |
1329 | | } |
1330 | | } |
1331 | | |
1332 | | /* Check if any expert information fields have to be added. |
1333 | | */ |
1334 | | if (header_data->state == HTTP3_HD_DECODER_ERROR) { |
1335 | | proto_tree_add_expert_format(tree, pinfo, &ei_http3_header_decoding_failed, tvb, tvb_offset, 0, |
1336 | | "QPACK decoding error %d (%s)", |
1337 | | header_data->error, nghttp3_strerror(header_data->error)); |
1338 | | } else if (header_data->state == HTTP3_HD_DECODER_BLOCKED) { |
1339 | | ti = proto_tree_add_boolean(tree, hf_http3_header_qpack_blocked, tvb, tvb_offset, 0, true); |
1340 | | proto_item_set_generated(ti); |
1341 | | proto_tree_add_expert_format(tree, pinfo, &ei_http3_header_decoding_blocked, tvb, tvb_offset, 0, |
1342 | | "QPACK decoding blocked"); |
1343 | | } |
1344 | | |
1345 | | if (header_data->encoded.pos < tvb_offset && header_data->encoded.len != 0) { |
1346 | | proto_tree_add_expert_format(tree, pinfo, &ei_http3_header_encoded_state, tvb, tvb_offset, 0, |
1347 | | "HTTP3 encoded headers - bytes %p pos %d len %d", header_data->encoded.bytes, |
1348 | | header_data->encoded.pos, header_data->encoded.len); |
1349 | | } |
1350 | | |
1351 | | if ((header_data->header_fields == NULL) || (wmem_array_get_count(header_data->header_fields) == 0)) { |
1352 | | return tvb_offset; |
1353 | | } |
1354 | | |
1355 | | /* Header data contains decoded header values; start building the protocol trees. */ |
1356 | | header_tvb = tvb_new_composite(); |
1357 | | header_len = 0; |
1358 | | hoffset = 0; |
1359 | | pseudo_headers = HTTP3_PSEUDO_HEADERS_INITIALIZER; |
1360 | | |
1361 | | for (unsigned i = 0; i < wmem_array_get_count(header_data->header_fields); ++i) { |
1362 | | http3_header_field_t *in; |
1363 | | tvbuff_t *next_tvb; |
1364 | | |
1365 | | in = (http3_header_field_t *)wmem_array_index(header_data->header_fields, i); |
1366 | | header_len += in->decoded.len; |
1367 | | |
1368 | | /* Now setup the tvb buffer to have the new data */ |
1369 | | next_tvb = tvb_new_child_real_data(tvb, (uint8_t*)in->decoded.bytes, in->decoded.len, in->decoded.len); |
1370 | | tvb_composite_append(header_tvb, next_tvb); |
1371 | | } |
1372 | | |
1373 | | tvb_composite_finalize(header_tvb); |
1374 | | add_new_data_source(pinfo, header_tvb, "Decompressed Header"); |
1375 | | |
1376 | | ti = proto_tree_add_uint(tree, hf_http3_headers_decoded_length, header_tvb, hoffset, 1, header_len); |
1377 | | proto_item_set_generated(ti); |
1378 | | if (header_data->header_size_exceeded) { |
1379 | | expert_add_info(pinfo, ti, &ei_http3_header_size); |
1380 | | } |
1381 | | |
1382 | | ti = proto_tree_add_uint(tree, hf_http3_headers_count, header_tvb, hoffset, 1, |
1383 | | wmem_array_get_count(header_data->header_fields)); |
1384 | | proto_item_set_generated(ti); |
1385 | | |
1386 | | wmem_strbuf_t *headers_buf = wmem_strbuf_create(pinfo->pool); |
1387 | | wmem_strbuf_t *header_buf; |
1388 | | |
1389 | | for (unsigned i = 0; i < wmem_array_get_count(header_data->header_fields); ++i) { |
1390 | | http3_header_field_t *in; |
1391 | | proto_item *header; |
1392 | | proto_tree *header_tree; |
1393 | | uint32_t header_name_length; |
1394 | | const char *header_name; |
1395 | | uint32_t header_value_length; |
1396 | | const char *header_value; |
1397 | | |
1398 | | in = (http3_header_field_t *)wmem_array_index(header_data->header_fields, i); |
1399 | | |
1400 | | /* Populate tree with header name/value details. */ |
1401 | | /* Add 'Header' subtree with description. */ |
1402 | | header = proto_tree_add_item(tree, hf_http3_header, tvb, tvb_offset, in->encoded.len, ENC_NA); |
1403 | | |
1404 | | header_tree = proto_item_add_subtree(header, ett_http3_headers); |
1405 | | |
1406 | | /* header value length */ |
1407 | | proto_tree_add_item_ret_uint(header_tree, hf_http3_header_name_length, header_tvb, hoffset, 4, |
1408 | | ENC_BIG_ENDIAN, &header_name_length); |
1409 | | hoffset += 4; |
1410 | | |
1411 | | /* Add header name. */ |
1412 | | proto_tree_add_item_ret_string(header_tree, hf_http3_header_name, header_tvb, hoffset, header_name_length, |
1413 | | ENC_ASCII | ENC_NA, pinfo->pool, (const uint8_t**)&header_name); |
1414 | | hoffset += header_name_length; |
1415 | | |
1416 | | /* header value length */ |
1417 | | proto_tree_add_item_ret_uint(header_tree, hf_http3_header_value_length, header_tvb, hoffset, 4, |
1418 | | ENC_BIG_ENDIAN, &header_value_length); |
1419 | | hoffset += 4; |
1420 | | |
1421 | | /* Add header value. */ |
1422 | | proto_tree_add_item_ret_string(header_tree, hf_http3_header_value, header_tvb, hoffset, header_value_length, |
1423 | | ENC_ASCII | ENC_NA, pinfo->pool, (const uint8_t**)&header_value); |
1424 | | |
1425 | | try_add_named_header_field(header_tree, pinfo, header_tvb, hoffset, header_value_length, |
1426 | | header_name, header_value); |
1427 | | |
1428 | | hoffset += header_value_length; |
1429 | | |
1430 | | /* Create a buffer of one header name and value per line to resemble |
1431 | | * text-based HTTP for adding to Follow Stream. The header_tvb already |
1432 | | * created has the string lengths and lacks newlines. */ |
1433 | | header_buf = wmem_strbuf_new(pinfo->pool, header_name); |
1434 | | wmem_strbuf_append_printf(header_buf, ": %s", header_value); |
1435 | | proto_item_append_text(header, ": %s", wmem_strbuf_get_str(header_buf)); |
1436 | | wmem_strbuf_append_printf(headers_buf, "%s\n", wmem_strbuf_finalize(header_buf)); |
1437 | | |
1438 | | /* Track pseudo-header and header values stored persistently. */ |
1439 | | if (!PINFO_FD_VISITED(pinfo)) { |
1440 | | populate_http3_header_tracking(pinfo, http3_stream, |
1441 | | header_name, header_value); |
1442 | | } |
1443 | | |
1444 | | /* Add special values to the tree for certain pseudo-headers |
1445 | | * and headers, and collect the values for later processing, |
1446 | | * as some depend on each other. */ |
1447 | | if (strcmp(header_name, HTTP3_HEADER_NAME_METHOD) == 0) { |
1448 | | pseudo_headers.method = header_value; |
1449 | | } else if (strcmp(header_name, HTTP3_HEADER_NAME_PROTOCOL) == 0) { |
1450 | | pseudo_headers.protocol = header_value; |
1451 | | } else if (strcmp(header_name, HTTP3_HEADER_NAME_PATH) == 0) { |
1452 | | pseudo_headers.path = header_value; |
1453 | | } else if (strcmp(header_name, HTTP3_HEADER_NAME_AUTHORITY) == 0) { |
1454 | | pseudo_headers.authority = header_value; |
1455 | | } else if (strcmp(header_name, HTTP3_HEADER_NAME_SCHEME) == 0) { |
1456 | | pseudo_headers.scheme = header_value; |
1457 | | } else if (strcmp(header_name, HTTP3_HEADER_NAME_STATUS) == 0) { |
1458 | | unsigned status_code; |
1459 | | |
1460 | | status_code = (unsigned)strtoul(header_value, NULL, 10); |
1461 | | pseudo_headers.status = header_value; |
1462 | | pseudo_headers.reason_phrase = val_to_str_const(status_code, vals_http_status_code, "Unknown"); |
1463 | | proto_item_append_text(header, " %s", pseudo_headers.reason_phrase); |
1464 | | proto_item_append_text(proto_item_get_parent(header), ", %s %s", pseudo_headers.status, pseudo_headers.reason_phrase); |
1465 | | } else if (strcmp(header_name, HTTP3_HEADER_NAME_TRANSFER_ENCODING) == 0) { |
1466 | | /* The Transfer-Encoding header field MUST NOT be used. |
1467 | | * https://www.rfc-editor.org/rfc/rfc9114.html#section-4.1-11 */ |
1468 | | expert_add_info(pinfo, header, &ei_http3_header_transfer_encoding); |
1469 | | } |
1470 | | |
1471 | | tvb_offset += in->encoded.len; |
1472 | | } |
1473 | | |
1474 | | if (have_tap_listener(http3_follow_tap)) { |
1475 | | quic_follow_tap_data_t *follow_data = wmem_new0(pinfo->pool, quic_follow_tap_data_t); |
1476 | | |
1477 | | wmem_strbuf_append(headers_buf, "\n"); |
1478 | | follow_data->tvb = tvb_new_child_real_data(header_tvb, |
1479 | | (const uint8_t*)wmem_strbuf_get_str(headers_buf), |
1480 | | (unsigned)wmem_strbuf_get_len(headers_buf), |
1481 | | (unsigned)wmem_strbuf_get_len(headers_buf)); |
1482 | | follow_data->stream_id = http3_stream->id; |
1483 | | follow_data->from_server = stream_info->from_server; |
1484 | | |
1485 | | tap_queue_packet(http3_follow_tap, pinfo, follow_data); |
1486 | | } |
1487 | | |
1488 | | /* We have finished constructing the tree for the header fields. |
1489 | | * Proceed to determine whether this is a variant of the CONNECT |
1490 | | * method and to update the info column display accordingly. |
1491 | | * |
1492 | | * We do this here, because while pseudo-headers must appear before |
1493 | | * regular headers, there is no guarantee of their internal ordering |
1494 | | * (although some implementations may assume that), and we want to |
1495 | | * add to COL_INFO in a particular order, and only can determine |
1496 | | * the CONNECT variant with both ":protocol" and ":method", etc. |
1497 | | */ |
1498 | | if (pseudo_headers.method != NULL) { |
1499 | | const char *uri; |
1500 | | |
1501 | | if (!PINFO_FD_VISITED(pinfo)) { |
1502 | | if (http3_stream->is_connect && pseudo_headers.protocol) { |
1503 | | http3_stream->protocol = wmem_strdup(wmem_file_scope(), pseudo_headers.protocol); |
1504 | | http3_stream->next_handle = http_upgrade_dissector(http3_stream->protocol); |
1505 | | http3_stream->upgrade_info = wmem_new0(wmem_file_scope(), http_upgrade_info_t); |
1506 | | http3_stream->upgrade_info->server_port = pinfo->destport; |
1507 | | http3_stream->upgrade_info->http_version = 3; |
1508 | | http3_stream->upgrade_info->get_header_value = http3_get_header_value; |
1509 | | } |
1510 | | } |
1511 | | |
1512 | | /* HTTP/1.1 and HTTP/2 only add the method and path here */ |
1513 | | uri = http3_get_request_full_uri(pinfo, http3_stream); |
1514 | | col_append_sep_fstr(pinfo->cinfo, COL_INFO, ": ", "%s %s", |
1515 | | pseudo_headers.method, uri); |
1516 | | } else if (pseudo_headers.status != NULL) { |
1517 | | DISSECTOR_ASSERT(pseudo_headers.reason_phrase); /* Must be filled together with `:status' */ |
1518 | | /* append the status code and the reason phrase (for example, HEADERS: 200 OK) */ |
1519 | | col_append_sep_fstr(pinfo->cinfo, COL_INFO, ": ", "%s %s", |
1520 | | pseudo_headers.status, pseudo_headers.reason_phrase); |
1521 | | } |
1522 | | |
1523 | | return tvb_offset; |
1524 | | } |
1525 | | |
1526 | | #else /* HAVE_NGHTTP3 */ |
1527 | | const char * |
1528 | 0 | http3_get_header_value(packet_info *pinfo _U_, const char* name _U_, bool the_other_direction _U_) { |
1529 | 0 | return NULL; |
1530 | 0 | } |
1531 | | |
1532 | | #endif /* HAVE_NGHTTP3 */ |
1533 | | |
1534 | | static http3_session_info_t * |
1535 | | http3_session_new(void) |
1536 | 0 | { |
1537 | 0 | http3_session_info_t *http3_session; |
1538 | |
|
1539 | 0 | http3_session = wmem_new0(wmem_file_scope(), http3_session_info_t); |
1540 | |
|
1541 | | #ifdef HAVE_NGHTTP3 |
1542 | | http3_initialize_qpack_decoders(http3_session); |
1543 | | #endif |
1544 | |
|
1545 | 0 | return http3_session; |
1546 | 0 | } |
1547 | | |
1548 | | static http3_session_info_t * |
1549 | | http3_session_lookup_or_create(packet_info *pinfo) |
1550 | 0 | { |
1551 | 0 | http3_session_info_t *http3_session; |
1552 | | |
1553 | | /* First, try to look up the session by initial QUIC DCID */ |
1554 | 0 | quic_cid_t initial_dcid = {0}; |
1555 | 0 | if (quic_conn_data_get_conn_client_dcid_initial(pinfo, &initial_dcid)) { |
1556 | | /* Look up the session data in the conn map */ |
1557 | 0 | http3_session = (http3_session_info_t *)wmem_map_lookup(HTTP3_CONN_INFO_MAP, &initial_dcid); |
1558 | 0 | if (http3_session == NULL) { |
1559 | 0 | quic_cid_t *dcid_p = wmem_memdup(wmem_file_scope(), &initial_dcid, sizeof(initial_dcid)); |
1560 | 0 | http3_session = http3_session_new(); |
1561 | 0 | wmem_map_insert(HTTP3_CONN_INFO_MAP, dcid_p, http3_session); |
1562 | 0 | } |
1563 | 0 | } else { |
1564 | | /* Initial DCID can not be found, use the 5-tuple for lookup */ |
1565 | 0 | conversation_t *conversation = find_or_create_conversation(pinfo); |
1566 | 0 | http3_session = (http3_session_info_t *)conversation_get_proto_data(conversation, proto_http3); |
1567 | |
|
1568 | 0 | if (http3_session == NULL) { |
1569 | 0 | http3_session = http3_session_new(); |
1570 | 0 | conversation_add_proto_data(conversation, proto_http3, http3_session); |
1571 | 0 | } |
1572 | 0 | } |
1573 | |
|
1574 | 0 | return http3_session; |
1575 | 0 | } |
1576 | | |
1577 | | #ifdef HAVE_NGHTTP3 |
1578 | | |
1579 | | static conversation_t * |
1580 | | http3_find_inner_conversation(packet_info *pinfo, quic_stream_info *stream_info, http3_stream_info_t *http3_stream, void **ctx) |
1581 | | { |
1582 | | conversation_t *inner_conv = NULL; |
1583 | | |
1584 | | if (stream_info != NULL) { |
1585 | | if (ctx) { |
1586 | | *ctx = pinfo->conv_elements; |
1587 | | } |
1588 | | |
1589 | | wmem_array_t *conversation_elements = wmem_array_new(pinfo->pool, sizeof(conversation_element_t)); |
1590 | | |
1591 | | conversation_element_t h3_stream_addr = { |
1592 | | .type = CE_ADDRESS, |
1593 | | .addr_val = (pinfo->srcport < pinfo->destport) ? pinfo->src : pinfo->dst, |
1594 | | }; |
1595 | | wmem_array_append_one(conversation_elements, h3_stream_addr); |
1596 | | |
1597 | | conversation_element_t h3_stream_port = { |
1598 | | .type = CE_PORT, |
1599 | | .port_val = (pinfo->srcport < pinfo->destport) ? pinfo->srcport : pinfo->destport, |
1600 | | }; |
1601 | | wmem_array_append_one(conversation_elements, h3_stream_port); |
1602 | | |
1603 | | conversation_element_t h3_stream_quic_stream = { |
1604 | | .type = CE_UINT64, |
1605 | | .uint64_val = http3_stream->id, |
1606 | | }; |
1607 | | wmem_array_append_one(conversation_elements, h3_stream_quic_stream); |
1608 | | |
1609 | | conversation_element_t h3_stream_last = { |
1610 | | .type = CE_CONVERSATION_TYPE, |
1611 | | .conversation_type_val = CONVERSATION_LOG, |
1612 | | }; |
1613 | | wmem_array_append_one(conversation_elements, h3_stream_last); |
1614 | | |
1615 | | pinfo->conv_elements = (conversation_element_t *)wmem_array_get_raw(conversation_elements); |
1616 | | inner_conv = find_conversation_pinfo(pinfo, 0); |
1617 | | if (!inner_conv) { |
1618 | | inner_conv = conversation_new_full(pinfo->fd->num, pinfo->conv_elements); |
1619 | | } |
1620 | | } |
1621 | | |
1622 | | return inner_conv; |
1623 | | } |
1624 | | |
1625 | | static void |
1626 | | http3_reset_inner_conversation(packet_info *pinfo, void *ctx) |
1627 | | { |
1628 | | if (ctx) { |
1629 | | struct conversation_element *conv_elements = (struct conversation_element *)ctx; |
1630 | | pinfo->conv_elements = conv_elements; |
1631 | | } |
1632 | | } |
1633 | | |
1634 | | enum body_decompression { |
1635 | | BODY_DECOMPRESSION_NONE, |
1636 | | BODY_DECOMPRESSION_ZLIB, |
1637 | | BODY_DECOMPRESSION_BROTLI, |
1638 | | BODY_DECOMPRESSION_ZSTD, |
1639 | | BODY_DECOMPRESSION_FAIL |
1640 | | }; |
1641 | | |
1642 | | static enum body_decompression |
1643 | | get_body_decompression_info(packet_info *pinfo) |
1644 | | { |
1645 | | const char *content_encoding = http3_get_header_value(pinfo, HTTP3_HEADER_NAME_CONTENT_ENCODING, false); |
1646 | | const char *status = http3_get_header_value(pinfo, HTTP3_HEADER_NAME_STATUS, false); |
1647 | | if (content_encoding == NULL) { |
1648 | | return BODY_DECOMPRESSION_NONE; |
1649 | | } |
1650 | | if (!http3_decompress_body || g_strcmp0(status, HTTP3_HEADER_STATUS_PARTIAL_CONTENT) == 0) { |
1651 | | return BODY_DECOMPRESSION_FAIL; |
1652 | | } |
1653 | | #ifdef USE_ZLIB_OR_ZLIBNG |
1654 | | if (strncmp(content_encoding, "gzip", 4) == 0 || strncmp(content_encoding, "deflate", 7) == 0) { |
1655 | | return BODY_DECOMPRESSION_ZLIB; |
1656 | | } |
1657 | | #endif |
1658 | | #ifdef HAVE_BROTLI |
1659 | | if (strncmp(content_encoding, "br", 2) == 0) { |
1660 | | return BODY_DECOMPRESSION_BROTLI; |
1661 | | } |
1662 | | #endif |
1663 | | #ifdef HAVE_ZSTD |
1664 | | if (strncmp(content_encoding, "zstd", 4) == 0) { |
1665 | | return BODY_DECOMPRESSION_ZSTD; |
1666 | | } |
1667 | | #endif |
1668 | | |
1669 | | return BODY_DECOMPRESSION_FAIL; |
1670 | | } |
1671 | | |
1672 | | static void |
1673 | | dissect_http3_body_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *body_tree, http3_stream_info_t *http3_stream, bool decompression_success) |
1674 | | { |
1675 | | unsigned length = tvb_reported_length(tvb); |
1676 | | http_eo_t *eo_info; |
1677 | | const char *content_type = http3_get_header_value(pinfo, HTTP3_HEADER_NAME_CONTENT_TYPE, false); |
1678 | | |
1679 | | proto_tree_add_item(body_tree, hf_http3_data, tvb, 0, length, ENC_NA); |
1680 | | |
1681 | | if (have_tap_listener(http3_follow_tap)) { |
1682 | | quic_follow_tap_data_t *follow_data = wmem_new0(pinfo->pool, quic_follow_tap_data_t); |
1683 | | |
1684 | | follow_data->tvb = tvb; |
1685 | | follow_data->stream_id = http3_stream->id; |
1686 | | follow_data->from_server = http3_stream->direction; |
1687 | | |
1688 | | tap_queue_packet(http3_follow_tap, pinfo, follow_data); |
1689 | | } |
1690 | | |
1691 | | if (have_tap_listener(http_eo_tap)) { |
1692 | | eo_info = wmem_new0(pinfo->pool, http_eo_t); |
1693 | | |
1694 | | eo_info->filename = http3_stream->pseudo_headers.path; |
1695 | | eo_info->hostname = http3_stream->pseudo_headers.authority; |
1696 | | eo_info->content_type = content_type; |
1697 | | eo_info->payload = tvb; |
1698 | | |
1699 | | tap_queue_packet(http_eo_tap, pinfo, eo_info); |
1700 | | } |
1701 | | |
1702 | | /* If we couldn't, or wouldn't, decompress the data, stop here. */ |
1703 | | if (!decompression_success) |
1704 | | return; |
1705 | | |
1706 | | if (content_type != NULL) { |
1707 | | const char *semicolon = ws_strchrnul(content_type, ';'); |
1708 | | char *media_type = wmem_ascii_strdown(pinfo->pool, content_type, semicolon - content_type); |
1709 | | char *media_type_parameters = NULL; |
1710 | | while (*semicolon && *semicolon == ';' && g_ascii_isspace(*semicolon)) { |
1711 | | ++semicolon; |
1712 | | } |
1713 | | if (*semicolon) { |
1714 | | media_type_parameters = wmem_strdup(pinfo->pool, semicolon); |
1715 | | } |
1716 | | media_content_info_t media_type_metadata = { MEDIA_CONTAINER_HTTP_OTHERS, media_type_parameters, NULL, NULL}; |
1717 | | if (!dissector_try_string_with_data(media_type_dissector_table, media_type, |
1718 | | tvb, pinfo, proto_tree_get_root(body_tree), true, &media_type_metadata)) { |
1719 | | |
1720 | | const char *saved_match_string = pinfo->match_string; |
1721 | | pinfo->match_string = media_type; |
1722 | | call_dissector_with_data(media_handle, tvb, pinfo, proto_tree_get_root(body_tree), &media_type_metadata); |
1723 | | pinfo->match_string = saved_match_string; |
1724 | | } |
1725 | | } |
1726 | | } |
1727 | | |
1728 | | static void |
1729 | | dissect_http3_data_full_body(tvbuff_t *tvb, packet_info *pinfo, proto_tree *http3_tree, http3_stream_info_t *http3_stream) |
1730 | | { |
1731 | | unsigned datalen = tvb_reported_length(tvb); |
1732 | | proto_tree *body_tree = http3_tree; |
1733 | | tvbuff_t *next_tvb = tvb; |
1734 | | |
1735 | | const char *content_encoding = http3_get_header_value(pinfo, HTTP3_HEADER_NAME_CONTENT_ENCODING, false); |
1736 | | enum body_decompression decompression = get_body_decompression_info(pinfo); |
1737 | | bool decompression_success = true; |
1738 | | |
1739 | | if (decompression != BODY_DECOMPRESSION_NONE) { |
1740 | | proto_item *compressed_ti = NULL; |
1741 | | |
1742 | | tvbuff_t *decompressed_tvb = NULL; |
1743 | | switch (decompression) { |
1744 | | case BODY_DECOMPRESSION_ZLIB: |
1745 | | decompressed_tvb = tvb_child_uncompress_zlib(tvb, tvb, 0, datalen); |
1746 | | break; |
1747 | | case BODY_DECOMPRESSION_BROTLI: |
1748 | | decompressed_tvb = tvb_child_uncompress_brotli(tvb, tvb, 0, datalen); |
1749 | | break; |
1750 | | case BODY_DECOMPRESSION_ZSTD: |
1751 | | decompressed_tvb = tvb_child_uncompress_zstd(tvb, tvb, 0, datalen); |
1752 | | break; |
1753 | | default: |
1754 | | break; |
1755 | | } |
1756 | | |
1757 | | compressed_ti = proto_tree_add_none_format(http3_tree, |
1758 | | hf_http3_encoded_entity, tvb, 0, datalen, |
1759 | | "Content-encoded entity body (%s): %u bytes", |
1760 | | content_encoding == NULL ? "unknown" : content_encoding, datalen); |
1761 | | |
1762 | | if (decompressed_tvb) { |
1763 | | unsigned decompressed_length = tvb_reported_length(decompressed_tvb); |
1764 | | add_new_data_source(pinfo, decompressed_tvb, "Decompressed entity body"); |
1765 | | |
1766 | | proto_item_append_text(compressed_ti, " -> %u bytes", decompressed_length); |
1767 | | body_tree = proto_item_add_subtree(compressed_ti, ett_http3_encoded_entity); |
1768 | | next_tvb = decompressed_tvb; |
1769 | | } else { |
1770 | | expert_add_info(pinfo, compressed_ti, &ei_http3_body_decompression_failed); |
1771 | | decompression_success = false; |
1772 | | } |
1773 | | } |
1774 | | |
1775 | | dissect_http3_body_data(next_tvb, pinfo, body_tree, http3_stream, decompression_success); |
1776 | | } |
1777 | | |
1778 | | static void |
1779 | | dissect_http3_data_partial_body(tvbuff_t *tvb, packet_info *pinfo, proto_tree *http3_tree, quic_stream_info *stream_info, http3_stream_info_t *http3_stream) |
1780 | | { |
1781 | | unsigned length; |
1782 | | void *saved_ctx = NULL; |
1783 | | |
1784 | | if (http3_stream->is_connect) { |
1785 | | /* Part of a tunneled CONNECT method. */ |
1786 | | proto_item_append_text(http3_tree, " (tunneled data)"); |
1787 | | } else { |
1788 | | /* Not part of CONNECT, frame that should be reassembled later. */ |
1789 | | proto_item_append_text(http3_tree, " (partial entity body)"); |
1790 | | } |
1791 | | |
1792 | | length = tvb_reported_length(tvb); |
1793 | | /* Adding this seems redundant with http3.frame_payload. */ |
1794 | | proto_tree_add_item(http3_tree, hf_http3_data, tvb, 0, length, ENC_NA); |
1795 | | |
1796 | | if (http3_stream->next_handle) { |
1797 | | /* Extended CONNECT */ |
1798 | | /* inner_conv = */ http3_find_inner_conversation(pinfo, stream_info, http3_stream, &saved_ctx); |
1799 | | http3_stream->upgrade_info->from_server = http3_stream->direction; |
1800 | | call_dissector_only(http3_stream->next_handle, tvb, pinfo, proto_tree_get_root(http3_tree), http3_stream->upgrade_info); |
1801 | | http3_reset_inner_conversation(pinfo, saved_ctx); |
1802 | | } |
1803 | | } |
1804 | | |
1805 | | static bool |
1806 | | should_attempt_to_reassemble_data_content(http3_stream_info_t *http3_stream) |
1807 | | { |
1808 | | /* If this data frame is part of a CONNECT tunnel, don't try to reassemble. |
1809 | | * XXX - This is what HTTP/2 does, but perhaps CONNECT data should also |
1810 | | * use the streaming reassembly mode, once that is implemented. |
1811 | | */ |
1812 | | if (http3_stream->is_connect) { |
1813 | | return false; |
1814 | | } |
1815 | | |
1816 | | return true; |
1817 | | } |
1818 | | |
1819 | | static tvbuff_t* |
1820 | | reassemble_http3_data_into_full_content(tvbuff_t *tvb, packet_info *pinfo, proto_tree *http3_tree, unsigned offset, quic_stream_info *stream_info, http3_stream_info_t *http3_stream, bool fin) |
1821 | | { |
1822 | | if (!should_attempt_to_reassemble_data_content(http3_stream)) { |
1823 | | return NULL; |
1824 | | } |
1825 | | |
1826 | | fragment_head *head = NULL; |
1827 | | unsigned remaining = tvb_captured_length_remaining(tvb, offset); |
1828 | | unsigned content_length; |
1829 | | bool content_length_set = false; |
1830 | | |
1831 | | /* We can re-use the QUIC reassembly functions; we only have one body |
1832 | | * reassembly per request/response (since we only reassemble at FIN |
1833 | | * or when reaching the content-length), so the QUIC stream_info |
1834 | | * has all the necessary information to make the reassembly unique. */ |
1835 | | |
1836 | | /* Are we starting the body defragmentation? */ |
1837 | | if (!PINFO_FD_VISITED(pinfo) && !fragment_get(&http3_body_reassembly_table, pinfo, 0, stream_info)) { |
1838 | | /* If so, do we know the content length */ |
1839 | | const char *content_length_str = http3_get_header_value(pinfo, HTTP3_HEADER_NAME_CONTENT_LENGTH, false); |
1840 | | if (content_length_str && ws_strtou(content_length_str, NULL, &content_length)) { |
1841 | | content_length_set = true; |
1842 | | /* Is this DATA frame the entire content-length? */ |
1843 | | if (remaining == content_length) { |
1844 | | /* Yes; there are no more DATA frames. If FIN isn't set, that |
1845 | | * is because a later QUIC frame with no DATA will have FIN. |
1846 | | * Let's go ahead and desegment now. The reassembly head isn't |
1847 | | * created, so we can't use fragment_set_tot_len, which will |
1848 | | * handle this in the other cases. |
1849 | | * |
1850 | | * XXX - Would it be simpler to have something like |
1851 | | * fragment_start_seq_check? |
1852 | | */ |
1853 | | fin = true; |
1854 | | } |
1855 | | } |
1856 | | } |
1857 | | |
1858 | | /* We might want a fragment_add_next instead of fragment_add_check_next so |
1859 | | * that we can get reassembly errors when the segment is too long instead |
1860 | | * of creating a new reassembly. */ |
1861 | | head = fragment_add_check_next(&http3_body_reassembly_table, tvb, offset, pinfo, 0, stream_info, remaining, !fin); |
1862 | | if (content_length_set) { |
1863 | | fragment_set_tot_len(&http3_body_reassembly_table, pinfo, 0, stream_info, content_length); |
1864 | | } |
1865 | | |
1866 | | if (head) { |
1867 | | if (!PINFO_FD_VISITED(pinfo)) { |
1868 | | /* XXX - Remember that we dissected via DATA frames so we don't need |
1869 | | * to dissect at FIN in a non-DATA frame, e.g. HEADERS or GREASE. */ |
1870 | | } |
1871 | | /* The Info column information is less useful when dissected in the |
1872 | | * same capture file frame, even in a different HTTP/3 DATA frame. */ |
1873 | | if (pinfo->num != head->reassembled_in) { |
1874 | | col_append_frame_number(pinfo, COL_INFO, " [HTTP/3 reassembled in #%u]", |
1875 | | head->reassembled_in); |
1876 | | } |
1877 | | /* This should work now (layer numbers are more stable), but check |
1878 | | * to see if we need to do what HTTP/2 does. */ |
1879 | | return process_reassembled_data(tvb, offset, pinfo, "Reassembled body", |
1880 | | head, &http3_body_fragment_items, NULL, http3_tree); |
1881 | | |
1882 | | #if 0 |
1883 | | proto_tree_add_uint(http3_tree, hf_http3_body_reassembled_in, tvb, 0, |
1884 | | 0, head->reassembled_in); |
1885 | | #endif |
1886 | | } |
1887 | | |
1888 | | return NULL; |
1889 | | } |
1890 | | |
1891 | | static int |
1892 | | dissect_http3_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *http3_tree, unsigned offset _U_, |
1893 | | quic_stream_info *stream_info, http3_stream_info_t *http3_stream, bool fin) |
1894 | | { |
1895 | | /* Padding is not defined in HTTP/3 DATA Frames |
1896 | | * https://www.rfc-editor.org/rfc/rfc9114.html#name-comparison-of-http-2-and-ht |
1897 | | */ |
1898 | | |
1899 | | /* XXX - HTTP/2 supports fake headers for a stream here */ |
1900 | | |
1901 | | /* "Because some messages are large or unbounded, endpoints SHOULD begin |
1902 | | * processing partial HTTP messages once enough of the message has been |
1903 | | * received to make progress." |
1904 | | * https://www.rfc-editor.org/rfc/rfc9114.html#section-4.1-14 |
1905 | | * Not all possible media type subdissectors support defragmentation via |
1906 | | * setting desegment_offset and desegment_len in pinfo, and the necessary |
1907 | | * hooks are not in this dissector. For now, just reassemble everything |
1908 | | * into the full content. |
1909 | | * |
1910 | | * XXX - Cf. what the HTTP/2 dissector does for the streaming reassembly |
1911 | | * mode. |
1912 | | */ |
1913 | | |
1914 | | tvbuff_t *data_tvb = reassemble_http3_data_into_full_content(tvb, pinfo, http3_tree, offset, stream_info, http3_stream, fin); |
1915 | | if (data_tvb) { |
1916 | | dissect_http3_data_full_body(data_tvb, pinfo, http3_tree, http3_stream); |
1917 | | } else { |
1918 | | dissect_http3_data_partial_body(tvb, pinfo, http3_tree, stream_info, http3_stream); |
1919 | | } |
1920 | | |
1921 | | return tvb_reported_length(tvb); |
1922 | | } |
1923 | | #else |
1924 | | static int |
1925 | | dissect_http3_data(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http3_tree, unsigned offset _U_, |
1926 | | quic_stream_info *stream_info _U_, http3_stream_info_t *http3_stream _U_, bool fin _U_) |
1927 | 0 | { |
1928 | 0 | unsigned length = tvb_reported_length(tvb); |
1929 | | /* Adding this seems redundant with http3.frame_payload. */ |
1930 | 0 | proto_tree_add_item(http3_tree, hf_http3_data, tvb, 0, length, ENC_NA); |
1931 | |
|
1932 | 0 | return length; |
1933 | 0 | } |
1934 | | #endif /* HAVE_NGHTTP3 */ |
1935 | | |
1936 | | static unsigned |
1937 | | #ifdef HAVE_NGHTTP3 |
1938 | | dissect_http3_headers(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, unsigned tvb_offset, unsigned offset, |
1939 | | quic_stream_info *stream_info, http3_stream_info_t *http3_stream) |
1940 | | #else |
1941 | | dissect_http3_headers(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, unsigned tvb_offset _U_, unsigned offset _U_, |
1942 | | quic_stream_info *stream_info _U_, http3_stream_info_t *http3_stream) |
1943 | | #endif |
1944 | 0 | { |
1945 | 0 | if (http3_stream->direction == FROM_CLIENT_TO_SERVER) { |
1946 | 0 | if (http3_stream->request_frame_num == 0) { |
1947 | 0 | http3_stream->request_frame_num = pinfo->num; |
1948 | 0 | http3_stream->request_ts = pinfo->abs_ts; |
1949 | 0 | } |
1950 | 0 | } else { |
1951 | 0 | if (http3_stream->response_frame_num == 0) { |
1952 | 0 | http3_stream->response_frame_num = pinfo->num; |
1953 | 0 | } |
1954 | 0 | } |
1955 | | #ifdef HAVE_NGHTTP3 |
1956 | | decode_http3_headers(tvb, pinfo, tree, tvb_offset, offset, stream_info, http3_stream); |
1957 | | #else |
1958 | 0 | if (have_tap_listener(http3_follow_tap)) { |
1959 | 0 | quic_follow_tap_data_t *follow_data = wmem_new0(pinfo->pool, quic_follow_tap_data_t); |
1960 | |
|
1961 | 0 | follow_data->tvb = tvb; |
1962 | 0 | follow_data->stream_id = http3_stream->id; |
1963 | 0 | follow_data->from_server = stream_info->from_server; |
1964 | |
|
1965 | 0 | tap_queue_packet(http3_follow_tap, pinfo, follow_data); |
1966 | 0 | } |
1967 | 0 | #endif |
1968 | 0 | return tvb_reported_length(tvb); |
1969 | 0 | } |
1970 | | |
1971 | | /* Settings */ |
1972 | | static int |
1973 | | dissect_http3_settings(tvbuff_t *tvb, packet_info *pinfo, proto_tree *http3_tree, unsigned offset) |
1974 | 0 | { |
1975 | 0 | uint64_t settingsid, value; |
1976 | 0 | unsigned lenvar; |
1977 | 0 | proto_item *ti_settings, *pi; |
1978 | 0 | proto_tree *settings_tree; |
1979 | |
|
1980 | 0 | while (tvb_reported_length_remaining(tvb, offset) > 0) { |
1981 | 0 | ti_settings = proto_tree_add_item(http3_tree, hf_http3_settings, tvb, offset, 2, ENC_NA); |
1982 | 0 | settings_tree = proto_item_add_subtree(ti_settings, ett_http3_settings); |
1983 | 0 | pi = proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_identifier, tvb, offset, -1, |
1984 | 0 | ENC_VARINT_QUIC, &settingsid, &lenvar); |
1985 | | /* Check if it is a GREASE Settings ID */ |
1986 | 0 | if (http3_is_reserved_code(settingsid)) { |
1987 | 0 | proto_item_set_text(pi, "Settings Identifier: Reserved (%#" PRIx64 ")", settingsid); |
1988 | 0 | proto_item_append_text(ti_settings, " - Reserved (GREASE)"); |
1989 | 0 | } else { |
1990 | 0 | proto_item_append_text(ti_settings, " - %s", |
1991 | 0 | val64_to_str_wmem(pinfo->pool, settingsid, http3_settings_vals, "Unknown (%#" PRIx64 ")")); |
1992 | 0 | } |
1993 | |
|
1994 | 0 | offset += lenvar; |
1995 | 0 | proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_value, tvb, offset, -1, ENC_VARINT_QUIC, NULL, |
1996 | 0 | &lenvar); |
1997 | |
|
1998 | 0 | switch (settingsid) { |
1999 | 0 | case HTTP3_QPACK_MAX_TABLE_CAPACITY: |
2000 | 0 | proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_qpack_max_table_capacity, tvb, offset, -1, |
2001 | 0 | ENC_VARINT_QUIC, &value, &lenvar); |
2002 | 0 | proto_item_append_text(ti_settings, ": %" PRIu64, value); |
2003 | 0 | break; |
2004 | 0 | case HTTP3_SETTINGS_MAX_FIELD_SECTION_SIZE: |
2005 | 0 | proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_max_field_section_size, tvb, offset, -1, |
2006 | 0 | ENC_VARINT_QUIC, &value, &lenvar); |
2007 | 0 | proto_item_append_text(ti_settings, ": %" PRIu64, value); |
2008 | 0 | break; |
2009 | 0 | case HTTP3_QPACK_BLOCKED_STREAMS: |
2010 | 0 | proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_qpack_blocked_streams, tvb, offset, -1, |
2011 | 0 | ENC_VARINT_QUIC, &value, &lenvar); |
2012 | 0 | proto_item_append_text(ti_settings, ": %" PRIu64, value); |
2013 | 0 | break; |
2014 | 0 | case HTTP3_EXTENDED_CONNECT: |
2015 | 0 | proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_extended_connect, tvb, offset, -1, |
2016 | 0 | ENC_VARINT_QUIC, &value, &lenvar); |
2017 | 0 | proto_item_append_text(ti_settings, ": %" PRIu64, value); |
2018 | 0 | break; |
2019 | 0 | case HTTP3_WEBTRANSPORT: |
2020 | 0 | proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_webtransport, tvb, offset, -1, |
2021 | 0 | ENC_VARINT_QUIC, &value, &lenvar); |
2022 | 0 | proto_item_append_text(ti_settings, ": %" PRIu64, value); |
2023 | 0 | break; |
2024 | 0 | case HTTP3_H3_DATAGRAM: |
2025 | 0 | proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_h3_datagram, tvb, offset, -1, |
2026 | 0 | ENC_VARINT_QUIC, &value, &lenvar); |
2027 | 0 | proto_item_append_text(ti_settings, ": %" PRIu64, value); |
2028 | 0 | break; |
2029 | 0 | case HTTP3_H3_DATAGRAM_DRAFT04: |
2030 | 0 | proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_h3_datagram_draft04, tvb, offset, -1, |
2031 | 0 | ENC_VARINT_QUIC, &value, &lenvar); |
2032 | 0 | proto_item_append_text(ti_settings, ": %" PRIu64, value); |
2033 | 0 | break; |
2034 | 0 | default: |
2035 | | /* No Default */ |
2036 | 0 | break; |
2037 | 0 | } |
2038 | 0 | offset += lenvar; |
2039 | 0 | } |
2040 | | |
2041 | 0 | return offset; |
2042 | 0 | } |
2043 | | |
2044 | | /** |
2045 | | * Priority Update |
2046 | | */ |
2047 | | static int |
2048 | | dissect_http3_priority_update(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http3_tree, unsigned offset, |
2049 | | http3_stream_info_t *http3_stream) |
2050 | 0 | { |
2051 | 0 | uint64_t element_id; |
2052 | 0 | unsigned priority_field_value_len; |
2053 | 0 | unsigned lenvar; |
2054 | |
|
2055 | 0 | proto_tree_add_item_ret_varint(http3_tree, hf_http3_priority_update_element_id, tvb, offset, -1, ENC_VARINT_QUIC, |
2056 | 0 | &element_id, &lenvar); |
2057 | 0 | offset += lenvar; |
2058 | 0 | priority_field_value_len = tvb_reported_length_remaining(tvb, offset); |
2059 | |
|
2060 | 0 | proto_tree_add_item(http3_tree, hf_http3_priority_update_field_value, tvb, offset, priority_field_value_len, |
2061 | 0 | ENC_ASCII); |
2062 | |
|
2063 | 0 | if (have_tap_listener(http3_follow_tap)) { |
2064 | 0 | quic_follow_tap_data_t *follow_data = wmem_new0(pinfo->pool, quic_follow_tap_data_t); |
2065 | |
|
2066 | 0 | wmem_strbuf_t *priority_buf = wmem_strbuf_new(pinfo->pool, "priority: "); |
2067 | 0 | tvbuff_t *priority_tvb = tvb_new_composite(); |
2068 | 0 | tvb_composite_append(priority_tvb, |
2069 | 0 | tvb_new_child_real_data(tvb, |
2070 | 0 | (const uint8_t*)wmem_strbuf_get_str(priority_buf), |
2071 | 0 | (unsigned)wmem_strbuf_get_len(priority_buf), |
2072 | 0 | (unsigned)wmem_strbuf_get_len(priority_buf))); |
2073 | 0 | tvb_composite_append(priority_tvb, tvb_new_subset_length(tvb, offset, priority_field_value_len)); |
2074 | 0 | priority_buf = wmem_strbuf_create(pinfo->pool); |
2075 | 0 | wmem_strbuf_append_printf(priority_buf, " [Prioritized Element ID %" PRIu64 "]\n", element_id); |
2076 | 0 | tvb_composite_append(priority_tvb, |
2077 | 0 | tvb_new_child_real_data(tvb, |
2078 | 0 | (const uint8_t*)wmem_strbuf_get_str(priority_buf), |
2079 | 0 | (unsigned)wmem_strbuf_get_len(priority_buf), |
2080 | 0 | (unsigned)wmem_strbuf_get_len(priority_buf))); |
2081 | 0 | tvb_composite_finalize(priority_tvb); |
2082 | 0 | follow_data->tvb = priority_tvb; |
2083 | 0 | follow_data->stream_id = http3_stream->id; |
2084 | 0 | follow_data->from_server = http3_stream->direction; |
2085 | |
|
2086 | 0 | tap_queue_packet(http3_follow_tap, pinfo, follow_data); |
2087 | 0 | } |
2088 | |
|
2089 | 0 | offset += priority_field_value_len; |
2090 | |
|
2091 | 0 | return offset; |
2092 | 0 | } |
2093 | | |
2094 | | static int |
2095 | | dissect_http3_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, quic_stream_info *stream_info, http3_stream_info_t *http3_stream) |
2096 | 0 | { |
2097 | 0 | uint64_t frame_type, frame_length; |
2098 | 0 | unsigned type_length_size, lenvar, payload_length, total_length; |
2099 | 0 | proto_item *ti_ft, *ti_ft_type; |
2100 | 0 | proto_tree *ft_tree; |
2101 | 0 | const char *ft_display_name; |
2102 | 0 | bool fin = false; |
2103 | |
|
2104 | 0 | ti_ft = proto_tree_add_item(tree, hf_http3_frame, tvb, offset, -1, ENC_NA); |
2105 | 0 | ft_tree = proto_item_add_subtree(ti_ft, ett_http3_frame); |
2106 | |
|
2107 | 0 | ti_ft_type = proto_tree_add_item_ret_varint(ft_tree, hf_http3_frame_type, tvb, offset, -1, ENC_VARINT_QUIC, &frame_type, |
2108 | 0 | &lenvar); |
2109 | 0 | offset += lenvar; |
2110 | 0 | type_length_size = lenvar; |
2111 | 0 | if (http3_is_reserved_code(frame_type)) { |
2112 | 0 | proto_item_set_text(ti_ft_type, "Type: Reserved (%#" PRIx64 ")", frame_type); |
2113 | 0 | ft_display_name = "Reserved (GREASE)"; |
2114 | 0 | } else { |
2115 | 0 | ft_display_name = val64_to_str_wmem(pinfo->pool, frame_type, http3_frame_types, "Unknown (%#" PRIx64 ")"); |
2116 | 0 | col_append_sep_str(pinfo->cinfo, COL_INFO, ", ", ft_display_name); |
2117 | 0 | } |
2118 | 0 | proto_tree_add_item_ret_varint(ft_tree, hf_http3_frame_length, tvb, offset, -1, ENC_VARINT_QUIC, &frame_length, |
2119 | 0 | &lenvar); |
2120 | 0 | proto_item_set_text(ti_ft, "%s len=%" PRId64, ft_display_name, frame_length); |
2121 | 0 | offset += lenvar; |
2122 | 0 | type_length_size += lenvar; |
2123 | |
|
2124 | 0 | if (ckd_add(&total_length, type_length_size, frame_length)) { |
2125 | | // There is no way for us to correctly handle these sizes. Most likely |
2126 | | // it is garbage. |
2127 | 0 | return INT32_MAX; |
2128 | 0 | } |
2129 | | |
2130 | 0 | proto_item_set_len(ti_ft, total_length); |
2131 | 0 | payload_length = (unsigned)frame_length; |
2132 | 0 | if (payload_length == 0) { |
2133 | 0 | return offset; |
2134 | 0 | } |
2135 | | |
2136 | 0 | if (stream_info->fin && tvb_reported_length_remaining(tvb, offset) == frame_length) { |
2137 | | // If the QUIC stream is at FIN, then finish reassembly iff this |
2138 | | // is the last HTTP/3 frame within the QUIC STREAM frame. |
2139 | 0 | fin = true; |
2140 | 0 | } |
2141 | 0 | proto_tree_add_item(ft_tree, hf_http3_frame_payload, tvb, offset, payload_length, ENC_NA); |
2142 | |
|
2143 | 0 | switch (frame_type) { |
2144 | 0 | case HTTP3_DATA: { /* TODO: dissect Data Frame */ |
2145 | 0 | tvbuff_t *next_tvb = tvb_new_subset_length(tvb, offset, payload_length); |
2146 | 0 | dissect_http3_data(next_tvb, pinfo, ft_tree, 0, stream_info, http3_stream, fin); |
2147 | 0 | fin = false; |
2148 | 0 | } break; |
2149 | 0 | case HTTP3_HEADERS: { |
2150 | 0 | tvbuff_t *next_tvb = tvb_new_subset_length(tvb, offset, payload_length); |
2151 | 0 | dissect_http3_headers(next_tvb, pinfo, ft_tree, 0, offset, stream_info, http3_stream); |
2152 | 0 | } break; |
2153 | 0 | case HTTP3_CANCEL_PUSH: /* TODO: dissect Cancel_Push Frame */ |
2154 | 0 | break; |
2155 | 0 | case HTTP3_SETTINGS: { /* Settings Frame */ |
2156 | 0 | tvbuff_t *next_tvb = tvb_new_subset_length(tvb, offset, payload_length); |
2157 | 0 | dissect_http3_settings(next_tvb, pinfo, ft_tree, 0); |
2158 | 0 | } break; |
2159 | 0 | case HTTP3_PUSH_PROMISE: /* TODO: dissect Push_Promise_Frame */ |
2160 | 0 | break; |
2161 | 0 | case HTTP3_GOAWAY: /* TODO: dissect Goaway Frame */ |
2162 | 0 | break; |
2163 | 0 | case HTTP3_MAX_PUSH_ID: /* TODO: dissect Max_Push_ID Frame */ |
2164 | 0 | break; |
2165 | 0 | case HTTP3_PRIORITY_UPDATE_REQUEST_STREAM: |
2166 | | /* FALLTHROUGH */ |
2167 | 0 | case HTTP3_PRIORITY_UPDATE_PUSH_STREAM: { /* Priority_Update Frame */ |
2168 | 0 | tvbuff_t *next_tvb = tvb_new_subset_length(tvb, offset, payload_length); |
2169 | 0 | dissect_http3_priority_update(next_tvb, pinfo, ft_tree, 0, http3_stream); |
2170 | 0 | } break; |
2171 | 0 | default: /* TODO: add expert advice (remember to treat GREASE differently) */ |
2172 | 0 | break; |
2173 | 0 | } |
2174 | | |
2175 | | #ifdef HAVE_NGHTTP3 |
2176 | | if (fin) { |
2177 | | fragment_head *head = NULL; |
2178 | | head = fragment_end_seq_next(&http3_body_reassembly_table, pinfo, 0, stream_info); |
2179 | | |
2180 | | if (head) { |
2181 | | tvbuff_t *reassembled_data = process_reassembled_data(tvb, offset, pinfo, "Reassembled body", |
2182 | | head, &http3_body_fragment_items, NULL, ft_tree); |
2183 | | if (reassembled_data) |
2184 | | dissect_http3_data_full_body(reassembled_data, pinfo, ft_tree, http3_stream); |
2185 | | } |
2186 | | } |
2187 | | #endif |
2188 | | |
2189 | 0 | offset += payload_length; |
2190 | 0 | return offset; |
2191 | 0 | } |
2192 | | |
2193 | | static void |
2194 | | report_unknown_stream_type(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, |
2195 | | quic_stream_info *stream_info, http3_stream_info_t *http3_stream) |
2196 | 0 | { |
2197 | | /* |
2198 | | * https://www.rfc-editor.org/rfc/rfc9114.html#name-unidirectional-streams |
2199 | | * |
2200 | | * "If the stream header indicates a stream type which is not supported by |
2201 | | * the recipient, the remainder of the stream cannot be consumed as the |
2202 | | * semantics are unknown." |
2203 | | */ |
2204 | 0 | proto_tree_add_expert_format(tree, pinfo, &ei_http3_unknown_stream_type, tvb, offset, 0, |
2205 | 0 | "Unknown stream type %#" PRIx64 " on Stream ID %#" PRIx64, |
2206 | 0 | http3_stream->uni_stream_type, stream_info->stream_id); |
2207 | 0 | } |
2208 | | |
2209 | | /** |
2210 | | * https://www.rfc-editor.org/rfc/rfc7541#section-5.1 |
2211 | | * via |
2212 | | * https://www.rfc-editor.org/rfc/rfc9204.html#name-prefixed-integers |
2213 | | * |
2214 | | * Read a QPACK varint value, return number of consumed bytes, including the prefix byte. |
2215 | | * |
2216 | | * Optionally return the value of the one-bit flag that precedes the QPACK prefixed integer. |
2217 | | * |
2218 | | * Such flag is is interpreted differently, depending on the context: |
2219 | | * - If the prefixed integer represents length of a string literal, the flag value |
2220 | | * indicates that the following string literal is encoded using Huffman code. |
2221 | | * See https://www.rfc-editor.org/rfc/rfc7541#section-5.2 for details. |
2222 | | * - If the prefixed integer represents a name index, the flag value indicates |
2223 | | * that the following name index belongs to the static/dynamic table. |
2224 | | * See https://www.rfc-editor.org/rfc/rfc9204.html#name-insert-with-name-reference |
2225 | | * for details. |
2226 | | */ |
2227 | 0 | #define HTTP3_QPACK_MAX_SHIFT 62 |
2228 | 0 | #define HTTP3_QPACK_MAX_INT ((1ull << HTTP3_QPACK_MAX_SHIFT) - 1) |
2229 | | |
2230 | | static unsigned |
2231 | | read_qpack_prefixed_integer(tvbuff_t *tvb, unsigned offset, unsigned prefix, |
2232 | | uint64_t *out_result, bool *out_flag) |
2233 | 0 | { |
2234 | | /* |
2235 | | * This can throw a ReportedBoundError; in fact, we count on that |
2236 | | * currently in order to detect QPACK fields split across packets. |
2237 | | */ |
2238 | 0 | uint64_t k = (uint8_t)((1 << prefix) - 1); |
2239 | 0 | uint64_t n = 0; |
2240 | 0 | uint64_t add = 0; |
2241 | 0 | uint64_t shift = 0; |
2242 | 0 | uint8_t byte; |
2243 | 0 | unsigned start_offset = offset; |
2244 | |
|
2245 | 0 | byte = tvb_get_uint8(tvb, offset); |
2246 | |
|
2247 | 0 | if (out_flag) { |
2248 | 0 | *out_flag = byte & (1 << prefix); |
2249 | 0 | } |
2250 | |
|
2251 | 0 | if ((byte & k) != k) { |
2252 | 0 | *out_result = byte & k; |
2253 | 0 | return 1; |
2254 | 0 | } |
2255 | | |
2256 | 0 | n = k; |
2257 | |
|
2258 | 0 | for (++offset; tvb_captured_length_remaining(tvb, offset); ++offset, shift += 7) { |
2259 | 0 | byte = tvb_get_uint8(tvb, offset); |
2260 | 0 | add = byte & 0x7f; |
2261 | 0 | if (shift > HTTP3_QPACK_MAX_SHIFT) { |
2262 | 0 | return 0; |
2263 | 0 | } |
2264 | 0 | if ((HTTP3_QPACK_MAX_INT >> shift) < add) { |
2265 | 0 | return 0; |
2266 | 0 | } |
2267 | 0 | add <<= shift; |
2268 | 0 | if (HTTP3_QPACK_MAX_INT - add < n) { |
2269 | 0 | return 0; |
2270 | 0 | } |
2271 | | |
2272 | 0 | n += add; |
2273 | |
|
2274 | 0 | if ((byte & (1 << 7)) == 0) { |
2275 | 0 | break; |
2276 | 0 | } |
2277 | 0 | } |
2278 | | |
2279 | 0 | *out_result = n; |
2280 | | |
2281 | | /* Otherwise, consume extra byte and mark the fin output param */ |
2282 | 0 | return offset - start_offset + 1; |
2283 | 0 | } |
2284 | | |
2285 | | static int |
2286 | | dissect_http3_qpack_encoder_stream(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, |
2287 | | int start_offset, http3_stream_info_t *http3_stream, int *picnt) |
2288 | 0 | { |
2289 | 0 | unsigned end_offset; /* Sentinel offset past the buffer. */ |
2290 | 0 | tvbuff_t *decoded_tvb; /* TVB with the result of decoding the Huffman-encoding strings */ |
2291 | 0 | volatile bool can_continue; /* Flag to indicate that we can parse the next QPACK instruction */ |
2292 | 0 | volatile int icnt; /* Number of dissected QPACK instructions */ |
2293 | 0 | volatile unsigned offset; /* Current parsing offset, relative to the start of the tvb */ |
2294 | |
|
2295 | 0 | can_continue = true; |
2296 | 0 | icnt = 0; |
2297 | 0 | offset = start_offset; |
2298 | 0 | end_offset = start_offset + tvb_captured_length_remaining(tvb, start_offset); |
2299 | 0 | wmem_strbuf_t *follow_buf = wmem_strbuf_create(pinfo->pool); |
2300 | 0 | wmem_strbuf_t *instr_buf; |
2301 | |
|
2302 | 0 | while (offset < end_offset && can_continue) { |
2303 | 0 | int inst_offset; /* Starting offset of the currently parsed instruction in the tvb */ |
2304 | 0 | int inst_len; /* Total length of the instruction */ |
2305 | 0 | unsigned varint_len; |
2306 | |
|
2307 | 0 | proto_item *opcode_ti = NULL, *huffman_ti; |
2308 | 0 | proto_tree *opcode_tree; |
2309 | |
|
2310 | 0 | inst_offset = offset; |
2311 | |
|
2312 | 0 | TRY { |
2313 | 0 | uint8_t opcode; /* The instruction opcode */ |
2314 | |
|
2315 | 0 | opcode = tvb_get_uint8(tvb, inst_offset) & QPACK_OPCODE_MASK; |
2316 | |
|
2317 | 0 | ws_noisy("Decoding opcode=%" PRIu8 " decoded=%d start=%d current=%d end=%d", |
2318 | 0 | opcode, (offset - start_offset), start_offset, offset, end_offset); |
2319 | |
|
2320 | 0 | if (opcode & QPACK_OPCODE_INSERT_INDEXED) { |
2321 | 0 | unsigned name_idx_len = 0; |
2322 | 0 | uint64_t name_idx = 0; |
2323 | 0 | unsigned val_offset = 0; |
2324 | 0 | uint64_t val_len = 0; |
2325 | 0 | bool val_huffman = false; |
2326 | 0 | const uint8_t *val_str = NULL; |
2327 | | |
2328 | | /* https://datatracker.ietf.org/doc/html/rfc9204#name-insert-with-name-reference |
2329 | | * |
2330 | | * 0 1 2 3 4 5 6 7 |
2331 | | * +---+---+---+---+---+---+---+---+ |
2332 | | * | 1 | T | Name Index (6+) | |
2333 | | * +---+---+-----------------------+ |
2334 | | * | H | Value Length (7+) | |
2335 | | * +---+---------------------------+ |
2336 | | * | Value String (Length bytes) | |
2337 | | * +-------------------------------+ |
2338 | | */ |
2339 | | |
2340 | | /* Read the 6-encoded name index len */ |
2341 | 0 | name_idx_len = read_qpack_prefixed_integer(tvb, offset, 6, &name_idx, NULL); |
2342 | 0 | if (name_idx_len == 0) { |
2343 | 0 | THROW(ScsiBoundsError); |
2344 | 0 | } |
2345 | 0 | offset += name_idx_len; |
2346 | | |
2347 | | /* Read the 7-encoded value len and set the value offset for subsequent dissection */ |
2348 | 0 | varint_len = read_qpack_prefixed_integer(tvb, offset, 7, &val_len, &val_huffman); |
2349 | 0 | if (varint_len == 0) { |
2350 | 0 | THROW(ScsiBoundsError); |
2351 | 0 | } |
2352 | | /* XXX - If val_len > UINT32_MAX, fail with expert info? */ |
2353 | 0 | offset += varint_len; |
2354 | 0 | val_offset = offset; |
2355 | 0 | offset += (uint32_t)val_len; |
2356 | | |
2357 | | /* Update the instruction length */ |
2358 | 0 | inst_len = offset - inst_offset; |
2359 | | |
2360 | | /* Add the protocol tree items */ |
2361 | 0 | opcode_ti = proto_tree_add_item(tree, hf_http3_qpack_encoder_opcode_insert_indexed, |
2362 | 0 | tvb, inst_offset,inst_len, ENC_NA); |
2363 | 0 | opcode_tree = proto_item_add_subtree(opcode_ti, ett_http3_qpack_opcode); |
2364 | 0 | proto_tree_add_uint64(opcode_tree, hf_http3_qpack_encoder_opcode_insert_indexed_ref, |
2365 | 0 | tvb, inst_offset, inst_len, name_idx); |
2366 | |
|
2367 | 0 | if (val_huffman) { |
2368 | 0 | huffman_ti = proto_tree_add_item(opcode_tree, hf_http3_qpack_encoder_opcode_insert_indexed_hval, |
2369 | 0 | tvb, val_offset, (uint32_t)val_len, ENC_NA); |
2370 | 0 | decoded_tvb = tvb_child_uncompress_hpack_huff(tvb, val_offset, (unsigned)val_len); |
2371 | 0 | if (decoded_tvb) { |
2372 | 0 | add_new_data_source(pinfo, decoded_tvb, "Decoded QPACK Value"); |
2373 | 0 | proto_tree_add_item_ret_string(opcode_tree, hf_http3_qpack_encoder_opcode_insert_indexed_val, |
2374 | 0 | decoded_tvb, 0, tvb_captured_length(decoded_tvb), ENC_NA, pinfo->pool, &val_str); |
2375 | 0 | } else { |
2376 | 0 | expert_add_info(pinfo, huffman_ti, &ei_http3_huffman_failed); |
2377 | 0 | val_str = (const uint8_t*)"???"; |
2378 | 0 | } |
2379 | 0 | } else { |
2380 | 0 | proto_tree_add_item_ret_string(opcode_tree, hf_http3_qpack_encoder_opcode_insert_indexed_val, |
2381 | 0 | tvb, val_offset, (uint32_t)val_len, ENC_NA, pinfo->pool, &val_str); |
2382 | 0 | } |
2383 | 0 | instr_buf = wmem_strbuf_new(pinfo->pool, "INSERT_INDEXED"); |
2384 | 0 | wmem_strbuf_append_printf(instr_buf, "[%" PRIu64 "=%s]", name_idx, val_str); |
2385 | 0 | proto_item_set_text(opcode_ti, "%s", wmem_strbuf_get_str(instr_buf)); |
2386 | 0 | wmem_strbuf_append_printf(follow_buf, "%s\n", wmem_strbuf_finalize(instr_buf)); |
2387 | 0 | } else if (opcode & QPACK_OPCODE_INSERT) { |
2388 | 0 | unsigned name_offset = 0; |
2389 | 0 | uint64_t name_len = 0; |
2390 | 0 | bool name_huffman = false; |
2391 | 0 | const uint8_t *name_str = NULL; |
2392 | 0 | bool val_huffman = false; |
2393 | 0 | unsigned val_offset = 0; |
2394 | 0 | uint64_t val_len = 0; |
2395 | 0 | const uint8_t *val_str = NULL; |
2396 | | |
2397 | | |
2398 | | /* https://datatracker.ietf.org/doc/html/rfc9204#name-insert-with-literal-name |
2399 | | * |
2400 | | * 0 1 2 3 4 5 6 7 |
2401 | | * +---+---+---+---+---+---+---+---+ |
2402 | | * | 0 | 1 | H | Name Length (5+) | |
2403 | | * +---+---+---+-------------------+ |
2404 | | * | Name String (Length bytes) | |
2405 | | * +---+---------------------------+ |
2406 | | * | H | Value Length (7+) | |
2407 | | * +---+---------------------------+ |
2408 | | * | Value String (Length bytes) | |
2409 | | * +-------------------------------+ |
2410 | | */ |
2411 | | |
2412 | | /* Read the 5-encoded name length and set the name offset for subsequent dissection */ |
2413 | 0 | varint_len = read_qpack_prefixed_integer(tvb, offset, 5, &name_len, &name_huffman); |
2414 | 0 | if (varint_len == 0) { |
2415 | 0 | THROW(ScsiBoundsError); |
2416 | 0 | } |
2417 | | /* XXX - If name_len > UINT32_MAX, fail with expert info? */ |
2418 | 0 | offset += varint_len; |
2419 | 0 | name_offset = offset; |
2420 | 0 | offset += (uint32_t)name_len; |
2421 | | |
2422 | | /* Read the 7-encoded value length and set the value offset for subsequent dissection */ |
2423 | 0 | varint_len = read_qpack_prefixed_integer(tvb, offset, 7, &val_len, &val_huffman); |
2424 | 0 | if (varint_len == 0) { |
2425 | 0 | THROW(ScsiBoundsError); |
2426 | 0 | } |
2427 | | /* XXX - If val_len > UINT32_MAX, fail with expert info? */ |
2428 | 0 | offset += varint_len; |
2429 | 0 | val_offset = offset; |
2430 | 0 | offset += (uint32_t)val_len; |
2431 | | |
2432 | | /* Update the instruction length */ |
2433 | 0 | inst_len = offset - inst_offset; |
2434 | | |
2435 | | /* Add the protocol tree items */ |
2436 | 0 | opcode_ti = proto_tree_add_item(tree, hf_http3_qpack_encoder_opcode_insert, |
2437 | 0 | tvb, inst_offset, inst_len, ENC_NA); |
2438 | 0 | opcode_tree = proto_item_add_subtree(opcode_ti, ett_http3_qpack_opcode); |
2439 | |
|
2440 | 0 | if (name_huffman) { |
2441 | 0 | huffman_ti = proto_tree_add_item(opcode_tree, hf_http3_qpack_encoder_opcode_insert_hname, |
2442 | 0 | tvb, name_offset, (uint32_t)name_len, ENC_NA); |
2443 | 0 | decoded_tvb = tvb_child_uncompress_hpack_huff(tvb, name_offset, (unsigned)name_len); |
2444 | 0 | if (decoded_tvb) { |
2445 | 0 | add_new_data_source(pinfo, decoded_tvb, "Decoded QPACK Name"); |
2446 | 0 | proto_tree_add_item_ret_string(opcode_tree, hf_http3_qpack_encoder_opcode_insert_name, |
2447 | 0 | decoded_tvb, 0, tvb_captured_length(decoded_tvb), ENC_NA, pinfo->pool, &name_str); |
2448 | 0 | } else { |
2449 | 0 | expert_add_info(pinfo, huffman_ti, &ei_http3_huffman_failed); |
2450 | 0 | name_str = (const uint8_t*)"???"; |
2451 | 0 | } |
2452 | 0 | } else { |
2453 | 0 | proto_tree_add_item_ret_string(opcode_tree, hf_http3_qpack_encoder_opcode_insert_name, |
2454 | 0 | tvb, name_offset,(uint32_t)name_len, ENC_NA, pinfo->pool, &name_str); |
2455 | 0 | } |
2456 | |
|
2457 | 0 | if (val_huffman) { |
2458 | 0 | huffman_ti = proto_tree_add_item(opcode_tree, hf_http3_qpack_encoder_opcode_insert_hval, |
2459 | 0 | tvb, val_offset,(uint32_t)val_len, ENC_NA); |
2460 | 0 | decoded_tvb = tvb_child_uncompress_hpack_huff(tvb, val_offset, (unsigned)val_len); |
2461 | 0 | if (decoded_tvb) { |
2462 | 0 | add_new_data_source(pinfo, decoded_tvb, "Decoded QPACK Value"); |
2463 | 0 | proto_tree_add_item_ret_string(opcode_tree, hf_http3_qpack_encoder_opcode_insert_val, |
2464 | 0 | decoded_tvb, 0, tvb_captured_length(decoded_tvb), ENC_NA, pinfo->pool, &val_str); |
2465 | 0 | } else { |
2466 | 0 | expert_add_info(pinfo, huffman_ti, &ei_http3_huffman_failed); |
2467 | 0 | val_str = (const uint8_t*)"???"; |
2468 | 0 | } |
2469 | 0 | } else { |
2470 | 0 | proto_tree_add_item_ret_string(opcode_tree, hf_http3_qpack_encoder_opcode_insert_val, |
2471 | 0 | tvb, val_offset,(uint32_t)val_len, ENC_NA, pinfo->pool, &val_str); |
2472 | 0 | } |
2473 | |
|
2474 | 0 | instr_buf = wmem_strbuf_new(pinfo->pool, "INSERT"); |
2475 | 0 | wmem_strbuf_append_printf(instr_buf, "[%s=%s]", name_str, val_str); |
2476 | 0 | proto_item_set_text(opcode_ti, "%s", wmem_strbuf_get_str(instr_buf)); |
2477 | 0 | wmem_strbuf_append_printf(follow_buf, "%s\n", wmem_strbuf_finalize(instr_buf)); |
2478 | 0 | } else if (opcode & QPACK_OPCODE_SET_DTABLE_CAP) { |
2479 | 0 | uint64_t dynamic_capacity = 0; |
2480 | | |
2481 | | /* https://datatracker.ietf.org/doc/html/rfc9204#name-set-dynamic-table-capacity |
2482 | | * |
2483 | | * 0 1 2 3 4 5 6 7 |
2484 | | * +---+---+---+---+---+---+---+---+ |
2485 | | * | 0 | 0 | 1 | Capacity (5+) | |
2486 | | * +---+---+---+-------------------+ |
2487 | | */ |
2488 | | |
2489 | | /* Read the 5-encoded table capacity */ |
2490 | 0 | varint_len = read_qpack_prefixed_integer(tvb, offset, 5, &dynamic_capacity, NULL); |
2491 | 0 | if (varint_len == 0) { |
2492 | 0 | THROW(ScsiBoundsError); |
2493 | 0 | } |
2494 | 0 | offset += varint_len; |
2495 | | |
2496 | | /* Update the instruction length */ |
2497 | 0 | inst_len = offset - inst_offset; |
2498 | | |
2499 | | /* Add the protocol tree items */ |
2500 | 0 | opcode_ti = proto_tree_add_item(tree, hf_http3_qpack_encoder_opcode_dtable_cap, |
2501 | 0 | tvb, inst_offset,inst_len, ENC_NA); |
2502 | 0 | opcode_tree = proto_item_add_subtree(opcode_ti, ett_http3_qpack_opcode); |
2503 | 0 | proto_tree_add_uint64(opcode_tree, hf_http3_qpack_encoder_opcode_dtable_cap_val, |
2504 | 0 | tvb, inst_offset, inst_len,dynamic_capacity); |
2505 | |
|
2506 | 0 | instr_buf = wmem_strbuf_new(pinfo->pool, "DTABLE_CAP"); |
2507 | 0 | wmem_strbuf_append_printf(instr_buf, "[%" PRIu64 "]", dynamic_capacity); |
2508 | 0 | proto_item_set_text(opcode_ti, "%s", wmem_strbuf_get_str(instr_buf)); |
2509 | 0 | wmem_strbuf_append_printf(follow_buf, "%s\n", wmem_strbuf_finalize(instr_buf)); |
2510 | 0 | } else if (opcode == QPACK_OPCODE_DUPLICATE) { |
2511 | 0 | uint64_t duplicate_of = 0; |
2512 | | |
2513 | | /* https://datatracker.ietf.org/doc/html/rfc9204#name-duplicate |
2514 | | * |
2515 | | * 0 1 2 3 4 5 6 7 |
2516 | | * +---+---+---+---+---+---+---+---+ |
2517 | | * | 0 | 0 | 0 | Index (5+) | |
2518 | | * +---+---+---+-------------------+ |
2519 | | */ |
2520 | | |
2521 | | /* Read the 5-encoded index of a duplicate instruction */ |
2522 | 0 | varint_len = read_qpack_prefixed_integer(tvb, offset, 5, &duplicate_of, NULL); |
2523 | 0 | if (varint_len == 0) { |
2524 | 0 | THROW(ScsiBoundsError); |
2525 | 0 | } |
2526 | 0 | offset += varint_len; |
2527 | | |
2528 | | /* Update the instruction length */ |
2529 | 0 | inst_len = offset - inst_offset; |
2530 | | |
2531 | | /* Add the protocol tree items */ |
2532 | 0 | proto_tree_add_item(tree, hf_http3_qpack_encoder_opcode_duplicate, |
2533 | 0 | tvb, inst_offset, inst_len, ENC_NA); |
2534 | |
|
2535 | 0 | instr_buf = wmem_strbuf_new(pinfo->pool, "DUPLICATE"); |
2536 | 0 | wmem_strbuf_append_printf(instr_buf, "[%" PRIu64 "]", duplicate_of); |
2537 | 0 | proto_item_set_text(opcode_ti, "%s", wmem_strbuf_get_str(instr_buf)); |
2538 | 0 | wmem_strbuf_append_printf(follow_buf, "%s\n", wmem_strbuf_finalize(instr_buf)); |
2539 | 0 | } else { |
2540 | 0 | ws_debug("Opcode=%" PRIu8 ": UNKNOWN", opcode); |
2541 | 0 | can_continue = false; |
2542 | 0 | } |
2543 | | /* Increment the instruction count */ |
2544 | 0 | icnt ++; |
2545 | 0 | } |
2546 | 0 | CATCH(ScsiBoundsError) { |
2547 | | /* This is obviously not SCSI, but we use a bounds type error |
2548 | | * which the main API won't throw. This too large integer is |
2549 | | * possibly a QUIC reassembly error or the payload not actually |
2550 | | * being HTTP/3. */ |
2551 | 0 | proto_tree_add_expert(tree, pinfo, &ei_http3_prefix_int_failed, tvb, offset, 1); |
2552 | | /* Above won't throw an exception because we would have thrown |
2553 | | * an error retrieving the first octet of the prefixed integer. */ |
2554 | 0 | offset = end_offset; |
2555 | 0 | can_continue = false; |
2556 | 0 | } |
2557 | 0 | CATCH(ReportedBoundsError) { |
2558 | | /* We could not parse the last instruction, hence update `decoded' accordingly. */ |
2559 | 0 | ws_debug("%u: Could not parse last instruction, rolling back parsing offset from %d to %d", |
2560 | 0 | pinfo->num, offset, inst_offset); |
2561 | 0 | offset = inst_offset; |
2562 | 0 | can_continue = false; |
2563 | 0 | } |
2564 | 0 | ENDTRY; |
2565 | 0 | } |
2566 | |
|
2567 | 0 | if (icnt && have_tap_listener(http3_follow_tap)) { |
2568 | 0 | quic_follow_tap_data_t *follow_data = wmem_new0(pinfo->pool, quic_follow_tap_data_t); |
2569 | |
|
2570 | 0 | follow_data->tvb = tvb_new_child_real_data(tvb, |
2571 | 0 | (const uint8_t*)wmem_strbuf_get_str(follow_buf), |
2572 | 0 | (unsigned)wmem_strbuf_get_len(follow_buf), |
2573 | 0 | (unsigned)wmem_strbuf_get_len(follow_buf)); |
2574 | 0 | follow_data->stream_id = http3_stream->id; |
2575 | 0 | follow_data->from_server = http3_stream->direction; |
2576 | |
|
2577 | 0 | tap_queue_packet(http3_follow_tap, pinfo, follow_data); |
2578 | 0 | } |
2579 | |
|
2580 | 0 | *picnt = icnt; |
2581 | 0 | return offset - start_offset; |
2582 | 0 | } |
2583 | | |
2584 | | static int |
2585 | | dissect_http3_qpack_enc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, |
2586 | | quic_stream_info *stream_info, http3_stream_info_t *http3_stream) |
2587 | 0 | { |
2588 | 0 | int remaining; |
2589 | 0 | int remaining_captured; |
2590 | 0 | int retval; |
2591 | |
|
2592 | 0 | remaining_captured = tvb_captured_length_remaining(tvb, offset); |
2593 | 0 | remaining = tvb_reported_length_remaining(tvb, offset); |
2594 | 0 | retval = remaining; |
2595 | |
|
2596 | 0 | DISSECTOR_ASSERT(remaining_captured == remaining); |
2597 | |
|
2598 | 0 | if (remaining > 0) { |
2599 | 0 | http3_session_info_t *http3_session; |
2600 | 0 | proto_item *qpack_update; |
2601 | 0 | proto_tree *qpack_update_tree; |
2602 | 0 | int decoded; |
2603 | 0 | int icnt; |
2604 | |
|
2605 | 0 | http3_session = http3_session_lookup_or_create(pinfo); |
2606 | 0 | DISSECTOR_ASSERT(http3_session); |
2607 | | |
2608 | | /* |
2609 | | * Add a QPACK encoder tree item. |
2610 | | */ |
2611 | 0 | qpack_update = proto_tree_add_item(tree, hf_http3_qpack_encoder, tvb, offset, remaining, ENC_NA); |
2612 | 0 | qpack_update_tree = proto_item_add_subtree(qpack_update, ett_http3_qpack_update); |
2613 | 0 | decoded = dissect_http3_qpack_encoder_stream(tvb, pinfo, qpack_update_tree, offset, |
2614 | 0 | http3_stream, &icnt); |
2615 | |
|
2616 | 0 | if (!PINFO_FD_VISITED(pinfo)) { |
2617 | 0 | ws_debug("decode encoder stream: Wireshark decoded=%u of %u", decoded, remaining); |
2618 | 0 | } |
2619 | 0 | if (decoded < remaining) { |
2620 | 0 | pinfo->desegment_offset = offset + decoded; |
2621 | 0 | pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT; |
2622 | 0 | } |
2623 | |
|
2624 | 0 | col_append_sep_fstr(pinfo->cinfo, COL_INFO, ", ", "QPACK ENC[%d]", icnt); |
2625 | |
|
2626 | | #ifdef HAVE_NGHTTP3 |
2627 | | http3_stream_dir packet_direction = http3_packet_get_direction(stream_info); |
2628 | | nghttp3_qpack_decoder *decoder = http3_session->qpack_decoder[packet_direction]; |
2629 | | proto_item *ti = NULL; |
2630 | | http3_qpack_encoder_state_t *encoder_state = http3_get_qpack_encoder_state(pinfo, tvb, offset); |
2631 | | |
2632 | | if (!PINFO_FD_VISITED(pinfo)) { |
2633 | | /* |
2634 | | * Since we are now defragmenting, pass only the number of bytes |
2635 | | * decoded to the nghttp3_qpack_decoder. Otherwise, we'll end up |
2636 | | * sending the same bytes to the decoder again when the packet |
2637 | | * is defragmented. |
2638 | | */ |
2639 | | uint8_t *qpack_buf = (uint8_t *)tvb_memdup(pinfo->pool, tvb, offset, decoded); |
2640 | | int qpack_buf_len = decoded; |
2641 | | |
2642 | | /* |
2643 | | * Get the instr count prior to processing the data. |
2644 | | */ |
2645 | | uint64_t icnt_before = nghttp3_qpack_decoder_get_icnt(decoder); |
2646 | | |
2647 | | encoder_state->nread = nghttp3_qpack_decoder_read_encoder(decoder, qpack_buf, qpack_buf_len); |
2648 | | encoder_state->icnt = nghttp3_qpack_decoder_get_icnt(decoder); |
2649 | | encoder_state->icnt_inc = (uint32_t)(encoder_state->icnt - icnt_before); |
2650 | | |
2651 | | ws_debug("decode encoder stream: decoder=%p nread=%td new insertions=%u total insertions=%" PRIu64, |
2652 | | decoder, encoder_state->nread, encoder_state->icnt_inc, encoder_state->icnt); |
2653 | | } |
2654 | | |
2655 | | /* nghttp3_qpack_decoder_read_encoder() returns a nghttp3_ssize |
2656 | | * (ptrdiff_t), negative in the case of errors, but nghttp3_strerror() |
2657 | | * accepts int instead. |
2658 | | */ |
2659 | | if (encoder_state->nread < 0) { |
2660 | | quic_cid_t quic_cid = {.len = 0}; |
2661 | | bool initial_cid_found = quic_conn_data_get_conn_client_dcid_initial(pinfo, &quic_cid); |
2662 | | proto_tree_add_expert_format(tree, pinfo, &ei_http3_qpack_failed, tvb, offset, 0, |
2663 | | "QPACK decoder %p DCID %s [found=%d] error %d (%s)", |
2664 | | decoder, cid_to_string(&quic_cid, pinfo->pool), initial_cid_found, |
2665 | | (int)encoder_state->nread, nghttp3_strerror((int)encoder_state->nread)); |
2666 | | } |
2667 | | |
2668 | | proto_item_set_text(qpack_update, "QPACK encoder stream; %d instructions (%" PRIu64 " total)", |
2669 | | encoder_state->icnt_inc, encoder_state->icnt); |
2670 | | ti = proto_tree_add_uint(qpack_update_tree, hf_http3_qpack_encoder_icnt_inc, tvb, offset, 0, |
2671 | | encoder_state->icnt_inc); |
2672 | | proto_item_set_generated(ti); |
2673 | | ti = proto_tree_add_uint64(qpack_update_tree, hf_http3_qpack_encoder_icnt, tvb, offset, 0, |
2674 | | encoder_state->icnt); |
2675 | | proto_item_set_generated(ti); |
2676 | | #else |
2677 | 0 | (void)stream_info; |
2678 | 0 | (void)qpack_update; |
2679 | 0 | (void)decoded; |
2680 | 0 | #endif /* HAVE_NGHTTP3 */ |
2681 | 0 | } |
2682 | |
|
2683 | 0 | return retval; |
2684 | 0 | } |
2685 | | |
2686 | | static int |
2687 | | dissect_http3_qpack_decoder_stream(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, |
2688 | | int start_offset, http3_stream_info_t *http3_stream, int *picnt) |
2689 | 0 | { |
2690 | 0 | unsigned end_offset; /* Sentinel offset past the buffer. */ |
2691 | 0 | volatile bool can_continue; /* Flag to indicate that we can parse the next QPACK instruction */ |
2692 | 0 | volatile int icnt; /* Number of dissected QPACK instructions */ |
2693 | 0 | volatile unsigned offset; /* Current parsing offset, relative to the start of the tvb */ |
2694 | |
|
2695 | 0 | can_continue = true; |
2696 | 0 | icnt = 0; |
2697 | 0 | offset = start_offset; |
2698 | 0 | end_offset = start_offset + tvb_captured_length_remaining(tvb, start_offset); |
2699 | |
|
2700 | 0 | wmem_strbuf_t *follow_buf = wmem_strbuf_create(pinfo->pool); |
2701 | 0 | wmem_strbuf_t *instr_buf; |
2702 | |
|
2703 | 0 | while (offset < end_offset && can_continue) { |
2704 | 0 | int inst_offset; /* Starting offset of the currently parsed instruction in the tvb */ |
2705 | 0 | int inst_len; /* Total length of the instruction */ |
2706 | 0 | proto_item *opcode_ti; |
2707 | 0 | proto_tree *opcode_tree; |
2708 | |
|
2709 | 0 | inst_offset = offset; |
2710 | |
|
2711 | 0 | TRY { |
2712 | 0 | uint8_t opcode; /* The instruction opcode */ |
2713 | 0 | unsigned varint_len; |
2714 | |
|
2715 | 0 | opcode = tvb_get_uint8(tvb, inst_offset) & QPACK_OPCODE_MASK; |
2716 | |
|
2717 | 0 | ws_noisy("Decoding opcode=%" PRIu8 " decoded=%d start=%d current=%d end=%d", |
2718 | 0 | opcode, (offset - start_offset), start_offset, offset, end_offset); |
2719 | |
|
2720 | 0 | if (opcode & QPACK_OPCODE_SECTION_ACK) { |
2721 | 0 | uint64_t stream_id = 0; |
2722 | | |
2723 | | /* https://datatracker.ietf.org/doc/html/rfc9204#section-4.4.1 |
2724 | | * |
2725 | | * 0 1 2 3 4 5 6 7 |
2726 | | * +---+---+---+---+---+---+---+---+ |
2727 | | * | 1 | Stream ID (7+) | |
2728 | | * +---+---------------------------+ |
2729 | | */ |
2730 | | |
2731 | | /* Read the 7-encoded name stream ID */ |
2732 | 0 | varint_len = read_qpack_prefixed_integer(tvb, offset, 7, &stream_id, NULL); |
2733 | 0 | if (varint_len == 0) { |
2734 | 0 | offset = end_offset; |
2735 | 0 | break; |
2736 | 0 | } |
2737 | 0 | offset += varint_len; |
2738 | | |
2739 | | /* Update the instruction length */ |
2740 | 0 | inst_len = offset - inst_offset; |
2741 | | |
2742 | | /* Add the protocol tree items */ |
2743 | 0 | opcode_ti = proto_tree_add_item(tree, hf_http3_qpack_decoder_opcode_section_ack, |
2744 | 0 | tvb, inst_offset, inst_len, ENC_NA); |
2745 | 0 | opcode_tree = proto_item_add_subtree(opcode_ti, ett_http3_qpack_opcode); |
2746 | 0 | proto_tree_add_uint64(opcode_tree, hf_http3_qpack_decoder_opcode_section_ack_stream_id, |
2747 | 0 | tvb, inst_offset, inst_len, stream_id); |
2748 | |
|
2749 | 0 | instr_buf = wmem_strbuf_new(pinfo->pool, "SECTION_ACK"); |
2750 | 0 | wmem_strbuf_append_printf(instr_buf, "[id=%" PRIu64 "]", stream_id); |
2751 | 0 | proto_item_set_text(opcode_ti, "%s", wmem_strbuf_get_str(instr_buf)); |
2752 | 0 | wmem_strbuf_append_printf(follow_buf, "%s\n", wmem_strbuf_finalize(instr_buf)); |
2753 | 0 | } else if (opcode & QPACK_OPCODE_STREAM_CANCEL) { |
2754 | 0 | uint64_t stream_id = 0; |
2755 | | |
2756 | | /* https://datatracker.ietf.org/doc/html/rfc9204#section-4.4.2 |
2757 | | * |
2758 | | * 0 1 2 3 4 5 6 7 |
2759 | | * +---+---+---+---+---+---+---+---+ |
2760 | | * | 0 | 1 | Stream ID (6+) | |
2761 | | * +---+---+---+-------------------+ |
2762 | | */ |
2763 | | |
2764 | | /* Read the 6-encoded name stream ID */ |
2765 | 0 | varint_len = read_qpack_prefixed_integer(tvb, offset, 6, &stream_id, NULL); |
2766 | 0 | if (varint_len == 0) { |
2767 | 0 | offset = end_offset; |
2768 | 0 | break; |
2769 | 0 | } |
2770 | 0 | offset += varint_len; |
2771 | | |
2772 | | /* Update the instruction length */ |
2773 | 0 | inst_len = offset - inst_offset; |
2774 | | |
2775 | | /* Add the protocol tree items */ |
2776 | 0 | opcode_ti = proto_tree_add_item(tree, hf_http3_qpack_decoder_opcode_cancel_stream, |
2777 | 0 | tvb, inst_offset, inst_len, ENC_NA); |
2778 | 0 | opcode_tree = proto_item_add_subtree(opcode_ti, ett_http3_qpack_opcode); |
2779 | 0 | proto_tree_add_uint64(opcode_tree, hf_http3_qpack_decoder_opcode_cancel_stream_id, |
2780 | 0 | tvb, inst_offset, inst_len,stream_id); |
2781 | |
|
2782 | 0 | instr_buf = wmem_strbuf_new(pinfo->pool, "STREAM_CANCEL"); |
2783 | 0 | wmem_strbuf_append_printf(instr_buf, "[id=%" PRIu64 "]", stream_id); |
2784 | 0 | proto_item_set_text(opcode_ti, "%s", wmem_strbuf_get_str(instr_buf)); |
2785 | 0 | wmem_strbuf_append_printf(follow_buf, "%s\n", wmem_strbuf_finalize(instr_buf)); |
2786 | 0 | } else if (opcode == QPACK_OPCODE_ICNT_INCREMENT) { |
2787 | 0 | uint64_t icnt_inc = 0; |
2788 | | |
2789 | | /* https://datatracker.ietf.org/doc/html/rfc9204#section-4.4.3 |
2790 | | * |
2791 | | * 0 1 2 3 4 5 6 7 |
2792 | | * +---+---+---+---+---+---+---+---+ |
2793 | | * | 0 | 0 | Increment (6+) | |
2794 | | * +---+---+-----------------------+ |
2795 | | */ |
2796 | | |
2797 | | /* Read the 6-encoded instruction count increment */ |
2798 | 0 | varint_len = read_qpack_prefixed_integer(tvb, offset, 6, &icnt_inc, NULL); |
2799 | 0 | if (varint_len == 0) { |
2800 | 0 | offset = end_offset; |
2801 | 0 | break; |
2802 | 0 | } |
2803 | 0 | offset += varint_len; |
2804 | | |
2805 | | /* Update the instruction length */ |
2806 | 0 | inst_len = offset - inst_offset; |
2807 | | /* Add the protocol tree items */ |
2808 | |
|
2809 | 0 | opcode_ti = proto_tree_add_item(tree, hf_http3_qpack_decoder_opcode_icnt_increment, |
2810 | 0 | tvb, inst_offset, inst_len, ENC_NA); |
2811 | 0 | opcode_tree = proto_item_add_subtree(opcode_ti, ett_http3_qpack_opcode); |
2812 | 0 | proto_tree_add_uint64(opcode_tree, hf_http3_qpack_decoder_opcode_icnt_increment_value, |
2813 | 0 | tvb, inst_offset, inst_len,icnt_inc); |
2814 | |
|
2815 | 0 | instr_buf = wmem_strbuf_new(pinfo->pool, "ICNT_INC"); |
2816 | 0 | wmem_strbuf_append_printf(instr_buf, "[%" PRIu64 "]", icnt_inc); |
2817 | 0 | proto_item_set_text(opcode_ti, "%s", wmem_strbuf_get_str(instr_buf)); |
2818 | 0 | wmem_strbuf_append_printf(follow_buf, "%s\n", wmem_strbuf_finalize(instr_buf)); |
2819 | 0 | } else { |
2820 | 0 | ws_debug("Opcode=%" PRIu8 ": UNKNOWN", opcode); |
2821 | 0 | can_continue = false; |
2822 | 0 | } |
2823 | | /* Increment the instruction count */ |
2824 | 0 | icnt ++; |
2825 | 0 | } |
2826 | 0 | CATCH(ReportedBoundsError) { |
2827 | | /* We could not parse the last instruction, hence roll back `parsing_offset' . */ |
2828 | 0 | ws_noisy("Could not parse last instruction, rolling back parsing offset from %d to %d", |
2829 | 0 | offset, inst_offset); |
2830 | 0 | offset = inst_offset; |
2831 | 0 | can_continue = false; |
2832 | 0 | } |
2833 | 0 | ENDTRY; |
2834 | 0 | } |
2835 | |
|
2836 | 0 | if (icnt && have_tap_listener(http3_follow_tap)) { |
2837 | 0 | quic_follow_tap_data_t *follow_data = wmem_new0(pinfo->pool, quic_follow_tap_data_t); |
2838 | |
|
2839 | 0 | follow_data->tvb = tvb_new_child_real_data(tvb, |
2840 | 0 | (const uint8_t*)wmem_strbuf_get_str(follow_buf), |
2841 | 0 | (unsigned)wmem_strbuf_get_len(follow_buf), |
2842 | 0 | (unsigned)wmem_strbuf_get_len(follow_buf)); |
2843 | 0 | follow_data->stream_id = http3_stream->id; |
2844 | 0 | follow_data->from_server = http3_stream->direction; |
2845 | |
|
2846 | 0 | tap_queue_packet(http3_follow_tap, pinfo, follow_data); |
2847 | 0 | } |
2848 | |
|
2849 | 0 | *picnt = icnt; |
2850 | 0 | return offset - start_offset; |
2851 | 0 | } |
2852 | | |
2853 | | static int |
2854 | | dissect_http3_qpack_dec(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, |
2855 | | quic_stream_info *stream_info _U_, http3_stream_info_t *http3_stream) |
2856 | 0 | { |
2857 | 0 | int remaining; |
2858 | 0 | int remaining_captured; |
2859 | 0 | int retval; |
2860 | |
|
2861 | 0 | remaining_captured = tvb_captured_length_remaining(tvb, offset); |
2862 | 0 | remaining = tvb_reported_length_remaining(tvb, offset); |
2863 | 0 | retval = remaining; |
2864 | |
|
2865 | 0 | DISSECTOR_ASSERT(remaining_captured == remaining); |
2866 | |
|
2867 | 0 | if (remaining > 0) { |
2868 | 0 | http3_session_info_t *http3_session; |
2869 | 0 | proto_item *qpack_update; |
2870 | 0 | proto_tree *qpack_update_tree; |
2871 | 0 | int decoded; |
2872 | 0 | int icnt; |
2873 | |
|
2874 | 0 | http3_session = http3_session_lookup_or_create(pinfo); |
2875 | 0 | DISSECTOR_ASSERT(http3_session); |
2876 | | |
2877 | | /* |
2878 | | * Add a QPACK encoder tree item. |
2879 | | */ |
2880 | 0 | qpack_update = proto_tree_add_item(tree, hf_http3_qpack_decoder, tvb, offset, remaining, ENC_NA); |
2881 | 0 | qpack_update_tree = proto_item_add_subtree(qpack_update, ett_http3_qpack_update); |
2882 | 0 | decoded = dissect_http3_qpack_decoder_stream(tvb, pinfo, qpack_update_tree, offset, http3_stream, &icnt); |
2883 | |
|
2884 | 0 | col_append_sep_fstr(pinfo->cinfo, COL_INFO, ", ", "QPACK DEC[%d]", icnt); |
2885 | |
|
2886 | 0 | if (!PINFO_FD_VISITED(pinfo)) { |
2887 | 0 | ws_debug("decode decoder stream: Wireshark decoded=%u of %u", decoded, remaining); |
2888 | 0 | } |
2889 | 0 | if (decoded < remaining) { |
2890 | 0 | pinfo->desegment_offset = offset + decoded; |
2891 | 0 | pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT; |
2892 | 0 | } |
2893 | 0 | retval = decoded; |
2894 | 0 | } |
2895 | |
|
2896 | 0 | return retval; |
2897 | 0 | } |
2898 | | |
2899 | | static int |
2900 | | dissect_http3_client_bidi_stream(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, |
2901 | | quic_stream_info *stream_info, http3_stream_info_t *http3_stream) |
2902 | 0 | { |
2903 | 0 | proto_item *ti_stream, *ti_stream_id; |
2904 | 0 | proto_tree *stream_tree; |
2905 | |
|
2906 | 0 | ti_stream = proto_tree_add_item(tree, hf_http3_stream_bidi, tvb, offset, 1, ENC_NA); |
2907 | 0 | stream_tree = proto_item_add_subtree(ti_stream, ett_http3_stream_bidi); |
2908 | |
|
2909 | 0 | ti_stream_id = proto_tree_add_uint64(stream_tree, hf_http3_stream_id, tvb, offset, 0, http3_stream->id); |
2910 | 0 | proto_item_set_generated(ti_stream_id); |
2911 | |
|
2912 | 0 | while (tvb_reported_length_remaining(tvb, offset)) { |
2913 | 0 | if (!http3_check_frame_size(tvb, pinfo, offset)) { |
2914 | 0 | offset = tvb_captured_length(tvb); |
2915 | 0 | break; |
2916 | 0 | } |
2917 | 0 | offset = dissect_http3_frame(tvb, pinfo, stream_tree, offset, stream_info, http3_stream); |
2918 | 0 | } |
2919 | |
|
2920 | 0 | const char *uri = http3_get_request_full_uri(pinfo, http3_stream); |
2921 | 0 | if (uri) { |
2922 | 0 | proto_item *ti_url = proto_tree_add_string(stream_tree, hf_http3_header_request_full_uri, tvb, 0, 0, uri); |
2923 | 0 | proto_item_set_url(ti_url); |
2924 | 0 | proto_item_set_generated(ti_url); |
2925 | 0 | } |
2926 | |
|
2927 | 0 | if (http3_stream->direction == FROM_CLIENT_TO_SERVER) { |
2928 | 0 | if (http3_stream->response_frame_num != 0) { |
2929 | 0 | proto_item_set_generated(proto_tree_add_uint(stream_tree, hf_http3_response_in, tvb, 0, 0, http3_stream->response_frame_num)); |
2930 | 0 | } |
2931 | 0 | } else { |
2932 | 0 | if (http3_stream->request_frame_num != 0) { |
2933 | 0 | if (!nstime_is_unset(&http3_stream->request_ts)) { |
2934 | 0 | nstime_t delta; |
2935 | |
|
2936 | 0 | nstime_delta(&delta, &pinfo->abs_ts, &http3_stream->request_ts); |
2937 | 0 | proto_item_set_generated(proto_tree_add_time(stream_tree, hf_http3_time, tvb, 0, 0, &delta)); |
2938 | 0 | } |
2939 | 0 | proto_item_set_generated(proto_tree_add_uint(stream_tree, hf_http3_request_in, tvb, 0, 0, http3_stream->request_frame_num)); |
2940 | 0 | } |
2941 | 0 | } |
2942 | |
|
2943 | 0 | return offset; |
2944 | 0 | } |
2945 | | |
2946 | | static int |
2947 | | dissect_http3_uni_stream(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, quic_stream_info *stream_info, |
2948 | | http3_stream_info_t *http3_stream) |
2949 | 0 | { |
2950 | 0 | uint64_t stream_type; |
2951 | 0 | unsigned lenvar; |
2952 | 0 | proto_item *ti_stream, *ti_stream_id, *ti_stream_type; |
2953 | 0 | proto_tree *stream_tree; |
2954 | 0 | const char *stream_display_name; |
2955 | |
|
2956 | 0 | ti_stream = proto_tree_add_item(tree, hf_http3_stream_uni, tvb, offset, -1, ENC_NA); |
2957 | 0 | stream_tree = proto_item_add_subtree(ti_stream, ett_http3_stream_uni); |
2958 | |
|
2959 | 0 | ti_stream_id = proto_tree_add_uint64(stream_tree, hf_http3_stream_id, tvb, offset, 0, http3_stream->id); |
2960 | 0 | proto_item_set_generated(ti_stream_id); |
2961 | |
|
2962 | 0 | if (stream_info->offset == 0) { |
2963 | 0 | ti_stream_type = proto_tree_add_item_ret_varint(stream_tree, hf_http3_stream_uni_type, tvb, offset, -1, ENC_VARINT_QUIC, &stream_type, |
2964 | 0 | &lenvar); |
2965 | 0 | offset += lenvar; |
2966 | 0 | http3_stream->uni_stream_type = stream_type; |
2967 | 0 | if (http3_is_reserved_code(stream_type)) { |
2968 | | // Reserved to exercise requirement that unknown types are ignored. |
2969 | 0 | proto_item_set_text(ti_stream_type, "Stream Type: Reserved (%#" PRIx64 ")", stream_type); |
2970 | 0 | stream_display_name = "Reserved (GREASE)"; |
2971 | 0 | } |
2972 | 0 | else { |
2973 | 0 | stream_display_name = val64_to_str_wmem(pinfo->pool, stream_type, http3_stream_types, "Unknown (%#" PRIx64 ")"); |
2974 | 0 | } |
2975 | 0 | proto_item_set_text(ti_stream, "UNI STREAM: %s off=%" PRIu64, stream_display_name, stream_info->stream_offset); |
2976 | 0 | } else { |
2977 | 0 | stream_type = http3_stream->uni_stream_type; |
2978 | | /*ti_stream_type = proto_tree_add_item(stream_tree, hf_http3_stream_uni_type, tvb, offset, -1, ENC_BIG_ENDIAN);*/ |
2979 | 0 | } |
2980 | |
|
2981 | 0 | switch (stream_type) { |
2982 | 0 | case HTTP3_STREAM_TYPE_CONTROL: |
2983 | 0 | while (tvb_reported_length_remaining(tvb, offset)) { |
2984 | 0 | if (!http3_check_frame_size(tvb, pinfo, offset)) { |
2985 | 0 | return tvb_captured_length(tvb); |
2986 | 0 | } |
2987 | 0 | offset = dissect_http3_frame(tvb, pinfo, stream_tree, offset, stream_info, http3_stream); |
2988 | 0 | } |
2989 | 0 | break; |
2990 | 0 | case HTTP3_STREAM_TYPE_PUSH: |
2991 | | // The remaining data of this stream consists of HTTP/3 frames. |
2992 | 0 | if (stream_info->offset == 0) { |
2993 | 0 | proto_tree_add_item_ret_varint(stream_tree, hf_http3_push_id, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); |
2994 | 0 | offset += lenvar; |
2995 | 0 | } |
2996 | 0 | break; |
2997 | 0 | case HTTP3_STREAM_TYPE_QPACK_ENCODER: |
2998 | 0 | offset = dissect_http3_qpack_enc(tvb, pinfo, stream_tree, offset, stream_info, http3_stream); |
2999 | 0 | break; |
3000 | 0 | case HTTP3_STREAM_TYPE_QPACK_DECODER: |
3001 | 0 | offset = dissect_http3_qpack_dec(tvb, pinfo, stream_tree, offset, stream_info, http3_stream); |
3002 | 0 | break; |
3003 | 0 | case HTTP3_STREAM_TYPE_WEBTRANSPORT: |
3004 | | // TODO |
3005 | 0 | offset = tvb_captured_length(tvb); |
3006 | 0 | break; |
3007 | 0 | default: |
3008 | | // Unknown or reserved stream type, consume everything. |
3009 | 0 | if (!http3_is_reserved_code(stream_type)) { |
3010 | 0 | if (!PINFO_FD_VISITED(pinfo)) { |
3011 | 0 | http3_stream->broken_from_offset = stream_info->offset + offset; |
3012 | 0 | } |
3013 | 0 | report_unknown_stream_type(tvb, pinfo, stream_tree, offset, stream_info, http3_stream); |
3014 | 0 | } |
3015 | 0 | offset = tvb_captured_length(tvb); |
3016 | 0 | break; |
3017 | 0 | } |
3018 | | |
3019 | 0 | return offset; |
3020 | 0 | } |
3021 | | |
3022 | | static proto_tree * |
3023 | 0 | start_http3_tree(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { |
3024 | 0 | proto_item * ti; |
3025 | |
|
3026 | 0 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "HTTP3"); |
3027 | | // Only clear the columns if this is the first HTTP/3 STREAM in the packet. |
3028 | 0 | if (proto_get_layer_num(pinfo, proto_http3) == 1) { |
3029 | 0 | col_clear(pinfo->cinfo, COL_INFO); |
3030 | 0 | } |
3031 | |
|
3032 | 0 | ti = proto_tree_add_item(tree, proto_http3, tvb, 0, -1, ENC_NA); |
3033 | 0 | return proto_item_add_subtree(ti, ett_http3); |
3034 | 0 | } |
3035 | | |
3036 | | static int |
3037 | | dissect_http3(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) |
3038 | 0 | { |
3039 | 0 | quic_stream_info * stream_info = (quic_stream_info *)data; |
3040 | 0 | proto_tree * http3_tree; |
3041 | 0 | int offset = 0; |
3042 | 0 | http3_stream_info_t *http3_stream; |
3043 | 0 | http3_session_info_t *http3_session; |
3044 | 0 | bool from_server; |
3045 | |
|
3046 | 0 | if (!stream_info) { |
3047 | 0 | return 0; |
3048 | 0 | } |
3049 | | |
3050 | | /* XXX - Handle implementations where QUIC signals FIN in a QUIC frame that |
3051 | | * has no HTTP/3 frames, and we don't get a Content-Length header either? |
3052 | | * Unlike HTTP/1.1, there might be nothing for QUIC itself to reassemble, |
3053 | | * since each HTTP/3 frame has a TPKT-like header with its own length. |
3054 | | * So tvb might be length 0 here. We also might want to handle the case |
3055 | | * where FIN is set but the length is too short for a frame. In that |
3056 | | * case, it is more likely that more DATA will be forthcoming, or would |
3057 | | * the QUIC stream just abruptly stop like a RESET_STREAM? For that matter, |
3058 | | * should we try to reassemble any leftover data on a RESET_STREAM? */ |
3059 | | |
3060 | 0 | switch (QUIC_STREAM_TYPE(stream_info->stream_id)) { |
3061 | 0 | case QUIC_STREAM_CLIENT_BIDI: |
3062 | | /* Used for HTTP requests and responses. */ |
3063 | 0 | if (!http3_check_frame_size(tvb, pinfo, offset)) { |
3064 | 0 | return tvb_captured_length(tvb); |
3065 | 0 | } |
3066 | 0 | break; |
3067 | 0 | case QUIC_STREAM_SERVER_BIDI: |
3068 | | /* "HTTP/3 does not use server-initiated bidirectional streams, |
3069 | | * though an extension could define a use for these streams." */ |
3070 | 0 | break; |
3071 | 0 | case QUIC_STREAM_CLIENT_UNI: |
3072 | 0 | case QUIC_STREAM_SERVER_UNI: |
3073 | 0 | break; |
3074 | 0 | } |
3075 | | |
3076 | 0 | http3_tree = start_http3_tree(tvb, pinfo, tree); |
3077 | | |
3078 | | /* We need a single HTTP/3 stream for both directions */ |
3079 | 0 | from_server = stream_info->from_server; |
3080 | 0 | if (QUIC_STREAM_TYPE(stream_info->stream_id) == QUIC_STREAM_CLIENT_BIDI && from_server) { |
3081 | 0 | stream_info->from_server = false; |
3082 | 0 | } |
3083 | 0 | http3_stream = (http3_stream_info_t *)quic_stream_get_proto_data(pinfo, stream_info); |
3084 | 0 | if (!http3_stream) { |
3085 | 0 | http3_stream = wmem_new0(wmem_file_scope(), http3_stream_info_t); |
3086 | 0 | nstime_set_unset(&http3_stream->request_ts); |
3087 | 0 | quic_stream_add_proto_data(pinfo, stream_info, http3_stream); |
3088 | 0 | http3_stream->id = stream_info->stream_id; |
3089 | 0 | } |
3090 | 0 | if (QUIC_STREAM_TYPE(stream_info->stream_id) == QUIC_STREAM_CLIENT_BIDI && from_server) { |
3091 | 0 | stream_info->from_server = true; |
3092 | 0 | } |
3093 | |
|
3094 | 0 | http3_session = http3_session_lookup_or_create(pinfo); |
3095 | 0 | http3_session->current_stream = http3_stream; |
3096 | 0 | http3_stream->direction = http3_packet_get_direction(stream_info); |
3097 | |
|
3098 | 0 | uint64_t *stream_id = wmem_new(pinfo->pool, uint64_t); |
3099 | 0 | *stream_id = http3_stream->id; |
3100 | 0 | p_add_proto_data(pinfo->pool, pinfo, hf_http3_stream_id, 0, stream_id); |
3101 | | |
3102 | | // If a STREAM has unknown data, everything afterwards cannot be dissected. |
3103 | 0 | if (http3_stream->broken_from_offset && http3_stream->broken_from_offset <= stream_info->offset + offset) { |
3104 | 0 | report_unknown_stream_type(tvb, pinfo, tree, offset, stream_info, http3_stream); |
3105 | 0 | return tvb_captured_length(tvb); |
3106 | 0 | } |
3107 | | |
3108 | 0 | switch (QUIC_STREAM_TYPE(stream_info->stream_id)) { |
3109 | 0 | case QUIC_STREAM_CLIENT_BIDI: |
3110 | | /* Used for HTTP requests and responses. */ |
3111 | 0 | dissect_http3_client_bidi_stream(tvb, pinfo, http3_tree, offset, stream_info, http3_stream); |
3112 | 0 | break; |
3113 | | |
3114 | 0 | case QUIC_STREAM_SERVER_BIDI: |
3115 | | /* "HTTP/3 does not use server-initiated bidirectional streams, |
3116 | | * though an extension could define a use for these streams." */ |
3117 | | // XXX expert info? |
3118 | 0 | return tvb_captured_length(tvb); |
3119 | | |
3120 | 0 | case QUIC_STREAM_CLIENT_UNI: |
3121 | 0 | case QUIC_STREAM_SERVER_UNI: |
3122 | 0 | dissect_http3_uni_stream(tvb, pinfo, http3_tree, offset, stream_info, http3_stream); |
3123 | 0 | break; |
3124 | 0 | } |
3125 | | |
3126 | 0 | return tvb_captured_length(tvb); |
3127 | 0 | } |
3128 | | |
3129 | | static int |
3130 | 0 | dissect_http3_datagram(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { |
3131 | 0 | quic_datagram_info * datagram_info = (quic_datagram_info *)data; |
3132 | 0 | uint64_t request_stream_id; |
3133 | 0 | proto_item * ti; |
3134 | 0 | proto_tree * http3_tree, * datragram_tree, * stream_id_tree; |
3135 | 0 | uint32_t lenvar; |
3136 | 0 | int offset = 0; |
3137 | |
|
3138 | 0 | if (!datagram_info) { |
3139 | 0 | return 0; |
3140 | 0 | } |
3141 | | |
3142 | 0 | http3_tree = start_http3_tree(tvb, pinfo, tree); |
3143 | 0 | col_append_sep_str(pinfo->cinfo, COL_INFO, ", ", "DATAGRAM"); |
3144 | |
|
3145 | 0 | ti = proto_tree_add_item(http3_tree, hf_http3_datagram, tvb, offset, -1, ENC_NA); |
3146 | 0 | datragram_tree = proto_item_add_subtree(ti, ett_http3_datagram); |
3147 | |
|
3148 | 0 | ti = proto_tree_add_item_ret_varint(datragram_tree, hf_http3_datagram_quarter_stream_id, tvb, offset, -1, ENC_VARINT_QUIC, &request_stream_id, &lenvar); |
3149 | 0 | stream_id_tree = proto_item_add_subtree(ti, ett_http3_datagram_stream_id); |
3150 | |
|
3151 | 0 | if (request_stream_id > ((1ULL << 60) - 1)) { |
3152 | 0 | proto_tree_add_expert_format(stream_id_tree, pinfo, &ei_http3_datagram_invalid_stream_id, tvb, offset, lenvar, |
3153 | 0 | "Quarter Stream ID is too big"); |
3154 | 0 | return tvb_captured_length(tvb); |
3155 | 0 | } |
3156 | | |
3157 | 0 | request_stream_id = request_stream_id * 4; |
3158 | 0 | ti = proto_tree_add_uint64(stream_id_tree, hf_http3_datagram_request_stream_id, tvb, offset, lenvar, request_stream_id); |
3159 | 0 | proto_item_set_generated(ti); |
3160 | 0 | offset += lenvar; |
3161 | |
|
3162 | 0 | proto_tree_add_item(datragram_tree, hf_http3_datagram_payload, tvb, offset, -1, ENC_NA); |
3163 | |
|
3164 | 0 | return tvb_captured_length(tvb); |
3165 | 0 | } |
3166 | | |
3167 | | #ifdef HAVE_NGHTTP3 |
3168 | | static void |
3169 | | register_static_headers(void) |
3170 | | { |
3171 | | header_fields_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); |
3172 | | |
3173 | | /* |
3174 | | * Here hf[x].hfinfo.name is a header method which is used as key |
3175 | | * for matching ids while processing http3 packets. |
3176 | | */ |
3177 | | static hf_register_info hf[] = { |
3178 | | { &hf_http3_headers_authority, |
3179 | | { ":authority", "http3.headers.authority", |
3180 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3181 | | "Authority portion of the target URI", HFILL } |
3182 | | }, |
3183 | | { &hf_http3_headers_status, |
3184 | | { ":status", "http3.headers.status", |
3185 | | FT_UINT16, BASE_DEC, VALS(vals_http_status_code), 0x0, |
3186 | | NULL, HFILL } |
3187 | | }, |
3188 | | { &hf_http3_headers_path, |
3189 | | { ":path", "http3.headers.path", |
3190 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3191 | | NULL, HFILL } |
3192 | | }, |
3193 | | { &hf_http3_headers_protocol, |
3194 | | { ":protocol", "http3.headers.protocol", |
3195 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3196 | | NULL, HFILL } |
3197 | | }, |
3198 | | { &hf_http3_headers_method, |
3199 | | { ":method", "http3.headers.method", |
3200 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3201 | | NULL, HFILL } |
3202 | | }, |
3203 | | { &hf_http3_headers_scheme, |
3204 | | { ":scheme", "http3.headers.scheme", |
3205 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3206 | | NULL, HFILL } |
3207 | | }, |
3208 | | { &hf_http3_headers_accept, |
3209 | | { "accept", "http3.headers.accept", |
3210 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3211 | | "Media types that are acceptable to the user agent", HFILL } |
3212 | | }, |
3213 | | { &hf_http3_headers_accept_charset, |
3214 | | { "accept-charset", "http3.headers.accept_charset", |
3215 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3216 | | "Acceptable charsets in textual responses for the user agent", HFILL } |
3217 | | }, |
3218 | | { &hf_http3_headers_accept_encoding, |
3219 | | { "accept-encoding", "http3.headers.accept_encoding", |
3220 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3221 | | "Acceptable content codings (like compression) in responses for the user agent", HFILL } |
3222 | | }, |
3223 | | { &hf_http3_headers_accept_language, |
3224 | | { "accept-language", "http3.headers.accept_language", |
3225 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3226 | | "Preferred natural languages for the user agent", HFILL } |
3227 | | }, |
3228 | | { &hf_http3_headers_accept_ranges, |
3229 | | { "accept-ranges", "http3.headers.accept_ranges", |
3230 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3231 | | "Bytes range which server may use for partial data transfer", HFILL } |
3232 | | }, |
3233 | | { &hf_http3_headers_access_control_allow_origin, |
3234 | | { "access-control-allow-origin", "http3.headers.access_control_allow_origin", |
3235 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3236 | | "Origin control for cross-origin resource sharing", HFILL } |
3237 | | }, |
3238 | | { &hf_http3_headers_age, |
3239 | | { "age", "http3.headers.age", |
3240 | | FT_UINT64, BASE_DEC, NULL, 0x0, |
3241 | | "Time in seconds which was spent for transferring data through proxy", HFILL } |
3242 | | }, |
3243 | | { &hf_http3_headers_allow, |
3244 | | { "allow", "http3.headers.allow", |
3245 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3246 | | "List of allowed methods for request", HFILL } |
3247 | | }, |
3248 | | { &hf_http3_headers_authorization, |
3249 | | { "authorization", "http3.headers.authorization", |
3250 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3251 | | "Credentials for a server-side authorization", HFILL } |
3252 | | }, |
3253 | | { &hf_http3_headers_cache_control, |
3254 | | { "cache-control", "http3.headers.cache_control", |
3255 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3256 | | "Request or response directives for a cache control", HFILL } |
3257 | | }, |
3258 | | { &hf_http3_headers_content_disposition, |
3259 | | { "content-disposition", "http3.headers.content_disposition", |
3260 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3261 | | "Indicates that response will be displayed as page or downloaded with dialog box", HFILL } |
3262 | | }, |
3263 | | { &hf_http3_headers_content_encoding, |
3264 | | { "content-encoding", "http3.headers.content_encoding", |
3265 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3266 | | NULL, HFILL } |
3267 | | }, |
3268 | | { &hf_http3_headers_content_language, |
3269 | | { "content-language", "http3.headers.content_language", |
3270 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3271 | | NULL, HFILL } |
3272 | | }, |
3273 | | { &hf_http3_headers_content_length, |
3274 | | { "content-length", "http3.headers.content_length", |
3275 | | FT_UINT64, BASE_DEC, NULL, 0x0, |
3276 | | "Size of body in bytes", HFILL } |
3277 | | }, |
3278 | | { &hf_http3_headers_content_location, |
3279 | | { "content-location", "http3.headers.content_location", |
3280 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3281 | | "Alternative URL for a response data", HFILL } |
3282 | | }, |
3283 | | { &hf_http3_headers_content_range, |
3284 | | { "content-range", "http3.headers.content_range", |
3285 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3286 | | "Range of bytes which was sent by server for partial data transfer", HFILL } |
3287 | | }, |
3288 | | { &hf_http3_headers_content_type, |
3289 | | { "content-type", "http3.headers.content_type", |
3290 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3291 | | "MIME type of response", HFILL } |
3292 | | }, |
3293 | | { &hf_http3_headers_cookie, |
3294 | | { "cookie", "http3.headers.cookie", |
3295 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3296 | | "Stored cookies", HFILL } |
3297 | | }, |
3298 | | { &hf_http3_headers_date, |
3299 | | { "date", "http3.headers.date", |
3300 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3301 | | "Date and time at which the data was originated", HFILL } |
3302 | | }, |
3303 | | { &hf_http3_headers_etag, |
3304 | | { "etag", "http3.headers.etag", |
3305 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3306 | | "Directive for version indication of resource", HFILL } |
3307 | | }, |
3308 | | { &hf_http3_headers_expect, |
3309 | | { "expect", "http3.headers.expect", |
3310 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3311 | | "Expectations that need to be fulfilled for correct request", HFILL } |
3312 | | }, |
3313 | | { &hf_http3_headers_expires, |
3314 | | { "expires", "http3.headers.expires", |
3315 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3316 | | "Data after which resource will be stale", HFILL } |
3317 | | }, |
3318 | | { &hf_http3_headers_from, |
3319 | | { "from", "http3.headers.from", |
3320 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3321 | | "Email of a person who responsible for a requesting data", HFILL } |
3322 | | }, |
3323 | | { &hf_http3_headers_if_match, |
3324 | | { "if-match", "http3.headers.if_match", |
3325 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3326 | | "Mechanism for requesting data matched by a list of ETags", HFILL } |
3327 | | }, |
3328 | | { &hf_http3_headers_if_modified_since, |
3329 | | { "if-modified-since", "http3.headers.if_modified_since", |
3330 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3331 | | "Resource will be sent with status code 200 if it was modified otherwise with status code 304", HFILL } |
3332 | | }, |
3333 | | { &hf_http3_headers_if_none_match, |
3334 | | { "if-none-match", "http3.headers.if_none_match", |
3335 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3336 | | "Mechanism for requesting data not matched by a list of ETags", HFILL } |
3337 | | }, |
3338 | | { &hf_http3_headers_if_range, |
3339 | | { "if-range", "http3.headers.if_range", |
3340 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3341 | | "Mechanism for a range request which is used to check if a resource was modified", HFILL } |
3342 | | }, |
3343 | | { &hf_http3_headers_if_unmodified_since, |
3344 | | { "if-unmodified-since", "http3.headers.if_unmodified_since", |
3345 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3346 | | "Resource will be processed if it was not modified otherwise 412 error will be returned", HFILL } |
3347 | | }, |
3348 | | { &hf_http3_headers_last_modified, |
3349 | | { "last-modified", "http3.headers.last_modified", |
3350 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3351 | | "Date and time at which the origin server believes the resource was last modified", HFILL } |
3352 | | }, |
3353 | | { &hf_http3_headers_link, |
3354 | | { "link", "http3.headers.link", |
3355 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3356 | | "Mechanism for indicating that resource will be preloaded", HFILL } |
3357 | | }, |
3358 | | { &hf_http3_headers_location, |
3359 | | { "location", "http3.headers.location", |
3360 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3361 | | "Mechanism for indicating that client will be redirected", HFILL } |
3362 | | }, |
3363 | | { &hf_http3_headers_max_forwards, |
3364 | | { "max-forwards", "http3.headers.max_forwards", |
3365 | | FT_UINT64, BASE_DEC, NULL, 0x0, |
3366 | | "Mechanism for limiting the number of proxies", HFILL } |
3367 | | }, |
3368 | | { &hf_http3_headers_proxy_authenticate, |
3369 | | { "proxy-authenticate", "http3.headers.proxy_authenticate", |
3370 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3371 | | "Authentication method that should be used to gain access to a resource behind a proxy server", HFILL } |
3372 | | }, |
3373 | | { &hf_http3_headers_proxy_authorization, |
3374 | | { "proxy-authorization", "http3.headers.proxy_authorization", |
3375 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3376 | | "Credentials for a proxy-side authorization", HFILL } |
3377 | | }, |
3378 | | { &hf_http3_headers_range, |
3379 | | { "range", "http3.headers.range", |
3380 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3381 | | "Range of resource bytes that server should return", HFILL } |
3382 | | }, |
3383 | | { &hf_http3_headers_referer, |
3384 | | { "referer", "http3.headers.referer", |
3385 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3386 | | "Address of the previous web page", HFILL } |
3387 | | }, |
3388 | | { &hf_http3_headers_refresh, |
3389 | | { "refresh", "http3.headers.refresh", |
3390 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3391 | | "Time in seconds after which client will be redirected by given url", HFILL } |
3392 | | }, |
3393 | | { &hf_http3_headers_retry_after, |
3394 | | { "retry-after", "http3.headers.retry_after", |
3395 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3396 | | "Mechanism to indicate when resource expected to be available", HFILL } |
3397 | | }, |
3398 | | { &hf_http3_headers_server, |
3399 | | { "server", "http3.headers.server", |
3400 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3401 | | "Information about server software", HFILL } |
3402 | | }, |
3403 | | { &hf_http3_headers_set_cookie, |
3404 | | { "set-cookie", "http3.headers.set_cookie", |
3405 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3406 | | "Send a cookie to the client", HFILL } |
3407 | | }, |
3408 | | { &hf_http3_headers_strict_transport_security, |
3409 | | { "strict-transport-security", "http3.headers.strict_transport_security", |
3410 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3411 | | "HSTS indicates that resource should be accessed only using HTTPS", HFILL } |
3412 | | }, |
3413 | | { &hf_http3_headers_user_agent, |
3414 | | { "user-agent", "http3.headers.user_agent", |
3415 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3416 | | "Information about client software", HFILL } |
3417 | | }, |
3418 | | { &hf_http3_headers_vary, |
3419 | | { "vary", "http3.headers.vary", |
3420 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3421 | | "Mechanism for selecting which header will be used for content negotiation algorithm", HFILL } |
3422 | | }, |
3423 | | { &hf_http3_headers_via, |
3424 | | { "via", "http3.headers.via", |
3425 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3426 | | "Additional information for loop detection and protocol capabilities in proxy requests", HFILL } |
3427 | | }, |
3428 | | { &hf_http3_headers_www_authenticate, |
3429 | | { "www-authenticate", "http3.headers.www_authenticate", |
3430 | | FT_STRING, BASE_NONE, NULL, 0x0, |
3431 | | "Authentication method that should be used to gain access to a resource", HFILL } |
3432 | | } |
3433 | | }; |
3434 | | |
3435 | | char *header_name; |
3436 | | for (unsigned i = 0; i < G_N_ELEMENTS(hf); ++i) { |
3437 | | header_name = g_strdup(hf[i].hfinfo.name); |
3438 | | |
3439 | | g_hash_table_insert(header_fields_hash, header_name, &hf[i].hfinfo.id); |
3440 | | } |
3441 | | proto_register_field_array(proto_http3, hf, G_N_ELEMENTS(hf)); |
3442 | | } |
3443 | | #endif /* HAVE_NGHTTP3 */ |
3444 | | |
3445 | | void |
3446 | | proto_register_http3(void) |
3447 | 16 | { |
3448 | 16 | expert_module_t *expert_http3; |
3449 | 16 | module_t *module_http3 _U_; |
3450 | | |
3451 | 16 | static hf_register_info hf[] = { |
3452 | 16 | { &hf_http3_stream_uni, |
3453 | 16 | { "Uni Stream", "http3.stream.uni", |
3454 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, |
3455 | 16 | NULL, HFILL } |
3456 | 16 | }, |
3457 | 16 | { &hf_http3_stream_uni_type, |
3458 | 16 | { "Uni Stream Type", "http3.stream_uni_type", |
3459 | 16 | FT_UINT64, BASE_HEX|BASE_VAL64_STRING, VALS64(http3_stream_types), 0x0, |
3460 | 16 | NULL, HFILL } |
3461 | 16 | }, |
3462 | 16 | { &hf_http3_stream_bidi, |
3463 | 16 | { "Request Stream", "http3.stream", |
3464 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, |
3465 | 16 | NULL, HFILL } |
3466 | 16 | }, |
3467 | 16 | { &hf_http3_stream_id, |
3468 | 16 | { "Stream ID", "http3.stream.id", |
3469 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3470 | 16 | "62-bit value identical to the QUIC Stream ID", HFILL } |
3471 | 16 | }, |
3472 | 16 | { &hf_http3_push_id, |
3473 | 16 | { "Push ID", "http3.push_id", |
3474 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3475 | 16 | NULL, HFILL } |
3476 | 16 | }, |
3477 | 16 | { &hf_http3_frame, |
3478 | 16 | { "Frame", "http3.frame", |
3479 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, |
3480 | 16 | NULL, HFILL } |
3481 | 16 | }, |
3482 | 16 | { &hf_http3_frame_type, |
3483 | 16 | { "Type", "http3.frame_type", |
3484 | 16 | FT_UINT64, BASE_HEX|BASE_VAL64_STRING, VALS64(http3_frame_types), 0x0, |
3485 | 16 | "Frame Type", HFILL } |
3486 | 16 | }, |
3487 | 16 | { &hf_http3_frame_length, |
3488 | 16 | { "Length", "http3.frame_length", |
3489 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3490 | 16 | "Length of the Frame Payload", HFILL } |
3491 | 16 | }, |
3492 | 16 | { &hf_http3_frame_payload, |
3493 | 16 | { "Frame Payload", "http3.frame_payload", |
3494 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3495 | 16 | NULL, HFILL } |
3496 | 16 | }, |
3497 | | |
3498 | | /* Generated Fields */ |
3499 | 16 | { &hf_http3_time, |
3500 | 16 | { "Time since request", "http3.time", |
3501 | 16 | FT_RELATIVE_TIME, BASE_NONE, NULL, 0, |
3502 | 16 | "Time since the request was sent", HFILL } |
3503 | 16 | }, |
3504 | 16 | { &hf_http3_request_in, |
3505 | 16 | { "Request in frame", "http3.request_in", |
3506 | 16 | FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), 0x0, |
3507 | 16 | "This frame is part of a response to a HTTP/3 request that began in this frame", HFILL } |
3508 | 16 | }, |
3509 | 16 | { &hf_http3_response_in, |
3510 | 16 | { "Response in frame", "http3.response_in", |
3511 | 16 | FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), 0x0, |
3512 | 16 | "This frame is part of a request responded in the frame with this number", HFILL } |
3513 | 16 | }, |
3514 | | |
3515 | | /* Data */ |
3516 | 16 | { &hf_http3_data, |
3517 | 16 | { "Data", "http3.data", |
3518 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3519 | 16 | NULL, HFILL } |
3520 | 16 | }, |
3521 | 16 | { &hf_http3_encoded_entity, |
3522 | 16 | { "Content-encoded entity body", "http3.body.content_encoded", |
3523 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, |
3524 | 16 | NULL, HFILL } |
3525 | 16 | }, |
3526 | | /* Body fragments */ |
3527 | 16 | { &hf_http3_body_fragments, |
3528 | 16 | { "Body fragments", "http3.body.fragments", |
3529 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, |
3530 | 16 | NULL, HFILL } |
3531 | 16 | }, |
3532 | 16 | { &hf_http3_body_fragment, |
3533 | 16 | { "Body fragment", "http3.body.fragment", |
3534 | 16 | FT_FRAMENUM, BASE_NONE, NULL, 0x0, |
3535 | 16 | NULL, HFILL } |
3536 | 16 | }, |
3537 | 16 | { &hf_http3_body_fragment_overlap, |
3538 | 16 | { "Body fragment overlap", "http3.body.fragment.overlap", |
3539 | 16 | FT_BOOLEAN, BASE_NONE, NULL, 0x0, |
3540 | 16 | NULL, HFILL } |
3541 | 16 | }, |
3542 | 16 | { &hf_http3_body_fragment_overlap_conflicts, |
3543 | 16 | { "Body fragment overlapping with conflicting data", "http3.body.fragment.overlap.conflicts", |
3544 | 16 | FT_BOOLEAN, BASE_NONE, NULL, 0x0, |
3545 | 16 | NULL, HFILL } |
3546 | 16 | }, |
3547 | 16 | { &hf_http3_body_fragment_multiple_tails, |
3548 | 16 | { "Body has multiple tail fragments", "http3.body.fragment.multiple_tails", |
3549 | 16 | FT_BOOLEAN, BASE_NONE, NULL, 0x0, |
3550 | 16 | NULL, HFILL } |
3551 | 16 | }, |
3552 | 16 | { &hf_http3_body_fragment_too_long_fragment, |
3553 | 16 | { "Body fragment too long", "http3.body.fragment.too_long_fragment", |
3554 | 16 | FT_BOOLEAN, BASE_NONE, NULL, 0x0, |
3555 | 16 | NULL, HFILL } |
3556 | 16 | }, |
3557 | 16 | { &hf_http3_body_fragment_error, |
3558 | 16 | { "Body defragment error", "http3.body.fragment.error", |
3559 | 16 | FT_FRAMENUM, BASE_NONE, NULL, 0x0, |
3560 | 16 | NULL, HFILL } |
3561 | 16 | }, |
3562 | 16 | { &hf_http3_body_fragment_count, |
3563 | 16 | { "Body fragment count", "http3.body.fragment.count", |
3564 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, |
3565 | 16 | NULL, HFILL } |
3566 | 16 | }, |
3567 | 16 | { &hf_http3_body_reassembled_in, |
3568 | 16 | { "Reassembled body in frame", "http3.body.reassembled.in", |
3569 | 16 | FT_FRAMENUM, BASE_NONE, NULL, 0x0, |
3570 | 16 | "Reassembled body in frame number", HFILL } |
3571 | 16 | }, |
3572 | 16 | { &hf_http3_body_reassembled_length, |
3573 | 16 | { "Reassembled body length", "http3.body.reassembled.length", |
3574 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, |
3575 | 16 | "Reassembled body in frame number", HFILL } |
3576 | 16 | }, |
3577 | 16 | { &hf_http3_body_reassembled_data, |
3578 | 16 | { "Reassembled body data", "http3.body.reassembled.data", |
3579 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3580 | 16 | "Reassembled body data for multisegment PDU spanning across DATAs", HFILL } |
3581 | 16 | }, |
3582 | | |
3583 | | /* Headers */ |
3584 | 16 | { &hf_http3_headers_count, |
3585 | 16 | { "Headers Count", "http3.headers.count", |
3586 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, |
3587 | 16 | NULL, HFILL } |
3588 | 16 | }, |
3589 | 16 | { &hf_http3_header, |
3590 | 16 | { "Header", "http3.headers.header", |
3591 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, |
3592 | 16 | NULL, HFILL } |
3593 | 16 | }, |
3594 | 16 | { &hf_http3_headers_decoded_length, |
3595 | 16 | { "Decoded Headers Length", "http3.headers.decoded_length", |
3596 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, |
3597 | 16 | NULL, HFILL } |
3598 | 16 | }, |
3599 | 16 | { &hf_http3_header_name_length, |
3600 | 16 | { "Name Length", "http3.headers.header.name.length", |
3601 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, |
3602 | 16 | NULL, HFILL } |
3603 | 16 | }, |
3604 | 16 | { &hf_http3_header_name, |
3605 | 16 | { "Name", "http3.header.header.name", |
3606 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
3607 | 16 | NULL, HFILL } |
3608 | 16 | }, |
3609 | 16 | { &hf_http3_header_value_length, |
3610 | 16 | { "Value Length", "http3.headers.header.value.length", |
3611 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, |
3612 | 16 | NULL, HFILL } |
3613 | 16 | }, |
3614 | 16 | { &hf_http3_header_value, |
3615 | 16 | { "Value", "http3.headers.header.value", |
3616 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
3617 | 16 | NULL, HFILL } |
3618 | 16 | }, |
3619 | 16 | { &hf_http3_header_request_full_uri, |
3620 | 16 | { "Full request URI", "http3.request.full_uri", |
3621 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
3622 | 16 | "The full requested URI (including host name)", HFILL } |
3623 | 16 | }, |
3624 | | |
3625 | | /* QPACK */ |
3626 | 16 | { &hf_http3_header_qpack_blocked, |
3627 | 16 | { "HEADERS head-of-line-blocked on QPACK encoder stream", "http3.header.qpack.blocked", |
3628 | 16 | FT_BOOLEAN, BASE_NONE, NULL, 0x0, |
3629 | 16 | NULL, HFILL } |
3630 | 16 | }, |
3631 | 16 | { &hf_http3_header_qpack_blocked_stream_rcint, |
3632 | 16 | { "Required instruction count", "http3.header.qpack.blocked.rcint", |
3633 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, |
3634 | 16 | NULL, HFILL } |
3635 | 16 | }, |
3636 | 16 | { &hf_http3_header_qpack_blocked_decoder_wicnt, |
3637 | 16 | { "Available instruction count", "http3.header.qpack.blocked.wcint", |
3638 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, |
3639 | 16 | NULL, HFILL } |
3640 | 16 | }, |
3641 | 16 | { &hf_http3_qpack_encoder, |
3642 | 16 | { "QPACK encoder", "http3.qpack.encoder", |
3643 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3644 | 16 | NULL, HFILL } |
3645 | 16 | }, |
3646 | 16 | { &hf_http3_qpack_encoder_icnt, |
3647 | 16 | { "QPACK encoder instruction count", "http3.qpack.encoder.icnt", |
3648 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3649 | 16 | NULL, HFILL } |
3650 | 16 | }, |
3651 | 16 | { &hf_http3_qpack_encoder_icnt_inc, |
3652 | 16 | { "QPACK encoder instruction count increment", "http3.qpack.encoder.icnt.inc", |
3653 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, |
3654 | 16 | NULL, HFILL } |
3655 | 16 | }, |
3656 | 16 | { &hf_http3_qpack_encoder_opcode_insert_indexed, |
3657 | 16 | { "Insert with Name Reference", "http3.qpack.encoder.opcode.insert_indexed", |
3658 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3659 | 16 | NULL, HFILL } |
3660 | 16 | }, |
3661 | 16 | { &hf_http3_qpack_encoder_opcode_insert_indexed_ref, |
3662 | 16 | { "Name Reference", "http3.qpack.encoder.opcode.insert_indexed.ref", |
3663 | 16 | FT_UINT64, BASE_HEX, NULL, 0x0, |
3664 | 16 | NULL, HFILL } |
3665 | 16 | }, |
3666 | 16 | { &hf_http3_qpack_encoder_opcode_insert_indexed_val, |
3667 | 16 | { "Value", "http3.qpack.encoder.opcode.insert_indexed.val", |
3668 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
3669 | 16 | NULL, HFILL } |
3670 | 16 | }, |
3671 | 16 | { &hf_http3_qpack_encoder_opcode_insert_indexed_hval, |
3672 | 16 | { "Value (Huffman)", "http3.qpack.encoder.opcode.insert_indexed.hval", |
3673 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3674 | 16 | NULL, HFILL } |
3675 | 16 | }, |
3676 | 16 | { &hf_http3_qpack_encoder_opcode_insert, |
3677 | 16 | { "Insert with Literal Name", "http3.qpack.encoder.opcode.insert", |
3678 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3679 | 16 | NULL, HFILL } |
3680 | 16 | }, |
3681 | 16 | { &hf_http3_qpack_encoder_opcode_insert_name, |
3682 | 16 | { "Literal Name", "http3.qpack.encoder.opcode.insert.name", |
3683 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
3684 | 16 | NULL, HFILL } |
3685 | 16 | }, |
3686 | 16 | { &hf_http3_qpack_encoder_opcode_insert_hname, |
3687 | 16 | { "Literal Name (Huffman)", "http3.qpack.encoder.opcode.insert.hname", |
3688 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3689 | 16 | NULL, HFILL } |
3690 | 16 | }, |
3691 | 16 | { &hf_http3_qpack_encoder_opcode_insert_val, |
3692 | 16 | { "Value", "http3.qpack.encoder.opcode.insert.val", |
3693 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
3694 | 16 | NULL, HFILL } |
3695 | 16 | }, |
3696 | 16 | { &hf_http3_qpack_encoder_opcode_insert_hval, |
3697 | 16 | { "Value (Huffman)", "http3.qpack.encoder.opcode.insert.hval", |
3698 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3699 | 16 | NULL, HFILL } |
3700 | 16 | }, |
3701 | 16 | { &hf_http3_qpack_encoder_opcode_duplicate, |
3702 | 16 | { "Duplicate", "http3.qpack.encoder.opcode.duplicate", |
3703 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3704 | 16 | NULL, HFILL } |
3705 | 16 | }, |
3706 | 16 | { &hf_http3_qpack_encoder_opcode_dtable_cap, |
3707 | 16 | { "Set Dynamic Table Capacity", "http3.qpack.encoder.opcode.dtable_cap", |
3708 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3709 | 16 | NULL, HFILL } |
3710 | 16 | }, |
3711 | 16 | { &hf_http3_qpack_encoder_opcode_dtable_cap_val, |
3712 | 16 | { "Capacity", "http3.qpack.encoder.opcode.dtable_cap.val", |
3713 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3714 | 16 | NULL, HFILL } |
3715 | 16 | }, |
3716 | 16 | { &hf_http3_qpack_decoder, |
3717 | 16 | { "QPACK decoder", "http3.qpack.decoder", |
3718 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3719 | 16 | NULL, HFILL } |
3720 | 16 | }, |
3721 | 16 | { &hf_http3_qpack_decoder_opcode_section_ack, |
3722 | 16 | { "Section ACK", "http3.qpack.decoder.opcode.section_ack", |
3723 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3724 | 16 | NULL, HFILL } |
3725 | 16 | }, |
3726 | 16 | { &hf_http3_qpack_decoder_opcode_section_ack_stream_id, |
3727 | 16 | { "Section ACK stream ID", "http3.qpack.decoder.opcode.section_ack.stream_id", |
3728 | 16 | FT_UINT64, BASE_HEX, NULL, 0x0, |
3729 | 16 | NULL, HFILL } |
3730 | 16 | }, |
3731 | 16 | { &hf_http3_qpack_decoder_opcode_cancel_stream, |
3732 | 16 | { "Cancel stream", "http3.qpack.decoder.opcode.cancel_stream", |
3733 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3734 | 16 | NULL, HFILL } |
3735 | 16 | }, |
3736 | 16 | { &hf_http3_qpack_decoder_opcode_cancel_stream_id, |
3737 | 16 | { "Cancel stream stream ID", "http3.qpack.decoder.opcode.cancel_stream.id", |
3738 | 16 | FT_UINT64, BASE_HEX, NULL, 0x0, |
3739 | 16 | NULL, HFILL } |
3740 | 16 | }, |
3741 | 16 | { &hf_http3_qpack_decoder_opcode_icnt_increment, |
3742 | 16 | { "Instruction count increment", "http3.qpack.decoder.opcode.icnt_inc", |
3743 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3744 | 16 | NULL, HFILL } |
3745 | 16 | }, |
3746 | 16 | { &hf_http3_qpack_decoder_opcode_icnt_increment_value, |
3747 | 16 | { "Instruction count increment value", "http3.qpack.decoder.opcode.icnt_inc.val", |
3748 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3749 | 16 | NULL, HFILL } |
3750 | 16 | }, |
3751 | | |
3752 | | /* Settings */ |
3753 | 16 | { &hf_http3_settings, |
3754 | 16 | { "Settings", "http3.settings", |
3755 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, |
3756 | 16 | NULL, HFILL } |
3757 | 16 | }, |
3758 | 16 | { &hf_http3_settings_identifier, |
3759 | 16 | { "Settings Identifier", "http3.settings.id", |
3760 | 16 | FT_UINT64, BASE_HEX|BASE_VAL64_STRING, VALS64(http3_settings_vals), 0x0, |
3761 | 16 | NULL, HFILL } |
3762 | 16 | }, |
3763 | 16 | { &hf_http3_settings_value, |
3764 | 16 | { "Settings Value", "http3.settings.value", |
3765 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3766 | 16 | NULL, HFILL } |
3767 | 16 | }, |
3768 | 16 | { &hf_http3_settings_qpack_max_table_capacity, |
3769 | 16 | { "Max Table Capacity", "http3.settings.qpack.max_table_capacity", |
3770 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3771 | 16 | NULL, HFILL } |
3772 | 16 | }, |
3773 | 16 | { &hf_http3_settings_max_field_section_size, |
3774 | 16 | { "Max header list size", "http3.settings.max_field_section_size", |
3775 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3776 | 16 | "The default value is unlimited.", HFILL } |
3777 | 16 | }, |
3778 | 16 | { &hf_http3_settings_qpack_blocked_streams, |
3779 | 16 | { "Blocked Streams", "http3.settings.qpack.blocked_streams", |
3780 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3781 | 16 | NULL, HFILL } |
3782 | 16 | }, |
3783 | 16 | { &hf_http3_settings_extended_connect, |
3784 | 16 | { "Extended CONNECT", "http3.settings.extended_connect", |
3785 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3786 | 16 | NULL, HFILL } |
3787 | 16 | }, |
3788 | 16 | { &hf_http3_settings_webtransport, |
3789 | 16 | { "WebTransport", "http3.settings.webtransport", |
3790 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3791 | 16 | NULL, HFILL } |
3792 | 16 | }, |
3793 | 16 | { &hf_http3_settings_h3_datagram, |
3794 | 16 | { "H3 DATAGRAM", "http3.settings.h3_datagram", |
3795 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3796 | 16 | NULL, HFILL } |
3797 | 16 | }, |
3798 | 16 | { &hf_http3_settings_h3_datagram_draft04, |
3799 | 16 | { "H3 DATAGRAM Draft04", "http3.settings.h3_datagram_draft04", |
3800 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3801 | 16 | NULL, HFILL } |
3802 | 16 | }, |
3803 | | |
3804 | | /* Priority Update */ |
3805 | 16 | { &hf_http3_priority_update_element_id, |
3806 | 16 | { "Priority Update Element ID", "http3.priority_update_element_id", |
3807 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3808 | 16 | NULL, HFILL } |
3809 | 16 | }, |
3810 | 16 | { &hf_http3_priority_update_field_value, |
3811 | 16 | { "Priority Update Field Value", "http3.priority_update_field_value", |
3812 | 16 | FT_STRING, BASE_NONE, NULL, 0x0, |
3813 | 16 | NULL, HFILL } |
3814 | 16 | }, |
3815 | | |
3816 | | /* Datagram */ |
3817 | 16 | { &hf_http3_datagram, |
3818 | 16 | { "Datagram", "http3.datagram", |
3819 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, |
3820 | 16 | NULL, HFILL } |
3821 | 16 | }, |
3822 | 16 | { &hf_http3_datagram_quarter_stream_id, |
3823 | 16 | { "Quarter Stream ID", "http3.datagram.quarter_stream_id", |
3824 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3825 | 16 | "Request stream id divided by 4", HFILL } |
3826 | 16 | }, |
3827 | 16 | { &hf_http3_datagram_request_stream_id, |
3828 | 16 | { "Associated Request Stream ID", "http3.datagram.request_stream_id", |
3829 | 16 | FT_UINT64, BASE_DEC, NULL, 0x0, |
3830 | 16 | NULL, HFILL } |
3831 | 16 | }, |
3832 | 16 | { &hf_http3_datagram_payload, |
3833 | 16 | { "Datagram Payload", "http3.datagram.payload", |
3834 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, |
3835 | 16 | NULL, HFILL } |
3836 | 16 | }, |
3837 | 16 | }; |
3838 | | |
3839 | 16 | static int *ett[] = {&ett_http3, |
3840 | 16 | &ett_http3_stream_uni, |
3841 | 16 | &ett_http3_stream_bidi, |
3842 | 16 | &ett_http3_frame, |
3843 | 16 | &ett_http3_body_fragment, |
3844 | 16 | &ett_http3_body_fragments, |
3845 | 16 | &ett_http3_encoded_entity, |
3846 | 16 | &ett_http3_settings, |
3847 | 16 | &ett_http3_headers, |
3848 | 16 | &ett_http3_headers_qpack_blocked, |
3849 | 16 | &ett_http3_qpack_update, |
3850 | 16 | &ett_http3_qpack_opcode, |
3851 | 16 | &ett_http3_datagram, |
3852 | 16 | &ett_http3_datagram_stream_id}; |
3853 | | |
3854 | 16 | static ei_register_info ei[] = { |
3855 | 16 | { &ei_http3_unknown_stream_type, |
3856 | 16 | { "http3.unknown_stream_type", PI_UNDECODED, PI_WARN, |
3857 | 16 | "An unknown stream type was encountered", EXPFILL } |
3858 | 16 | }, |
3859 | 16 | { &ei_http3_qpack_failed, |
3860 | 16 | { "http3.qpack_enc_failed", PI_UNDECODED, PI_NOTE, |
3861 | 16 | "Error decoding QPACK buffer", EXPFILL } |
3862 | 16 | }, |
3863 | 16 | { &ei_http3_prefix_int_failed, |
3864 | 16 | { "http3.prefix_int.failed", PI_UNDECODED, PI_WARN, |
3865 | 16 | "Error decoding prefixed integer (too big)", EXPFILL } |
3866 | 16 | }, |
3867 | 16 | { &ei_http3_huffman_failed, |
3868 | 16 | { "http3.huffman.failed", PI_UNDECODED, PI_WARN, |
3869 | 16 | "Error in Huffman decoding", EXPFILL } |
3870 | 16 | }, |
3871 | 16 | { &ei_http3_header_encoded_state , |
3872 | 16 | { "http3.expert.header.encoded_state", PI_DEBUG, PI_NOTE, |
3873 | 16 | "HTTP3 header encoded block", EXPFILL } |
3874 | 16 | }, |
3875 | 16 | { &ei_http3_header_decoding_failed , |
3876 | 16 | { "http3.expert.header_decoding.failed", PI_UNDECODED, PI_NOTE, |
3877 | 16 | "Failed to decode HTTP3 header name/value", EXPFILL } |
3878 | 16 | }, |
3879 | 16 | { &ei_http3_header_decoding_blocked, |
3880 | 16 | { "http3.expert.header_decoding.blocked", PI_UNDECODED, PI_NOTE, |
3881 | 16 | "Failed to decode HTTP3 header name/value (blocked on QPACK)", EXPFILL} |
3882 | 16 | }, |
3883 | 16 | { &ei_http3_header_decoding_no_output, |
3884 | 16 | { "http3.expert.header_decoding.no_output", PI_UNDECODED, PI_NOTE, |
3885 | 16 | "Failed to decode HTTP3 header name/value (QPACK decoder no emission)", EXPFILL} |
3886 | 16 | }, |
3887 | | /* Stopping due to excessive headers is possibly PI_SECURITY |
3888 | | * (decompression bomb or other dangerous implemention). */ |
3889 | 16 | { &ei_http3_header_size, |
3890 | 16 | { "http3.expert.header_decoding.header_size_exceeded", PI_UNDECODED, PI_WARN, |
3891 | 16 | "QPACK decompression stopped after " G_STRINGIFY(QPACK_MAX_HEADER_SIZE) " bytes", EXPFILL} |
3892 | 16 | }, |
3893 | 16 | { &ei_http3_header_transfer_encoding, |
3894 | 16 | { "http3.header.transfer_encoding", PI_PROTOCOL, PI_WARN, |
3895 | 16 | "The Transfer-Encoding header field MUST NOT be used in HTTP/3", EXPFILL} |
3896 | 16 | }, |
3897 | 16 | { &ei_http3_body_decompression_failed, |
3898 | 16 | { "http3.body.content_encoded.failed", PI_UNDECODED, PI_WARN, |
3899 | 16 | "Unable to decompress content-encoded entity", EXPFILL} |
3900 | 16 | }, |
3901 | 16 | { &ei_http3_datagram_invalid_stream_id, |
3902 | 16 | { "http3.expert.datagram.invalid_stream_id", PI_UNDECODED, PI_WARN, |
3903 | 16 | "Failed to decode HTTP3 datagram stream id", EXPFILL} |
3904 | 16 | }, |
3905 | 16 | }; |
3906 | | |
3907 | 16 | proto_http3 = proto_register_protocol("Hypertext Transfer Protocol Version 3", "HTTP3", "http3"); |
3908 | | |
3909 | 16 | proto_register_field_array(proto_http3, hf, array_length(hf)); |
3910 | 16 | proto_register_subtree_array(ett, array_length(ett)); |
3911 | | |
3912 | 16 | module_http3 = prefs_register_protocol(proto_http3, NULL); |
3913 | | |
3914 | 16 | expert_http3 = expert_register_protocol(proto_http3); |
3915 | 16 | expert_register_field_array(expert_http3, ei, array_length(ei)); |
3916 | | |
3917 | 16 | http3_handle = register_dissector("http3", dissect_http3, proto_http3); |
3918 | 16 | http3_datagram_handle = register_dissector("http3.datagram", dissect_http3_datagram, proto_http3); |
3919 | | #ifdef HAVE_NGHTTP3 |
3920 | | /* Fill hash table with static headers */ |
3921 | | register_static_headers(); |
3922 | | |
3923 | | #if defined(HAVE_ZLIB) || defined(HAVE_ZLIBNG) || defined(HAVE_BROTLI) || defined(HAVE_ZSTD) |
3924 | | prefs_register_bool_preference(module_http3, "decompress_body", |
3925 | | "Decompress entity bodies", |
3926 | | "Whether to decompress entity bodies that are compressed " |
3927 | | "using \"Content-Encoding: \"", |
3928 | | &http3_decompress_body); |
3929 | | #else |
3930 | | prefs_register_obsolete_preference(module_http3, "decompress_body"); |
3931 | | #endif |
3932 | | |
3933 | | reassembly_table_register(&http3_body_reassembly_table, |
3934 | | &quic_reassembly_table_functions); |
3935 | | #endif |
3936 | | |
3937 | 16 | http3_follow_tap = register_tap("http3_follow"); |
3938 | | |
3939 | | /* Just use the QUIC functions for now, since the IDs are the same. |
3940 | | * This may change once QUIC multipath is supported. */ |
3941 | 16 | register_follow_stream(proto_http3, "http3_follow", quic_follow_conv_filter, quic_follow_index_filter, udp_follow_address_filter, udp_port_to_display, follow_quic_tap_listener, get_quic_connections_count, quic_get_sub_stream_id); |
3942 | 16 | } |
3943 | | |
3944 | | void |
3945 | | proto_reg_handoff_http3(void) |
3946 | 16 | { |
3947 | | #ifdef HAVE_NGHTTP3 |
3948 | | media_type_dissector_table = find_dissector_table("media_type"); |
3949 | | media_handle = find_dissector_add_dependency("media", proto_http3); |
3950 | | |
3951 | | register_eo_t *http_eo = get_eo_by_name("http"); |
3952 | | if (http_eo) { |
3953 | | http_eo_tap = find_tap_id(get_eo_tap_listener_name(http_eo)); |
3954 | | } |
3955 | | #endif |
3956 | | |
3957 | 16 | dissector_add_string("quic.proto", "h3", http3_handle); |
3958 | 16 | dissector_add_string("quic.proto.datagram", "h3", http3_datagram_handle); |
3959 | 16 | } |
3960 | | |
3961 | | /** |
3962 | | * Implementation of helper functions. |
3963 | | */ |
3964 | | static http3_file_local_ctx *g_http3_file_local_ctx; |
3965 | | |
3966 | | static unsigned |
3967 | | http3_conn_info_hash(const void *key) |
3968 | 0 | { |
3969 | 0 | uint8_t bkey[QUIC_MAX_CID_LENGTH]; |
3970 | 0 | const quic_cid_t *v; |
3971 | 0 | unsigned h = 0; |
3972 | |
|
3973 | 0 | if (key) { |
3974 | 0 | v = (const quic_cid_t *)key; |
3975 | 0 | memset(&bkey[0], 0, QUIC_MAX_CID_LENGTH); |
3976 | 0 | memcpy(&bkey[0], &v->cid[0], MIN(v->len, QUIC_MAX_CID_LENGTH)); |
3977 | 0 | h = wmem_strong_hash(&bkey[0], QUIC_MAX_CID_LENGTH); |
3978 | 0 | } |
3979 | 0 | return h; |
3980 | 0 | } |
3981 | | |
3982 | | static gboolean |
3983 | | http3_conn_info_equal(const void *lhs, const void *rhs) |
3984 | 0 | { |
3985 | 0 | const quic_cid_t *a = (const quic_cid_t *)lhs; |
3986 | 0 | const quic_cid_t *b = (const quic_cid_t *)rhs; |
3987 | 0 | size_t alen = a->len; |
3988 | 0 | size_t blen = b->len; |
3989 | |
|
3990 | 0 | return alen == blen && memcmp(&a->cid[0], &b->cid[0], alen) == 0; |
3991 | 0 | } |
3992 | | |
3993 | | #ifdef HAVE_NGHTTP3 |
3994 | | /* Due to QPACK compression, we may get lots of relatively large |
3995 | | header decoded_header_fields (e.g., 4KiB). Allocating each of them requires lots |
3996 | | of memory. The maximum compression is achieved in QPACK by |
3997 | | referencing header field stored in dynamic table by one or two |
3998 | | bytes. We reduce memory usage by caching header field in this |
3999 | | wmem_map_t to reuse its memory region when we see the same header |
4000 | | field next time. */ |
4001 | | |
4002 | | static size_t |
4003 | | http3_hdrcache_length(const void *vv) |
4004 | | { |
4005 | | const uint8_t *v = (const uint8_t *)vv; |
4006 | | uint32_t namelen, valuelen; |
4007 | | |
4008 | | namelen = pntohu32(v); |
4009 | | valuelen = pntohu32(v + sizeof(namelen) + namelen); |
4010 | | |
4011 | | return namelen + sizeof(namelen) + valuelen + sizeof(valuelen); |
4012 | | } |
4013 | | |
4014 | | static unsigned |
4015 | | http3_hdrcache_hash(const void *key) |
4016 | | { |
4017 | | return wmem_strong_hash((const uint8_t *)key, http3_hdrcache_length(key)); |
4018 | | } |
4019 | | |
4020 | | static gboolean |
4021 | | http3_hdrcache_equal(const void *lhs, const void *rhs) |
4022 | | { |
4023 | | const uint8_t *a = (const uint8_t *)lhs; |
4024 | | const uint8_t *b = (const uint8_t *)rhs; |
4025 | | size_t alen = http3_hdrcache_length(a); |
4026 | | size_t blen = http3_hdrcache_length(b); |
4027 | | |
4028 | | return alen == blen && memcmp(a, b, alen) == 0; |
4029 | | } |
4030 | | #endif |
4031 | | |
4032 | | /* Deallocation callback */ |
4033 | | static bool |
4034 | | http3_file_local_ctx_del_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event _U_, void *user_data _U_) |
4035 | 0 | { |
4036 | 0 | g_http3_file_local_ctx = NULL; |
4037 | 0 | return false; |
4038 | 0 | } |
4039 | | |
4040 | | static http3_file_local_ctx * |
4041 | | http3_get_file_local_ctx(void) |
4042 | 0 | { |
4043 | 0 | if (g_http3_file_local_ctx == NULL) { |
4044 | | /* |
4045 | | * The file-local context hasn't been initialized yet |
4046 | | * for the current file. |
4047 | | */ |
4048 | 0 | g_http3_file_local_ctx = wmem_new(wmem_file_scope(), http3_file_local_ctx); |
4049 | 0 | g_http3_file_local_ctx->conn_info_map = |
4050 | 0 | wmem_map_new(wmem_file_scope(), http3_conn_info_hash, http3_conn_info_equal); |
4051 | | #ifdef HAVE_NGHTTP3 |
4052 | | g_http3_file_local_ctx->hdr_cache_map = |
4053 | | wmem_map_new(wmem_file_scope(), http3_hdrcache_hash, http3_hdrcache_equal); |
4054 | | #endif |
4055 | 0 | wmem_register_callback(wmem_file_scope(), http3_file_local_ctx_del_cb, NULL); |
4056 | 0 | } |
4057 | |
|
4058 | 0 | return g_http3_file_local_ctx; |
4059 | 0 | } |
4060 | | |
4061 | | /* |
4062 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
4063 | | * |
4064 | | * Local variables: |
4065 | | * c-basic-offset: 4 |
4066 | | * tab-width: 8 |
4067 | | * indent-tabs-mode: nil |
4068 | | * End: |
4069 | | * |
4070 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
4071 | | * :indentSize=4:tabSize=8:noTabs=true: |
4072 | | */ |