/src/wireshark/epan/dissectors/packet-websocket.c
Line | Count | Source |
1 | | /* packet-websocket.c |
2 | | * Routines for WebSocket dissection |
3 | | * Copyright 2012, Alexis La Goutte <alexis.lagoutte@gmail.com> |
4 | | * 2015, Peter Wu <peter@lekensteyn.nl> |
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 | | #include "config.h" |
14 | | #include <wsutil/wslog.h> |
15 | | |
16 | | #include <epan/addr_resolv.h> |
17 | | #include <epan/conversation.h> |
18 | | #include <epan/follow.h> |
19 | | #include <epan/proto_data.h> |
20 | | #include <epan/packet.h> |
21 | | #include <epan/expert.h> |
22 | | #include <epan/prefs.h> |
23 | | #include <epan/reassemble.h> |
24 | | #include <wsutil/strtoi.h> |
25 | | #include <wsutil/zlib_compat.h> |
26 | | |
27 | | #include "packet-http.h" |
28 | | #include "packet-tcp.h" |
29 | | |
30 | | /* |
31 | | * The information used comes from: |
32 | | * RFC6455: The WebSocket Protocol |
33 | | * http://www.iana.org/assignments/websocket (last updated 2012-04-12) |
34 | | */ |
35 | | |
36 | | void proto_register_websocket(void); |
37 | | void proto_reg_handoff_websocket(void); |
38 | | |
39 | | static dissector_handle_t websocket_handle; |
40 | | static dissector_handle_t websocket_tcp_handle; |
41 | | static dissector_handle_t text_lines_handle; |
42 | | static dissector_handle_t json_handle; |
43 | | static dissector_handle_t sip_handle; |
44 | | |
45 | 30 | #define WEBSOCKET_NONE 0 |
46 | 15 | #define WEBSOCKET_TEXT 1 |
47 | 15 | #define WEBSOCKET_JSON 2 |
48 | 15 | #define WEBSOCKET_SIP 3 |
49 | | |
50 | | /* Use key values counting down from UINT32_MAX to avoid clash with pkt_info proto_data key */ |
51 | 0 | #define FRAG_DATA_KEY (UINT32_MAX - 0) |
52 | | |
53 | | static int pref_text_type = WEBSOCKET_NONE; |
54 | | static bool pref_decompress = true; |
55 | | |
56 | | #define DEFAULT_MAX_UNMASKED_LEN (1024 * 256) |
57 | | static unsigned pref_max_unmasked_len = DEFAULT_MAX_UNMASKED_LEN; |
58 | | |
59 | | typedef struct { |
60 | | const char *subprotocol; |
61 | | uint16_t server_port; |
62 | | bool permessage_deflate; |
63 | | #ifdef USE_ZLIB_OR_ZLIBNG |
64 | | bool permessage_deflate_ok; |
65 | | int8_t server_wbits; |
66 | | int8_t client_wbits; |
67 | | zlib_streamp server_take_over_context; |
68 | | zlib_streamp client_take_over_context; |
69 | | #endif /* USE_ZLIB_OR_ZLIBNG */ |
70 | | uint32_t frag_id; |
71 | | /* The following two parameters are only valid on the first linear pass. */ |
72 | | uint8_t first_frag_opcode; |
73 | | bool first_frag_pmc; |
74 | | uint8_t http_version; |
75 | | } websocket_conv_t; |
76 | | |
77 | | #ifdef USE_ZLIB_OR_ZLIBNG |
78 | | typedef struct { |
79 | | uint8_t *decompr_payload; |
80 | | unsigned decompr_len; |
81 | | } websocket_packet_t; |
82 | | #endif /* USE_ZLIB_OR_ZLIBNG */ |
83 | | |
84 | | static int websocket_follow_tap; |
85 | | |
86 | | /* Initialize the protocol and registered fields */ |
87 | | static int proto_websocket; |
88 | | |
89 | | static int hf_ws_fin; |
90 | | static int hf_ws_reserved; |
91 | | static int hf_ws_pmc; |
92 | | static int hf_ws_opcode; |
93 | | static int hf_ws_mask; |
94 | | static int hf_ws_payload_length; |
95 | | static int hf_ws_payload_length_ext_16; |
96 | | static int hf_ws_payload_length_ext_64; |
97 | | static int hf_ws_masking_key; |
98 | | static int hf_ws_payload; |
99 | | static int hf_ws_masked_payload; |
100 | | static int hf_ws_payload_continue; |
101 | | static int hf_ws_payload_text; |
102 | | static int hf_ws_payload_close; |
103 | | static int hf_ws_payload_close_status_code; |
104 | | static int hf_ws_payload_close_reason; |
105 | | static int hf_ws_payload_ping; |
106 | | static int hf_ws_payload_pong; |
107 | | static int hf_ws_payload_unknown; |
108 | | static int hf_ws_fragments; |
109 | | static int hf_ws_fragment; |
110 | | static int hf_ws_fragment_overlap; |
111 | | static int hf_ws_fragment_overlap_conflict; |
112 | | static int hf_ws_fragment_multiple_tails; |
113 | | static int hf_ws_fragment_too_long_fragment; |
114 | | static int hf_ws_fragment_error; |
115 | | static int hf_ws_fragment_count; |
116 | | static int hf_ws_reassembled_length; |
117 | | |
118 | | static int ett_ws; |
119 | | static int ett_ws_pl; |
120 | | static int ett_ws_mask; |
121 | | static int ett_ws_control_close; |
122 | | static int ett_ws_fragments; |
123 | | static int ett_ws_fragment; |
124 | | |
125 | | static expert_field ei_ws_payload_unknown; |
126 | | static expert_field ei_ws_decompression_failed; |
127 | | static expert_field ei_ws_not_fully_unmasked; |
128 | | |
129 | 0 | #define WS_CONTINUE 0x0 |
130 | 0 | #define WS_TEXT 0x1 |
131 | 0 | #define WS_BINARY 0x2 |
132 | 0 | #define WS_CLOSE 0x8 |
133 | 0 | #define WS_PING 0x9 |
134 | 0 | #define WS_PONG 0xA |
135 | | |
136 | | static const value_string ws_opcode_vals[] = { |
137 | | { WS_CONTINUE, "Continuation" }, |
138 | | { WS_TEXT, "Text" }, |
139 | | { WS_BINARY, "Binary" }, |
140 | | { WS_CLOSE, "Connection Close" }, |
141 | | { WS_PING, "Ping" }, |
142 | | { WS_PONG, "Pong" }, |
143 | | { 0, NULL} |
144 | | }; |
145 | | |
146 | 15 | #define MASK_WS_FIN 0x80 |
147 | 15 | #define MASK_WS_RSV 0x70 |
148 | 15 | #define MASK_WS_RSV1 0x40 |
149 | 15 | #define MASK_WS_OPCODE 0x0F |
150 | 15 | #define MASK_WS_MASK 0x80 |
151 | 15 | #define MASK_WS_PAYLOAD_LEN 0x7F |
152 | | |
153 | | static const value_string ws_close_status_code_vals[] = { |
154 | | { 1000, "Normal Closure" }, |
155 | | { 1001, "Going Away" }, |
156 | | { 1002, "Protocol error" }, |
157 | | { 1003, "Unsupported Data" }, |
158 | | { 1004, "---Reserved----" }, |
159 | | { 1005, "No Status Rcvd" }, |
160 | | { 1006, "Abnormal Closure" }, |
161 | | { 1007, "Invalid frame payload data" }, |
162 | | { 1008, "Policy Violation" }, |
163 | | { 1009, "Message Too Big" }, |
164 | | { 1010, "Mandatory Ext." }, |
165 | | { 1011, "Internal Server" }, |
166 | | { 1015, "TLS handshake" }, |
167 | | { 0, NULL} |
168 | | }; |
169 | | |
170 | | static const fragment_items ws_frag_items = { |
171 | | &ett_ws_fragments, |
172 | | &ett_ws_fragment, |
173 | | |
174 | | &hf_ws_fragments, |
175 | | &hf_ws_fragment, |
176 | | &hf_ws_fragment_overlap, |
177 | | &hf_ws_fragment_overlap_conflict, |
178 | | &hf_ws_fragment_multiple_tails, |
179 | | &hf_ws_fragment_too_long_fragment, |
180 | | &hf_ws_fragment_error, |
181 | | &hf_ws_fragment_count, |
182 | | NULL, |
183 | | &hf_ws_reassembled_length, |
184 | | /* Reassembled data field */ |
185 | | NULL, |
186 | | "websocket fragments" |
187 | | }; |
188 | | |
189 | | typedef struct { |
190 | | uint8_t opcode; |
191 | | bool pmc; |
192 | | } websocket_frag_t; |
193 | | |
194 | | static dissector_table_t port_subdissector_table; |
195 | | static dissector_table_t protocol_subdissector_table; |
196 | | static heur_dissector_list_t heur_subdissector_list; |
197 | | |
198 | | static reassembly_table ws_reassembly_table; |
199 | | |
200 | | static tvbuff_t * |
201 | | tvb_unmasked(tvbuff_t *tvb, packet_info *pinfo, const unsigned offset, unsigned payload_length, const uint8_t *masking_key) |
202 | 0 | { |
203 | |
|
204 | 0 | uint8_t *data_unmask; |
205 | 0 | unsigned i; |
206 | 0 | const uint8_t *data_mask; |
207 | 0 | unsigned unmasked_length = payload_length > pref_max_unmasked_len ? pref_max_unmasked_len : payload_length; |
208 | |
|
209 | 0 | data_unmask = (uint8_t*)wmem_alloc(pinfo->pool, unmasked_length); |
210 | 0 | data_mask = tvb_get_ptr(tvb, offset, unmasked_length); |
211 | | /* Unmasked(XOR) Data... */ |
212 | 0 | for(i=0; i < unmasked_length; i++) { |
213 | 0 | data_unmask[i] = data_mask[i] ^ masking_key[i%4]; |
214 | 0 | } |
215 | |
|
216 | 0 | return tvb_new_child_real_data(tvb, data_unmask, unmasked_length, payload_length); |
217 | 0 | } |
218 | | |
219 | | #ifdef USE_ZLIB_OR_ZLIBNG |
220 | | static int8_t |
221 | | websocket_extract_wbits(const char *str) |
222 | 0 | { |
223 | 0 | uint8_t wbits; |
224 | 0 | const char *end; |
225 | |
|
226 | 0 | if (str && ws_strtou8(str, &end, &wbits) && |
227 | 0 | (*end == '\0' || strchr(";\t ", *end))) { |
228 | 0 | if (wbits < 8) { |
229 | 0 | wbits = 8; |
230 | 0 | } else if (wbits > 15) { |
231 | 0 | wbits = 15; |
232 | 0 | } |
233 | 0 | } else { |
234 | 0 | wbits = 15; |
235 | 0 | } |
236 | 0 | return -wbits; |
237 | 0 | } |
238 | | |
239 | | static void * |
240 | | websocket_zalloc(void *opaque _U_, unsigned int items, unsigned int size) |
241 | 0 | { |
242 | 0 | return wmem_alloc(wmem_file_scope(), items*size); |
243 | 0 | } |
244 | | |
245 | | static void |
246 | | websocket_zfree(void *opaque _U_, void *addr) |
247 | 0 | { |
248 | 0 | wmem_free(wmem_file_scope(), addr); |
249 | 0 | } |
250 | | static zlib_streamp |
251 | | websocket_init_z_stream_context(int8_t wbits) |
252 | 0 | { |
253 | 0 | zlib_streamp z_strm = wmem_new0(wmem_file_scope(), zlib_stream); |
254 | 0 | z_strm->zalloc = websocket_zalloc; |
255 | 0 | z_strm->zfree = websocket_zfree; |
256 | |
|
257 | 0 | if (ZLIB_PREFIX(inflateInit2)(z_strm, wbits) != Z_OK) { |
258 | 0 | ZLIB_PREFIX(inflateEnd)(z_strm); |
259 | 0 | wmem_free(wmem_file_scope(), z_strm); |
260 | 0 | return NULL; |
261 | 0 | } |
262 | 0 | return z_strm; |
263 | 0 | } |
264 | | |
265 | | /* |
266 | | * Decompress the given buffer using the given zlib context. On success, the |
267 | | * (possibly empty) buffer is stored as "proto data" and true is returned. |
268 | | * Otherwise false is returned. |
269 | | */ |
270 | | |
271 | | /* Because the stream, or at least the sliding window, has to be reused |
272 | | * between messages, this doesn't call the functions in tvbuff_zlib. |
273 | | * XXX - Would it make sense to provide a tvbuff_zlib API call that |
274 | | * takes either a zlib stream pointer or a sliding window/dictionary, |
275 | | * in order to consolidate some code? */ |
276 | | /* Same constants as tvbuff_zlib.c */ |
277 | | #define TVB_Z_MIN_BUFSIZ 32768 |
278 | 0 | #define TVB_Z_MAX_BUFSIZ 1048576 * 10 |
279 | | |
280 | | static bool |
281 | | websocket_uncompress(tvbuff_t* tvb, packet_info* pinfo, zlib_streamp z_strm, tvbuff_t** uncompressed_tvb, uint32_t key) |
282 | 0 | { |
283 | | /* |
284 | | * Decompressing a message: append "0x00 0x00 0xff 0xff" to the end of |
285 | | * message, then apply DEFLATE to the result. |
286 | | * https://tools.ietf.org/html/rfc7692#section-7.2.2 |
287 | | */ |
288 | 0 | uint8_t *decompr_payload = NULL; |
289 | 0 | unsigned decompr_len = 0; |
290 | 0 | unsigned compr_len, decompr_buf_len; |
291 | 0 | uint8_t *compr_payload, *decompr_buf; |
292 | 0 | int err; |
293 | |
|
294 | 0 | compr_len = tvb_captured_length(tvb) + 4; |
295 | 0 | compr_payload = (uint8_t *)wmem_alloc(pinfo->pool, compr_len); |
296 | 0 | tvb_memcpy(tvb, compr_payload, 0, compr_len-4); |
297 | 0 | compr_payload[compr_len-4] = compr_payload[compr_len-3] = 0x00; |
298 | 0 | compr_payload[compr_len-2] = compr_payload[compr_len-1] = 0xff; |
299 | |
|
300 | 0 | if (ckd_mul(&decompr_buf_len, compr_len, 2)) { |
301 | 0 | decompr_buf_len = TVB_Z_MAX_BUFSIZ; |
302 | 0 | } else { |
303 | 0 | decompr_buf_len = CLAMP(decompr_buf_len, TVB_Z_MIN_BUFSIZ, TVB_Z_MAX_BUFSIZ); |
304 | 0 | } |
305 | |
|
306 | 0 | decompr_buf = (uint8_t *)wmem_alloc(pinfo->pool, decompr_buf_len); |
307 | |
|
308 | 0 | z_strm->next_in = compr_payload; |
309 | 0 | z_strm->avail_in = compr_len; |
310 | | /* Decompress all available data. */ |
311 | 0 | do { |
312 | 0 | z_strm->next_out = decompr_buf; |
313 | 0 | z_strm->avail_out = decompr_buf_len; |
314 | |
|
315 | 0 | err = ZLIB_PREFIX(inflate)(z_strm, Z_SYNC_FLUSH); |
316 | |
|
317 | 0 | if (err == Z_OK || err == Z_STREAM_END || err == Z_BUF_ERROR) { |
318 | 0 | unsigned avail_bytes = decompr_buf_len - z_strm->avail_out; |
319 | | /* Note z_strm, and thus z_strm->total_out, does not necessarily get reset |
320 | | * between messages because the same sliding window may be used. */ |
321 | 0 | if (decompr_len + avail_bytes > INT_MAX) { |
322 | | /* Out of room (various tvb and Qt API functions will fail on anything |
323 | | * bigger than a signed int. We could lower this more. (Because the |
324 | | * size of the decompression buffer is clamped, this can't overflow.) */ |
325 | 0 | err = ZLIB_PREFIX(inflateSync)(z_strm); |
326 | | /* This should succeed and find the 00 00 FF FF at the end, but if the |
327 | | * flush point is a sync flush point and not a full flush point, i.e. |
328 | | * the siding window needs to be used for the next message, the stream |
329 | | * probably gets left in a bad state. */ |
330 | 0 | continue; |
331 | 0 | } |
332 | 0 | if (avail_bytes) { |
333 | 0 | decompr_payload = (uint8_t *)wmem_realloc(wmem_file_scope(), decompr_payload, |
334 | 0 | decompr_len + avail_bytes); |
335 | 0 | memcpy(&decompr_payload[decompr_len], decompr_buf, avail_bytes); |
336 | 0 | decompr_len += avail_bytes; |
337 | 0 | } |
338 | 0 | } |
339 | 0 | } while (err == Z_OK); |
340 | |
|
341 | 0 | if (err == Z_STREAM_END || err == Z_BUF_ERROR) { |
342 | | /* Data was (partially) uncompressed. */ |
343 | 0 | websocket_packet_t *pkt_info = wmem_new0(wmem_file_scope(), websocket_packet_t); |
344 | 0 | if (decompr_len > 0) { |
345 | 0 | pkt_info->decompr_payload = decompr_payload; |
346 | 0 | pkt_info->decompr_len = decompr_len; |
347 | 0 | *uncompressed_tvb = tvb_new_child_real_data(tvb, decompr_payload, decompr_len, decompr_len); |
348 | 0 | } |
349 | 0 | p_add_proto_data(wmem_file_scope(), pinfo, proto_websocket, key, pkt_info); |
350 | 0 | return true; |
351 | 0 | } else { |
352 | | /* decompression failed */ |
353 | 0 | wmem_free(wmem_file_scope(), decompr_payload); |
354 | 0 | return false; |
355 | 0 | } |
356 | 0 | } |
357 | | #endif /* USE_ZLIB_OR_ZLIBNG */ |
358 | | |
359 | | static void |
360 | | dissect_websocket_control_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint8_t opcode) |
361 | 0 | { |
362 | 0 | proto_item *ti; |
363 | 0 | proto_tree *subtree; |
364 | 0 | const unsigned offset = 0, length = tvb_reported_length(tvb); |
365 | |
|
366 | 0 | switch (opcode) { |
367 | 0 | case WS_CLOSE: /* Close */ |
368 | 0 | ti = proto_tree_add_item(tree, hf_ws_payload_close, tvb, offset, length, ENC_NA); |
369 | 0 | subtree = proto_item_add_subtree(ti, ett_ws_control_close); |
370 | | /* Close frame MAY contain a body. */ |
371 | 0 | if (length >= 2) { |
372 | 0 | proto_tree_add_item(subtree, hf_ws_payload_close_status_code, tvb, offset, 2, ENC_BIG_ENDIAN); |
373 | 0 | if (length > 2) |
374 | 0 | proto_tree_add_item(subtree, hf_ws_payload_close_reason, tvb, offset+2, length-2, ENC_UTF_8); |
375 | 0 | } |
376 | 0 | break; |
377 | | |
378 | 0 | case WS_PING: /* Ping */ |
379 | 0 | proto_tree_add_item(tree, hf_ws_payload_ping, tvb, offset, length, ENC_NA); |
380 | 0 | break; |
381 | | |
382 | 0 | case WS_PONG: /* Pong */ |
383 | 0 | proto_tree_add_item(tree, hf_ws_payload_pong, tvb, offset, length, ENC_NA); |
384 | 0 | break; |
385 | | |
386 | 0 | default: /* Unknown */ |
387 | 0 | ti = proto_tree_add_item(tree, hf_ws_payload_unknown, tvb, offset, length, ENC_NA); |
388 | 0 | expert_add_info_format(pinfo, ti, &ei_ws_payload_unknown, "Dissector for Websocket Opcode (%d)" |
389 | 0 | " code not implemented, Contact Wireshark developers" |
390 | 0 | " if you want this supported", opcode); |
391 | 0 | break; |
392 | 0 | } |
393 | 0 | } |
394 | | |
395 | | static void |
396 | | dissect_websocket_data_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_tree *pl_tree, uint8_t opcode, websocket_conv_t *websocket_conv, bool pmc _U_, int raw_offset _U_, bool from_server _U_) |
397 | 0 | { |
398 | 0 | proto_item *ti; |
399 | 0 | dissector_handle_t handle = NULL; |
400 | 0 | heur_dtbl_entry_t *hdtbl_entry; |
401 | |
|
402 | 0 | if (pinfo->fragmented) { |
403 | | /* Skip dissecting fragmented payload data. */ |
404 | 0 | return; |
405 | 0 | } |
406 | | |
407 | | /* try to find a dissector which accepts the data. */ |
408 | 0 | if (websocket_conv->subprotocol) { |
409 | 0 | handle = dissector_get_string_handle(protocol_subdissector_table, websocket_conv->subprotocol); |
410 | 0 | } else if (websocket_conv->server_port) { |
411 | 0 | handle = dissector_get_uint_handle(port_subdissector_table, websocket_conv->server_port); |
412 | 0 | } |
413 | |
|
414 | 0 | #ifdef USE_ZLIB_OR_ZLIBNG |
415 | 0 | if (websocket_conv->permessage_deflate_ok && pmc) { |
416 | 0 | tvbuff_t *uncompressed = NULL; |
417 | 0 | bool uncompress_ok = false; |
418 | |
|
419 | 0 | if (!PINFO_FD_VISITED(pinfo)) { |
420 | 0 | zlib_streamp z_strm; |
421 | 0 | int8_t wbits; |
422 | |
|
423 | 0 | if (from_server) { |
424 | 0 | z_strm = websocket_conv->server_take_over_context; |
425 | 0 | wbits = websocket_conv->server_wbits; |
426 | 0 | } else { |
427 | 0 | z_strm = websocket_conv->client_take_over_context; |
428 | 0 | wbits = websocket_conv->client_wbits; |
429 | 0 | } |
430 | |
|
431 | 0 | if (z_strm) { |
432 | 0 | uncompress_ok = websocket_uncompress(tvb, pinfo, z_strm, &uncompressed, raw_offset); |
433 | 0 | } else { |
434 | | /* no context take over, initialize a new context */ |
435 | 0 | z_strm = wmem_new0(pinfo->pool, zlib_stream); |
436 | 0 | if (ZLIB_PREFIX(inflateInit2)(z_strm, wbits) == Z_OK) { |
437 | 0 | uncompress_ok = websocket_uncompress(tvb, pinfo, z_strm, &uncompressed, raw_offset); |
438 | 0 | } |
439 | 0 | ZLIB_PREFIX(inflateEnd)(z_strm); |
440 | 0 | } |
441 | 0 | } else { |
442 | 0 | websocket_packet_t *pkt_info = |
443 | 0 | (websocket_packet_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_websocket, raw_offset); |
444 | 0 | if (pkt_info) { |
445 | 0 | uncompress_ok = true; |
446 | 0 | if (pkt_info->decompr_len > 0) { |
447 | 0 | uncompressed = tvb_new_child_real_data(tvb, pkt_info->decompr_payload, pkt_info->decompr_len, pkt_info->decompr_len); |
448 | 0 | } |
449 | 0 | } |
450 | 0 | } |
451 | |
|
452 | 0 | if (!uncompress_ok) { |
453 | 0 | proto_tree_add_expert(tree, pinfo, &ei_ws_decompression_failed, tvb, 0, -1); |
454 | 0 | return; |
455 | 0 | } |
456 | 0 | if (uncompressed) { |
457 | 0 | add_new_data_source(pinfo, uncompressed, "Decompressed payload"); |
458 | 0 | tvb = uncompressed; |
459 | 0 | } |
460 | 0 | } |
461 | 0 | #endif /* USE_ZLIB_OR_ZLIBNG */ |
462 | | |
463 | 0 | if (have_tap_listener(websocket_follow_tap)) { |
464 | 0 | tap_queue_packet(websocket_follow_tap, pinfo, tvb); |
465 | 0 | } |
466 | |
|
467 | 0 | if (handle) { |
468 | 0 | call_dissector_only(handle, tvb, pinfo, tree, NULL); |
469 | 0 | return; /* handle found, assume dissector took care of it. */ |
470 | 0 | } else if (dissector_try_heuristic(heur_subdissector_list, tvb, pinfo, tree, &hdtbl_entry, NULL)) { |
471 | 0 | return; /* heuristics dissector handled it. */ |
472 | 0 | } |
473 | | |
474 | | /* no dissector wanted it, try to print something appropriate. */ |
475 | 0 | switch (opcode) { |
476 | 0 | case WS_TEXT: /* Text */ |
477 | 0 | { |
478 | 0 | proto_tree_add_item(pl_tree, hf_ws_payload_text, tvb, 0, -1, ENC_UTF_8); |
479 | 0 | const char *saved_match_string = pinfo->match_string; |
480 | |
|
481 | 0 | pinfo->match_string = NULL; |
482 | 0 | switch (pref_text_type) { |
483 | 0 | case WEBSOCKET_TEXT: |
484 | 0 | case WEBSOCKET_NONE: |
485 | 0 | default: |
486 | | /* Assume that most text protocols are line-based. */ |
487 | 0 | call_dissector(text_lines_handle, tvb, pinfo, tree); |
488 | 0 | break; |
489 | 0 | case WEBSOCKET_JSON: |
490 | 0 | call_dissector(json_handle, tvb, pinfo, tree); |
491 | 0 | break; |
492 | 0 | case WEBSOCKET_SIP: |
493 | 0 | call_dissector(sip_handle, tvb, pinfo, tree); |
494 | 0 | break; |
495 | 0 | } |
496 | 0 | pinfo->match_string = saved_match_string; |
497 | 0 | } |
498 | 0 | break; |
499 | | |
500 | 0 | case WS_BINARY: /* Binary */ |
501 | 0 | call_data_dissector(tvb, pinfo, tree); |
502 | 0 | break; |
503 | | |
504 | 0 | default: /* Unknown */ |
505 | 0 | ti = proto_tree_add_item(pl_tree, hf_ws_payload_unknown, tvb, 0, -1, ENC_NA); |
506 | 0 | expert_add_info_format(pinfo, ti, &ei_ws_payload_unknown, "Dissector for Websocket Opcode (%d)" |
507 | 0 | " code not implemented, Contact Wireshark developers" |
508 | 0 | " if you want this supported", opcode); |
509 | 0 | break; |
510 | 0 | } |
511 | 0 | } |
512 | | |
513 | | static void |
514 | | websocket_parse_extensions(websocket_conv_t *websocket_conv, const char *str) |
515 | 0 | { |
516 | | /* |
517 | | * Grammar for the header: |
518 | | * |
519 | | * Sec-WebSocket-Extensions = extension-list |
520 | | * extension-list = 1#extension |
521 | | * extension = extension-token *( ";" extension-param ) |
522 | | * extension-token = registered-token |
523 | | * registered-token = token |
524 | | * extension-param = token [ "=" (token | quoted-string) ] |
525 | | */ |
526 | | |
527 | | /* |
528 | | * RFC 7692 permessage-deflate parsing. |
529 | | * "x-webkit-deflate-frame" is an alias used by some versions of Safari browser |
530 | | */ |
531 | |
|
532 | 0 | websocket_conv->permessage_deflate = !!strstr(str, "permessage-deflate") |
533 | 0 | || !!strstr(str, "x-webkit-deflate-frame"); |
534 | 0 | #ifdef USE_ZLIB_OR_ZLIBNG |
535 | 0 | websocket_conv->permessage_deflate_ok = pref_decompress && |
536 | 0 | websocket_conv->permessage_deflate; |
537 | 0 | if (websocket_conv->permessage_deflate_ok) { |
538 | 0 | websocket_conv->server_wbits = |
539 | 0 | websocket_extract_wbits(strstr(str, "server_max_window_bits=")); |
540 | 0 | if (!strstr(str, "server_no_context_takeover")) { |
541 | 0 | websocket_conv->server_take_over_context = |
542 | 0 | websocket_init_z_stream_context(websocket_conv->server_wbits); |
543 | 0 | } |
544 | 0 | websocket_conv->client_wbits = |
545 | 0 | websocket_extract_wbits(strstr(str, "client_max_window_bits=")); |
546 | 0 | if (!strstr(str, "client_no_context_takeover")) { |
547 | 0 | websocket_conv->client_take_over_context = |
548 | 0 | websocket_init_z_stream_context(websocket_conv->client_wbits); |
549 | 0 | } |
550 | 0 | } |
551 | 0 | #endif /* USE_ZLIB_OR_ZLIBNG */ |
552 | 0 | } |
553 | | |
554 | | static void |
555 | | dissect_websocket_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_tree *ws_tree, bool fin, uint8_t opcode, websocket_conv_t *websocket_conv, bool pmc, int raw_offset, unsigned masked_payload_length, bool from_server) |
556 | 0 | { |
557 | 0 | const unsigned offset = 0, length = tvb_reported_length(tvb); |
558 | 0 | const unsigned capture_length = tvb_captured_length(tvb); |
559 | 0 | proto_item *ti; |
560 | 0 | proto_tree *pl_tree; |
561 | 0 | tvbuff_t *tvb_appdata; |
562 | 0 | tvbuff_t *frag_tvb = NULL; |
563 | | |
564 | | /* Payload */ |
565 | 0 | ti = proto_tree_add_item(ws_tree, hf_ws_payload, tvb, offset, length, ENC_NA); |
566 | 0 | pl_tree = proto_item_add_subtree(ti, ett_ws_pl); |
567 | |
|
568 | 0 | if (masked_payload_length > capture_length) { |
569 | 0 | expert_add_info_format(pinfo, ti, &ei_ws_not_fully_unmasked, "Payload not fully unmasked. " |
570 | 0 | "%u bytes not yet unmasked due to the preference of max unmasked length limit (%u bytes).", |
571 | 0 | masked_payload_length - capture_length, pref_max_unmasked_len); |
572 | 0 | } |
573 | | |
574 | | /* Extension Data */ |
575 | | /* TODO: Add dissector of Extension (not extension available for the moment...) */ |
576 | |
|
577 | 0 | if (opcode & 8) { /* Control frames have MSB set. */ |
578 | 0 | dissect_websocket_control_frame(tvb, pinfo, pl_tree, opcode); |
579 | 0 | return; |
580 | 0 | } |
581 | | |
582 | | /* According to RFC 6455 5.5, control frames can be interjected in the |
583 | | * middle of a fragmented message, can have a payload, and MUST NOT be |
584 | | * fragmented. So we do not save the first frag opcode and pmc until now. |
585 | | */ |
586 | | |
587 | 0 | if (!PINFO_FD_VISITED(pinfo) && opcode != WS_CONTINUE && !fin) { |
588 | | /* This is a first frame of a multifragment message. The first time |
589 | | * we see this frame, store information needed for later fragments |
590 | | * of the same message. |
591 | | */ |
592 | |
|
593 | 0 | websocket_conv->first_frag_opcode = opcode; |
594 | 0 | websocket_conv->first_frag_pmc = pmc; |
595 | 0 | } |
596 | |
|
597 | 0 | bool save_fragmented = pinfo->fragmented; |
598 | |
|
599 | 0 | if (!fin || opcode == WS_CONTINUE) { |
600 | | /* Fragmented data frame */ |
601 | 0 | fragment_head *frag_msg; |
602 | |
|
603 | 0 | pinfo->fragmented = true; |
604 | | |
605 | | /* XXX - The same fragment ID is used for all reassemblies on this |
606 | | * conversation. Note this doesn't work properly if more than one |
607 | | * reassembly for the same conversation completes in the same frame |
608 | | * in the capture file. That should be rare in practice, though. |
609 | | * (This is a common issue with fragment_add_seq_next.) |
610 | | */ |
611 | 0 | frag_msg = fragment_add_seq_next(&ws_reassembly_table, tvb, offset, |
612 | 0 | pinfo, websocket_conv->frag_id, |
613 | 0 | NULL, tvb_captured_length_remaining(tvb, offset), |
614 | 0 | !fin); |
615 | 0 | frag_tvb = process_reassembled_data(tvb, offset, pinfo, |
616 | 0 | "Reassembled Message", frag_msg, &ws_frag_items, |
617 | 0 | NULL, tree); |
618 | 0 | } |
619 | |
|
620 | 0 | if (frag_tvb) { |
621 | | /* Fragments were fully reassembled. */ |
622 | 0 | tvb_appdata = frag_tvb; |
623 | |
|
624 | 0 | websocket_frag_t *frag_data; |
625 | |
|
626 | 0 | if (!PINFO_FD_VISITED(pinfo)) { |
627 | | /* First time fragments fully reassembled, store opcode from first fragment */ |
628 | 0 | frag_data = wmem_new(wmem_file_scope(), websocket_frag_t); |
629 | 0 | frag_data->opcode = websocket_conv->first_frag_opcode; |
630 | 0 | frag_data->pmc = websocket_conv->first_frag_pmc; |
631 | 0 | p_add_proto_data(wmem_file_scope(), pinfo, proto_websocket, FRAG_DATA_KEY, frag_data); |
632 | 0 | } else { |
633 | | /* Lookup opcode and pmc from first fragment */ |
634 | 0 | frag_data = p_get_proto_data(wmem_file_scope(), pinfo, proto_websocket, FRAG_DATA_KEY); |
635 | 0 | DISSECTOR_ASSERT(frag_data); |
636 | 0 | } |
637 | 0 | opcode = frag_data->opcode; |
638 | 0 | pmc = frag_data->pmc; |
639 | 0 | } else { |
640 | | /* Right now this is exactly the same, this may change when exts. are added. |
641 | | tvb_appdata = tvb_new_subset_length(tvb, offset, length); |
642 | | */ |
643 | 0 | tvb_appdata = tvb; |
644 | 0 | } |
645 | | |
646 | | /* Application Data */ |
647 | |
|
648 | 0 | if (pinfo->fragmented && opcode == WS_CONTINUE) { |
649 | | /* Not last fragment, dissect continue fragment as is */ |
650 | 0 | proto_tree_add_item(tree, hf_ws_payload_continue, tvb_appdata, offset, length, ENC_NA); |
651 | 0 | return; |
652 | 0 | } |
653 | | |
654 | 0 | dissect_websocket_data_frame(tvb_appdata, pinfo, tree, pl_tree, opcode, websocket_conv, pmc, raw_offset, from_server); |
655 | 0 | pinfo->fragmented = save_fragmented; |
656 | 0 | } |
657 | | |
658 | | static int |
659 | | dissect_websocket_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) |
660 | 0 | { |
661 | 0 | static uint32_t frag_id_counter = 0; |
662 | 0 | proto_item *ti, *ti_len; |
663 | 0 | uint8_t opcode; |
664 | 0 | bool mask, fin, pmc = false; |
665 | 0 | unsigned short_length, payload_length; |
666 | 0 | unsigned payload_offset, mask_offset; |
667 | 0 | proto_tree *ws_tree; |
668 | 0 | const uint8_t *masking_key = NULL; |
669 | 0 | tvbuff_t *tvb_payload; |
670 | 0 | conversation_t *conv; |
671 | 0 | websocket_conv_t *websocket_conv; |
672 | |
|
673 | 0 | const http_upgrade_info_t *http_info = (http_upgrade_info_t *)data; |
674 | | /* |
675 | | * If this is a new Websocket session, try to parse HTTP Sec-Websocket-* |
676 | | * headers once. |
677 | | */ |
678 | 0 | conv = find_or_create_conversation(pinfo); |
679 | 0 | websocket_conv = (websocket_conv_t *)conversation_get_proto_data(conv, proto_websocket); |
680 | 0 | if (!websocket_conv) { |
681 | 0 | websocket_conv = wmem_new0(wmem_file_scope(), websocket_conv_t); |
682 | 0 | websocket_conv->frag_id = ++frag_id_counter; |
683 | |
|
684 | 0 | if (http_info) { |
685 | 0 | websocket_conv->subprotocol = http_info->get_header_value(pinfo, "sec-websocket-protocol", !http_info->from_server); |
686 | 0 | websocket_conv->server_port = http_info->server_port; |
687 | 0 | const char *websocket_extensions = http_info->get_header_value(pinfo, "sec-websocket-extensions", !http_info->from_server); |
688 | 0 | if (websocket_extensions) { |
689 | 0 | websocket_parse_extensions(websocket_conv, websocket_extensions); |
690 | 0 | } |
691 | 0 | websocket_conv->http_version = http_info->http_version; |
692 | 0 | } else if (pinfo->match_uint == pinfo->srcport || pinfo->match_uint == pinfo->destport) { |
693 | | /* The session was not set up by HTTP upgrade, but by Decode As. |
694 | | * Assume the matched port is the server port. */ |
695 | 0 | websocket_conv->server_port = (uint16_t)pinfo->match_uint; |
696 | 0 | } else { |
697 | | /* match_uint is not one of the ports, which means the session was |
698 | | * set up by the heuristic Websocket dissector. */ |
699 | 0 | uint32_t low_port, high_port; |
700 | 0 | if (pinfo->srcport > pinfo->destport) { |
701 | 0 | low_port = pinfo->destport; |
702 | 0 | high_port = pinfo->srcport; |
703 | 0 | } else { |
704 | 0 | low_port = pinfo->srcport; |
705 | 0 | high_port = pinfo->destport; |
706 | 0 | } |
707 | 0 | if (dissector_get_uint_handle(port_subdissector_table, low_port)) { |
708 | 0 | websocket_conv->server_port = (uint16_t)low_port; |
709 | 0 | } else if (dissector_get_uint_handle(port_subdissector_table, high_port)) { |
710 | 0 | websocket_conv->server_port = (uint16_t)high_port; |
711 | 0 | } |
712 | 0 | } |
713 | |
|
714 | 0 | conversation_add_proto_data(conv, proto_websocket, websocket_conv); |
715 | 0 | } |
716 | |
|
717 | 0 | const bool from_server = http_info ? http_info->from_server : websocket_conv->server_port == pinfo->srcport; |
718 | |
|
719 | 0 | short_length = tvb_get_uint8(tvb, 1) & MASK_WS_PAYLOAD_LEN; |
720 | 0 | mask_offset = 2; |
721 | 0 | if (short_length == 126) { |
722 | 0 | payload_length = tvb_get_ntohs(tvb, 2); |
723 | 0 | mask_offset += 2; |
724 | 0 | } else if (short_length == 127) { |
725 | | /* warning C4244: '=' : conversion from 'uint64_t' to 'unsigned ', possible loss of data */ |
726 | 0 | payload_length = (unsigned)tvb_get_ntoh64(tvb, 2); |
727 | 0 | mask_offset += 8; |
728 | 0 | } else { |
729 | 0 | payload_length = short_length; |
730 | 0 | } |
731 | | |
732 | | /* Mask */ |
733 | 0 | mask = (tvb_get_uint8(tvb, 1) & MASK_WS_MASK) != 0; |
734 | 0 | payload_offset = mask_offset + (mask ? 4 : 0); |
735 | | |
736 | | /* HTTP/2 and HTTP/3 can carry more than one protocol in a given packet */ |
737 | 0 | if (websocket_conv->http_version < 2) { |
738 | 0 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "WebSocket"); |
739 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "WebSocket"); |
740 | 0 | } else { |
741 | 0 | col_append_str(pinfo->cinfo, COL_INFO, ", WebSocket"); |
742 | 0 | } |
743 | |
|
744 | 0 | ti = proto_tree_add_item(tree, proto_websocket, tvb, 0, payload_offset, ENC_NA); |
745 | 0 | ws_tree = proto_item_add_subtree(ti, ett_ws); |
746 | | |
747 | | /* Flags */ |
748 | 0 | proto_tree_add_item_ret_boolean(ws_tree, hf_ws_fin, tvb, 0, 1, ENC_NA, &fin); |
749 | 0 | proto_tree_add_item(ws_tree, hf_ws_reserved, tvb, 0, 1, ENC_BIG_ENDIAN); |
750 | 0 | if (websocket_conv->permessage_deflate) { |
751 | | /* RSV1 is Per-Message Compressed bit (RFC 7692). */ |
752 | 0 | proto_tree_add_item_ret_boolean(ws_tree, hf_ws_pmc, tvb, 0, 1, ENC_BIG_ENDIAN, &pmc); |
753 | 0 | } |
754 | | |
755 | | /* Opcode */ |
756 | 0 | proto_tree_add_item_ret_uint8(ws_tree, hf_ws_opcode, tvb, 0, 1, ENC_BIG_ENDIAN, &opcode); |
757 | 0 | col_append_fstr(pinfo->cinfo, COL_INFO, " %s", val_to_str_const(opcode, ws_opcode_vals, "Unknown Opcode")); |
758 | 0 | col_append_str(pinfo->cinfo, COL_INFO, fin ? " [FIN]" : "[FRAGMENT] "); |
759 | | |
760 | | /* Add Mask bit to the tree */ |
761 | 0 | proto_tree_add_item(ws_tree, hf_ws_mask, tvb, 1, 1, ENC_NA); |
762 | 0 | col_append_str(pinfo->cinfo, COL_INFO, mask ? " [MASKED]" : " "); |
763 | | |
764 | | /* (Extended) Payload Length */ |
765 | 0 | ti_len = proto_tree_add_item(ws_tree, hf_ws_payload_length, tvb, 1, 1, ENC_BIG_ENDIAN); |
766 | 0 | if (short_length == 126) { |
767 | 0 | proto_item_append_text(ti_len, " Extended Payload Length (16 bits)"); |
768 | 0 | proto_tree_add_item(ws_tree, hf_ws_payload_length_ext_16, tvb, 2, 2, ENC_BIG_ENDIAN); |
769 | 0 | } |
770 | 0 | else if (short_length == 127) { |
771 | 0 | proto_item_append_text(ti_len, " Extended Payload Length (64 bits)"); |
772 | 0 | proto_tree_add_item(ws_tree, hf_ws_payload_length_ext_64, tvb, 2, 8, ENC_BIG_ENDIAN); |
773 | 0 | } |
774 | | |
775 | | /* Masking-key */ |
776 | 0 | if (mask) { |
777 | 0 | proto_tree_add_item(ws_tree, hf_ws_masking_key, tvb, mask_offset, 4, ENC_NA); |
778 | 0 | masking_key = tvb_get_ptr(tvb, mask_offset, 4); |
779 | 0 | } |
780 | |
|
781 | 0 | if (payload_length > 0) { |
782 | | /* Always unmask payload data before analysing it. */ |
783 | 0 | if (mask) { |
784 | 0 | proto_tree_add_item(ws_tree, hf_ws_masked_payload, tvb, payload_offset, payload_length, ENC_NA); |
785 | 0 | tvb_payload = tvb_unmasked(tvb, pinfo, payload_offset, payload_length, masking_key); |
786 | 0 | add_new_data_source(pinfo, tvb_payload, "Unmasked data"); |
787 | 0 | } else { |
788 | 0 | tvb_payload = tvb_new_subset_length(tvb, payload_offset, payload_length); |
789 | 0 | } |
790 | 0 | dissect_websocket_payload(tvb_payload, pinfo, tree, ws_tree, fin, opcode, websocket_conv, pmc, tvb_raw_offset(tvb), (mask ? payload_length : 0), from_server); |
791 | 0 | } |
792 | |
|
793 | 0 | return tvb_captured_length(tvb); |
794 | 0 | } |
795 | | |
796 | | static unsigned |
797 | | get_websocket_frame_length(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_) |
798 | 0 | { |
799 | 0 | unsigned frame_length, payload_length; |
800 | 0 | bool mask; |
801 | |
|
802 | 0 | frame_length = 2; /* flags, opcode and Payload length */ |
803 | 0 | mask = tvb_get_uint8(tvb, offset + 1) & MASK_WS_MASK; |
804 | |
|
805 | 0 | payload_length = tvb_get_uint8(tvb, offset + 1) & MASK_WS_PAYLOAD_LEN; |
806 | 0 | offset += 2; /* Skip flags, opcode and Payload length */ |
807 | | |
808 | | /* Check for Extended Payload Length. */ |
809 | 0 | if (payload_length == 126) { |
810 | 0 | if (tvb_reported_length_remaining(tvb, offset) < 2) |
811 | 0 | return 0; /* Need more data. */ |
812 | | |
813 | 0 | payload_length = tvb_get_ntohs(tvb, offset); |
814 | 0 | frame_length += 2; /* Extended payload length */ |
815 | 0 | } else if (payload_length == 127) { |
816 | 0 | if (tvb_reported_length_remaining(tvb, offset) < 8) |
817 | 0 | return 0; /* Need more data. */ |
818 | | |
819 | 0 | payload_length = (unsigned)tvb_get_ntoh64(tvb, offset); |
820 | 0 | frame_length += 8; /* Extended payload length */ |
821 | 0 | } |
822 | | |
823 | 0 | if (mask) |
824 | 0 | frame_length += 4; /* Masking-key */ |
825 | 0 | frame_length += payload_length; /* Payload data */ |
826 | 0 | return frame_length; |
827 | 0 | } |
828 | | |
829 | | static int |
830 | | dissect_websocket(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) |
831 | 0 | { |
832 | | /* Need at least two bytes for flags, opcode and Payload length. */ |
833 | 0 | tcp_dissect_pdus(tvb, pinfo, tree, true, 2, |
834 | 0 | get_websocket_frame_length, dissect_websocket_frame, data); |
835 | 0 | return tvb_captured_length(tvb); |
836 | 0 | } |
837 | | |
838 | | static int |
839 | | dissect_websocket_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) |
840 | 0 | { |
841 | 0 | return dissect_websocket(tvb, pinfo, tree, NULL); |
842 | 0 | } |
843 | | |
844 | | static bool |
845 | | test_websocket(packet_info* pinfo _U_, tvbuff_t* tvb, int offset _U_, void* data _U_) |
846 | 0 | { |
847 | 0 | unsigned buffer_length = tvb_captured_length(tvb); |
848 | | |
849 | | // At least 2 bytes are required for a websocket header |
850 | 0 | if (buffer_length < 2) |
851 | 0 | { |
852 | 0 | return false; |
853 | 0 | } |
854 | 0 | uint8_t first_byte = tvb_get_uint8(tvb, 0); |
855 | 0 | uint8_t second_byte = tvb_get_uint8(tvb, 1); |
856 | | |
857 | | // Reserved bits RSV1, RSV2 and RSV3 need to be 0 |
858 | 0 | if ((first_byte & 0x70) > 0) |
859 | 0 | { |
860 | 0 | return false; |
861 | 0 | } |
862 | | |
863 | 0 | uint8_t op_code = first_byte & 0x0F; |
864 | | |
865 | | // op_code must be one of WS_CONTINUE, WS_TEXT, WS_BINARY, WS_CLOSE, WS_PING or WS_PONG |
866 | 0 | if (!(op_code == WS_CONTINUE || op_code == WS_TEXT || op_code == WS_BINARY || op_code == WS_CLOSE || op_code == WS_PING || op_code == WS_PONG)) |
867 | 0 | { |
868 | 0 | return false; |
869 | 0 | } |
870 | | |
871 | | // It is necessary to prevent that HTTP connection setups are treated as websocket. |
872 | | // If HTTP catches and it upgrades to websocket then HTTP takes care that websocket dissector gets called for this stream. |
873 | | // If first two byte start with printable characters from the alphabet it's likely that it is part of a HTTP connection setup. |
874 | 0 | if (((first_byte >= 'a' && first_byte <= 'z') || (first_byte >= 'A' && first_byte <= 'Z')) && |
875 | 0 | ((second_byte >= 'a' && second_byte <= 'z') || (second_byte >= 'A' && second_byte <= 'Z'))) |
876 | 0 | { |
877 | 0 | return false; |
878 | 0 | } |
879 | | |
880 | 0 | return true; |
881 | 0 | } |
882 | | |
883 | | static bool |
884 | | dissect_websocket_heur_tcp(tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, void* data) |
885 | 0 | { |
886 | 0 | if (!test_websocket(pinfo, tvb, 0, data)) |
887 | 0 | { |
888 | 0 | return false; |
889 | 0 | } |
890 | 0 | conversation_t* conversation = find_or_create_conversation(pinfo); |
891 | 0 | conversation_set_dissector(conversation, websocket_tcp_handle); |
892 | |
|
893 | 0 | tcp_dissect_pdus(tvb, pinfo, tree, true, 2, get_websocket_frame_length, dissect_websocket_frame, NULL); |
894 | 0 | return true; |
895 | 0 | } |
896 | | |
897 | | void |
898 | | proto_register_websocket(void) |
899 | 15 | { |
900 | | |
901 | 15 | static hf_register_info hf[] = { |
902 | 15 | { &hf_ws_fin, |
903 | 15 | { "Fin", "websocket.fin", |
904 | 15 | FT_BOOLEAN, 8, NULL, MASK_WS_FIN, |
905 | 15 | "Indicates that this is the final fragment in a message", HFILL } |
906 | 15 | }, |
907 | 15 | { &hf_ws_reserved, |
908 | 15 | { "Reserved", "websocket.rsv", |
909 | 15 | FT_UINT8, BASE_HEX, NULL, MASK_WS_RSV, |
910 | 15 | "Must be zero", HFILL } |
911 | 15 | }, |
912 | 15 | { &hf_ws_pmc, |
913 | 15 | { "Per-Message Compressed", "websocket.pmc", |
914 | 15 | FT_BOOLEAN, 8, NULL, MASK_WS_RSV1, |
915 | 15 | "Whether a message is compressed or not", HFILL } |
916 | 15 | }, |
917 | 15 | { &hf_ws_opcode, |
918 | 15 | { "Opcode", "websocket.opcode", |
919 | 15 | FT_UINT8, BASE_DEC, VALS(ws_opcode_vals), MASK_WS_OPCODE, |
920 | 15 | "Defines the interpretation of the Payload data", HFILL } |
921 | 15 | }, |
922 | 15 | { &hf_ws_mask, |
923 | 15 | { "Mask", "websocket.mask", |
924 | 15 | FT_BOOLEAN, 8, NULL, MASK_WS_MASK, |
925 | 15 | "Defines whether the Payload data is masked", HFILL } |
926 | 15 | }, |
927 | 15 | { &hf_ws_payload_length, |
928 | 15 | { "Payload length", "websocket.payload_length", |
929 | 15 | FT_UINT8, BASE_DEC, NULL, MASK_WS_PAYLOAD_LEN, |
930 | 15 | "The length of the Payload data", HFILL } |
931 | 15 | }, |
932 | 15 | { &hf_ws_payload_length_ext_16, |
933 | 15 | { "Extended Payload length (16 bits)", "websocket.payload_length_ext_16", |
934 | 15 | FT_UINT16, BASE_DEC, NULL, 0x0, |
935 | 15 | "The length (16 bits) of the Payload data", HFILL } |
936 | 15 | }, |
937 | 15 | { &hf_ws_payload_length_ext_64, |
938 | 15 | { "Extended Payload length (64 bits)", "websocket.payload_length_ext_64", |
939 | 15 | FT_UINT64, BASE_DEC, NULL, 0x0, |
940 | 15 | "The length (64 bits) of the Payload data", HFILL } |
941 | 15 | }, |
942 | 15 | { &hf_ws_masking_key, |
943 | 15 | { "Masking-Key", "websocket.masking_key", |
944 | 15 | FT_BYTES, BASE_NONE, NULL, 0x0, |
945 | 15 | "All frames sent from the client to the server are masked by a 32-bit value that is contained within the frame", HFILL } |
946 | 15 | }, |
947 | 15 | { &hf_ws_payload, |
948 | 15 | { "Payload", "websocket.payload", |
949 | 15 | FT_BYTES, BASE_NONE|BASE_NO_DISPLAY_VALUE, NULL, 0x0, |
950 | 15 | "Payload (after unmasking)", HFILL } |
951 | 15 | }, |
952 | 15 | { &hf_ws_masked_payload, |
953 | 15 | { "Masked payload", "websocket.masked_payload", |
954 | 15 | FT_BYTES, BASE_NONE|BASE_NO_DISPLAY_VALUE, NULL, 0x0, |
955 | 15 | NULL, HFILL } |
956 | 15 | }, |
957 | 15 | { &hf_ws_payload_continue, |
958 | 15 | { "Continue", "websocket.payload.continue", |
959 | 15 | FT_BYTES, BASE_NONE, NULL, 0x0, |
960 | 15 | NULL, HFILL } |
961 | 15 | }, |
962 | 15 | { &hf_ws_payload_text, |
963 | 15 | { "Text", "websocket.payload.text", |
964 | 15 | FT_STRING, BASE_NONE, NULL, 0x0, |
965 | 15 | NULL, HFILL } |
966 | 15 | }, |
967 | 15 | { &hf_ws_payload_close, |
968 | 15 | { "Close", "websocket.payload.close", |
969 | 15 | FT_BYTES, BASE_NONE|BASE_NO_DISPLAY_VALUE, NULL, 0x0, |
970 | 15 | NULL, HFILL } |
971 | 15 | }, |
972 | 15 | { &hf_ws_payload_close_status_code, |
973 | 15 | { "Status code", "websocket.payload.close.status_code", |
974 | 15 | FT_UINT16, BASE_DEC, VALS(ws_close_status_code_vals), 0x0, |
975 | 15 | NULL, HFILL } |
976 | 15 | }, |
977 | 15 | { &hf_ws_payload_close_reason, |
978 | 15 | { "Reason", "websocket.payload.close.reason", |
979 | 15 | FT_STRING, BASE_NONE, NULL, 0x0, |
980 | 15 | NULL, HFILL } |
981 | 15 | }, |
982 | 15 | { &hf_ws_payload_ping, |
983 | 15 | { "Ping", "websocket.payload.ping", |
984 | 15 | FT_BYTES, BASE_NONE, NULL, 0x0, |
985 | 15 | NULL, HFILL } |
986 | 15 | }, |
987 | 15 | { &hf_ws_payload_pong, |
988 | 15 | { "Pong", "websocket.payload.pong", |
989 | 15 | FT_BYTES, BASE_NONE, NULL, 0x0, |
990 | 15 | NULL, HFILL } |
991 | 15 | }, |
992 | 15 | { &hf_ws_payload_unknown, |
993 | 15 | { "Unknown", "websocket.payload.unknown", |
994 | 15 | FT_BYTES, BASE_NONE, NULL, 0x0, |
995 | 15 | NULL, HFILL } |
996 | 15 | }, |
997 | | /* Reassembly */ |
998 | 15 | { &hf_ws_fragments, |
999 | 15 | { "Reassembled websocket Fragments", "websocket.fragments", |
1000 | 15 | FT_NONE, BASE_NONE, NULL, 0x0, |
1001 | 15 | NULL, HFILL } |
1002 | 15 | }, |
1003 | 15 | { &hf_ws_fragment, |
1004 | 15 | { "Websocket Fragment", "websocket.fragment", |
1005 | 15 | FT_FRAMENUM, BASE_NONE, NULL, 0x0, |
1006 | 15 | NULL, HFILL } |
1007 | 15 | }, |
1008 | 15 | { &hf_ws_fragment_overlap, |
1009 | 15 | { "Fragment overlap", "websocket.fragment.overlap", |
1010 | 15 | FT_BOOLEAN, BASE_NONE, NULL, 0x0, |
1011 | 15 | "Fragment overlaps with other fragments", HFILL } |
1012 | 15 | }, |
1013 | 15 | { &hf_ws_fragment_overlap_conflict, |
1014 | 15 | { "Conflicting data in fragment overlap", "websocket.fragment.overlap.conflict", |
1015 | 15 | FT_BOOLEAN, BASE_NONE, NULL, 0x0, |
1016 | 15 | "Overlapping fragments contained conflicting data", HFILL } |
1017 | 15 | }, |
1018 | 15 | { &hf_ws_fragment_multiple_tails, |
1019 | 15 | { "Multiple tail fragments found", "websocket.fragment.multipletails", |
1020 | 15 | FT_BOOLEAN, BASE_NONE, NULL, 0x0, |
1021 | 15 | "Several tails were found when defragmenting the packet", HFILL } |
1022 | 15 | }, |
1023 | 15 | { &hf_ws_fragment_too_long_fragment, |
1024 | 15 | { "Fragment too long", "websocket.fragment.toolongfragment", |
1025 | 15 | FT_BOOLEAN, BASE_NONE, NULL, 0x0, |
1026 | 15 | "Fragment contained data past end of packet", HFILL } |
1027 | 15 | }, |
1028 | 15 | { &hf_ws_fragment_error, |
1029 | 15 | { "Defragmentation error", "websocket.fragment.error", |
1030 | 15 | FT_FRAMENUM, BASE_NONE, NULL, 0x0, |
1031 | 15 | "Defragmentation error due to illegal fragments", HFILL } |
1032 | 15 | }, |
1033 | 15 | { &hf_ws_fragment_count, |
1034 | 15 | { "Fragment count", "websocket.fragment.count", |
1035 | 15 | FT_UINT32, BASE_DEC, NULL, 0x0, |
1036 | 15 | NULL, HFILL } |
1037 | 15 | }, |
1038 | 15 | { &hf_ws_reassembled_length, |
1039 | 15 | { "Reassembled websocket Payload length", "websocket.reassembled.length", |
1040 | 15 | FT_UINT32, BASE_DEC, NULL, 0x0, |
1041 | 15 | "The total length of the reassembled payload", HFILL } |
1042 | 15 | }, |
1043 | 15 | }; |
1044 | | |
1045 | | |
1046 | 15 | static int *ett[] = { |
1047 | 15 | &ett_ws, |
1048 | 15 | &ett_ws_pl, |
1049 | 15 | &ett_ws_mask, |
1050 | 15 | &ett_ws_control_close, |
1051 | 15 | &ett_ws_fragment, |
1052 | 15 | &ett_ws_fragments, |
1053 | 15 | }; |
1054 | | |
1055 | 15 | static ei_register_info ei[] = { |
1056 | 15 | { &ei_ws_payload_unknown, { "websocket.payload.unknown.expert", PI_UNDECODED, PI_NOTE, "Dissector for Websocket Opcode", EXPFILL }}, |
1057 | 15 | { &ei_ws_decompression_failed, { "websocket.decompression.failed.expert", PI_PROTOCOL, PI_WARN, "Decompression failed", EXPFILL }}, |
1058 | 15 | { &ei_ws_not_fully_unmasked, { "websocket.payload.not.fully.unmasked", PI_UNDECODED, PI_NOTE, "Payload not fully unmasked", EXPFILL }}, |
1059 | 15 | }; |
1060 | | |
1061 | 15 | static const enum_val_t text_types[] = { |
1062 | 15 | {"None", "No subdissection", WEBSOCKET_NONE}, |
1063 | 15 | {"Text", "Line based text", WEBSOCKET_TEXT}, |
1064 | 15 | {"JSON", "As json", WEBSOCKET_JSON}, |
1065 | 15 | {"SIP", "As SIP", WEBSOCKET_SIP}, |
1066 | 15 | {NULL, NULL, -1} |
1067 | 15 | }; |
1068 | | |
1069 | 15 | module_t *websocket_module; |
1070 | 15 | expert_module_t* expert_websocket; |
1071 | | |
1072 | 15 | proto_websocket = proto_register_protocol("WebSocket", "WebSocket", "websocket"); |
1073 | | |
1074 | | /* |
1075 | | * Heuristic dissectors SHOULD register themselves in |
1076 | | * this table using the standard heur_dissector_add() |
1077 | | * function. |
1078 | | */ |
1079 | 15 | heur_subdissector_list = register_heur_dissector_list_with_description("ws", "WebSocket data frame", proto_websocket); |
1080 | | |
1081 | 15 | port_subdissector_table = register_dissector_table("ws.port", |
1082 | 15 | "TCP port for protocols using WebSocket", proto_websocket, FT_UINT16, BASE_DEC); |
1083 | | |
1084 | 15 | protocol_subdissector_table = register_dissector_table("ws.protocol", |
1085 | 15 | "Negotiated WebSocket protocol", proto_websocket, FT_STRING, STRING_CASE_SENSITIVE); |
1086 | | |
1087 | 15 | reassembly_table_register(&ws_reassembly_table, &addresses_reassembly_table_functions); |
1088 | | |
1089 | 15 | websocket_follow_tap = register_tap("websocket_follow"); /* websocket follow tap */ |
1090 | 15 | register_follow_stream(proto_websocket, "websocket_follow", tcp_follow_conv_filter, tcp_follow_index_filter, |
1091 | 15 | tcp_follow_address_filter, tcp_port_to_display, follow_tvb_tap_listener, |
1092 | 15 | get_tcp_stream_count, NULL); |
1093 | | |
1094 | 15 | proto_register_field_array(proto_websocket, hf, array_length(hf)); |
1095 | 15 | proto_register_subtree_array(ett, array_length(ett)); |
1096 | 15 | expert_websocket = expert_register_protocol(proto_websocket); |
1097 | 15 | expert_register_field_array(expert_websocket, ei, array_length(ei)); |
1098 | | |
1099 | 15 | websocket_handle = register_dissector("websocket", dissect_websocket, proto_websocket); |
1100 | 15 | websocket_tcp_handle = register_dissector("websocket_tcp", dissect_websocket_tcp, proto_websocket); |
1101 | | |
1102 | 15 | websocket_module = prefs_register_protocol(proto_websocket, NULL); |
1103 | | |
1104 | 15 | prefs_register_enum_preference(websocket_module, "text_type", |
1105 | 15 | "Dissect websocket text as", |
1106 | 15 | "Select dissector for websocket text", |
1107 | 15 | &pref_text_type, text_types, WEBSOCKET_NONE); |
1108 | | |
1109 | 15 | prefs_register_bool_preference(websocket_module, "decompress", |
1110 | 15 | "Try to decompress permessage-deflate payload", NULL, &pref_decompress); |
1111 | | |
1112 | 15 | prefs_register_uint_preference(websocket_module, "max_unmasked_len", "Max unmasked payload length", |
1113 | 15 | "The default value is 256KB (1024x256) bytes. If the preference is too large, it may affect the parsing speed.", |
1114 | 15 | 10, &pref_max_unmasked_len); |
1115 | 15 | } |
1116 | | |
1117 | | void |
1118 | | proto_reg_handoff_websocket(void) |
1119 | 15 | { |
1120 | 15 | dissector_add_string("http.upgrade", "websocket", websocket_handle); |
1121 | 15 | dissector_add_string("http.upgrade", "WebSocket", websocket_handle); |
1122 | | |
1123 | 15 | dissector_add_for_decode_as("tcp.port", websocket_tcp_handle); |
1124 | | |
1125 | 15 | heur_dissector_add("tcp", dissect_websocket_heur_tcp, "WebSocket Heuristic", "websocket_tcp", proto_websocket, HEURISTIC_DISABLE); |
1126 | | |
1127 | 15 | text_lines_handle = find_dissector_add_dependency("data-text-lines", proto_websocket); |
1128 | 15 | json_handle = find_dissector_add_dependency("json", proto_websocket); |
1129 | 15 | sip_handle = find_dissector_add_dependency("sip", proto_websocket); |
1130 | 15 | } |
1131 | | /* |
1132 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
1133 | | * |
1134 | | * Local variables: |
1135 | | * c-basic-offset: 2 |
1136 | | * tab-width: 8 |
1137 | | * indent-tabs-mode: nil |
1138 | | * End: |
1139 | | * |
1140 | | * vi: set shiftwidth=2 tabstop=8 expandtab: |
1141 | | * :indentSize=2:tabSize=8:noTabs=true: |
1142 | | */ |