Ethereal-dev: [ethereal-dev] Protocol tree changes

Note: This archive is from the project's previous web site, ethereal.com. This list is no longer active.

From: Gilbert Ramirez <gram@xxxxxxxxxx>
Date: Fri, 11 Jun 1999 15:21:30 -0500
Here's the diff that I mentioned in my previous e-mail. I had not
intended on publishing it until I finished converting all the top-level
dissectors (tr, eth, fddi, ppp), but since Ashok brought up the subject
today I thought I'd go ahead and let everyone see, especially since I
had just finished the ethernet dissector.

This is a patch against the latest CVS tree, with the new protocols that
were added today. I recommend copying your ethereal source tree and
patching this in your copy, since it makes some very big changes. And
since I haven't made my proto_tree_add_item() function
backwards-compatible, I have to compile and link in only those
dissectors which have been converted to the new style of protocol tree,
which at this time is only the Frame level (packet.c) and ethernet.

I've already given a description of what this patch is, and here is the
programmer's doc that I'm writing while working on this new code. This
is also in doc/proto_tree in the patch, but I'll include it here for
easier reading.

I sent a very early draft of my patch to Guy, and he gave me some good
feedback. This is a re-work of that original code.

Here are some points to keep in mind:

I haven't handled all cases of field types in the code that draws the
GUI tree. I was going to add them on a case-by-case basis.

The protocols and fields are registered from each dissector, but I still
call each registration function from a central location, in proto.c. But
this one piece of centralization is better than total centralization of
protocol/field registration. Suggestions?

Maybe I should expend the energy to make this new code backwards
compatible so that I don't cripple ethereal until all dissector code has
been converted to this new method. Thoughts?

For most field types, I copy the data into the field info struct. If the
field has an uint16 value, I copy that uint16 into the struct. For
ethers, I don't. I could just keep pointers in the struct and not do
*any* copying. I'm worried about strange cases that make uint8's out of
nibbles or other odd boundaries. More thoughts?

Also, there is not yet a function that returns a list of protocols, as
needed by the future color selector. I keep a list of both protocols and
fields combined. I wonder if combining these is bad. I can easily create
a list of just the protocols and pass it to the color-selection
function. But if I kept protocols and fields in separate lists
internally, it would be even _easier_ to give a list of protocols to the
color-selection function. Thoughts?

I'm open to any and all suggestions.

--gilbert


The Ethereal Protocol Tree
==========================

Up until version 0.6.2 of ethereal, the protocol tree that is displayed
in the middle pane of the ethereal GUI had been created by having the
protocol dissection routines add strings to a GTK+ tree. This GUI
container was not easily manipulated; the print routines had to reach
inside what should be an opaque structure and pull out the data. The
tree of strings also did not lend itself to filtering on the data
available in the tree.

Mostly to solve the display filter problem, I decided to have the
protocol dissection routines put their data into a logical tree instead
of a GUI tree. This tree structure would provide a generic way for
multiple routines, like the dissection routines, the display filter
routines, and the print routines, to retrieve data about the protocol
fields. The GUI routines would then be modified to draw the GUI tree
based on the data in the logical tree. By structuring this logical tree
well, with well-defined field types, ethereal can have a very powerful
display filter option. No longer would display filters be limited to the
ability of the BPF compiler (libpcap or wiretap), but would have access to the
full range of C field types available within ethereal.

The dissection routines are still passed a proto_tree pointer, but a
proto_tree is no longer the same as a GtkTree. Now a proto_tree is a
GNode, the N-way tree structure available within GLIB. Of course the
protocol dissectors don't care what a proto_tree really is; they just
pass the proto_tree pointer as an argument to the routines which allow
them to add items and new branches to the tree.

In packet_list_select_cb() you'll now find this:

    if (protocol_tree)
        proto_tree_free(protocol_tree);
    protocol_tree = proto_tree_create_root();
    dissect_packet(cf.pd, fd, protocol_tree);
    proto_tree_draw(protocol_tree, tree_view);

When a packet is selected in the packet-list pane, a new logical
protocol tree (proto_tree) is created. The pointer to the proto_tree (in
this case, 'protocol tree'), is passed to the top-level protocol
dissector, and then the GUI tree is drawn via proto_tree_draw().

Programming for the proto_tree
==============================
The logical proto_tree now needs to know detail information about the
protocols and fields about which information will be collected from the
dissection routines. No longer will is the data just a bunch of strings.
Now the data will be typed so that searching and filtering on protocol
header fields will be possible. This means that the for every protocol
and field (which I also call "header fields", since they are fields in
the protocol headers) which might be attached to a tree, some
information is needed.

Every dissector routine will need to register its protocols and fields
with the central protocol routines (in proto.c). At first I thought I
might keep all the protocol and field information about all the
dissectors in one file, but decentralization seemed like a better idea.
That one file would have gotten very large; one small change would have
required a re-compilation of the entire file. Also, by allowing
registration of protocols and fields at run-time, loadable modules of
protocol dissectors (perhaps even user-supplied) is feasible.

For every protocol or field that a dissector wants to register, a variable of
type int needs to be used to keep track of the protocol. The IDs are
needed for establishing parent/child relationships between protocols and
fields, as well as associating data with a particular field so that it
can be stored in the logical tree and displayed in the GUI protocol
tree.

Some dissectors will need to create branches within their tree to help
organize header fields. These branches should be registered as header
fields. Only true protocols should be registered as protocols. This is
so that a display filter user interface knows how to distinguish
protocols from fields.

A protocol is registered with the name of the protocol and its
abbreviation.

Here is how the frame "protocol" is registered.

	int proto_frame;

        proto_frame = proto_register_protocol (
                /* name */      "Frame",
                /* abbrev */    "frame" );


A header field is also registered with its name and abbreviation, but
information about the its data type is needed. Some fields will use
value_strings to represent their values, so the value_string
is also passed. And of course the parent protocol for the field is indicated
during registration.

	int hf_frame_arrival_time;

        hf_frame_arrival_time = proto_register_field (
                /* name */      "Arrival Time",
                /* abbrev */    "frame.time",
                /* ftype */     FT_ABSOLUTE_TIME,
                /* parent */    proto_frame,
                /* vals[] */    NULL );

The name can be used in any type of display, either in the GUI tree, or
in a display filter UI. The abbreviation is used when representing a
display filter as a string. For example, the following strings could be a
valid display filter, depending upon the implementation of the display
filter parser and engine.

	frame[20:1] = 0x0a
	frame.time > 'May 21, 1999 13:15:00'

The field type come from an enum. Currently, enum ftenum is comprised
of:

/* field types */
enum ftenum {
        FT_NONE,        /* used for protocol labels (thus no field type) */
        FT_UINT8,
        FT_UINT16,
        FT_UINT32,
        FT_ABSOLUTE_TIME,
        FT_RELATIVE_TIME,
        FT_STRING,
        FT_ETHER,
        FT_BYTES,
        FT_IPv4,
        FT_IPv6,
        FT_IPXSERVER,
        FT_VALS_UINT8,
        FT_VALS_UINT16,
        FT_VALS_UINT32,
        NUM_FIELD_TYPES /* last item number plus one */
};

Previously, the sequence needed within a dissector to add a new branch
to the GUI tree was this:

	item = proto_tree_add_item(....);
	new_tree = proto_tree_new();
	proto_item_add_subtree(item, new_tree, tree_type);

With the new system, the call to proto_tree_new() is no longer needed,
as proto_item_add_subtree creates the new tree for you. The change was
necessary so that the proto_tree routines could maintain the
parent/child relationship within the logical tree. But it has a nice
side-effect of cleaning up the dissector code. The new method is like
this:

	item = proto_tree_add_item(....);
	new_tree = proto_item_add_subtree(item, tree_type);

There are now 3 functions that the programmer can use to add either
protocol or field labels to the proto_tree:

	proto_item*
	proto_tree_add_item(tree, id, start, length, value);

	proto_item*
	proto_tree_add_item_format(tree, id, start, length,
		value, format, ...);

	proto_item*
	proto_tree_add_item_hidden(tree, id, start, length, value);

The first function, proto_tree_add_item, is used when you wish to do no
special formatting. The item added to the GUI tree will contain the name
(as passed in the proto_register_*() function) and any value. If your
field does have a value, it is passed after the length variable (notice
the ellipsis in the function prototype).

The second function, proto_tree_add_free_format(), is used when the
dissector routines wants complete control over how the field and value
will be represented on the GUI tree. The caller must pass include the
name of the protocol or field; it is not added automatically as in
proto_tree_add_item().

The third function is used to add fields and values to a tree, but not
show them on a GUI tree. The caller may want a value to be included in a
tree so that the packet can be filtered on this field, but the
representation of that field in the tree is not appropriate. An example
is the token-ring routing information field (RIF). The best way to show the
RIF in a GUI is by a sequence of ring and bridge numbers. Rings are
3-digit hex numbers, and bridges are single hex digits:

	RIF: 001-A-013-9-C0F-B-555

In the case of RIF, the programmer should use a field with no value and
use proto_tree_add_item_format() to build the above representation. The
programmer can then add the ring and bridge values, one-by-one, with
proto_tree_add_item_hidden() so that the user can then filter on or
search for a particular ring or bridge. Here's a skeleton of how the
programmer might code this.

	char *rif;
	rif = create_rif_string(...);

	proto_tree_add_item_format(tree, hf_tr_rif_label,..., "RIF: %s", rif);

	for(i = 0; i < num_rings; i++) {
		proto_tree_add_item_hidden(tree, hf_tr_rif_ring, ..., ring[i]);
	}
	for(i = 0; i < num_rings - 1; i++) {
		proto_tree_add_item_hidden(tree, hf_tr_rif_ring, ..., bridge[i]);
	}

The logical tree has these items:

	hf_tr_rif_label, text="RIF: 001-A-013-9-C0F-B-555", value = NONE
	hf_tr_rif_ring,  hidden, value=0x001
	hf_tr_rif_bridge, hidden, value=0xA
	hf_tr_rif_ring,  hidden, value=0x013
	hf_tr_rif_bridge, hidden, value=0x9
	hf_tr_rif_ring,  hidden, value=0xC0F
	hf_tr_rif_bridge, hidden, value=0xB
	hf_tr_rif_ring,  hidden, value=0x555

GUI or print code will not display the hidden fields, but a display
filter or "packet grep" routine will still see the values. The possible
filter is then possible:

	tr.rif_ring eq 0x013



Attachment: eth-proto.diff.gz
Description: application/gunzip