Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / epan / dissectors / packet-websocket.c
blobf7a83372dcb5bbd563877290ab795adc442e30d4
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>
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
13 #include "config.h"
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"
29 #ifdef HAVE_ZLIBNG
30 #define ZLIB_PREFIX(x) zng_ ## x
31 #include <zlib-ng.h>
32 typedef zng_stream zlib_stream;
33 #else
34 #ifdef HAVE_ZLIB
35 #define ZLIB_PREFIX(x) x
36 #include <zlib.h>
37 typedef z_stream zlib_stream;
38 #endif /* HAVE_ZLIB */
39 #endif
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;
69 typedef struct {
70 const char *subprotocol;
71 uint16_t server_port;
72 bool permessage_deflate;
73 #ifdef HAVE_ZLIBNG
74 bool permessage_deflate_ok;
75 int8_t server_wbits;
76 int8_t client_wbits;
77 zng_streamp server_take_over_context;
78 zng_streamp client_take_over_context;
79 #else
80 #ifdef HAVE_ZLIB
81 bool permessage_deflate_ok;
82 int8_t server_wbits;
83 int8_t client_wbits;
84 z_streamp server_take_over_context;
85 z_streamp client_take_over_context;
86 #endif
87 #endif
88 uint32_t frag_id;
89 bool first_frag;
90 uint8_t first_frag_opcode;
91 bool first_frag_pmc;
92 } websocket_conv_t;
94 #if defined (HAVE_ZLIB) || defined (HAVE_ZLIBNG)
95 typedef struct {
96 uint8_t *decompr_payload;
97 unsigned decompr_len;
98 } websocket_packet_t;
99 #endif
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;
136 static int ett_ws;
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
148 #define WS_TEXT 0x1
149 #define WS_BINARY 0x2
150 #define WS_CLOSE 0x8
151 #define WS_PING 0x9
152 #define WS_PONG 0xA
154 static const value_string ws_opcode_vals[] = {
155 { WS_CONTINUE, "Continuation" },
156 { WS_TEXT, "Text" },
157 { WS_BINARY, "Binary" },
158 { WS_CLOSE, "Connection Close" },
159 { WS_PING, "Ping" },
160 { WS_PONG, "Pong" },
161 { 0, NULL}
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" },
185 { 0, NULL}
188 static const fragment_items ws_frag_items = {
189 &ett_ws_fragments,
190 &ett_ws_fragment,
192 &hf_ws_fragments,
193 &hf_ws_fragment,
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,
200 NULL,
201 &hf_ws_reassembled_length,
202 /* Reassembled data field */
203 NULL,
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;
213 static tvbuff_t *
214 tvb_unmasked(tvbuff_t *tvb, packet_info *pinfo, const unsigned offset, unsigned payload_length, const uint8_t *masking_key)
217 char *data_unmask;
218 unsigned i;
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)
233 static int8_t
234 websocket_extract_wbits(const char *str)
236 uint8_t wbits;
237 const char *end;
239 if (str && ws_strtou8(str, &end, &wbits) &&
240 (*end == '\0' || strchr(";\t ", *end))) {
241 if (wbits < 8) {
242 wbits = 8;
243 } else if (wbits > 15) {
244 wbits = 15;
246 } else {
247 wbits = 15;
249 return -wbits;
252 static void *
253 websocket_zalloc(void *opaque _U_, unsigned int items, unsigned int size)
255 return wmem_alloc(wmem_file_scope(), items*size);
258 static void
259 websocket_zfree(void *opaque _U_, void *addr)
261 wmem_free(wmem_file_scope(), addr);
263 #ifdef HAVE_ZLIBNG
264 static zng_streamp
265 websocket_init_z_stream_context(int8_t wbits)
267 zng_streamp z_strm = wmem_new0(wmem_file_scope(), zlib_stream);
268 #else
269 static z_streamp
270 websocket_init_z_stream_context(int8_t wbits)
272 z_streamp z_strm = wmem_new0(wmem_file_scope(), zlib_stream);
273 #endif
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);
280 return NULL;
282 return 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.
290 static bool
291 #ifdef HAVE_ZLIBNG
292 websocket_uncompress(tvbuff_t* tvb, packet_info* pinfo, zng_streamp z_strm, tvbuff_t** uncompressed_tvb, uint32_t key)
293 #else
294 websocket_uncompress(tvbuff_t *tvb, packet_info *pinfo, z_streamp z_strm, tvbuff_t **uncompressed_tvb, uint32_t key)
295 #endif
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;
306 int err;
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. */
319 do {
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;
327 if (avail_bytes) {
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);
345 return true;
346 } else {
347 /* decompression failed */
348 wmem_free(wmem_file_scope(), decompr_payload);
349 return false;
352 #endif
354 static void
355 dissect_websocket_control_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint8_t opcode)
357 proto_item *ti;
358 proto_tree *subtree;
359 const unsigned offset = 0, length = tvb_reported_length(tvb);
361 switch (opcode) {
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. */
366 if (length >= 2) {
367 proto_tree_add_item(subtree, hf_ws_payload_close_status_code, tvb, offset, 2, ENC_BIG_ENDIAN);
368 if (length > 2)
369 proto_tree_add_item(subtree, hf_ws_payload_close_reason, tvb, offset+2, length-2, ENC_UTF_8);
371 break;
373 case WS_PING: /* Ping */
374 proto_tree_add_item(tree, hf_ws_payload_ping, tvb, offset, length, ENC_NA);
375 break;
377 case WS_PONG: /* Pong */
378 proto_tree_add_item(tree, hf_ws_payload_pong, tvb, offset, length, ENC_NA);
379 break;
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);
386 break;
390 static void
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_)
393 proto_item *ti;
394 dissector_handle_t handle = NULL;
395 heur_dtbl_entry_t *hdtbl_entry;
397 if (pinfo->fragmented) {
398 /* Skip dissecting fragmented payload data. */
399 return;
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)) {
415 #ifdef HAVE_ZLIBNG
416 zng_streamp z_strm;
417 #else
418 z_streamp z_strm;
419 #endif
420 int8_t wbits;
422 if (pinfo->destport == websocket_conv->server_port) {
423 z_strm = websocket_conv->server_take_over_context;
424 wbits = websocket_conv->server_wbits;
425 } else {
426 z_strm = websocket_conv->client_take_over_context;
427 wbits = websocket_conv->client_wbits;
430 if (z_strm) {
431 uncompress_ok = websocket_uncompress(tvb, pinfo, z_strm, &uncompressed, raw_offset);
432 } else {
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);
440 } else {
441 websocket_packet_t *pkt_info =
442 (websocket_packet_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_websocket, raw_offset);
443 if (pkt_info) {
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);
453 return;
455 if (uncompressed) {
456 add_new_data_source(pinfo, uncompressed, "Decompressed payload");
457 tvb = uncompressed;
460 #endif
462 if (have_tap_listener(websocket_follow_tap)) {
463 tap_queue_packet(websocket_follow_tap, pinfo, tvb);
466 if (handle) {
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. */
474 switch (opcode) {
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) {
482 case WEBSOCKET_TEXT:
483 case WEBSOCKET_NONE:
484 default:
485 /* Assume that most text protocols are line-based. */
486 call_dissector(text_lines_handle, tvb, pinfo, tree);
487 break;
488 case WEBSOCKET_JSON:
489 call_dissector(json_handle, tvb, pinfo, tree);
490 break;
491 case WEBSOCKET_SIP:
492 call_dissector(sip_handle, tvb, pinfo, tree);
493 break;
495 pinfo->match_string = saved_match_string;
497 break;
499 case WS_BINARY: /* Binary */
500 call_data_dissector(tvb, pinfo, tree);
501 break;
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);
508 break;
512 static void
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);
550 #endif
553 static void
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);
558 proto_item *ti;
559 proto_tree *pl_tree;
560 tvbuff_t *tvb_appdata;
561 tvbuff_t *frag_tvb = NULL;
563 /* Payload */
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);
573 /* Extension Data */
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);
578 return;
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),
592 !fin);
593 frag_tvb = process_reassembled_data(tvb, offset, pinfo,
594 "Reassembled Message", frag_msg, &ws_frag_items,
595 NULL, tree);
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));
604 if (frag_tvb) {
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;
612 } else {
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);
616 tvb_appdata = tvb;
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);
624 return;
627 dissect_websocket_data_frame(tvb_appdata, pinfo, tree, pl_tree, opcode, websocket_conv, raw_offset);
628 pinfo->fragmented = save_fragmented;
631 static int
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;
636 uint8_t fin, opcode;
637 bool mask;
638 unsigned short_length, payload_length;
639 unsigned payload_offset, mask_offset;
640 proto_tree *ws_tree;
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-*
648 * headers once.
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);
658 if (http_conv) {
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;
668 } else {
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;
675 } else {
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);
687 } else {
688 websocket_conv->first_frag = false;
691 short_length = tvb_get_uint8(tvb, 1) & MASK_WS_PAYLOAD_LEN;
692 mask_offset = 2;
693 if (short_length == 126) {
694 payload_length = tvb_get_ntohs(tvb, 2);
695 mask_offset += 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);
699 mask_offset += 8;
700 } else {
701 payload_length = short_length;
704 /* Mask */
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);
714 /* Flags */
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);
727 /* Opcode */
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);
752 /* Masking-key */
753 if (mask) {
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. */
760 if (mask) {
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");
764 } else {
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);
773 static unsigned
774 get_websocket_frame_length(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
776 unsigned frame_length, payload_length;
777 bool mask;
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 */
800 if (mask)
801 frame_length += 4; /* Masking-key */
802 frame_length += payload_length; /* Payload data */
803 return frame_length;
806 static int
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);
815 static bool
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)
823 return false;
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)
831 return false;
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))
839 return false;
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')))
848 return false;
851 return true;
854 static bool
855 dissect_websocket_heur_tcp(tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, void* data)
857 if (!test_websocket(pinfo, tvb, 0, data))
859 return false;
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);
865 return true;
868 void
869 proto_register_websocket(void)
872 static hf_register_info hf[] = {
873 { &hf_ws_fin,
874 { "Fin", "websocket.fin",
875 FT_BOOLEAN, 8, NULL, MASK_WS_FIN,
876 "Indicates that this is the final fragment in a message", HFILL }
878 { &hf_ws_reserved,
879 { "Reserved", "websocket.rsv",
880 FT_UINT8, BASE_HEX, NULL, MASK_WS_RSV,
881 "Must be zero", HFILL }
883 { &hf_ws_pmc,
884 { "Per-Message Compressed", "websocket.pmc",
885 FT_BOOLEAN, 8, NULL, MASK_WS_RSV1,
886 "Whether a message is compressed or not", HFILL }
888 { &hf_ws_opcode,
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 }
893 { &hf_ws_mask,
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 }
918 { &hf_ws_payload,
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,
926 NULL, HFILL }
928 { &hf_ws_payload_continue,
929 { "Continue", "websocket.payload.continue",
930 FT_BYTES, BASE_NONE, NULL, 0x0,
931 NULL, HFILL }
933 { &hf_ws_payload_text,
934 { "Text", "websocket.payload.text",
935 FT_STRING, BASE_NONE, NULL, 0x0,
936 NULL, HFILL }
938 { &hf_ws_payload_close,
939 { "Close", "websocket.payload.close",
940 FT_BYTES, BASE_NONE|BASE_NO_DISPLAY_VALUE, NULL, 0x0,
941 NULL, HFILL }
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,
946 NULL, HFILL }
948 { &hf_ws_payload_close_reason,
949 { "Reason", "websocket.payload.close.reason",
950 FT_STRING, BASE_NONE, NULL, 0x0,
951 NULL, HFILL }
953 { &hf_ws_payload_ping,
954 { "Ping", "websocket.payload.ping",
955 FT_BYTES, BASE_NONE, NULL, 0x0,
956 NULL, HFILL }
958 { &hf_ws_payload_pong,
959 { "Pong", "websocket.payload.pong",
960 FT_BYTES, BASE_NONE, NULL, 0x0,
961 NULL, HFILL }
963 { &hf_ws_payload_unknown,
964 { "Unknown", "websocket.payload.unknown",
965 FT_BYTES, BASE_NONE, NULL, 0x0,
966 NULL, HFILL }
968 /* Reassembly */
969 { &hf_ws_fragments,
970 { "Reassembled websocket Fragments", "websocket.fragments",
971 FT_NONE, BASE_NONE, NULL, 0x0,
972 NULL, HFILL }
974 { &hf_ws_fragment,
975 { "Websocket Fragment", "websocket.fragment",
976 FT_FRAMENUM, BASE_NONE, NULL, 0x0,
977 NULL, HFILL }
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,
1007 NULL, HFILL }
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[] = {
1018 &ett_ws,
1019 &ett_ws_pl,
1020 &ett_ws_mask,
1021 &ett_ws_control_close,
1022 &ett_ws_fragment,
1023 &ett_ws_fragments,
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},
1037 {NULL, NULL, -1}
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()
1048 * function.
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);
1087 void
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
1105 * Local variables:
1106 * c-basic-offset: 2
1107 * tab-width: 8
1108 * indent-tabs-mode: nil
1109 * End:
1111 * vi: set shiftwidth=2 tabstop=8 expandtab:
1112 * :indentSize=2:tabSize=8:noTabs=true: