Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

9.2. Adding a basic dissector

Let's step through adding a basic dissector. We'll start with the made up "foo" protocol. It consists of the following basic items.

  • A packet type - 8 bits, possible values: 1 - initialisation, 2 - terminate, 3 - data.

  • A set of flags stored in 8 bits, 0x01 - start packet, 0x02 - end packet, 0x04 - priority packet.

  • A sequence number - 16 bits.

  • An IP address.

9.2.1. Setting up the dissector

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.

Plugins are the easiest to write initially, so let's 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.

Example 9.1. Dissector Initialisation.

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <epan/packet.h>

#define FOO_PORT 1234

static int proto_foo = -1;


void
proto_register_foo(void)
{
	proto_foo = proto_register_protocol (
		"FOO Protocol",	/* name       */
		"FOO",		/* short name */
		"foo"		/* abbrev     */
		);
}
   

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.

Next we have an int that is initialised to -1 that records our protocol. This will get updated when we register this dissector with the main program. It's good practice to make all variables and functions that aren't exported static to keep name space pollution down. Normally this isn't a problem unless your dissector gets so big it has to span multiple files.

Then a #define for the UDP port that we'll assume we are dissecting traffic for.

Now that we have the basics in place to interact with the main program, we'll start with two protocol dissector setup functions.

First we'll call the proto_register_protocol function which registers the protocol. We can give it three names that will be used for display in various places. The full and short name are used in e.g. the "Preferences" and "Enabled protocols" dialogs as well as the generated field name list in the documentation. The abbreviation is used as the display filter name.

Next we need a handoff routine.

Example 9.2. 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("udp.port", FOO_PORT, foo_handle);
}
   

What's happening here? We are initialising the dissector. First we create a dissector handle; It is associated with the foo protocol and with a routine to be called to do the actual dissecting. Then we associate the handle with a UDP port number so that the main program will know to call us when it gets UDP traffic on that port.

The stardard Wireshark dissector convention is to put proto_register_foo and proto_reg_handoff_foo as the last two functions in the dissector source.

Now at last we get to write some dissecting code. For the moment we'll leave it as a basic placeholder.

Example 9.3. Dissection.

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);
	}
}
   

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.

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 it's been 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 should have a basic dissector ready to compile and install. It doesn't do much at present, other than identify the protocol and label it.

In order to compile this dissector and create a plugin a couple of support files are required, besides the dissector source in packet-foo.c:

  • Makefile.am - This is the UNIX/Linux makefile template

  • Makefile.common - This contains the file names of this plugin

  • Makefile.nmake - This contains the Wireshark plugin makefile for Windows

  • moduleinfo.h - This contains plugin version info

  • moduleinfo.nmake - This contains DLL version info for Windows

  • packet-foo.c - This is your dissector source

  • plugin.rc.in - This contains the DLL resource template for Windows

You can find a good example for these files in the agentx plugin directory. Makefile.common and Makefile.am have to be modified to reflect the relevant files and dissector name. moduldeinfo.h and moduleinfo.nmake have to be filled in with the version information. Compile the dissector to a DLL or shared library and copy it into the plugin directory of the installation.

9.2.2. Dissecting the details of the protocol

Now that we have our basic dissector up and running, let's 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.

The first thing 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, let's enhance our dissector.

Example 9.4. Plugin Packet Dissection.

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);
	}
}
   

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.

We are also marking the area of data that is being consumed by this protocol. In our case it's 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.

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.

Now let's go to the next step and add some protocol dissection. For this step we'll need to construct a couple of tables that help with dissection. This needs some additions to the proto_register_foo function shown previously.

Two statically allocated arrays are added at the beginning of proto_register_foo. The arrays are then registered after the call to proto_register_protocol.

Example 9.5. Registering data structures.

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 gint *ett[] = {
		&ett_foo
	};

	proto_foo = proto_register_protocol (
		"FOO Protocol",	/* name       */
		"FOO",		/* short name */
		"foo"		/* abbrev     */
		);

	proto_register_field_array(proto_foo, hf, array_length(hf));
	proto_register_subtree_array(ett, array_length(ett));
}
   

The variables hf_foo_pdu_type and ett_foo also need to be declared somewhere near the top of the file.

Example 9.6. Dissector data structure globals.

static int hf_foo_pdu_type = -1;

static gint ett_foo = -1;
   

Now we can enhance the protocol display with some detail.

Example 9.7. Dissector starting to dissect the packets.

	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);
	}
   

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.

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 issue it's best to keep this correct.

If we look in detail at the hf_foo_pdu_type declaration in the static array we can see the details of the definition.

  • hf_foo_pdu_type - the index for this node.

  • FOO PDU Type - the label for this item.

  • foo.type - this is the filter string. It enables us to type constructs such as foo.type=1 into the filter box.

  • 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.

  • 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.

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 hf array, and a couple more procedure calls.

Example 9.8. Wrapping up the packet dissection.

...
static int hf_foo_flags = -1;
static int hf_foo_sequenceno = -1;
static int hf_foo_initialip = -1;
...

static void
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
	gint offset = 0;

	...

	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, 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;
	}
	...
}

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 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.

9.2.3. Improving the dissection information

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.

Example 9.9. 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.

Example 9.10. 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.

Example 9.11. 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 = -1;
static int hf_foo_endflag = -1;
static int hf_foo_priorityflag = -1;

static void
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
	...
		...
		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;
		...
	...
}

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. Note we keep the same offset for each of the flags.

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_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.

Example 9.12. Enhancing the display.

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;
	}
}
   

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.


 
 
  Published under the terms fo the GNU General Public License Design by Interspire