Adding Layer 2 Protocol Dissectors to Wireshark

Overview:

This tutorial describes a method to add Layer 2 protocol dissection to Wireshark. This tutorial was originally for Ethereal version 0.8.19. Notes are given for Wireshark version 0.99.5 where changes needed to be made, but these have not been tested.

Wireshark’s Protocol Dissector:

Wireshark’s protocol dissector plug-in API was designed with Layer 3 protocols in mind. Implementation of a Layer 2 protocol dissector in Wireshark is not as straight forward as a Layer 3 protocol dissector plug-in. Dissecting Layer 2 messages in Wireshark requires modification to Wireshark’s source code. There is more than one type of Layer 2 message header structure. For the sake of simplicity, this article will deal only with adding detection for a new Layer 2 protocol that uses an LLC/SNAP header with a unique OUI (Organizational Unit Identifier/Organizationally Unique Identifier). Similar modifications to Wireshark’s code would need to be made for other Layer2 header types.

Although Wireshark supports plug-ins, they can not be used directly for Layer 2 protocols because Wireshark only recognizes LLC/SNAP headers for Ethernet, AppleTalk and Cisco. Instead, Wireshark’s source code must be modified to allow additional Layer 2 SNAP headers to be recognized. From that modified code we will be able to call a standard Wireshark plug-in to dissect the packet. This makes it difficult to use newer versions of Wireshark since the newer source code will need to be modified as well. In the future Wireshark may add better support for handling new Layer 2 protocols using only a plug-in.

See Figure 1 below for a flowchart of the basic operation of Wireshark’s packet capture a dissection processes.

Figure 1, Flowchart of basic Wireshark Layer 2 dissection.
Figure 1, Flowchart of basic Wireshark Layer 2 dissection.

Procedure:

To perform Layer 2 packet decoding, code modifications are made to Wireshark’s LLC protocol handler (packet-llc.c). These modifications will force Wireshark to call a custom Layer 2 dissector when an LLC/SNAP segment is captured that contains the OUI (Organizational Unit Identifier) we are looking for. Other source files might need modification as well to support the additional Layer 2 detection and dissection. Additional source code files need to be added to the Wireshark project to handle dissection of the Layer 2 packets. Once the LLC protocol handler has been modified, the detected Layer 2 packets can be handed off to standard Wireshark plugins.

Figure 2, Screen Capture of Wireshark's User Interface with added Layer 2 dissection.
Figure 2, Screen Capture of Wireshark’s User Interface with added Layer 2 dissection.

Files that may need modification:

Wireshark file “epan\oui.h”

This header file contains definitions for Organizational Unit Identifiers. If the OUI associated with the new Layer 2 protocol is not already in this file, it will need to be added. For the following example a new constant representing the new OUI is named OUI_MYOUI.

Wireshark file “epan\dissectors\packet-llc.c”

This source code file contains functions to handle capture and dissection of LLC and SNAP segments. Three major modifications are required in this file. The text description for Layer 2 OUI must be added to the existing list of OUI text descriptions in the oui_vals[ ] array.

const value_string oui_vals[ ] = {
	{ OUI_ENCAP_ETHER, "Encapsulated Ethernet" },
	{ OUI_CISCO,       "Cisco" },
	{ OUI_CISCO_90,    "Cisco IOS 9.0 Compatible" },
	{ OUI_BRIDGED,     "Frame Relay or ATM bridged frames" },
	{ OUI_ATM_FORUM,   "ATM Forum" },
	{ OUI_CABLE_BPDU,  "DOCSIS Spanning Tree" }, /* DOCSIS spanning tree BPDU */
	{ OUI_APPLE_ATALK, "Apple (AppleTalk)" },
/* Begin new code */
	{ OUI_MYOUI,       "My Organization" },
/* End new code */
	{ 0,               NULL }
};

The capture_llc() function can also be modified to count the new Layer 2 packets as they arrive during a capture. ( Wireshark 0.99.5 note: this code has been moved out to capture_snap() )

void
capture_llc(const u_char *pd, int offset, packet_counts *ld) {
...

  if (is_snap) {
	oui = pd[offset+3] << 16 | pd[offset+4] << 8 | pd[offset+5];

	if (XDLC_IS_INFORMATION(control)) {
		etype = pntohs(&pd[offset+6]);
		switch (oui) {
		
		case OUI_ENCAP_ETHER:
		case OUI_CISCO_90:
		case OUI_APPLE_ATALK:
			/* No, I have no idea why Apple used
			   one of their own OUIs, rather than
			   OUI_ENCAP_ETHER, and an Ethernet
			   packet type as protocol ID, for
			   AppleTalk data packets - but used
			   OUI_ENCAP_ETHER and an Ethernet
			   packet type for AARP packets. */
			capture_ethertype(etype, offset+8, pd,
				ld);
			break;
		
		case OUI_CISCO:
			capture_ethertype(etype,
					offset + 8, pd, ld);
			break;

/* Begin new code */
		case OUI_MYOUI:			
			/* Example: double check header info, modify accordingly */
			/* Make sure PID is correct */
			if(etype = 0x8001)  
			{
				/* Increment my L2 message counter */
				ld->myl2p++;
			}
			else
			{
				/* We don't know what this is, increment 'other' */
				ld->other++;
			}

			break;
/* End new code */

		default:
			ld->other++;
			break;
		}

	}		
...
}

Finally, the dissect_snap() function needs to be modified to call the Layer 2 dissector in added packet-my-layer2.c source code file when a SNAP segment arrives with the proper Layer 2 OUI. The code comments and structure of the code in these areas show how the developers did not expect anyone to add any new Layer 2 protocol detection to the software.

void
dissect_snap(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree,
    proto_tree *snap_tree, int control, int hf_oui, int hf_type, int hf_pid,
    int bridge_pad)
{
	guint32		oui;
	guint16		etype;
	tvbuff_t	*next_tvb;

	oui =	tvb_get_ntoh34(tvb, offset);
	etype = tvb_get_ntohs(tvb, offset+3);

	if (check_col(pinfo->fd, COL_INFO)) {
		col_append_fstr(pinfo->fd, COL_INFO,
		    "; SNAP, OUI 0x%06X (%s), PID 0x%04X",
		    oui, val_to_str(oui, oui_vals, "Unknown"), etype);
	}
	if (tree) {
		proto_tree_add_uint(snap_tree, hf_oui, tvb, offset, 3, oui);
	}

	switch (oui) {

	...

/* Begin new code */
	case OUI_MYOUI:
		/* Show PID */
		proto_tree_add_uint(snap_tree, hf_pid, tvb, offset + 3, 2, etype);
		/* Call my L2 protocol dissector */
		next_tvb = tvb_new_subset(tvb, offset+5, -1, -1);
		call_dissector(my_l2_handle, next_tvb, pinfo, tree);
		break;
/* End new code */

...

	default:
		if (tree) {
			proto_tree_add_uint(snap_tree, hf_pid, tvb, offset+3, 2,
			    etype);
		}
		next_tvb = tvb_new_subset(tvb, offset+5, -1, -1);
		dissect_data(next_tvb, 0, pinfo, tree);
		break;
	}
}			

 

Files that may need modification (continued):

Wireshark file “epan\dissectors\register.c”

This source code file contains functions that handle registering the protocol dissector functions and associated data structures. This file has the dissector functions for Layer 2 messages added to its registration functions automatically at build time if you have the build environment completely set up. If not, you will need to manually add the dissector function prototypes to this file.

Wireshark files “capture.c” and “epan\packet.h”

These source code files contain structures used by Wireshark’s realtime capture statistics window. The “counts[]” array structure can be modified in “capture.c” to add packet counting and display for the new Layer 2 protocol during capture. A global initialization of the new counter needs to be added. The packet_counts structure in “epan\packet.h” can be modified to add a counter for the new Layer 2 packets. These modifications are only necessary if you wish to have the capture window display packet counts specifically for the new Layer 2 protocol that are detected.

All Wireshark “*.nmake” files (Windows platform)

These are makefiles used by Microsoft’s NMAKE utility. The nmake files supplied with the Ethereal code-base contained many absolute paths and references to utility programs that are not absolutely necessary for building Ethereal. Considerable work was required to either duplicate the build environment, or to modify the project and build files to use only relative paths. (Wireshark 0.99.5 note: This appears to have been cleaned up, however some edits are still needed to match your build environment.)

Files that need to be added to the Wireshark project:

Add File “packet-my-layer2.c”

This added source code file will contain functions to dissect the header portion of Layer 2 packets and dispatch the dissection of the Layer 2 message body to functions in packet-my-layer2-msg.c. If the new Layer 2 protocol is simple enough, then further dissection may not be required.

Add file “packet-my-layer2-msg.c”

This added source code file will contain functions used to dissect the body of the new Layer 2 packets, if necessary. Once all the other file modifications are done, this will normally be the only file that requires additions when adding new Layer 2 message type dissectors. Again, this file and it’s code may not be necessary if the new Layer 2 protocol is simple enough.

Build dependencies (Windows platform):

To successfully build the Wireshark application when source code control is used config.h, ps.c, and image\*.res must either be checked out, or set for write access. Enter the following command line in the root directory of the Wireshark source code to build Wireshark:

nmake -f makefile.nmake

To build a setup program for the modified Wireshark project, NSIS must be installed on the development system. NSIS is a public domain installation program creator and is available at: http://nsis.sourceforge.net/Download

To build the setup program for Wireshark right-click on the file named ‘Wireshark-0.99.5\packaging\nsis\wireshark.nsi’ and select ‘Compile NSI’ from the pop-up menu.

 

Protocol Dissector Parameters:

Each Wireshark protocol dissector is passed the following parameters when its dissector function is called.

Data Type Name Description
tvbuff_t * tvb Pointer to a “Testy” Virtual Buffer. A buffer structure designed to throw an exception if any attempt is made to read outside its boundaries.
packet_info * pinfo Pointer to data structure containing information about the packet data contained in the buffer.
proto_tree * tree A pointer to a hierarchical data tree used to display dissected packet data on the user interface.

Local Data for Protocol Dissectors

This section outlines the data that is declared static at the top of a single file in this module. Local data defined by packet-my-layer2.c and packet-my-layer2-msg.c are integer values used as handles when creating hierarchical data trees for display. The following is only an example, your implementation may vary. Consult documentation on Wireshark’s plugin API for further information on how hierarchical data tree displays are handled.

packet-my-layer2.c

/* Initialize the protocol and registered fields */
static int proto_my_l2 = -1;
static int hf_my_l2_msg_type = -1;
static int hf_my_l2_seq_num = -1;
static int hf_my_l2_data_item_0 = -1;
static int hf_my_l2_data_item_1 = -1;
static int hf_my_l2_data_item_2 = -1;

/* Initialize the subtree pointers */
static gint ett_my_l2 = -1;
static gint ett_my_l2_msg = -1;

packet-my-layer2-msg.c

/* Initialize the protocol handle */
static int proto_my_layer2_msg = -1;

These handles are registered with Wireshark and filled in with the correct values later during packet data dissection and display.

 

Interfaces:

The following interface is used to register a protocol with Wireshark. Handles to the hierarchical trees, protocol description information, and header fields that can be used in searching and filtering are all registered within this function. This function name must be globally unique.

The following interface registers a protocol hand-off with Wireshark. If this protocol dissector does not completely decode everything in the buffer, it may hand-off the rest of the decoding to another dissector by registering the hand-off with this function. This is not always needed, but the function must be declared anyway. This function name must be globally unique.

/*
 * FUNCTION: proto_reg_handoff_my_layer2
 *
 * PARAMETERS: None
 *
 * DESCRIPTION: If this dissector uses sub- 
 * dissector registration add a 
 * registration routine.
 * This format is required because a 
 * script is used to find these 
 * routines and create the code that 
 * calls these routines.
 *
 * RETURNS: None
 *
 */

void
proto_reg_handoff_my_layer2(void);

The following interface is used by Wireshark to pass data for a protocol to a dissector. Since this function is registered with the protocol registration function and is declared static, its name does not need to be globally unique. All dissector functions could simply be named “dissect_protocol.” However, giving these functions descriptive names may make the code more readable.

/*
 * FUNCTION: dissect_L2_protocol
 *
 * PARAMETERS:
 * tvb tvbuff_t pointer to capture data structure.
 * pinfo packet_info pointer.
 * tree proto_tree pointer.
 *
 * DESCRIPTION: Code to dissect the Layer 2 segments.
 *
 * RETURNS: None
 *
 */

static void dissect_L2_protocol(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);

 

Protocol Registration:

The Wireshark API function proto_register_protocol() registers a description of the protocol for display in Wireshark’s menus and hierarchical trees. The “my.layer2.msg” parameter is the key value used to link this description info with this protocol’s dissector and can also be used by other protocols that want to link together with this protocol.

The register_dissector() function associates the protocol’s dissector function and hierarchical tree handle with the protocol. The following code shows a simple example of this.


/* Local Prototype */
static void
dissect_L2_protocol_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);

/* Initialize the protocol handle */
static int proto_my_layer2_msg = -1;

/* Protocol registration function */
void
proto_register_my_layer2_msg(void)
{ 
	/* Register the protocol name and description */
	proto_my_layer2_msg = proto_register_protocol(
		"My Company’s Layer 2 Protocol Message", /* Long name */
		"MyL2 (My Company)", /* Short Name */
		"my.layer2.msg"); /* Key value */

	register_dissector(
		"my.layer2.msg", /* Key Value */ 
		dissect_L2_protocol_msg, /* Function pointer */ 
		proto_my_layer2_msg); /* Protocol handle */ 
}

If the protocol dissector doesn’t completely decode the packet data it can hand-off the remaining data in the packet for further decoding by another registered protocol dissector. To do this, a copy of the handle to another protocol dissector can be retrieved in the proto_reg_handoff function using the Wireshark API find_dissector call. Find_dissector takes the protocol key value of the protocol being linked to. Later in this protocol’s dissector function, this returned handle could be used by the call_dissector function to hand-off decoding to the linked protocol. The following code shows a simple example of retrieving a copy of a registered protocol for later use.

/* Initialize dissector handle */
static dissector_handle_t protocol_handle = NULL;

/* Protocol handoff registration function */
void proto_reg_handoff_my_layer2(void)
{
	/* Copy the "my.layer2.msg" protocol handle */
	protocol_handle = find_dissector("my.layer2.msg");
}

Protocol Decoding:

There are many functions available to perform packet dissection and display. However, the most often used functions will be those shown below.

Function Name

Purpose
tvb_get_guint8 Retrieve an 8-bit byte from the buffer at the specified offset.
tvb_get_ntohs Retrieve a 16-bit word from the buffer at the specified offset. Performs network to host conversion.
tvb_get_ntohl Retrieve a 32-bit word from the buffer at the specified offset. Performs network to host conversion.
tvb_get_nstringz0 Retrieve a null terminated string from the buffer starting at the specified offset.
proto_tree_add_protocol_format Adds an entry to the hierarchical display tree using printf style format strings. The offset and size parameters of this function instruct Wireshark what to highlight in the hex data display when this item is selected in the display tree.
val_to_str Returns a text description string for the specified value. If no matching value is found, the specified printf style format string is used to return an error message string instead.

The following code is an example of retrieving an 8-bit byte from the packet data buffer, then adding it to the hierarchical display tree, and converting the 8-bit value to a text description using the val_to_str function.

/* String value structure used by val_to_str function */
static const value_string vs_comp_types[] = {
	{ MY_L2_TYPE1, "DATA TYPE 1"},
	{ MY_L2_TYPE2, "DATA TYPE 2"},
	{ MY_L2_TYPE3, "DATA TYPE 3"},

	/* More entries here */

	{ 0, NULL } /* Marks end of list */
};

/* Handle setup by protocol registration function */
static int protocol_handle = -1;

/* Calculate the offset to the data we want */
#define MY_DATA_PTR offsetof(msg_my_L2_msg, data.item0)

/* Dissector function */
static void dissect_protocol(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
	uint8 my_data;

	/* Do nothing if tree is not valid */
	if(!tree) return;

	/* Retrieve the byte from the buffer */
	my_data = tvb_get_guint8(tvb, MY_DATA_PTR);

	/* Add to the display tree */
	proto_tree_add_protocol_format(
		tree, /* Tree pointer */
		protocol_handle, /* Protocol handle */
		tvb, /* Buffer pointer */
		MY_DATA_PTR, /* Offset in buffer */
		sizeof(uint8), /* Size of data */

		/* printf style format string */
		"Data Item[0]: 0x%x (%s)",

		my_data,

		/* Value to string call */
		val_to_str(
			my_data, /* Value */
			vs_comp_types, /* struct */
			"Unknown Item 0x%x") /* Err str */
	);
}

Unit Testing

To test the Wireshark Protocol Dissector, a packet data capture file should be generated with samples of each Layer 2 message type. The capture file should be edited to include errant Layer 2 messages as well, to test error handling of the protocol decoder.

Once this gold file is created, compare the output of the protocol decoder to expected values.