Coverage Report

Created: 2026-07-12 07:10

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