aboutsummaryrefslogtreecommitdiffstats
path: root/epan
diff options
context:
space:
mode:
authorDr. Lars Voelker <lars.voelker@bmw.de>2017-04-30 23:31:26 -0400
committerMichael Mann <mmann78@netscape.net>2017-05-05 12:33:38 +0000
commite3d284f6a89b2a04f378d0125cf7a75ec35b76de (patch)
treee292b7b235cb91437817443cc3baa9c253d02d75 /epan
parent4302a0ad650627b4c0e6f48a90ed642939b14841 (diff)
Adding support for the NM protocol.
UDP-NM is an automotive communication protocol as standardized by AUTOSAR and is specified in AUTOSAR_SWS_UDPNetworkManagement.pdf, which can be accessed on: autosar.org -> Classic Platform -> Software Arch -> Comm Stack. It can run over UDP or CAN, which is why "UDP" is not in any user exposed strings. Change-Id: I68adfd941c193588a6c8ef0fe1cb7271f921623e Reviewed-on: https://code.wireshark.org/review/21437 Petri-Dish: Michael Mann <mmann78@netscape.net> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Michael Mann <mmann78@netscape.net>
Diffstat (limited to 'epan')
-rw-r--r--epan/dissectors/CMakeLists.txt1
-rw-r--r--epan/dissectors/Makefile.am1
-rw-r--r--epan/dissectors/packet-udp-nm.c533
3 files changed, 535 insertions, 0 deletions
diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt
index ccbe2d3ba9..c3fa212364 100644
--- a/epan/dissectors/CMakeLists.txt
+++ b/epan/dissectors/CMakeLists.txt
@@ -1296,6 +1296,7 @@ set(DISSECTOR_SRC
packet-ucp.c
packet-udld.c
packet-udp.c
+ packet-udp-nm.c
packet-uds.c
packet-udt.c
packet-uftp.c
diff --git a/epan/dissectors/Makefile.am b/epan/dissectors/Makefile.am
index 8a6b501fd0..1dc17e9230 100644
--- a/epan/dissectors/Makefile.am
+++ b/epan/dissectors/Makefile.am
@@ -1318,6 +1318,7 @@ DISSECTOR_SRC = \
packet-ucp.c \
packet-udld.c \
packet-udp.c \
+ packet-udp-nm.c \
packet-uds.c \
packet-udt.c \
packet-uftp.c \
diff --git a/epan/dissectors/packet-udp-nm.c b/epan/dissectors/packet-udp-nm.c
new file mode 100644
index 0000000000..677e93675e
--- /dev/null
+++ b/epan/dissectors/packet-udp-nm.c
@@ -0,0 +1,533 @@
+/* packet-udp-nm.c
+ * UDP-NM Dissector
+ * By Dr. Lars Voelker <lars.voelker@bmw.de>
+ * Copyright 2014-2017 Dr. Lars Voelker, BMW
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * UDP-NM is an automotive communication protocol as standardized by
+ * AUTOSAR (www.autosar.org) and is specified in AUTOSAR_SWS_UDPNetworkManagement.pdf,
+ * which can be accessed on:
+ * autosar.org -> Classic Platform -> Software Arch -> Comm Stack.
+ */
+
+#include <config.h>
+
+#include <epan/packet.h>
+#include <epan/prefs.h>
+#include <epan/uat.h>
+
+void proto_reg_handoff_udp_nm(void);
+void proto_register_udp_nm(void);
+
+typedef struct _user_data_field_t {
+ gchar* udf_name;
+ gchar* udf_desc;
+ guint32 udf_offset;
+ guint32 udf_length;
+ guint32 udf_mask;
+ gchar* udf_value_desc;
+} user_data_field_t;
+
+static int proto_udp_nm = -1;
+
+/*** header fields ***/
+static int hf_udp_nm_source_node_identifier = -1;
+static int hf_udp_nm_control_bit_vector = -1;
+static int hf_udp_nm_control_bit_vector_repeat_msg_req = -1;
+static int hf_udp_nm_control_bit_vector_nm_coord_sleep = -1;
+static int hf_udp_nm_control_bit_vector_active_wakeup = -1;
+static int hf_udp_nm_control_bit_vector_pni = -1;
+static int hf_udp_nm_user_data = -1;
+
+/*** protocol tree items ***/
+static gint ett_udp_nm = -1;
+static gint ett_udp_nm_cbv = -1;
+static gint ett_udp_nm_user_data = -1;
+
+/*** Bit meanings ***/
+static const true_false_string tfs_udp_nm_control_rep_msg_req = {
+ "Repeat Message State requested", "Repeat Message State not requested" };
+
+static const true_false_string tfs_udp_nm_control_sleep_bit = {
+ "Start of synchronized shutdown requested", "Start of synchronized shutdown not requested" };
+
+static const true_false_string tfs_udp_nm_control_active_wakeup = {
+ "Node has woken up the network", "Node has not woken up the network" };
+
+static const true_false_string tfs_udp_nm_control_pni = {
+ "NM message contains no Partial Network request information", "NM message contains Partial Network request information" };
+
+/*** Configuration items ***/
+/* Set the order of the first two fields (Source Node Identifier and Control Bit Vector */
+static gboolean g_udp_nm_swap_first_fields = TRUE;
+
+/*******************************
+ ****** User data fields ******
+ *******************************/
+
+/*** stolen from the HTTP disector ;-) ***/
+
+static user_data_field_t* user_data_fields = NULL;
+static guint num_user_data_fields = 0;
+static GHashTable* user_data_fields_hash_hf = NULL;
+static GHashTable* user_data_fields_hash_ett = NULL;
+
+static gboolean
+user_data_fields_update_cb(void *r, char **err)
+{
+ user_data_field_t *rec = (user_data_field_t *)r;
+ char c;
+ *err = NULL;
+
+ if (rec->udf_length == 0) {
+ *err = g_strdup_printf("length of user data field can't be 0 Bytes (name: %s offset: %i length: %i)", rec->udf_name, rec->udf_offset, rec->udf_length);
+ return (*err == NULL);
+ }
+
+ if (rec->udf_length > 4) {
+ *err = g_strdup_printf("length of user data field can't be greater 4 Bytes (name: %s offset: %i length: %i)", rec->udf_name, rec->udf_offset, rec->udf_length);
+ return (*err == NULL);
+ }
+
+ if (rec->udf_offset < 2) {
+ *err = g_strdup_printf("offset of user data field can't be short than 2 (name: %s offset: %i length: %i)", rec->udf_name, rec->udf_offset, rec->udf_length);
+ return (*err == NULL);
+ }
+
+ if (rec->udf_mask >= G_MAXUINT32) {
+ *err = g_strdup_printf("mask can only be up to 32bits (name: %s)", rec->udf_name);
+ return (*err == NULL);
+ }
+
+ if (rec->udf_name == NULL) {
+ *err = g_strdup_printf("Name of user data field can't be empty");
+ return (*err == NULL);
+ }
+
+ g_strstrip(rec->udf_name);
+ if (rec->udf_name[0] == 0) {
+ *err = g_strdup_printf("Name of user data field can't be empty");
+ return (*err == NULL);
+ }
+
+ /* Check for invalid characters (to avoid asserting out when
+ * registering the field).
+ */
+ c = proto_check_field_name(rec->udf_name);
+ if (c) {
+ *err = g_strdup_printf("Name of user data field can't contain '%c'", c);
+ return (*err == NULL);
+ }
+
+ return (*err == NULL);
+}
+
+static void *
+user_data_fields_copy_cb(void* n, const void* o, size_t siz _U_)
+{
+ user_data_field_t* new_rec = (user_data_field_t*)n;
+ const user_data_field_t* old_rec = (const user_data_field_t*)o;
+
+ if (old_rec->udf_name) {
+ new_rec->udf_name = g_strdup(old_rec->udf_name);
+ }
+ else {
+ new_rec->udf_name = NULL;
+ }
+
+ if (old_rec->udf_desc) {
+ new_rec->udf_desc = g_strdup(old_rec->udf_desc);
+ }
+ else {
+ new_rec->udf_desc = NULL;
+ }
+
+ new_rec->udf_offset = old_rec->udf_offset;
+ new_rec->udf_length = old_rec->udf_length;
+
+ new_rec->udf_mask = old_rec->udf_mask;
+
+ if (old_rec->udf_value_desc) {
+ new_rec->udf_value_desc = g_strdup(old_rec->udf_value_desc);
+ }
+ else {
+ new_rec->udf_value_desc = NULL;
+ }
+
+ return new_rec;
+}
+
+static void
+user_data_fields_free_cb(void*r)
+{
+ user_data_field_t* rec = (user_data_field_t*)r;
+
+ if (rec->udf_name) g_free(rec->udf_name);
+ if (rec->udf_desc) g_free(rec->udf_desc);
+ if (rec->udf_value_desc) g_free(rec->udf_value_desc);
+}
+
+UAT_CSTRING_CB_DEF(user_data_fields, udf_name, user_data_field_t)
+UAT_CSTRING_CB_DEF(user_data_fields, udf_desc, user_data_field_t)
+UAT_DEC_CB_DEF(user_data_fields, udf_offset, user_data_field_t)
+UAT_DEC_CB_DEF(user_data_fields, udf_length, user_data_field_t)
+UAT_HEX_CB_DEF(user_data_fields, udf_mask, user_data_field_t)
+UAT_CSTRING_CB_DEF(user_data_fields, udf_value_desc, user_data_field_t)
+
+static guint64
+calc_ett_key(guint32 offset, guint32 length)
+{
+ guint64 ret = offset;
+ return (ret * 0x100000000) ^ length;
+}
+
+/*
+ * This creates a string for you that can be used as key for the hash table.
+ * YOU must g_free that string!
+ */
+static gchar*
+calc_hf_key(user_data_field_t udf)
+{
+ gchar* ret = NULL;
+ ret = g_strdup_printf("%i-%i-%i-%s", udf.udf_offset, udf.udf_length, udf.udf_mask, udf.udf_name);
+ return ret;
+}
+
+/*
+ *
+ */
+static gint*
+get_hf_for_user_data(gchar* key)
+{
+ gint* hf_id = NULL;
+
+ if (user_data_fields_hash_hf) {
+ hf_id = (gint*)g_hash_table_lookup(user_data_fields_hash_hf, key);
+ }
+ else {
+ hf_id = NULL;
+ }
+
+ return hf_id;
+}
+
+/*
+ *
+ */
+static gint*
+get_ett_for_user_data(guint32 offset, guint32 length)
+{
+ gint* ett_id = NULL;
+
+ guint64 key = calc_ett_key(offset, length);
+
+ if (user_data_fields_hash_ett) {
+ ett_id = (gint*)g_hash_table_lookup(user_data_fields_hash_ett, &key);
+ }
+ else {
+ ett_id = NULL;
+ }
+
+ return ett_id;
+}
+
+/*
+ *
+ */
+static void
+user_data_post_update_cb(void)
+{
+ static hf_register_info* hf;
+ gint* hf_id;
+ guint i;
+ //gchar* udf_name;
+
+ gchar* tmp = NULL;
+ guint64* key = NULL;
+
+ static gint ett_dummy = -1;
+ static gint *ett[] = {
+ &ett_dummy,
+ };
+
+ static gint *ett_id;
+
+ if (user_data_fields_hash_hf && hf) {
+ guint hf_size = g_hash_table_size(user_data_fields_hash_hf);
+ /* Unregister all fields */
+ for (i = 0; i < hf_size; i++) {
+ proto_deregister_field(proto_udp_nm, *(hf[i].p_id));
+ }
+ g_hash_table_destroy(user_data_fields_hash_hf);
+
+ user_data_fields_hash_hf = NULL;
+ }
+
+ // we cannot unregister ETTs, so we should try to limit the damage of an update
+ if (num_user_data_fields) {
+ user_data_fields_hash_hf = g_hash_table_new(g_str_hash, g_str_equal);
+ hf = g_new0(hf_register_info, num_user_data_fields);
+
+ if (user_data_fields_hash_ett == NULL) {
+ user_data_fields_hash_ett = g_hash_table_new(g_int64_hash, g_int64_equal);
+ }
+
+ for (i = 0; i < num_user_data_fields; i++) {
+ hf_id = g_new(gint, 1);
+ *hf_id = -1;
+
+ hf[i].p_id = hf_id;
+ hf[i].hfinfo.strings = NULL;
+ hf[i].hfinfo.bitmask = user_data_fields[i].udf_mask;
+ hf[i].hfinfo.same_name_next = NULL;
+ hf[i].hfinfo.same_name_prev_id = -1;
+
+ if (user_data_fields[i].udf_mask == 0 || user_data_fields[i].udf_length <= 0 || user_data_fields[i].udf_length>4) {
+ hf[i].hfinfo.name = g_strdup(user_data_fields[i].udf_name);
+ hf[i].hfinfo.abbrev = g_strdup_printf("nm.user_data.%s", user_data_fields[i].udf_name);
+ hf[i].hfinfo.type = FT_BYTES;
+ hf[i].hfinfo.display = BASE_NONE;
+ hf[i].hfinfo.bitmask = 0;
+ hf[i].hfinfo.blurb = g_strdup(user_data_fields[i].udf_desc);
+ }
+ else {
+ hf[i].hfinfo.name = g_strdup(user_data_fields[i].udf_value_desc);
+ hf[i].hfinfo.abbrev = g_strdup_printf("nm.user_data.%s.%s", user_data_fields[i].udf_name, user_data_fields[i].udf_value_desc);
+ hf[i].hfinfo.type = FT_BOOLEAN;
+ hf[i].hfinfo.display = 8 * (user_data_fields[i].udf_length);
+ // hf[i].hfinfo.bitmask = 0;
+ hf[i].hfinfo.blurb = g_strdup(user_data_fields[i].udf_value_desc);
+ }
+
+ tmp = calc_hf_key(user_data_fields[i]);
+ g_hash_table_insert(user_data_fields_hash_hf, tmp, hf_id);
+
+ // generate etts for new fields only
+ if (get_ett_for_user_data(user_data_fields[i].udf_offset, user_data_fields[i].udf_length) == NULL) {
+ ett_dummy = -1;
+ proto_register_subtree_array(ett, array_length(ett));
+
+ ett_id = g_new(gint, 1);
+ *ett_id = ett_dummy;
+
+ key = g_new(guint64, 1);
+ *key = calc_ett_key(user_data_fields[i].udf_offset, user_data_fields[i].udf_length);
+
+ g_hash_table_insert(user_data_fields_hash_ett, key, ett_id);
+ }
+ }
+
+ proto_register_field_array(proto_udp_nm, hf, num_user_data_fields);
+ }
+}
+
+
+/**********************************
+ ****** The dissector itself ******
+ **********************************/
+
+static int
+dissect_udp_nm(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, void *data _U_)
+{
+ proto_item *ti;
+ proto_tree *udp_nm_tree;
+ proto_tree *udp_nm_subtree = NULL;
+ gchar* tmp = NULL;
+ guint32 offset = 0;
+ guint32 length = 0;
+ guint32 msg_length = 0;
+ guint32 ctrl_bit_vector;
+ guint32 src_node_id;
+ guint i = 0;
+ int* hf_id;
+ int ett_id;
+
+ // AUTOSAR says default is Source Node ID first and Ctrl Bit Vector second but this can be also swapped
+ guint32 offset_ctrl_bit_vector = 1;
+ guint32 offset_src_node_id = 0;
+
+ static const int * control_bits[] = {
+ &hf_udp_nm_control_bit_vector_repeat_msg_req,
+ &hf_udp_nm_control_bit_vector_nm_coord_sleep,
+ &hf_udp_nm_control_bit_vector_active_wakeup,
+ &hf_udp_nm_control_bit_vector_pni,
+ NULL
+ };
+
+ if (g_udp_nm_swap_first_fields == TRUE) {
+ offset_ctrl_bit_vector = 0;
+ offset_src_node_id = 1;
+ }
+
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, "NM");
+ col_clear(pinfo->cinfo, COL_INFO);
+
+ msg_length = tvb_reported_length(tvb);
+
+ ti = proto_tree_add_item(tree, proto_udp_nm, tvb, 0, -1, ENC_NA);
+ udp_nm_tree = proto_item_add_subtree(ti, ett_udp_nm);
+
+ if (g_udp_nm_swap_first_fields == FALSE) {
+ proto_tree_add_item_ret_uint(udp_nm_tree, hf_udp_nm_source_node_identifier, tvb, offset_src_node_id, 1, ENC_BIG_ENDIAN, &src_node_id);
+ }
+
+ proto_tree_add_bitmask(udp_nm_tree, tvb, offset_ctrl_bit_vector, hf_udp_nm_control_bit_vector, ett_udp_nm_cbv, control_bits, ENC_BIG_ENDIAN);
+ ctrl_bit_vector = tvb_get_guint8(tvb, offset_ctrl_bit_vector);
+
+ if (g_udp_nm_swap_first_fields == TRUE) {
+ proto_tree_add_item_ret_uint(udp_nm_tree, hf_udp_nm_source_node_identifier, tvb, offset_src_node_id, 1, ENC_BIG_ENDIAN, &src_node_id);
+ }
+
+ col_add_fstr(pinfo->cinfo, COL_INFO, "Control Bit Vector: 0x%02x, Source Node: 0x%02x", ctrl_bit_vector, src_node_id);
+ proto_item_append_text(ti, ", Control Bit Vector: 0x%02x, Source Node: %i", ctrl_bit_vector, src_node_id);
+
+ offset = 2;
+
+ /* now we need to process the user defined fields ... */
+ ti = proto_tree_add_item(udp_nm_tree, hf_udp_nm_user_data, tvb, offset, msg_length - offset, ENC_NA);
+ udp_nm_tree = proto_item_add_subtree(ti, ett_udp_nm_user_data);
+
+ for (i = 0; i < num_user_data_fields; i++) {
+ tmp = calc_hf_key(user_data_fields[i]);
+ hf_id = get_hf_for_user_data(tmp);
+
+ offset = user_data_fields[i].udf_offset;
+ length = user_data_fields[i].udf_length;
+ ett_id = *(get_ett_for_user_data(offset, length));
+
+ if (hf_id && msg_length >= length + offset) {
+ if (user_data_fields[i].udf_mask == 0) {
+ ti = proto_tree_add_item(udp_nm_tree, *hf_id, tvb, offset, length, ENC_BIG_ENDIAN);
+ udp_nm_subtree = proto_item_add_subtree(ti, ett_id);
+ }
+ else {
+ if (udp_nm_subtree != NULL) {
+ ti = proto_tree_add_item(udp_nm_subtree, *hf_id, tvb, offset, length, ENC_BIG_ENDIAN);
+ }
+ }
+ }
+ else {
+ /* should we warn? */
+ }
+
+ g_free(tmp);
+ }
+
+ return 8;
+}
+
+void proto_register_udp_nm(void)
+{
+ module_t *udp_nm_module;
+ uat_t* user_data_fields_uat;
+
+ static hf_register_info hf_udp_nm[] = {
+ { &hf_udp_nm_control_bit_vector,
+ { "Control Bit Vector", "nm.ctrl", FT_UINT8, BASE_HEX, NULL, 0x0, "The Control Bit Vector", HFILL } },
+ { &hf_udp_nm_control_bit_vector_repeat_msg_req,
+ { "Repeat Message Request", "nm.ctrl.repeat_msg_req", FT_BOOLEAN, 8, TFS(&tfs_udp_nm_control_rep_msg_req), 0x01, "The Repeat Message Request Bit", HFILL } },
+ { &hf_udp_nm_control_bit_vector_nm_coord_sleep,
+ { "NM Coordinator Sleep", "nm.ctrl.nm_coord_sleep", FT_BOOLEAN, 8, TFS(&tfs_udp_nm_control_sleep_bit), 0x08, "NM Coordinator Sleep Bit", HFILL } },
+ { &hf_udp_nm_control_bit_vector_active_wakeup,
+ { "Active Wakeup", "nm.ctrl.active_wakeup", FT_BOOLEAN, 8, TFS(&tfs_udp_nm_control_active_wakeup), 0x10, "Active Wakeup Bit", HFILL } },
+ { &hf_udp_nm_control_bit_vector_pni,
+ { "Partial Network Information", "nm.ctrl.pni", FT_BOOLEAN, 8, TFS(&tfs_udp_nm_control_pni), 0x40, "Partial Network Information Bit", HFILL } },
+
+ { &hf_udp_nm_source_node_identifier,
+ { "Source Node Identifier", "nm.src", FT_UINT8, BASE_DEC, NULL, 0x0, "The identification of the sending node", HFILL } },
+
+ { &hf_udp_nm_user_data,
+ { "User Data", "nm.user_data", FT_BYTES, BASE_NONE, NULL, 0x0, "The User Data", HFILL } },
+ };
+
+ static gint *ett[] = {
+ &ett_udp_nm,
+ &ett_udp_nm_cbv,
+ &ett_udp_nm_user_data,
+ };
+
+ /* UAT for user_data fields */
+ static uat_field_t user_data_uat_fields[] = {
+ UAT_FLD_CSTRING(user_data_fields, udf_name, "User data name", "Name of user data field"),
+ UAT_FLD_CSTRING(user_data_fields, udf_desc, "User data desc", "Description of user data field"),
+ UAT_FLD_DEC(user_data_fields, udf_offset, "User data offset", "Offset of the user data field in the UDP-NM message (uint32)"),
+ UAT_FLD_DEC(user_data_fields, udf_length, "User data length", "Length of the user data field in the UDP-NM message (uint32)"),
+ UAT_FLD_DEC(user_data_fields, udf_mask, "User data mask", "Relevant bits of the user data field in the UDP-NM message (uint32)"),
+ UAT_FLD_CSTRING(user_data_fields, udf_value_desc, "User data value", "Description what the masked bits mean"),
+ UAT_END_FIELDS
+ };
+
+ /* Register the protocol name and description */
+ proto_udp_nm = proto_register_protocol("Network Management", "NM", "nm");
+ proto_register_field_array(proto_udp_nm, hf_udp_nm, array_length(hf_udp_nm));
+ proto_register_subtree_array(ett, array_length(ett));
+
+ /* Register configuration options */
+ udp_nm_module = prefs_register_protocol(proto_udp_nm, NULL);
+ prefs_register_bool_preference(udp_nm_module, "swap_ctrl_and_src",
+ "Swap Source Node Identifier and Control Bit Vector",
+ "In the standard the Source Node Identifier is the first byte "
+ "and the Control Bit Vector is the second byte. "
+ "Using this parameter they can be swapped",
+ &g_udp_nm_swap_first_fields);
+
+ /* UAT */
+ user_data_fields_uat = uat_new("NM User Data Fields Table",
+ sizeof(user_data_field_t), /* record size */
+ "NM_user_data_fields", /* filename */
+ TRUE, /* from_profile */
+ &user_data_fields, /* data_ptr */
+ &num_user_data_fields, /* numitems_ptr */
+ UAT_AFFECTS_DISSECTION | UAT_AFFECTS_FIELDS, /* specifies named fields, so affects dissection and the set of named fields */
+ NULL, /* help */
+ user_data_fields_copy_cb, /* copy callback */
+ user_data_fields_update_cb, /* update callback */
+ user_data_fields_free_cb, /* free callback */
+ user_data_post_update_cb, /* post update callback */
+ NULL, /* reset callback */
+ user_data_uat_fields); /* UAT field definitions */
+
+ prefs_register_uat_preference(udp_nm_module, "udp_nm_user_data_fields", "User Data Field Configuration",
+ "A table to define user defined fields in the NM payload",
+ user_data_fields_uat);
+}
+
+void proto_reg_handoff_udp_nm(void)
+{
+ dissector_handle_t nm_handle = create_dissector_handle(dissect_udp_nm, proto_udp_nm);
+
+ dissector_add_for_decode_as_with_preference("udp.port", nm_handle);
+ dissector_add_for_decode_as("can.subdissector", nm_handle);
+}
+
+/*
+ * Editor modelines
+ *
+ * Local Variables:
+ * c-basic-offset: 2
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * ex: set shiftwidth=2 tabstop=8 expandtab:
+ * :indentSize=2:tabSize=8:noTabs=true:
+ */