@node ns-3 Packets @chapter ns-3 Packets The design of the Packet framework of @emph{ns} was heavily guided by a few important use-cases: @itemize @bullet @item avoid changing the core of the simulator to introduce new types of packet headers or trailers @item maximize the ease of integration with real-world code and systems @item make it easy to support fragmentation, defragmentation, and, concatenation which are important, especially in wireless systems. @item make memory management of this object efficient @item allow actual application data or dummy application bytes for emulated applications @end itemize @emph{ns} Packet objects contain a buffer of bytes: protocol headers and trailers are serialized in this buffer of bytes using user-provided serialization and deserialization routines. The content of this byte buffer is expected to match bit-for-bit the content of a real packet on a real network implementing the protocol of interest. Fragmentation and defragmentation are quite natural to implement within this context: since we have a buffer of real bytes, we can split it in multiple fragments and re-assemble these fragments. We expect that this choice will make it really easy to wrap our Packet data structure within Linux-style skb or BSD-style mbuf to integrate real-world kernel code in the simulator. We also expect that performing a real-time plug of the simulator to a real-world network will be easy. Because we understand that simulation developers often wish to store in packet objects data which is not found in the real packets (such as timestamps or any kind of similar in-band data), the @emph{ns} Packet class can also store extra per-packet "Tags" which are 16 bytes blobs of data. Any Packet can store any number of unique Tags, each of which is uniquely identified by its C++ type. These tags make it easy to attach per-model data to a packet without having to patch the main Packet class or Packet facilities. Memory management of Packet objects is entirely automatic and extremely efficient: memory for the application-level payload can be modelized by a virtual buffer of zero-filled bytes for which memory is never allocated unless explicitely requested by the user or unless the packet is fragmented. Furthermore, copying, adding, and, removing headers or trailers to a packet has been optimized to be virtually free through a technique known as Copy On Write. Packets (messages) are fundamental objects in the simulator and their design is important from a performance and resource management perspective. There are various ways to design the simulation packet, and tradeoffs among the different approaches. In particular, there is a tension between ease-of-use, performance, and safe interface design. There are a few requirements on this object design: @itemize @bullet @item Creation, management, and deletion of this object should be as simple as possible, while avoiding the chance for memory leaks and/or heap corruption; @item Packets should support serialization and deserialization so that network emulation is supported; @item Packets should support fragmentation and concatenation (multiple packets in a data link frame), especially for wireless support; @item It should be natural for packets to carry actual application data, or if there is only an emulated application and there is no need to carry dummy bytes, smaller packets could be used with just the headers and a record of the payload size, but not actual application bytes, conveyed in the simulated packet. @item Packets should facilitate BSD-like operations on mbufs, for support of ported operating system stacks. @item Additional side-information should be supported, such as a tag for cross-layer information. @end itemize @section Packet design overview Unlike @emph{ns-2}, in which Packet objects contain a buffer of C++ structures corresponding to protocol headers, each network packet in @emph{ns-3} contains a byte Buffer and a list of Tags: @itemize @bullet @item The byte buffer stores the serialized content of the chunks added to a packet. The serialized representation of these chunks is expected to match that of real network packets bit for bit (although nothing forces you to do this) which means that the content of a packet buffer is expected to be that of a real packet. Packets can also be created with an arbitrary zero-filled payload for which no real memory is allocated. @item The list of tags stores an arbitrarily large set of arbitrary user-provided data structures in the packet. Each Tag is uniquely identified by its type; only one instance of each type of data structure is allowed in a list of tags. These tags typically contain per-packet cross-layer information or flow identifiers (i.e., things that you wouldn't find in the bits on the wire). Each tag stored in the tag list can be at most 16 bytes. Trying to attach bigger data structures will trigger crashes at runtime. The 16 byte limit is a modifiable compilation constant. @end itemize @float Figure,fig:packets @caption{Implementation overview of Packet class.} @image{figures/packet} @end float Figure @ref{fig:packets} is a high-level overview of the Packet implementation; more detail on the byte Buffer implementation is provided later in Figure @ref{fig:buffer}. In \nsthree, the Packet byte buffer is analogous to a Linux skbuff or BSD mbuf; it is a serialized representation of the actual data in the packet. The tag list is a container for extra items useful for simulation convenience; if a Packet is converted to an emulated packet and put over an actual network, the tags are stripped off and the byte buffer is copied directly into a real packet. The Packet class has value semantics: it can be freely copied around, allocated on the stack, and passed to functions as arguments. Whenever an instance is copied, the full underlying data is not copied; it has ``copy-on-write'' (COW) semantics. Packet instances can be passed by value to function arguments without any performance hit. The fundamental classes for adding to and removing from the byte buffer are @code{class Header} and @code{class Trailer}. Headers are more common but the below discussion also largely applies to protocols using trailers. Every protocol header that needs to be inserted and removed from a Packet instance should derive from the abstract Header base class and implement the private pure virtual methods listed below: @itemize @bullet @item @code{ns3::Header::SerializeTo()} @item @code{ns3::Header::DeserializeFrom()} @item @code{ns3::Header::GetSerializedSize()} @item @code{ns3::Header::PrintTo()} @end itemize Basically, the first three functions are used to serialize and deserialize protocol control information to/from a Buffer. For example, one may define @code{class TCPHeader : public Header}. The TCPHeader object will typically consist of some private data (like a sequence number) and public interface access functions (such as checking the bounds of an input). But the underlying representation of the TCPHeader in a Packet Buffer is 20 serialized bytes (plus TCP options). The TCPHeader::SerializeTo() function would therefore be designed to write these 20 bytes properly into the packet, in network byte order. The last function is used to define how the Header object prints itself onto an output stream. Similarly, user-defined Tags can be appended to the packet. Unlike Headers, Tags are not serialized into a contiguous buffer but are stored in an array. By default, Tags are limited to 16 bytes in size. Tags can be flexibly defined to be any type, but there can only be one instance of any particular object type in the Tags buffer at any time. The implementation makes use of templates to generate the proper set of Add(), Remove(), and Peek() functions for each Tag type. @section Packet interface The public member functions of a Packet object are as follows: @subsection Constructors @verbatim /** * Create an empty packet with a new uid (as returned * by getUid). */ Packet (); /** * Create a packet with a zero-filled payload. * The memory necessary for the payload is not allocated: * it will be allocated at any later point if you attempt * to fragment this packet or to access the zero-filled * bytes. The packet is allocated with a new uid (as * returned by getUid). * * \param size the size of the zero-filled payload */ Packet (uint32_t size); @end verbatim @subsection Adding and removing Buffer data The below code is reproduced for Header class only; similar functions exist for Trailers. @verbatim /** * Add header to this packet. This method invokes the * ns3::Header::serializeTo method to request the header to serialize * itself in the packet buffer. * * \param header a reference to the header to add to this packet. */ void Add (Header const &header); /** * Deserialize header from this packet. This method invokes the * ns3::Header::deserializeFrom method to request the header to deserialize * itself from the packet buffer. This method does not remove * the data from the buffer. It merely reads it. * * \param header a reference to the header to deserialize from the buffer */ void Peek (Header &header); /** * Remove a deserialized header from the internal buffer. * This method removes the bytes read by Packet::peek from * the packet buffer. * * \param header a reference to the header to remove from the internal buffer. */ void Remove (Header const &header); /** * Add trailer to this packet. This method invokes the * ns3::Trailer::serializeTo method to request the trailer to serialize * itself in the packet buffer. * * \param trailer a reference to the trailer to add to this packet. */ @end verbatim @subsection Adding and removing Tags @verbatim /** * Attach a tag to this packet. The tag is fully copied * in a packet-specific internal buffer. This operation * is expected to be really fast. * * \param tag a pointer to the tag to attach to this packet. */ template void AddTag (T const &tag); /** * Remove a tag from this packet. The data stored internally * for this tag is copied in the input tag if an instance * of this tag type is present in the internal buffer. If this * tag type is not present, the input tag is not modified. * * This operation can be potentially slow and might trigger * unexpectedly large memory allocations. It is thus * usually a better idea to create a copy of this packet, * and invoke removeAllTags on the copy to remove all * tags rather than remove the tags one by one from a packet. * * \param tag a pointer to the tag to remove from this packet * \returns true if an instance of this tag type is stored * in this packet, false otherwise. */ template bool RemoveTag (T &tag); /** * Copy a tag stored internally to the input tag. If no instance * of this tag is present internally, the input tag is not modified. * * \param tag a pointer to the tag to read from this packet * \returns true if an instance of this tag type is stored * in this packet, false otherwise. */ template bool PeekTag (T &tag) const; /** * Remove all the tags stored in this packet. This operation is * much much faster than invoking removeTag n times. */ void RemoveAllTags (void); @end verbatim @subsection Fragmentation @verbatim /** * Create a new packet which contains a fragment of the original * packet. The returned packet shares the same uid as this packet. * * \param start offset from start of packet to start of fragment to create * \param length length of fragment to create * \returns a fragment of the original packet */ Packet CreateFragment (uint32_t start, uint32_t length) const; /** * Concatenate the input packet at the end of the current * packet. This does not alter the uid of either packet. * * \param packet packet to concatenate */ void addAtEnd (Packet packet); /oncatenate the input packet at the end of the current * packet. This does not alter the uid of either packet. * * \param packet packet to concatenate */ void AddAtEnd (Packet packet); /** * Concatenate the fragment of the input packet identified * by the offset and size parameters at the end of the current * packet. This does not alter the uid of either packet. * * \param packet to concatenate * \param offset offset of fragment to copy from the start of the input packet * \param size size of fragment of input packet to copy. */ void AddAtEnd (Packet packet, uint32_t offset, uint32_t size); /** * Remove size bytes from the end of the current packet * It is safe to remove more bytes that what is present in * the packet. * * \param size number of bytes from remove */ void RemoveAtEnd (uint32_t size); /** * Remove size bytes from the start of the current packet. * It is safe to remove more bytes that what is present in * the packet. * * \param size number of bytes from remove */ void RemoveAtStart (uint32_t size); @end verbatim @subsection Miscellaneous @verbatim /** * \returns the size in bytes of the packet (including the zero-filled * initial payload) */ uint32_t GetSize (void) const; /** * If you try to change the content of the buffer * returned by this method, you will die. * * \returns a pointer to the internal buffer of the packet. */ uint8_t const *PeekData (void) const; /** * A packet is allocated a new uid when it is created * empty or with zero-filled payload. * * \returns an integer identifier which uniquely * identifies this packet. */ uint32_t GetUid (void) const; @end verbatim @section Using Headers @emph{walk through an example of adding a UDP header} @section Using Tags @emph{walk through an example of adding a flow ID} @section Using Fragmentation @emph{walk through an example of link-layer fragmentation/reassembly} @section Sample program The below sample program (from @code{ns3/samples/main-packet.cc}) illustrates some use of the Packet, Header, and Tag classes. @verbatim /* -*- Mode:C++; c-basic-offset:4; tab-width:4; indent-tabs-mode:nil -*- */ #include "ns3/packet.h" #include "ns3/header.h" #include using namespace ns3; /* A sample Header implementation */ class MyHeader : public Header { public: MyHeader (); virtual ~MyHeader (); void SetData (uint16_t data); uint16_t GetData (void) const; private: virtual void PrintTo (std::ostream &os) const; virtual void SerializeTo (Buffer::Iterator start) const; virtual void DeserializeFrom (Buffer::Iterator start); virtual uint32_t GetSerializedSize (void) const; uint16_t m_data; }; MyHeader::MyHeader () {} MyHeader::~MyHeader () {} void MyHeader::PrintTo (std::ostream &os) const { os << "MyHeader data=" << m_data << std::endl; } uint32_t MyHeader::GetSerializedSize (void) const { return 2; } void MyHeader::SerializeTo (Buffer::Iterator start) const { // serialize in head of buffer start.WriteHtonU16 (m_data); } void MyHeader::DeserializeFrom (Buffer::Iterator start) { // deserialize from head of buffer m_data = start.ReadNtohU16 (); } void MyHeader::SetData (uint16_t data) { m_data = data; } uint16_t MyHeader::GetData (void) const { return m_data; } /* A sample Tag implementation */ struct MyTag { uint16_t m_streamId; }; static TagRegistration g_MyTagRegistration ("ns3::MyTag", 0); static void Receive (Packet p) { MyHeader my; p.Peek (my); p.Remove (my); std::cout << "received data=" << my.GetData () << std::endl; struct MyTag myTag; p.PeekTag (myTag); } int main (int argc, char *argv[]) { Packet p; MyHeader my; my.SetData (2); std::cout << "send data=2" << std::endl; p.Add (my); struct MyTag myTag; myTag.m_streamId = 5; p.AddTag (myTag); Receive (p); return 0; } @end verbatim @section Implementation details @subsection Private member variables A Packet object's interface provides access to some private data: @verbatim Buffer m_buffer; Tags m_tags; uint32_t m_uid; static uint32_t m_global_uid; @end verbatim Each Packet has a Buffer and a Tags object, and a 32-bit unique ID (m\_uid). A static member variable keeps track of the UIDs allocated. Note that real network packets do not have a UID; the UID is therefore an instance of data that normally would be stored as a Tag in the packet. However, it was felt that a UID is a special case that is so often used in simulations that it would be more convenient to store it in a member variable. @subsection Buffer implementation Class Buffer represents a buffer of bytes. Its size is automatically adjusted to hold any data prepended or appended by the user. Its implementation is optimized to ensure that the number of buffer resizes is minimized, by creating new Buffers of the maximum size ever used. The correct maximum size is learned at runtime during use by recording the maximum size of each packet. Authors of new Header or Trailer classes need to know the public API of the Buffer class. (add summary here) The byte buffer is implemented as follows: @verbatim struct BufferData { uint32_t m_count; uint32_t m_size; uint32_t m_initialStart; uint32_t m_dirtyStart; uint32_t m_dirtySize; uint8_t m_data[1]; }; struct BufferData *m_data; uint32_t m_zeroAreaSize; uint32_t m_start; uint32_t m_size; @end verbatim @itemize @bullet @item @code{BufferData::m_count}: reference count for BufferData structure @item @code{BufferData::m_size}: size of data buffer stored in BufferData structure @item @code{BufferData::m_initialStart}: offset from start of data buffer where data was first inserted @item @code{BufferData::m_dirtyStart}: offset from start of buffer where every Buffer which holds a reference to this BufferData instance have written data so far @item @code{BufferData::m_dirtySize}: size of area where data has been written so far @item @code{BufferData::m_data}: pointer to data buffer @item @code{Buffer::m_zeroAreaSize}: size of zero area which extends before @code{m_initialStart} @item @code{Buffer::m_start}: offset from start of buffer to area used by this buffer @item @code{Buffer::m_size}: size of area used by this Buffer in its BufferData structure @end itemize @float Figure,fig:buffer @caption{Implementation overview of a packet's byte Buffer.} @image{figures/buffer,15cm} @end float This data structure is summarized in Figure @ref{fig:buffer}. Each Buffer holds a pointer to an instance of a BufferData. Most Buffers should be able to share the same underlying BufferData and thus simply increase the BufferData's reference count. If they have to change the content of a BufferData inside the Dirty Area, and if the reference count is not one, they first create a copy of the BufferData and then complete their state-changing operation. @subsection Tags implementation Tags are implemented by a single pointer which points to the start of a linked list ofTagData data structures. Each TagData structure points to the next TagData in the list (its next pointer contains zero to indicate the end of the linked list). Each TagData contains an integer unique id which identifies the type of the tag stored in the TagData. @verbatim struct TagData { struct TagData *m_next; uint32_t m_id; uint32_t m_count; uint8_t m_data[Tags::SIZE]; }; class Tags { struct TagData *m_next; }; @end verbatim Adding a tag is a matter of inserting a new TagData at the head of the linked list. Looking at a tag requires you to find the relevant TagData in the linked list and copy its data into the user data structure. Removing a tag and updating the content of a tag requires a deep copy of the linked list before performing this operation. On the other hand, copying a Packet and its tags is a matter of copying the TagData head pointer and incrementing its reference count. Tags are found by the unique mapping betweent the Tag type and its underlying id. This is why at most one instance of any Tag can be stored in a packet. The mapping between Tag type and underlying id is performed by a registration as follows: @verbatim /* A sample Tag implementation */ struct MyTag { uint16_t m_streamId; }; @end verbatim @emph{add description of TagRegistration for printing} @subsection Memory management @emph{Describe free list.} @emph{Describe dataless vs. data-full packets.} @subsection Copy-on-write semantics The current implementation of the byte buffers and tag list is based on COW (Copy On Write). An introduction to COW can be found in Scott Meyer's "More Effective C++", items 17 and 29). This design feature and aspects of the public interface borrows from the packet design of the Georgia Tech Network Simulator. This implementation of COW uses a customized reference counting smart pointer class. What COW means is that copying packets without modifying them is very cheap (in terms of CPU and memory usage) and modifying them can be also very cheap. What is key for proper COW implementations is being able to detect when a given modification of the state of a packet triggers a full copy of the data prior to the modification: COW systems need to detect when an operation is ``dirty'' and must therefore invoke a true copy. Dirty operations: @itemize @bullet @item Packet::RemoveTag() @item Packet::Add() @item both versions of ns3::Packet::AddAtEnd() @end itemize Non-dirty operations: @itemize @bullet @item Packet::AddTag() @item Packet::RemoveAllTags() @item Packet::PeekTag() @item Packet::Peek() @item Packet::Remove() @item Packet::CreateFragment() @item Packet::RemoveAtStart() @item Packet::RemoveAtEnd() @end itemize Dirty operations will always be slower than non-dirty operations, sometimes by several orders of magnitude. However, even the dirty operations have been optimized for common use-cases which means that most of the time, these operations will not trigger data copies and will thus be still very fast.