Guides

Ip Assembly

Working Ip Fragment Reassembly Example

This is a bit more advanced example of jNetPcap usage. This example is an application that reads packets out of a capture file and reassembles fragmented Ip4 packets.

Tutorial:

Download Source from SVN:

IpReassemblyExample.java

view plaincopy to clipboardprint?

  1. package org.jnetpcap.app;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5. import java.util.PriorityQueue;  
  6. import java.util.Queue;  
  7.   
  8. import org.jnetpcap.Pcap;  
  9. import org.jnetpcap.nio.JBuffer;  
  10. import org.jnetpcap.nio.JMemory.Type;  
  11. import org.jnetpcap.packet.JMemoryPacket;  
  12. import org.jnetpcap.packet.JPacket;  
  13. import org.jnetpcap.packet.PcapPacket;  
  14. import org.jnetpcap.packet.PcapPacketHandler;  
  15. import org.jnetpcap.packet.header.Ip4;  
  16.   
  17. /** 
  18.  * This is a demonstration application for reassembling IP fragments. 
  19.  * The application is intended only for show purposes on how jNetPcap 
  20.  * API can be used. 
  21.  * <p> 
  22.  * This example application captures IP packets, makes sure they are 
  23.  * IPs and creates special packets that are ip only. We will use 
  24.  * JMemoryPacket which nicely allows us to construct a new custom 
  25.  * packet. Our new packets don't care about the lower OSI layers since 
  26.  * that information is irrelavent for Ip reassembly and for the user 
  27.  * as well. If we receive a packet that is not fragmented we simply 
  28.  * pass it through, no sense in doing anything special with it. 
  29.  * </p> 
  30.  *  
  31.  * @author Mark Bednarczyk 
  32.  * @author Sly Technologies, Inc. 
  33.  */  
  34. public class IpReassemblyExample  
  35.     implements PcapPacketHandler<Object> {  
  36.   
  37.   /** 
  38.    * Our custom interface that allows listeners to get our special 
  39.    * reassembled IP packets and also provide them with the actual 
  40.    * reassembled buffer. 
  41.    *  
  42.    * @author Mark Bednarczyk 
  43.    * @author Sly Technologies, Inc. 
  44.    * @param <T> 
  45.    */  
  46.   public interface IpReassemblyBufferHandler {  
  47.     public void nextIpDatagram(IpReassemblyBuffer buffer);  
  48.   }  
  49.   
  50.   /** 
  51.    * A special buffer used for reassembling the original IP datagram. 
  52.    * The reassembled buffer contains an IP header upfront and it 
  53.    * suitable for peering with a packet and then decoding. 
  54.    *  
  55.    * <pre> 
  56.    * +-----------+-----------+-----------+--&tilde;&tilde;&tilde;&tilde;--+ 
  57.    * | Ip Header | Ip frag 1 | Ip frag 2 | etc... | 
  58.    * +-----------+-----------+-----------+--&tilde;&tilde;&tilde;&tilde;--+ 
  59.    * </pre> 
  60.    *  
  61.    * The header comes from the first segment that was seen part of 
  62.    * this IP datagram. The original header is used as a template and 
  63.    * then several fields are reset to reflect the state of reassembled 
  64.    * packet. The fields modified are: 
  65.    * <ul> 
  66.    * <li> Ip4.flags</li> 
  67.    * <li> Ip4.length</li> 
  68.    * <li> Ip4.hlen</li> 
  69.    * <li> Ip4.offset</li> 
  70.    * </ul> 
  71.    *  
  72.    * @author Mark Bednarczyk 
  73.    * @author Sly Technologies, Inc. 
  74.    */  
  75.   public static class IpReassemblyBuffer  
  76.       extends JBuffer  
  77.       implements Comparable<IpReassemblyBuffer> {  
  78.   
  79.     /** 
  80.      * The IP header found at the beggining of this buffer 
  81.      */  
  82.     private Ip4 header = new Ip4();  
  83.   
  84.     /** 
  85.      * Total length of the reassembled IP fragments including the ip 
  86.      * header 
  87.      */  
  88.     private int ipDatagramLength = -1;  
  89.   
  90.     /** 
  91.      * Keeps track of how many bytes have been copied into this 
  92.      * buffer. When bytesCopiedIntoBuffer == ipDatagramLength, where 
  93.      * bytesCopiedIntoBuffer keeps track of the total size of the 
  94.      * original datagram in its entirety (including Ip4 header), then 
  95.      * the reassembly is complete. 
  96.      */  
  97.     private int bytesCopiedIntoBuffer = 20;  
  98.   
  99.     /** 
  100.      * Offset where the Ip payload begins in the reassembled IP 
  101.      * datagram. Always constant since our IP header is constant as 
  102.      * well. 
  103.      */  
  104.     private final int start = 20// length Ip4 header  
  105.   
  106.     /** 
  107.      * Timestamp when this buffer is officially timedout 
  108.      */  
  109.     private final long timeout;  
  110.   
  111.     /** 
  112.      * A hash of Ip4.source, Ip4.destination, Ip4.id, Ip4.type 
  113.      */  
  114.     private final int hash;  
  115.   
  116.     /** 
  117.      * Override the default hashcode with our special Ip4 based one 
  118.      */  
  119.     @Override  
  120.     public int hashCode() {  
  121.       return this.hash;  
  122.     }  
  123.   
  124.     /** 
  125.      * Creates a new buffer for IP fragment reassebly. The buffer 
  126.      * appends an Ip4 header to the front of the buffer. The supplied 
  127.      * ip header is only used as a template and a copy is made. This 
  128.      * allows the buffer to retain all the vital Ip4 information found 
  129.      * in the original Ip4 datagram before it was fragmented. 
  130.      *  
  131.      * @param ip 
  132.      *            ip header of one of the fragments to be used as a 
  133.      *            template for the reassembled packet 
  134.      * @param size 
  135.      *            amount of memory to allocate for reassembly 
  136.      * @param timeout 
  137.      *            timestamp in millis when this buffer should be timed 
  138.      *            out 
  139.      * @param hash 
  140.      *            special Ip4 based hash used for identifying this 
  141.      *            buffer quickly 
  142.      */  
  143.     public IpReassemblyBuffer(Ip4 ip, int size, long timeout, int hash) {  
  144.       super(size); // allocate memory  
  145.   
  146.       this.timeout = timeout;  
  147.       this.hash = hash;  
  148.   
  149.       transferFrom(ip); // copy fragment's Ip header to our buffer  
  150.     }  
  151.   
  152.     /** 
  153.      * Deep copy the supplied Ip4 header to the front of the buffer. 
  154.      * Reset some ip fields to reflect the state of this buffer. 
  155.      *  
  156.      * @param ip 
  157.      *            source Ip4 header to use as template 
  158.      */  
  159.     private void transferFrom(Ip4 ip) {  
  160.       /* 
  161.        * Copy ip header as a template 
  162.        */  
  163.       ip.transferTo(this0200);  
  164.   
  165.       /* 
  166.        * Peer a temporary working Ip4 header to the start of our 
  167.        * buffer. It contains our template Ip4 header data. 
  168.        */  
  169.       header.peer(this020);  
  170.   
  171.       /* 
  172.        * Now reset a few things that are no longer neccessary in a 
  173.        * reassembled datagram 
  174.        */  
  175.       header.hlen(5); // Clear IP optional headers  
  176.       header.clearFlags(Ip4.FLAG_MORE_FRAGEMNTS); // FRAG flag  
  177.       header.offset(0); // Offset is now 0  
  178.       header.checksum(0); // Reset header CRC, unless we calculate it  
  179.       // again  
  180.     }  
  181.   
  182.     /** 
  183.      * Adds a IP fragment to the buffer. This fragment is also the 
  184.      * last framgent of the fragment series which carries special 
  185.      * information about the original IP datagram. 
  186.      *  
  187.      * @param packet 
  188.      *            a packet buffer containing IP fragment data 
  189.      * @param offset 
  190.      *            offset into this buffer where the fragment data 
  191.      *            should be copied to 
  192.      * @param length 
  193.      *            the length of the fragment data 
  194.      * @param packetOffset 
  195.      *            offset into the packet buffer where fragment data 
  196.      *            begins 
  197.      */  
  198.     public void addLastSegment(JBuffer packet, int offset,  
  199.         int length, int packetOffset) {  
  200.   
  201.       addSegment(packet, offset, length, packetOffset);  
  202.   
  203.       this.ipDatagramLength = start + offset + length;  
  204.   
  205.       /* 
  206.        * Trucate the size of the JBuffer to match that of ip reassebly 
  207.        * buffer now that we know that we have received the last 
  208.        * fragment and where it ends 
  209.        */  
  210.       super.setSize(this.ipDatagramLength);  
  211.   
  212.       /* 
  213.        * Set Ip4 total length field, now that we know what it is 
  214.        */  
  215.       header.length(ipDatagramLength); // Set Ip4 total length field  
  216.     }  
  217.   
  218.     /** 
  219.      * Adds a IP fragment to the buffer. The fragment data is copied 
  220.      * into this buffer at specified offset from the supplied packet 
  221.      * data buffer. 
  222.      *  
  223.      * @param packet 
  224.      *            a packet buffer containing IP fragment data 
  225.      * @param offset 
  226.      *            offset into this buffer where the fragment data 
  227.      *            should be copied to 
  228.      * @param length 
  229.      *            the length of the fragment data 
  230.      * @param packetOffset 
  231.      *            offset into the packet buffer where fragment data 
  232.      *            begins 
  233.      */  
  234.     public void addSegment(JBuffer packet, int offset, int length,  
  235.         int packetOffset) {  
  236.   
  237.       /* 
  238.        * Keep track of how much data we're copied so far. Needed to 
  239.        * determine if the reassembly process is complete. 
  240.        */  
  241.       this.bytesCopiedIntoBuffer += length;  
  242.   
  243.       /* 
  244.        * Do the actual copy of fragment data into this buffer. The 
  245.        * transfer is done using a native copy call. 
  246.        */  
  247.       packet.transferTo(this, packetOffset, length, offset + start);  
  248.     }  
  249.   
  250.     /** 
  251.      * For ordering buffers according to their timeout value. This is 
  252.      * specifically useful when using a PriorityQueue which will order 
  253.      * the buffers for us according to the timeout timestamp. The 
  254.      * oldest buffers are on top of the queue, while the youngest are 
  255.      * at the bottom. 
  256.      */  
  257.     public int compareTo(IpReassemblyBuffer o) {  
  258.       return (int) (o.timeout - this.timeout);  
  259.     }  
  260.   
  261.     /** 
  262.      * Checks if the buffer reassembly is complete. If the number of 
  263.      * bytes copied into this buffer including the ip header up front, 
  264.      * equals the length of the original IP datagram, that means the 
  265.      * fragmentation succeeded and is complete. 
  266.      *  
  267.      * @return true if fragmentation succeeded and completely done 
  268.      */  
  269.     public boolean isComplete() {  
  270.       return this.ipDatagramLength == this.bytesCopiedIntoBuffer;  
  271.     }  
  272.   
  273.     /** 
  274.      * Compares the timeout timestamp against the current time. If 
  275.      * timeout timestamp is still in the future, then it returns 
  276.      * false. If the timestamp is in the past, then true is returned 
  277.      * and buffer is considered timedout. 
  278.      *  
  279.      * @return true if buffer is timedout, otherwise false 
  280.      */  
  281.     public boolean isTimedout() {  
  282.       return this.timeout < System.currentTimeMillis(); // Future or  
  283.       // past  
  284.     }  
  285.   
  286.     /** 
  287.      * Returns the working Ip4 header instance found at the front of 
  288.      * this buffer 
  289.      *  
  290.      * @return Ip4 header for this IP datagram 
  291.      */  
  292.     public Ip4 getIpHeader() {  
  293.       return header;  
  294.     }  
  295.   
  296.   }  
  297.   
  298.   /** 
  299.    * Default buffer size to allocate for reassembly. Needs to be large 
  300.    * enough to hold the Ip4 header upfront and the contetents of all 
  301.    * fragment for a single fragmented Ip4 datagram. 
  302.    */  
  303.   private static final int DEFAULT_REASSEMBLY_SIZE = 8 * 1024// 8k  
  304.   
  305.   // packets  
  306.   
  307.   /** 
  308.    * Our example application. Arguments are ignored. Reads 6 packets 
  309.    * from file "tests/test-ipreassembly2.pcap" and reassembles the IP 
  310.    * fragments found into a new ip-only super packet. The new packet 
  311.    * contains the Ip4 header as DLT. 
  312.    *  
  313.    * @param args 
  314.    *            ignored 
  315.    */  
  316.   public static void main(String[] args) {  
  317.   
  318.     StringBuilder errbuf = new StringBuilder();  
  319.     Pcap pcap =  
  320.         Pcap.openOffline("tests/test-ipreassembly2.pcap", errbuf);  
  321.     if (pcap == null) {  
  322.       System.err.println(errbuf.toString());  
  323.       return;  
  324.     }  
  325.   
  326.     /** 
  327.      * Set the capture. We capture 6 packets, use a 5 second timeout 
  328.      * on reassembly buffers and we supply our reassembly handler (our 
  329.      * application) as the recipient of packets from libpcap. To it, 
  330.      * we supply an anonymous handler that receives the reassembly 
  331.      * buffers. We simply convert those to packets and print them out. 
  332.      * If the buffer is incomplete, meaning it was timed out before we 
  333.      * received the last IP fragment, we simply report the event as an 
  334.      * warning. 
  335.      */  
  336.     pcap.loop(6new IpReassemblyExample(5 * 1000,  
  337.         new IpReassemblyBufferHandler() {  
  338.   
  339.           public void nextIpDatagram(IpReassemblyBuffer buffer) {  
  340.   
  341.             if (buffer.isComplete() == false) {  
  342.               System.err.println("WARNING: missing fragments");  
  343.             } else {  
  344.   
  345.               /* 
  346.                * Create a packet pointer. Uninitialized packet. 
  347.                */  
  348.               JPacket packet = new JMemoryPacket(Type.POINTER);  
  349.   
  350.               /** 
  351.                * The buffer contains Ip4 header upfront followed by 
  352.                * original Ip4 datagram payload. We peer the packet's 
  353.                * data buffer to the reassmbly buffer, starting at the 
  354.                * Ip4 header. 
  355.                */  
  356.               packet.peer(buffer);  
  357.   
  358.               /* 
  359.                * Decode the packet. We know the first header is the 
  360.                * Ip4 header. 
  361.                */  
  362.               packet.scan(Ip4.ID); // decode the packet  
  363.   
  364.               /* 
  365.                * Pretty print the packet 
  366.                */  
  367.               System.out.println(packet.toString());  
  368.             }  
  369.   
  370.           }  
  371.   
  372.         }), null);  
  373.   }  
  374.   
  375.   /** 
  376.    * Keeps track of all IP datagrams being reassembled 
  377.    */  
  378.   private Map<Integer, IpReassemblyBuffer> buffers =  
  379.       new HashMap<Integer, IpReassemblyBuffer>();  
  380.   
  381.   /** 
  382.    * User registered handler. 
  383.    */  
  384.   private IpReassemblyBufferHandler handler;  
  385.   
  386.   /** 
  387.    * Ip4 header we use for incoming packets from libpcap 
  388.    */  
  389.   private Ip4 ip = new Ip4(); // Ip4 header  
  390.   
  391.   /** 
  392.    * Amount of time in milli seconds, after which reassembly buffers 
  393.    * are timedout out of the queue. 
  394.    */  
  395.   private final long timeout;  
  396.   
  397.   /** 
  398.    * Timeout queue to which all new reassembly buffers are added. The 
  399.    * queue is prioritized according to timeout timestamp of each 
  400.    * buffer. 
  401.    */  
  402.   private final Queue<IpReassemblyBuffer> timeoutQueue =  
  403.       new PriorityQueue<IpReassemblyBuffer>();  
  404.   
  405.   /** 
  406.    * Creates a reassembly handler that reassembles all incoming IP 
  407.    * packet. 
  408.    *  
  409.    * @param timeout 
  410.    *            sets the default amount of time in millis, before 
  411.    *            reassembly buffers are timedout 
  412.    * @param handler 
  413.    *            user supplied handler to call with reassembled buffers 
  414.    */  
  415.   public IpReassemblyExample(long timeout,  
  416.       IpReassemblyBufferHandler handler) {  
  417.     this.timeout = timeout;  
  418.     if (handler == null) {  
  419.       throw new NullPointerException();  
  420.     }  
  421.     this.handler = handler;  
  422.   }  
  423.   
  424.   /** 
  425.    * Process an Ip4 fragment. Fragment is copied into appropriate 
  426.    * reassembly buffer 
  427.    *  
  428.    * @param packet 
  429.    *            Ip4 fragment packet 
  430.    * @param ip 
  431.    *            our working Ip4 header already peered to the packet 
  432.    */  
  433.   private IpReassemblyBuffer bufferFragment(PcapPacket packet, Ip4 ip) {  
  434.     IpReassemblyBuffer buffer = getBuffer(ip);  
  435.   
  436.     /* 
  437.      * Lets keep in mind that ip.getOffset() is a header offset into 
  438.      * the packet buffer, while ip.offset() is the Ip4.offset field 
  439.      * which is the fragment offset into the overall datagram, in 
  440.      * multiples of 8 bytes 
  441.      */  
  442.     final int hlen = ip.hlen() * 4;  
  443.     final int len = ip.length() - hlen;  
  444.     final int packetOffset = ip.getOffset() + hlen;  
  445.     final int dgramOffset = ip.offset() * 8;  
  446.     buffer.addSegment(packet, dgramOffset, len, packetOffset);  
  447.   
  448.     if (buffer.isComplete()) {  
  449.       if (buffers.remove(ip.hashCode()) == null) {  
  450.         System.err  
  451.             .println("bufferFragment(): failed to remove buffer");  
  452.         System.exit(0);  
  453.       }  
  454.       timeoutQueue.remove(buffer);  
  455.   
  456.       dispatch(buffer);  
  457.     }  
  458.   
  459.     return buffer;  
  460.   }  
  461.   
  462.   /** 
  463.    * Process an Ip4 fragment. Fragment is copied into appropriate 
  464.    * reassembly buffer. This is also a special fragment as its the 
  465.    * last fragment of the fragmented IP datagram. 
  466.    *  
  467.    * @param packet 
  468.    *            Ip4 fragment packet 
  469.    * @param ip 
  470.    *            our working Ip4 header already peered to the packet 
  471.    */  
  472.   private IpReassemblyBuffer bufferLastFragment(PcapPacket packet,  
  473.       Ip4 ip) {  
  474.     IpReassemblyBuffer buffer = getBuffer(ip);  
  475.   
  476.     /* 
  477.      * Lets keep in mind that ip.getOffset() is a header offset into 
  478.      * the packet buffer, while ip.offset() is the Ip4.offset field 
  479.      * which is the fragment offset into the overall datagram, in 
  480.      * multiples of 8 bytes 
  481.      */  
  482.     final int hlen = ip.hlen() * 4;  
  483.     final int len = ip.length() - hlen;  
  484.     final int packetOffset = ip.getOffset() + hlen;  
  485.     final int dgramOffset = ip.offset() * 8;  
  486.     buffer.addLastSegment(packet, dgramOffset, len, packetOffset);  
  487.   
  488.     if (buffer.isComplete()) {  
  489.       if (buffers.remove(buffer.hashCode()) == null) {  
  490.         System.err  
  491.             .println("bufferLastFragment(): failed to remove buffer");  
  492.         System.exit(0);  
  493.       }  
  494.       timeoutQueue.remove(buffer);  
  495.   
  496.       dispatch(buffer);  
  497.     }  
  498.   
  499.     return buffer;  
  500.   }  
  501.   
  502.   /** 
  503.    * Calls on user's handlers nextIpDatagram() callback method. 
  504.    *  
  505.    * @param buffer 
  506.    *            reassembled buffer to send to the user's callback 
  507.    */  
  508.   private void dispatch(IpReassemblyBuffer buffer) {  
  509.     handler.nextIpDatagram(buffer);  
  510.   }  
  511.   
  512.   /** 
  513.    * Retrieves a reassembly buffer for this particular Ip packet. The 
  514.    * supplied Ip4 header is used to determine if a buffer already 
  515.    * exists and if not a new one is created. 
  516.    *  
  517.    * @param ip 
  518.    *            Ip4 header of current Ip4 fragment 
  519.    * @return a reassembly buffer used for reassembly of this Ip4 
  520.    *         datagram 
  521.    */  
  522.   private IpReassemblyBuffer getBuffer(Ip4 ip) {  
  523.   
  524.     IpReassemblyBuffer buffer = buffers.get(ip.hashCode());  
  525.     if (buffer == null) { // First time we're seeing this id  
  526.   
  527.       /* 
  528.        * Calculate when the buffer should be timedout due to missing 
  529.        * fragments 
  530.        */  
  531.       final long bufTimeout =  
  532.           System.currentTimeMillis() + this.timeout;  
  533.       buffer =  
  534.           new IpReassemblyBuffer(ip, DEFAULT_REASSEMBLY_SIZE,  
  535.               bufTimeout, ip.hashCode());  
  536.       buffers.put(ip.hashCode(), buffer);  
  537.     }  
  538.   
  539.     return buffer;  
  540.   }  
  541.   
  542.   /** 
  543.    * Catch incoming packets from libpcap and if they are Ip packets 
  544.    * reassemble them. 
  545.    *  
  546.    * @param packet 
  547.    *            a temporary singleton packet received from libpcap 
  548.    * @param user 
  549.    *            user object 
  550.    */  
  551.   public void nextPacket(PcapPacket packet, Object user) {  
  552.   
  553.     if (packet.hasHeader(ip)) {  
  554.       /* 
  555.        * Check if we have an IP fragment 
  556.        */  
  557.       if ((ip.flags() & Ip4.FLAG_MORE_FRAGEMNTS) != 0) {  
  558.         bufferFragment(packet, ip);  
  559.   
  560.         /* 
  561.          * record the last fragment 
  562.          */  
  563.       } else {  
  564.   
  565.         bufferLastFragment(packet, ip);  
  566.   
  567.         /* 
  568.          * Here we have a non-fragmented IP packet so we just pass it 
  569.          * on 
  570.          */  
  571.       }  
  572.   
  573.       /* 
  574.        * Our crude timeout mechanism, should be implemented as a 
  575.        * separate thread 
  576.        */  
  577.       timeoutBuffers();  
  578.     }  
  579.   }  
  580.   
  581.   /** 
  582.    * Check the timeout queue and timeout any buffers that are 
  583.    * timedout. The timed out buffer are dispatched, incomplete to the 
  584.    * user's handler. Buffers that are still on the queue, are 
  585.    * incomplete but have not timedout yet are ignored. 
  586.    */  
  587.   private void timeoutBuffers() {  
  588.     while (timeoutQueue.isEmpty() == false) {  
  589.   
  590.       if (timeoutQueue.peek().isTimedout()) {  
  591.         dispatch(timeoutQueue.poll());  
  592.       } else {  
  593.         break;  
  594.       }  
  595.     }  
  596.   }  
  597. }  

import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;

import org.jnetpcap.Pcap;
import org.jnetpcap.nio.JBuffer;
import org.jnetpcap.nio.JMemory.Type;
import org.jnetpcap.packet.JMemoryPacket;
import org.jnetpcap.packet.JPacket;
import org.jnetpcap.packet.PcapPacket;
import org.jnetpcap.packet.PcapPacketHandler;
import org.jnetpcap.packet.header.Ip4;

/**
* This is a demonstration application for reassembling IP fragments.
* The application is intended only for show purposes on how jNetPcap
* API can be used.
* <p>
* This example application captures IP packets, makes sure they are
* IPs and creates special packets that are ip only. We will use
* JMemoryPacket which nicely allows us to construct a new custom
* packet. Our new packets don't care about the lower OSI layers since
* that information is irrelavent for Ip reassembly and for the user
* as well. If we receive a packet that is not fragmented we simply
* pass it through, no sense in doing anything special with it.
* </p>
*
* @author Mark Bednarczyk
* @author Sly Technologies, Inc.
*/
public class IpReassemblyExample
implements PcapPacketHandler<Object> {

/**
* Our custom interface that allows listeners to get our special
* reassembled IP packets and also provide them with the actual
* reassembled buffer.
*
* @author Mark Bednarczyk
* @author Sly Technologies, Inc.
* @param <T>
*/
public interface IpReassemblyBufferHandler {
public void nextIpDatagram(IpReassemblyBuffer buffer);
}

/**
* A special buffer used for reassembling the original IP datagram.
* The reassembled buffer contains an IP header upfront and it
* suitable for peering with a packet and then decoding.
*
* <pre>
* +-----------+-----------+-----------+--&tilde;&tilde;&tilde;&tilde;--+
* | Ip Header | Ip frag 1 | Ip frag 2 | etc... |
* +-----------+-----------+-----------+--&tilde;&tilde;&tilde;&tilde;--+
* </pre>
*
* The header comes from the first segment that was seen part of
* this IP datagram. The original header is used as a template and
* then several fields are reset to reflect the state of reassembled
* packet. The fields modified are:
* <ul>
* <li> Ip4.flags</li>
* <li> Ip4.length</li>
* <li> Ip4.hlen</li>
* <li> Ip4.offset</li>
* </ul>
*
* @author Mark Bednarczyk
* @author Sly Technologies, Inc.
*/
public static class IpReassemblyBuffer
extends JBuffer
implements Comparable<IpReassemblyBuffer> {

/**
* The IP header found at the beggining of this buffer
*/
private Ip4 header = new Ip4();

/**
* Total length of the reassembled IP fragments including the ip
* header
*/
private int ipDatagramLength = -1;

/**
* Keeps track of how many bytes have been copied into this
* buffer. When bytesCopiedIntoBuffer == ipDatagramLength, where
* bytesCopiedIntoBuffer keeps track of the total size of the
* original datagram in its entirety (including Ip4 header), then
* the reassembly is complete.
*/
private int bytesCopiedIntoBuffer = 20;

/**
* Offset where the Ip payload begins in the reassembled IP
* datagram. Always constant since our IP header is constant as
* well.
*/
private final int start = 20; // length Ip4 header

/**
* Timestamp when this buffer is officially timedout
*/
private final long timeout;

/**
* A hash of Ip4.source, Ip4.destination, Ip4.id, Ip4.type
*/
private final int hash;

/**
* Override the default hashcode with our special Ip4 based one
*/
@Override
public int hashCode() {
return this.hash;
}

/**
* Creates a new buffer for IP fragment reassebly. The buffer
* appends an Ip4 header to the front of the buffer. The supplied
* ip header is only used as a template and a copy is made. This
* allows the buffer to retain all the vital Ip4 information found
* in the original Ip4 datagram before it was fragmented.
*
* @param ip
* ip header of one of the fragments to be used as a
* template for the reassembled packet
* @param size
* amount of memory to allocate for reassembly
* @param timeout
* timestamp in millis when this buffer should be timed
* out
* @param hash
* special Ip4 based hash used for identifying this
* buffer quickly
*/
public IpReassemblyBuffer(Ip4 ip, int size, long timeout, int hash) {
super(size); // allocate memory

this.timeout = timeout;
this.hash = hash;

transferFrom(ip); // copy fragment's Ip header to our buffer
}

/**
* Deep copy the supplied Ip4 header to the front of the buffer.
* Reset some ip fields to reflect the state of this buffer.
*
* @param ip
* source Ip4 header to use as template
*/
private void transferFrom(Ip4 ip) {
/*
* Copy ip header as a template
*/
ip.transferTo(this, 0, 20, 0);

/*
* Peer a temporary working Ip4 header to the start of our
* buffer. It contains our template Ip4 header data.
*/
header.peer(this, 0, 20);

/*
* Now reset a few things that are no longer neccessary in a
* reassembled datagram
*/
header.hlen(5); // Clear IP optional headers
header.clearFlags(Ip4.FLAG_MORE_FRAGEMNTS); // FRAG flag
header.offset(0); // Offset is now 0
header.checksum(0); // Reset header CRC, unless we calculate it
// again
}

/**
* Adds a IP fragment to the buffer. This fragment is also the
* last framgent of the fragment series which carries special
* information about the original IP datagram.
*
* @param packet
* a packet buffer containing IP fragment data
* @param offset
* offset into this buffer where the fragment data
* should be copied to
* @param length
* the length of the fragment data
* @param packetOffset
* offset into the packet buffer where fragment data
* begins
*/
public void addLastSegment(JBuffer packet, int offset,
int length, int packetOffset) {

addSegment(packet, offset, length, packetOffset);

this.ipDatagramLength = start + offset + length;

/*
* Trucate the size of the JBuffer to match that of ip reassebly
* buffer now that we know that we have received the last
* fragment and where it ends
*/
super.setSize(this.ipDatagramLength);

/*
* Set Ip4 total length field, now that we know what it is
*/
header.length(ipDatagramLength); // Set Ip4 total length field
}

/**
* Adds a IP fragment to the buffer. The fragment data is copied
* into this buffer at specified offset from the supplied packet
* data buffer.
*
* @param packet
* a packet buffer containing IP fragment data
* @param offset
* offset into this buffer where the fragment data
* should be copied to
* @param length
* the length of the fragment data
* @param packetOffset
* offset into the packet buffer where fragment data
* begins
*/
public void addSegment(JBuffer packet, int offset, int length,
int packetOffset) {

/*
* Keep track of how much data we're copied so far. Needed to
* determine if the reassembly process is complete.
*/
this.bytesCopiedIntoBuffer += length;

/*
* Do the actual copy of fragment data into this buffer. The
* transfer is done using a native copy call.
*/
packet.transferTo(this, packetOffset, length, offset + start);
}

/**
* For ordering buffers according to their timeout value. This is
* specifically useful when using a PriorityQueue which will order
* the buffers for us according to the timeout timestamp. The
* oldest buffers are on top of the queue, while the youngest are
* at the bottom.
*/
public int compareTo(IpReassemblyBuffer o) {
return (int) (o.timeout - this.timeout);
}

/**
* Checks if the buffer reassembly is complete. If the number of
* bytes copied into this buffer including the ip header up front,
* equals the length of the original IP datagram, that means the
* fragmentation succeeded and is complete.
*
* @return true if fragmentation succeeded and completely done
*/
public boolean isComplete() {
return this.ipDatagramLength == this.bytesCopiedIntoBuffer;
}

/**
* Compares the timeout timestamp against the current time. If
* timeout timestamp is still in the future, then it returns
* false. If the timestamp is in the past, then true is returned
* and buffer is considered timedout.
*
* @return true if buffer is timedout, otherwise false
*/
public boolean isTimedout() {
return this.timeout < System.currentTimeMillis(); // Future or
// past
}

/**
* Returns the working Ip4 header instance found at the front of
* this buffer
*
* @return Ip4 header for this IP datagram
*/
public Ip4 getIpHeader() {
return header;
}

}

/**
* Default buffer size to allocate for reassembly. Needs to be large
* enough to hold the Ip4 header upfront and the contetents of all
* fragment for a single fragmented Ip4 datagram.
*/
private static final int DEFAULT_REASSEMBLY_SIZE = 8 * 1024; // 8k

// packets

/**
* Our example application. Arguments are ignored. Reads 6 packets
* from file "tests/test-ipreassembly2.pcap" and reassembles the IP
* fragments found into a new ip-only super packet. The new packet
* contains the Ip4 header as DLT.
*
* @param args
* ignored
*/
public static void main(String[] args) {

StringBuilder errbuf = new StringBuilder();
Pcap pcap =
Pcap.openOffline("tests/test-ipreassembly2.pcap", errbuf);
if (pcap == null) {
System.err.println(errbuf.toString());
return;
}

/**
* Set the capture. We capture 6 packets, use a 5 second timeout
* on reassembly buffers and we supply our reassembly handler (our
* application) as the recipient of packets from libpcap. To it,
* we supply an anonymous handler that receives the reassembly
* buffers. We simply convert those to packets and print them out.
* If the buffer is incomplete, meaning it was timed out before we
* received the last IP fragment, we simply report the event as an
* warning.
*/
pcap.loop(6, new IpReassemblyExample(5 * 1000,
new IpReassemblyBufferHandler() {

public void nextIpDatagram(IpReassemblyBuffer buffer) {

if (buffer.isComplete() == false) {
System.err.println("WARNING: missing fragments");
} else {

/*
* Create a packet pointer. Uninitialized packet.
*/
JPacket packet = new JMemoryPacket(Type.POINTER);

/**
* The buffer contains Ip4 header upfront followed by
* original Ip4 datagram payload. We peer the packet's
* data buffer to the reassmbly buffer, starting at the
* Ip4 header.
*/
packet.peer(buffer);

/*
* Decode the packet. We know the first header is the
* Ip4 header.
*/
packet.scan(Ip4.ID); // decode the packet

/*
* Pretty print the packet
*/
System.out.println(packet.toString());
}

}

}), null);
}

/**
* Keeps track of all IP datagrams being reassembled
*/
private Map<Integer, IpReassemblyBuffer> buffers =
new HashMap<Integer, IpReassemblyBuffer>();

/**
* User registered handler.
*/
private IpReassemblyBufferHandler handler;

/**
* Ip4 header we use for incoming packets from libpcap
*/
private Ip4 ip = new Ip4(); // Ip4 header

/**
* Amount of time in milli seconds, after which reassembly buffers
* are timedout out of the queue.
*/
private final long timeout;

/**
* Timeout queue to which all new reassembly buffers are added. The
* queue is prioritized according to timeout timestamp of each
* buffer.
*/
private final Queue<IpReassemblyBuffer> timeoutQueue =
new PriorityQueue<IpReassemblyBuffer>();

/**
* Creates a reassembly handler that reassembles all incoming IP
* packet.
*
* @param timeout
* sets the default amount of time in millis, before
* reassembly buffers are timedout
* @param handler
* user supplied handler to call with reassembled buffers
*/
public IpReassemblyExample(long timeout,
IpReassemblyBufferHandler handler) {
this.timeout = timeout;
if (handler == null) {
throw new NullPointerException();
}
this.handler = handler;
}

/**
* Process an Ip4 fragment. Fragment is copied into appropriate
* reassembly buffer
*
* @param packet
* Ip4 fragment packet
* @param ip
* our working Ip4 header already peered to the packet
*/
private IpReassemblyBuffer bufferFragment(PcapPacket packet, Ip4 ip) {
IpReassemblyBuffer buffer = getBuffer(ip);

/*
* Lets keep in mind that ip.getOffset() is a header offset into
* the packet buffer, while ip.offset() is the Ip4.offset field
* which is the fragment offset into the overall datagram, in
* multiples of 8 bytes
*/
final int hlen = ip.hlen() * 4;
final int len = ip.length() - hlen;
final int packetOffset = ip.getOffset() + hlen;
final int dgramOffset = ip.offset() * 8;
buffer.addSegment(packet, dgramOffset, len, packetOffset);

if (buffer.isComplete()) {
if (buffers.remove(ip.hashCode()) == null) {
System.err
.println("bufferFragment(): failed to remove buffer");
System.exit(0);
}
timeoutQueue.remove(buffer);

dispatch(buffer);
}

return buffer;
}

/**
* Process an Ip4 fragment. Fragment is copied into appropriate
* reassembly buffer. This is also a special fragment as its the
* last fragment of the fragmented IP datagram.
*
* @param packet
* Ip4 fragment packet
* @param ip
* our working Ip4 header already peered to the packet
*/
private IpReassemblyBuffer bufferLastFragment(PcapPacket packet,
Ip4 ip) {
IpReassemblyBuffer buffer = getBuffer(ip);

/*
* Lets keep in mind that ip.getOffset() is a header offset into
* the packet buffer, while ip.offset() is the Ip4.offset field
* which is the fragment offset into the overall datagram, in
* multiples of 8 bytes
*/
final int hlen = ip.hlen() * 4;
final int len = ip.length() - hlen;
final int packetOffset = ip.getOffset() + hlen;
final int dgramOffset = ip.offset() * 8;
buffer.addLastSegment(packet, dgramOffset, len, packetOffset);

if (buffer.isComplete()) {
if (buffers.remove(buffer.hashCode()) == null) {
System.err
.println("bufferLastFragment(): failed to remove buffer");
System.exit(0);
}
timeoutQueue.remove(buffer);

dispatch(buffer);
}

return buffer;
}

/**
* Calls on user's handlers nextIpDatagram() callback method.
*
* @param buffer
* reassembled buffer to send to the user's callback
*/
private void dispatch(IpReassemblyBuffer buffer) {
handler.nextIpDatagram(buffer);
}

/**
* Retrieves a reassembly buffer for this particular Ip packet. The
* supplied Ip4 header is used to determine if a buffer already
* exists and if not a new one is created.
*
* @param ip
* Ip4 header of current Ip4 fragment
* @return a reassembly buffer used for reassembly of this Ip4
* datagram
*/
private IpReassemblyBuffer getBuffer(Ip4 ip) {

IpReassemblyBuffer buffer = buffers.get(ip.hashCode());
if (buffer == null) { // First time we're seeing this id

/*
* Calculate when the buffer should be timedout due to missing
* fragments
*/
final long bufTimeout =
System.currentTimeMillis() + this.timeout;
buffer =
new IpReassemblyBuffer(ip, DEFAULT_REASSEMBLY_SIZE,
bufTimeout, ip.hashCode());
buffers.put(ip.hashCode(), buffer);
}

return buffer;
}

/**
* Catch incoming packets from libpcap and if they are Ip packets
* reassemble them.
*
* @param packet
* a temporary singleton packet received from libpcap
* @param user
* user object
*/
public void nextPacket(PcapPacket packet, Object user) {

if (packet.hasHeader(ip)) {
/*
* Check if we have an IP fragment
*/
if ((ip.flags() & Ip4.FLAG_MORE_FRAGEMNTS) != Innocent {
bufferFragment(packet, ip);

/*
* record the last fragment
*/
} else {

bufferLastFragment(packet, ip);

/*
* Here we have a non-fragmented IP packet so we just pass it
* on
*/
}

/*
* Our crude timeout mechanism, should be implemented as a
* separate thread
*/
timeoutBuffers();
}
}

/**
* Check the timeout queue and timeout any buffers that are
* timedout. The timed out buffer are dispatched, incomplete to the
* user's handler. Buffers that are still on the queue, are
* incomplete but have not timedout yet are ignored.
*/
private void timeoutBuffers() {
while (timeoutQueue.isEmpty() == false) {

if (timeoutQueue.peek().isTimedout()) {
dispatch(timeoutQueue.poll());
} else {
break;
}
}
}
}