Ethereal-users: [Ethereal-users] Patch to add the "Decode as" functionality to tethereal

Note: This archive is from the project's previous web site, ethereal.com. This list is no longer active.

From: Lionel Ains <lains@xxxxxxx>
Date: Tue, 27 May 2003 23:59:56 +0200
Guy Harris wrote:

There's no way in Ethereal 0.9.12 or earlier versions to get the name of
a registered dissector from the handle for that dissector.

However, I've just checked into CVS a change to add a
"dissector_handle_get_dissector_name()" API that returns, for a
dissector handle, the name used when the handle was created.

Note, however, that there is no guarantee that a dissector handle *has*
a name!  "create_dissector_handle()" creates anonymous handles.

In the GUI version (ethereal), a table of possible dissectors is created by parsing all dissectors for a given dissector table. Short names are then displayed, and the underlying functions actually work on the dissector handle. In tethereal, I'm trying to find out from the user input which dissector handle to use. So far, I've been trying to use the key in the dissector hash "registered_dissectors" (I pass it to find_dissector() and get the dissector handle I need). The problem comes from the fact that the command-line argument must therefore contain the *hash key* for a dissector (or something that leads to at most one hash key), and the user doesn't necessarily know it (if using ethereal, she/he will only see the *short name* string, associated with this dissector, that gets displayed in the "Decode As" dialog). The second issue is that the registered_dissectors hash table will not allow the user to access all protocols allowed by ethereal in the "Decode As" dialog (at least, I think so). So I had to use something else to get my dissector handle from.

In the patch attached to this email, tethereal tries to get a dissector handle from the short name associated with that dissector. Unfortunately, if this short name isn't unique, tethereal will use the first one it finds, which happens for DHCPV6 in tcp.port, for example. It will however generate a warning if more than one dissector were found. If this short name isn't found, names are used (thanks to the function Guy Harris has added), if this fails as well, then I call find_dissector using the user-specified name as the key in registered_dissectors, but we can then end up trying to decode IP over UDP or that sort of things... is it really useful? A parameter would look like that "-d 'tcp.port == 8080, HTTP'" or "udp.port == 5000, RTP" In case a name is registered for the dissector we want to use, it is then also possible to use the dissector's name:
"-d 'tcp.port == 8080, http'" would actually do that.
As a last attempt, the protocol name specified on the command-line will be attempted as a key in the registered_dissectors hash: "-d 'tcp.port == 122, ssh'" would do that (ssh doesn't register a name in the 0.9.12 version of ethereal, ssh is actually directly the key in the hash)

Here is a patch (attached) to get this functionality into tethereal (this requires at least v1.66 for epan/packet.h and at least v1.92 for epan/packet.c, Guy Harris' patch is providing the necessary changes). When the versions of epan/packet.h and epan/packet.c are OK, the patch provided here can be applied: "cd ethereal-x.x.x; patch -Np1 < path_to_patch_filename"

To summarise:
- if, using ethereal, I open "Decode As...", then select the tab "Transport", and specify UDP port 5000 as RTP - the command-line for the equivalent feature on tethereal patched with the code below would be: "./tethereal -d udp.port==5000,RTP"

Note that this patch worked fine for me on a PC running Linux, so far, but I can't guarantee that it won't crash the machine or other nasty things.

Lionel
--- ethereal-0.9.12_patch_guy_harris_20030523/tethereal.c	2003-05-27 14:21:05.000000000 +0000
+++ ethereal-0.9.12/tethereal.c	2003-05-27 18:02:12.000000000 +0000
@@ -102,6 +102,7 @@
 #include "capture-wpcap.h"
 #endif
 
+static gchar   decode_as_arg_template[] = "<layer_type>==<selector>,<decode_as_protocol>"; /* This is the template for the decode as option, it is shared between the various functions that output the usage for this parameter */
 static guint32 firstsec, firstusec;
 static guint32 prevsec, prevusec;
 static GString *comp_info_str, *runtime_info_str;
@@ -224,10 +225,12 @@
   fprintf(stderr, "\t[ -f <capture filter> ] [ -F <output file type> ]\n");
   fprintf(stderr, "\t[ -i <interface> ] [ -n ] [ -N <resolving> ]\n");
   fprintf(stderr, "\t[ -o <preference setting> ] ... [ -r <infile> ] [ -R <read filter> ]\n");
+  fprintf(stderr, "\t[ -d %s ] ...\n", decode_as_arg_template);
   fprintf(stderr, "\t[ -s <snaplen> ] [ -t <time stamp format> ] [ -w <savefile> ] [ -x ]\n");
 #else
   fprintf(stderr, "\nt%s [ -vVhl ] [ -F <output file type> ] [ -n ] [ -N <resolving> ]\n", PACKAGE);
   fprintf(stderr, "\t[ -o <preference setting> ] ... [ -r <infile> ] [ -R <read filter> ]\n");
+  fprintf(stderr, "\t[ -d %s ] ...\n", decode_as_arg_template);
   fprintf(stderr, "\t[ -t <time stamp format> ] [ -w <savefile> ] [ -x ]\n");
 #endif
   fprintf(stderr, "\t[ -Z <statistics string> ]\n");
@@ -341,6 +344,330 @@
 
 }
 
+
+/*
+ * Get a dissector short name and name associated with the handle provided
+ * The output will be sent to stderr, as a comma-separated list of protocol names
+ */
+
+static void
+display_dissector_short_name(gchar *table _U_, gpointer handle, gpointer data _U_)
+{
+  const gchar        *proto_short_name;
+  const gchar        *proto_name;
+  gboolean           sth_output;
+
+  g_assert(handle);
+  
+  proto_short_name = dissector_handle_get_short_name((dissector_handle_t)handle); /* Get the short name for the protocol handled by this dissector */
+
+  proto_name = dissector_handle_get_dissector_name((dissector_handle_t)handle); /* Get the name for the protocol handled by this dissector */
+
+  sth_output = FALSE;
+
+  if (proto_short_name != NULL) {
+    fprintf(stderr, "%s%s", (sth_output?", ":""), proto_short_name);
+    sth_output = TRUE;
+  }
+
+  if (proto_name != NULL) {
+    fprintf(stderr, "%s%s", (sth_output?", ":""), proto_name);
+    sth_output = TRUE;
+  }
+
+  if (sth_output) {
+    fprintf(stderr, "\n");
+  }
+}
+
+/*
+ * The protocol_name_search structure is used by find_name_shortname_func() to pass parameters and store results
+ */
+struct protocol_name_search{
+  gchar              *searched_shortname; /* Dissector short name we are looking for */
+  gchar              *searched_name; /* Dissector name we are looking for */
+  dissector_handle_t  matched_shortname_dissector_handle; /* Handle for a dissector which short name matches the one searched for */
+  dissector_handle_t  matched_name_dissector_handle; /* Handle for a dissector which name matches the one searched for */
+  guint32             nb_shortname_match; /* How many dissectors matched searched_shortname */
+  guint32             nb_name_match; /* How many dissectors matched searched_name */
+};
+typedef struct protocol_name_search *protocol_name_search_t;
+
+/*
+ * This function parses all dissector associated with a table to find the ones with the requested short name and name. Typically, it is called as a reference function in a call to dissector_table_foreach_handle.
+ * The short name and names we are looking for, as well as the results are all stored in the protocol_name_search struct pointed to by user_data
+ * If called using dissector_table_foreach_handle, we actually parse the whole list of dissectors only once here. Matches are searched for both short names and names at the same time
+ */
+static void
+find_name_shortname_func(gchar *table _U_, gpointer handle, gpointer user_data) {
+
+  const gchar                *dissector_name;
+  const gchar                *dissector_shortname;
+  protocol_name_search_t      search_info;
+
+  g_assert(handle);
+  g_assert(user_data);
+
+  search_info = (protocol_name_search_t)user_data;
+
+  dissector_shortname = dissector_handle_get_short_name((dissector_handle_t)handle);
+
+  if (dissector_shortname != NULL) { /* Ignore invalid short name pointers */
+    g_assert(search_info->searched_shortname);
+
+    if (strcmp(dissector_shortname, search_info->searched_shortname) == 0) { /* Found a match */
+      if (search_info->nb_shortname_match++ == 0) { /* Record this handle only if this is the first short name match */
+        search_info->matched_shortname_dissector_handle = (dissector_handle_t)handle; /* Record the handle for this matching dissector */
+      }
+    }
+  }
+
+  dissector_name = dissector_handle_get_dissector_name((dissector_handle_t)handle);
+
+  if (dissector_name != NULL) { /* Ignore invalid name pointers */
+    g_assert(search_info->searched_name);
+
+    if (strcmp(dissector_name, search_info->searched_name) == 0) { /* Found a match */
+      if (search_info->nb_name_match++ == 0) { /* Record this handle only if this is the first name match */
+        search_info->matched_name_dissector_handle = (dissector_handle_t)handle; /* Record the handle for this matching dissector */
+      }
+    }
+  }
+}
+
+/*
+ * This struct summarizes one decode as option, in order to perform the actual dissector registration according to the table_name, selector, and dissector_handle
+ */
+struct decode_as_pref{
+  gchar              *table_name;
+  dissector_table_t   table_handle;
+  guint32             selector;
+  dissector_handle_t  dissector_handle;
+};
+typedef struct decode_as_pref *decode_as_pref_t;
+
+/*
+ * This function takes a decode_as_pref struct (see above) and performs the dissector registration according to the fields in this struct
+ */
+static gboolean
+add_user_custom_dissector(const decode_as_pref_t custom_pref) {
+
+  g_assert(custom_pref);
+  g_assert(custom_pref->table_name);
+  g_assert(custom_pref->table_handle);
+  g_assert(custom_pref->dissector_handle);
+
+  dissector_change(custom_pref->table_name, custom_pref->selector, custom_pref->dissector_handle);
+  return TRUE; /* We return true without really knowing whether this dissector has been added successfully... this can be improved if dissector_change() returns success or failure */
+}
+
+/*
+ * The function below parses the command-line parameters for the decode as feature (a string pointer by cl_param)
+ * It checks the format of the command-line, searches for a matching table and dissector. If a table/dissector match is not found, we display a summary of the available tables/dissectors (on stderr) and return FALSE
+ * If everything is fine, we construct a decode_as_pref struct and get the "Decode as" preference activated, then we return TRUE
+ */
+static gboolean
+add_decode_as(const gchar *cl_param) {
+
+  dissector_handle_t            dissector_matching;
+  dissector_table_t             table_matching;
+  struct decode_as_pref         preference;
+  gchar                        *decoded_param;
+  gchar                        *remaining_param;
+  gchar                        *selector_str;
+  gchar                        *dissector_str;
+  int                           selector_int;
+  struct protocol_name_search   user_protocol_name;
+#ifdef DEBUG
+  gchar                        *dissector_matching_sn;
+#endif
+
+/* The following code will allocate and copy the command-line options in a string pointed by decoded_param */
+
+  g_assert(cl_param);
+  decoded_param = g_malloc( sizeof(gchar) * strlen(cl_param) + 1 ); /* Allocate enough space to have a working copy of the command-line parameter */
+  g_assert(decoded_param);
+  strcpy(decoded_param, cl_param);
+
+
+/* The lines below will parse this string (modifying it) to extract all necessary information and store it in the preference structure. Note that decoded_param is still needed since strings are not copied into preference. preference is merely a structure that points to the strings in decoded_parm */
+
+  preference.table_name = decoded_param; /* Layer type string starts from beginning */
+
+  remaining_param = strchr(preference.table_name, '=');
+  if (remaining_param == NULL) {
+    fprintf(stderr, "tethereal: Parameter \"%s\" doesn't follow the template \"%s\"\n", cl_param, decode_as_arg_template);
+    g_free(decoded_param);
+    return FALSE;
+  }
+
+  *remaining_param = '\0'; /* Terminate the layer type string (table_name) where '=' was detected */
+  
+  if (*(remaining_param + 1) != '=') { /* Check for "==" and not only '=' */
+    fprintf(stderr, "tethereal: Warning: -d requires \"==\" instead of \"=\". Option will be treated as \"%s==%s\"\n", preference.table_name, remaining_param + 1);
+  }
+  else {
+    remaining_param++; /* Move to the second '=' */
+    *remaining_param = '\0'; /* Remove the second '=' */
+  }
+  remaining_param++; /* Position after the layer type string */
+
+  selector_str = remaining_param; /* Next part starts with the selector number */
+
+  /* Remove leading and trailing spaces from the table name */
+  while ( preference.table_name[0] == ' ' )
+    preference.table_name++; 
+  while ( preference.table_name[strlen(preference.table_name) - 1] == ' ' )
+    preference.table_name[strlen(preference.table_name) - 1] = '\0'; /* Note: if empty string, while loop will eventually exit */
+
+
+  remaining_param = strchr(selector_str, ',');
+  if (remaining_param == NULL) {
+    fprintf(stderr, "tethereal: Parameter \"%s\" doesn't follow the template \"%s\"\n", cl_param, decode_as_arg_template);
+    g_free(decoded_param);
+    return FALSE;
+  }
+
+  *remaining_param = '\0'; /* Terminate the selector number string (selector_str) where ',' was detected */
+  
+  remaining_param++; /* Position after the selector number string */
+
+
+  dissector_str = remaining_param; /* All the rest of the string is the dissector (decode as protocol) name */
+
+  /* No need to remove leading and trailing spaces from the selector number string because scanf will do that for us*/
+  if ( sscanf(selector_str, "%i", &selector_int) != 1 ) {
+    fprintf(stderr, "tethereal: Invalid selector number \"%s\"\n", selector_str);
+    g_free(decoded_param);
+    return FALSE;
+  }
+
+  preference.selector = selector_int; /* Save selector integer in preference struct */
+
+  /* Remove leading and trailing spaces from the dissector name */
+  while ( dissector_str[0] == ' ' )
+    dissector_str++; 
+  while ( dissector_str[strlen(dissector_str) - 1] == ' ' )
+    dissector_str[strlen(dissector_str) - 1] = '\0'; /* Note: if empty string, while loop will eventually exit */
+
+
+#ifdef DEBUG
+  fprintf(stderr, "tethereal: Debug info: table=\"%s\", selector=\"%d\", dissector=\"%s\"\n", preference.table_name, preference.selector, dissector_str); // For debug only!
+#endif
+
+/* The is the end of the code that parses the command-line options. All information have now been stored in the structure preference. All strings are still pointing to decoded_parm that needs to be kept in memory as long as preference is needed, and decoded_param needs to be deallocated at each exit point of this function */
+
+
+  table_matching = NULL;
+  dissector_matching = NULL;
+
+/* The following part looks for the layer type part of the parameter */
+
+/* Look for the requested table */
+  if ( !(*(preference.table_name)) ) { /* Is the table name empty, if so, don't even search for anything, display a message */
+    fprintf(stderr, "tethereal: No layer type specified\n"); /* Note, we don't exit here, but table_matching will remain NULL, so we exit below */
+  }
+  else {
+    table_matching = find_dissector_table(preference.table_name);
+    if (!table_matching) {
+      fprintf(stderr, "tethereal: Unknown layer type -- %s\n", preference.table_name); /* Note, we don't exit here, but table_matching will remain NULL, so we exit below */
+    }
+  }
+
+  if (!table_matching) {
+    fprintf(stderr, "tethereal: Warning: Parameter \"%s\" ignored\n", cl_param);
+    fprintf(stderr, "tethereal: Valid layer types are:\n");
+    if (find_dissector_table("ethertype") != NULL) /* If this table exists... and it should! */
+      fprintf(stderr, "ethertype (%s)\n", get_dissector_table_ui_name("ethertype"));
+    if (find_dissector_table("ip.proto") != NULL) /* If this table exists... and it should! */
+      fprintf(stderr, "ip.proto (%s)\n", get_dissector_table_ui_name("ip.proto"));
+    if (find_dissector_table("tcp.port") != NULL) /* If this table exists... and it should! */
+      fprintf(stderr, "tcp.port (%s)\n", get_dissector_table_ui_name("tcp.port"));
+    if (find_dissector_table("udp.port") != NULL) /* If this table exists... and it should! */
+      fprintf(stderr, "udp.port (%s)\n", get_dissector_table_ui_name("udp.port"));
+
+    g_free(decoded_param); 
+    return FALSE;
+  }
+
+/* We now have a pointer to the handle for the requested table inside the variable table_matching */
+  preference.table_handle = table_matching; /* Save this handle in the preference struct */
+
+  if ( ! (*dissector_str) ) { /* Is the dissector name empty, if so, don't even search for a matching dissector and display all dissectors found for the selected table */
+    fprintf(stderr, "tethereal: No protocol name specified\n"); /* Note, we don't exit here, but dissector_matching will remain NULL, so we exit below */
+  }
+  else {
+    user_protocol_name.nb_shortname_match = 0;
+    user_protocol_name.nb_name_match = 0;
+    user_protocol_name.searched_shortname = dissector_str;
+    user_protocol_name.searched_name = dissector_str;
+    user_protocol_name.matched_shortname_dissector_handle = NULL;
+    user_protocol_name.matched_name_dissector_handle = NULL;
+    dissector_table_foreach_handle(preference.table_name, find_name_shortname_func, &user_protocol_name); /* Go and perform the search for this dissector in the this table's dissectors' names and shortnames */
+
+    if (user_protocol_name.nb_shortname_match != 0) {
+      dissector_matching = user_protocol_name.matched_shortname_dissector_handle;
+      if (user_protocol_name.nb_shortname_match > 1) {
+        fprintf(stderr, "tethereal: Warning: Protocol %s matched %d dissectors, first one will be used\n", dissector_str, user_protocol_name.nb_shortname_match);
+      }
+#ifdef DEBUG
+      fprintf(stderr, "tethereal: Protocol %s matched a dissector short name\n", dissector_str);
+#endif
+    }
+    else if (user_protocol_name.nb_name_match != 0) {
+      dissector_matching = user_protocol_name.matched_name_dissector_handle;
+      if (user_protocol_name.nb_name_match > 1) {
+        fprintf(stderr, "tethereal: Warning: Protocol %s matched %d dissectors, first one will be used\n", dissector_str, user_protocol_name.nb_name_match);
+      }
+#ifdef DEBUG
+      fprintf(stderr, "tethereal: Protocol %s matched a dissector name\n", dissector_str);
+#endif
+    }
+    else {
+      dissector_matching = find_dissector(dissector_str);
+#ifdef DEBUG
+      if (dissector_matching != NULL) {
+        fprintf(stderr, "tethereal: Protocol %s matched a dissector key\n", dissector_str);
+      }
+#endif
+    }
+
+    if (!dissector_matching) {
+      fprintf(stderr, "tethereal: Unknown protocol -- %s\n", dissector_str); /* Note, we don't exit here, but dissector_matching will remain NULL, so we exit below */
+    }
+  }
+
+  if (!dissector_matching) {
+    fprintf(stderr, "tethereal: Warning: Parameter \"%s\" ignored\n", cl_param);
+    fprintf(stderr, "tethereal: Valid protocols for layer type \"%s\" are:\n", preference.table_name);
+    dissector_table_foreach_handle(preference.table_name, display_dissector_short_name, NULL);
+    g_free(decoded_param); 
+    return FALSE;
+  }
+
+/* We now have a pointer to the handle for the requested dissector (requested protocol) inside the variable dissector_matching */
+  preference.dissector_handle = dissector_matching; /* Save this handle in the preference struct */
+
+  if (!add_user_custom_dissector(&preference)) { /* "Decode As" rule has failed. Say it */
+    fprintf(stderr, "tethereal: Could not add a rule corresponding to the switch \"-d %s\"\n", cl_param);
+    g_free(decoded_param); 
+    return TRUE;
+  }
+
+/* If execution reaches this point, the dissector has been registered for the requested layer type and selector */
+#ifdef DEBUG
+  dissector_matching_sn = dissector_handle_get_short_name(preference.dissector_handle);
+  if (dissector_matching_sn == NULL) {
+    fprintf(stderr, "tethereal: On layer type %s, selector %d will be decoded as %s\n", preference.table_name, preference.selector, dissector_str);
+  }
+  else {
+    fprintf(stderr, "tethereal: On layer type %s, selector %d will be decoded as %s\n", preference.table_name, preference.selector, dissector_matching_sn);
+  }
+#endif
+  g_free(decoded_param); /* "Decode As" rule has been succesfully added */
+  return TRUE;
+}
+
 int
 main(int argc, char *argv[])
 {
@@ -445,7 +772,7 @@
   get_runtime_version_info(runtime_info_str);
 
   /* Now get our args */
-  while ((opt = getopt(argc, argv, "a:b:c:Df:F:hi:lnN:o:pqr:R:s:St:vw:Vxz:")) != -1) {
+  while ((opt = getopt(argc, argv, "a:b:c:d:Df:F:hi:lnN:o:pqr:R:s:St:vw:Vxz:")) != -1) {
     switch (opt) {
       case 'a':        /* autostop criteria */
 #ifdef HAVE_LIBPCAP
@@ -477,6 +804,9 @@
         arg_error = TRUE;
 #endif
         break;
+      case 'd':        /* Decode as rule */
+        add_decode_as(optarg); /* Parse the parameter and setup the required dissector */
+	break;
       case 'D':        /* Print a list of capture devices */
 #ifdef HAVE_LIBPCAP
         if_list = get_interface_list(&err, err_str);