The packet can be parced while Wireshark but not with Pcap.Net

Sep 28, 2012 at 5:50 AM

After spending a few hours trying to find where the problem is I am really looking for some help now...

I have published the pcap file on Dropbox: https://dl.dropbox.com/u/45608909/PcapDotNet/PcapDotNet.zip

The pcap file contains only a single packet which can be successfully loaded in Wireshark and the TCP payload correctly identified as HTTP datagram.

However Pcap.Net fails to go further IP layer. The problem is demonstrated in the code below. 

        var file = "test1.pcap";

   using (PacketCommunicator comm = new OfflinePacketDevice(file).Open(-1, PacketDeviceOpenAttributes.Promiscuous, 700000))
   {
        Packet packet;
        comm.ReceivePacket(out packet);
        var ipPayload = packet.Ethernet.IpV4.Payload; //always null
        var tcp = packet.Ethernet.IpV4.Tcp; //always null
   }

 

Any help or suggestion would be appreciated.

Thanks.  

Coordinator
Sep 28, 2012 at 12:54 PM

This is interesting.

If you look at the packet, you see the bytes of the Total Length field in the IP layer are 0.

Pcap.Net reads this as if the Total Length is 0 and so this IP layer is invalid.

Wireshark reads it as if the IP layer takes the entire packet (except for the Ethernet header).

 

After Googling this a bit, I've found out that it's due to Wireshark support of packet capture from IP TSO-enabled hardware.

I guess the hardware that captured this device is such hardware.


Pcap.Net currently doesn't support capturing packets from that hardware.

If you disable "Support packet-capture from IP TSO-enabled hardware" in Wireshark IPv4 protocol you'll see how Pcap.Net sees this packet.

 

More information: http://blogs.technet.com/b/nettracer/archive/2010/10/05/bogus-ip-packets-and-wireshark.aspx

Sep 29, 2012 at 7:40 AM
Edited Sep 30, 2012 at 11:53 PM

Thank you for the reply and the link. 

I have also has come to the conclusion that the problem is caused by the bogus IP packet and specifically the TotalLength segment (Buffer[16..17]).

Actually Wireshark made it very difficult to spot. In the LayersView the field displayed as 2367 but in the BufferView it has two zero bytes without any indication of the discrepancy.

When I discovered this it was obvious that Wireshark (in the mode I used it) did not "trust" the TotalLength field and instead processed the packet as whole. 

Unfortunately in my case I need to ensure that absolutely all possible effort is made to extract the TCP payload (in this case HTTP content). Particularly because my customer will be using Wireshark as a benchmark thus the attitude is simple "If Wireshark can do this so should your solution".

I actually find the work around. It is possible to rebuild the the packet in question with Pcap.Net the same way as Wireshark does (even if the functionality is not supported natively by Pcap.Net).

I have provided the code below to demonstrate how to disassemble the bogus packet and assemble it back with Pcap.Net.

Note that the technique is based on the buffer (datagrams) as an input data. This is because extracting layers and building a new Packet from them does not work in this case as the layers are also "corrupt". Writing to the newly created ILayer(s) does not work neither. 

static public class Extensions
{
    public static T CreateDatagram<T>(byte[] data, int offset, int length)
    {
        var constructor = typeof(T).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(byte[]), typeof(int), typeof(int) }, null);
        return (T)constructor.Invoke(new object[] { data, offset, length });
    }

    public static T CreateDatagram<T>(byte[] data)
    {
        var constructor = typeof(T).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(byte[]), typeof(int), typeof(int) }, null);
        return (T)constructor.Invoke(new object[] { data, 0, data.Length });
    }

    public static Packet BuildPacket(DateTime timestamp, params Datagram[] datagrams)
    {
        ILayer[] layers = datagrams.Select(item => item.ExtractLayer()).ToArray();
        return new PacketBuilder(layers).Build(timestamp);
    }

    public static Packet Rebuild(this Packet packet)
    {
        byte[] buff = packet.Ethernet.ToArray();
        int ethernetDatagramLength = EthernetDatagram.HeaderLengthValue;
        int ipHeaderLength = packet.Ethernet.IpV4.HeaderLength;

        var eDatagram = CreateDatagram<EthernetDatagram>(buff);
        var ipDatagram = CreateDatagram<IpV4Datagram>(buff, ethernetDatagramLength, ipHeaderLength);
        var payloadDatagram = new Datagram(buff, ethernetDatagramLength + ipHeaderLength, buff.Length - ethernetDatagramLength - ipHeaderLength);

        return Extensions.BuildPacket(packet.Timestamp,
                                      eDatagram,
                                      ipDatagram,
                                      payloadDatagram);
    }
}

And usage:

if(packet.Ethernet.IpV4.Protocol == IpV4Protocol.Tcp && packet.Ethernet.IpV4.Tcp == null)
    packet = packet.Rebuild();

On a side note, I would like to pass a critical (but hopefully constructive) comment on the API design. :o)

I see very little justification for making all datagram constructors private (internal). Constructing the datagram from a byte array data is arguably the most important functionality of Pcap.Net. But allowing supplying data from files (pcap files) but not from the memory buffer seems illogical.

Allowing direct construction of datagrams is critical for variety of "work arounds". It saved me ~3 years ago (when it was no other way to handle GRE)  and it saved me now. But without public constructors one have to resort to Reflection (see code above).

Please consider making the constructors public. If you want to discourage the direct datagram construction you can throw the exception from the constructor unless the developer activated some permissive switch. (e.g. set special "unsafe" attribute to the host assembly).