from Julian Onions: add a devel section about start writing a dissector

some editing by me ...

svn path=/trunk/; revision=14570
This commit is contained in:
Ulf Lamping 2005-06-06 20:32:08 +00:00
parent fa91e9559d
commit 3acf278bf5
1 changed files with 638 additions and 3 deletions

View File

@ -7,15 +7,650 @@
<section id="ChDissectWorks">
<title>How it works</title>
<para>
XXX - well, how does it work?
Each dissector decodes it's part of the protocol, and then hand off
decoding to subsequent dissectors for an encapsulated protocol.
</para>
<para>
So it might all start with a Frame dissector which dissects the packet details
of the capture file itself (e.g. timestamps), passes the data on to an
Ethernet frame dissector that decodes the Ethernet header,
and then passes the payload to the next dissector (e.g. IP) and so on.
At each stage, details of the packet will be decoded and displayed.
</para>
<para>
Dissection can be implemented in two possible ways. One is to have a dissector
module compiled into the main program, which means its always available.
Another way is to make a plugin (a shared library/DLL) that registers itself
to handle dissection. - XXX add a special explanation section for this?
</para>
</section>
<section id="ChDissectAdd">
<title>Adding a basic dissector</title>
<para>
The steps to add a new dissector (including some skeleton code) can be
found in the file doc/README.developer.
Lets step through adding a basic dissector. We'll start with the made up
"foo" protocol. It consists of the following basic items.
</para>
<itemizedlist>
<listitem><para>
A packet type - 8 bits, possible values: 1 - initialisation, 2 - terminate, 3 - data.
</para></listitem>
<listitem><para>
A set of flags stored in 8 bits, 0x01 - start packet, 0x02 - end packet, 0x04 - priority packet.
</para></listitem>
<listitem><para>
A sequence number - 16 bits.
</para></listitem>
<listitem><para>
An IP address.
</para></listitem>
</itemizedlist>
<section id="ChDissectSetup">
<title>Setting up the dissector</title>
<para>
The first decision you need to make is if this dissector will be a
built in dissector, included in the main program, or a plugin.
</para>
<para>
Plugins are the easiest to write initially as they don't need
write permission on the main code base. So lets start with that.
With a little care, the plugin can be made to run as a built in
easily too - so we haven't lost anything.
</para>
<example><title>Basic Plugin setup.</title>
<programlisting>
<![CDATA[
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <gmodule.h>
#include <epan/packet.h>
#include <epan/prefs.h>
/* forward reference */
void proto_register_foo();
void proto_reg_handoff_foo();
void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
/* Define version if we are not building ethereal statically */
#ifndef ENABLE_STATIC
G_MODULE_EXPORT const gchar version[] = "0.0";
#endif
static int proto_foo = -1;
static int global_foo_port = 1234;
static dissector_handle_t foo_handle;
#ifndef ENABLE_STATIC
G_MODULE_EXPORT void
plugin_register(void)
{
/* register the new protocol, protocol fields, and subtrees */
if (proto_foo == -1) { /* execute protocol initialization only once */
proto_register_foo();
}
}
G_MODULE_EXPORT void
plugin_reg_handoff(void){
proto_reg_handoff_foo();
}
#endif]]>
</programlisting>
</example>
<para>
Lets go through this a bit at a time. First we have some boiler plate
include files. These will be pretty constant to start with. Here we also
pre-declare some functions that we'll be writing shortly.
</para>
<para>
Next we have a section surrounded by #ifdef ENABLE_STATIC. This is what
makes this a plugin rather than a built in dissector.
</para>
<para>
The version is a simple string that is used to report on the version of this
dissector. You should increase this number each time you make changes that you
need to keep track of.
</para>
<para>
Next we have an int that is initialised to -1 that records our protocol.
This will get updated when we register this plugin with the main program.
We can use this as a handy way to detect if we've been initalised yet.
Its good practice to make all variables and functions that aren't exported
static to keep name space pollution. Normally this isn't a problem unless your
dissector gets so big it has to span multiple files.
</para>
<para>
Then a global variable which contains the UDP port that we'll assume we are dissecting traffic for.
</para>
<para>
Next a dissector reference that we'll initialise later.
</para>
<para>
Next, the first plugin entry point. The function plugin_register() is called
when the plugin is loaded and allows you to do some initialisation stuff,
which will include communicating with the main program what you're plugins
capabilities are.
</para>
<para>
The plugin_reg_handoff routine is used when dissecting sub protocols. As our
hyperthetical protocol will be hyperthetically carried over UDP then we will
need to do this.
</para>
<para>
Now we have the basics in place to interact with the main program, we had
better fill in those missing functions. Lets start with register function.
</para>
<example><title>Plugin Initialisation.</title>
<programlisting>
<![CDATA[
void
proto_register_foo(void)
{
module_t *foo_module;
if (proto_foo == -1) {
proto_foo = proto_register_protocol (
"FOO Protocol", /* name */
"FOO", /* short name */
"foo" /* abbrev */
);
}
foo_module = prefs_register_protocol(proto_foo, proto_reg_handoff_foo);
}]]>
</programlisting></example>
<para>
First a call to proto_register_protocol that
registers the protocol. We can give it three names that
will be used in various places to display it.
- XXX explain where, this can be confusing
</para>
<para>
Then we call the preference register function. At the moment we have
no specific protocol preferences so this will be all that we need.
This takes a function parameter which is our handoff function.
I guess we'd better write that next.
</para>
<example><title>Plugin Handoff.</title>
<programlisting>
<![CDATA[
void
proto_reg_handoff_foo(void)
{
static int Initialized=FALSE;
if (!Initialized) {
foo_handle = create_dissector_handle(dissect_foo, proto_foo);
dissector_add("udp.port", global_foo_port, foo_handle);
}
}]]>
</programlisting></example>
<para>
Whats happening here? We are initialising the dissector if it hasn't
been initialised yet.
First we create the dissector. This registers a routine
to be called to do the actual dissecting.
Then we associate it with a udp port number
so that the main program will know to call us when it gets UDP traffic on that port.
</para>
<para>
Now at last we finaly get to write some dissecting code. For the moment we'll
leave it as a basic placeholder.
</para>
<example><title>Plugin Dissection.</title>
<programlisting>
<![CDATA[
static void
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
if (check_col(pinfo->cinfo, COL_PROTOCOL))
col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
/* Clear out stuff in the info column */
if(check_col(pinfo->cinfo,COL_INFO)){
col_clear(pinfo->cinfo,COL_INFO);
}
}]]>
</programlisting></example>
<para>
This function is called to dissect the packets presented to it.
The packet data is held in a special buffer referenced here as tvb.
We shall become fairly familiar with this as we get deeper into the details
of the protocol.
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.
</para>
<para>
For now we'll do the minimum we can get away with.
The first two lines check to see if the Protocol column is being displayed in
the UI. If it is, we set the text of this to our protocol, so everyone
can see its been recognised.
The only other thing we do is to clear out any data in the INFO column
if its being displayed.
</para>
<para>
At this point we should have a basic dissector ready to compile and install.
It doesn't do much at present, than identify the protcol and label it.
Compile the dissector to a dll or shared library, and copy it into the plugin
directory of the installation. To finish this off a Makefile of some sort will be
required. A Makefile.nmake for Windows platforms and a Makefile.am for unix/linux
types.
</para>
<example><title>Makefile.nmake for Windows.</title>
<programlisting>
<![CDATA[
include ..\..\config.nmake
############### no need to modify below this line #########
CFLAGS=/DHAVE_CONFIG_H /I../.. /I../../wiretap $(GLIB_CFLAGS) \
/I$(PCAP_DIR)\include -D_U_="" $(LOCAL_CFLAGS)
LDFLAGS = /NOLOGO /INCREMENTAL:no /MACHINE:I386 $(LOCAL_LDFLAGS)
!IFDEF ENABLE_LIBETHEREAL
LINK_PLUGIN_WITH=..\..\epan\libethereal.lib
CFLAGS=/DHAVE_WIN32_LIBETHEREAL_LIB /D_NEED_VAR_IMPORT_ $(CFLAGS)
OBJECTS=foo.obj
foo.dll foo.exp foo.lib : $(OBJECTS) $(LINK_PLUGIN_WITH)
link -dll /out:foo.dll $(LDFLAGS) $(OBJECTS) $(LINK_PLUGIN_WITH) \
$(GLIB_LIBS)
!ENDIF
clean:
rm -f $(OBJECTS) foo.dll foo.exp foo.lib *.pdb
distclean: clean
maintainer-clean: distclean]]>
</programlisting></example>
<example><title>Makefile.am for unix/linux.</title>
<programlisting>
<![CDATA[
INCLUDES = -I$(top_srcdir)
plugindir = @plugindir@
plugin_LTLIBRARIES = foo.la
foo_la_SOURCES = foo.c moduleinfo.h
foo_la_LDFLAGS = -module -avoid-version
foo_la_LIBADD = @PLUGIN_LIBS@
# Libs must be cleared, or else libtool won't create a shared module.
# If your module needs to be linked against any particular libraries,
# add them here.
LIBS =
CLEANFILES = \
foo \
*~
EXTRA_DIST = \
Makefile.nmake
]]>
</programlisting></example>
</section>
<section id="ChDissectDetails">
<title>Dissecting the details of the protocol</title>
<para>
Now we have our basic dissector up and running, lets do something with it.
The simplest thing to do to start with is to just label the payload.
This will allow us to set up some of the parts we will need.
</para>
<para>
The first thig we will do is to build a subtree to decode our results into.
This helps to keep things looking nice in the detailed display.
Now the dissector is called in two different cases. In one case
it is called to get a summary of the packet, in the other case it is
called to look into details of the packet. These two cases can be
distinguished by the tree pointer. If the tree pointer is NULL, then
we are being asked for a summary. If it is non null, we can pick apart
the protocol for display. So with that in mind, lets enhance our dissector.
</para>
<example><title>Plugin Packet Dissection.</title>
<programlisting>
<![CDATA[
static void
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
if (check_col(pinfo->cinfo, COL_PROTOCOL))
col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
/* Clear out stuff in the info column */
if(check_col(pinfo->cinfo,COL_INFO)){
col_clear(pinfo->cinfo,COL_INFO);
}
if (tree) { /* we are being asked for details */
proto_item *ti = NULL;
ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, FALSE);
}
}]]>
</programlisting></example>
<para>
What we're doing here is adding a subtree to the dissection.
This subtree will hold all the details of this protocol and so not clutter
up the display when not required.
</para>
<para>
We are also marking the area of data that is being consumed by this
protocol. In our cases its all that has been passed to us, as we're assuming
this protocol does not encapsulate another.
Therefore, we add the new tree node with proto_tree_add_item, adding
it to the passed in tree, label it with the protocol, use the passed in
tvb buffer as the data, and consume from 0 to the end (-1) of this data.
The FALSE we'll ignore for now.
</para>
<para>
After this change, there should be a label in the detailed display for the protocol,
and selecting this will highlight the remaining contents of the packet.
</para>
<para>
Now lets go to the next step and add some protocol disection.
For this step we'll need to construct a couple of tables that help with dissection.
This needs some changes to proto_register_foo. First a couple of statically
declare arrays.
</para>
<example><title>Plugin Registering data structures.</title>
<programlisting>
<![CDATA[
static hf_register_info hf[] = {
{ &hf_foo_pdu_type,
{ "FOO PDU Type", "foo.type",
FT_UINT8, BASE_DEC, NULL, 0x0,
"", HFILL }
},
};
/* Setup protocol subtree array */
static gint *ett[] = {
&ett_foo,
};
]]>
</programlisting></example>
<para>
Then, after the registration code, we register these arrays.
</para>
<example><title>Plugin Registering data structures.</title>
<programlisting>
<![CDATA[
proto_register_field_array(proto_foo, hf, array_length(hf));
proto_register_subtree_array(ett, array_length(ett));
]]>
</programlisting></example>
<para>
The variables hf_foo_pdu_type and ett_foo also need to be declared
somewhere near the top of the file.
</para>
<example><title>Plugin data structure globals.</title>
<programlisting>
<![CDATA[
static int hf_foo_pdu_type = -1;
static gint ett_foo = -1;
]]>
</programlisting></example>
<para>
Now we can enhance the protocol display with some detail.
</para>
<example><title>Plugin starting to dissect the packets.</title>
<programlisting>
<![CDATA[
if (tree) { /* we are being asked for details */
proto_item *ti = NULL;
proto_tree *foo_tree = NULL;
ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, FALSE);
foo_tree = proto_item_add_subtree(ti, ett_foo);
proto_tree_add_item(foo_tree, hf_foo_pdu_type, tvb, 0, 1, FALSE);
}
]]>
</programlisting></example>
<para>
Now the dissection is starting to look more interesting. We have picked apart
our first bit of the protocol. One byte of data at the start of the packet
that defines the packet type for foo protocol.
</para>
<para>
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,
so that is why we use FALSE. Although for 1 byte there is no order issues
its best to keep right.
</para>
<para>
If we look in detail at the hf_foo_pdu_type declaration in the static array
we can see the details of the definition.
</para>
<itemizedlist>
<listitem><para>
hf_foo_pdu_type - the index for this node.
</para></listitem>
<listitem><para>
FOO PDU Type - the label for this item.
</para></listitem>
<listitem><para>
foo.type - this is the filter string. It enables us to type constructs such
as foo.type=1 into the filter box.
</para></listitem>
<listitem><para>
FT_UNIT8 - this specifies this item is an 8bit unsigned integer.
This tallies with our call above where we tell it to only look at one byte.
</para></listitem>
<listitem><para>
BASE_DEC - for an integer type, this tells it to be printed as a decimal
number. It could be BASE_HEX or BASE_OCT if that made more sense.
</para></listitem>
</itemizedlist>
<para>
We'll ignore the rest of the structure for now.
</para>
<para>
If you install this plugin and try it out, you'll see something that begins to look
useful.
</para>
<para>
Now lets finish off dissecting the simple protocol. We need to add a few
more variables to the hf array, and a couple more procedure calls.
</para>
<example><title>Plugin wrapping up the packet dissection.</title>
<programlisting>
<![CDATA[
static int hf_foo_flags = -1;
static int hf_foo_sequenceno = -1;
static int hf_foo_initialip = -1;
...
{ &hf_foo_flags,
{ "FOO PDU Flags", "foo.flags",
FT_UINT8, BASE_HEX, NULL, 0x0,
"", HFILL }
},
{ &hf_foo_sequenceno,
{ "FOO PDU Sequence Number", "foo.seqn",
FT_UINT16, BASE_DEC, NULL, 0x0,
"", HFILL }
},
{ &hf_foo_initialip,
{ "FOO PDU Initial IP", "foo.initialip",
FT_IPv4, BASE_NONE, NULL, 0x0,
"", HFILL }
},
...
gint offset = 0;
ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, FALSE);
foo_tree = proto_item_add_subtree(ti, ett_foo);
proto_tree_add_item(foo_tree, hf_foo_pdu_type, tvb, offset, 1, FALSE); offset += 1;
proto_tree_add_item(foo_tree, hf_foo_flags, tvb, offset, 1, FALSE); offset += 1;
proto_tree_add_item(foo_tree, hf_foo_sequenceno, tvb, offset, 2, FALSE); offset += 2;
proto_tree_add_item(foo_tree, hf_foo_initialip, tvb, offset, 4, FALSE); offset += 4;
]]>
</programlisting></example>
<para>
This dissects all the bits of this simple hypothetical protocol. We've introduced a new
variable offset into 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.
</para>
</section>
<section><title>Improving the dissection information</title>
<para>
We can certainly improve the display of the protcol with a bit of extra data.
The first step is to add some text labels. Lets start by labelling 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.
</para>
<example><title>Naming the packet types.</title>
<programlisting>
<![CDATA[
static const value_string packettypenames[] = {
{ 1, "Initialise" },
{ 2, "Terminate" },
{ 3, "Data" },
{ 0, NULL },
};
]]>
</programlisting></example>
<para>
This is a handy data structure that can be used to look up value to names.
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 data, using the VALS macro.
</para>
<example><title>Adding Names to the protocol.</title>
<programlisting>
<![CDATA[
{ &hf_foo_pdu_type,
{ "FOO PDU Type", "foo.type",
FT_UINT8, BASE_DEC, VALS(packettypenames), 0x0,
"", HFILL }
},
]]>
</programlisting></example>
<para>
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.
</para>
<example><title>Adding Flags to the protocol.</title>
<programlisting>
<![CDATA[
#define FOO_START_FLAG 0x01
#define FOO_END_FLAG 0x02
#define FOO_PRIORITY_FLAG 0x04
static int hf_foo_startflag = -1;
static int hf_foo_endflag = -1;
static int hf_foo_priorityflag = -1;
...
{ &hf_foo_startflag,
{ "FOO PDU Start Flags", "foo.flags.start",
FT_BOOLEAN, 8, NULL, FOO_START_FLAG,
"", HFILL }
},
{ &hf_foo_endflag,
{ "FOO PDU End Flags", "foo.flags.end",
FT_BOOLEAN, 8, NULL, FOO_END_FLAG,
"", HFILL }
},
{ &hf_foo_priorityflag,
{ "FOO PDU Priority Flags", "foo.flags.priority",
FT_BOOLEAN, 8, NULL, FOO_PRIORITY_FLAG,
"", HFILL }
},
...
proto_tree_add_item(foo_tree, hf_foo_flags, tvb, offset, 1, FALSE);
proto_tree_add_item(foo_tree, hf_foo_startflag, tvb, offset, 1, FALSE);
proto_tree_add_item(foo_tree, hf_foo_endflag, tvb, offset, 1, FALSE);
proto_tree_add_item(foo_tree, hf_foo_priorityflag, tvb, offset, 1, FALSE); offset += 1;
]]>
</programlisting></example>
<para>
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. Note we keep the same offset for each of the flags.
</para>
<para>
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 whislt correct is a little uninformative.
We can enhance this by adding a little more detail.
First, lets get hold of the actual value of the protocol type. We can use the handy
function tvb_get_guint8 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.
</para>
<example><title>Enhancing the display.</title>
<programlisting>
static void
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
guint8 packet_type = tvb_get_guint8(tvb, 0);
if (check_col(pinfo->cinfo, COL_PROTOCOL))
col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
/* Clear out stuff in the info column */
if(check_col(pinfo->cinfo,COL_INFO)){
col_clear(pinfo->cinfo,COL_INFO);
}
if (check_col(pinfo->cinfo, COL_INFO)) {
col_add_fstr(pinfo->cinfo, COL_INFO, "Type %s",
val_to_str(packet_type, packettypenames, "Unknown (0x%02x)"));
}
if (tree) { /* we are being asked for details */
proto_item *ti = NULL;
proto_tree *foo_tree = NULL;
gint offset = 0;
ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, FALSE);
proto_item_append_text(ti, ", Type %s",
val_to_str(packet_type, packettypenames, "Unknown (0x%02x)"));
foo_tree = proto_item_add_subtree(ti, ett_foo);
proto_tree_add_item(foo_tree, hf_foo_pdu_type, tvb, offset, 1, FALSE); offset += 1;
...
</programlisting></example>
<para>
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 its displayed, and
similarly we append this data to the base of our dissecting tree.
</para>
</section>
</section>
<section id="ChDissectTap">
<title>How to tap protocols</title>
<para>
Some info about how to use conversations in a dissector can be
found in the file doc/README.tapping.
</para>
</section>
<section id="ChDissectStats">
<title>How to produce protocol stats</title>
<para>
Some info about how to use conversations in a dissector can be
found in the file doc/README.stats_tree.
</para>
</section>