]> git.samba.org - metze/wireshark/wip.git/blob - epan/dissectors/packet-websocket.c
GSM A DTAP: add UMTS EVS to supported codecs list IE
[metze/wireshark/wip.git] / epan / dissectors / packet-websocket.c
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
15 #include <epan/conversation.h>
16 #include <epan/proto_data.h>
17 #include <epan/packet.h>
18 #include <epan/expert.h>
19 #include <epan/prefs.h>
20 #include <wsutil/strtoi.h>
21
22 #include "packet-http.h"
23 #include "packet-tcp.h"
24
25 #ifdef HAVE_ZLIB
26 #define ZLIB_CONST
27 #include <zlib.h>
28 #endif
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 text_lines_handle;
41 static dissector_handle_t json_handle;
42 static dissector_handle_t sip_handle;
43
44 #define WEBSOCKET_NONE 0
45 #define WEBSOCKET_TEXT 1
46 #define WEBSOCKET_JSON 2
47 #define WEBSOCKET_SIP 3
48
49 static gint  pref_text_type             = WEBSOCKET_NONE;
50 static gboolean pref_decompress         = TRUE;
51
52 typedef struct {
53   const char   *subprotocol;
54   guint16       server_port;
55   gboolean      permessage_deflate;
56 #ifdef HAVE_ZLIB
57   gboolean      permessage_deflate_ok;
58   gint8         server_wbits;
59   gint8         client_wbits;
60   z_streamp     server_take_over_context;
61   z_streamp     client_take_over_context;
62 #endif
63 } websocket_conv_t;
64
65 #ifdef HAVE_ZLIB
66 typedef struct {
67   guint8 *decompr_payload;
68   guint decompr_len;
69 } websocket_packet_t;
70 #endif
71
72 /* Initialize the protocol and registered fields */
73 static int proto_websocket = -1;
74 static int proto_http = -1;
75
76 static int hf_ws_fin = -1;
77 static int hf_ws_reserved = -1;
78 static int hf_ws_pmc = -1;
79 static int hf_ws_opcode = -1;
80 static int hf_ws_mask = -1;
81 static int hf_ws_payload_length = -1;
82 static int hf_ws_payload_length_ext_16 = -1;
83 static int hf_ws_payload_length_ext_64 = -1;
84 static int hf_ws_masking_key = -1;
85 static int hf_ws_payload = -1;
86 static int hf_ws_masked_payload = -1;
87 static int hf_ws_payload_continue = -1;
88 static int hf_ws_payload_close = -1;
89 static int hf_ws_payload_close_status_code = -1;
90 static int hf_ws_payload_close_reason = -1;
91 static int hf_ws_payload_ping = -1;
92 static int hf_ws_payload_pong = -1;
93 static int hf_ws_payload_unknown = -1;
94
95 static gint ett_ws = -1;
96 static gint ett_ws_pl = -1;
97 static gint ett_ws_mask = -1;
98 static gint ett_ws_control_close = -1;
99
100 static expert_field ei_ws_payload_unknown = EI_INIT;
101 static expert_field ei_ws_decompression_failed = EI_INIT;
102
103 #define WS_CONTINUE 0x0
104 #define WS_TEXT     0x1
105 #define WS_BINARY   0x2
106 #define WS_CLOSE    0x8
107 #define WS_PING     0x9
108 #define WS_PONG     0xA
109
110 static const value_string ws_opcode_vals[] = {
111   { WS_CONTINUE, "Continuation" },
112   { WS_TEXT, "Text" },
113   { WS_BINARY, "Binary" },
114   { WS_CLOSE, "Connection Close" },
115   { WS_PING, "Ping" },
116   { WS_PONG, "Pong" },
117   { 0, NULL}
118 };
119
120 #define MASK_WS_FIN 0x80
121 #define MASK_WS_RSV 0x70
122 #define MASK_WS_RSV1 0x40
123 #define MASK_WS_OPCODE 0x0F
124 #define MASK_WS_MASK 0x80
125 #define MASK_WS_PAYLOAD_LEN 0x7F
126
127 static const value_string ws_close_status_code_vals[] = {
128   { 1000, "Normal Closure" },
129   { 1001, "Going Away" },
130   { 1002, "Protocol error" },
131   { 1003, "Unsupported Data" },
132   { 1004, "---Reserved----" },
133   { 1005, "No Status Rcvd" },
134   { 1006, "Abnormal Closure" },
135   { 1007, "Invalid frame payload data" },
136   { 1008, "Policy Violation" },
137   { 1009, "Message Too Big" },
138   { 1010, "Mandatory Ext." },
139   { 1011, "Internal Server" },
140   { 1015, "TLS handshake" },
141   { 0,    NULL}
142 };
143
144 static dissector_table_t port_subdissector_table;
145 static dissector_table_t protocol_subdissector_table;
146 static heur_dissector_list_t heur_subdissector_list;
147
148 #define MAX_UNMASKED_LEN (1024 * 256)
149 static tvbuff_t *
150 tvb_unmasked(tvbuff_t *tvb, packet_info *pinfo, const guint offset, guint payload_length, const guint8 *masking_key)
151 {
152
153   gchar        *data_unmask;
154   guint         i;
155   const guint8 *data_mask;
156   guint         unmasked_length = payload_length > MAX_UNMASKED_LEN ? MAX_UNMASKED_LEN : payload_length;
157
158   data_unmask = (gchar *)wmem_alloc(pinfo->pool, unmasked_length);
159   data_mask   = tvb_get_ptr(tvb, offset, unmasked_length);
160   /* Unmasked(XOR) Data... */
161   for(i=0; i < unmasked_length; i++) {
162     data_unmask[i] = data_mask[i] ^ masking_key[i%4];
163   }
164
165   return tvb_new_real_data(data_unmask, unmasked_length, payload_length);
166 }
167
168 #ifdef HAVE_ZLIB
169 static gint8
170 websocket_extract_wbits(const gchar *str)
171 {
172   guint8 wbits;
173   const gchar *end;
174
175   if (str && ws_strtou8(str, &end, &wbits) &&
176       (*end == '\0' || strchr(";\t ", *end))) {
177     if (wbits < 8) {
178       wbits = 8;
179     } else if (wbits > 15) {
180       wbits = 15;
181     }
182   } else {
183     wbits = 15;
184   }
185   return -wbits;
186 }
187
188 static void *
189 websocket_zalloc(void *opaque _U_, unsigned int items, unsigned int size)
190 {
191   return wmem_alloc(wmem_file_scope(), items*size);
192 }
193
194 static void
195 websocket_zfree(void *opaque _U_, void *addr)
196 {
197   wmem_free(wmem_file_scope(), addr);
198 }
199
200 static z_streamp
201 websocket_init_z_stream_context(gint8 wbits)
202 {
203   z_streamp z_strm = wmem_new0(wmem_file_scope(), z_stream);
204
205   z_strm->zalloc = websocket_zalloc;
206   z_strm->zfree = websocket_zfree;
207
208   if (inflateInit2(z_strm, wbits) != Z_OK) {
209     inflateEnd(z_strm);
210     wmem_free(wmem_file_scope(), z_strm);
211     return NULL;
212   }
213   return z_strm;
214 }
215
216 /*
217  * Decompress the given buffer using the given zlib context. On success, the
218  * (possibly empty) buffer is stored as "proto data" and TRUE is returned.
219  * Otherwise FALSE is returned.
220  */
221 static gboolean
222 websocket_uncompress(tvbuff_t *tvb, packet_info *pinfo, z_streamp z_strm, tvbuff_t **uncompressed_tvb, guint32 key)
223 {
224   /*
225    * Decompression a message: append "0x00 0x00 0xff 0xff" to the end of
226    * message, then apply DEFLATE to the result.
227    * https://tools.ietf.org/html/rfc7692#section-7.2.2
228    */
229   guint8   *decompr_payload = NULL;
230   guint     decompr_len = 0;
231   guint     compr_len, decompr_buf_len;
232   guint8   *compr_payload, *decompr_buf;
233   gint      err;
234
235   compr_len = tvb_captured_length(tvb) + 4;
236   compr_payload = (guint8 *)wmem_alloc(wmem_packet_scope(), compr_len);
237   tvb_memcpy(tvb, compr_payload, 0, compr_len-4);
238   compr_payload[compr_len-4] = compr_payload[compr_len-3] = 0x00;
239   compr_payload[compr_len-2] = compr_payload[compr_len-1] = 0xff;
240   decompr_buf_len = 2*compr_len;
241   decompr_buf = (guint8 *)wmem_alloc(wmem_packet_scope(), decompr_buf_len);
242
243   z_strm->next_in = compr_payload;
244   z_strm->avail_in = compr_len;
245   /* Decompress all available data. */
246   do {
247     z_strm->next_out = decompr_buf;
248     z_strm->avail_out = decompr_buf_len;
249
250     err = inflate(z_strm, Z_SYNC_FLUSH);
251
252     if (err == Z_OK || err == Z_STREAM_END || err == Z_BUF_ERROR) {
253       guint avail_bytes = decompr_buf_len - z_strm->avail_out;
254       if (avail_bytes) {
255         decompr_payload = (guint8 *)wmem_realloc(wmem_file_scope(), decompr_payload,
256                                                  decompr_len + avail_bytes);
257         memcpy(&decompr_payload[decompr_len], decompr_buf, avail_bytes);
258         decompr_len += avail_bytes;
259       }
260     }
261   } while (err == Z_OK);
262
263   if (err == Z_STREAM_END || err == Z_BUF_ERROR) {
264     /* Data was (partially) uncompressed. */
265     websocket_packet_t *pkt_info = wmem_new0(wmem_file_scope(), websocket_packet_t);
266     if (decompr_len > 0) {
267       pkt_info->decompr_payload = decompr_payload;
268       pkt_info->decompr_len = decompr_len;
269       *uncompressed_tvb = tvb_new_real_data(decompr_payload, decompr_len, decompr_len);
270     }
271     p_add_proto_data(wmem_file_scope(), pinfo, proto_websocket, key, pkt_info);
272     return TRUE;
273   } else {
274     /* decompression failed */
275     wmem_free(wmem_file_scope(), decompr_payload);
276     return FALSE;
277   }
278 }
279 #endif
280
281 static void
282 dissect_websocket_control_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint8 opcode)
283 {
284   proto_item         *ti;
285   proto_tree         *subtree;
286   const guint         offset = 0, length = tvb_reported_length(tvb);
287
288   switch (opcode) {
289     case WS_CLOSE: /* Close */
290       ti = proto_tree_add_item(tree, hf_ws_payload_close, tvb, offset, length, ENC_NA);
291       subtree = proto_item_add_subtree(ti, ett_ws_control_close);
292       /* Close frame MAY contain a body. */
293       if (length >= 2) {
294         proto_tree_add_item(subtree, hf_ws_payload_close_status_code, tvb, offset, 2, ENC_BIG_ENDIAN);
295         if (length > 2)
296           proto_tree_add_item(subtree, hf_ws_payload_close_reason, tvb, offset+2, length-2, ENC_UTF_8|ENC_NA);
297       }
298       break;
299
300     case WS_PING: /* Ping */
301       proto_tree_add_item(tree, hf_ws_payload_ping, tvb, offset, length, ENC_NA);
302       break;
303
304     case WS_PONG: /* Pong */
305       proto_tree_add_item(tree, hf_ws_payload_pong, tvb, offset, length, ENC_NA);
306       break;
307
308     default: /* Unknown */
309       ti = proto_tree_add_item(tree, hf_ws_payload_unknown, tvb, offset, length, ENC_NA);
310       expert_add_info_format(pinfo, ti, &ei_ws_payload_unknown, "Dissector for Websocket Opcode (%d)"
311         " code not implemented, Contact Wireshark developers"
312         " if you want this supported", opcode);
313       break;
314   }
315 }
316
317 static void
318 dissect_websocket_data_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_tree *pl_tree, guint8 opcode, websocket_conv_t *websocket_conv, gboolean pmc _U_, gint raw_offset _U_)
319 {
320   proto_item         *ti;
321   dissector_handle_t  handle = NULL;
322   heur_dtbl_entry_t  *hdtbl_entry;
323
324   /* try to find a dissector which accepts the data. */
325   if (websocket_conv->subprotocol) {
326     handle = dissector_get_string_handle(protocol_subdissector_table, websocket_conv->subprotocol);
327   } else if (websocket_conv->server_port) {
328     handle = dissector_get_uint_handle(port_subdissector_table, websocket_conv->server_port);
329   }
330
331 #ifdef HAVE_ZLIB
332   if (websocket_conv->permessage_deflate_ok && pmc) {
333     tvbuff_t   *uncompressed = NULL;
334     gboolean    uncompress_ok = FALSE;
335
336     if (!PINFO_FD_VISITED(pinfo)) {
337       z_streamp z_strm;
338       gint8 wbits;
339
340       if (pinfo->destport == websocket_conv->server_port) {
341         z_strm = websocket_conv->server_take_over_context;
342         wbits = websocket_conv->server_wbits;
343       } else {
344         z_strm = websocket_conv->client_take_over_context;
345         wbits = websocket_conv->client_wbits;
346       }
347
348       if (z_strm) {
349         uncompress_ok = websocket_uncompress(tvb, pinfo, z_strm, &uncompressed, raw_offset);
350       } else {
351         /* no context take over, initialize a new context */
352         z_strm = wmem_new0(wmem_packet_scope(), z_stream);
353         if (inflateInit2(z_strm, wbits) == Z_OK) {
354           uncompress_ok = websocket_uncompress(tvb, pinfo, z_strm, &uncompressed, raw_offset);
355         }
356         inflateEnd(z_strm);
357       }
358     } else {
359       websocket_packet_t *pkt_info =
360           (websocket_packet_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_websocket, raw_offset);
361       if (pkt_info) {
362         uncompress_ok = TRUE;
363         if (pkt_info->decompr_len > 0) {
364           uncompressed = tvb_new_real_data(pkt_info->decompr_payload, pkt_info->decompr_len, pkt_info->decompr_len);
365         }
366       }
367     }
368
369     if (!uncompress_ok) {
370       proto_tree_add_expert(tree, pinfo, &ei_ws_decompression_failed, tvb, 0, -1);
371       return;
372     }
373     if (uncompressed) {
374       add_new_data_source(pinfo, uncompressed, "Decompressed payload");
375       tvb = uncompressed;
376     }
377   }
378 #endif
379
380   if (handle) {
381     call_dissector_only(handle, tvb, pinfo, tree, NULL);
382     return; /* handle found, assume dissector took care of it. */
383   } else if (dissector_try_heuristic(heur_subdissector_list, tvb, pinfo, tree, &hdtbl_entry, NULL)) {
384     return; /* heuristics dissector handled it. */
385   }
386
387   /* no dissector wanted it, try to print something appropriate. */
388   switch (opcode) {
389     case WS_TEXT: /* Text */
390     {
391       const gchar  *saved_match_string = pinfo->match_string;
392
393       pinfo->match_string = NULL;
394       switch (pref_text_type) {
395       case WEBSOCKET_TEXT:
396       case WEBSOCKET_NONE:
397       default:
398         /* Assume that most text protocols are line-based. */
399         call_dissector(text_lines_handle, tvb, pinfo, tree);
400         break;
401       case WEBSOCKET_JSON:
402         call_dissector(json_handle, tvb, pinfo, tree);
403         break;
404       case WEBSOCKET_SIP:
405         call_dissector(sip_handle, tvb, pinfo, tree);
406         break;
407       }
408       pinfo->match_string = saved_match_string;
409     }
410     break;
411
412     case WS_BINARY: /* Binary */
413       call_data_dissector(tvb, pinfo, tree);
414       break;
415
416     default: /* Unknown */
417       ti = proto_tree_add_item(pl_tree, hf_ws_payload_unknown, tvb, 0, -1, ENC_NA);
418       expert_add_info_format(pinfo, ti, &ei_ws_payload_unknown, "Dissector for Websocket Opcode (%d)"
419         " code not implemented, Contact Wireshark developers"
420         " if you want this supported", opcode);
421       break;
422   }
423 }
424
425 static void
426 websocket_parse_extensions(websocket_conv_t *websocket_conv, const char *str)
427 {
428   /*
429    * Grammar for the header:
430    *
431    *    Sec-WebSocket-Extensions = extension-list
432    *    extension-list = 1#extension
433    *    extension = extension-token *( ";" extension-param )
434    *    extension-token = registered-token
435    *    registered-token = token
436    *    extension-param = token [ "=" (token | quoted-string) ]
437    */
438
439   /*
440    * RFC 7692 permessage-deflate parsing.
441    */
442
443   websocket_conv->permessage_deflate = !!strstr(str, "permessage-deflate");
444 #ifdef HAVE_ZLIB
445   websocket_conv->permessage_deflate_ok = pref_decompress &&
446        websocket_conv->permessage_deflate;
447   if (websocket_conv->permessage_deflate_ok) {
448     websocket_conv->server_wbits =
449         websocket_extract_wbits(strstr(str, "server_max_window_bits="));
450     if (!strstr(str, "server_no_context_takeover")) {
451       websocket_conv->server_take_over_context =
452           websocket_init_z_stream_context(websocket_conv->server_wbits);
453     }
454     websocket_conv->client_wbits =
455         websocket_extract_wbits(strstr(str, "client_max_window_bits="));
456     if (!strstr(str, "client_no_context_takeover")) {
457       websocket_conv->client_take_over_context =
458           websocket_init_z_stream_context(websocket_conv->client_wbits);
459     }
460   }
461 #endif
462 }
463
464 static void
465 dissect_websocket_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_tree *ws_tree, guint8 opcode, websocket_conv_t *websocket_conv, gboolean pmc, gint raw_offset)
466 {
467   const guint         offset = 0, length = tvb_reported_length(tvb);
468   proto_item         *ti;
469   proto_tree         *pl_tree;
470   tvbuff_t           *tvb_appdata;
471
472   /* Payload */
473   ti = proto_tree_add_item(ws_tree, hf_ws_payload, tvb, offset, length, ENC_NA);
474   pl_tree = proto_item_add_subtree(ti, ett_ws_pl);
475
476   /* Extension Data */
477   /* TODO: Add dissector of Extension (not extension available for the moment...) */
478
479
480   /* Application Data */
481   if (opcode == WS_CONTINUE) {
482     proto_tree_add_item(tree, hf_ws_payload_continue, tvb, offset, length, ENC_NA);
483     /* TODO: Add Fragmentation support (needs FIN bit)
484      * https://tools.ietf.org/html/rfc6455#section-5.4 */
485     return;
486   }
487   /* Right now this is exactly the same, this may change when exts. are added.
488   tvb_appdata = tvb_new_subset_length_caplen(tvb, offset, length, length);
489   */
490   tvb_appdata = tvb;
491
492   if (opcode & 8) { /* Control frames have MSB set. */
493     dissect_websocket_control_frame(tvb_appdata, pinfo, pl_tree, opcode);
494   } else {
495     dissect_websocket_data_frame(tvb_appdata, pinfo, tree, pl_tree, opcode, websocket_conv, pmc, raw_offset);
496   }
497 }
498
499
500 static int
501 dissect_websocket_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
502 {
503   proto_item   *ti, *ti_len;
504   guint8        fin, opcode;
505   gboolean      mask;
506   guint         short_length, payload_length;
507   guint         payload_offset, mask_offset;
508   proto_tree   *ws_tree;
509   const guint8 *masking_key = NULL;
510   tvbuff_t     *tvb_payload;
511   conversation_t *conv;
512   websocket_conv_t *websocket_conv;
513   gboolean      pmc = FALSE;
514
515   /*
516    * If this is a new Websocket session, try to parse HTTP Sec-Websocket-*
517    * headers once.
518    */
519   conv = find_or_create_conversation(pinfo);
520   websocket_conv = (websocket_conv_t *)conversation_get_proto_data(conv, proto_websocket);
521   if (!websocket_conv) {
522     websocket_conv = wmem_new0(wmem_file_scope(), websocket_conv_t);
523
524     http_conv_t *http_conv = (http_conv_t *)conversation_get_proto_data(conv, proto_http);
525     if (http_conv) {
526       websocket_conv->subprotocol = http_conv->websocket_protocol;
527       websocket_conv->server_port = http_conv->server_port;
528       if ( http_conv->websocket_extensions) {
529         websocket_parse_extensions(websocket_conv, http_conv->websocket_extensions);
530       }
531     }
532
533     conversation_add_proto_data(conv, proto_websocket, websocket_conv);
534   }
535
536   short_length = tvb_get_guint8(tvb, 1) & MASK_WS_PAYLOAD_LEN;
537   mask_offset = 2;
538   if (short_length == 126) {
539     payload_length = tvb_get_ntohs(tvb, 2);
540     mask_offset += 2;
541   } else if (short_length == 127) {
542     /* warning C4244: '=' : conversion from 'guint64' to 'guint ', possible loss of data */
543     payload_length = (guint)tvb_get_ntoh64(tvb, 2);
544     mask_offset += 8;
545   } else {
546     payload_length = short_length;
547   }
548
549   /* Mask */
550   mask = (tvb_get_guint8(tvb, 1) & MASK_WS_MASK) != 0;
551   payload_offset = mask_offset + (mask ? 4 : 0);
552
553   col_set_str(pinfo->cinfo, COL_PROTOCOL, "WebSocket");
554   col_set_str(pinfo->cinfo, COL_INFO, "WebSocket");
555
556   ti = proto_tree_add_item(tree, proto_websocket, tvb, 0, payload_offset, ENC_NA);
557   ws_tree = proto_item_add_subtree(ti, ett_ws);
558
559   /* Flags */
560   proto_tree_add_item(ws_tree, hf_ws_fin, tvb, 0, 1, ENC_NA);
561   fin = (tvb_get_guint8(tvb, 0) & MASK_WS_FIN) >> 4;
562   proto_tree_add_item(ws_tree, hf_ws_reserved, tvb, 0, 1, ENC_BIG_ENDIAN);
563   if (websocket_conv->permessage_deflate) {
564     /* RSV1 is Per-Message Compressed bit (RFC 7692). */
565     pmc = !!(tvb_get_guint8(tvb, 0) & MASK_WS_RSV1);
566     proto_tree_add_item(ws_tree, hf_ws_pmc, tvb, 0, 1, ENC_BIG_ENDIAN);
567   }
568
569   /* Opcode */
570   proto_tree_add_item(ws_tree, hf_ws_opcode, tvb, 0, 1, ENC_BIG_ENDIAN);
571   opcode = tvb_get_guint8(tvb, 0) & MASK_WS_OPCODE;
572   col_append_fstr(pinfo->cinfo, COL_INFO, " %s", val_to_str_const(opcode, ws_opcode_vals, "Unknown Opcode"));
573   col_append_str(pinfo->cinfo, COL_INFO, fin ? " [FIN]" : " ");
574
575   /* Add Mask bit to the tree */
576   proto_tree_add_item(ws_tree, hf_ws_mask, tvb, 1, 1, ENC_NA);
577   col_append_str(pinfo->cinfo, COL_INFO, mask ? " [MASKED]" : " ");
578
579   /* (Extended) Payload Length */
580   ti_len = proto_tree_add_item(ws_tree, hf_ws_payload_length, tvb, 1, 1, ENC_BIG_ENDIAN);
581   if (short_length == 126) {
582     proto_item_append_text(ti_len, " Extended Payload Length (16 bits)");
583     proto_tree_add_item(ws_tree, hf_ws_payload_length_ext_16, tvb, 2, 2, ENC_BIG_ENDIAN);
584   }
585   else if (short_length == 127) {
586     proto_item_append_text(ti_len, " Extended Payload Length (64 bits)");
587     proto_tree_add_item(ws_tree, hf_ws_payload_length_ext_64, tvb, 2, 8, ENC_BIG_ENDIAN);
588   }
589
590   /* Masking-key */
591   if (mask) {
592     proto_tree_add_item(ws_tree, hf_ws_masking_key, tvb, mask_offset, 4, ENC_NA);
593     masking_key = tvb_get_ptr(tvb, mask_offset, 4);
594   }
595
596   if (payload_length > 0) {
597     /* Always unmask payload data before analysing it. */
598     if (mask) {
599       proto_tree_add_item(ws_tree, hf_ws_masked_payload, tvb, payload_offset, payload_length, ENC_NA);
600       tvb_payload = tvb_unmasked(tvb, pinfo, payload_offset, payload_length, masking_key);
601       tvb_set_child_real_data_tvbuff(tvb, tvb_payload);
602       add_new_data_source(pinfo, tvb_payload, "Unmasked data");
603     } else {
604       tvb_payload = tvb_new_subset_length_caplen(tvb, payload_offset, payload_length, payload_length);
605     }
606     dissect_websocket_payload(tvb_payload, pinfo, tree, ws_tree, opcode, websocket_conv, pmc, tvb_raw_offset(tvb));
607   }
608
609   return tvb_captured_length(tvb);
610 }
611
612 static guint
613 get_websocket_frame_length(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
614 {
615   guint         frame_length, payload_length;
616   gboolean      mask;
617
618   frame_length = 2;                 /* flags, opcode and Payload length */
619   mask = tvb_get_guint8(tvb, offset + 1) & MASK_WS_MASK;
620
621   payload_length = tvb_get_guint8(tvb, offset + 1) & MASK_WS_PAYLOAD_LEN;
622   offset += 2; /* Skip flags, opcode and Payload length */
623
624   /* Check for Extended Payload Length. */
625   if (payload_length == 126) {
626     if (tvb_reported_length_remaining(tvb, offset) < 2)
627       return 0; /* Need more data. */
628
629     payload_length = tvb_get_ntohs(tvb, offset);
630     frame_length += 2;              /* Extended payload length */
631   } else if (payload_length == 127) {
632     if (tvb_reported_length_remaining(tvb, offset) < 8)
633       return 0; /* Need more data. */
634
635     payload_length = (guint)tvb_get_ntoh64(tvb, offset);
636     frame_length += 8;              /* Extended payload length */
637   }
638
639   if (mask)
640     frame_length += 4;              /* Masking-key */
641   frame_length += payload_length;   /* Payload data */
642   return frame_length;
643 }
644
645 static int
646 dissect_websocket(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
647 {
648   /* Need at least two bytes for flags, opcode and Payload length. */
649   tcp_dissect_pdus(tvb, pinfo, tree, TRUE, 2,
650                    get_websocket_frame_length, dissect_websocket_frame, data);
651   return tvb_captured_length(tvb);
652 }
653
654
655 void
656 proto_register_websocket(void)
657 {
658
659   static hf_register_info hf[] = {
660     { &hf_ws_fin,
661       { "Fin", "websocket.fin",
662       FT_BOOLEAN, 8, NULL, MASK_WS_FIN,
663       "Indicates that this is the final fragment in a message", HFILL }
664     },
665     { &hf_ws_reserved,
666       { "Reserved", "websocket.rsv",
667       FT_UINT8, BASE_HEX, NULL, MASK_WS_RSV,
668       "Must be zero", HFILL }
669     },
670     { &hf_ws_pmc,
671       { "Per-Message Compressed", "websocket.pmc",
672       FT_BOOLEAN, 8, NULL, MASK_WS_RSV1,
673       "Whether a message is compressed or not", HFILL }
674     },
675     { &hf_ws_opcode,
676       { "Opcode", "websocket.opcode",
677       FT_UINT8, BASE_DEC, VALS(ws_opcode_vals), MASK_WS_OPCODE,
678       "Defines the interpretation of the Payload data", HFILL }
679     },
680     { &hf_ws_mask,
681       { "Mask", "websocket.mask",
682       FT_BOOLEAN, 8, NULL, MASK_WS_MASK,
683       "Defines whether the Payload data is masked", HFILL }
684     },
685     { &hf_ws_payload_length,
686       { "Payload length", "websocket.payload_length",
687       FT_UINT8, BASE_DEC, NULL, MASK_WS_PAYLOAD_LEN,
688       "The length of the Payload data", HFILL }
689     },
690     { &hf_ws_payload_length_ext_16,
691       { "Extended Payload length (16 bits)", "websocket.payload_length_ext_16",
692       FT_UINT16, BASE_DEC, NULL, 0x0,
693       "The length (16 bits) of the Payload data", HFILL }
694     },
695     { &hf_ws_payload_length_ext_64,
696       { "Extended Payload length (64 bits)", "websocket.payload_length_ext_64",
697       FT_UINT64, BASE_DEC, NULL, 0x0,
698       "The length (64 bits) of the Payload data", HFILL }
699     },
700     { &hf_ws_masking_key,
701       { "Masking-Key", "websocket.masking_key",
702       FT_BYTES, BASE_NONE, NULL, 0x0,
703       "All frames sent from the client to the server are masked by a 32-bit value that is contained within the frame", HFILL }
704     },
705     { &hf_ws_payload,
706       { "Payload", "websocket.payload",
707       FT_NONE, BASE_NONE, NULL, 0x0,
708       "Payload (after unmasking)", HFILL }
709     },
710     { &hf_ws_masked_payload,
711       { "Masked payload", "websocket.masked_payload",
712       FT_NONE, BASE_NONE, NULL, 0x0,
713       NULL, HFILL }
714     },
715     { &hf_ws_payload_continue,
716       { "Continue", "websocket.payload.continue",
717       FT_BYTES, BASE_NONE, NULL, 0x0,
718       NULL, HFILL }
719     },
720     { &hf_ws_payload_close,
721       { "Close", "websocket.payload.close",
722       FT_NONE, BASE_NONE, NULL, 0x0,
723       NULL, HFILL }
724     },
725     { &hf_ws_payload_close_status_code,
726       { "Status code", "websocket.payload.close.status_code",
727       FT_UINT16, BASE_DEC, VALS(ws_close_status_code_vals), 0x0,
728       NULL, HFILL }
729     },
730     { &hf_ws_payload_close_reason,
731       { "Reason", "websocket.payload.close.reason",
732       FT_STRING, BASE_NONE, NULL, 0x0,
733       NULL, HFILL }
734     },
735     { &hf_ws_payload_ping,
736       { "Ping", "websocket.payload.ping",
737       FT_BYTES, BASE_NONE, NULL, 0x0,
738       NULL, HFILL }
739     },
740     { &hf_ws_payload_pong,
741       { "Pong", "websocket.payload.pong",
742       FT_BYTES, BASE_NONE, NULL, 0x0,
743       NULL, HFILL }
744     },
745     { &hf_ws_payload_unknown,
746       { "Unknown", "websocket.payload.unknown",
747       FT_BYTES, BASE_NONE, NULL, 0x0,
748       NULL, HFILL }
749     },
750   };
751
752
753   static gint *ett[] = {
754     &ett_ws,
755     &ett_ws_pl,
756     &ett_ws_mask,
757     &ett_ws_control_close,
758   };
759
760   static ei_register_info ei[] = {
761     { &ei_ws_payload_unknown, { "websocket.payload.unknown.expert", PI_UNDECODED, PI_NOTE, "Dissector for Websocket Opcode", EXPFILL }},
762     { &ei_ws_decompression_failed, { "websocket.decompression.failed.expert", PI_PROTOCOL, PI_WARN, "Decompression failed", EXPFILL }},
763   };
764
765   static const enum_val_t text_types[] = {
766       {"None",            "No subdissection", WEBSOCKET_NONE},
767       {"Line based text", "Line based text",  WEBSOCKET_TEXT},
768       {"As JSON",         "As json",          WEBSOCKET_JSON},
769       {"As SIP",         "As SIP",          WEBSOCKET_SIP},
770       {NULL, NULL, -1}
771   };
772
773   module_t *websocket_module;
774   expert_module_t* expert_websocket;
775
776   proto_websocket = proto_register_protocol("WebSocket",
777       "WebSocket", "websocket");
778
779   /*
780    * Heuristic dissectors SHOULD register themselves in
781    * this table using the standard heur_dissector_add()
782    * function.
783    */
784   heur_subdissector_list = register_heur_dissector_list("ws", proto_websocket);
785
786   port_subdissector_table = register_dissector_table("ws.port",
787       "TCP port for protocols using WebSocket", proto_websocket, FT_UINT16, BASE_DEC);
788
789   protocol_subdissector_table = register_dissector_table("ws.protocol",
790       "Negotiated WebSocket protocol", proto_websocket, FT_STRING, BASE_NONE);
791
792   proto_register_field_array(proto_websocket, hf, array_length(hf));
793   proto_register_subtree_array(ett, array_length(ett));
794   expert_websocket = expert_register_protocol(proto_websocket);
795   expert_register_field_array(expert_websocket, ei, array_length(ei));
796
797   websocket_handle = register_dissector("websocket", dissect_websocket, proto_websocket);
798
799   websocket_module = prefs_register_protocol(proto_websocket, proto_reg_handoff_websocket);
800
801   prefs_register_enum_preference(websocket_module, "text_type",
802         "Dissect websocket text as",
803         "Select dissector for websocket text",
804         &pref_text_type, text_types, WEBSOCKET_NONE);
805   prefs_register_bool_preference(websocket_module, "decompress",
806         "Try to decompress permessage-deflate payload", NULL, &pref_decompress);
807 }
808
809 void
810 proto_reg_handoff_websocket(void)
811 {
812   dissector_add_string("http.upgrade", "websocket", websocket_handle);
813
814   text_lines_handle = find_dissector_add_dependency("data-text-lines", proto_websocket);
815   json_handle = find_dissector_add_dependency("json", proto_websocket);
816   sip_handle = find_dissector_add_dependency("sip", proto_websocket);
817
818   proto_http = proto_get_id_by_filter_name("http");
819 }
820 /*
821  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
822  *
823  * Local variables:
824  * c-basic-offset: 2
825  * tab-width: 8
826  * indent-tabs-mode: nil
827  * End:
828  *
829  * vi: set shiftwidth=2 tabstop=8 expandtab:
830  * :indentSize=2:tabSize=8:noTabs=true:
831  */