Manual: Packet Headers and Formats

From nsnam
Jump to: navigation, search

Previous chapter: "Timers" | index | Next chapter: "Error Model"

The procedures and functions described in this chapter can be found in ~ns/tcl/lib/ns-lib.tcl, ~ns/tcl/lib/ns-packet.tcl, and ~ns/common/packet.{cc, h}.

Objects in the class Packet are the fundamental unit of exchange between objects in the simulation. The class Packet provides enough information to link a packet on to a list (i.e., in a PacketQueue or on a free list of packets), refer to a buffer containing packet headers that are defined on a per-protocol basis, and to refer to a buffer of packet data. New protocols may define their own packet headers or may extend existing headers with additional fields.

New packet headers are introduced into the simulator by defining a C++ structure with the needed fields, defining a static class to provide OTcl linkage, and then modifying some of the simulator initialization code to assign a byte offset in each packet where the new header is to be located relative to others.

When the simulator is initialized through OTcl, a user may choose to enable only a subset of the compiled-in packet formats, resulting in a modest savings of memory during the execution of the simulation. Presently, most configured-in packet formats are enabled. The management of which packet formats are currently enabled in a simulation is handled by a special packet header manager object described below. This object supports an OTcl method used to specify which packet headers will be used in a simulation. If an object in the simulator makes use of a field in a header which has not been enabled, a run-time fatal program abort occurs.

A Protocol-Specific Packet Header

Protocol developers will often wish to provide a specific header type to be used in packets. Doing so allows a new protocol implementation to avoid overloading already-existing header fields. We consider a simplified version of RTP as an example. The RTP header will require a sequence number fields and a source identifier field. The following classes create the needed header (see ~ns/apps/rtp.h and ~ns/apps/

From rtp.h:

/* rtp packet. For now, just have srcid + seqno. */
struct hdr_rtp {
   u_int32_t srcid_;
   int seqno_;
   /* per-field member functions */
   u_int32_t& srcid() { return (srcid_); }
   int& seqno() { return (seqno_); }
   /* Packet header access functions */
   static int offset_;
   inline static int& offset() { return offset_; }
   inline static hdr_rtp* access(const Packet* p) {
      return (hdr_rtp*) p->access(offset_);


class RTPHeaderClass : public PacketHeaderClass {
   RTPHeaderClass() : PacketHeaderClass("PacketHeader/RTP",
   sizeof(hdr_rtp)) {
} class_rtphdr;
void RTPAgent::sendpkt()
   Packet* p = allocpkt();
   hdr_rtp *rh = hdr_rtp::access(p);
   lastpkttime_ = Scheduler::instance().clock();
   /* Fill in srcid_ and seqno */
   rh->seqno() = seqno_++;
   rh->srcid() = session_->srcid();
   target_->recv(p, 0);
   : session_(0), lastpkttime_(-1e6)
   type_ = PT_RTP;
   bind("seqno_", &seqno_);

The first structure, hdr_rtp, defines the layout of the RTP packet header (in terms of words and their placement): which fields are needed and how big they are. This structure definition is only used by the compiler to compute byte offsets of fields; no objects of this structure type are ever directly allocated. The structure also provides member functions which in turn provide a layer of data hiding for objects wishing to read or modify header fields of packets. Note that the static class variable offset_ is used to find the byte offset at which the rtp header is located in an arbitrary nspacket. Two methods are provided to utilize this variable to access this header in any packet: offset() and access(). The latter is what most users should choose to access this particular header in a packet; the former is used by the packet header management class and should seldom be used. For example, to access the RTP packet header in a packet pointed by p, one simply says hdr_rtp::access(p). The actual binding of offset_ to the position of this header in a packet is done by routines inside ~ns/tcl/lib/ns-packet.tcl and ~ns/common/ The const in access()’s argument provides (presumably) read-only access to a const Packet, although read-only is enforced since the return pointer is not const. One correct way to do this is to provide two methods, one for write access, the other for read-only access. However, this is not currently implemented.

IMPORTANT: Notice that this is completely different from the original (and obsolete) method to access a packet header, which requires that an integer variable, off_<hdrname>_, be defined for any packet header that one needs to access. This method is now obsolete; its usage is tricky and its misuse can be very difficult to detect.

The static object class_rtphdr of class RTPHeaderClass is used to provide linkage to OTcl when the RTP header is enabled at configuration time. When the simulator executes, this static object calls the PacketHeaderClass constructor with arguments "PacketHeader/RTP" and sizeof(hdr_rtp). This causes the size of the RTP header to be stored and made available to the packet headermanager at configuration time (see below). Notice that bind_offset() MUST be called in the constructor of this class, so that the packet header manager knows where to store the offset for this particular packet header.

The sample member function sendpkt() method of RTPAgent creates a new packet to send by calling allocpkt(), which handles assignment of all the network-layer packet header fields (in this case, IP). Headers other than IP are handled separately. In this case, the agent uses the RTPHeader defined above. The Packet::access(void) member function returns the address of the first byte in a buffer used to hold header information (see below). Its return value is cast as a pointer to the header of interest, after which member functions of the RTPHeader object are used to access individual fields.

Adding a New Packet Header Type

Assuming we wish to create a new header called newhdr the following steps are performed:

  1. create a new structure defining the raw fields (called hdr_newhdr), define offset_ and access methods.
  2. define member functions for needed fields.
  3. create a static class to perform OTcl linkage (defines PacketHeader/Newhdr), do bind_offset() in its constructor.
  4. edit ~ns/tcl/lib/ns-packet.tcl to enable new packet format (see p_info Class and the PacketHeaderManager Class).

This is the recommended way to add your packet headers. If you do not follow this method, your simulation may still work, but it may behave in a unpredictable way when more protocols are added into your simulation. The reason is that the BOB (Bag of Bits) in nspacket is a large sparse space, assigning one wrong packet header offset may not trigger failure immediately.

Selectively Including Packet Headers in Your Simulation

By default, ns includes ALL packet headers of ALL protocols in ns in EVERY packet in your simulation. This is a LOT of overhead, and will increase as more protocols are added into ns. For “packet-intensive” simulations, this could be a huge overhead. For instance, as of Aug 30, 2000, the size of packet headers of all protocols in ns is about 1.9KB; however, if you turn on only the common header, the IP header and the TCP header, they add up to about 100 bytes. If you are doing large-scale web traffic simulation with many big fat pipes, reducing unused packet headers can lead to major memory saving.

To include only the packet headers that are of interest to you in your specific simulation, follow this pattern (e.g., you want to remove AODV and ARP headers from your simulation):

remove-packet-header AODV ARP
set ns [new Simulator]

Notice that remove-packet-headerMUST go before the simulator is created. All packet header names are in the forms of PacketHeader/[hdr]. You only need to supply the [hdr] part, not the prefix. To find the names of packet headers, you may either look them up in ~ns/tcl/lib/ns-packet.tcl, or run the following simple commands in ns:

foreach cl [PacketHeader info subclass] {
   puts $cl

To include only a specific set of headers in your simulation, e.g., IP and TCP, follow this pattern:

add-packet-header IP TCP
set ns [new Simulator]

IMPORTANT: You MUST never remove common header from your simulation. As you can see in ~ns/tcl/lib/ns-packet.tcl, this is enforced by these header manipulation procs.

Notice that by default, all packet headers are included.

Packet Classes

There are four C++ classes relevant to the handling of packets and packet headers in general: Packet, p_info PacketHeader, and PacketHeaderManager. The class Packet defines the type for all packets in the simulation; it is a subclass of Event so that packets may be scheduled (e.g. for later arrival at some queue). The class packet_info holds all text representations for packet names. The class PacketHeader provides a base class for any packet header configured into the simulation. It essentially provides enough internal state to locate any particular packet header in the collection of packet headers present in any given packet. The class PacketHeaderManager defines a class used to collect and manage currently-configured headers. It is invoked by a method available to OTcl at simulation configuration time to enable some subset of the compiled-in packet headers.

The Packet Class

The class Packet defines the structure of a packet and provides member functions to handle a free list for objects of this type. It is illustrated in Figure 12.1 and defined as follows in packet.h:

class Packet : public Event {
   friend class PacketQueue;
   u_char* bits_;
   u_char* data_;         /* variable size buffer for ’data’ */
   u_int datalen_;        /* length of variable size buffer */
   static Packet* free_;
   Packet* next_;         /* for queues and the free list */
   static int hdrlen_;
   Packet() : bits_(0), datalen_(0), next_(0) {}
   u_char* const bits() { return (bits_); }
   Packet* copy() const;
   static Packet* alloc();
   static Packet* alloc(int);
   inline void allocdata(int);
   static void free(Packet*);
   inline u_char* access(int off) {
       if (off < 0)
       return (&bits_[off]);
  inline u_char* accessdata() { return data_; }

This class holds a pointer to a generic array of unsigned characters (commonly called the “bag of bits” or BOB for short) where packet header fields are stored. It also holds a pointer to packet “data” (which is often not used in simulations). The bits_ variable contains the address of the first byte of the BOB. Effectively BOB is (currently implemented as) a concatenation of all the structures defined for each packet header (by convention, the structures with names beginning hdr_<something>) that have been configured in. BOB generally remains a fixed size throughout a simulation, and the size is recorded in the Packet::hdrlen_ member variable. This size is updated during simulator configuration by OTcl. (It is not intended to be updated after configuration time. Doing so should be possible, but is currently untested.)

The other methods of the class Packet are for creating new packets and storing old (unused) ones on a private free list. Such allocation and deallocation is performed by the following code (in ~ns/common/packet.h):

inline Packet* Packet::alloc()
   Packet* p = free_;
   if (p != 0)
       free_ = p->next_;
   else {
       p = new Packet;
       p->bits_ = new u_char[hdrsize_];
       if (p == 0 || p->bits_ == 0)
   return (p);
/* allocate a packet with an n byte data buffer */
inline Packet* Packet::alloc(int n)
   Packet* p = alloc();
   if (n > 0)
   return (p);
/* allocate an n byte data buffer to an existing packet */
inline void Packet::allocdata(int n)
   datalen_ = n;
   data_ = new u_char[n];
   if (data_ == 0)
inline void Packet::free(Packet* p)
   p->next_ = free_;
   free_ = p;
   if (p->datalen_) {
       delete p->data_;
       p->datalen_ = 0;
inline Packet* Packet::copy() const
   Packet* p = alloc();
   memcpy(p->bits(), bits_, hdrlen_);
   if (datalen_) {
       p->datalen_ = datalen_;
       p->data_ = new u_char[datalen_];
       memcpy(p->data_, data_, datalen_);
   return (p);

The alloc() method is a support function commonly used to create new packets. It is called by Agent::allocpkt() method on behalf of agents and is thus not normally invoked directly by most objects. It first attempts to locate an old packet on the free list and if this fails allocates a new one using the C++ new operator. Note that Packet class objects and BOBs are allocated separately. The free() method frees a packet by returning it to the free list. Note that packets are never returned to the system's memory allocator. Instead, they are stored on a free list when Packet::free() is called. The copy() member creates a new, identical copy of a packet with the exception of the uid_ field, which is unique. This function is used by Replicator objects to support multicast distribution and LANs.

p_info Class

This class is used as a "glue" to bind numeric packet type values with their symbolic names. When a new packet type is defined, its numeric code should be added to the enumeration packet_t (see ~ns/common/packet.h) (Note: PT_NTYPE should remain the last element of this enumeration) and its symbolic name should be added to the constructor of p_info:

enum packet_t {
   PT_NTYPE // This MUST be the LAST one
class p_info {
   p_info() {
       name_[PT_TCP]= "tcp";

The hdr_cmn Class

Every packet in the simulator has a "common" header which is defined in ~ns/common/packet.h as follows:

struct hdr_cmn {
   enum dir_t { DOWN= -1, NONE= 0, UP= 1 };
   packet_t ptype_;        // packet type (see above)
   int     size_;          // simulated packet size
   int     uid_;           // unique id
   int     error_;         // error flag
   int     errbitcnt_;     // # of corrupted bits jahn
   int     fecsize_;
   double  ts_;            // timestamp: for q-delay measurement
   int     iface_;         // receiving interface (label)
   dir_t   direction_;     // direction: 0=none, 1=up, -1=down
   // source routing 
   char src_rt_valid;
   double ts_arr_; // Required by Marker of JOBS 

   //Monarch extn begins
   nsaddr_t prev_hop_;     // IP addr of forwarding hop
   nsaddr_t next_hop_;     // next hop for this packet
   int      addr_type_;    // type of next_hop_ addr
   nsaddr_t last_hop_;     // for tracing on multi-user channels

   // called if pkt can't obtain media or isn't ack'd. not called if
   // droped by a queue
   FailureCallback xmit_failure_;
   void *xmit_failure_data_;

    * MONARCH wants to know if the MAC layer is passing this back because
    * it could not get the RTS through or because it did not receive
    * an ACK.
   int     xmit_reason_;
#define XMIT_REASON_RTS 0x01
#define XMIT_REASON_ACK 0x02

   // filled in by GOD on first transmission, used for trace analysis
   int num_forwards_;      // how many times this pkt was forwarded
   int opt_num_forwards_;   // optimal #forwards
   // Monarch extn ends;
   // tx time for this packet in sec
   double txtime_;
   inline double& txtime() { return(txtime_); }
   static int offset_;     // offset for this header
   inline static int& offset() { return offset_; }
   inline static hdr_cmn* access(const Packet* p) {
           return (hdr_cmn*) p->access(offset_);
   /* per-field member functions */
   inline packet_t& ptype() { return (ptype_); }
   inline int& size() { return (size_); }
   inline int& uid() { return (uid_); }
   inline int& error() { return error_; }
   inline int& errbitcnt() {return errbitcnt_; }
   inline int& fecsize() {return fecsize_; }
   inline double& timestamp() { return (ts_); }
   inline int& iface() { return (iface_); }
   inline dir_t& direction() { return (direction_); }
   // monarch_begin
   inline nsaddr_t& next_hop() { return (next_hop_); }
   inline int& addr_type() { return (addr_type_); }
   inline int& num_forwards() { return (num_forwards_); }
   inline int& opt_num_forwards() { return (opt_num_forwards_); }

   ModulationScheme mod_scheme_;
   inline ModulationScheme& mod_scheme() { return (mod_scheme_); }

This structure primarily defines fields used for tracing the flow of packets or measuring other quantities.

  • The time stamp field is used to measure queuing delay at switch nodes. It only has local significance, and can be reset by each node. The TCP header has an end-to-end time stamp.
  • The ptype_ field is used to identify the type of packets, which makes reading traces simpler.
  • The uid_ field is used by the scheduler in scheduling packet arrivals. It only has local significance, and can be reset by each node. The TCP sequence number can be partly fill the role of global identifier.
  • The size_ field is of general use and gives the simulated packet's size in bytes. Note that the actual number of bytes consumed in the simulation may not relate to the value of this field (i.e., size_ has no relationship to sizeof(struct hdr_cmn) or other ns structures). Rather, it is used most often in computing the time required for a packet to be delivered along a network link. As such it should be set to the sum of the application data size and IP-, transport-, and application-level headers for the simulated packet.
  • The iface_ field is used by the simulator when performing multicast distribution tree computations. It is a label indicating (typically) on which link a packet was received.

The PacketHeaderManager Class

An object of the class PacketHeaderManager is used to manage the set of currently-active packet header types and assign each of them unique offsets in the BOB. It is defined in both the C++ and OTcl code:

From tcl/lib/ns-packet.tcl:

PacketHeaderManager set hdrlen_ 0
foreach prot {
} {
   add-packet-header $prot
Simulator instproc create_packetformat {} {
   PacketHeaderManager instvar tab_
   set pm [new PacketHeaderManager]
   foreach cl [PacketHeader info subclass] {
       if [info exists tab_($cl)] {
           set off [$pm allochdr $cl]
           $cl offset $off
   $self set packetManager_ $pm
PacketHeaderManager instproc allochdr cl {
   set size [$cl set hdrlen_]
   $self instvar hdrlen_
   set NS_ALIGN 8 ;# round up to nearest NS_ALIGN bytes, (needed on sparc/solaris)
   set incr [expr ($size + ($NS_ALIGN-1)) & ~($NS_ALIGN-1)]
   set base $hdrlen_
   incr hdrlen_ $incr
   return $base


/* manages active packet header types */
class PacketHeaderManager : public TclObject {
   PacketHeaderManager() {
       bind("hdrlen_", &Packet::hdrlen_);

The code in ~ns/tcl/lib/ns-packet.tcl is executed when the simulator initializes. Thus, the foreach statement is executed before the simulation begins, and initializes the OTcl class array tab_ to contain the mapping between class the name and the names of the currently active packet header classes. As discussed above, packet headers should be accessed using hdr_<hdrname::access(). The create_packetformat{} instance procedure is part of the basic Simulator class and is called one time during simulator configuration. It first creates a single PacketHeaderManager object. The C++ constructor links the OTcl instance variable hdrlen_ (of class PacketHeaderManager) to the C++ variable Packet::hdrlen_ (a static member of the Packet class). This has the effect of setting Packet::hdrlen_ to zero. Note that binding across class types in this fashion is unusual. After creating the packet manager, the foreach loop enables each of the packet headers of interest. This loop iterates through the list of defined packet headers of the form (hi, oi) where hi is the name of the ith header and oiis the name of the variable containing the location of the hi header in BOB. The placement of headers is performed by the allochdr instproc of the PacketHeaderManager OTcl class. The procedure keeps a running variable hdrlen_ with the current length of BOB as new packet headers are enabled. It also arranges for 8-byte alignment for any newly-enabled packet header. This is needed to ensure that when double-world length quantities are used in packet headers on machines where double-word alignment is required, access faults are not produced. (In some processor architectures, including the Sparc and HP-PA, double-word access must be performed on a double-word boundary (i.e. addresses ending in 0 mod 8). Attempting to perform unaligned accesses result in an abnormal program termination.)

Commands at a glance

Following is a list of packet-header related procedures:


This is an internal simulator procedure and is called once during the simulator configuration to setup a packetHeaderManager object.


This is another internal procedure of Class PacketHeaderManager that keeps track of a variable called hdrlen_ as new packet-headers are enabled. It also allows 8-byte allignment for any newly-enabled pkt header.

add-packet-header takes a list of arguments, each of which is a packet header name (without PacketHeader/ prefix). This global proc will tell simulator to include the specified packet header(s) in your simulation.

remove-packet-header operates in the same syntax, but it removes the specified headers from your simulation; notice that it does not remove the common header even it is instructed to do so.

remove-all-packet-headers is a global Tcl proc. It takes no argument and removes all packet headers, except the common header, from your simulation. add-all-packet-headers is its counterpart.

Previous chapter: "Timers" | index | Next chapter: "Error Model"