2.7.2 - PcapPacketHandler and JPacketHandler

These two handlers are very similar and identical in implementation, so we will only discuss PcapPacketHandler which provides all the capabilities. JPacketHandler provides diminished API that is generic and available across all packet types (there is currently 1 packet type.)

Just like with JBufferHandler a single copy of PcapPacket is reused for every packet from the same instance of the pcap dispatch loop. The packet arrives fully decoded and can be accessed immediately, but can not be put away onto a queue or other permanent/semi-permanent storage. It needs to be either processed immediately by the user's application, discarded or copied to more permanent memory location.

Lets take a look at the handlers exact signature:

class PcapPacketHandler<T>;
PcapPacketHandler.nextPacket(PcapPacket buffer, T user);

Where user is a user supplied object of T class type. The signature is the simplest of all the handlers. The packet header can be accessed from the packet using PcapPacket.getCaptureHeader() and the user parameter is still supplied at the end of the parameter list.

Here is the simplest example of how a handler is used:

StringBuilder errbuf = new StringBuilder();
Pcap pcap = Pcap.openOffline("tests/test-afs.pcap", errbuf);

PcapPacketHandler<String> handler = new PcapPacketHandler<String>() {
  public void nextPacket(PcapPacket packet, String user) {
    System.out.println("size of packet is=" + packet.size());
  }
}

pcap.loop(10, handler, "jNetPcap rocks!");

pcap.close();

As mentioned in more detail in section 2.7.1, the packet is only suitable for a single iteration of the dispatch loop. If we wanted to enhance our example and put the packet on a queue, we need to copy the received temporary packet to a new memory location that is will not be de-allocated or reused until we are done with it. This is actually quiet simple to do.

We can enhance our example as follows:

StringBuilder errbuf = new StringBuilder();
Pcap pcap = Pcap.openOffline("tests/test-afs.pcap", errbuf);

PcapPacketHandler<Queue<PcapPacket>> handler = new PcapPacketHandler<Queue<PcapPacket>>() {
  public void nextPacket(PcapPacket packet, Queue<PcapPacket> queue) {
    PcapPacket permanent = new PcapPacket(packet);

    queue.offer(packet);
  }
}

Queue<PcapPacket> queue = new ArrayBlockingQueue<PcapPacket>();

pcap.loop(10, handler, queue);

System.out.println("we have " + queue.size() + " packets in our queue");

pcap.close();

We have only made 2 changes to our example. First we replaced our user type with a Queue<PcapPacket>. This provides us with a queue that we can put packets on to. The second change is we are now copying the entire packet contents into a new packet:

PcapPacket permanent = new PcapPacket(packet); 

We create a new packet and we supply to its constructor a packet we want to make a copy of. This is a simply step that hides a lot of detail. First the packet's constructor gets the total size of the packet using its PcapPacket.getTotalSize(). This size in bytes includes the size of the capture header, packet data and decoded state structures that the packet holds. A single large memory buffer is allocated from the default JMemoryPool which efficiently allocates native memory for our packets, then all 3 packet components, header, state and data are copied sequentially into the buffer. So a single allocation is holding all of the packets data. The memory layout within the buffer is as follows:

+------------+--------------------------+-------------+
| PcapHeader | packet_state_t structure | packet data |
+------------+--------------------------+-------------+

Getting back to our example, with every iteration of the dispatcher loop we add a new packet onto our queue. When 10 packets have been captured, the loop exits and our Pcap.loop() returns. At that point we print out the number of packets on our queue which should read 10.