diff options
author | Tomas Kukosa <tomas.kukosa@siemens.com> | 2011-02-04 14:10:37 +0000 |
---|---|---|
committer | Tomas Kukosa <tomas.kukosa@siemens.com> | 2011-02-04 14:10:37 +0000 |
commit | e3808393e1ec40a315f8e09d0014064558a02799 (patch) | |
tree | 35f2422014b73333b8fd4e6acf5e21dda677f283 /epan | |
parent | da9d25c9ebc3403303212e95e668a7e2530d823e (diff) |
Erlang Port Mapper Daemon protocol updated and Erlang Distribution Protocol implemented
svn path=/trunk/; revision=35819
Diffstat (limited to 'epan')
-rw-r--r-- | epan/CMakeLists.txt | 1 | ||||
-rw-r--r-- | epan/dissectors/Makefile.common | 2 | ||||
-rw-r--r-- | epan/dissectors/packet-epmd.c | 396 | ||||
-rw-r--r-- | epan/dissectors/packet-epmd.h | 26 | ||||
-rw-r--r-- | epan/dissectors/packet-erldp.c | 592 |
5 files changed, 865 insertions, 152 deletions
diff --git a/epan/CMakeLists.txt b/epan/CMakeLists.txt index 055ed864b8..ebb37d99d4 100644 --- a/epan/CMakeLists.txt +++ b/epan/CMakeLists.txt @@ -519,6 +519,7 @@ set(DISSECTOR_SRC dissectors/packet-eapol.c dissectors/packet-echo.c dissectors/packet-edonkey.c + dissectors/packet-erldp.c dissectors/packet-egd.c dissectors/packet-ehs.c dissectors/packet-eigrp.c diff --git a/epan/dissectors/Makefile.common b/epan/dissectors/Makefile.common index b89ce0546d..24d4469ef9 100644 --- a/epan/dissectors/Makefile.common +++ b/epan/dissectors/Makefile.common @@ -430,6 +430,7 @@ DISSECTOR_SRC = \ packet-eapol.c \ packet-echo.c \ packet-edonkey.c \ + packet-erldp.c \ packet-egd.c \ packet-ehs.c \ packet-eigrp.c \ @@ -1094,6 +1095,7 @@ DISSECTOR_INCLUDES = \ packet-enc.h \ packet-epl.h \ packet-epl_v1.h \ + packet-epmd.h \ packet-esis.h \ packet-ess.h \ packet-eth.h \ diff --git a/epan/dissectors/packet-epmd.c b/epan/dissectors/packet-epmd.c index 5fe2430208..8f572da80f 100644 --- a/epan/dissectors/packet-epmd.c +++ b/epan/dissectors/packet-epmd.c @@ -36,12 +36,18 @@ #endif #include <epan/packet.h> -#include <epan/ptvcursor.h> +#include <epan/conversation.h> + +#define PNAME "Erlang Port Mapper Daemon" +#define PSNAME "EPMD" +#define PFNAME "epmd" static int proto_epmd = -1; static int hf_epmd_len = -1; static int hf_epmd_type = -1; -static int hf_epmd_tcp_port = -1; +static int hf_epmd_port_no = -1; +static int hf_epmd_node_type = -1; +static int hf_epmd_protocol = -1; static int hf_epmd_dist_high = -1; static int hf_epmd_dist_low = -1; static int hf_epmd_name_len = -1; @@ -54,133 +60,216 @@ static int hf_epmd_creation = -1; static gint ett_epmd = -1; +/* Other dissectors */ +static dissector_handle_t edp_handle = NULL; + #define EPMD_PORT 4369 -/* requests */ -#define EPMD_ALIVE2 'x' -#define EPMD_PORT_PLEASE 'p' -#define EPMD_PORT_PLEASE2 'z' -#define EPMD_NAMES 'n' -#define EPMD_ALIVE 'a' +/* Definitions of message codes */ +#define EPMD_ALIVE_REQ 'a' +#define EPMD_ALIVE_OK_RESP 'Y' +#define EPMD_PORT_REQ 'p' +#define EPMD_NAMES_REQ 'n' +#define EPMD_DUMP_REQ 'd' +#define EPMD_KILL_REQ 'k' +#define EPMD_STOP_REQ 's' +/* New epmd messages */ +#define EPMD_ALIVE2_REQ 'x' /* 120 */ +#define EPMD_PORT2_REQ 'z' /* 122 */ +#define EPMD_ALIVE2_RESP 'y' /* 121 */ +#define EPMD_PORT2_RESP 'w' /* 119 */ -/* responses */ -#define EPMD_ALIVE_OK 'Y' -#define EPMD_ALIVE2_OK 'y' -#define EPMD_PORT_PLEASE2_OK 'w' +static const value_string message_types[] = { + { EPMD_ALIVE_REQ , "EPMD_ALIVE_REQ" }, + { EPMD_ALIVE_OK_RESP, "EPMD_ALIVE_OK_RESP" }, + { EPMD_PORT_REQ , "EPMD_PORT_REQ" }, + { EPMD_NAMES_REQ , "EPMD_NAMES_REQ" }, + { EPMD_DUMP_REQ , "EPMD_DUMP_REQ" }, + { EPMD_KILL_REQ , "EPMD_KILL_REQ" }, + { EPMD_STOP_REQ , "EPMD_STOP_REQ" }, + { EPMD_ALIVE2_REQ , "EPMD_ALIVE2_REQ" }, + { EPMD_PORT2_REQ , "EPMD_PORT2_REQ" }, + { EPMD_ALIVE2_RESP , "EPMD_ALIVE2_RESP" }, + { EPMD_PORT2_RESP , "EPMD_PORT2_RESP" }, + { 0, NULL } +}; -/* unknown; currently not implemented */ -#define EPMD_DUMP 'd' -#define EPMD_KILL 'k' -#define EPMD_STOP 's' +static const value_string node_type_vals[] = { + { 72 , "R3 hidden node" }, + { 77 , "R3 erlang node" }, + { 104 , "R4 hidden node" }, + { 109 , "R4 erlang node" }, + { 110 , "R6 nodes" }, + { 0, NULL } +}; -static const value_string message_types[] = -{ - { EPMD_ALIVE, "Alive" }, - { EPMD_PORT_PLEASE, "Port Please" }, - { EPMD_NAMES, "Names" }, - { EPMD_DUMP, "Dump" }, - { EPMD_KILL, "Kill" }, - { EPMD_STOP, "Stop" }, - { EPMD_ALIVE_OK, "Alive Ok" }, - { EPMD_ALIVE2, "Alive 2" }, - { EPMD_PORT_PLEASE2, "Port Please 2" }, - { EPMD_ALIVE2_OK, "Alive 2 Ok" }, - { EPMD_PORT_PLEASE2_OK, "Port Please 2 Ok" }, - { 0, NULL } +static const value_string protocol_vals[] = { + { 0 , "tcp/ip-v4" }, + { 0, NULL } +}; + +const value_string epmd_version_vals[] = { + { 0 , "R3" }, + { 1 , "R4" }, + { 2 , "R5" }, + { 3 , "R5C" }, + { 4 , "R6 dev" }, + { 5 , "R6" }, + { 0, NULL } }; static void -dissect_epmd_request(ptvcursor_t *cursor) -{ - tvbuff_t *tvb; - guint8 type; +dissect_epmd_request(packet_info *pinfo, tvbuff_t *tvb, gint offset, proto_tree *tree) { + guint8 type; + guint16 name_length = 0; + const gchar *name = NULL; - tvb = ptvcursor_tvbuff(cursor); - ptvcursor_add(cursor, hf_epmd_len, 2, FALSE); + proto_tree_add_item(tree, hf_epmd_len, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + type = tvb_get_guint8(tvb, offset); + proto_tree_add_item(tree, hf_epmd_type, tvb, offset, 1, ENC_BIG_ENDIAN); + offset++; + col_add_str(pinfo->cinfo, COL_INFO, val_to_str(type, VALS(message_types), "unknown (0x%02X)")); - type = tvb_get_guint8(tvb, ptvcursor_current_offset(cursor)); - ptvcursor_add(cursor, hf_epmd_type, 1, FALSE); - switch (type) { - case EPMD_ALIVE2: { - guint16 name_length, elen; - ptvcursor_add(cursor, hf_epmd_tcp_port, 2, FALSE); - ptvcursor_advance(cursor,2); /* 'M', 0 */ - ptvcursor_add(cursor, hf_epmd_dist_high, 2, FALSE); - ptvcursor_add(cursor, hf_epmd_dist_low, 2, FALSE); - name_length = tvb_get_ntohs(tvb, ptvcursor_current_offset(cursor)); - ptvcursor_add(cursor, hf_epmd_name_len, 2, FALSE); - ptvcursor_add(cursor, hf_epmd_name, name_length, FALSE); - elen = tvb_get_ntohs(tvb, ptvcursor_current_offset(cursor)); - ptvcursor_add(cursor, hf_epmd_elen, 2, FALSE); - if (elen > 0) - ptvcursor_add(cursor, hf_epmd_edata, elen, FALSE); - break; - } - case EPMD_PORT_PLEASE: - case EPMD_PORT_PLEASE2: - /*ptvcursor_add(cursor, hf_epmd_name, tvb_length(tvb)-3, FALSE);*/ - ptvcursor_add(cursor, hf_epmd_name, -1, FALSE); - break; - case EPMD_ALIVE: { - ptvcursor_add(cursor, hf_epmd_tcp_port, 2, FALSE); - /*ptvcursor_add(cursor, hf_epmd_name, tvb_length(tvb)-3, FALSE);*/ - ptvcursor_add(cursor, hf_epmd_name, -1, FALSE); - break; - } - case EPMD_NAMES: - default: - break; - } + switch (type) { + case EPMD_ALIVE2_REQ: + proto_tree_add_item(tree, hf_epmd_port_no, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + proto_tree_add_item(tree, hf_epmd_node_type, tvb, offset, 1, ENC_BIG_ENDIAN); + offset++; + proto_tree_add_item(tree, hf_epmd_protocol, tvb, offset, 1, ENC_BIG_ENDIAN); + offset++; + proto_tree_add_item(tree, hf_epmd_dist_high, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + proto_tree_add_item(tree, hf_epmd_dist_low, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + name_length = tvb_get_ntohs(tvb, offset); + proto_tree_add_item(tree, hf_epmd_name_len, tvb, offset, 2, ENC_BIG_ENDIAN); + proto_tree_add_item(tree, hf_epmd_name, tvb, offset + 2, name_length, ENC_NA); + name = tvb_get_ephemeral_string(tvb, offset + 2, name_length); + offset += 2 + name_length; + if (tvb_length_remaining(tvb, offset) >= 2) { + guint16 elen=0; + elen = tvb_get_ntohs(tvb, offset); + proto_tree_add_item(tree, hf_epmd_elen, tvb, offset, 2, ENC_BIG_ENDIAN); + if (elen > 0) + proto_tree_add_item(tree, hf_epmd_edata, tvb, offset + 2, elen, ENC_NA); + offset += 2 + elen; + } + break; + + case EPMD_PORT_REQ: + case EPMD_PORT2_REQ: + name_length = tvb_length_remaining(tvb, offset); + proto_tree_add_item(tree, hf_epmd_name, tvb, offset, name_length, ENC_NA); + name = tvb_get_ephemeral_string(tvb, offset, name_length); + break; + + case EPMD_ALIVE_REQ: + proto_tree_add_item(tree, hf_epmd_port_no, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + name_length = tvb_length_remaining(tvb, offset); + proto_tree_add_item(tree, hf_epmd_name, tvb, offset, name_length, ENC_NA); + name = tvb_get_ephemeral_string(tvb, offset, name_length); + break; + + case EPMD_NAMES_REQ: + break; + + } + + if (name) { + col_append_fstr(pinfo->cinfo, COL_INFO, " %s", name); + } + } static void -dissect_epmd_response_names(ptvcursor_t *cursor) -{ - ptvcursor_add(cursor, hf_epmd_tcp_port, 2, FALSE); - ptvcursor_add(cursor, hf_epmd_names, -1, FALSE); - /* TODO: parse names */ +dissect_epmd_response_names(packet_info *pinfo, tvbuff_t *tvb, gint offset, proto_tree *tree) { + proto_tree_add_item(tree, hf_epmd_port_no, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + proto_tree_add_item(tree, hf_epmd_names, tvb, offset, -1, ENC_NA); } static void -dissect_epmd_response(ptvcursor_t *cursor) -{ - tvbuff_t *tvb; - guint32 port; - guint8 type; +dissect_epmd_response(packet_info *pinfo, tvbuff_t *tvb, gint offset, proto_tree *tree) { + guint8 type, result; + guint32 port; + guint16 name_length = 0; + const gchar *name = NULL; + conversation_t *conv = NULL; - tvb = ptvcursor_tvbuff(cursor); - port = tvb_get_ntohl(tvb, 0); - if (port == EPMD_PORT) { - dissect_epmd_response_names(cursor); - return; - } + port = tvb_get_ntohl(tvb, offset); + if (port == EPMD_PORT) { + dissect_epmd_response_names(pinfo, tvb, offset, tree); + return; + } - type = tvb_get_guint8(tvb, 0); - ptvcursor_add(cursor, hf_epmd_type, 1, FALSE); - switch (type) { - case EPMD_PORT_PLEASE2_OK: { - ptvcursor_advance(cursor, 1); -/* 'w', 0, Port(16), Type(8), Proto(8), High(16), Low(16), NLen(16), Name(x) */ - ptvcursor_add(cursor, hf_epmd_tcp_port, 2, FALSE); - ptvcursor_advance(cursor, 2); /* 'M', 0 */ - ptvcursor_add(cursor, hf_epmd_dist_high, 2, FALSE); - ptvcursor_add(cursor, hf_epmd_dist_low, 2, FALSE); - ptvcursor_add(cursor, hf_epmd_name_len, 2, FALSE); - ptvcursor_add(cursor, hf_epmd_name, -1, FALSE); - } - case EPMD_ALIVE_OK: - case EPMD_ALIVE2_OK: { - ptvcursor_add(cursor, hf_epmd_result, 1, FALSE); - ptvcursor_add(cursor, hf_epmd_creation, 2, FALSE); - } - default: - break; - } + type = tvb_get_guint8(tvb, offset); + proto_tree_add_item(tree, hf_epmd_type, tvb, offset, 1, ENC_BIG_ENDIAN); + offset++; + col_add_str(pinfo->cinfo, COL_INFO, val_to_str(type, VALS(message_types), "unknown (0x%02X)")); + + switch (type) { + case EPMD_ALIVE_OK_RESP: + case EPMD_ALIVE2_RESP: + result = tvb_get_guint8(tvb, offset); + proto_tree_add_item(tree, hf_epmd_result, tvb, offset, 1, ENC_BIG_ENDIAN); + offset++; + proto_tree_add_item(tree, hf_epmd_creation, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + if (!result) { + col_append_str(pinfo->cinfo, COL_INFO, " OK"); + } else { + col_append_fstr(pinfo->cinfo, COL_INFO, " ERROR 0x%02X", result); + } + break; + + case EPMD_PORT2_RESP: + result = tvb_get_guint8(tvb, offset); + proto_tree_add_item(tree, hf_epmd_result, tvb, offset, 1, ENC_BIG_ENDIAN); + offset++; + if (!result) { + col_append_str(pinfo->cinfo, COL_INFO, " OK"); + } else { + col_append_fstr(pinfo->cinfo, COL_INFO, " ERROR 0x%02X", result); + break; + } + port = tvb_get_ntohs(tvb, offset); + proto_tree_add_item(tree, hf_epmd_port_no, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + proto_tree_add_item(tree, hf_epmd_node_type, tvb, offset, 1, ENC_BIG_ENDIAN); + offset++; + proto_tree_add_item(tree, hf_epmd_protocol, tvb, offset, 1, ENC_BIG_ENDIAN); + offset++; + proto_tree_add_item(tree, hf_epmd_dist_high, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + proto_tree_add_item(tree, hf_epmd_dist_low, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + name_length = tvb_get_ntohs(tvb, offset); + proto_tree_add_item(tree, hf_epmd_name_len, tvb, offset, 2, ENC_BIG_ENDIAN); + proto_tree_add_item(tree, hf_epmd_name, tvb, offset + 2, name_length, ENC_NA); + name = tvb_get_ephemeral_string(tvb, offset + 2, name_length); + offset += 2 + name_length; + if (tvb_length_remaining(tvb, offset) >= 2) { + guint16 elen=0; + elen = tvb_get_ntohs(tvb, offset); + proto_tree_add_item(tree, hf_epmd_elen, tvb, offset, 2, ENC_BIG_ENDIAN); + if (elen > 0) + proto_tree_add_item(tree, hf_epmd_edata, tvb, offset + 2, elen, ENC_NA); + offset += 2 + elen; + } + col_append_fstr(pinfo->cinfo, COL_INFO, " %s port=%d", name, port); + if (!pinfo->fd->flags.visited) { + conv = conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst, PT_TCP, port, 0, NO_PORT2); + conversation_set_dissector(conv, edp_handle); + } + break; + } } static gboolean -check_epmd(tvbuff_t *tvb) -{ +check_epmd(tvbuff_t *tvb) { guint8 type; /* simple heuristic: @@ -196,9 +285,9 @@ check_epmd(tvbuff_t *tvb) type = tvb_get_guint8(tvb, 0); switch (type) { - case EPMD_ALIVE_OK: - case EPMD_ALIVE2_OK: - case EPMD_PORT_PLEASE2_OK: + case EPMD_ALIVE_OK_RESP: + case EPMD_ALIVE2_RESP: + case EPMD_PORT2_RESP: return(TRUE); default: break; @@ -206,11 +295,11 @@ check_epmd(tvbuff_t *tvb) type = tvb_get_guint8(tvb, 2); switch (type) { - case EPMD_ALIVE2: - case EPMD_PORT_PLEASE: - case EPMD_PORT_PLEASE2: - case EPMD_NAMES: - case EPMD_ALIVE: + case EPMD_ALIVE_REQ: + case EPMD_ALIVE2_REQ: + case EPMD_PORT_REQ: + case EPMD_PORT2_REQ: + case EPMD_NAMES_REQ: return( TRUE); default: break; @@ -220,32 +309,25 @@ check_epmd(tvbuff_t *tvb) } static int -dissect_epmd(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) -{ - proto_tree *epmd_tree; - proto_item *ti; - ptvcursor_t *cursor; +dissect_epmd(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { + proto_tree *epmd_tree; + proto_item *ti; - if (!check_epmd(tvb)) - return(0); + if (!check_epmd(tvb)) + return(0); - col_set_str(pinfo->cinfo, COL_PROTOCOL, "EPMD"); + col_set_str(pinfo->cinfo, COL_PROTOCOL, PSNAME); - if (tree) { - ti = proto_tree_add_item(tree, proto_epmd, tvb, 0, -1, FALSE); - epmd_tree = proto_item_add_subtree(ti, ett_epmd); - cursor = ptvcursor_new(epmd_tree, tvb, 0); + ti = proto_tree_add_item(tree, proto_epmd, tvb, 0, -1, FALSE); + epmd_tree = proto_item_add_subtree(ti, ett_epmd); - if (pinfo->srcport==EPMD_PORT) { - dissect_epmd_response(cursor); - } else { - dissect_epmd_request(cursor); - } + if (pinfo->match_port == pinfo->destport) { + dissect_epmd_request(pinfo, tvb, 0, epmd_tree); + } else { + dissect_epmd_response(pinfo, tvb, 0, epmd_tree); + } - ptvcursor_free(cursor); - } - - return(tvb_length(tvb)); + return(tvb_length(tvb)); } void @@ -264,30 +346,38 @@ proto_register_epmd(void) { "Result", "epmd.result", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_epmd_tcp_port, - { "TCP Port", "epmd.tcp_port", + { &hf_epmd_port_no, + { "Port No", "epmd.port_no", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, + { &hf_epmd_node_type, + { "Node Type", "epmd.node_type", + FT_UINT8, BASE_DEC, VALS(node_type_vals), 0x0, + "Node Type", HFILL }}, + { &hf_epmd_protocol, + { "Protocol", "epmd.protocol", + FT_UINT8, BASE_DEC, VALS(protocol_vals), 0x0, + "Protocol", HFILL }}, { &hf_epmd_creation, { "Creation", "epmd.creation", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_epmd_dist_high, - { "Dist High", "epmd.dist_high", - FT_UINT16, BASE_DEC, NULL, 0x0, + { "Highest Version", "epmd.dist_high", + FT_UINT16, BASE_DEC, VALS(epmd_version_vals), 0x0, NULL, HFILL }}, { &hf_epmd_dist_low, - { "Dist Low", "epmd.dist_low", - FT_UINT16, BASE_DEC, NULL, 0x0, + { "Lowest Version", "epmd.dist_low", + FT_UINT16, BASE_DEC, VALS(epmd_version_vals), 0x0, NULL, HFILL }}, { &hf_epmd_name_len, { "Name Length", "epmd.name_len", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_epmd_name, - { "Name", "epmd.name", + { "Node Name", "epmd.name", FT_STRING, BASE_NONE, NULL, 0x0, - NULL, HFILL }}, + "Node Name", HFILL }}, { &hf_epmd_elen, { "Elen", "epmd.elen", FT_UINT16, BASE_DEC, NULL, 0x0, @@ -305,16 +395,18 @@ proto_register_epmd(void) &ett_epmd, }; - proto_epmd = proto_register_protocol("EPMD Protocol", "EPMD", "epmd"); + proto_epmd = proto_register_protocol(PNAME, PSNAME, PFNAME); proto_register_field_array(proto_epmd, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); - new_register_dissector("epmd", dissect_epmd, proto_epmd); + new_register_dissector(PFNAME, dissect_epmd, proto_epmd); } void -proto_reg_handoff_epmd(void) -{ - dissector_handle_t epmd_handle; - epmd_handle = find_dissector("epmd"); - dissector_add_uint("tcp.port", EPMD_PORT, epmd_handle); +proto_reg_handoff_epmd(void) { + dissector_handle_t epmd_handle; + + epmd_handle = find_dissector("epmd"); + edp_handle = find_dissector("erldp"); + + dissector_add_uint("tcp.port", EPMD_PORT, epmd_handle); } diff --git a/epan/dissectors/packet-epmd.h b/epan/dissectors/packet-epmd.h new file mode 100644 index 0000000000..fbe22b7153 --- /dev/null +++ b/epan/dissectors/packet-epmd.h @@ -0,0 +1,26 @@ +/* packet-epmd.h + * Definitions for EPMD (Erlang Port Mapper Daemon) messages + * + * $Id$ + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __PACKET_EPMD_H__ +#define __PACKET_EPMD_H__ + +extern const value_string epmd_version_vals[]; + +#endif diff --git a/epan/dissectors/packet-erldp.c b/epan/dissectors/packet-erldp.c new file mode 100644 index 0000000000..7396e75e7d --- /dev/null +++ b/epan/dissectors/packet-erldp.c @@ -0,0 +1,592 @@ +/* packet-erldp.c + * Erlang Distribution Protocol + * http://www.erlang.org/doc/apps/erts/erl_dist_protocol.html + * + * 2010 Tomas Kukosa + * + * $Id$ + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <string.h> + +#include <epan/packet.h> +#include <epan/strutil.h> +#include <epan/emem.h> + +#include <epan/dissectors/packet-tcp.h> +#include <epan/dissectors/packet-epmd.h> + +#define ERL_PASS_THROUGH 'p' + +#define VERSION_MAGIC 131 /* 130 in erlang 4.2 */ + +#define SMALL_INTEGER_EXT 'a' +#define INTEGER_EXT 'b' +#define FLOAT_EXT 'c' +#define ATOM_EXT 'd' +#define SMALL_ATOM_EXT 's' +#define REFERENCE_EXT 'e' +#define NEW_REFERENCE_EXT 'r' +#define PORT_EXT 'f' +#define NEW_FLOAT_EXT 'F' +#define PID_EXT 'g' +#define SMALL_TUPLE_EXT 'h' +#define LARGE_TUPLE_EXT 'i' +#define NIL_EXT 'j' +#define STRING_EXT 'k' +#define LIST_EXT 'l' +#define BINARY_EXT 'm' +#define BIT_BINARY_EXT 'M' +#define SMALL_BIG_EXT 'n' +#define LARGE_BIG_EXT 'o' +#define NEW_FUN_EXT 'p' +#define EXPORT_EXT 'q' +#define FUN_EXT 'u' + +#define DIST_HEADER 'D' +#define ATOM_CACHE_REF 'R' +#define COMPRESSED 'P' + +#define PNAME "Erlang Distribution Protocol" +#define PSNAME "ErlDP" +#define PFNAME "erldp" + +static const value_string etf_tag_vals[] = { + { SMALL_INTEGER_EXT , "SMALL_INTEGER_EXT" }, + { INTEGER_EXT , "INTEGER_EXT" }, + { FLOAT_EXT , "FLOAT_EXT" }, + { ATOM_EXT , "ATOM_EXT" }, + { SMALL_ATOM_EXT , "SMALL_ATOM_EXT" }, + { REFERENCE_EXT , "REFERENCE_EXT" }, + { NEW_REFERENCE_EXT , "NEW_REFERENCE_EXT" }, + { PORT_EXT , "PORT_EXT" }, + { NEW_FLOAT_EXT , "NEW_FLOAT_EXT" }, + { PID_EXT , "PID_EXT" }, + { SMALL_TUPLE_EXT , "SMALL_TUPLE_EXT" }, + { LARGE_TUPLE_EXT , "LARGE_TUPLE_EXT" }, + { NIL_EXT , "NIL_EXT" }, + { STRING_EXT , "STRING_EXT" }, + { LIST_EXT , "LIST_EXT" }, + { BINARY_EXT , "BINARY_EXT" }, + { BIT_BINARY_EXT , "BIT_BINARY_EXT" }, + { SMALL_BIG_EXT , "SMALL_BIG_EXT" }, + { LARGE_BIG_EXT , "LARGE_BIG_EXT" }, + { NEW_FUN_EXT , "NEW_FUN_EXT" }, + { EXPORT_EXT , "EXPORT_EXT" }, + { FUN_EXT , "FUN_EXT" }, + { DIST_HEADER , "DIST_HEADER" }, + { ATOM_CACHE_REF , "ATOM_CACHE_REF" }, + { COMPRESSED , "COMPRESSED" }, + { 0, NULL } +}; + +static const value_string erldp_ctlmsg_vals[] = { + { 1, "LINK" }, + { 2, "SEND" }, + { 3, "EXIT" }, + { 4, "UNLINK" }, + { 5, "NODE_LINK" }, + { 6, "REG_SEND" }, + { 7, "GROUP_LEADER" }, + { 8, "EXIT2" }, + { 12, "SEND_TT" }, + { 13, "EXIT_TT" }, + { 16, "REG_SEND_TT" }, + { 18, "EXIT2_TT" }, + { 19, "MONITOR_P" }, + { 20, "DEMONITOR_P" }, + { 21, "MONITOR_P_EXIT" }, + { 0, NULL } +}; + +/* Initialize the protocol and registered fields */ +int proto_erldp = -1; +static int hf_erldp_length_2 = -1; +static int hf_erldp_length_4 = -1; +static int hf_erldp_tag = -1; +static int hf_erldp_tagd = -1; +static int hf_erldp_type = -1; +static int hf_erldp_version = -1; +static int hf_erldp_flags = -1; +static int hf_erldp_challenge = -1; +static int hf_erldp_digest = -1; +static int hf_erldp_name = -1; +static int hf_erldp_status = -1; + +static int hf_etf_tag = -1; + +/* Initialize the subtree pointers */ +static gint ett_erldp = -1; + +static gint ett_etf = -1; +static gint ett_etf_flags = -1; +static gint ett_etf_acrs = -1; +static gint ett_etf_acr = -1; +static gint ett_etf_tmp = -1; + +/* Preferences */ +static gboolean erldp_desegment = TRUE; + +/* Dissectors */ +static dissector_handle_t erldp_handle = NULL; + +/* Subdissectors */ +dissector_handle_t data_handle; + +/*--- External Term Format ---*/ + +static gint dissect_etf_type(const gchar *label, packet_info *pinfo, tvbuff_t *tvb, gint offset, proto_tree *tree); + +static gint dissect_etf_dist_header(packet_info *pinfo, tvbuff_t *tvb, gint offset, proto_tree *tree) { + guint8 num, flen, i, flg, isi; + gint flg_offset, acrs_offset, acr_offset; + guint32 atom_txt_len; + gboolean new_entry, long_atom; + proto_item *ti_acrs, *ti_acr, *ti_tmp; + proto_tree *flags_tree, *acrs_tree, *acr_tree; + const gchar *str; + + num = tvb_get_guint8(tvb, offset); + proto_tree_add_text(tree, tvb, offset, 1, "NumberOfAtomCacheRefs: %d", num); + offset++; + + if (num == 0) + return offset; + + flg_offset = offset; + flen = num / 2 + 1; + ti_tmp = proto_tree_add_text(tree, tvb, offset, flen, "Flags: %s", tvb_bytes_to_str(tvb, offset, flen)); + flags_tree = proto_item_add_subtree(ti_tmp, ett_etf_flags); + for (i=0; i<num; i++) { + flg = tvb_get_guint8(tvb, offset + i / 2); + proto_tree_add_text(flags_tree, tvb, offset + i / 2, 1, + decode_boolean_bitfield(flg, 0x08 << 4*(i%2), 8, + ep_strdup_printf("NewCacheEntryFlag[%2d]: SET", i), + ep_strdup_printf("NewCacheEntryFlag[%2d]: ---", i))); + proto_tree_add_text(flags_tree, tvb, offset + i / 2, 1, + decode_numeric_bitfield(flg, 0x07 << 4*(i%2), 8, ep_strdup_printf("SegmentIndex [%2d]: %%u", i))); + } + flg = tvb_get_guint8(tvb, offset + num / 2); + proto_tree_add_text(flags_tree, tvb, offset + num / 2, 1, + decode_boolean_bitfield(flg, 0x01 << 4*(num%2), 8, + "LongAtoms: YES", + "LongAtoms: NO")); + long_atom = flg & (0x01 << 4*(num%2)); + offset += flen; + + acrs_offset = offset; + ti_acrs = proto_tree_add_text(tree, tvb, offset, 0, "AtomCacheRefs"); + acrs_tree = proto_item_add_subtree(ti_acrs, ett_etf_acrs); + for (i=0; i<num; i++) { + flg = tvb_get_guint8(tvb, flg_offset + i / 2); + new_entry = flg & (0x08 << 4*(i%2)); + acr_offset = offset; + ti_acr = proto_tree_add_text(acrs_tree, tvb, offset, 0, "AtomCacheRef[%2d]:", i); + acr_tree = proto_item_add_subtree(ti_acr, ett_etf_acr); + isi = tvb_get_guint8(tvb, offset); + proto_tree_add_text(acr_tree, tvb, offset, 1, "InternalSegmentIndex: %d", isi); + proto_item_append_text(ti_acr, " %3d", isi); + offset++; + if (!new_entry) + continue; + atom_txt_len = (long_atom) ? tvb_get_ntohs(tvb, offset) : tvb_get_guint8(tvb, offset); + proto_tree_add_text(acr_tree, tvb, offset, (long_atom) ? 2 : 1, "Length: %d", atom_txt_len); + offset += (long_atom) ? 2 : 1; + str = tvb_get_ephemeral_string(tvb, offset, atom_txt_len); + proto_tree_add_text(acr_tree, tvb, offset, atom_txt_len, "AtomText: %s", str); + proto_item_append_text(ti_acr, " - '%s'", str); + offset += atom_txt_len; + proto_item_set_len(ti_acr, offset - acr_offset); + } + proto_item_set_len(ti_acrs, offset - acrs_offset); + + return offset; +} + +static gint dissect_etf_tuple_content(gboolean large, packet_info *pinfo, tvbuff_t *tvb, gint offset, proto_tree *tree, gchar **value_str) { + guint32 arity, i; + + arity = (large) ? tvb_get_ntohl(tvb, offset) : tvb_get_guint8(tvb, offset); + proto_tree_add_text(tree, tvb, offset, (large) ? 4 : 1, "Arity: %u", arity); + offset += (large) ? 4 : 1; + for (i=0; i<arity; i++) { + offset = dissect_etf_type(NULL, pinfo, tvb, offset, tree); + } + + return offset; +} + +static gint dissect_etf_type_content(guint8 tag, packet_info *pinfo, tvbuff_t *tvb, gint offset, proto_tree *tree, gchar **value_str) { + gint32 len, int_val, i; + + switch (tag) { + case DIST_HEADER: + offset = dissect_etf_dist_header(pinfo, tvb, offset, tree); + break; + + case ATOM_CACHE_REF: + int_val = tvb_get_guint8(tvb, offset); + proto_tree_add_text(tree, tvb, offset, 1, "AtomCacheReferenceIndex: %d", int_val); + offset += 1; + if (value_str) + *value_str = ep_strdup_printf("%d", int_val); + break; + + case SMALL_INTEGER_EXT: + int_val = tvb_get_guint8(tvb, offset); + proto_tree_add_text(tree, tvb, offset, 1, "Int: %d", int_val); + offset += 1; + if (value_str) + *value_str = ep_strdup_printf("%d", int_val); + break; + + case INTEGER_EXT: + int_val = tvb_get_ntohl(tvb, offset); + proto_tree_add_text(tree, tvb, offset, 4, "Int: %d", int_val); + offset += 4; + if (value_str) + *value_str = ep_strdup_printf("%d", int_val); + break; + + case PID_EXT: + offset = dissect_etf_type("Node", pinfo, tvb, offset, tree); + proto_tree_add_text(tree, tvb, offset, 4, "ID: 0x%08X", tvb_get_ntohl(tvb, offset)); + offset += 4; + proto_tree_add_text(tree, tvb, offset, 4, "Serial: %u", tvb_get_ntohl(tvb, offset)); + offset += 4; + proto_tree_add_text(tree, tvb, offset, 1, "Creation: %u", tvb_get_guint8(tvb, offset)); + offset++; + break; + + case SMALL_TUPLE_EXT: + offset = dissect_etf_tuple_content(FALSE, pinfo, tvb, offset, tree, value_str); + break; + + case LARGE_TUPLE_EXT: + offset = dissect_etf_tuple_content(TRUE, pinfo, tvb, offset, tree, value_str); + break; + + case NIL_EXT: + break; + + case LIST_EXT: + len = tvb_get_ntohl(tvb, offset); + proto_tree_add_text(tree, tvb, offset, 4, "Len: %d", len); + offset += 4; + for (i=0; i<len; i++) { + offset = dissect_etf_type(NULL, pinfo, tvb, offset, tree); + } + offset = dissect_etf_type("Tail", pinfo, tvb, offset, tree); + break; + + case NEW_REFERENCE_EXT: + len = tvb_get_ntohs(tvb, offset); + proto_tree_add_text(tree, tvb, offset, 2, "Len: %d", len); + offset += 2; + offset = dissect_etf_type("Node", pinfo, tvb, offset, tree); + proto_tree_add_text(tree, tvb, offset, 1, "Creation: %u", tvb_get_guint8(tvb, offset)); + offset++; + for (i=0; i<len; i++) { + proto_tree_add_text(tree, tvb, offset, 4, "ID[%d]: 0x%08X", i, tvb_get_ntohl(tvb, offset)); + offset += 4; + } + break; + } + + return offset; +} + +static gint dissect_etf_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, const gchar *label) { + gint offset = 0; + guint8 mag, tag; + proto_item *ti; + proto_tree *etf_tree; + + mag = tvb_get_guint8(tvb, offset); + if (mag != VERSION_MAGIC) { + return 0; + } + + ti = proto_tree_add_text(tree, tvb, offset, -1, (label) ? label : "External Term Format"); + etf_tree = proto_item_add_subtree(ti, ett_etf); + + proto_tree_add_text(etf_tree, tvb, offset, 1, "VERSION_MAGIC: %d", mag); + offset++; + + tag = tvb_get_guint8(tvb, offset); + proto_tree_add_item(etf_tree, hf_etf_tag, tvb, offset, 1, ENC_BIG_ENDIAN); + offset++; + + if (!label) + proto_item_set_text(ti, "%s", val_to_str(tag, VALS(etf_tag_vals), "unknown tag (%d)")); + + offset = dissect_etf_type_content(tag, pinfo, tvb, offset, etf_tree, NULL); + + proto_item_set_len(ti, offset); + + return offset; +} + +static gint dissect_etf_type(const gchar *label, packet_info *pinfo, tvbuff_t *tvb, gint offset, proto_tree *tree) { + gint begin = offset; + guint8 tag; + proto_item *ti; + proto_tree *etf_tree; + gchar *value_str = NULL; + + ti = proto_tree_add_text(tree, tvb, offset, -1, (label) ? label : "External Term Format"); + etf_tree = proto_item_add_subtree(ti, ett_etf); + + tag = tvb_get_guint8(tvb, offset); + proto_tree_add_item(etf_tree, hf_etf_tag, tvb, offset, 1, ENC_BIG_ENDIAN); + offset++; + + if (!label) + proto_item_set_text(ti, "%s", val_to_str(tag, VALS(etf_tag_vals), "unknown tag (%d)")); + + offset = dissect_etf_type_content(tag, pinfo, tvb, offset, etf_tree, &value_str); + if (value_str) + proto_item_append_text(ti, ": %s", value_str); + + proto_item_set_len(ti, offset - begin); + + return offset; +} + +static gboolean is_handshake(tvbuff_t *tvb, int offset) { + guint32 len = tvb_get_ntohs(tvb, offset); + guint8 tag = tvb_get_guint8(tvb, offset + 2); + return ((len > 0) && strchr("nras", tag) && (len == tvb_length_remaining(tvb, offset + 2))); +} + +/*--- dissect_erldp_handshake -------------------------------------------------*/ +static void dissect_erldp_handshake(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { + gint offset = 0; + guint8 tag; + gint i; + gboolean is_challenge = FALSE; + guint32 str_len; + const gchar *str; + + proto_tree_add_item(tree, hf_erldp_length_2, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + tag = tvb_get_guint8(tvb, offset); + proto_tree_add_item(tree, hf_erldp_tag, tvb, offset, 1, ENC_BIG_ENDIAN); + offset++; + + switch (tag) { + case 'n' : + proto_tree_add_item(tree, hf_erldp_version, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + proto_tree_add_item(tree, hf_erldp_flags, tvb, offset, 4, ENC_BIG_ENDIAN); + offset += 4; + if (tvb_bytes_exist(tvb, offset, 4)) { + for (i=0; i<4; i++) + if(!g_ascii_isprint(tvb_get_guint8(tvb, offset + i))) { + is_challenge = TRUE; + break; + } + } + if (is_challenge) { + proto_tree_add_item(tree, hf_erldp_challenge, tvb, offset, 4, ENC_BIG_ENDIAN); + offset += 4; + } + str_len = tvb_length_remaining(tvb, offset); + str = tvb_get_ephemeral_string(tvb, offset, str_len); + proto_tree_add_item(tree, hf_erldp_name, tvb, offset, str_len, ENC_NA); + col_add_fstr(pinfo->cinfo, COL_INFO, "%s %s", (is_challenge) ? "SEND_CHALLENGE" : "SEND_NAME", str); + break; + + case 'r' : + proto_tree_add_item(tree, hf_erldp_challenge, tvb, offset, 4, ENC_BIG_ENDIAN); + offset += 4; + proto_tree_add_item(tree, hf_erldp_digest, tvb, offset, 16, ENC_NA); + offset += 16; + col_add_str(pinfo->cinfo, COL_INFO, "SEND_CHALLENGE_REPLY"); + break; + + case 'a' : + proto_tree_add_item(tree, hf_erldp_digest, tvb, offset, 16, ENC_NA); + offset += 16; + col_add_str(pinfo->cinfo, COL_INFO, "SEND_CHALLENGE_ACK"); + break; + + case 's' : + str_len = tvb_length_remaining(tvb, offset); + str = tvb_get_ephemeral_string(tvb, offset, str_len); + proto_tree_add_item(tree, hf_erldp_status, tvb, offset, str_len, ENC_NA); + col_add_fstr(pinfo->cinfo, COL_INFO, "SEND_STATUS %s", str); + break; + } +} + +/*--- dissect_erldp_pdu -------------------------------------------------*/ +static void dissect_erldp_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { + gint offset; + guint32 msg_len; + guint8 type, ctl_op; + proto_tree *erldp_tree; + proto_item *ti; + tvbuff_t *next_tvb = NULL; + + col_set_str(pinfo->cinfo, COL_PROTOCOL, PSNAME); + + ti = proto_tree_add_item(tree, proto_erldp, tvb, 0, -1, FALSE); + erldp_tree = proto_item_add_subtree(ti, ett_erldp); + + if (is_handshake(tvb, 0)) { + dissect_erldp_handshake(tvb, pinfo, erldp_tree); + return; + } + + offset = 0; + + msg_len = tvb_get_ntohl(tvb, offset); + proto_tree_add_item(erldp_tree, hf_erldp_length_4, tvb, offset, 4, ENC_BIG_ENDIAN); + offset += 4; + + if (msg_len == 0) { + col_add_str(pinfo->cinfo, COL_INFO, "KEEP_ALIVE"); + return; + } + + type = tvb_get_guint8(tvb, offset); + switch (type) { + case ERL_PASS_THROUGH: + proto_tree_add_item(erldp_tree, hf_erldp_type, tvb, offset, 1, ENC_BIG_ENDIAN); + offset++; + break; + + case VERSION_MAGIC: + next_tvb = tvb_new_subset(tvb, offset, -1, 4 + msg_len - offset); + offset += dissect_etf_pdu(next_tvb, pinfo, erldp_tree, "DistributionHeader"); + if ((tvb_get_guint8(tvb, offset) == SMALL_TUPLE_EXT) && (tvb_get_guint8(tvb, offset + 2) == SMALL_INTEGER_EXT)) { + ctl_op = tvb_get_guint8(tvb, offset + 3); + col_add_str(pinfo->cinfo, COL_INFO, val_to_str(ctl_op, VALS(erldp_ctlmsg_vals), "unknown ControlMessage operation (%d)")); + } + offset = dissect_etf_type("ControlMessage", pinfo, tvb, offset, erldp_tree); + if (tvb_length_remaining(tvb, offset) > 0) + offset = dissect_etf_type("Message", pinfo, tvb, offset, erldp_tree); + break; + + default: + proto_tree_add_item(erldp_tree, hf_erldp_type, tvb, offset, 1, ENC_BIG_ENDIAN); + offset++; + col_add_str(pinfo->cinfo, COL_INFO, "unknown header format"); + return; + } +} + +/*--- get_erldp_pdu_len -------------------------------------------------*/ +static guint get_erldp_pdu_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset) { + return (is_handshake(tvb, offset)) ? 2 + tvb_get_ntohs(tvb, offset) : 4 + tvb_get_ntohl(tvb, offset); +} + +/*--- dissect_erldp -------------------------------------------------*/ +static void +dissect_erldp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { + tcp_dissect_pdus(tvb, pinfo, tree, + erldp_desegment, /* desegment or not */ + 4, /* fixed-length part of the PDU */ + get_erldp_pdu_len, /* routine to get the length of the PDU */ + dissect_erldp_pdu); /* routine to dissect a PDU */ +} + +/*--- proto_register_erldp ----------------------------------------------*/ +void proto_register_erldp(void) { + /* module_t *erldp_module; */ + + /* List of fields */ + static hf_register_info hf[] = { + /*--- Handshake fields ---*/ + { &hf_erldp_length_2, { "Length", "erldp.len", + FT_UINT16, BASE_DEC, NULL, 0x0, + "Message Length", HFILL}}, + { &hf_erldp_tag, { "Tag", "erldp.tag", + FT_STRING, BASE_NONE, NULL, 0x0, + "Tag", HFILL}}, + { &hf_erldp_tagd, { "Tag", "erldp.tag", + FT_UINT8, BASE_DEC, NULL, 0x0, + "Tag", HFILL}}, + { &hf_erldp_type, { "Type", "erldp.type", + FT_UINT8, BASE_DEC, NULL, 0x0, + "Type", HFILL}}, + { &hf_erldp_version, { "Version", "erldp.version", + FT_UINT16, BASE_DEC, VALS(epmd_version_vals), 0x0, + "Version", HFILL}}, + { &hf_erldp_flags, { "Flags", "erldp.flags", + FT_UINT32, BASE_HEX, NULL, 0x0, + "Flags", HFILL}}, + { &hf_erldp_challenge, { "Challenge", "erldp.challenge", + FT_UINT32, BASE_HEX, NULL, 0x0, + "Challenge", HFILL}}, + { &hf_erldp_digest, { "Digest", "erldp.digest", + FT_BYTES, BASE_NONE, NULL, 0x0, + "Digest", HFILL}}, + { &hf_erldp_name, { "Name", "erldp.name", + FT_STRING, BASE_NONE, NULL, 0x0, + "Name", HFILL}}, + { &hf_erldp_status, { "Status", "erldp.status", + FT_STRING, BASE_NONE, NULL, 0x0, + "Status", HFILL}}, + /*--- ---*/ + { &hf_erldp_length_4, { "Length", "erldp.len", + FT_UINT32, BASE_DEC, NULL, 0x0, + "Message Length", HFILL}}, + + /*--- ETF ---*/ + { &hf_etf_tag, { "Tag", "etf.tag", + FT_UINT8, BASE_DEC, VALS(etf_tag_vals), 0x0, + "Tag", HFILL}}, + }; + + /* List of subtrees */ + static gint *ett[] = { + &ett_erldp, + &ett_etf, + &ett_etf_flags, + &ett_etf_acrs, + &ett_etf_acr, + &ett_etf_tmp, + }; + + /* Register protocol and dissector */ + proto_erldp = proto_register_protocol(PNAME, PSNAME, PFNAME); + register_dissector(PFNAME, dissect_erldp, proto_erldp); + erldp_handle = find_dissector(PFNAME); + + /* Register fields and subtrees */ + proto_register_field_array(proto_erldp, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); + +} + +/*--- proto_reg_handoff_erldp -------------------------------------------*/ +void proto_reg_handoff_erldp(void) { + + dissector_add_handle("tcp.port", erldp_handle); + + data_handle = find_dissector("data"); +} |