websocket: restructure tree, always unmask payload
authorPeter Wu <peter@lekensteyn.nl>
Sat, 21 Feb 2015 17:46:25 +0000 (18:46 +0100)
committerAlexis La Goutte <alexis.lagoutte@gmail.com>
Sun, 31 May 2015 16:27:37 +0000 (16:27 +0000)
Changes:
 - Instead of special-casing masked and unmasked payload data, always
   unmask the payload before using it. This fixes handling of SIP
   requests which are masked and would previously not be dissected by
   the SIP handle. (As a result, many fields are removed).
 - Dissected text protocols (for example SIP) are now shown below the
   Websocket layer instead of inside the payload tree.
 - Use line-based text dissector as fallback for text decoding, and use
   data dissector for binary decoding.
 - Treat the optional close reason as UTF-8 instead of ASCII.
 - Group the close fields (status code, reason) in a subtree below close
   to avoid confusion. Make Close FT_NONE to avoid displaying hex.
 - Split dissection of the payload in separate functions for control and
   data frames.

Change-Id: I78b0078d51271bef94229d4b7c6c528b5e3a424d
Reviewed-on: https://code.wireshark.org/review/7294
Petri-Dish: Alexis La Goutte <alexis.lagoutte@gmail.com>
Petri-Dish: Peter Wu <peter@lekensteyn.nl>
Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org>
Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com>
epan/dissectors/packet-websocket.c

index 432034a1a3a7e454a985ecc188c523d8e59549ef..5ff78a71d2a91298ee33b7450a79644b0b564af8 100644 (file)
@@ -1,6 +1,7 @@
 /* packet-websocket.c
  * Routines for WebSocket dissection
  * Copyright 2012, Alexis La Goutte <alexis.lagoutte@gmail.com>
+ *           2015, Peter Wu <peter@lekensteyn.nl>
  *
  * Wireshark - Network traffic analyzer
  * By Gerald Combs <gerald@wireshark.org>
@@ -41,6 +42,7 @@
 void proto_register_websocket(void);
 void proto_reg_handoff_websocket(void);
 
+static dissector_handle_t data_handle;
 static dissector_handle_t text_lines_handle;
 static dissector_handle_t json_handle;
 static dissector_handle_t sip_handle;
@@ -65,30 +67,19 @@ static int hf_ws_payload_length_ext_16 = -1;
 static int hf_ws_payload_length_ext_64 = -1;
 static int hf_ws_masking_key = -1;
 static int hf_ws_payload = -1;
-static int hf_ws_payload_unmask = -1;
+static int hf_ws_masked_payload = -1;
 static int hf_ws_payload_continue = -1;
-static int hf_ws_payload_text = -1;
-static int hf_ws_payload_text_mask = -1;
-static int hf_ws_payload_text_unmask = -1;
-static int hf_ws_payload_binary = -1;
-static int hf_ws_payload_binary_mask = -1;
-static int hf_ws_payload_binary_unmask = -1;
 static int hf_ws_payload_close = -1;
-static int hf_ws_payload_close_mask = -1;
-static int hf_ws_payload_close_unmask = -1;
 static int hf_ws_payload_close_status_code = -1;
 static int hf_ws_payload_close_reason = -1;
 static int hf_ws_payload_ping = -1;
-static int hf_ws_payload_ping_mask = -1;
-static int hf_ws_payload_ping_unmask = -1;
 static int hf_ws_payload_pong = -1;
-static int hf_ws_payload_pong_mask = -1;
-static int hf_ws_payload_pong_unmask = -1;
 static int hf_ws_payload_unknown = -1;
 
 static gint ett_ws = -1;
 static gint ett_ws_pl = -1;
 static gint ett_ws_mask = -1;
+static gint ett_ws_control_close = -1;
 
 static expert_field ei_ws_payload_unknown = EI_INIT;
 
@@ -155,161 +146,139 @@ tvb_unmasked(tvbuff_t *tvb, packet_info *pinfo, const guint offset, guint payloa
   return tvb_new_real_data(data_unmask, unmasked_length, payload_length);
 }
 
-static int
-dissect_websocket_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_tree *ws_tree, guint8 opcode, guint payload_length, gboolean mask, const guint8* masking_key)
+static void
+dissect_websocket_control_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint8 opcode)
 {
-  guint               offset = 0;
-  proto_item         *ti_unmask, *ti;
+  proto_item         *ti;
+  proto_tree         *subtree;
+  const guint         offset = 0, length = tvb_reported_length(tvb);
+
+  switch (opcode) {
+    case WS_CLOSE: /* Close */
+      ti = proto_tree_add_item(tree, hf_ws_payload_close, tvb, offset, length, ENC_NA);
+      subtree = proto_item_add_subtree(ti, ett_ws_control_close);
+      /* Close frame MAY contain a body. */
+      if (length >= 2) {
+        proto_tree_add_item(subtree, hf_ws_payload_close_status_code, tvb, offset, 2, ENC_BIG_ENDIAN);
+        if (length > 2)
+          proto_tree_add_item(subtree, hf_ws_payload_close_reason, tvb, offset+2, length-2, ENC_UTF_8|ENC_NA);
+      }
+      break;
+
+    case WS_PING: /* Ping */
+      proto_tree_add_item(tree, hf_ws_payload_ping, tvb, offset, length, ENC_NA);
+      break;
+
+    case WS_PONG: /* Pong */
+      proto_tree_add_item(tree, hf_ws_payload_pong, tvb, offset, length, ENC_NA);
+      break;
+
+    default: /* Unknown */
+      ti = proto_tree_add_item(tree, hf_ws_payload_unknown, tvb, offset, length, ENC_NA);
+      expert_add_info_format(pinfo, ti, &ei_ws_payload_unknown, "Dissector for Websocket Opcode (%d)"
+        " code not implemented, Contact Wireshark developers"
+        " if you want this supported", opcode);
+      break;
+  }
+}
+
+static void
+dissect_websocket_data_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_tree *pl_tree, guint8 opcode)
+{
+  proto_item         *ti;
+  const guint         offset = 0, length = tvb_reported_length(tvb);
   dissector_handle_t  handle = NULL;
-  proto_tree         *pl_tree, *mask_tree = NULL;
-  tvbuff_t           *payload_tvb         = NULL;
   heur_dtbl_entry_t  *hdtbl_entry;
   conversation_t     *conv;
   http_conv_t        *http_conv = NULL;
 
-  /* Payload */
-  ti = proto_tree_add_item(ws_tree, hf_ws_payload, tvb, offset, payload_length, ENC_NA);
-  pl_tree = proto_item_add_subtree(ti, ett_ws_pl);
-  if (mask) {
-    payload_tvb = tvb_unmasked(tvb, pinfo, offset, payload_length, masking_key);
-    tvb_set_child_real_data_tvbuff(tvb, payload_tvb);
-    add_new_data_source(pinfo, payload_tvb, payload_length > tvb_captured_length(payload_tvb) ? "Unmasked Data (truncated)" : "Unmasked Data");
-    ti = proto_tree_add_item(ws_tree, hf_ws_payload_unmask, payload_tvb, offset, payload_length, ENC_NA);
-    if (payload_length > tvb_captured_length(payload_tvb)) {
-      proto_item_append_text(ti, " [truncated]");
-    }
-    mask_tree = proto_item_add_subtree(ti, ett_ws_mask);
-  } else {
-    payload_tvb = tvb_new_subset(tvb, offset, payload_length, -1);
-  }
-
+  /* try to find a dissector which accepts the data. */
   conv = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
-  if (conv)
+  if (conv) {
     http_conv = (http_conv_t *)conversation_get_proto_data(conv, proto_http);
 
-  if (http_conv)
-    handle = dissector_get_uint_handle(port_subdissector_table, http_conv->server_port);
-
-  if (handle)
-    call_dissector_only(handle, payload_tvb, pinfo, tree, NULL);
-  else
-    dissector_try_heuristic(heur_subdissector_list, payload_tvb, pinfo, tree, &hdtbl_entry, NULL);
+    if (http_conv)
+      handle = dissector_get_uint_handle(port_subdissector_table, http_conv->server_port);
+  }
 
-  /* Extension Data */
-  /* TODO: Add dissector of Extension (not extension available for the moment...) */
+  if (handle) {
+    call_dissector_only(handle, tvb, pinfo, tree, NULL);
+    return; /* handle found, assume dissector took care of it. */
+  } else if (dissector_try_heuristic(heur_subdissector_list, tvb, pinfo, tree, &hdtbl_entry, NULL)) {
+    return; /* heuristics dissector handled it. */
+  }
 
-  /* Application Data */
+  /* no dissector wanted it, try to print something appropriate. */
   switch (opcode) {
-
-    case WS_CONTINUE: /* Continue */
-      proto_tree_add_item(pl_tree, hf_ws_payload_continue, tvb, offset, payload_length, ENC_NA);
-      /* TODO: Add Fragmentation support... */
-    break;
-
     case WS_TEXT: /* Text */
-    if (mask) {
-
-      proto_tree_add_item(pl_tree, hf_ws_payload_text_mask, tvb, offset, payload_length, ENC_NA);
-      ti_unmask = proto_tree_add_item(mask_tree, hf_ws_payload_text_unmask, payload_tvb, offset, payload_length, ENC_UTF_8|ENC_NA);
-      PROTO_ITEM_SET_GENERATED(ti_unmask);
-      ti_unmask = proto_tree_add_item(mask_tree, hf_ws_payload_text, payload_tvb, offset, payload_length, ENC_UTF_8|ENC_NA);
-      PROTO_ITEM_SET_HIDDEN(ti_unmask);
-    } else {
+    {
       const gchar  *saved_match_string = pinfo->match_string;
 
       pinfo->match_string = NULL;
       switch (pref_text_type) {
       case WEBSOCKET_TEXT:
-          call_dissector(text_lines_handle, payload_tvb, pinfo, pl_tree);
-          break;
-      case WEBSOCKET_JSON:
-          call_dissector(json_handle, payload_tvb, pinfo, pl_tree);
-          break;
-      case WEBSOCKET_SIP:
-          call_dissector(sip_handle, payload_tvb, pinfo, pl_tree);
-          break;
       case WEBSOCKET_NONE:
-          /* falltrough */
       default:
-          proto_tree_add_item(pl_tree, hf_ws_payload_text, tvb, offset, payload_length, ENC_UTF_8|ENC_NA);
-          break;
+        /* Assume that most text protocols are line-based. */
+        call_dissector(text_lines_handle, tvb, pinfo, tree);
+        break;
+      case WEBSOCKET_JSON:
+        call_dissector(json_handle, tvb, pinfo, tree);
+        break;
+      case WEBSOCKET_SIP:
+        call_dissector(sip_handle, tvb, pinfo, tree);
+        break;
       }
       pinfo->match_string = saved_match_string;
     }
-    offset += payload_length;
     break;
 
     case WS_BINARY: /* Binary */
-    if (mask) {
-      proto_tree_add_item(pl_tree, hf_ws_payload_binary_mask, tvb, offset, payload_length, ENC_NA);
-      ti_unmask = proto_tree_add_item(mask_tree, hf_ws_payload_binary_unmask, payload_tvb, offset, payload_length, ENC_NA);
-      PROTO_ITEM_SET_GENERATED(ti_unmask);
-      ti_unmask = proto_tree_add_item(mask_tree, hf_ws_payload_binary, payload_tvb, offset, payload_length, ENC_NA);
-      PROTO_ITEM_SET_HIDDEN(ti_unmask);
-    } else {
-      proto_tree_add_item(pl_tree, hf_ws_payload_binary, tvb, offset, payload_length, ENC_NA);
-    }
-    offset += payload_length;
-    break;
-
-    case WS_CLOSE: /* Close */
-    if (mask) {
-      proto_tree_add_item(pl_tree, hf_ws_payload_close_mask, tvb, offset, payload_length, ENC_NA);
-      ti_unmask = proto_tree_add_item(mask_tree, hf_ws_payload_close_unmask, payload_tvb, offset, payload_length, ENC_NA);
-      PROTO_ITEM_SET_GENERATED(ti_unmask);
-      ti_unmask = proto_tree_add_item(mask_tree, hf_ws_payload_close, payload_tvb, offset, payload_length, ENC_NA);
-      PROTO_ITEM_SET_HIDDEN(ti_unmask);
-      ti_unmask = proto_tree_add_item(mask_tree, hf_ws_payload_close_status_code, payload_tvb, offset, 2, ENC_BIG_ENDIAN);
-      PROTO_ITEM_SET_GENERATED(ti_unmask);
-
-      if (payload_length > 2) {
-        ti_unmask = proto_tree_add_item(mask_tree, hf_ws_payload_close_reason, payload_tvb, offset+2, payload_length-2, ENC_ASCII|ENC_NA);
-        PROTO_ITEM_SET_GENERATED(ti_unmask);
-      }
-    } else {
-      proto_tree_add_item(pl_tree, hf_ws_payload_close, tvb, offset, payload_length, ENC_NA);
-      proto_tree_add_item(pl_tree, hf_ws_payload_close_status_code, tvb, offset, 2, ENC_BIG_ENDIAN);
-      if (payload_length > 2) {
-        proto_tree_add_item(pl_tree, hf_ws_payload_close_reason, tvb, offset+2, payload_length-2, ENC_ASCII|ENC_NA);
-      }
-    }
-    offset += payload_length;
-    break;
-
-    case WS_PING: /* Ping */
-    if (mask) {
-      proto_tree_add_item(pl_tree, hf_ws_payload_ping_mask, tvb, offset, payload_length, ENC_NA);
-      ti_unmask = proto_tree_add_item(mask_tree, hf_ws_payload_ping_unmask, payload_tvb, offset, payload_length, ENC_NA);
-      PROTO_ITEM_SET_GENERATED(ti_unmask);
-      ti_unmask = proto_tree_add_item(mask_tree, hf_ws_payload_ping, payload_tvb, offset, payload_length, ENC_NA);
-      PROTO_ITEM_SET_HIDDEN(ti_unmask);
-    } else {
-      proto_tree_add_item(pl_tree, hf_ws_payload_ping, tvb, offset, payload_length, ENC_NA);
-    }
-    offset += payload_length;
-    break;
-
-    case WS_PONG: /* Pong */
-    if (mask) {
-      proto_tree_add_item(pl_tree, hf_ws_payload_pong_mask, tvb, offset, payload_length, ENC_NA);
-      ti_unmask = proto_tree_add_item(mask_tree, hf_ws_payload_pong_unmask, payload_tvb, offset, payload_length, ENC_NA);
-      PROTO_ITEM_SET_GENERATED(ti_unmask);
-      ti_unmask = proto_tree_add_item(mask_tree, hf_ws_payload_pong, payload_tvb, offset, payload_length, ENC_NA);
-      PROTO_ITEM_SET_HIDDEN(ti_unmask);
-    } else {
-      proto_tree_add_item(pl_tree, hf_ws_payload_pong, tvb, offset, payload_length, ENC_NA);
-    }
-    offset += payload_length;
-    break;
+      call_dissector(data_handle, tvb, pinfo, tree);
+      break;
 
     default: /* Unknown */
-      ti = proto_tree_add_item(pl_tree, hf_ws_payload_unknown, tvb, offset, payload_length, ENC_NA);
+      ti = proto_tree_add_item(pl_tree, hf_ws_payload_unknown, tvb, offset, length, ENC_NA);
       expert_add_info_format(pinfo, ti, &ei_ws_payload_unknown, "Dissector for Websocket Opcode (%d)"
         " code not implemented, Contact Wireshark developers"
         " if you want this supported", opcode);
-    break;
+      break;
+  }
+}
+
+static void
+dissect_websocket_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_tree *ws_tree, guint8 opcode)
+{
+  const guint         offset = 0, length = tvb_reported_length(tvb);
+  proto_item         *ti;
+  proto_tree         *pl_tree;
+  tvbuff_t           *tvb_appdata;
+
+  /* Payload */
+  ti = proto_tree_add_item(ws_tree, hf_ws_payload, tvb, offset, length, ENC_NA);
+  pl_tree = proto_item_add_subtree(ti, ett_ws_pl);
+
+  /* Extension Data */
+  /* TODO: Add dissector of Extension (not extension available for the moment...) */
+
+
+  /* Application Data */
+  if (opcode == WS_CONTINUE) {
+    proto_tree_add_item(tree, hf_ws_payload_continue, tvb, offset, length, ENC_NA);
+    /* TODO: Add Fragmentation support (needs FIN bit)
+     * https://tools.ietf.org/html/rfc6455#section-5.4 */
+    return;
+  }
+  /* Right now this is exactly the same, this may change when exts. are added.
+  tvb_appdata = tvb_new_subset(tvb, offset, length, length);
+  */
+  tvb_appdata = tvb;
+
+  if (opcode & 8) { /* Control frames have MSB set. */
+    dissect_websocket_control_frame(tvb_appdata, pinfo, pl_tree, opcode);
+  } else {
+    dissect_websocket_data_frame(tvb_appdata, pinfo, tree, pl_tree, opcode);
   }
-  return offset;
 }
 
 
@@ -381,8 +350,16 @@ dissect_websocket_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, voi
   }
 
   if (payload_length > 0) {
-    tvb_payload = tvb_new_subset_remaining(tvb, payload_offset);
-    dissect_websocket_payload(tvb_payload, pinfo, tree, ws_tree, opcode, payload_length, mask, masking_key);
+    /* Always unmask payload data before analysing it. */
+    if (mask) {
+      ti = proto_tree_add_item(ws_tree, hf_ws_masked_payload, tvb, payload_offset, payload_length, ENC_NA);
+      tvb_payload = tvb_unmasked(tvb, pinfo, payload_offset, payload_length, masking_key);
+      tvb_set_child_real_data_tvbuff(tvb, tvb_payload);
+      add_new_data_source(pinfo, tvb_payload, "Unmasked data");
+    } else {
+      tvb_payload = tvb_new_subset(tvb, payload_offset, payload_length, payload_length);
+    }
+    dissect_websocket_payload(tvb_payload, pinfo, tree, ws_tree, opcode);
   }
 
   return tvb_captured_length(tvb);
@@ -479,10 +456,10 @@ proto_register_websocket(void)
     { &hf_ws_payload,
       { "Payload", "websocket.payload",
       FT_NONE, BASE_NONE, NULL, 0x0,
-      NULL, HFILL }
+      "Payload (after unmasking)", HFILL }
     },
-    { &hf_ws_payload_unmask,
-      { "Unmask Payload", "websocket.payload.unmask",
+    { &hf_ws_masked_payload,
+      { "Masked payload", "websocket.masked_payload",
       FT_NONE, BASE_NONE, NULL, 0x0,
       NULL, HFILL }
     },
@@ -491,53 +468,13 @@ proto_register_websocket(void)
       FT_BYTES, BASE_NONE, NULL, 0x0,
       NULL, HFILL }
     },
-    { &hf_ws_payload_text,
-      { "Text", "websocket.payload.text",
-      FT_STRING, BASE_NONE, NULL, 0x0,
-      NULL, HFILL }
-    },
-    { &hf_ws_payload_text_mask,
-      { "Text", "websocket.payload.text_mask",
-      FT_BYTES, BASE_NONE, NULL, 0x0,
-      NULL, HFILL }
-    },
-    { &hf_ws_payload_text_unmask,
-      { "Text unmask", "websocket.payload.text_unmask",
-      FT_STRING, BASE_NONE, NULL, 0x0,
-      NULL, HFILL }
-    },
-    { &hf_ws_payload_binary,
-      { "Binary", "websocket.payload.binary",
-      FT_BYTES, BASE_NONE, NULL, 0x0,
-      NULL, HFILL }
-    },
-    { &hf_ws_payload_binary_mask,
-      { "Binary", "websocket.payload.binary_mask",
-      FT_BYTES, BASE_NONE, NULL, 0x0,
-      NULL, HFILL }
-    },
-    { &hf_ws_payload_binary_unmask,
-      { "Binary", "websocket.payload.binary_unmask",
-      FT_BYTES, BASE_NONE, NULL, 0x0,
-      NULL, HFILL }
-    },
     { &hf_ws_payload_close,
       { "Close", "websocket.payload.close",
-      FT_BYTES, BASE_NONE, NULL, 0x0,
-      NULL, HFILL }
-    },
-    { &hf_ws_payload_close_mask,
-      { "Close", "websocket.payload.close_mask",
-      FT_BYTES, BASE_NONE, NULL, 0x0,
-      NULL, HFILL }
-    },
-    { &hf_ws_payload_close_unmask,
-      { "Unmask Close", "websocket.payload.close_unmask",
-      FT_BYTES, BASE_NONE, NULL, 0x0,
+      FT_NONE, BASE_NONE, NULL, 0x0,
       NULL, HFILL }
     },
     { &hf_ws_payload_close_status_code,
-      { "Close", "websocket.payload.close.status_code",
+      { "Status code", "websocket.payload.close.status_code",
       FT_UINT16, BASE_DEC, VALS(ws_close_status_code_vals), 0x0,
       NULL, HFILL }
     },
@@ -551,31 +488,11 @@ proto_register_websocket(void)
       FT_BYTES, BASE_NONE, NULL, 0x0,
       NULL, HFILL }
     },
-    { &hf_ws_payload_ping_mask,
-      { "Ping", "websocket.payload.ping_mask",
-      FT_BYTES, BASE_NONE, NULL, 0x0,
-      NULL, HFILL }
-    },
-    { &hf_ws_payload_ping_unmask,
-      { "Ping", "websocket.payload.ping_unmask",
-      FT_BYTES, BASE_NONE, NULL, 0x0,
-      NULL, HFILL }
-    },
     { &hf_ws_payload_pong,
       { "Pong", "websocket.payload.pong",
       FT_BYTES, BASE_NONE, NULL, 0x0,
       NULL, HFILL }
     },
-    { &hf_ws_payload_pong_mask,
-      { "Pong", "websocket.payload.pong_mask",
-      FT_BYTES, BASE_NONE, NULL, 0x0,
-      NULL, HFILL }
-    },
-    { &hf_ws_payload_pong_unmask,
-      { "Pong", "websocket.payload.pong_unmask",
-      FT_BYTES, BASE_NONE, NULL, 0x0,
-      NULL, HFILL }
-    },
     { &hf_ws_payload_unknown,
       { "Unknown", "websocket.payload.unknown",
       FT_BYTES, BASE_NONE, NULL, 0x0,
@@ -587,7 +504,8 @@ proto_register_websocket(void)
   static gint *ett[] = {
     &ett_ws,
     &ett_ws_pl,
-    &ett_ws_mask
+    &ett_ws_mask,
+    &ett_ws_control_close,
   };
 
   static ei_register_info ei[] = {
@@ -638,6 +556,7 @@ proto_register_websocket(void)
 void
 proto_reg_handoff_websocket(void)
 {
+  data_handle = find_dissector("data");
   text_lines_handle = find_dissector("data-text-lines");
   json_handle = find_dissector("json");
   sip_handle = find_dissector("sip");
@@ -645,7 +564,7 @@ proto_reg_handoff_websocket(void)
   proto_http = proto_get_id_by_filter_name("http");
 }
 /*
- * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
+ * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
  *
  * Local variables:
  * c-basic-offset: 2