Let’s step through adding a basic dissector. We’ll start with the made up "foo" protocol. It consists of the following basic items.
The first decision you need to make is if this dissector will be a built-in dissector and included in the main program, or a plugin.
Plugins are easier to write initially, so let’s start with that. With a little care, the plugin can be converted into a built-in dissector.
Dissector Initialisation.
#include "config.h" #include <epan/packet.h> #define FOO_PORT 1234 static int proto_foo; void proto_register_foo(void) { proto_foo = proto_register_protocol ( "FOO Protocol", /* name */ "FOO", /* short name */ "foo" /* filter_name */ ); }
Let’s go through this a bit at a time. First we have some boilerplate include files. These will be pretty constant to start with.
Then a #define
for the UDP port that carries foo traffic.
Next we have proto_foo
, an int that stores our protocol handle and is
initialised to -1
.
This handle will be set when the dissector is registered within the main program.
It’s good practice to make all variables and functions that aren’t exported
static to minimize name space pollution. This normally isn’t a problem unless your
dissector gets so big that it spans multiple files.
Now that we have the basics in place to interact with the main program, we’ll
start with two protocol dissector setup functions: proto_register_XXX
and
proto_reg_handoff_XXX
.
Each protocol must have a register function with the form "proto_register_XXX".
This function is used to register the protocol in Wireshark.
The code to call the register routines is generated automatically and is
called when Wireshark starts. In this example, the function is named
proto_register_foo
.
proto_register_foo
calls proto_register_protocol()
, which takes a name
,
short name
, and filter_name
. The
name and short name are used in the "Preferences" and "Enabled protocols"
dialogs and the documentation’s generated field name list. The
filter_name
is used as the display filter name. proto_register_protocol()
returns a protocol handle, which can be used to refer to the protocol and
obtain a handle to the protocol’s dissector.
Next we need a handoff routine.
Dissector Handoff.
void proto_reg_handoff_foo(void) { static dissector_handle_t foo_handle; foo_handle = create_dissector_handle(dissect_foo, proto_foo); dissector_add_uint("udp.port", FOO_PORT, foo_handle); }
A handoff routine associates a protocol handler with the protocol’s traffic. It consists of two major steps: The first step is to create a dissector handle, which is a handle associated with the protocol and the function called to do the actual dissecting. The second step is to register the dissector handle so that traffic associated with the protocol calls the dissector.
In this example, proto_reg_handoff_foo()
calls create_dissector_handle()
to obtain a dissector handle for the foo protocol. It then uses
dissector_add_uint()
to associate traffic on UDP port FOO_PORT (1234)
with the foo protocol, so that Wireshark will call dissect_foo()
when
it receives UDP traffic on port 1234.
Wireshark’s dissector convention is to put proto_register_foo()
and
proto_reg_handoff_foo()
as the last two functions in the dissector source.
The next step is to write the dissecting function, dissect_foo()
.
We’ll start with a basic placeholder.
Dissection.
static int dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO"); /* Clear the info column */ col_clear(pinfo->cinfo,COL_INFO); return tvb_captured_length(tvb); }
dissect_foo()
is called to dissect the packets presented to it. The packet data
is held in a special buffer referenced here as tvb
. The packet_info structure
contains general data about the protocol and we can update
information here. The tree parameter is where the detail dissection takes place.
Note that the _U_
following tree
and data
signals to the compiler that the
parameters are unused, so that the compiler does not print a warning.
For now we’ll do the minimum we can get away with. col_set_str()
is used to set
Wireshark’s Protocol column to "FOO" so everyone can see it’s being
recognised. The
only other thing we do is to clear out any data in the INFO column if it’s being
displayed.
At this point we have a basic dissector ready to compile and install. The dissector doesn’t do anything other than identify the protocol and label it. Here is the dissector’s complete code:
Complete packet-foo.c:.
#include "config.h" #include <epan/packet.h> #define FOO_PORT 1234 static int proto_foo; static int dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO"); /* Clear the info column */ col_clear(pinfo->cinfo,COL_INFO); return tvb_captured_length(tvb); } void proto_register_foo(void) { proto_foo = proto_register_protocol ( "FOO Protocol", /* name */ "FOO", /* short_name */ "foo" /* filter_name */ ); } void proto_reg_handoff_foo(void) { static dissector_handle_t foo_handle; foo_handle = create_dissector_handle(dissect_foo, proto_foo); dissector_add_uint("udp.port", FOO_PORT, foo_handle); }
To compile this dissector and create a plugin a few support files are required, besides the dissector source in packet-foo.c:
Samples of these files are available in the gryphon plugin directory (plugins/epan/gryphon). If you copy the files from the gryphon plugin, CMakeLists.txt will need to be updated with the correct plugin name, version info, and the relevant files to compile.
In the main top-level source directory, copy CMakeListsCustom.txt.example to
CMakeListsCustom.txt and add the path of your plugin to the list in
CUSTOM_PLUGIN_SRC_DIR
.
Compile the dissector to a DLL or shared library and either run Wireshark from the build directory as detailed in Section 3.7, “Run Your Version Of Wireshark” or copy the plugin binary into the plugin directory of your Wireshark installation and run that.
Now that we have our basic dissector up and running, let’s do something with it. The simplest thing to start with is labeling the payload. We can label the payload by building a subtree to decode our results into. This subtree will hold all the protocol’s details and helps keep things looking nice in the detailed display.
We add the new subtree with proto_tree_add_item()
, as is depicted below:
Plugin Packet Dissection.
static int dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO"); /* Clear out stuff in the info column */ col_clear(pinfo->cinfo,COL_INFO); proto_item *ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA); return tvb_captured_length(tvb); }
As the FOO
protocol does not encapsulate another protocol, we
consume all of the tvb’s data, from 0
to the end (-1
).
The final parameter specifies the "encoding" and is set to
ENC_NA
("not applicable"), as the protocol doesn’t specifically
use big endian (ENC_BIG_ENDIAN
) or little endian (ENC_LITTLE_ENDIAN
).
After adding the call to
proto_tree_add_item()
, there should be a label FOO
in the protocol’s detailed display.
Selecting this label will highlight the remaining contents of the packet.
Now let’s go to the next step and add some protocol dissection. To do this
we’ll need to construct tables to define which fields will be present in the
packet and to store the opened/closed state of the subtree. We’ll
add these statically allocated arrays to the beginning of the file
and name them
hf_register_info
('hf' is short for 'header field') and ett
.
The arrays wil then registered after the call to
proto_register_protocol()
by calling proto_register_field_array()
and proto_register_subtree_array()
:
Registering data structures.
static int hf_foo_pdu_type; static int ett_foo; /* ... */ void proto_register_foo(void) { static hf_register_info hf[] = { { &hf_foo_pdu_type, { "FOO PDU Type", "foo.type", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } } }; /* Setup protocol subtree array */ static int *ett[] = { &ett_foo }; proto_foo = proto_register_protocol ( "FOO Protocol", /* name */ "FOO", /* short_name */ "foo" /* filter_name*/ ); proto_register_field_array(proto_foo, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); }
As you can see, a field foo.type
was defined inside the array of
header fields.
Now we can dissect the FOO PDU Type
(referenced as foo.type
)
field in dissect_foo()
by adding
the FOO Protocol’s subtree with proto_item_add_subtree()
and
then calling proto_tree_add_item()
to add the field:
Dissector starting to dissect the packets.
proto_item *ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA); proto_tree *foo_tree = proto_item_add_subtree(ti, ett_foo); proto_tree_add_item(foo_tree, hf_foo_pdu_type, tvb, 0, 1, ENC_BIG_ENDIAN);
As mentioned earlier, the foo protocol begins with an 8-bit packet type
which can have three possible values: 1 - initialisation, 2 - terminate, 3 - data.
Here’s how we can add the packet details:
The proto_item_add_subtree()
call has added a child node
to the protocol tree which is where we will do our detail dissection.
The expansion of this node is controlled by the ett_foo
variable. This remembers if the node should be expanded or not as you move
between packets. All subsequent dissection will be added to this tree,
as you can see from the next call.
A call to proto_tree_add_item()
in the foo_tree,
this time using the hf_foo_pdu_type
to control the formatting
of the item. The pdu type is one byte of data, starting at 0. We assume it is
in network order (also called big endian), so that is why we use ENC_BIG_ENDIAN
.
For a 1-byte quantity, there is no order issue, but it is good practice to
make this the same as any multibyte fields that may be present, and as we will
see in the next section, this particular protocol uses network order.
If we look in detail at the hf_foo_pdu_type
declaration in
the static array we can see the details of the definition.
static hf_register_info hf[] = { { &hf_foo_pdu_type, { "FOO PDU Type", "foo.type", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } } };
foo.type=1
).
We’ll ignore the rest of the structure for now.
If you install this plugin and try it out, you’ll see something that begins to look useful.
Now let’s finish off dissecting the simple protocol. We need to add a few more variables to the hfarray, and a couple more procedure calls.
Wrapping up the packet dissection.
... static int hf_foo_flags; static int hf_foo_sequenceno; static int hf_foo_initialip; ... static int dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { int offset = 0; ... proto_item *ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA); proto_tree *foo_tree = proto_item_add_subtree(ti, ett_foo); proto_tree_add_item(foo_tree, hf_foo_pdu_type, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; proto_tree_add_item(foo_tree, hf_foo_flags, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; proto_tree_add_item(foo_tree, hf_foo_sequenceno, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; proto_tree_add_item(foo_tree, hf_foo_initialip, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; ... return tvb_captured_length(tvb); } void proto_register_foo(void) { ... ... { &hf_foo_flags, { "FOO PDU Flags", "foo.flags", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_foo_sequenceno, { "FOO PDU Sequence Number", "foo.seqn", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_foo_initialip, { "FOO PDU Initial IP", "foo.initialip", FT_IPv4, BASE_NONE, NULL, 0x0, NULL, HFILL } }, ... ... } ...
This dissects all the bits of this simple hypothetical protocol. We’ve introduced a new variable offsetinto the mix to help keep track of where we are in the packet dissection. With these extra bits in place, the whole protocol is now dissected.
We can certainly improve the display of the protocol with a bit of extra data. The first step is to add some text labels. Let’s start by labeling the packet types. There is some useful support for this sort of thing by adding a couple of extra things. First we add a simple table of type to name.
Naming the packet types.
static const value_string packettypenames[] = { { 1, "Initialise" }, { 2, "Terminate" }, { 3, "Data" }, { 0, NULL } };
This is a handy data structure that can be used to look up a name for a value.
There are routines to directly access this lookup table, but we don’t need to
do that, as the support code already has that added in. We just have to give
these details to the appropriate part of the data, using the VALS
macro.
Adding Names to the protocol.
{ &hf_foo_pdu_type, { "FOO PDU Type", "foo.type", FT_UINT8, BASE_DEC, VALS(packettypenames), 0x0, NULL, HFILL } }
This helps in deciphering the packets, and we can do a similar thing for the flags structure. For this we need to add some more data to the table though.
Adding Flags to the protocol.
#define FOO_START_FLAG 0x01 #define FOO_END_FLAG 0x02 #define FOO_PRIORITY_FLAG 0x04 static int hf_foo_startflag; static int hf_foo_endflag; static int hf_foo_priorityflag; static int dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { ... ... static int* const bits[] = { &hf_foo_startflag, &hf_foo_endflag, &hf_foo_priorityflag, NULL }; proto_tree_add_bitmask(foo_tree, tvb, offset, hf_foo_flags, ett_foo, bits, ENC_BIG_ENDIAN); offset += 1; ... ... return tvb_captured_length(tvb); } void proto_register_foo(void) { ... ... { &hf_foo_startflag, { "FOO PDU Start Flags", "foo.flags.start", FT_BOOLEAN, 8, NULL, FOO_START_FLAG, NULL, HFILL } }, { &hf_foo_endflag, { "FOO PDU End Flags", "foo.flags.end", FT_BOOLEAN, 8, NULL, FOO_END_FLAG, NULL, HFILL } }, { &hf_foo_priorityflag, { "FOO PDU Priority Flags", "foo.flags.priority", FT_BOOLEAN, 8, NULL, FOO_PRIORITY_FLAG, NULL, HFILL } }, ... ... } ...
Some things to note here. For the flags, as each bit is a different flag, we use
the type FT_BOOLEAN
, as the flag is either on or off. Second, we include the flag
mask in the 7th field of the data, which allows the system to mask the relevant bit.
We’ve also changed the 5th field to 8, to indicate that we are looking at an 8 bit
quantity when the flags are extracted. Then finally we add the extra constructs
to the dissection routine.
This is starting to look fairly full featured now, but there are a couple of
other things we can do to make things look even more pretty. At the moment our
dissection shows the packets as "Foo Protocol" which whilst correct is a little
uninformative. We can enhance this by adding a little more detail. First, let’s
get hold of the actual value of the protocol type. We can use the handy function
tvb_get_uint8()
to do this. With this value in hand, there are a couple of
things we can do. First we can set the INFO column of the non-detailed view to
show what sort of PDU it is - which is extremely helpful when looking at
protocol traces. Second, we can also display this information in the dissection
window.
Enhancing the display.
static int dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { int offset = 0; uint8_t packet_type = tvb_get_uint8(tvb, 0); col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO"); /* Clear out stuff in the info column */ col_clear(pinfo->cinfo,COL_INFO); col_add_fstr(pinfo->cinfo, COL_INFO, "Type %s", val_to_str(packet_type, packettypenames, "Unknown (0x%02x)")); proto_item *ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA); proto_item_append_text(ti, ", Type %s", val_to_str(packet_type, packettypenames, "Unknown (0x%02x)")); proto_tree *foo_tree = proto_item_add_subtree(ti, ett_foo); proto_tree_add_item(foo_tree, hf_foo_pdu_type, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; return tvb_captured_length(tvb); }
So here, after grabbing the value of the first 8 bits, we use it with one of the
built-in utility routines val_to_str()
, to lookup the value. If the value
isn’t found we provide a fallback which just prints the value in hex. We use
this twice, once in the INFO field of the columns — if it’s displayed, and
similarly we append this data to the base of our dissecting tree.