ns Grid implementation notes
20 November 2000
Douglas S. J. De Couto

These notes describe bits about ns and the CMU wireless extensions
that I have had to know to implement a new protocol (the Grid
protocols) in the simulator.  Some of this may be wrong, but it
reflects my best understanding.  I *hope* nothing is wrong.  the
information comes from reading the CMU DSR implementation, Jinyang
Li's HGPS implementation, the ``ns Notes and Documentation'' of 25
February 2000 [ns-doc], and all the other ns source code.


Adding a new protocol
---------------------

Remember this: your fingers will be everywhere...

First define the packet format.  This is covered in [ns-doc], Chapter
11.  Edit ns/packet.h to add a PT_FOO, and the corresponding name in
PT_NAMES.  

Add your agent and packet header classes, e.g. in
ns/cmu/foo/fooagent.{h,hh} and ns/cmu/foo/hdr_foo.{h,cc}. Your
hdr_foo.cc file should create a header class object with a static
offset variable.  See hdr_grid.{h,cc}.  The header struct constructors
will never be called.  You must either not depend on these structs
being intialized, or do it yurself

Add trace support for your packets.  In ns/cmu/cmu-trace.{cc,h} add a
format_foo function.  In cmu-trace.h add the CMUTrace::_off_foo member
and the corresponding bind() in the CMUTrace constructor to hook up
your new packet format.  Modify CMUTrace::format() to call your new
format function for your packets.

packet.h has to be modified.

Add your packet type and packet offset to ns/tcl/lib/ns-packet.tcl in
the ``foreach pair ...'' clause.

Add support for processing the traces with your packets, e.g. create
ns/cmu/scripts/foo.pl by copying the Grid (grid.pl), HGPS, or DSR
processing file.

Add the Tcl support for your agent, create ns/cmu/foo/foo.tcl by
modifying grid.tcl etc.

Modify ns/Makefile.in to add your c++ implementation files to the OBJS
variable.  I don't know about dependencies.


Writing an Agent, e.g. FooAgent
-------------------------------

Your agent class will inherit from Agent.  You have to define
command() to handle the OTcl commands, and recv() to handle packets
received and originated at your agent.  Probably Terminate() should do
something useful about cleaning up.

You may also want to add a trace() function you can call to trace
special events for you agent.  It will just call the logtarget_'s
trace function and make sure it gets dumped to the log.  

the command() function executes your Agent's tcl commands, e.g. adding
parameters, starting the protocol, setting the logtarget_ and
mobile_node_ fields, and adding the link layer object.  See
gridagent.cc for an example.  You will probably want to assume that
Tcl code calls some of these functions on startup before others,
e.g. that "add-ll", "mobile-node", "log-target", and other
configuration parameters are set before calling "start-foo".

Timers:  ``Starting'' your protocol actually means starting any
retransmit or expiration timers you need.  Derive your timer class
from the TimerHandler base class.  Define the expire() method.  That
method will probably just call some method of your agent, and then
reschedule the timer by calling resched().  You construct the timers
with Agent, e.g. in the initializers line: ``FooAgent() :
my_timer_(this) { }''

Receiving packets: all packets come into your agent via the recv()
method.  You can see if it was originated by your node or received by
your node by checking the hdr_ip src_ field.  Also, since your
protocol may loop, I guess you check the hdr_cmn num_forwards_ field.
If you actually use it...

All packets in the simulator actually have all the configured in
packet headers.  Another part of the packet has the data.  Your agent
should explicitly set the size of the packet for delay calculations
etc. by the interface layer (e.g. how long it takes to transmit).
That size will depend on what headers your code think a packet actually
has at any point in time.  e.g. increment the existing size (set by
the data generation layer, e.g. CBR?) by the size of your headers.

You can figure out what headers are actually used in the packet by a
combination of the packet type (cmnh->type_) and context.  e.g. my
Grid implementation sets the hdr_grid_encap header for all PT_CBR data
packets.  All the packets should have the hdr_ip set (by the higher
layer e.g. CBR, or by the routing agent layer for routing protocol
packets).  Also, the hdr_cmn is set.  You should set the cmnh->sport
and dport to RT_PORT so the various multiplexers etc. channel the
packet to your agent.


Sending a packet: The hdr_ip should reflect the packet's original
source and ultimate destination in the src_ and dst_ fields.  the
hdr_cmn->next_hop_ should contain the next hop's address, and the
hdr_cmn_->addr_type_ should be set to AF_INET.  In my case I don't
want to screw around with a link layer, so I post packets directly to
the interface queue (ifq_) and fill in the MAC addresses myself, e.g.
mac_->hdr_dst(p->access(off_mac_), ntohl(next_hop)) where mac_ is the
802.11 MAC object, and next_hop is the network byte order IP of the
next hop.  MAC addresses in ns are the same as the IP addresses.  Then
ask the scheduler to send the packet,
e.g. Scheduler::instance().schedule(ifq_, p, delay) where ifq_ is the
interface queue object, p is the packet, and delay is how long to wait
(mostly will be 0).

Failure callbacks: set the hdr_cmn xmit_failure_ and
xmit_failure_data_ fields of the packet and your failure callback
function will be called with the packet if it can't transmit.  You
might to check your interface queue at this point.


ns addresses: ns uses integer node addresses, for IP addresses.  If
your code (like mine) uses network byte order host addresses, and you
find it convenient to give node 1 an IP address of 0.0.0.1, remember to use
htonl() and htonl() when exchanging addresses between your protocol
and ns.  The hdr_ip and hdr_cmn addresses are all in host byte order.





