100 lines
5.3 KiB
Plaintext
100 lines
5.3 KiB
Plaintext
Packet.Net
|
|
|
|
The high level design goals of Packet.Net are:
|
|
- High performance. As little memory allocation/access and cpu usage as possible
|
|
- Simpler, modular design
|
|
- Clear and well documented api
|
|
|
|
|
|
Packet.Net is a rewrite of SharpPcap's packet parsers.
|
|
|
|
SharpPcap's packet parsers have a few shortcomings in their design. To understand
|
|
what these are it is important to know more about SharpPcap's design.
|
|
|
|
|
|
SharpPcap's packet parsing
|
|
--------------------------
|
|
SharpPcap considers all packets to be of type 'Packet'. Packet types are subclassed, so
|
|
a UdpPacket is an IpPacket which is an EthernetPacket which is a Packet. This is useful
|
|
because a user can have a 'UdpPacket udp' and can access the source mac address via
|
|
'udp.SourceHwAddress', without having to jump through any hoops.
|
|
|
|
As packets are received they are classified into their most specific type, first by
|
|
looking at whether they are an EthernetPacket, checking if the EthernetPacket is an IpPacket
|
|
and then if the IpPacket is a Udp or Tcp packet. At this time the most specific packet is created
|
|
by passing in the byte stream, like TcpPacket tcp = new TcpPacket(packetBytes);
|
|
|
|
SharpPcap performs lazy field parsing. Each packet field is extracted from the byte stream
|
|
when requested. Some caching is performed in the TcpPacket class but in most cases the
|
|
values are re-parsed by reading a number of bytes from a specific offset each time
|
|
the class property is read.
|
|
|
|
|
|
Shortcomings of SharpPcap's design
|
|
----------------------------------
|
|
|
|
Creating packets from values is cumbersome. Because a TcpPacket is an IpPacket which is an
|
|
EthernetPacket, creating a TcpPacket means we need to somehow simultaniously create the
|
|
TcpPacket, IpPacket and EthernetPacket. The most natural way to do this was to have the
|
|
TcpPacket constructor take in the parameters that make up a TcpPacket in addition to an IpPacket.
|
|
The IpPacket constructor follows the same rule, it takes in its unique parameters plus an EthernetPacket.
|
|
One user reported that this hierarchy seems confusing because the encapsulation is usually
|
|
thought of in the reverse order where an ethernet packet contains an ip packet which contains a
|
|
tcp packet. Another issue of a more technical aspect is the internal method
|
|
used to actually build the TcpPacket. Because packet bytes are contiguous the TcpPacket
|
|
constructor has to allocate enough bytes for the IpPacket plus enough bytes for itself.
|
|
The constructor then has to set its internal buffer to point to this newly allocated buffer, copy in
|
|
the IpPacket's bytes and then set its own values. This code is complex and can be error prone.
|
|
The same complexity exists in almost all of the value constructors.
|
|
|
|
|
|
Shortcomings in SharpPcap's implementation
|
|
------------------------------------------
|
|
|
|
SharpPcap allows direct access to the header and content bytes of a packet. The Packet class,
|
|
defined in Packet.cs, defines public accessors for retrieving the byte[] of the header and
|
|
packet bytes, byte[] Bytes and byte[] Header. These routines do not take care to make the
|
|
packets valid, ie. if the size of a TcpPacket changes the IpPacket length field is not
|
|
updated to reflect the change.
|
|
|
|
|
|
Design improvements over SharpPcap
|
|
----------------------------------
|
|
|
|
The inheritance order should be reversed. A user refers to the payload of an EthernetPacket
|
|
to get an IpPacket and the IpPacket payload to get the TcpPacket. Reversing the encapsulation
|
|
better matches how packets are parsed. Reversing the encapsulation also allows for modifying
|
|
the type of packet without conflicting with the original class type for example when
|
|
an IpPacket's payload is changed from a TcpPacket to a UdpPacket. With SharpPcap this is
|
|
an impossible change because the packet instance is of type TcpPacket. There is no way to
|
|
convert it to a UdpPacket without creating a new packet that is a copy of the original but with
|
|
a its type altered.
|
|
|
|
Eliminating the inheritance model of SharpPcap also makes packets easier to construct by value.
|
|
In SharpPcap a TcpPacket required building an EthernetPacket and an IpPacket and passing
|
|
both to a TcpPacket constructor that would discard their payloads and copy their headers
|
|
into a byte[] that was allocated to fit the headers plus the tcp payload. In Packet.Net because
|
|
each packet is separate a user is less confused by having to build both an IpPacket, that
|
|
contains an EthernetPacket, and an EthernetPacket separately.
|
|
|
|
|
|
Implementation improvements over SharpPcap
|
|
------------------------------------------
|
|
|
|
Packet.Net's Packet has a 'byte[] Bytes' get accessor retrieves packet bytes from
|
|
the current packet as well as all sub packets. It does so using a fast mechanism in the case
|
|
where memory is contiguous. Packet data will be contiguous unless the packet was constructed
|
|
by values or had its payload altered. The contiguous fast case is the typical usage case as it
|
|
reflects the condition when a packet is captured over the line by a capture library like
|
|
SharpPcap and parsed using Packet.Net. In the case of non-contiguous memory a fallback
|
|
mechanism is used that is slightly slower.
|
|
|
|
|
|
Things kept the same as SharpPcap
|
|
---------------------------------
|
|
|
|
Packet.Net does as much lazy field evaluation as possible. Values are read from memory only when
|
|
requested by a call to the appropriate accessor. This reduces the number of memory
|
|
reads in most cases and performs well even in the case when a user wants to retrieve
|
|
each of the fields of a packet.
|