Coverage Report

Created: 2026-11-12 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
32
#define WEBSOCKET_NONE 0
46
16
#define WEBSOCKET_TEXT 1
47
16
#define WEBSOCKET_JSON 2
48
16
#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
16
#define MASK_WS_FIN 0x80
147
16
#define MASK_WS_RSV 0x70
148
16
#define MASK_WS_RSV1 0x40
149
16
#define MASK_WS_OPCODE 0x0F
150
16
#define MASK_WS_MASK 0x80
151
16
#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_remaining(tree, pinfo, &ei_ws_decompression_failed, tvb, 0);
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
      /* The header value may be packet scope (HTTP/2, HTTP/3). */
686
0
      websocket_conv->subprotocol = wmem_strdup(wmem_file_scope(),
687
0
          http_info->get_header_value(pinfo, "sec-websocket-protocol", !http_info->from_server));
688
0
      websocket_conv->server_port = http_info->server_port;
689
0
      const char *websocket_extensions = http_info->get_header_value(pinfo, "sec-websocket-extensions", !http_info->from_server);
690
0
      if (websocket_extensions) {
691
0
        websocket_parse_extensions(websocket_conv, websocket_extensions);
692
0
      }
693
0
      websocket_conv->http_version = http_info->http_version;
694
0
    } else if (pinfo->match_uint == pinfo->srcport || pinfo->match_uint == pinfo->destport) {
695
      /* The session was not set up by HTTP upgrade, but by Decode As.
696
       * Assume the matched port is the server port. */
697
0
      websocket_conv->server_port = (uint16_t)pinfo->match_uint;
698
0
    } else {
699
      /* match_uint is not one of the ports, which means the session was
700
       * set up by the heuristic Websocket dissector. */
701
0
      uint32_t low_port, high_port;
702
0
      if (pinfo->srcport > pinfo->destport) {
703
0
        low_port = pinfo->destport;
704
0
        high_port = pinfo->srcport;
705
0
      } else {
706
0
        low_port = pinfo->srcport;
707
0
        high_port = pinfo->destport;
708
0
      }
709
0
      if (dissector_get_uint_handle(port_subdissector_table, low_port)) {
710
0
        websocket_conv->server_port = (uint16_t)low_port;
711
0
      } else if (dissector_get_uint_handle(port_subdissector_table, high_port)) {
712
0
        websocket_conv->server_port = (uint16_t)high_port;
713
0
      }
714
0
    }
715
716
0
    conversation_add_proto_data(conv, proto_websocket, websocket_conv);
717
0
  }
718
719
0
  const bool from_server = http_info ? http_info->from_server : websocket_conv->server_port == pinfo->srcport;
720
721
0
  short_length = tvb_get_uint8(tvb, 1) & MASK_WS_PAYLOAD_LEN;
722
0
  mask_offset = 2;
723
0
  if (short_length == 126) {
724
0
    payload_length = tvb_get_ntohs(tvb, 2);
725
0
    mask_offset += 2;
726
0
  } else if (short_length == 127) {
727
    /* warning C4244: '=' : conversion from 'uint64_t' to 'unsigned ', possible loss of data */
728
0
    payload_length = (unsigned)tvb_get_ntoh64(tvb, 2);
729
0
    mask_offset += 8;
730
0
  } else {
731
0
    payload_length = short_length;
732
0
  }
733
734
  /* Mask */
735
0
  mask = (tvb_get_uint8(tvb, 1) & MASK_WS_MASK) != 0;
736
0
  payload_offset = mask_offset + (mask ? 4 : 0);
737
738
  /* HTTP/2 and HTTP/3 can carry more than one protocol in a given packet */
739
0
  if (websocket_conv->http_version < 2) {
740
0
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "WebSocket");
741
0
    col_set_str(pinfo->cinfo, COL_INFO, "WebSocket");
742
0
  } else {
743
0
    col_append_str(pinfo->cinfo, COL_INFO, ", WebSocket");
744
0
  }
745
746
0
  ti = proto_tree_add_item(tree, proto_websocket, tvb, 0, payload_offset, ENC_NA);
747
0
  ws_tree = proto_item_add_subtree(ti, ett_ws);
748
749
  /* Flags */
750
0
  proto_tree_add_item_ret_boolean(ws_tree, hf_ws_fin, tvb, 0, 1, ENC_NA, &fin);
751
0
  proto_tree_add_item(ws_tree, hf_ws_reserved, tvb, 0, 1, ENC_BIG_ENDIAN);
752
0
  if (websocket_conv->permessage_deflate) {
753
    /* RSV1 is Per-Message Compressed bit (RFC 7692). */
754
0
    proto_tree_add_item_ret_boolean(ws_tree, hf_ws_pmc, tvb, 0, 1, ENC_BIG_ENDIAN, &pmc);
755
0
  }
756
757
  /* Opcode */
758
0
  proto_tree_add_item_ret_uint8(ws_tree, hf_ws_opcode, tvb, 0, 1, ENC_BIG_ENDIAN, &opcode);
759
0
  col_append_fstr(pinfo->cinfo, COL_INFO, " %s", val_to_str_const(opcode, ws_opcode_vals, "Unknown Opcode"));
760
0
  col_append_str(pinfo->cinfo, COL_INFO, fin ? " [FIN]" : "[FRAGMENT] ");
761
762
  /* Add Mask bit to the tree */
763
0
  proto_tree_add_item(ws_tree, hf_ws_mask, tvb, 1, 1, ENC_NA);
764
0
  col_append_str(pinfo->cinfo, COL_INFO, mask ? " [MASKED]" : " ");
765
766
  /* (Extended) Payload Length */
767
0
  ti_len = proto_tree_add_item(ws_tree, hf_ws_payload_length, tvb, 1, 1, ENC_BIG_ENDIAN);
768
0
  if (short_length == 126) {
769
0
    proto_item_append_text(ti_len, " Extended Payload Length (16 bits)");
770
0
    proto_tree_add_item(ws_tree, hf_ws_payload_length_ext_16, tvb, 2, 2, ENC_BIG_ENDIAN);
771
0
  }
772
0
  else if (short_length == 127) {
773
0
    proto_item_append_text(ti_len, " Extended Payload Length (64 bits)");
774
0
    proto_tree_add_item(ws_tree, hf_ws_payload_length_ext_64, tvb, 2, 8, ENC_BIG_ENDIAN);
775
0
  }
776
777
  /* Masking-key */
778
0
  if (mask) {
779
0
    proto_tree_add_item(ws_tree, hf_ws_masking_key, tvb, mask_offset, 4, ENC_NA);
780
0
    masking_key = tvb_get_ptr(tvb, mask_offset, 4);
781
0
  }
782
783
0
  if (payload_length > 0) {
784
    /* Always unmask payload data before analysing it. */
785
0
    if (mask) {
786
0
      proto_tree_add_item(ws_tree, hf_ws_masked_payload, tvb, payload_offset, payload_length, ENC_NA);
787
0
      tvb_payload = tvb_unmasked(tvb, pinfo, payload_offset, payload_length, masking_key);
788
0
      add_new_data_source(pinfo, tvb_payload, "Unmasked data");
789
0
    } else {
790
0
      tvb_payload = tvb_new_subset_length(tvb, payload_offset, payload_length);
791
0
    }
792
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);
793
0
  }
794
795
0
  return tvb_captured_length(tvb);
796
0
}
797
798
static unsigned
799
get_websocket_frame_length(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
800
0
{
801
0
  unsigned      frame_length, payload_length;
802
0
  bool          mask;
803
804
0
  frame_length = 2;                 /* flags, opcode and Payload length */
805
0
  mask = tvb_get_uint8(tvb, offset + 1) & MASK_WS_MASK;
806
807
0
  payload_length = tvb_get_uint8(tvb, offset + 1) & MASK_WS_PAYLOAD_LEN;
808
0
  offset += 2; /* Skip flags, opcode and Payload length */
809
810
  /* Check for Extended Payload Length. */
811
0
  if (payload_length == 126) {
812
0
    if (tvb_reported_length_remaining(tvb, offset) < 2)
813
0
      return 0; /* Need more data. */
814
815
0
    payload_length = tvb_get_ntohs(tvb, offset);
816
0
    frame_length += 2;              /* Extended payload length */
817
0
  } else if (payload_length == 127) {
818
0
    if (tvb_reported_length_remaining(tvb, offset) < 8)
819
0
      return 0; /* Need more data. */
820
821
0
    payload_length = (unsigned)tvb_get_ntoh64(tvb, offset);
822
0
    frame_length += 8;              /* Extended payload length */
823
0
  }
824
825
0
  if (mask)
826
0
    frame_length += 4;              /* Masking-key */
827
0
  frame_length += payload_length;   /* Payload data */
828
0
  return frame_length;
829
0
}
830
831
static int
832
dissect_websocket(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
833
0
{
834
  /* data is an http_upgrade_info_t only when HTTP, HTTP/2 or HTTP/3 hands
835
   * the connection over. Called by name otherwise (for example from an
836
   * Exported PDU, which can pass a struct tcpinfo), it is something else. */
837
0
  if (data && !proto_is_frame_protocol(pinfo->layers, "http") &&
838
0
      !proto_is_frame_protocol(pinfo->layers, "http2") &&
839
0
      !proto_is_frame_protocol(pinfo->layers, "http3")) {
840
0
    data = NULL;
841
0
  }
842
843
  /* Need at least two bytes for flags, opcode and Payload length. */
844
0
  tcp_dissect_pdus(tvb, pinfo, tree, true, 2,
845
0
                   get_websocket_frame_length, dissect_websocket_frame, data);
846
0
  return tvb_captured_length(tvb);
847
0
}
848
849
static int
850
dissect_websocket_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
851
0
{
852
0
  return dissect_websocket(tvb, pinfo, tree, NULL);
853
0
}
854
855
static bool
856
test_websocket(packet_info* pinfo _U_, tvbuff_t* tvb, int offset _U_, void* data _U_)
857
0
{
858
0
  unsigned buffer_length = tvb_captured_length(tvb);
859
860
  // At least 2 bytes are required for a websocket header
861
0
  if (buffer_length < 2)
862
0
  {
863
0
    return false;
864
0
  }
865
0
  uint8_t first_byte = tvb_get_uint8(tvb, 0);
866
0
  uint8_t second_byte = tvb_get_uint8(tvb, 1);
867
868
  // Reserved bits RSV1, RSV2 and RSV3 need to be 0
869
0
  if ((first_byte & 0x70) > 0)
870
0
  {
871
0
    return false;
872
0
  }
873
874
0
  uint8_t op_code = first_byte & 0x0F;
875
876
  // op_code must be one of WS_CONTINUE, WS_TEXT, WS_BINARY, WS_CLOSE, WS_PING or WS_PONG
877
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))
878
0
  {
879
0
    return false;
880
0
  }
881
882
  // It is necessary to prevent that HTTP connection setups are treated as websocket.
883
  // If HTTP catches and it upgrades to websocket then HTTP takes care that websocket dissector gets called for this stream.
884
  // If first two byte start with printable characters from the alphabet it's likely that it is part of a HTTP connection setup.
885
0
  if (((first_byte >= 'a' && first_byte <= 'z') || (first_byte >= 'A' && first_byte <= 'Z')) &&
886
0
    ((second_byte >= 'a' && second_byte <= 'z') || (second_byte >= 'A' && second_byte <= 'Z')))
887
0
  {
888
0
    return false;
889
0
  }
890
891
0
  return true;
892
0
}
893
894
static bool
895
dissect_websocket_heur_tcp(tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, void* data)
896
0
{
897
0
  if (!test_websocket(pinfo, tvb, 0, data))
898
0
  {
899
0
    return false;
900
0
  }
901
0
  conversation_t* conversation = find_or_create_conversation(pinfo);
902
0
  conversation_set_dissector(conversation, websocket_tcp_handle);
903
904
0
  tcp_dissect_pdus(tvb, pinfo, tree, true, 2, get_websocket_frame_length, dissect_websocket_frame, NULL);
905
0
  return true;
906
0
}
907
908
void
909
proto_register_websocket(void)
910
16
{
911
912
16
  static hf_register_info hf[] = {
913
16
    { &hf_ws_fin,
914
16
      { "Fin", "websocket.fin",
915
16
      FT_BOOLEAN, 8, NULL, MASK_WS_FIN,
916
16
      "Indicates that this is the final fragment in a message", HFILL }
917
16
    },
918
16
    { &hf_ws_reserved,
919
16
      { "Reserved", "websocket.rsv",
920
16
      FT_UINT8, BASE_HEX, NULL, MASK_WS_RSV,
921
16
      "Must be zero", HFILL }
922
16
    },
923
16
    { &hf_ws_pmc,
924
16
      { "Per-Message Compressed", "websocket.pmc",
925
16
      FT_BOOLEAN, 8, NULL, MASK_WS_RSV1,
926
16
      "Whether a message is compressed or not", HFILL }
927
16
    },
928
16
    { &hf_ws_opcode,
929
16
      { "Opcode", "websocket.opcode",
930
16
      FT_UINT8, BASE_DEC, VALS(ws_opcode_vals), MASK_WS_OPCODE,
931
16
      "Defines the interpretation of the Payload data", HFILL }
932
16
    },
933
16
    { &hf_ws_mask,
934
16
      { "Mask", "websocket.mask",
935
16
      FT_BOOLEAN, 8, NULL, MASK_WS_MASK,
936
16
      "Defines whether the Payload data is masked", HFILL }
937
16
    },
938
16
    { &hf_ws_payload_length,
939
16
      { "Payload length", "websocket.payload_length",
940
16
      FT_UINT8, BASE_DEC, NULL, MASK_WS_PAYLOAD_LEN,
941
16
      "The length of the Payload data", HFILL }
942
16
    },
943
16
    { &hf_ws_payload_length_ext_16,
944
16
      { "Extended Payload length (16 bits)", "websocket.payload_length_ext_16",
945
16
      FT_UINT16, BASE_DEC, NULL, 0x0,
946
16
      "The length (16 bits) of the Payload data", HFILL }
947
16
    },
948
16
    { &hf_ws_payload_length_ext_64,
949
16
      { "Extended Payload length (64 bits)", "websocket.payload_length_ext_64",
950
16
      FT_UINT64, BASE_DEC, NULL, 0x0,
951
16
      "The length (64 bits) of the Payload data", HFILL }
952
16
    },
953
16
    { &hf_ws_masking_key,
954
16
      { "Masking-Key", "websocket.masking_key",
955
16
      FT_BYTES, BASE_NONE, NULL, 0x0,
956
16
      "All frames sent from the client to the server are masked by a 32-bit value that is contained within the frame", HFILL }
957
16
    },
958
16
    { &hf_ws_payload,
959
16
      { "Payload", "websocket.payload",
960
16
      FT_BYTES, BASE_NONE|BASE_NO_DISPLAY_VALUE, NULL, 0x0,
961
16
      "Payload (after unmasking)", HFILL }
962
16
    },
963
16
    { &hf_ws_masked_payload,
964
16
      { "Masked payload", "websocket.masked_payload",
965
16
      FT_BYTES, BASE_NONE|BASE_NO_DISPLAY_VALUE, NULL, 0x0,
966
16
      NULL, HFILL }
967
16
    },
968
16
    { &hf_ws_payload_continue,
969
16
      { "Continue", "websocket.payload.continue",
970
16
      FT_BYTES, BASE_NONE, NULL, 0x0,
971
16
      NULL, HFILL }
972
16
    },
973
16
    { &hf_ws_payload_text,
974
16
      { "Text", "websocket.payload.text",
975
16
      FT_STRING, BASE_NONE, NULL, 0x0,
976
16
      NULL, HFILL }
977
16
    },
978
16
    { &hf_ws_payload_close,
979
16
      { "Close", "websocket.payload.close",
980
16
      FT_BYTES, BASE_NONE|BASE_NO_DISPLAY_VALUE, NULL, 0x0,
981
16
      NULL, HFILL }
982
16
    },
983
16
    { &hf_ws_payload_close_status_code,
984
16
      { "Status code", "websocket.payload.close.status_code",
985
16
      FT_UINT16, BASE_DEC, VALS(ws_close_status_code_vals), 0x0,
986
16
      NULL, HFILL }
987
16
    },
988
16
    { &hf_ws_payload_close_reason,
989
16
      { "Reason", "websocket.payload.close.reason",
990
16
      FT_STRING, BASE_NONE, NULL, 0x0,
991
16
      NULL, HFILL }
992
16
    },
993
16
    { &hf_ws_payload_ping,
994
16
      { "Ping", "websocket.payload.ping",
995
16
      FT_BYTES, BASE_NONE, NULL, 0x0,
996
16
      NULL, HFILL }
997
16
    },
998
16
    { &hf_ws_payload_pong,
999
16
      { "Pong", "websocket.payload.pong",
1000
16
      FT_BYTES, BASE_NONE, NULL, 0x0,
1001
16
      NULL, HFILL }
1002
16
    },
1003
16
    { &hf_ws_payload_unknown,
1004
16
      { "Unknown", "websocket.payload.unknown",
1005
16
      FT_BYTES, BASE_NONE, NULL, 0x0,
1006
16
      NULL, HFILL }
1007
16
    },
1008
    /* Reassembly */
1009
16
    { &hf_ws_fragments,
1010
16
      { "Reassembled websocket Fragments", "websocket.fragments",
1011
16
      FT_NONE, BASE_NONE, NULL, 0x0,
1012
16
      NULL, HFILL }
1013
16
    },
1014
16
    { &hf_ws_fragment,
1015
16
      { "Websocket Fragment", "websocket.fragment",
1016
16
      FT_FRAMENUM, BASE_NONE, NULL, 0x0,
1017
16
      NULL, HFILL }
1018
16
    },
1019
16
    { &hf_ws_fragment_overlap,
1020
16
      { "Fragment overlap", "websocket.fragment.overlap",
1021
16
      FT_BOOLEAN, BASE_NONE, NULL, 0x0,
1022
16
      "Fragment overlaps with other fragments", HFILL }
1023
16
    },
1024
16
    { &hf_ws_fragment_overlap_conflict,
1025
16
      { "Conflicting data in fragment overlap", "websocket.fragment.overlap.conflict",
1026
16
      FT_BOOLEAN, BASE_NONE, NULL, 0x0,
1027
16
      "Overlapping fragments contained conflicting data", HFILL }
1028
16
    },
1029
16
    { &hf_ws_fragment_multiple_tails,
1030
16
      { "Multiple tail fragments found", "websocket.fragment.multipletails",
1031
16
      FT_BOOLEAN, BASE_NONE, NULL, 0x0,
1032
16
      "Several tails were found when defragmenting the packet", HFILL }
1033
16
    },
1034
16
    { &hf_ws_fragment_too_long_fragment,
1035
16
      { "Fragment too long", "websocket.fragment.toolongfragment",
1036
16
      FT_BOOLEAN, BASE_NONE, NULL, 0x0,
1037
16
      "Fragment contained data past end of packet", HFILL }
1038
16
    },
1039
16
    { &hf_ws_fragment_error,
1040
16
      { "Defragmentation error", "websocket.fragment.error",
1041
16
      FT_FRAMENUM, BASE_NONE, NULL, 0x0,
1042
16
      "Defragmentation error due to illegal fragments", HFILL }
1043
16
    },
1044
16
    { &hf_ws_fragment_count,
1045
16
      { "Fragment count", "websocket.fragment.count",
1046
16
      FT_UINT32, BASE_DEC, NULL, 0x0,
1047
16
      NULL, HFILL }
1048
16
    },
1049
16
    { &hf_ws_reassembled_length,
1050
16
      { "Reassembled websocket Payload length", "websocket.reassembled.length",
1051
16
      FT_UINT32, BASE_DEC, NULL, 0x0,
1052
16
      "The total length of the reassembled payload", HFILL }
1053
16
    },
1054
16
  };
1055
1056
1057
16
  static int *ett[] = {
1058
16
    &ett_ws,
1059
16
    &ett_ws_pl,
1060
16
    &ett_ws_mask,
1061
16
    &ett_ws_control_close,
1062
16
    &ett_ws_fragment,
1063
16
    &ett_ws_fragments,
1064
16
  };
1065
1066
16
  static ei_register_info ei[] = {
1067
16
    { &ei_ws_payload_unknown, { "websocket.payload.unknown.expert", PI_UNDECODED, PI_NOTE, "Dissector for Websocket Opcode", EXPFILL }},
1068
16
    { &ei_ws_decompression_failed, { "websocket.decompression.failed.expert", PI_PROTOCOL, PI_WARN, "Decompression failed", EXPFILL }},
1069
16
    { &ei_ws_not_fully_unmasked, { "websocket.payload.not.fully.unmasked", PI_UNDECODED, PI_NOTE, "Payload not fully unmasked", EXPFILL }},
1070
16
  };
1071
1072
16
  static const enum_val_t text_types[] = {
1073
16
      {"None", "No subdissection", WEBSOCKET_NONE},
1074
16
      {"Text", "Line based text",  WEBSOCKET_TEXT},
1075
16
      {"JSON", "As json",          WEBSOCKET_JSON},
1076
16
      {"SIP",  "As SIP",           WEBSOCKET_SIP},
1077
16
      {NULL, NULL, -1}
1078
16
  };
1079
1080
16
  module_t *websocket_module;
1081
16
  expert_module_t* expert_websocket;
1082
1083
16
  proto_websocket = proto_register_protocol("WebSocket", "WebSocket", "websocket");
1084
1085
  /*
1086
   * Heuristic dissectors SHOULD register themselves in
1087
   * this table using the standard heur_dissector_add()
1088
   * function.
1089
   */
1090
16
  heur_subdissector_list = register_heur_dissector_list_with_description("ws", "WebSocket data frame", proto_websocket);
1091
1092
16
  port_subdissector_table = register_dissector_table("ws.port",
1093
16
      "TCP port for protocols using WebSocket", proto_websocket, FT_UINT16, BASE_DEC);
1094
1095
16
  protocol_subdissector_table = register_dissector_table("ws.protocol",
1096
16
      "Negotiated WebSocket protocol", proto_websocket, FT_STRING, STRING_CASE_SENSITIVE);
1097
1098
16
  reassembly_table_register(&ws_reassembly_table, &addresses_reassembly_table_functions);
1099
1100
16
  websocket_follow_tap = register_tap("websocket_follow"); /* websocket follow tap */
1101
16
  register_follow_stream(proto_websocket, "websocket_follow", tcp_follow_conv_filter, tcp_follow_index_filter,
1102
16
                         tcp_follow_address_filter, tcp_port_to_display, follow_tvb_tap_listener,
1103
16
                         get_tcp_stream_count, NULL);
1104
1105
16
  proto_register_field_array(proto_websocket, hf, array_length(hf));
1106
16
  proto_register_subtree_array(ett, array_length(ett));
1107
16
  expert_websocket = expert_register_protocol(proto_websocket);
1108
16
  expert_register_field_array(expert_websocket, ei, array_length(ei));
1109
1110
16
  websocket_handle = register_dissector("websocket", dissect_websocket, proto_websocket);
1111
16
  websocket_tcp_handle = register_dissector("websocket_tcp", dissect_websocket_tcp, proto_websocket);
1112
1113
16
  websocket_module = prefs_register_protocol(proto_websocket, NULL);
1114
1115
16
  prefs_register_enum_preference(websocket_module, "text_type",
1116
16
        "Dissect websocket text as",
1117
16
        "Select dissector for websocket text",
1118
16
        &pref_text_type, text_types, WEBSOCKET_NONE);
1119
1120
16
  prefs_register_bool_preference(websocket_module, "decompress",
1121
16
        "Try to decompress permessage-deflate payload", NULL, &pref_decompress);
1122
1123
16
  prefs_register_uint_preference(websocket_module, "max_unmasked_len", "Max unmasked payload length",
1124
16
    "The default value is 256KB (1024x256) bytes. If the preference is too large, it may affect the parsing speed.",
1125
16
    10, &pref_max_unmasked_len);
1126
16
}
1127
1128
void
1129
proto_reg_handoff_websocket(void)
1130
16
{
1131
16
  dissector_add_string("http.upgrade", "websocket", websocket_handle);
1132
16
  dissector_add_string("http.upgrade", "WebSocket", websocket_handle);
1133
1134
16
  dissector_add_for_decode_as("tcp.port", websocket_tcp_handle);
1135
1136
16
  heur_dissector_add("tcp", dissect_websocket_heur_tcp, "WebSocket Heuristic", "websocket_tcp", proto_websocket, HEURISTIC_DISABLE);
1137
1138
16
  text_lines_handle = find_dissector_add_dependency("data-text-lines", proto_websocket);
1139
16
  json_handle = find_dissector_add_dependency("json", proto_websocket);
1140
16
  sip_handle = find_dissector_add_dependency("sip", proto_websocket);
1141
16
}
1142
/*
1143
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
1144
 *
1145
 * Local variables:
1146
 * c-basic-offset: 2
1147
 * tab-width: 8
1148
 * indent-tabs-mode: nil
1149
 * End:
1150
 *
1151
 * vi: set shiftwidth=2 tabstop=8 expandtab:
1152
 * :indentSize=2:tabSize=8:noTabs=true:
1153
 */