2 * Routines for WebSocket dissection
3 * Copyright 2012, Alexis La Goutte <alexis.lagoutte@gmail.com>
4 * 2015, Peter Wu <peter@lekensteyn.nl>
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * SPDX-License-Identifier: GPL-2.0-or-later
14 #include <wsutil/wslog.h>
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>
26 #include "packet-http.h"
27 #include "packet-tcp.h"
30 #define ZLIB_PREFIX(x) zng_ ## x
32 typedef zng_stream zlib_stream
;
35 #define ZLIB_PREFIX(x) x
37 typedef z_stream zlib_stream
;
38 #endif /* HAVE_ZLIB */
42 * The information used comes from:
43 * RFC6455: The WebSocket Protocol
44 * http://www.iana.org/assignments/websocket (last updated 2012-04-12)
47 void proto_register_websocket(void);
48 void proto_reg_handoff_websocket(void);
50 static dissector_handle_t websocket_handle
;
51 static dissector_handle_t text_lines_handle
;
52 static dissector_handle_t json_handle
;
53 static dissector_handle_t sip_handle
;
55 #define WEBSOCKET_NONE 0
56 #define WEBSOCKET_TEXT 1
57 #define WEBSOCKET_JSON 2
58 #define WEBSOCKET_SIP 3
60 /* Use key values counting down from UINT32_MAX to avoid clash with pkt_info proto_data key */
61 #define OPCODE_KEY (UINT32_MAX - 0)
63 static int pref_text_type
= WEBSOCKET_NONE
;
64 static bool pref_decompress
= true;
66 #define DEFAULT_MAX_UNMASKED_LEN (1024 * 256)
67 static unsigned pref_max_unmasked_len
= DEFAULT_MAX_UNMASKED_LEN
;
70 const char *subprotocol
;
72 bool permessage_deflate
;
74 bool permessage_deflate_ok
;
77 zng_streamp server_take_over_context
;
78 zng_streamp client_take_over_context
;
81 bool permessage_deflate_ok
;
84 z_streamp server_take_over_context
;
85 z_streamp client_take_over_context
;
90 uint8_t first_frag_opcode
;
94 #if defined (HAVE_ZLIB) || defined (HAVE_ZLIBNG)
96 uint8_t *decompr_payload
;
101 static int websocket_follow_tap
;
103 /* Initialize the protocol and registered fields */
104 static int proto_websocket
;
105 static int proto_http
;
107 static int hf_ws_fin
;
108 static int hf_ws_reserved
;
109 static int hf_ws_pmc
;
110 static int hf_ws_opcode
;
111 static int hf_ws_mask
;
112 static int hf_ws_payload_length
;
113 static int hf_ws_payload_length_ext_16
;
114 static int hf_ws_payload_length_ext_64
;
115 static int hf_ws_masking_key
;
116 static int hf_ws_payload
;
117 static int hf_ws_masked_payload
;
118 static int hf_ws_payload_continue
;
119 static int hf_ws_payload_text
;
120 static int hf_ws_payload_close
;
121 static int hf_ws_payload_close_status_code
;
122 static int hf_ws_payload_close_reason
;
123 static int hf_ws_payload_ping
;
124 static int hf_ws_payload_pong
;
125 static int hf_ws_payload_unknown
;
126 static int hf_ws_fragments
;
127 static int hf_ws_fragment
;
128 static int hf_ws_fragment_overlap
;
129 static int hf_ws_fragment_overlap_conflict
;
130 static int hf_ws_fragment_multiple_tails
;
131 static int hf_ws_fragment_too_long_fragment
;
132 static int hf_ws_fragment_error
;
133 static int hf_ws_fragment_count
;
134 static int hf_ws_reassembled_length
;
137 static int ett_ws_pl
;
138 static int ett_ws_mask
;
139 static int ett_ws_control_close
;
140 static int ett_ws_fragments
;
141 static int ett_ws_fragment
;
143 static expert_field ei_ws_payload_unknown
;
144 static expert_field ei_ws_decompression_failed
;
145 static expert_field ei_ws_not_fully_unmasked
;
147 #define WS_CONTINUE 0x0
149 #define WS_BINARY 0x2
154 static const value_string ws_opcode_vals
[] = {
155 { WS_CONTINUE
, "Continuation" },
157 { WS_BINARY
, "Binary" },
158 { WS_CLOSE
, "Connection Close" },
164 #define MASK_WS_FIN 0x80
165 #define MASK_WS_RSV 0x70
166 #define MASK_WS_RSV1 0x40
167 #define MASK_WS_OPCODE 0x0F
168 #define MASK_WS_MASK 0x80
169 #define MASK_WS_PAYLOAD_LEN 0x7F
171 static const value_string ws_close_status_code_vals
[] = {
172 { 1000, "Normal Closure" },
173 { 1001, "Going Away" },
174 { 1002, "Protocol error" },
175 { 1003, "Unsupported Data" },
176 { 1004, "---Reserved----" },
177 { 1005, "No Status Rcvd" },
178 { 1006, "Abnormal Closure" },
179 { 1007, "Invalid frame payload data" },
180 { 1008, "Policy Violation" },
181 { 1009, "Message Too Big" },
182 { 1010, "Mandatory Ext." },
183 { 1011, "Internal Server" },
184 { 1015, "TLS handshake" },
188 static const fragment_items ws_frag_items
= {
194 &hf_ws_fragment_overlap
,
195 &hf_ws_fragment_overlap_conflict
,
196 &hf_ws_fragment_multiple_tails
,
197 &hf_ws_fragment_too_long_fragment
,
198 &hf_ws_fragment_error
,
199 &hf_ws_fragment_count
,
201 &hf_ws_reassembled_length
,
202 /* Reassembled data field */
204 "websocket fragments"
207 static dissector_table_t port_subdissector_table
;
208 static dissector_table_t protocol_subdissector_table
;
209 static heur_dissector_list_t heur_subdissector_list
;
211 static reassembly_table ws_reassembly_table
;
214 tvb_unmasked(tvbuff_t
*tvb
, packet_info
*pinfo
, const unsigned offset
, unsigned payload_length
, const uint8_t *masking_key
)
219 const uint8_t *data_mask
;
220 unsigned unmasked_length
= payload_length
> pref_max_unmasked_len
? pref_max_unmasked_len
: payload_length
;
222 data_unmask
= (char *)wmem_alloc(pinfo
->pool
, unmasked_length
);
223 data_mask
= tvb_get_ptr(tvb
, offset
, unmasked_length
);
224 /* Unmasked(XOR) Data... */
225 for(i
=0; i
< unmasked_length
; i
++) {
226 data_unmask
[i
] = data_mask
[i
] ^ masking_key
[i
%4];
229 return tvb_new_child_real_data(tvb
, data_unmask
, unmasked_length
, payload_length
);
232 #if defined (HAVE_ZLIB) || defined (HAVE_ZLIBNG)
234 websocket_extract_wbits(const char *str
)
239 if (str
&& ws_strtou8(str
, &end
, &wbits
) &&
240 (*end
== '\0' || strchr(";\t ", *end
))) {
243 } else if (wbits
> 15) {
253 websocket_zalloc(void *opaque _U_
, unsigned int items
, unsigned int size
)
255 return wmem_alloc(wmem_file_scope(), items
*size
);
259 websocket_zfree(void *opaque _U_
, void *addr
)
261 wmem_free(wmem_file_scope(), addr
);
265 websocket_init_z_stream_context(int8_t wbits
)
267 zng_streamp z_strm
= wmem_new0(wmem_file_scope(), zlib_stream
);
270 websocket_init_z_stream_context(int8_t wbits
)
272 z_streamp z_strm
= wmem_new0(wmem_file_scope(), zlib_stream
);
274 z_strm
->zalloc
= websocket_zalloc
;
275 z_strm
->zfree
= websocket_zfree
;
277 if (ZLIB_PREFIX(inflateInit2
)(z_strm
, wbits
) != Z_OK
) {
278 ZLIB_PREFIX(inflateEnd
)(z_strm
);
279 wmem_free(wmem_file_scope(), z_strm
);
286 * Decompress the given buffer using the given zlib context. On success, the
287 * (possibly empty) buffer is stored as "proto data" and true is returned.
288 * Otherwise false is returned.
292 websocket_uncompress(tvbuff_t
* tvb
, packet_info
* pinfo
, zng_streamp z_strm
, tvbuff_t
** uncompressed_tvb
, uint32_t key
)
294 websocket_uncompress(tvbuff_t
*tvb
, packet_info
*pinfo
, z_streamp z_strm
, tvbuff_t
**uncompressed_tvb
, uint32_t key
)
298 * Decompression a message: append "0x00 0x00 0xff 0xff" to the end of
299 * message, then apply DEFLATE to the result.
300 * https://tools.ietf.org/html/rfc7692#section-7.2.2
302 uint8_t *decompr_payload
= NULL
;
303 unsigned decompr_len
= 0;
304 unsigned compr_len
, decompr_buf_len
;
305 uint8_t *compr_payload
, *decompr_buf
;
308 compr_len
= tvb_captured_length(tvb
) + 4;
309 compr_payload
= (uint8_t *)wmem_alloc(pinfo
->pool
, compr_len
);
310 tvb_memcpy(tvb
, compr_payload
, 0, compr_len
-4);
311 compr_payload
[compr_len
-4] = compr_payload
[compr_len
-3] = 0x00;
312 compr_payload
[compr_len
-2] = compr_payload
[compr_len
-1] = 0xff;
313 decompr_buf_len
= 2*compr_len
;
314 decompr_buf
= (uint8_t *)wmem_alloc(pinfo
->pool
, decompr_buf_len
);
316 z_strm
->next_in
= compr_payload
;
317 z_strm
->avail_in
= compr_len
;
318 /* Decompress all available data. */
320 z_strm
->next_out
= decompr_buf
;
321 z_strm
->avail_out
= decompr_buf_len
;
323 err
= ZLIB_PREFIX(inflate
)(z_strm
, Z_SYNC_FLUSH
);
325 if (err
== Z_OK
|| err
== Z_STREAM_END
|| err
== Z_BUF_ERROR
) {
326 unsigned avail_bytes
= decompr_buf_len
- z_strm
->avail_out
;
328 decompr_payload
= (uint8_t *)wmem_realloc(wmem_file_scope(), decompr_payload
,
329 decompr_len
+ avail_bytes
);
330 memcpy(&decompr_payload
[decompr_len
], decompr_buf
, avail_bytes
);
331 decompr_len
+= avail_bytes
;
334 } while (err
== Z_OK
);
336 if (err
== Z_STREAM_END
|| err
== Z_BUF_ERROR
) {
337 /* Data was (partially) uncompressed. */
338 websocket_packet_t
*pkt_info
= wmem_new0(wmem_file_scope(), websocket_packet_t
);
339 if (decompr_len
> 0) {
340 pkt_info
->decompr_payload
= decompr_payload
;
341 pkt_info
->decompr_len
= decompr_len
;
342 *uncompressed_tvb
= tvb_new_child_real_data(tvb
, decompr_payload
, decompr_len
, decompr_len
);
344 p_add_proto_data(wmem_file_scope(), pinfo
, proto_websocket
, key
, pkt_info
);
347 /* decompression failed */
348 wmem_free(wmem_file_scope(), decompr_payload
);
355 dissect_websocket_control_frame(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, uint8_t opcode
)
359 const unsigned offset
= 0, length
= tvb_reported_length(tvb
);
362 case WS_CLOSE
: /* Close */
363 ti
= proto_tree_add_item(tree
, hf_ws_payload_close
, tvb
, offset
, length
, ENC_NA
);
364 subtree
= proto_item_add_subtree(ti
, ett_ws_control_close
);
365 /* Close frame MAY contain a body. */
367 proto_tree_add_item(subtree
, hf_ws_payload_close_status_code
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
369 proto_tree_add_item(subtree
, hf_ws_payload_close_reason
, tvb
, offset
+2, length
-2, ENC_UTF_8
);
373 case WS_PING
: /* Ping */
374 proto_tree_add_item(tree
, hf_ws_payload_ping
, tvb
, offset
, length
, ENC_NA
);
377 case WS_PONG
: /* Pong */
378 proto_tree_add_item(tree
, hf_ws_payload_pong
, tvb
, offset
, length
, ENC_NA
);
381 default: /* Unknown */
382 ti
= proto_tree_add_item(tree
, hf_ws_payload_unknown
, tvb
, offset
, length
, ENC_NA
);
383 expert_add_info_format(pinfo
, ti
, &ei_ws_payload_unknown
, "Dissector for Websocket Opcode (%d)"
384 " code not implemented, Contact Wireshark developers"
385 " if you want this supported", opcode
);
391 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
, int raw_offset _U_
)
394 dissector_handle_t handle
= NULL
;
395 heur_dtbl_entry_t
*hdtbl_entry
;
397 if (pinfo
->fragmented
) {
398 /* Skip dissecting fragmented payload data. */
402 /* try to find a dissector which accepts the data. */
403 if (websocket_conv
->subprotocol
) {
404 handle
= dissector_get_string_handle(protocol_subdissector_table
, websocket_conv
->subprotocol
);
405 } else if (websocket_conv
->server_port
) {
406 handle
= dissector_get_uint_handle(port_subdissector_table
, websocket_conv
->server_port
);
409 #if defined (HAVE_ZLIB) || defined (HAVE_ZLIBNG)
410 if (websocket_conv
->permessage_deflate_ok
&& websocket_conv
->first_frag_pmc
) {
411 tvbuff_t
*uncompressed
= NULL
;
412 bool uncompress_ok
= false;
414 if (!PINFO_FD_VISITED(pinfo
)) {
422 if (pinfo
->destport
== websocket_conv
->server_port
) {
423 z_strm
= websocket_conv
->server_take_over_context
;
424 wbits
= websocket_conv
->server_wbits
;
426 z_strm
= websocket_conv
->client_take_over_context
;
427 wbits
= websocket_conv
->client_wbits
;
431 uncompress_ok
= websocket_uncompress(tvb
, pinfo
, z_strm
, &uncompressed
, raw_offset
);
433 /* no context take over, initialize a new context */
434 z_strm
= wmem_new0(pinfo
->pool
, zlib_stream
);
435 if (ZLIB_PREFIX(inflateInit2
)(z_strm
, wbits
) == Z_OK
) {
436 uncompress_ok
= websocket_uncompress(tvb
, pinfo
, z_strm
, &uncompressed
, raw_offset
);
438 ZLIB_PREFIX(inflateEnd
)(z_strm
);
441 websocket_packet_t
*pkt_info
=
442 (websocket_packet_t
*)p_get_proto_data(wmem_file_scope(), pinfo
, proto_websocket
, raw_offset
);
444 uncompress_ok
= true;
445 if (pkt_info
->decompr_len
> 0) {
446 uncompressed
= tvb_new_child_real_data(tvb
, pkt_info
->decompr_payload
, pkt_info
->decompr_len
, pkt_info
->decompr_len
);
451 if (!uncompress_ok
) {
452 proto_tree_add_expert(tree
, pinfo
, &ei_ws_decompression_failed
, tvb
, 0, -1);
456 add_new_data_source(pinfo
, uncompressed
, "Decompressed payload");
462 if (have_tap_listener(websocket_follow_tap
)) {
463 tap_queue_packet(websocket_follow_tap
, pinfo
, tvb
);
467 call_dissector_only(handle
, tvb
, pinfo
, tree
, NULL
);
468 return; /* handle found, assume dissector took care of it. */
469 } else if (dissector_try_heuristic(heur_subdissector_list
, tvb
, pinfo
, tree
, &hdtbl_entry
, NULL
)) {
470 return; /* heuristics dissector handled it. */
473 /* no dissector wanted it, try to print something appropriate. */
475 case WS_TEXT
: /* Text */
477 proto_tree_add_item(pl_tree
, hf_ws_payload_text
, tvb
, 0, -1, ENC_UTF_8
);
478 const char *saved_match_string
= pinfo
->match_string
;
480 pinfo
->match_string
= NULL
;
481 switch (pref_text_type
) {
485 /* Assume that most text protocols are line-based. */
486 call_dissector(text_lines_handle
, tvb
, pinfo
, tree
);
489 call_dissector(json_handle
, tvb
, pinfo
, tree
);
492 call_dissector(sip_handle
, tvb
, pinfo
, tree
);
495 pinfo
->match_string
= saved_match_string
;
499 case WS_BINARY
: /* Binary */
500 call_data_dissector(tvb
, pinfo
, tree
);
503 default: /* Unknown */
504 ti
= proto_tree_add_item(pl_tree
, hf_ws_payload_unknown
, tvb
, 0, -1, ENC_NA
);
505 expert_add_info_format(pinfo
, ti
, &ei_ws_payload_unknown
, "Dissector for Websocket Opcode (%d)"
506 " code not implemented, Contact Wireshark developers"
507 " if you want this supported", opcode
);
513 websocket_parse_extensions(websocket_conv_t
*websocket_conv
, const char *str
)
516 * Grammar for the header:
518 * Sec-WebSocket-Extensions = extension-list
519 * extension-list = 1#extension
520 * extension = extension-token *( ";" extension-param )
521 * extension-token = registered-token
522 * registered-token = token
523 * extension-param = token [ "=" (token | quoted-string) ]
527 * RFC 7692 permessage-deflate parsing.
528 * "x-webkit-deflate-frame" is an alias used by some versions of Safari browser
531 websocket_conv
->permessage_deflate
= !!strstr(str
, "permessage-deflate")
532 || !!strstr(str
, "x-webkit-deflate-frame");
533 #if defined (HAVE_ZLIB) || defined (HAVE_ZLIBNG)
534 websocket_conv
->permessage_deflate_ok
= pref_decompress
&&
535 websocket_conv
->permessage_deflate
;
536 if (websocket_conv
->permessage_deflate_ok
) {
537 websocket_conv
->server_wbits
=
538 websocket_extract_wbits(strstr(str
, "server_max_window_bits="));
539 if (!strstr(str
, "server_no_context_takeover")) {
540 websocket_conv
->server_take_over_context
=
541 websocket_init_z_stream_context(websocket_conv
->server_wbits
);
543 websocket_conv
->client_wbits
=
544 websocket_extract_wbits(strstr(str
, "client_max_window_bits="));
545 if (!strstr(str
, "client_no_context_takeover")) {
546 websocket_conv
->client_take_over_context
=
547 websocket_init_z_stream_context(websocket_conv
->client_wbits
);
554 dissect_websocket_payload(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, proto_tree
*ws_tree
, uint8_t fin
, uint8_t opcode
, websocket_conv_t
*websocket_conv
, int raw_offset
, unsigned masked_payload_length
)
556 const unsigned offset
= 0, length
= tvb_reported_length(tvb
);
557 const unsigned capture_length
= tvb_captured_length(tvb
);
560 tvbuff_t
*tvb_appdata
;
561 tvbuff_t
*frag_tvb
= NULL
;
564 ti
= proto_tree_add_item(ws_tree
, hf_ws_payload
, tvb
, offset
, length
, ENC_NA
);
565 pl_tree
= proto_item_add_subtree(ti
, ett_ws_pl
);
567 if (masked_payload_length
> capture_length
) {
568 expert_add_info_format(pinfo
, ti
, &ei_ws_not_fully_unmasked
, "Payload not fully unmasked. "
569 "%u bytes not yet unmasked due to the preference of max unmasked length limit (%u bytes).",
570 masked_payload_length
- capture_length
, pref_max_unmasked_len
);
574 /* TODO: Add dissector of Extension (not extension available for the moment...) */
576 if (opcode
& 8) { /* Control frames have MSB set. */
577 dissect_websocket_control_frame(tvb
, pinfo
, pl_tree
, opcode
);
581 bool save_fragmented
= pinfo
->fragmented
;
583 if (!fin
|| opcode
== WS_CONTINUE
) {
584 /* Fragmented data frame */
585 fragment_head
*frag_msg
;
587 pinfo
->fragmented
= true;
589 frag_msg
= fragment_add_seq_next(&ws_reassembly_table
, tvb
, offset
,
590 pinfo
, websocket_conv
->frag_id
,
591 NULL
, tvb_captured_length_remaining(tvb
, offset
),
593 frag_tvb
= process_reassembled_data(tvb
, offset
, pinfo
,
594 "Reassembled Message", frag_msg
, &ws_frag_items
,
598 if (!PINFO_FD_VISITED(pinfo
) && frag_tvb
) {
599 /* First time fragments fully reassembled, store opcode from first fragment */
600 p_add_proto_data(wmem_file_scope(), pinfo
, proto_websocket
, OPCODE_KEY
,
601 GUINT_TO_POINTER(websocket_conv
->first_frag_opcode
));
605 /* Fragments were fully reassembled. */
606 tvb_appdata
= frag_tvb
;
608 /* Lookup opcode from first fragment */
609 unsigned first_frag_opcode
= GPOINTER_TO_UINT(
610 p_get_proto_data(wmem_file_scope(),pinfo
, proto_websocket
, OPCODE_KEY
));
611 opcode
= (uint8_t)first_frag_opcode
;
613 /* Right now this is exactly the same, this may change when exts. are added.
614 tvb_appdata = tvb_new_subset_length(tvb, offset, length);
619 /* Application Data */
621 if (pinfo
->fragmented
&& opcode
== WS_CONTINUE
) {
622 /* Not last fragment, dissect continue fragment as is */
623 proto_tree_add_item(tree
, hf_ws_payload_continue
, tvb_appdata
, offset
, length
, ENC_NA
);
627 dissect_websocket_data_frame(tvb_appdata
, pinfo
, tree
, pl_tree
, opcode
, websocket_conv
, raw_offset
);
628 pinfo
->fragmented
= save_fragmented
;
632 dissect_websocket_frame(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data _U_
)
634 static uint32_t frag_id_counter
= 0;
635 proto_item
*ti
, *ti_len
;
638 unsigned short_length
, payload_length
;
639 unsigned payload_offset
, mask_offset
;
641 const uint8_t *masking_key
= NULL
;
642 tvbuff_t
*tvb_payload
;
643 conversation_t
*conv
;
644 websocket_conv_t
*websocket_conv
;
647 * If this is a new Websocket session, try to parse HTTP Sec-Websocket-*
650 conv
= find_or_create_conversation(pinfo
);
651 websocket_conv
= (websocket_conv_t
*)conversation_get_proto_data(conv
, proto_websocket
);
652 if (!websocket_conv
) {
653 websocket_conv
= wmem_new0(wmem_file_scope(), websocket_conv_t
);
654 websocket_conv
->first_frag
= true;
655 websocket_conv
->frag_id
= ++frag_id_counter
;
657 http_conv_t
*http_conv
= (http_conv_t
*)conversation_get_proto_data(conv
, proto_http
);
659 websocket_conv
->subprotocol
= http_conv
->websocket_protocol
;
660 websocket_conv
->server_port
= http_conv
->server_port
;
661 if ( http_conv
->websocket_extensions
) {
662 websocket_parse_extensions(websocket_conv
, http_conv
->websocket_extensions
);
664 } else if (pinfo
->match_uint
== pinfo
->srcport
|| pinfo
->match_uint
== pinfo
->destport
) {
665 /* The session was not set up by HTTP upgrade, but by Decode As.
666 * Assume the matched port is the server port. */
667 websocket_conv
->server_port
= (uint16_t)pinfo
->match_uint
;
669 /* match_uint is not one of the ports, which means the session was
670 * set up by the heuristic Websocket dissector. */
671 uint32_t low_port
, high_port
;
672 if (pinfo
->srcport
> pinfo
->destport
) {
673 low_port
= pinfo
->destport
;
674 high_port
= pinfo
->srcport
;
676 low_port
= pinfo
->srcport
;
677 high_port
= pinfo
->destport
;
679 if (dissector_get_uint_handle(port_subdissector_table
, low_port
)) {
680 websocket_conv
->server_port
= (uint16_t)low_port
;
681 } else if (dissector_get_uint_handle(port_subdissector_table
, high_port
)) {
682 websocket_conv
->server_port
= (uint16_t)high_port
;
686 conversation_add_proto_data(conv
, proto_websocket
, websocket_conv
);
688 websocket_conv
->first_frag
= false;
691 short_length
= tvb_get_uint8(tvb
, 1) & MASK_WS_PAYLOAD_LEN
;
693 if (short_length
== 126) {
694 payload_length
= tvb_get_ntohs(tvb
, 2);
696 } else if (short_length
== 127) {
697 /* warning C4244: '=' : conversion from 'uint64_t' to 'unsigned ', possible loss of data */
698 payload_length
= (unsigned)tvb_get_ntoh64(tvb
, 2);
701 payload_length
= short_length
;
705 mask
= (tvb_get_uint8(tvb
, 1) & MASK_WS_MASK
) != 0;
706 payload_offset
= mask_offset
+ (mask
? 4 : 0);
708 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "WebSocket");
709 col_set_str(pinfo
->cinfo
, COL_INFO
, "WebSocket");
711 ti
= proto_tree_add_item(tree
, proto_websocket
, tvb
, 0, payload_offset
, ENC_NA
);
712 ws_tree
= proto_item_add_subtree(ti
, ett_ws
);
715 proto_tree_add_item(ws_tree
, hf_ws_fin
, tvb
, 0, 1, ENC_NA
);
716 fin
= (tvb_get_uint8(tvb
, 0) & MASK_WS_FIN
) >> 4;
717 proto_tree_add_item(ws_tree
, hf_ws_reserved
, tvb
, 0, 1, ENC_BIG_ENDIAN
);
718 if (websocket_conv
->permessage_deflate
) {
719 /* RSV1 is Per-Message Compressed bit (RFC 7692). */
720 if (websocket_conv
->first_frag
) {
721 /* First fragment, save pmc flag needed for decompressing continuation fragments */
722 websocket_conv
->first_frag_pmc
= !!(tvb_get_uint8(tvb
, 0) & MASK_WS_RSV1
);
724 proto_tree_add_item(ws_tree
, hf_ws_pmc
, tvb
, 0, 1, ENC_BIG_ENDIAN
);
728 proto_tree_add_item(ws_tree
, hf_ws_opcode
, tvb
, 0, 1, ENC_BIG_ENDIAN
);
729 opcode
= tvb_get_uint8(tvb
, 0) & MASK_WS_OPCODE
;
730 if (websocket_conv
->first_frag
) {
731 /* First fragment, save opcode needed when dissecting the reassembled frame */
732 websocket_conv
->first_frag_opcode
= opcode
;
734 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " %s", val_to_str_const(opcode
, ws_opcode_vals
, "Unknown Opcode"));
735 col_append_str(pinfo
->cinfo
, COL_INFO
, fin
? " [FIN]" : "[FRAGMENT] ");
737 /* Add Mask bit to the tree */
738 proto_tree_add_item(ws_tree
, hf_ws_mask
, tvb
, 1, 1, ENC_NA
);
739 col_append_str(pinfo
->cinfo
, COL_INFO
, mask
? " [MASKED]" : " ");
741 /* (Extended) Payload Length */
742 ti_len
= proto_tree_add_item(ws_tree
, hf_ws_payload_length
, tvb
, 1, 1, ENC_BIG_ENDIAN
);
743 if (short_length
== 126) {
744 proto_item_append_text(ti_len
, " Extended Payload Length (16 bits)");
745 proto_tree_add_item(ws_tree
, hf_ws_payload_length_ext_16
, tvb
, 2, 2, ENC_BIG_ENDIAN
);
747 else if (short_length
== 127) {
748 proto_item_append_text(ti_len
, " Extended Payload Length (64 bits)");
749 proto_tree_add_item(ws_tree
, hf_ws_payload_length_ext_64
, tvb
, 2, 8, ENC_BIG_ENDIAN
);
754 proto_tree_add_item(ws_tree
, hf_ws_masking_key
, tvb
, mask_offset
, 4, ENC_NA
);
755 masking_key
= tvb_get_ptr(tvb
, mask_offset
, 4);
758 if (payload_length
> 0) {
759 /* Always unmask payload data before analysing it. */
761 proto_tree_add_item(ws_tree
, hf_ws_masked_payload
, tvb
, payload_offset
, payload_length
, ENC_NA
);
762 tvb_payload
= tvb_unmasked(tvb
, pinfo
, payload_offset
, payload_length
, masking_key
);
763 add_new_data_source(pinfo
, tvb_payload
, "Unmasked data");
765 tvb_payload
= tvb_new_subset_length(tvb
, payload_offset
, payload_length
);
767 dissect_websocket_payload(tvb_payload
, pinfo
, tree
, ws_tree
, fin
, opcode
, websocket_conv
, tvb_raw_offset(tvb
), (mask
? payload_length
: 0));
770 return tvb_captured_length(tvb
);
774 get_websocket_frame_length(packet_info
*pinfo _U_
, tvbuff_t
*tvb
, int offset
, void *data _U_
)
776 unsigned frame_length
, payload_length
;
779 frame_length
= 2; /* flags, opcode and Payload length */
780 mask
= tvb_get_uint8(tvb
, offset
+ 1) & MASK_WS_MASK
;
782 payload_length
= tvb_get_uint8(tvb
, offset
+ 1) & MASK_WS_PAYLOAD_LEN
;
783 offset
+= 2; /* Skip flags, opcode and Payload length */
785 /* Check for Extended Payload Length. */
786 if (payload_length
== 126) {
787 if (tvb_reported_length_remaining(tvb
, offset
) < 2)
788 return 0; /* Need more data. */
790 payload_length
= tvb_get_ntohs(tvb
, offset
);
791 frame_length
+= 2; /* Extended payload length */
792 } else if (payload_length
== 127) {
793 if (tvb_reported_length_remaining(tvb
, offset
) < 8)
794 return 0; /* Need more data. */
796 payload_length
= (unsigned)tvb_get_ntoh64(tvb
, offset
);
797 frame_length
+= 8; /* Extended payload length */
801 frame_length
+= 4; /* Masking-key */
802 frame_length
+= payload_length
; /* Payload data */
807 dissect_websocket(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data
)
809 /* Need at least two bytes for flags, opcode and Payload length. */
810 tcp_dissect_pdus(tvb
, pinfo
, tree
, true, 2,
811 get_websocket_frame_length
, dissect_websocket_frame
, data
);
812 return tvb_captured_length(tvb
);
816 test_websocket(packet_info
* pinfo _U_
, tvbuff_t
* tvb
, int offset _U_
, void* data _U_
)
818 unsigned buffer_length
= tvb_captured_length(tvb
);
820 // At least 2 bytes are required for a websocket header
821 if (buffer_length
< 2)
825 uint8_t first_byte
= tvb_get_uint8(tvb
, 0);
826 uint8_t second_byte
= tvb_get_uint8(tvb
, 1);
828 // Reserved bits RSV1, RSV2 and RSV3 need to be 0
829 if ((first_byte
& 0x70) > 0)
834 uint8_t op_code
= first_byte
& 0x0F;
836 // op_code must be one of WS_CONTINUE, WS_TEXT, WS_BINARY, WS_CLOSE, WS_PING or WS_PONG
837 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
))
842 // It is necessary to prevent that HTTP connection setups are treated as websocket.
843 // If HTTP catches and it upgrades to websocket then HTTP takes care that websocket dissector gets called for this stream.
844 // If first two byte start with printable characters from the alphabet it's likely that it is part of a HTTP connection setup.
845 if (((first_byte
>= 'a' && first_byte
<= 'z') || (first_byte
>= 'A' && first_byte
<= 'Z')) &&
846 ((second_byte
>= 'a' && second_byte
<= 'z') || (second_byte
>= 'A' && second_byte
<= 'Z')))
855 dissect_websocket_heur_tcp(tvbuff_t
* tvb
, packet_info
* pinfo
, proto_tree
* tree
, void* data
)
857 if (!test_websocket(pinfo
, tvb
, 0, data
))
861 conversation_t
* conversation
= find_or_create_conversation(pinfo
);
862 conversation_set_dissector(conversation
, websocket_handle
);
864 tcp_dissect_pdus(tvb
, pinfo
, tree
, true, 2, get_websocket_frame_length
, dissect_websocket_frame
, data
);
869 proto_register_websocket(void)
872 static hf_register_info hf
[] = {
874 { "Fin", "websocket.fin",
875 FT_BOOLEAN
, 8, NULL
, MASK_WS_FIN
,
876 "Indicates that this is the final fragment in a message", HFILL
}
879 { "Reserved", "websocket.rsv",
880 FT_UINT8
, BASE_HEX
, NULL
, MASK_WS_RSV
,
881 "Must be zero", HFILL
}
884 { "Per-Message Compressed", "websocket.pmc",
885 FT_BOOLEAN
, 8, NULL
, MASK_WS_RSV1
,
886 "Whether a message is compressed or not", HFILL
}
889 { "Opcode", "websocket.opcode",
890 FT_UINT8
, BASE_DEC
, VALS(ws_opcode_vals
), MASK_WS_OPCODE
,
891 "Defines the interpretation of the Payload data", HFILL
}
894 { "Mask", "websocket.mask",
895 FT_BOOLEAN
, 8, NULL
, MASK_WS_MASK
,
896 "Defines whether the Payload data is masked", HFILL
}
898 { &hf_ws_payload_length
,
899 { "Payload length", "websocket.payload_length",
900 FT_UINT8
, BASE_DEC
, NULL
, MASK_WS_PAYLOAD_LEN
,
901 "The length of the Payload data", HFILL
}
903 { &hf_ws_payload_length_ext_16
,
904 { "Extended Payload length (16 bits)", "websocket.payload_length_ext_16",
905 FT_UINT16
, BASE_DEC
, NULL
, 0x0,
906 "The length (16 bits) of the Payload data", HFILL
}
908 { &hf_ws_payload_length_ext_64
,
909 { "Extended Payload length (64 bits)", "websocket.payload_length_ext_64",
910 FT_UINT64
, BASE_DEC
, NULL
, 0x0,
911 "The length (64 bits) of the Payload data", HFILL
}
913 { &hf_ws_masking_key
,
914 { "Masking-Key", "websocket.masking_key",
915 FT_BYTES
, BASE_NONE
, NULL
, 0x0,
916 "All frames sent from the client to the server are masked by a 32-bit value that is contained within the frame", HFILL
}
919 { "Payload", "websocket.payload",
920 FT_BYTES
, BASE_NONE
|BASE_NO_DISPLAY_VALUE
, NULL
, 0x0,
921 "Payload (after unmasking)", HFILL
}
923 { &hf_ws_masked_payload
,
924 { "Masked payload", "websocket.masked_payload",
925 FT_BYTES
, BASE_NONE
|BASE_NO_DISPLAY_VALUE
, NULL
, 0x0,
928 { &hf_ws_payload_continue
,
929 { "Continue", "websocket.payload.continue",
930 FT_BYTES
, BASE_NONE
, NULL
, 0x0,
933 { &hf_ws_payload_text
,
934 { "Text", "websocket.payload.text",
935 FT_STRING
, BASE_NONE
, NULL
, 0x0,
938 { &hf_ws_payload_close
,
939 { "Close", "websocket.payload.close",
940 FT_BYTES
, BASE_NONE
|BASE_NO_DISPLAY_VALUE
, NULL
, 0x0,
943 { &hf_ws_payload_close_status_code
,
944 { "Status code", "websocket.payload.close.status_code",
945 FT_UINT16
, BASE_DEC
, VALS(ws_close_status_code_vals
), 0x0,
948 { &hf_ws_payload_close_reason
,
949 { "Reason", "websocket.payload.close.reason",
950 FT_STRING
, BASE_NONE
, NULL
, 0x0,
953 { &hf_ws_payload_ping
,
954 { "Ping", "websocket.payload.ping",
955 FT_BYTES
, BASE_NONE
, NULL
, 0x0,
958 { &hf_ws_payload_pong
,
959 { "Pong", "websocket.payload.pong",
960 FT_BYTES
, BASE_NONE
, NULL
, 0x0,
963 { &hf_ws_payload_unknown
,
964 { "Unknown", "websocket.payload.unknown",
965 FT_BYTES
, BASE_NONE
, NULL
, 0x0,
970 { "Reassembled websocket Fragments", "websocket.fragments",
971 FT_NONE
, BASE_NONE
, NULL
, 0x0,
975 { "Websocket Fragment", "websocket.fragment",
976 FT_FRAMENUM
, BASE_NONE
, NULL
, 0x0,
979 { &hf_ws_fragment_overlap
,
980 { "Fragment overlap", "websocket.fragment.overlap",
981 FT_BOOLEAN
, BASE_NONE
, NULL
, 0x0,
982 "Fragment overlaps with other fragments", HFILL
}
984 { &hf_ws_fragment_overlap_conflict
,
985 { "Conflicting data in fragment overlap", "websocket.fragment.overlap.conflict",
986 FT_BOOLEAN
, BASE_NONE
, NULL
, 0x0,
987 "Overlapping fragments contained conflicting data", HFILL
}
989 { &hf_ws_fragment_multiple_tails
,
990 { "Multiple tail fragments found", "websocket.fragment.multipletails",
991 FT_BOOLEAN
, BASE_NONE
, NULL
, 0x0,
992 "Several tails were found when defragmenting the packet", HFILL
}
994 { &hf_ws_fragment_too_long_fragment
,
995 { "Fragment too long", "websocket.fragment.toolongfragment",
996 FT_BOOLEAN
, BASE_NONE
, NULL
, 0x0,
997 "Fragment contained data past end of packet", HFILL
}
999 { &hf_ws_fragment_error
,
1000 { "Defragmentation error", "websocket.fragment.error",
1001 FT_FRAMENUM
, BASE_NONE
, NULL
, 0x0,
1002 "Defragmentation error due to illegal fragments", HFILL
}
1004 { &hf_ws_fragment_count
,
1005 { "Fragment count", "websocket.fragment.count",
1006 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
1009 { &hf_ws_reassembled_length
,
1010 { "Reassembled websocket Payload length", "websocket.reassembled.length",
1011 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
1012 "The total length of the reassembled payload", HFILL
}
1017 static int *ett
[] = {
1021 &ett_ws_control_close
,
1026 static ei_register_info ei
[] = {
1027 { &ei_ws_payload_unknown
, { "websocket.payload.unknown.expert", PI_UNDECODED
, PI_NOTE
, "Dissector for Websocket Opcode", EXPFILL
}},
1028 { &ei_ws_decompression_failed
, { "websocket.decompression.failed.expert", PI_PROTOCOL
, PI_WARN
, "Decompression failed", EXPFILL
}},
1029 { &ei_ws_not_fully_unmasked
, { "websocket.payload.not.fully.unmasked", PI_UNDECODED
, PI_NOTE
, "Payload not fully unmasked", EXPFILL
}},
1032 static const enum_val_t text_types
[] = {
1033 {"None", "No subdissection", WEBSOCKET_NONE
},
1034 {"Text", "Line based text", WEBSOCKET_TEXT
},
1035 {"JSON", "As json", WEBSOCKET_JSON
},
1036 {"SIP", "As SIP", WEBSOCKET_SIP
},
1040 module_t
*websocket_module
;
1041 expert_module_t
* expert_websocket
;
1043 proto_websocket
= proto_register_protocol("WebSocket", "WebSocket", "websocket");
1046 * Heuristic dissectors SHOULD register themselves in
1047 * this table using the standard heur_dissector_add()
1050 heur_subdissector_list
= register_heur_dissector_list_with_description("ws", "WebSocket data frame", proto_websocket
);
1052 port_subdissector_table
= register_dissector_table("ws.port",
1053 "TCP port for protocols using WebSocket", proto_websocket
, FT_UINT16
, BASE_DEC
);
1055 protocol_subdissector_table
= register_dissector_table("ws.protocol",
1056 "Negotiated WebSocket protocol", proto_websocket
, FT_STRING
, STRING_CASE_SENSITIVE
);
1058 reassembly_table_register(&ws_reassembly_table
, &addresses_reassembly_table_functions
);
1060 websocket_follow_tap
= register_tap("websocket_follow"); /* websocket follow tap */
1061 register_follow_stream(proto_websocket
, "websocket_follow", tcp_follow_conv_filter
, tcp_follow_index_filter
,
1062 tcp_follow_address_filter
, tcp_port_to_display
, follow_tvb_tap_listener
,
1063 get_tcp_stream_count
, NULL
);
1065 proto_register_field_array(proto_websocket
, hf
, array_length(hf
));
1066 proto_register_subtree_array(ett
, array_length(ett
));
1067 expert_websocket
= expert_register_protocol(proto_websocket
);
1068 expert_register_field_array(expert_websocket
, ei
, array_length(ei
));
1070 websocket_handle
= register_dissector("websocket", dissect_websocket
, proto_websocket
);
1072 websocket_module
= prefs_register_protocol(proto_websocket
, NULL
);
1074 prefs_register_enum_preference(websocket_module
, "text_type",
1075 "Dissect websocket text as",
1076 "Select dissector for websocket text",
1077 &pref_text_type
, text_types
, WEBSOCKET_NONE
);
1079 prefs_register_bool_preference(websocket_module
, "decompress",
1080 "Try to decompress permessage-deflate payload", NULL
, &pref_decompress
);
1082 prefs_register_uint_preference(websocket_module
, "max_unmasked_len", "Max unmasked payload length",
1083 "The default value is 256KB (1024x256) bytes. If the preference is too large, it may affect the parsing speed.",
1084 10, &pref_max_unmasked_len
);
1088 proto_reg_handoff_websocket(void)
1090 dissector_add_string("http.upgrade", "websocket", websocket_handle
);
1092 dissector_add_for_decode_as("tcp.port", websocket_handle
);
1094 heur_dissector_add("tcp", dissect_websocket_heur_tcp
, "WebSocket Heuristic", "websocket_tcp", proto_websocket
, HEURISTIC_DISABLE
);
1096 text_lines_handle
= find_dissector_add_dependency("data-text-lines", proto_websocket
);
1097 json_handle
= find_dissector_add_dependency("json", proto_websocket
);
1098 sip_handle
= find_dissector_add_dependency("sip", proto_websocket
);
1100 proto_http
= proto_get_id_by_filter_name("http");
1103 * Editor modelines - https://www.wireshark.org/tools/modelines.html
1108 * indent-tabs-mode: nil
1111 * vi: set shiftwidth=2 tabstop=8 expandtab:
1112 * :indentSize=2:tabSize=8:noTabs=true: