style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-7505528228218001"
data-ad-slot="1225241371">

Sub Headers

Working with sub-headers Example

This example demonstrates how to work with subheaders. Certain protocols such as ICMP in this example, have either optional sub headers or headers that are not optional but part of the main protocol definition. Icmp protocol definition defines the main part of the header in the base Icmp class while all the specific ICMP types each in its own sub-header. A sub header is a normal header with the exception it has a parent header and can not exist unless that part exists.

Here is Icmp header hierachy as defined in Icmp class:

  • Icmp class
    • EchoReply class
    • EchoRequest class
    • DestinationUnreachable class
    • SourceQuench class
    • ParamProblem class

Download Source from SVN:

Here is a jUnit test case which demonstrates how to check for specific Icmp types and access their fields:

/**
 * Packet dump:
 * 
 * Ethernet:  ******* Ethernet (Eth) offset=0 length=14
 * Ethernet: 
 * Ethernet:      destination = 16-03-78-01-16-03
 * Ethernet:           source = 00-60-08-9F-B1-F3
 * Ethernet:         protocol = 0x800 (2048) [ip version 4]
 * Ethernet: 
 * ip4:  ******* ip4 (ip) offset=14 length=20
 * ip4: 
 * ip4:          version = 4
 * ip4:             hlen = 5 [*4 = 20 bytes]
 * ip4:            diffs = 0xC0 (192)
 * ip4:                    1100 00..  = [48] reserved bit: code point 48
 * ip4:                    .... ..0.  = [0] ECN bit: ECN capable transport: no
 * ip4:                    .... ...0  = [0] ECE bit: ECE-CE: no
 * ip4:           length = 468
 * ip4:            flags = 0x0 (0)
 * ip4:                    0..  = [0] reserved bit: not set
 * ip4:                    .0.  = [0] don't fragment: not set
 * ip4:                    ..0  = [0] more fragments: not set
 * ip4:               id = 0xE253 (57939)
 * ip4:           offset = 0
 * ip4:     time to live = 255 router hops
 * ip4:         protocol = 1 [icmp - internet message control protocol]
 * ip4:  header checksum = 0xAE96 (44694)
 * ip4:           source = 131.151.32.21
 * ip4:      destination = 131.151.1.59
 * ip4: 
 * icmp:  ******* icmp (icmp) offset=34 length=8
 * icmp: 
 * icmp:             type = 3 [destination unreachable]
 * icmp:             code = 3 [destination port unreachable]
 * icmp:         checksum = 0x2731 (10033)
 * icmp: 
 * icmp: + DestUnreachable: offset=4 length=4
 * icmp:         reserved = 0
 * icmp: 
 * ip4:  ******* ip4 (ip) offset=42 length=20
 * ip4: 
 * ip4:          version = 4
 * ip4:             hlen = 5 [*4 = 20 bytes]
 * ip4:            diffs = 0x0 (0)
 * ip4:                    0000 00..  = [0] reserved bit: not set
 * ip4:                    .... ..0.  = [0] ECN bit: ECN capable transport: no
 * ip4:                    .... ...0  = [0] ECE bit: ECE-CE: no
 * ip4:           length = 440
 * ip4:            flags = 0x2 (2)
 * ip4:                    0..  = [0] reserved bit: not set
 * ip4:                    .1.  = [1] don't fragment: set
 * ip4:                    ..0  = [0] more fragments: not set
 * ip4:               id = 0xCB91 (52113)
 * ip4:           offset = 0
 * ip4:     time to live = 254 router hops
 * ip4:         protocol = 17 [udp - unreliable datagram protocol]
 * ip4:  header checksum = 0x8724 (34596)
 * ip4:           source = 131.151.1.59
 * ip4:      destination = 131.151.32.21
 * ip4: 
 * udp:  ******* udp (udp) offset=62 length=8
 * udp: 
 * udp:           source = 7003
 * udp:      destination = 1792
 * udp:           length = 420
 * udp:         checksum = 44574
 * udp: 
 * payload:  ******* payload (data) offset=70 length=412
 * payload: 
 * payload: 0046: 382b3948 e09dbee8 00000001 00000001   8  +  9  H  \e0\9d\be\e8\0 \0 \0 \1 \0 \0 \0 \1 
 * payload: 0056: 00000002 01060000 00000034 00000072   \0 \0 \0 \2 \1 \6 \0 \0 \0 \0 \0 4  \0 \0 \0 r  
 * [truncated...]
 */
public void testIcmpDestUnreachable() {
	// Wireshark packet # 29 (1-based)
	PcapPacket packet = TestUtils.getPcapPacket("tests/test-afs.pcap", 29 - 1);
	
	Ip4 ip = new Ip4();
	Icmp icmp = new Icmp(); // Need an instance so we can check on sub header
	Icmp.DestinationUnreachable unreach = new Icmp.DestinationUnreachable();

	assertTrue(packet.hasHeader(Ethernet.ID));
	assertTrue(packet.hasHeader(Ip4.ID, 0)); // 1st IP header
	assertTrue(packet.hasHeader(icmp));
	assertTrue(icmp.hasSubHeader(IcmpType.DESTINATION_UNREACHABLE.getId()));
	assertTrue(icmp.hasSubHeader(unreach));
	assertTrue(packet.hasHeader(ip, 1)); // 2nd IP header
	assertTrue(packet.hasHeader(Udp.ID));
	assertTrue(packet.hasHeader(Payload.ID));

	// Check specific values
	assertEquals(3, icmp.type());
	assertEquals(3, icmp.code());
	assertEquals(0x2731, icmp.checksum());
	assertEquals(0, unreach.reserved());

	assertEquals(0x8724, ip.checksum());
	assertEquals(440, ip.length());

	// Devil's advocate
	assertFalse(icmp.hasSubHeader(IcmpType.ECHO_REPLY.getId()));
	assertFalse(icmp.hasSubHeader(IcmpType.PARAM_PROBLEM.getId()));

}


Explanation

There are a lot of subtleties in the example we should explore.

    PcapPacket packet = TestUtils.getPcapPacket("tests/test-afs.pcap", 29 - 1);  

First line#76 uses a predefined utility method part of the jnetpcap tests that facilitates easy packet access into files. So we are reading a single packet at index 29 from file tests/test-afs.pcap.

	Ip4 ip = new Ip4();
	Icmp icmp = new Icmp(); // Need an instance so we can check on sub header
	Icmp.DestinationUnreachable unreach = new Icmp.DestinationUnreachable();

Lines#78-80 we allocate uninitialized headers that will be peered with portions of the packet buffer that contain their respective headers. Peered in the sense, that they will point to and address the physical native memory within the packet buffer. For now, they are empty and will throw null pointer exception when any of their accessors are called on.

Then we do various asserts to check values within the packet and more specifically within the decoded state of the packet. The packet state contains information about each header that is present. Each header has a unique numerical ID that is used as a lookup index into various native structures which maintain the packet decoded state.

	assertTrue(packet.hasHeader(Ethernet.ID));

Line#82 checks if ethernet header exists in the packet. The Ethernet.ID is a static final constant defined by jnetpcap API as this is one of the core protocols part of the package. Other protocols can be dynamically registered and will receive a unique runtime ID for the duration of the running program.

	assertTrue(packet.hasHeader(Ip4.ID, 0)); // 1st IP header

Line#83 again checks for the presence of Ip4 header, but with the caveat that we are looking for the first Ip4 header in the packet, incase there are more then one, which is the case in this particular icmp packet.

	assertTrue(packet.hasHeader(icmp));

Line#84 does 2 things. First it checks if icmp header is present (just like previous 2 lines but using some dynamic methods for accessing the id behind the scene) and second, it also peers the icmp header object if icmp header is found and returns a boolean true to let the user know if the peering took place or not (or another words, if the header exists does the peer or returns false and no peering.) This is a shortcut method which essentially equates to the following expression

packet.hasHeader(Icmp.ID) && (packet.getHeader(icmp) != null)

	assertTrue(icmp.hasSubHeader(IcmpType.DESTINATION_UNREACHABLE.getId()));

Line#85 simply checks if sub-header dest-ureachable exists and uses a dynamic method to lookup the sub-header's ID. Sub headers always get their IDs dynamically, therefore we don't have a predefined constant we can use. No peering with this type of call.

	assertTrue(icmp.hasSubHeader(unreach));

Line#86 does both header exists check and peers at the same time in one call. Since we use this type of call here, line #85 is redundant, but its in the testcase anyhow, since are using a slightly different part of the API.

	assertTrue(packet.hasHeader(ip, 1)); // 2nd IP header

Line#87 looks almost identical to the line#83, except we are now looking for the second instance of Ip4 header within the packet, that is the Ip4 header we expect to find under icmp-unreach header. This is the check and peer version of the call, so our ip object is peered and can read values out of the buffer that correspond to the second Ip4 header.

	assertTrue(packet.hasHeader(Udp.ID));
	assertTrue(packet.hasHeader(Payload.ID));

Line#88-89 we check for presence of udp and a special builtin payload header which is a catch all header at the end of every packet. It acts like a real header, and you use it as such. Payload only contains a single field which is essentially all of its data. You can access any part of any header, not just the payload header, using the super class JBuffer methods, but the field also has a special purpose. It allows the payload or the last part of the packet to be formatted for user output when the packet is converted to a string or dumped to a stream.

	// Check specific values
	assertEquals(3, icmp.type());
	assertEquals(3, icmp.code());
	assertEquals(0x2731, icmp.checksum());
	assertEquals(0, unreach.reserved());

	assertEquals(0x8724, ip.checksum());
	assertEquals(440, ip.length());

Lines#91-98 demonstrate how our peered objects can be used to read values and fields out of the packet.

	assertFalse(icmp.hasSubHeader(IcmpType.ECHO_REPLY.getId()));
	assertFalse(icmp.hasSubHeader(IcmpType.PARAM_PROBLEM.getId()));

Lines#101-102 play the devil's advocate and check for sub-headers we know for sure do not exist in the packet. They check for their absence not their presence with assertFalse calls.