This ns-3 project document is one of a set of project documents:

  * Software Architecture

  * Manual

  * Tutorial (this document)

This document is written in GNU Texinfo and is to be maintained in revision control on the ns-3 code server.  Both PDF and HTML versions should be available on the server.  Changes to the document should be discussed on the ns-developers@isi.edu mailing list.

ns-3 Tutorial (html version)
****************************

For a pdf version of this manual, see http://www.nsnam.org/docs/tutorial.pdf.

This ns-3 project document is one of a set of project documents:

  * Software Architecture

  * Manual

  * Tutorial (this document)

This document is written in GNU Texinfo and is to be maintained in revision control on the ns-3 code server.  Both PDF and HTML versions should be available on the server.  Changes to the document should be discussed on the ns-developers@isi.edu mailing list.

This software is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program.  If not, see http://www.gnu.org/licenses/.

Preface
*******

The purpose of this tutorial is to introduce new ns-3 users to the system in a structured way.  It is sometimes difficult for new users to glean essential information from detailed manuals and to convert this information into working simulations.  In this tutorial, we will build several example simulations, introducing and explaining key concepts and features as we go.

As the tutorial unfolds, we will introduce the full ns-3 documentation and provide pointers to source code for those interested in delving deeper into the workings of the system.

This document is one of a set of ns-3 project documents:

  * Software Architecture

  * Manual

  * Tutorial (this document)

This document is written in Texinfo and is to be maintained in revision control on the ns-3 code server.  Both PDF and HTML versions should be available on the server.  Changes to the document should be discussed on the ns-developers@isi.edu mailing list.

Introduction
************

The ns-3 project is a discrete-event network simulator targeted primarily for research and educational use.  It is aimed at comprehensively redesigning and enhancing the popular Network Simulator ns-2.

For those familiar with ns-2, the most visible outward change when moving to ns-3 is the choice of scripting language. ns-2 is typically scripted in Tcl and results of simulations are often visualized using the Network Animator nam.  In ns-3 there is currently no visualization module, and multiple language bindings are allowed.  In this tutorial, we will concentrate on scripting directly in C++ and interpreting results via trace files.  Scripting in other languages will typically be done via straightforward bindings of the target language into the underlying C++.

The goal of this tutorial is to introduce new users of ns-3 to enough of the system to enable them to author simple simulation scripts and extract useful information from the simulations.  We begin by introducing some of the other important resources that are available to those interested in using or writing scripts, models and even those interested in making contributions to the core ns-3 system.  We provide an overview of some of the important abstractions, design patterns and idioms used when writing ns-3 scripts, and then dig right in by begining to write simulation scripts, run them and interpret results.

After completing this tutorial, one should be able to:

  * Find documentation resources in the distribution and on the web;

  * Download and compile the ns-3 system;

  * Use the provided devices to author network simulations of fairly significant complexity;

  * Use the default value system to configure simulations;

  * Write new ns-3 applications;

  * Use the tracing subsystem.

Resources
*********

The Web
=======

There are several important resources of which any ns-3 user must be aware.  The main web site is located at http://www.nsnam.org and provides access to basic information about the ns-3 system. Detailed documentation is available through the main web site at http://www.nsnam.org/documents.html.

You can find documents relating to the system architecture from this page, and also gain access to the detailed software documentation.  The software system is documented in great detail using Doxygen.  There is a Wiki that complements the main ns-3 web site which you will find at http://www.nsnam.org/wiki/.

You will find user and developer FAQs there as well as troubleshooting guides, third-party contributed code, papers, etc. The source code may be found and browsed at http://code.nsnam.org/.

There you will find the current development tree in the repository named ns-3-dev. Past releases and experimental repositories of the core developers may also be found there.

Mercurial
=========

Complex software systems need some way to manage the organization and changes to the underlying code and documentation.  There are many ways to perform this feat, and you may have heard of some of the systems that are currently used to do this.  The Concurrent Version System (CVS) is probably the most well known.

The ns-3 project uses Mercurial as its source code management system. Although you do not need to know much about Mercurial in order to complete this tutorial, we recommend becoming familiar with Mercurial and using it to access the source code.  Mercurial has a web site at http://www.selenic.com/mercurial/, from which you can get binary or source releases of this Software Configuration Management (SCM) system.  Selenic (the developer of Mercurial) also provides a tutorial at http://www.selenic.com/mercurial/wiki/index.cgi/Tutorial/, and a QuickStart guide at http://www.selenic.com/mercurial/wiki/index.cgi/QuickStart/.

You can also find vital information about using Mercurial and ns-3 on the main ns-3 web site.

Waf
===

Once you have source code downloaded to your local system, you will need to compile that source to produce usable programs.  Just as in the case of source code management, there are many tools available to perform this function.  Probably the most famous of these tools is make.  Along with being the most famous, make is probably the most difficult to use in a very large and highly configurable system.  Because of this, many alternatives have been developed.  Recently these systems have been developed using the Python language.

The build system Waf is used on the ns-3 project.  It is one of the new generation of Python-based build systems.  You will not need to understand any Python to build the existing ns-3 system, and will only have to understand a tiny and intuitively obvious subset of Python in order to extend the system in most cases.

For those interested in the gory details of Waf, the main web site can be found at http://freehackers.org/\~tnagy/waf.html.

Environment, Idioms, and Design Patterns
========================================

As mentioned above, scripting in ns-3 is done in C++.  A working knowledge of C++ and object-oriented concepts is assumed in this document. We will take some time to review some of the more advanced concepts or possibly unfamiliar language features, idioms and design patterns as they appear.  We don't want this tutorial to devolve into a C++ tutorial, though, so we do expect a basic command of the language.  There are an almost unimaginable number of sources of information on C++ available on the web or in print.

If you are new to C++, you may want to find a tutorial- or cookbook-based book or web site and work through at least the basic features of the language before proceeding.

Environment
-----------

The ns-3 system uses the GNU ``toolchain'' for development. A software toolchain is the set of programming tools available in the given environment. For a quick review of what is included in the GNU toolchain see, http://en.wikipedia.org/wiki/GNU_toolchain.

Typically a ns-3 author will work in Linux or a Linux-like environment.  For those running under Windows, there do exist environments which simulate the Linux environment to various degrees.  The ns-3 project supports development in the Cygwin and the MinGW environments for these users.  See http://www.cygwin.com/ and http://www.mingw.org/ for details on downloading and using these systems.  I use Cygwin in these cases since it provides all of the Linux tools I know and love.  It can, however, sometimes be problematic due to the way it actually does its emulation, and sometimes interactions with other Windows software can cause problems.

If you do use Cygwin or MinGW; and use Logitech products, I will save you quite a bit of heartburn right off the bat and encourage you to take a look at the MinGW FAQ.

Search for ``Logitech'' and read the FAQ entry, ``why does make often crash creating a sh.exe.stackdump file when I try to compile my source code.'' Believe it or not, the Logitech Process Monitor insinuates itself into every DLL in the system when it is running.  It can cause your Cygwin or MinGW DLLs to die in mysterious ways and often prevents debuggers from running.  Beware of Logitech.

Idioms and Design Patterns
--------------------------

In any system, there are a number of problems to be solved that happen repeatedly.  Often the solutions to these problems can be generalized and applied in a similar way across the system.  These solutions are called Design Patterns.  The ns-3 system relies on several classic design patterns.

Also, in any language, there are constructs that, while they aren't part of the language per se, are commonly found and useful.  For example, at the lowest level a C programmer should be able to immediately recognize the purpose and intent  of the following code without having to reflect on the details:

  for (;;)

These low-level constructs, or idioms, extend upward in complexity, eventually becoming implementations of design patterns.  As you are exposed to more and more of the ns-3 system, you will begin to recognize and be comfortable with the C++ implementations (idioms) of several important design patterns.

The ns-3 code relies heavily on Generalized Functors, Callbacks, Smart Pointers, Singletons, and Object Factories.  Although we will not assume any detailed knowledge of the idioms and design patterns used in the ns-3 system, it will be useful for readers who intend to delve deeply into the system to understand some important related concepts.  We recommend two resources: Design Patterns: Elements of Reusable Object-Oriented Software and Modern C++ Design: Generic Programming and Design Patterns Applied.

Gamma addresses the abstract design patterns, and Alexandrescu addresses the C++ idioms you will often see throughout the ns-3 code.

Almost any use of ns-3 will require some basic knowledge of C++ templates. We will discuss the high-level uses in this tutorial.  However, if you venture deeply into the source code, you will see fairly heavy use of relatively sophisticated C++ templates in some of low-level modules of the system.  The You don't have to be a template guru to complete this tutorial but if you expect to work in ns-3 at a low level you will have to be somewhat fluent with templates.  If you  want to truly grok C++ templates we recommend, C++ Templates: The Complete Guide.

Socket Programming
==================

We will assume a basic facility with the Berkeley Sockets API in the examples used in this tutorial.  If you are new to sockets, we recommend reviewing the API and some common usage cases.  For a good overview of programming TCP/IP sockets we recommend Practical TCP/IP Sockets in C.

There is an associated web site that includes source for the examples in the book, which you can find at: http://cs.baylor.edu/~donahoo/practical/CSockets/.

If you understand the first four chapters of the book (or for those who do not have access to a copy of the book, the echo clients and servers shown in the website above) you will be in good shape to understand the tutorial. There is a similar book on Multicast Sockets, Multicast Sockets. that covers material you may need to understand for the multicast examples.

The Basics
**********

From this point forward, we are going to assume that the reader is working in Linux or a Linux emulation environment (Linux, Cygwin, etc.) and has the GNU toolchain installed and verified.

We are going to assume that you have Mercurial and Waf installed and running on the target system as described in the Getting Started section of the ns-3 web site: http://www.nsnam.org/getting_started.html.

Downloading
===========

The ns-3 code is available in Mercurial repositories on the server code.nsnam.org.  You can download a tarball, but we recommend working with Mercurial --- it will make your life easier in the long run.

If you go to the following link: http://code.nsnam.org/, you will see a number of repositories.  Many are the private repositories of the ns-3 development team.  The repositories of interest to you will be prefixed with ``ns-3''.  The current development snapshot (unreleased) of ns-3 may be found at: http://code.nsnam.org/ns-3-dev/.

The developers attempt to keep this repository in a consistent, working state but it is a development area with unreleased code present, so you may want to consider downloading an official release.

There will be a number of released repositories present at code.nsnam.org. These repos will have names like ns-3.0.1 --- which referes to release 3.0.1 of the network simulator (or if you like, release 0.1 of ns-3). Since the releases are changing at a rate of one per month, I will stick with the more constant ns-3-dev here, but you can replace the string ns-3-dev with your choice of release (e.g., ns-3.0.5) below.  You can find the latest version  of the code either by inspection of the repository list or by going to the ``Getting Started'' web page and looking for the latest release identifier.

I typically create a directory called repos in my home directory under which I keep all of my local Mercurial repositories.  Hint:  I will assume you do this later in the tutorial. If you adopt that approach, you can get a copy of the development version of ns-3 by typing the following into your Linux shell (I use bash).

  cd
  mkdir repos
  cd !$
  hg clone http://code.nanam.org/ns-3-dev

As the hg command executes, you should see something like the following,

  destination directory: ns-3-dev
  requesting all changes
  adding changesets
  adding manifests
  adding file changes
  added 1513 changesets with 5687 changes to 733 files
  358 files updated, 0 files merged, 0 files removed, 0 files unresolved

After the clone command completes, you should have a directory called ns-3-dev under your ~/repos directory, the contents of which should look something like the following:

  AUTHORS  RELEASE_NOTES  examples/  src/       waf*
  LICENSE  VERSION        ns3/       tutorial/  waf.bat*
  README   doc/           samples/   utils/     wscript

You are now ready to build the ns-3 distribution.

Building
========

We use Waf to build the ns-3 project.  The first thing you will need to do is to configure the build.  For reasons that will become clear later, we are going to work with debug builds in the tutorial.  To explain to Waf that it should do debug builds you will need to execute the following command,

  ./waf -d debug configure

This runs the copy of Waf in the local directory (which is provided as a convenience for you).  As the build system checks for various dependencies you should see output that looks similar to the following,

  ~/repos/ns-3-dev >./waf -d debug configure
  Checking for program g++                : ok /usr/bin/g++
  Checking for program cpp                : ok /usr/bin/cpp
  Checking for program ar                 : ok /usr/bin/ar
  Checking for program ranlib             : ok /usr/bin/ranlib
  Checking for compiler could create programs : ok
  Checking for compiler could create shared libs : ok
  Checking for compiler could create static libs : ok
  Checking for flags -Wall                       : ok
  Checking for flags -O2                         : ok
  Checking for flags -g -DDEBUG                  : ok
  Checking for flags -g3 -O0 -DDEBUG             : ok
  Checking for g++                               : ok
  Checking for header stdlib.h                   : ok
  Checking for header stdlib.h                   : ok
  Checking for header signal.h                   : ok
  Checking for high precision time implementation: 128-bit integer
  Checking for header stdint.h                   : ok
  Checking for header inttypes.h                 : ok
  Checking for header sys/inttypes.h             : not found
  Configuration finished successfully; project is now ready to build.
  ~/repos/ns-3-dev >

The build system is now configured and you can build the debug versions of the ns-3 programs by simply typing,

  ./waf

You will see many Waf status messages displayed as the system compiles.  The most important is the last one,

  Compilation finished successfully

Running a Script
================

We typically run scripts under the control of Waf.  This allows the build system to ensure that the shared library paths are set correctly and that the libraries are available at run time.  To run a program, simply use the run option in Waf.  Let's run the ns-3 equivalent of the hello world program by typing the following:

  ./waf --run hello-simulator

Waf first checks to make sure that the program is built correctly and executes a build if required.  Waf then then executes the program, which produces the following output.

  Hello Simulator

Congratulations.  You are now an ns-3 user.

Some Prerequisites
******************

The first thing we need to do before actually starting to code is to explain a few core concepts, abstractions and idioms in the system.  Much of this may appear transparently obvious to some, but we recommend taking the time to read through this chapter just to ensure you are starting on a firm foundation.

Abstractions
============

In this section, we'll review some terms that are commonly used in networking, but have a specific meaning in ns-3.

Node
----

In Internet jargon, a computing device that connects to a network is called a host or sometimes an end system.  Because ns-3 is a network simulator, not specifically an Internet simulator, we intentionally do not use the term host since it is closely associated with the Internet and its protocols.  Instead, we use a more generic term also used by other simulators that originates in Graph Theory --- the node.

In ns-3 the basic computing device abstraction is called the node.  This abstraction is represented in C++ by the class Node.  The Node class provides methods for managing the representations of computing devices in simulations.  Developers are expected to specialize the Node in the object-oriented programming sense to create new computing device models.  In this tutorial, we will use a specialization of class Node called InternetNode.  As you might expect, the InternetNode is a class that represents a host in the Internet sense, and automatically provides core IPv4 networking protocols.

You should think of a Node as a computer to which you will add functionality.  One adds things like applications, protocol stacks and peripheral cards with their associated drivers to enable the computer to do useful work.  We use the same basic model in ns-3.

Application
-----------

Typically, computer software is divided into two broad classes.  System Software organizes various computer resources such as memory, processor cycles, disk, network, etc., according to some computing model.  System software usually does not use those resources to complete tasks that directly benefit a user.  A user would typically run an application that acquires and uses the resources controlled by the system software to accomplish some goal.

Often, the line of separation between system and application software is made at the privilege level change that happens in operating system traps. In ns-3 there is no real concept of operating system and especially no concept of privilege levels or system calls.  We do, however, have the idea of an application.  Just as software applications run on computers to perform tasks in the ``real world,'' ns-3 applications run on ns-3 Nodes to drive simulations in the simulated world.

In ns-3 the basic abstraction for a user program that generates some activity to be simulated is the application.  This abstraction is represented in C++ by the class Application.  The Application class provides methods for managing the representations of our version of user-level applications in simulations.  Developers are expected to specialize the Application in the object-oriented programming sense to create new applications.  In this tutorial, we will use specializations of class Application called UdpEchoClient and UdpEchoServer. As you might expect, these applications compose a client/server application set used to generate and echo simulated network packets

Channel
-------

In the real world, one can connect a computer to a network.  Often the media over which data flows in these netowrks are called channels.  When you connect your Ethernet cable to the plug in the wall, you are connecting your computer to an Ethernet communication channel.  In the simulated world of ns-3 one connects a Node to an object representing a communication channel.  Here the basic communication subnetwork abstraction is called the channel and is represented in C++ by the class Channel.

The Channel class provides methods for managing communication subnetwork objects and connecting nodes to them.  They may also be specialized by developers in the object oriented programming sense.  A Channel specialization may model something as simple as a wire.  The specialized Channel can also model things as complicated as a large Ethernet switch, or three-dimensional space in the case of wireless networks.

We will use specialized versions of the Channel called CsmaChannel and PointToPointChannel in this tutorial.  The CsmaChannel, for example, models a version of a communication subnetwork that implements a carrier sense multiple access communication medium. This gives us Ethernet-like functionality.

Net Device
----------

It used to be the case that if you wanted to connect a computers to a network, you had to buy a specific kind of network cable and a hardware device called (in PC terminology) a peripheral card that needed to be installed in your computer.  These cards were called Network Interface Cards, or NICs.  Today most computers come with the network controller hardware built in and users don't see these building blocks.

A NIC will not work without a software driver to control the hardware.  In Unix (or Linux), a piece of peripheral hardware is classified as a device.  Devices are controlled using device drivers, and network devices (NICs) are controlled using network device drivers collectively known as net devices.  In Unix and Linux you refer to these net devices by names such as eth0.

In ns-3 the net device abstraction covers both the software driver and the simulated hardware.  A net device is ``attached'' to a Node in order to enable the Node to communicate with other Nodes in the simulation via Channels.  Just as in a real computer, a Node may be connected to more than one Channel via multiple NetDevices.

The net device abstraction is represented in C++ by the class NetDevice. The NetDevice class provides methods for managing connections to Node and Channel objects; and may be specialized by developers in the object-oriented programming sense.  We will use the specialized version of the NetDevice called the CsmaNetDevice in this tutorial. Just as an Ethernet NIC is designed to work with an Ethernet network, the CsmaNetDevice is designed to work with a CsmaChannel.

Topology Helpers
----------------

In a real network, you will find host computers with added (or built-in) NICs.  In ns-3 we would say that you will find Nodes with attached NetDevices.  In a large simulated network you will need to arrange many connections between Nodes, NetDevices and Channels.

Since connecting a NetDevice to a Node, and a NetDevice to a Channel is such a common task in ns-3 we provide what we call topology helpers to make this as easy as possible.  Topology helpers perform much of the dirty work of creating and connecting net devices. For example, it may take several distinct method calls to create a NetDevice, add a MAC address, connect the net device to a Node and configure the protocol stack, and then connect the NetDevice to a Channel. We use topology helper functions to compose those distinct operations into an easy to use model.

Topology helper functions use the abstractions (described above) of Network Interface Cards and Cables.  When you think of adding a new kind of network, you may think of going out to the local computer retailer and buying a kit. This kit might include a nework cable and some number of peripheral cards and thier associated software drivers.  You can think of topology helpers in roughly the same way.  Instead of buying a kit for a given type of network, you will use a topology helper class for a given type of network, to accomplish the equivalent of installing the network ``kit.''

Important Idioms
================

Now that we have identified that there are C++ classes in the system called Node and InternetNode, we need to understand how to bring objects of these classes into existance, and manage their lifetimes.  Let's examine this in some detail here.

In ns-3, if we want to create an InternetNode in a script, we will typically do something like the following example:

  Ptr<Node> p = Create<InternetNode> ();

To some, it may seem intuitively obvious that we're creating an InternetNode object and assigning responsibility for managing the object to a smart pointer named p.  For the rest of us, there may be a lot in that line that is unfamiliar, so let's look at what this line means in some detail.

Templates 101
-------------

If you are familiar with C++ templates, you may skip this section as it is just a cursory introduction to function and class templates.

Referring back to the example line of code, reproduced below for your convenience, the angle brackets you see in the code indicate that we are using C++ templates.

  Ptr<Node> p = Create<InternetNode> ();

The purpose of templates is to allow a programmer to write one version of code that is applicable over multiple types.  Some people consider templates to be an enhancement of the C preprocessor macro functionality.  At some level this comparison reveal some similarities, but C++ templates are really quite different.

In C++, just as with most language constructs, templates are declared, defined and used.  A declaration of a template might look something like,

  template <typename T> T Add (T first, T second);

This line uses the keyword template followed by a declaration of a type name (in this case T) in angle brackets.  The angle brackets should indicate to you that a template is being declared, defined or used. The type name T can be thought of as a string that will be substitited during the use phase of the template.  For example, the T may be replaced by the word int.  It is this substitution that leads people to compare templates with macros.

Without going into too much more detail, this snippet declares that a piece of code exists that will be able to call a function Add that will add arbitrary types together.  The T will be eventually replaced by a C++ data type name.  For example,

  T Add (T first, T second);

might eventually become

  int Add (int first, int second);

If the template has been declared, we need to define what that piece of code will actually do.  That might look something like,

  template <typename T>
  T Add (T first, T second)
  {
    return first + second;
  }

All we've done here is to provide an implementation of the template that adds the two variables together and returns the result.  Note that this implementation works for any type that provides an operator+.

The puzzle all comes together when you understand that using a template causes the compiler to automatically instantiate code for a specific function according to the given template parameters.  You might use the above template like,

  int x, y, z;
  z = Add<int> (x, y);

When the compiler sees Add<int> it understands that it needs to make sure that code is instantiated (created) to perform the Add using the specified type <int>.  To a first approximation, the compiler will replace the typename T with the specified type int and automagically generate code equivalent to,

  int Add (int first, int second)
  {
    return first + second;
  }

A user of the template definition could just as easily have provided a use that assigned the type float.  This would simply be done like,

  float x, y, z;
  z = Add<float> (x, y);

In this case, the compiler would automatically generate code that looked like,

  float Add (float first, float second)
  {
    return first + second;
  }

This particular kind of template programming uses what are called function templates.  They are called function templates since you are templating function declarations and definitions.

Templates can also be used in conjunction with classes, in which case you are said to be using, not too surprisingly, class templates.  The syntax and use is similar.  To declare a class template you might use something like,

  template <typename T>
  class MyStack
  {
    void Push (T data);
    T Pop (void);
  };

The methods can be defined separately in a method similar to function template definitions,

  template <typename T> void MyStack<T>::Push (T data)
  {
    ...
  };

You can then use the new templated class in the following way,

  int x, y;

  MyStack<int> stack;
  stack.Push (x);
  y = stack.Pop ();

Similarly to the function template case, the compiler knows that it has to automatically generate code to fill out the class and method declarations and definitions using the appropriate type specified by <int>.

Smart Pointers 101
------------------

If you are familiar with C++ smart pointers, you may skip this section as it is just a cursory introduction to smart pointers and intrusive reference counting.

Referring back to the example line of code, partially reproduced below for your convenience below, the left hand side is the declaration and initialization of a class template that implements a smart pointer.

  Ptr<Node> p = ...

To a first approximation, you can think of Ptr<Node> as the a new kind of declaration of a pointer to a Node object.  The difference is that a smart pointer is a user-defined data type (instantiated via a templated class) that simulates a classical pointer but provides additional features.  As an aside, you typically pronounce  Ptr<Node> as ``pooter node'' where pooter rhymes with footer.

One of the most important ``additional feature'' provided by smart pointers is automatic memory management.  Since you now understand class templates, you will understand how the template allows us to write the pointer code once, but allows us to point to many different kinds of objects.  Later in the tutorial you will see variations such as Ptr<Ipv4> and Ptr<Channel>, which are smart pointers to an IP version 4 object and a channel object, respectively.

The use of built-in pointers in C and C++ is a major source of bugs.  Constant allocation of, passing of responsibility for, and deallocation of underlying data makes it very likely that errors will occur.  In one of these errors, the usual problem is that the responsibility for deallocating a memory block is misplaced.  This may result in a memory leak or a duplicate deallocation. Smart pointers try to prevent this kind of problem by working with the scope and extent rules of the language to make memory deallocation automatic.

The scope of a variable defines where in a program a given variable may be referred to.  The extent of a variable defines when in the program's execution the variable has a valid value.  Consider a simple subroutine that contains a smart pointer.

  void SimpleSubroutine (void)
  {
    Ptr<Node> p;
  }

The variable named p has a scope limited to the subroutine itself.  The variable is said to come into scope as the subroutine is entered during execution.  At this time, the constructor of the underlying class is executed and a valid variable is available for use.  When the subroutine is done executing, the variable is said to go out of scope.  This causes the destructor of the underlying class to be executed and the variable no longer has a valid value.  This is not a problem since it is no longer valid to refer to the parameter.  Smart pointers take advantage of these defined actions at points where variables must be valid and become discardable to determine when underlying data can be freed.

The ns-3 smart pointer mechanism uses a mechanism called intrusive reference counting to determine when a memory block should be automatically deallocated.  The term ``intrusive'' means that a reference count (a count of variables required to have valid data) is stored in the object being managed instead of in a proxy object.  This means that each piece of memory managed by a ns-3 smart pointer includes a reference count.  When a smart pointer to a reference counted object is created, this reference count is incremented.  This indicates that a new variable requires a valid data object be present.  When a smart pointer to a reference counted object is destroyed (for example, when going out of scope) the reference count of the managed object is decremented.  When the reference count goes to zero it means that all smart pointers to the underlying object have gone out of scope and the object is no longer needed by any past ``users'' of the object.  This in turn means that the object can be safely deallocated, and this is done automatically for you as the ``last'' smart pointer goes out of scope.

Consider how this might work as you pass a smart pointer to an object down a protocol stack.  At each level of the stack, you pass the smart pointer by value.  This causes a copy of the smart pointer to be made, which increments the reference count of the underlying object.  When the calling method is done executing, the calling smart pointer goes out of scope and  the reference count is decremented.  This leaves the single smart pointer in the called method with a reference to the underlying object. When the smart pointer in the called method goes out of scope, the destructor for the smart pointer is called.  The destructor checks the reference count of the underlying object and sees that it becomes zero.  This indicates that the object can be deallocated, and the destructor does so.  This results in the lifetime management of the underlying object being automatically managed, a boon if you have experience with ``manual'' memory management and finding memory leaks.

Now, we want to make this feature available as widely as possible to objects in the ns-3 system.  The basic operations of the smart pointer class are the same across any intrusively reference counted object.  C++ provides a mechanism to achieve this kind of generic behavior --- the template.  Let's examine the declaration of the smart pointer in more detail.  First consider the way you might declare and use a built-in pointer.  For the sake of simplicity, just assume that a C++ object of the class MyClass exists. Further assume that MyClass provides one method called method. Using built-in pointers, you could do something like the following:

  MyClass *p = ...
  p->method ();

One of the key design points of smart pointers is that they should simulate built-in pointers.  In C++ this is done by overloading operator->, operator= and operator*.  To implement a smart pointer we need to provide a generic class that implements these operators.  This generic class should allow operations that appear as if it were a built-in pointer to the reference counted object. Typically this is accomplished via a relatively simple C++ class template.  If you are interested in the details of how this may be accomplished, see Alexandrescu for a good treatment,

Taking the template as given, in order to declare a smart pointer you will need to create a smart pointer object and provide the template parameter needed to instantiate the required code.  This parameter will be the name of the reference counted class to which you want to point.  The smart pointer class overrides operator= which allows initialization of the smart pointer just as if it were a built-in pointer.  The end result is that you use smart pointers just as if they were built-in pointers:

  SmartPointer<MyClass> p = ...
  p->method ();

Object Creation
---------------

On the right hand side of the line of code we're examining (reproduced below for convenience) is the creation of an InternetNode object.

  ... = Create<InternetNode> ();

This turns out to be an instance of use of a C++ function template. The definition of the Create<typename T>() template calls the new operator to create an object of the type T.  It then creates a new smart pointer of the appropriate type (i.e., Ptr<T>).  This new smart pointer is assigned initial responsibility for the new object which has its reference count set to one.

Since the underlying creation mechanism is via the new operator, and you can pass parameters to the constructor for an object, we provide several templates that you can use for passing parameters to the object constructors. If the constructor for the object requires a parameter, you simply pass that parameter to the Create function like this,

  int parm = 1;
  ... = Create<MyClass> (parm);

We provide Create templates with up to seven parameters, so you could conceivably use the Create template in situations such as,

  int parm = 1;
  ... = Create<MyClass> (p1, p2, p3, p4, p5, p6, p7);

Type Safety
-----------

Lets take one final look at the now infamous example line of code that we have been examining for some time (again reproduced below).

  Ptr<Node> p = Create<InternetNode> ();

You may have noticed that the smart pointer on the left hand side of the assignment is associated with the type Node and the Create template on the right hand side creates an InternetNode object and returns a Ptr<InternetNode> smart pointer.  For this assignment of a Ptr<InternetNode> to a Ptr<Node> to work, there must be some kind of type conversion going on.

Many programmers use implicit conversions without even realizing it since they are sometimes so intuitive.  For example, in the following code,

  int i = 1;
  double d = 2.;
  if (n == d) ...

the integer (1) is implicitly converted to a double (1.) before the comparison takes place.  This conversion is performed using what is known as a C++ standard conversion.  There are a number of standard conversions defined by the C++ standard.  Among them are,

  * Integral Promotions

  * Integral Conversions

  * Floating Conversions

  * Pointer Conversions

  * Reference Conversions

For the case of interest here, we need to know what happens in the assignment operator (operator=) of our smart pointer Ptr<Node>. This operator takes a reference to a Ptr<Node> and not a reference to a Ptr<InternetNode>.  The one situation where this works automatically in C++ is if the ``destination'' reference is to a visible, unambiguous base class of the ``source'' reference.  In this case, the underlying pointer is cast from one type to the other automatically.

To summarize: The magic happens in the assignment operator.  Class InternetNode inherits from class Node.  The reference to the InternetNode object in question is, in essence, a pointer to an InternetNode object.  The InternetNode class inherits from the Node base class in a way that makes Node visible and unambiguous. Therefore, there exists a standard conversion from an InternetNode * to a Node * and by extension from an InternetNode & to a Node &.  This conversion is applied automatically (and invisibly) during paramater passing in the assignment operator we are examining.

This is a rather involved way of saying there's an invisible pointer cast to a base class happening in the assignment.  That means that

  Ptr<Node> p = Create<InternetNode> ();

or,

  Ptr<Channel> p = Create<CsmaChannel> ();

will work just fine.  Of course, if you try something bad (TM), like:

  Ptr<Node> p = Create<CsmaChannel> ();

the compiler will quite appropriately complain that there is no conversion between these completely unrelated objects (CsmaChannel and Node).

Summary
-------

Going back to our infamous first line of ns-3 code, we said that if we want to create an InternetNode in a script, we will typically do something like:

  Ptr<Node> p = Create<InternetNode> ();

Now we know that this is really a simple statement.  We create an InternetNode object on the heap (indirecly using operator new and passing no parameters to its constructor) and assign responsibility for managing the new object's lifetime to a smart pointer.  This smart pointer is a pointer to a Node object, so there was a hidden cast from InternetNode to a Node done via a standard C++ conversion.

This may have been quite a hurdle to get past that first line of code, but we have covered quite a few of the important idioms that you'll encounter in this tutorial.

A First ns-3 script
*******************

Lets build a simple network using the ns-3 design patterns, idioms, classes and helpers we have just looked at.  If you downloaded the system as was suggested above, you will have a release of ns-3 in a directory called repos under your home directory.  Change into that directory, where you should see a directory structure something like the following.

  AUTHORS  RELEASE_NOTES  examples/  src/       waf*
  LICENSE  VERSION        ns3/       tutorial/  waf.bat*
  README   doc/           samples/   utils/     wscript

Change into the tutorial directory.  You should see a file named hello-simulator.cc located there.  Copy this file into one named simple.cc.  If you open this new file in your favorite editor you will see some copyright information and the following C++ code:

  #include "ns3/log.h"

  NS_LOG_COMPONENT_DEFINE ("HelloSimulator");

  using namespace ns3;

  int
  main (int argc, char *argv[])
  {
    LogComponentEnable ("HelloSimulator", LOG_LEVEL_INFO);

    NS_LOG_INFO ("Hello Simulator");
  }

This is the ns-3 version of the ubiquitous hello-world program.  It uses the ns-3 Log module to print ``Hello Simulator'' into the standard error output stream.

Log components are named objects that provide for controlling the verbosity of debugging output in the system.  We'll have a lot more to say about logging later on, but for now you can just consider the macro NS_LOG_INFO to be a kind of fancy printf to the standard error.

A Simple Network
================

Let's create a simple network of InternetNode elements.  In order to actually create an InternetNode, you will have to include some header files.  Put the following code after the include statement in simple.cc.

  #include "ns3/ptr.h"
  #include "ns3/internet-node.h"

The ns-3 build system places the core include files it needs into a directory called ns-3 and so whenever you need to include one of the core files you need to explicitly code this.  The file ptr.h defines the generic smart pointer that we use.  The file internet-node.h defines the class InternetNode which, as described above, represents an IP version 4-based computing element in the simulator.

So let's create a few new InternetNodes by adding the following lines of code after the call to NS_LOG_INFO in the simple.cc file right after the call to NS_LOG_INFO.

  Ptr<Node> n0 = Create<InternetNode> ();
  Ptr<Node> n1 = Create<InternetNode> ();
  Ptr<Node> n2 = Create<InternetNode> ();
  Ptr<Node> n3 = Create<InternetNode> ();

As we now understand, this will create four InternetNode objects on the heap and create four Ptr<Node> smart pointer objects on the stack to manage them.  You should remember that by using the smart pointers you are freed from the responsibility to delete the objects you assign to them.

The next step is to create a channel over which these nodes can communicate. Let's use the CsmaChannel and create a local area network that will allow us to hook up nodes similarly to an Ethernet.

As usual, we'll need to include the file that provides the appropriate class declarations:

  #include "ns3/csma-channel.h"

Next, Add the following line of code (typically done after node creation) to create a channel with a five megabit per second data rate and a two millisecond speed-of-light delay between all nodes.  The idiom for creating the channel is similar to that of the node, but the actual Create function is hidden from us in the topology code.  Observe that we are using a Csma topology helper function to free us from the details regarding how the Carrier Sense Multiple Access Channel is actually brought into existence and initialized.

  Ptr<CsmaChannel> lan =
    CsmaTopology::CreateCsmaChannel (DataRate (5000000), MilliSeconds (2));

You may be unfamiliar with the unnamed parameter idiom used here. When added to a list of parameters, the code DataRate (5000000) constructs a DataRate object on the stack using the appropriate  constructor. The resulting object has no name, and therefore cannot be referenced elsewhere, but is passed to the callee method where it has a valid name and can be used.  This idiom is essentially a shorthand version of the following:

  DataRate rate (5000000);
  Time latency (MilliSeconds (2));
  Ptr<CsmaChannel> lan = CsmaTopology::CreateCsmaChannel (rate, latency);

We should pause for a moment and discuss the constructor to the Time data type.  There are a number of different constructors for these objects, and so there are a number of ways that this initialization could have been done. There is a constructor that takes a string argument, consisting of expressions using the units s, ms, us, ns, ps or fs, so this could have been written,

  Time latency ("2ms");

There are also helper functions available that create time units (one of these was used in the example):

  * Seconds (double)

  * MilliSeconds (uint64_t)

  * MicroSeconds (uint64_t)

  * NanoSeconds (uint64_t)

  * PicoSeconds (uint64_t)

  * FemtoSeconds (uint64_t)

C++ will attempt to promote parameters appropriately, but you will typically see constructions that respect the type corrrectness of the constructor, as in Seconds (1.) and MilliSeconds (2).  Notice that the code Seconds (1) will work just as well as Seconds (1.) since the integer 1 will be automatically promoted to a double 1. in the former code. The converse will not work --- i.e., you cannot write code that says MilliSeconds (2.) since a type demotion would be required that could lose information and the compiler will not do such things ``behind your back.''  Don't be thrown off by this kind of automatic conversion.

Okay, now we have code to create four nodes and a local area network.  The next step is to wire the network together.  We do this by adding net devices to the node.  When we add the net device, we also specify the network to which the net device is connected and provide a MAC address appropriate to the device and network types.  Since we're creating an IP version 4 network using a Csma channel, you may expect that we'll be using topology helpers appropriate to those types --- the CsmaIpv4Topology helper.  As you may expect, we'll need to include some files to get the appropriate definitions:

  #include "ns3/mac48-address.h"
  #include "ns3/csma-net-device.h"
  #include "ns3/csma-topology.h"
  #include "ns3/csma-ipv4-topology.h"

Now, all that is left is to do the ``wiring'':

  uint32_t nd0 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n0, lan,
    Mac48Address("08:00:2e:00:00:00"));

[Note the additional unnamed parameter idiom usage here.]

This code calls the topology helper relating to Csma channels and IP version four nodes.  It asks to install a Csma net device ``into'' node zero (n0) connecting the device to the channel named (lan).  It also assigns a MAC address to the net device.  You can add similar lines of code connecting the other nodes to the lan (remembering to assign new MAC addresses).

  uint32_t nd1 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n1, lan,
    Mac48Address("08:00:2e:00:00:01"));

  uint32_t nd2 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n2, lan,
    Mac48Address("08:00:2e:00:00:02"));

  uint32_t nd3 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n3, lan,
    Mac48Address("08:00:2e:00:00:03"));

Finally, we need to add IP addresses to our nodes.  The pointers to the nodes are stored in n0, n1, n2 and n3.  We added net devices to each of the nodes and remembered the net device index numbers as nd0, nd1, nd2 and nd3.  You can add multiple net devices to each node resulting in a situation similar to a multi-homed host.  Each time you add a net device, you will get a new index.  Since the IP address for a multi-homed host is associated with a net device, we need to provide that index (which we have saved) to the topology helper.  We provide an IP version four address via the ns-3 class Ipv4Address which takes a dotted decimal string as a constructor parameter.  We also provide a network mask using the ns-3 class Ipv4Mask which also takes a dotted decimal string.  The code to perform the IP address assignment, then, looks like the following:

  CsmaIpv4Topology::AddIpv4Address (n0, nd0, Ipv4Address ("10.1.1.1"),
    Ipv4Mask ("255.255.255.0"));

  CsmaIpv4Topology::AddIpv4Address (n1, nd1, Ipv4Address ("10.1.1.2"),
    Ipv4Mask ("255.255.255.0"));

  CsmaIpv4Topology::AddIpv4Address (n2, nd2, Ipv4Address ("10.1.1.3"),
    Ipv4Mask ("255.255.255.0"));

  CsmaIpv4Topology::AddIpv4Address (n3, nd3, Ipv4Address ("10.1.1.4"),
    Ipv4Mask ("255.255.255.0"));

We have now constructed a simulated network.  Your code should now look something like the following,

  #include "ns3/log.h"
  #include "ns3/ptr.h"
  #include "ns3/internet-node.h"
  #include "ns3/csma-channel.h"
  #include "ns3/mac48-address.h"
  #include "ns3/csma-net-device.h"
  #include "ns3/csma-topology.h"
  #include "ns3/csma-ipv4-topology.h"

  NS_LOG_COMPONENT_DEFINE ("HelloSimulator");

  using namespace ns3;

  int
  main (int argc, char *argv[])
  {
    LogComponentEnable ("HelloSimulator", LOG_LEVEL_INFO);

    NS_LOG_INFO ("Hello Simulator");

    Ptr<Node> n0 = Create<InternetNode> ();
    Ptr<Node> n1 = Create<InternetNode> ();
    Ptr<Node> n2 = Create<InternetNode> ();
    Ptr<Node> n3 = Create<InternetNode> ();

    Ptr<CsmaChannel> lan =
      CsmaTopology::CreateCsmaChannel (DataRate (5000000), MilliSeconds (2));

    uint32_t nd0 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n0, lan,
      Mac48Address("08:00:2e:00:00:00"));

    uint32_t nd1 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n1, lan,
      Mac48Address("08:00:2e:00:00:01"));

    uint32_t nd2 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n2, lan,
      Mac48Address("08:00:2e:00:00:02"));

    uint32_t nd3 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n3, lan,
      Mac48Address("08:00:2e:00:00:03"));

    CsmaIpv4Topology::AddIpv4Address (n0, nd0, Ipv4Address ("10.1.1.1"),
      Ipv4Mask ("255.255.255.0"));

    CsmaIpv4Topology::AddIpv4Address (n1, nd1, Ipv4Address ("10.1.1.2"),
      Ipv4Mask ("255.255.255.0"));

    CsmaIpv4Topology::AddIpv4Address (n2, nd2, Ipv4Address ("10.1.1.3"),
      Ipv4Mask ("255.255.255.0"));

    CsmaIpv4Topology::AddIpv4Address (n3, nd3, Ipv4Address ("10.1.1.4"),
      Ipv4Mask ("255.255.255.0"));
  }

This script won't actually do anything yet.  The next trick will be to convince our nodes to try and send some data over the network.

Using Applications
==================

As mentioned above, we use Applications in ns-3 to generate the data used to drive simulations.  An Application is added to a ns-3 node conceptually just as if you would add an application to a computer.  When an application is created (using the Create template) we tell the application which Node it belongs to (and therefore on which node it is running) by passing a smart pointer to that Node in the constructor arguments.

A UDP Echo Client Application
-----------------------------

To use an application, we first have to load the header file in which it is defined.  For the UDP echo client, this would mean adding the line,

#include "ns3/udp-echo-client.h"

In order to create the UDP echo client application we will need to add the following code:

  uint32_t packetSize = 1024;
  uint16_t port = 7;
  uint32_t maxPacketCount = 1;
  Time interPacketInterval = Seconds (1.);

  Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n0, "10.1.1.2", port,
    maxPacketCount, interPacketInterval, packetSize);

The first four lines have broken out the configuration parameters for the application as named parameters for clarity.  We are telling the application to generate 1024 byte packets (packetSize = 1024); and to send these packets to port 7 (port = 7;).  The application is told to send at most one packet (maxPacketCount = 1;); and to delay for one second between packet sends (interpacketInterval = Seconds(1.)) which is not used since only one packet is sent.  We will defer addressing the type Time until we discuss the simulator engine.  For now just understand the semantics are to wait for one second.

The code to actually create the UdpEchoClient application uses the same creation idiom as we have used previously.  Notice that we have a case where the Create template is used to pass parameters to the constructor of the underlying object.

Notice that a string is passed as the second parameter.  The formal parameter to the constructor of the UdpEchoClient object is actually an Ipv4Address.  We get away with this since C++ allows what are called implicit conversion sequences to occur between the argument in the function call and the corresponding parameter in the function declaration. Basically, C++ will try to figure out a way to convert parameters for you transparently.

In this case the conversion sequence is based on the constructor for the Ipv4Address that takes a char const * as a parameter.  C++ notices that "10.1.1.2" refers to a char const * and knows that it needs to get from there to an Ipv4Address.  The compiler notices that there is an Ipv4Address constructor that takes a char const * and so it uses that constructor transparently to arrange for the conversion.

You therefore have several options for passing this value.  You can use an explicit named variable as in the following:

  Ipv4Address addr ("10.1.1.2");
  ...

  Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n0, addr, port,
    maxPacketCount, interPacketInterval, packetSize);

You can use the unnamed parameter idiom that we have previously seen:

  Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n0,
    Ipv4Address ("10.1.1.2"), port, maxPacketCount, interPacketInterval,
    packetSize);

Or you can rely on implicit conversion sequences as we just saw:

  Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n0, "10.1.1.2", port,
    maxPacketCount, interPacketInterval, packetSize);

Which approach to take is a matter of style, really, and you will probably see all three approaches taken in the ns-3 code.  You should be comfortable seeing and using all three methods.

A UDP Echo Server Application
-----------------------------

As usual, to use the UDP echo server we need to add a line to define the application:

#include "ns3/udp-echo-server.h"

In order to create the UDP echo server application we will need to add the following code:

  Ptr<UdpEchoServer> server = Create<UdpEchoServer> (n1, port);

We only need to tell the application which node to reside on and which port to listen on for UDP packets.  The code to actually create the UdpEchoServer application uses the now quite familiar ns-3 object creation idiom.

A UDP Echo Client-Server Simulation
-----------------------------------

Now we're getting somewhere.  Your code should look something like the following (let's change the log component name and program banner from ``Hello Simulator''to something more descriptive while we're at it).

  #include "ns3/log.h"
  #include "ns3/ptr.h"
  #include "ns3/internet-node.h"
  #include "ns3/csma-channel.h"
  #include "ns3/mac48-address.h"
  #include "ns3/csma-net-device.h"
  #include "ns3/csma-topology.h"
  #include "ns3/csma-ipv4-topology.h"
  #include "ns3/udp-echo-client.h"
  #include "ns3/udp-echo-server.h"

  NS_LOG_COMPONENT_DEFINE ("UdpEchoSimulation");

  using namespace ns3;

  int
  main (int argc, char *argv[])
  {
    LogComponentEnable ("UdpEchoSimulation", LOG_LEVEL_INFO);

    NS_LOG_INFO ("UDP Echo Simulation");

    Ptr<Node> n0 = Create<InternetNode> ();
    Ptr<Node> n1 = Create<InternetNode> ();
    Ptr<Node> n2 = Create<InternetNode> ();
    Ptr<Node> n3 = Create<InternetNode> ();

    Ptr<CsmaChannel> lan =
      CsmaTopology::CreateCsmaChannel (DataRate (5000000), MilliSeconds (2));

    uint32_t nd0 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n0, lan,
      Mac48Address("08:00:2e:00:00:00"));

    uint32_t nd1 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n1, lan,
      Mac48Address("08:00:2e:00:00:01"));

    uint32_t nd2 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n2, lan,
      Mac48Address("08:00:2e:00:00:02"));

    uint32_t nd3 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n3, lan,
      Mac48Address("08:00:2e:00:00:03"));

    CsmaIpv4Topology::AddIpv4Address (n0, nd0, Ipv4Address ("10.1.1.1"),
      Ipv4Mask ("255.255.255.0"));

    CsmaIpv4Topology::AddIpv4Address (n1, nd1, Ipv4Address ("10.1.1.2"),
      Ipv4Mask ("255.255.255.0"));

    CsmaIpv4Topology::AddIpv4Address (n2, nd2, Ipv4Address ("10.1.1.3"),
      Ipv4Mask ("255.255.255.0"));

    CsmaIpv4Topology::AddIpv4Address (n3, nd3, Ipv4Address ("10.1.1.4"),
      Ipv4Mask ("255.255.255.0"));

    uint32_t packetSize = 1024;
    uint16_t port = 7;
    uint32_t maxPacketCount = 1;
    Time interPacketInterval = Seconds (1.);

    Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n0, "10.1.1.2", port,
      maxPacketCount, interPacketInterval, packetSize);

    Ptr<UdpEchoServer> server = Create<UdpEchoServer> (n1, port);

  }

Using the Simulation Engine
===========================

You could say that the heart of the ns-3 system is the simulation engine (sometimes called the simulation executive in other systems).

In a computer simulation, a computer model of a real world system is constructed.  This is typically done to minimize cost since you do not have to actually buy, install and maintain physical hardware.  In the case of ns-3, a model is a representation of a networking component that is designed to imitate some number of important behaviors or characteristics of an actual component in a real network.  A system is a collection of models arranged for the purpose of analyzing some behavior.

Models
======

We have already encountered several ns-3 models without specifically calling them so.  The InternetNode, CsmaNetDevice and CsmaChannel objects are models of an Internet computing node, a CSMA network interface card (NIC), and a network cable able to move data to and from other CSMA NICs.

It is important to note that the Csma net devices and the Csma channel do not correspond to any real world hardware that you can actually go out and buy.  These models implement an approximation, or subset, of the behaviors that a real CSMA/CD network would have.  In this case, the CsmaNetDevice does not simulate collision detection (CD).  It does implement carrier sense and performs collision avoidance using global spatial knowledge available in the channel.  This would be impossible in any channel residing in our universe.

No model will fully implement all of the behaviors of a piece of hardware.  It is important to understand what is being modeled by the ns-3 components you are using and what is not.  For example, the Csma components we use in this tutorial model a highly abstract multiple access network that is topologically equivalent to an Ethernet.  It is not necessarily true that results found in a simulation using the Csma models will apply to a real-world Ethernet network.  You must understand what behaviors are simulated in each of the models before trusting that any results can be associated with real-world systems.

Time, Events and Callbacks
==========================

In a discrete event simulator time is not something that flows, nor is it something to be measured --- it is the driving force behind the progress of the simulation.  Time is progressed forward by the simulation engine and anything that happens in the simulation is ultimately caused by an event.  An event is some action in the system that is scheduled to happen at a certain time by the simulation engine.  Time does not flow continuously but steps discretely (in possibly large jumps) from one scheduled event to another.

For example, to start the flow of a packet through the system, one would have to schedule an event with the simulation engine before the simulation was started.  This is important since the simulation engine only jumps time forward if there is a next event to process.  The simulation stops if there are no more events, which is equivalent to a state where there is ``nothing more to do.''  Before the simulation starts, one schedules driving events in terms of absolute time.  For example, one could schedule an event to start the flow of a first packet at, say, ten simulated seconds.  In this case, the simulation would start its clock at zero seconds and look for the first event in its event queue.  It would immediately jump time forward by ten seconds and fire the scheduled event --- that is, make the event happen.

In ns-3 an event is basically a pre-packaged function call called a functor.  Functors are also known as function objects, which is a more descriptive term --- an object (in the object-oriented programming sense) that can be called as if it was a function.  Typically one uses a functor to implement deferred execution of a function or method.  The most commonly encoutered form of deferred execution is in a callback from an I/O system.  In this case, the goal would be to start an I/O operation and return immediately, without having to wait for the operation to complete.  One asks the I/O subsytem to notify you when an operation is complete by calling some function you provide.  This provided function is known as a callback function.  [Imagine calling someone on the telephone and asking them to do something for you.  You also ask them to call you back when they are done.]  Events in the ns-3 system work conceptually the same way, except that instead of an I/O completion driving the process, the arrival of some simulated time drives the process.  The ns-3 deferred exectution mechanism is via a class called Callback.

The internal details of the classes representing Time and Callback abstractions will be introduced as required.  We won't see events directly for some time, but you should know that they are happening ``under the sheets'' of the simulations you will be writing.

Driving the Simulation
======================

As mentioned previously, time is the driving force behind the progress of a ns-3 simulation.  Events are scheduled to happen at certain times by calling methods of the simulation engine, either directly or indirectly through, for example, an Application.

In order to get the simulation engine set up and running in our code, we must first include the language definitions required to describe time- and simulator-specific classes:

  #include "ns3/simulator.h"
  #include "ns3/nstime.h"

As we have seen, we need to ``seed'' the simulation with at least one event. In the case of an Application, a method to do this is provided.  This method must be implemented by each specialization of the class and we must call this method in our script before the simulation starts.  We can also provide an event (indirectly) to stop the output of the application at a certain time.  This is done by adding the following lines to our script:

  server->Start(Seconds(1.));
  client->Start(Seconds(2.));

  server->Stop (Seconds(10.));
  client->Stop (Seconds(10.));

In the case of the UdpEchoServer, the call to server->Start () gives the Application the chance to schedule an event that will perform the usual sockets server sequence of socket creation, binding and recvfrom (see Donahoo's UDPEchoServer.c).

In the case of the UdpEchoClient, the call to client->Start () gives the Application the chance to schedule an event that will perform the usual sockets client sequence of socket creation, sendto and recvfrom (see Donahoo's UDPEchoClient.c).

Note that the start event for the server is scheduled to happen before the start event of the client, just as you would start a server application before you would attempt to start a client application in the real world.

The ns-3 equivalent of the call to sendo in the client will schedule (immediately) the transmission of a UDP packet over the just created socket.  This will cause the packet to percolate down the protocol stack and eventually into the channel.  The channel will schedule a reception event in the net device on the destination node.  This event will eventually percolate up into the server application.  The server application will create a reply packet and send it back down its stack and eventually back to the channel. The channel will schedule a reception event back in the client and this will cause the reply to be sent back up the protocol stack to the client application.

The calls to Stop () for both applications cause the sockets to be torn down and therefore the sending and receiving of packets will be stopped irrespective of other application settings (such as max packets and interval in the client).

Finally, we need to run the simulation and when the simulation run is complete, clean up any resources allocated during the run.  This is done by the calling the following static methods:

  Simulator::Run ();
  Simulator::Destroy ();

We now have the makings of a complete ns-3 network simulation.  The source code for the script should look like the following:

  #include "ns3/log.h"
  #include "ns3/ptr.h"
  #include "ns3/internet-node.h"
  #include "ns3/csma-channel.h"
  #include "ns3/mac48-address.h"
  #include "ns3/csma-net-device.h"
  #include "ns3/csma-topology.h"
  #include "ns3/csma-topology.h"
  #include "ns3/csma-ipv4-topology.h"
  #include "ns3/udp-echo-client.h"
  #include "ns3/udp-echo-server.h"
  #include "ns3/simulator.h"
  #include "ns3/nstime.h"

  NS_LOG_COMPONENT_DEFINE ("UdpEchoSimulation");

  using namespace ns3;

  int
  main (int argc, char *argv[])
  {
    LogComponentEnable ("UdpEchoSimulation", LOG_LEVEL_INFO);

    NS_LOG_INFO ("UDP Echo Simulation");

    Ptr<Node> n0 = Create<InternetNode> ();
    Ptr<Node> n1 = Create<InternetNode> ();
    Ptr<Node> n2 = Create<InternetNode> ();
    Ptr<Node> n3 = Create<InternetNode> ();

    Ptr<CsmaChannel> lan =
      CsmaTopology::CreateCsmaChannel (DataRate (5000000), MilliSeconds (2));

    uint32_t nd0 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n0, lan,
      Mac48Address("08:00:2e:00:00:00"));

    uint32_t nd1 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n1, lan,
      Mac48Address("08:00:2e:00:00:01"));

    uint32_t nd2 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n2, lan,
      Mac48Address("08:00:2e:00:00:02"));

    uint32_t nd3 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n3, lan,
      Mac48Address("08:00:2e:00:00:03"));

    CsmaIpv4Topology::AddIpv4Address (n0, nd0, Ipv4Address ("10.1.1.1"),
      Ipv4Mask ("255.255.255.0"));

    CsmaIpv4Topology::AddIpv4Address (n1, nd1, Ipv4Address ("10.1.1.2"),
      Ipv4Mask ("255.255.255.0"));

    CsmaIpv4Topology::AddIpv4Address (n2, nd2, Ipv4Address ("10.1.1.3"),
      Ipv4Mask ("255.255.255.0"));

    CsmaIpv4Topology::AddIpv4Address (n3, nd3, Ipv4Address ("10.1.1.4"),
      Ipv4Mask ("255.255.255.0"));

    uint32_t packetSize = 1024;
    uint16_t port = 7;
    uint32_t maxPacketCount = 1;
    Time interPacketInterval = Seconds (1.);

    Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n0, "10.1.1.2", port,
      maxPacketCount, interPacketInterval, packetSize);

    Ptr<UdpEchoServer> server = Create<UdpEchoServer> (n1, port);

    server->Start(Seconds(1.));
    client->Start(Seconds(2.));

    server->Stop (Seconds(10.));
    client->Stop (Seconds(10.));

    Simulator::Run ();
    Simulator::Destroy ();
  }

Just to make sure you don't get caught up in debugging typographical errors we have provided this source code for you (along with a copyright header) in the tutorial subdirectory of the ns-3 distribution as csma-echo.cc.  We used this opportunity to do some ``clean up'' of some of our example cases by passing parameters using implicit conversion sequences and removing some of the named parameters. [These were used for pedagogic purposes and were not actually necessary.]

Building the Script
===================

C++ is a compiled language, so you know it had to happen.  We have to build the script before we run it.  As mentioned before, we use the Waf build system which is Python-based.  We have to change gears slightly and switch ourselves to Python mode in order to proceed.

In each subdirectory of the ns-3 distribution in which there are source files, you will find two files:  one will be named waf and one will be named wscript.  The former, waf, is a link that allows one to start the build process from any subdirectory.  We can ignore that one. The file we need to deal with is wscript.

Open the file ns-3-dev/tutorial/wscript in your favorite editor [remember I'm assuming that you have the distribution saved in a repository under a directory called repos in you home directory.]

You should see the following Python code (after an emacs mode line).

  def build(bld):
    obj = bld.create_ns3_program('hello-simulator')
    obj.source = 'hello-simulator.cc'

These are the only instructions required to build a simulation (I told you it wasn't going to be too bad).  The line with the method bld.create_ns3_program tells the build system to create an object file that is a program (executable) named hello-simulator.  The following line, with the method obj.source tells the build system that the source file for the program is the file hello-simulator.cc' in the local directory.  The required libraries are linked for you for free.

All that needed to be done in order to build the new simulation using the new source file was to copy the two lines describing the hello-simulator program and change the names to csma-echo.  You can see these lines in the wscript file,

  def build(bld):
    obj = bld.create_ns3_program('hello-simulator')
    obj.source = 'hello-simulator.cc'

    obj = bld.create_ns3_program('csma-echo')
    obj.source = 'csma-echo.cc'

    ...

When you built the system above, you actually already built this new simulation and a number of other examples.  Since you have already configured Waf and built the csma-echo script, you can run the simulation in the same way as you ran the hello-simulator script using the waf --run command:

~/repos/ns-3-dev/tutorial > waf --run csma-echo
Entering directory `~/repos/ns-3-dev/build'
Compilation finished successfully
UDP Echo Simulation
~/repos/ns-3-dev/tutorial >

Wow!  Wasn't that cool!  I'm sure you can barely contain yourself at this point.  Okay, well, maybe we should figure out how to get some useful information out of that simulation.  It did run ... I promise.

Tracing at a Glance
*******************

At this stage we have constructed a real simulation script, but have no way of getting information out of the simulation.  Returning to first principles, a number of questions come immediately to mind:  What do we mean by getting information out of the simulation?  What is information?  How is this information generated? What generates it? Where does it go?  How can we specify the quantity of information we get?  How can we specify the location of the sources?  What is a source anyway?

The ns-3 tracing system addresses each of these questions.  At the lowest levels, it is an extremely flexible module that comes with all of the associated complexity that inevitably comes with flexibility.  To minimize the amount of work required to get started, we provide wrapper functions to accomplish common tasks.  This makes the higher levels of the tracing system easy to use, but relatively inflexible.  This is a common trade-off.  Consider a toaster:  if you want to make toast, you can push a ``toast'' button; but if you want to control the color of your toast, you will need a knob to adjust. If you want to independently control slice color, you will need a number of knobs, etc.

In this chapter, we will discuss the highest levels of the tracing system that expose the fewest ``knobs.''

ASCII Trace Wrapper
===================

The ASCII trace wrapper is a wrapper around the ns-3 low-level tracing system that lets you get access to underlying trace events easily. The output of a trace of a simulation run is an ASCII file --- thus the name. In the spririt of keeping things simple, you won't be able to control or configure the output.  The details are all hidden from you, and are therefore inaccessible at this level.  Be assured that as you learn more and more about the tracing system you will be able to control it to your heart's delight.

Tracing Queue Operations
------------------------

Let's just jump right in.  As usual, we need to include the definitions related to using ASCII tracing (don't edit any files quite yet):

  #include "ns3/ascii-trace.h"

We then need to add the code to the script to actually enable the ASCII tracing code.  The following code must be inserted before the call to Simulator::Run ();:

  AsciiTrace asciitrace ("tutorial.tr");
  asciitrace.TraceAllQueues ();

The first line declares an object of type AsciiTrace named asciitrace and passes a string parameter to its constructor.  This parameter is a file name to which all of the trace information will be written. The last line, asciitrace.TraceAllQueues (); asks the trace object to arrange that all queue operations (enqueue, dequeue, drop) on the queues in all of the nodes of the system be traced.

Again, being the nice guys we are, we have provided you a file with these changes already made.  Make sure you understand what we've done before you just move on.  The new file is called csma-echo-ascii-trace.cc and is located in the tutorial directory.

You can just type the following to run the trace version of the echo program:

  ./waf --run csma-echo-ascii-trace

Just as you have seen previously, you will see some messages from Waf and then (I feel joy) the ``Compilation finished successfully'' message.  The next message, UDP Echo Simulation is from the running program.  When it ran, the program will have created a file named tutorial.tr. Because of the way that Waf works, the file is not created in the local directory, it is created at the top-level directory of the repository.  So, change into the top level directory and take a look at the file tutorial.tr in your favorite editor.

There's a lot of information there in a pretty dense form, but the first thing to notice is that there are a number of distinct lines in this file.  It may be difficult to see this clearly unless you widen your windows considerably. Each line in the file corresponds to a trace event.  A trace event happens whenever specific conditions happen in the simulation.  In this case we are tracing events on the device queue present in every net device on every node in the simulation.  The device queue is a queue through which every packet destined for a channel must pass --- it is the device transmit queue.  Note that each line in the trace file begins with a lone character (has a space after it).  This character will have the following meaning:

  * +: An enqueue operation occurred on the device queue;

  * -: A dequeue operation occurred on the device queue;

  * d: A packet was dropped, typically because the queue was full.

Let's take a more detailed view of the first line.  I'll break it down into sections (indented for clarity) with a two digit reference number on the left side:

  00  +
  01  2
  02  nodeid=0
  03  device=0
  04  queue-enqueue
  05  pkt-uid=9
  06  ETHERNET
  07    length/type=0x806,
  08    source=08:00:2e:00:00:00,
  09    destination=ff:ff:ff:ff:ff:ff
  10      ARP(request
  11        source mac: 08:00:2e:00:00:00
  12        source ipv4: 10.1.1.1
  13        dest ipv4: 10.1.1.2)
  14  ETHERNET fcs=0

The first line of this expanded trace event (reference number 00) is the queue operation.  We have a + character, so this corresponds to an enqueue operation.  The second line (reference 01) is the simulation time expressed in seconds.  You may recall that we asked the UdpEchoClient to start sending packets at two seconds.  Here we see confirmation that this is, indeed, happening.

The next lines of the example listing (references 02 and 03) tell us that this trace event originated in a given node and net device.  Each time a node is created it is given an identifying number that monotonically increases from zero.  Therefore, nodeid=0 means that the node in which the given trace event originated is the first node we created.  In the case of our script, this first node is is the node pointed to by the smart pointer n0.  Not too surpsisingly, this is also the node to which we attached the UdpEchoClient.  The device number is local to each node, and so the device given by device=0 is the first net device that we added to the node in question.  In our simulation, this corresponds to the CsmaNetDevice we added to node zero (n0).

The next line (reference 04) is a more readable form of the operation code seen in the first line --- i.e., the character + means queue-enqueue.  Reference number 05 indicates that the unique id of the packet being enqueued is 9.  The fact that the first packet we see has a unique ID of 9 should indicates to you that other things have happened in the protocol stack before we got to this point.  This will become clear momentarily.

Reference items 06 and 14 indicate that this is an Ethernet packet with a zero (not computed) checksum (note the indentation to make parsing this trace event a little easier).  Reference 08 and 09 are the source and destination addresses of this packet.  The packet is from the MAC address we assigned to the node zero net device in the script, and is destined for the broadcast address --- this is a broadcast packet.

Reference items 10 through 13 make clear what is happening.  This is an ARP (Address Resolution Protocol) request for the MAC address of the node on which the UdpEchoServer resides.  The protocol stack can't send a UDP packet to be echoed until it knows (resolves) the MAC address; and this trace event corresponds to an ARP request being queued for transmission to the local network.  The next line in the trace file (partially expanded),

  00  -
  01  2
  02  nodeid=0
  03  device=0
  04  queue-dequeue
  05  pkt-uid=9
  ...

shows the (same) ARP request packet being dequeued from the device queue by the net device and (implicitly) being sent down the channel to the broadcast MAC address.  We are not tracing net device reception events so we don't actually see all of the net devices receiving the broadcast packet.  We do, however see the following in the third line of the trace file:

  00  +
  01  2.00207
  02  nodeid=1
  03  device=0
  04  queue-enqueue
  05  pkt-uid=10
  06  ETHERNET
  07    length/type=0x806,
  08    source=08:00:2e:00:00:01,
  09    destination=08:00:2e:00:00:00,
  10      ARP(reply
  11        source mac: 08:00:2e:00:00:01
  12        source ipv4: 10.1.1.2
  13        dest mac: 08:00:2e:00:00:00
  14        dest ipv4: 10.1.1.1)
  15  ETHERNET fcs=0

Notice that this is a queue-enqueue operation (references 00 and 04) happening on node one (reference 02) at simulation time 2.00207 seconds (reference 01). Looking at the packet payload (references 10-14) we see that this is an ARP reply to the request sent by node one.  Note that the simulation time (reference 01) is now 2.00207 seconds.  This is direct result of the data rate (5 mb/s) and latency (2 ms) parameters that we passed to the CsmaChannel when we created it.  Clearly the ARP request packet was sent over the channel and received approximately 2 ms later by node one.  A corresponding ARP response packet was created and enqueued on node one's net device.  It is this enqueue trace event that has being logged.

Given the current state of affairs, the next thing you may expect to see is this ARP request being received by node zero, but remember we are only looking at trace events on the device transmit queue.  The reception of the ARP response by node zero will not directly trigger any trace event in this case, but it will enable the protocol stack to continue what it was originally doing (trying to send an echo packet).  Thus, the next line we see in the trace file (tutorial.tr) is the first UDP echo packet being sent to the net device.

  00 +
  01 2.00415
  02 nodeid=0
  03 device=0
  04 queue-enqueue
  05 pkt-uid=7
  06 ETHERNET
  07   length/type=0x800,
  08   source=08:00:2e:00:00:00,
  09   destination=08:00:2e:00:00:01
  10   IPV4(
  11     tos 0x0
  12     ttl 64
  13     id 0
  14     offset 0
  15     flags [none]
  16     length: 1052) 10.1.1.1 > 10.1.1.2
  17     UDP(length: 1032)
  18       49153 > 7
  19       DATA (length 1024)
  20 ETHERNET fcs=0

I won't go into too much detail about this packet, but I will point out a few key items in the trace.  First, the packet was enqueued at simulation time of 2.00415 seconds.  This time reflects the fact that the echo client application started at 2. seconds and there were two ARP packets transmitted across the network (two milliseconds + data transmission time each way).  The packet unique identifier (reference 05) is 7.  Notice that this is a lower number than the ARP request packet, which had a unique ID of 9.  This tells us that the UDP packet was actually created before the ARP request packet --- which makes perfect sense since it was the attempt to send packet 7 that triggered sending the ARP request packet 9.  Note that this an Ethernet packet (reference 06) like all other packets in this simulation, however this particular packet carries an IPV4 payload and therefore has an IP version 4 header (indicated by references 10-16).  This Ipv4 in turn contains a UDP header (references 17, 18) and finally 1024 bytes of data (reference 20). Clearly, this is the UDP echo packet emitted by the UdpEchoClient Application.

The next trace event is an ARP request from node one.  We can infer that node one has received the UDP echo packet and the UdpEchoServer Application on that node has turned the packet around.  Just as node zero needed to ARP for the MAC address of node one, now node one must ARP for the MAC address of node zero.  We see the ARP request enqueued on the transmit queue of node one; then we see the ARP request dequeued from the tranmit queue of node one (and implicitly transmitted to node zero).  Then we see an ARP response enqueued on the transmit queue of node zero; and finally the ARP response dequeued (and implicitly transmitted back to node one).

This exchange is summarized in the following trace event excerpts,

  + 2.00786 nodeid=1 ... ARP(request ...
  - 2.00786 nodeid=1 ... ARP(request ...
  + 2.00994 nodeid=0 ... ARP(reply ...
  - 2.00994 nodeid=0 ... ARP(reply ...

The final two trace events in the tutorial.tr file correspond to the echoed packet being enqueued for transmission on the net device for node one, and that packet being dequeued (and implicitly transmitted back to node zero).

Tracing Device Receive Operations
---------------------------------

There is one final ``knob'' we can turn on the ASCII trace wrapper.  We can enable net device receive operations.  In our analysis of the existing trace file we noted several times that we inferred that a packet was received by a given node.  We will now enable the event to actually trace that operation.

All we have to do is add one more line to the file csma-echo.cc to enable tracing of net device receive operations.  The code is already in the file, but disabled.

#if 0
  asciitrace.TraceAllNetDeviceRx ();
#endif
  asciitrace.TraceAllNetDeviceRx ();

Change the #if 0 to #if 1 using your favorite editor and compile and run the file using waf as we have done previously:

  ./waf --run csma-echo-ascii-trace

Now if you look at the trace file (tutorial.tr) you will see some new entries.  These entries all begin with the character r indicating a receive trace event.  Recall that the first packet sent on the network was a broadcast ARP request.  We should then see all four nodes receive a copy of this request.  This is the case, as the first four receive trace events are,

  r 2.00207 nodeid=0 device=0 dev-rx pkt-uid=9 ARP(request ...
  r 2.00207 nodeid=1 device=0 dev-rx pkt-uid=9 ARP(request ...
  r 2.00207 nodeid=2 device=0 dev-rx pkt-uid=9 ARP(request ...
  r 2.00207 nodeid=3 device=0 dev-rx pkt-uid=9 ARP(request ...

You can see that a copy of the broadcast packet with unique ID 9 was received by the net devices on nodes 0, 1, 2 and 3.  We leave it up to you to parse the rest of the trace file and understand the remaining reception events.

PCAP Trace Wrapper
==================

The ns-3 pcap trace wrapper is used to create trace files in .pcap format.  The acronym pcap (usually written in lower case) stands for packet capture, and is actually an API that includes the definition of a .pcap file format.  The most popular program that can read and display this format is Wireshark (formerly called Ethereal).

If you are unfamilar with Wireshark, there is a web site available from which you can download programs and documentation:  http://www.wireshark.org/.

The code used to enable pcap tracing is similar to that for ASCII tracing. We have provided another file, csma-echo-pcap-trace.cc that uses the pcap trace wrapper.  We have added the code to include the pcap trace wrapper defintions:

  #include "ns3/pcap-trace.h"

And then added the following code below the AsciiTrace methods:

  PcapTrace pcaptrace ("tutorial.pcap");
  pcaptrace.TraceAllIp ();

The first line of the code immediately above declares an object of type PcapTrace named pcaptrace and passes a string parameter to its constructor.  This object is used to hide the details of the actual tracing subsystem.  The parameter is a base file name from which the actual trace file names will be built.  The second line of code tells the PcamTrace object to trace all IP activity in all of the nodes present in the simulation.

Trace files are not created until trace activity is detected.  Each file name is composed of the base file name, followed by a '-', a node id followed by a '-', and an IP interface index.  You will soon see a file named tutorial.pcap-0-1, for example.  This will be the trace file generated as events are detected on node zero, interface index one.  N.B. Interface indices are different that net device indices --- interface index zero corresponds to the loopback interface and interface index one corresponds to the first net device you added to a node.

You may run the new program just like all of the others so far:

  ./waf --run csma-echo-pcap-trace

If you look at the top level directory of your distribution, you should now see three log files:  tutorial.tr is the ASCII trace file we have previously examined.  tutorial.pcap-0-1 and tutorial.pcap-1-1 are the new pcap files we just generated.  There will not be files corresponding to nodes two and three since we have not sent any IP packets to those nodes.

If you have Wireshark available, you can open each of the trace files and display the contents as if you had captured the packets using a packet sniffer.  Note that only IP packets are traced using this wrapper, so you will not see the ARP exchanges that were logged when using the ASCII trace wrapper.  You are encouraged to take a look at the contents of these pcap files using your favorite pcap software (or Wireshark).

Other Network Topologies
************************

Network topology is the study of the arrangement of of the elements (in ns-3 represented by the classes Channel and Node) of a network.  Two fundamental types of physical topologies are the point-to-point and bus topologies.  We have already been exposed to the ns-3 channel specialization named CsmaChannel.  This is a simulation of a bus network.  We also provide a simulation of a point-to-point channel with associated net devices.  As described previously, the associated C++ classes specialize the ns-3 base classes NetDevice and Channel and are called PointToPointNetDevice and PointToPointChannel respectively.

We will use combinations of these bus and point-to-point topology elements to show how to create several commonly seen network topologies.

A Point-to-Point Network
========================

We're going to take what might be seen as a step backward and look at a simple point-to-point network.  We will be building the simplest network you can imagine.  A serial link (point to point) between two computers.  When you see this point-to-point network, you can think of an RS-422 (or RS-232 for you old-timers) cable.  This topology is shown below.

We have provided a file for you in the tutorial directory called point-to-point.cc.  You should now be familiar enough with the system to pick out fairly easily what has been changed.  Let's focus on the following lines:

  Ptr<Node> n0 = Create<InternetNode> ();
  Ptr<Node> n1 = Create<InternetNode> ();

  Ptr<PointToPointChannel> link = PointToPointTopology::AddPointToPointLink (
    n0, n1, DataRate (38400), MilliSeconds (20));

  PointToPointTopology::AddIpv4Addresses (link, n0, "10.1.1.1",
    n1, "10.1.1.2");

You can see that we created two InternetNode objects in the usual way. Then, instead of creating a CsmaChannel we create a PointToPointChannel.  This point-to-point channel, which we call link, connects node zero (n0) and node one (n1) over a simulated link that runs at 38400 bits per second and has a 20 millisecond simulated speed-of-light delay.  This call also creates appropriate net devices and attaches them to nodes zero and one.

We then add IP addresses to the net devices we just created using the topology helper AddIpv4Addresses.  Node zero gets the IP address 10.1.1.1 and node one gets the IP address 10.1.1.2 assigned.

The alert tutorial user may wonder what the network number or prefix is of those IP addresses.  The point-to-point topology assumes that you want a /30 subnet and assigns an appropriate net mask for you.  It then then asserts that the network numbers of the two net devices match.  So there is an implicit network mask created down in the topology code that looks like,

  Ipv4Mask netmask("255.255.255.252");

The rest of the code you should recognize and understand.  We are just going to echo one packet across the point-to-point link.  You should be now be able to build and run this example and to locate and interpret the ASCII trace file.  This is left as an exercise for you.

The file point-to-point.cc is reproduced here for your convenience:

/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "ns3/log.h"
#include "ns3/ptr.h"
#include "ns3/internet-node.h"
#include "ns3/point-to-point-channel.h"
#include "ns3/mac48-address.h"
#include "ns3/point-to-point-net-device.h"
#include "ns3/point-to-point-topology.h"
#include "ns3/udp-echo-client.h"
#include "ns3/udp-echo-server.h"
#include "ns3/simulator.h"
#include "ns3/nstime.h"
#include "ns3/ascii-trace.h"
#include "ns3/pcap-trace.h"
#include "ns3/global-route-manager.h"

NS_LOG_COMPONENT_DEFINE ("PointToPointSimulation");

using namespace ns3;

// Network topology
//
//                       point to point
//                      +--------------+
//                      |              |
//                     n0             n1
//
int
main (int argc, char *argv[])
{
  LogComponentEnable ("PointToPointSimulation", LOG_LEVEL_INFO);

  NS_LOG_INFO ("Point to Point Topology Simulation");

  Ptr<Node> n0 = Create<InternetNode> ();
  Ptr<Node> n1 = Create<InternetNode> ();

  Ptr<PointToPointChannel> link = PointToPointTopology::AddPointToPointLink (
    n0, n1, DataRate (38400), MilliSeconds (20));

  PointToPointTopology::AddIpv4Addresses (link, n0, "10.1.1.1",
    n1, "10.1.1.2");

  uint16_t port = 7;

  Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n0, "10.1.1.2", port,
    1, Seconds(1.), 1024);

  Ptr<UdpEchoServer> server = Create<UdpEchoServer> (n1, port);

  server->Start(Seconds(1.));
  client->Start(Seconds(2.));

  server->Stop (Seconds(10.));
  client->Stop (Seconds(10.));

  AsciiTrace asciitrace ("tutorial.tr");
  asciitrace.TraceAllQueues ();
  asciitrace.TraceAllNetDeviceRx ();

  Simulator::Run ();
  Simulator::Destroy ();
}

A Star Network
==============

A point-to-point network is considered a special case of a star network.  As you might expect, the process of constructing a star network is an extension of the very simple process used for a point-to-point link.  We have provided a file for you in the tutorial directory called star.cc that implements a simple star network as seen below.

In order to create a star network, we need to be able to instantiate some number (greater than one) of net devices on a node.  In the name of simplicity of use, the PointToPointTopology topology helper does not allow one to do this.  We provided a separate topology helper class, the PointToPointIpv4Topology helper class that provides the slightly finer granularity we need to accomplish a star network.  In order to use this new helper we have to load the definitions by including the appropriate file.

  #include "ns3/point-to-point-ipv4-topology.h"

The star that we're going to create has a node in the center (n0) with six nodes surrounding (n1 - n6).  You should be able to easily find and understand the code that creates these nodes.

  Ptr<Node> n0 = Create<InternetNode> ();
  Ptr<Node> n1 = Create<InternetNode> ();
  Ptr<Node> n2 = Create<InternetNode> ();
  Ptr<Node> n3 = Create<InternetNode> ();
  Ptr<Node> n4 = Create<InternetNode> ();
  Ptr<Node> n5 = Create<InternetNode> ();
  Ptr<Node> n6 = Create<InternetNode> ();

Next, we get into the differences between the PointToPointTopology helper and the PointToPointIpv4Topology helper.  The PointToPointIpv4Topology helper looks and feels a little like the CsmaIpv4Topology helper.  Just like you created a CSMA channel previously, you need to create a point-to-point channel.  The following code creates a PointToPointChannel and calls it link01.  You can interpret this name as being the channel (or link) from node zero to node one.

  Ptr<PointToPointChannel> link01 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

You need to provide a data rate for the channel which we set at 38400 bits per second.  You must also provide a speed-of-light delay which we set at 20 milliseconds.

Just as you added a net device to the nodes in the CSMA tutorial section, you do the same here but with a point-to-point net device.  The following code illustrates how we do that:

  uint32_t nd01 = PointToPointIpv4Topology::AddNetDevice (n0,
    link01);

We call the PointToPointIpv4Topology helper and ask it to add a net device to node zero (n0) and connect it to the appropriate point-to-point link (link01) which you will recall is the serial link from node zero to node one.

If you look at the following code, you will see the same calls are repeated to create the remaining five point-to-point channels and connect them to net devices on node zero.

The next new code is found after the ``spokes'' of the star have been created. It looks like the following:

  uint32_t nd1 = PointToPointIpv4Topology::AddNetDevice (n1, link01);
  uint32_t nd2 = PointToPointIpv4Topology::AddNetDevice (n2, link02);
  uint32_t nd3 = PointToPointIpv4Topology::AddNetDevice (n3, link03);
  uint32_t nd4 = PointToPointIpv4Topology::AddNetDevice (n4, link04);
  uint32_t nd5 = PointToPointIpv4Topology::AddNetDevice (n5, link05);
  uint32_t nd6 = PointToPointIpv4Topology::AddNetDevice (n6, link06);

Here we are creating the net devices on the nodes surrounding the center node. In the first call, we are adding a net device on node one (n1) and connecting that net device to the channel named link01.  Remember that we created the channel link01 as the channel connecting node zero and node one.  We previously created a net device on node zero and attached that device to link01.  Here we are connecting the other side of that link to node one.  The return value from this call is the net device index of the created net device.

The next section of code adds addresses to the net devices we just created. The first call adds the IP address 10.1.1.1 to the net device going from node zero to node one.  Recall that we first created a node named n0 and a channel called link01.  We added a net device to n0 and remembered the net device index as the uint32_t nd01.  This meant the net device nd on node 0 that we connected to node 1. We call AddAddress to add an IP address (10.1.1.1) to the net device on node zero identified by the net device index nd01.  We provide a net mask suitable for a point to point network.  This is typically a /30 address but we don't force that in this API.

After setting up the address on node zero, we do the same for the node on the other end of the ``spoke'' --- in this case node one, with its single net device.  Note that the network number is the same on both sides of this network.

  PointToPointIpv4Topology::AddAddress (n0, nd01, "10.1.1.1",
    ``255.255.255.252'');

  PointToPointIpv4Topology::AddAddress (n1, nd1, "10.1.1.2",
    ``255.255.255.252'');

The following code repeats this pattern assining similar IP addresses to the remaining net devices.  Note that there are no Mac48Address address assignments --- they are not required.

The rest of the code you should recognize and understand.  We are just going to echo one packet across the point-to-point link.  You should be now be able to build and run this example and to locate and interpret the ASCII trace file.  This is left as an exercise for you.

The file star.cc is reproduced here for your convenience:

/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "ns3/log.h"
#include "ns3/ptr.h"
#include "ns3/internet-node.h"
#include "ns3/point-to-point-channel.h"
#include "ns3/mac48-address.h"
#include "ns3/point-to-point-net-device.h"
#include "ns3/point-to-point-ipv4-topology.h"
#include "ns3/udp-echo-client.h"
#include "ns3/udp-echo-server.h"
#include "ns3/simulator.h"
#include "ns3/nstime.h"
#include "ns3/ascii-trace.h"
#include "ns3/pcap-trace.h"
#include "ns3/global-route-manager.h"

NS_LOG_COMPONENT_DEFINE ("StarSimulation");

using namespace ns3;

// Network topology
//
//                  n3    n2
//                   |   /
//                    | /
//              n4 --- n0 --- n1
//                    /  |
//                   /    |
//                  n5    n6

int
main (int argc, char *argv[])
{
  LogComponentEnable ("StarSimulation", LOG_LEVEL_INFO);

  NS_LOG_INFO ("Star Topology Simulation");

  Ptr<Node> n0 = Create<InternetNode> ();
  Ptr<Node> n1 = Create<InternetNode> ();
  Ptr<Node> n2 = Create<InternetNode> ();
  Ptr<Node> n3 = Create<InternetNode> ();
  Ptr<Node> n4 = Create<InternetNode> ();
  Ptr<Node> n5 = Create<InternetNode> ();
  Ptr<Node> n6 = Create<InternetNode> ();

  Ptr<PointToPointChannel> link01 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

  uint32_t nd01 = PointToPointIpv4Topology::AddNetDevice (n0,
    link01);

  Ptr<PointToPointChannel> link02 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

  uint32_t nd02 = PointToPointIpv4Topology::AddNetDevice (n0,
    link02);

  Ptr<PointToPointChannel> link03 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

  uint32_t nd03 = PointToPointIpv4Topology::AddNetDevice (n0,
    link03);

  Ptr<PointToPointChannel> link04 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

  uint32_t nd04 = PointToPointIpv4Topology::AddNetDevice (n0,
    link04);

  Ptr<PointToPointChannel> link05 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

  uint32_t nd05 = PointToPointIpv4Topology::AddNetDevice (n0,
    link05);

  Ptr<PointToPointChannel> link06 =
    PointToPointIpv4Topology::CreateChannel (DataRate (38400),
    MilliSeconds (20));

  uint32_t nd06 = PointToPointIpv4Topology::AddNetDevice (n0, link06);

  uint32_t nd1 = PointToPointIpv4Topology::AddNetDevice (n1, link01);
  uint32_t nd2 = PointToPointIpv4Topology::AddNetDevice (n2, link02);
  uint32_t nd3 = PointToPointIpv4Topology::AddNetDevice (n3, link03);
  uint32_t nd4 = PointToPointIpv4Topology::AddNetDevice (n4, link04);
  uint32_t nd5 = PointToPointIpv4Topology::AddNetDevice (n5, link05);
  uint32_t nd6 = PointToPointIpv4Topology::AddNetDevice (n6, link06);

  PointToPointIpv4Topology::AddAddress (n0, nd01, "10.1.1.1",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n1, nd1, "10.1.1.2",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n0, nd02, "10.1.2.1",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n2, nd2, "10.1.2.2",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n0, nd03, "10.1.3.1",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n3, nd3, "10.1.2.2",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n0, nd04, "10.1.4.1",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n4, nd4, "10.1.4.2",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n0, nd05, "10.1.5.1",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n5, nd5, "10.1.5.2",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n0, nd06, "10.1.6.1",
    "255.255.255.252");

  PointToPointIpv4Topology::AddAddress (n6, nd6, "10.1.6.2",
    "255.255.255.252");

  uint16_t port = 7;

  Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n0, "10.1.1.2", port,
    1, Seconds(1.), 1024);

  Ptr<UdpEchoServer> server = Create<UdpEchoServer> (n1, port);

  server->Start(Seconds(1.));
  client->Start(Seconds(2.));

  server->Stop (Seconds(10.));
  client->Stop (Seconds(10.));

  AsciiTrace asciitrace ("tutorial.tr");
  asciitrace.TraceAllQueues ();
  asciitrace.TraceAllNetDeviceRx ();

  Simulator::Run ();
  Simulator::Destroy ();
}

Routing
-------

If you are really excited about this simulator you may have already tried to modify the scripts outside the tutorial.  I know that one of the first things that would have occurred to me when I saw the star network would have been to start trying to add applications to echo packets from nodes other than zero. If you tried, for example, to start the echo client on node one instead of node zero, you would have found an empty trace file.  The reason for this is that you have now created an internetwork.  This means you will need to enable internetwork routing.

We have provided a file for you in the tutorial directory called star-routing.cc to show you how this is done.  This extremely tricky and difficult change is shown below:

 GlobalRouteManager::PopulateRoutingTables ();

This one-line addition, located just before the simulation runs, tells the ns-3 global route manager to walk the topology you created and build internetwork routing tables for all of the nodes in the simulation. We changed the client application so that it runs on node four:

  Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n4, "10.1.1.2", port,
    1, Seconds(1.), 1024);

Now if you build and run star-routing.cc you can examine the tutorial.tr file and see that your UDP echo packets are now correctly routed through the topology.

A Dumbbell Network
==================

One of the most interesting simple topologies (from a phenomenological point of view) is commonly called a dumbbell network.  The name derives from a superficial similarity in form to a piece of exercise equipment.

The dumbbell model is typically composed of two bus or star network elements connected via a point-to-point link.  The point-to-point link is usually configured with a lower bandwidth than the bus elements to provide a choke point.

The following is a representation of the topology.

We have provided a file that constructs this dumbbell network and creates enough data flowing across the choke point that some packets will be dropped. The file is called linear-dumbbell.cc and is located in the tutorial directory.  We have already covered all of the code used to create this network, so we will just quickly go over the main sections of the script.

The first section creates a CSMA lan that will become the left side of the dumbbell network.  This code should be very familiar since we used the same process to create our first example.

//
// Create the lan on the left side of the dumbbell.
//
  Ptr<Node> n0 = Create<InternetNode> ();
  Ptr<Node> n1 = Create<InternetNode> ();
  Ptr<Node> n2 = Create<InternetNode> ();
  Ptr<Node> n3 = Create<InternetNode> ();

  Ptr<CsmaChannel> lan1 =
    CsmaTopology::CreateCsmaChannel (DataRate (10000000), MilliSeconds (2));

  uint32_t nd0 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n0, lan1,
    "08:00:2e:00:00:00");

  uint32_t nd1 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n1, lan1,
    "08:00:2e:00:00:01");

  uint32_t nd2 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n2, lan1,
    "08:00:2e:00:00:02");

  uint32_t nd3 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n3, lan1,
    "08:00:2e:00:00:03");

  CsmaIpv4Topology::AddIpv4Address (n0, nd0, "10.1.1.1", "255.255.255.0");
  CsmaIpv4Topology::AddIpv4Address (n1, nd1, "10.1.1.2", "255.255.255.0");
  CsmaIpv4Topology::AddIpv4Address (n2, nd2, "10.1.1.3", "255.255.255.0");
  CsmaIpv4Topology::AddIpv4Address (n3, nd3, "10.1.1.4", "255.255.255.0");

The code to generate the CSMA lan on the right side is similar; only the names have been changed.

//
// Create the lan on the right side of the dumbbell.
//
  Ptr<Node> n4 = Create<InternetNode> ();
  Ptr<Node> n5 = Create<InternetNode> ();
  Ptr<Node> n6 = Create<InternetNode> ();
  Ptr<Node> n7 = Create<InternetNode> ();

  Ptr<CsmaChannel> lan2 =
    CsmaTopology::CreateCsmaChannel (DataRate (10000000), MilliSeconds (2));

  uint32_t nd4 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n4, lan2,
    "08:00:2e:00:00:04");

  uint32_t nd5 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n5, lan2,
    "08:00:2e:00:00:05");

  uint32_t nd6 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n6, lan2,
    "08:00:2e:00:00:06");

  uint32_t nd7 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n7, lan2,
    "08:00:2e:00:00:07");

  CsmaIpv4Topology::AddIpv4Address (n4, nd4, "10.1.2.1", "255.255.255.0");
  CsmaIpv4Topology::AddIpv4Address (n5, nd5, "10.1.2.2", "255.255.255.0");
  CsmaIpv4Topology::AddIpv4Address (n6, nd6, "10.1.2.3", "255.255.255.0");
  CsmaIpv4Topology::AddIpv4Address (n7, nd7, "10.1.2.4", "255.255.255.0");

Next, we create a point to point link to connect the two lans.  We connect the point-to-point channel between nodes three (on the left lan) and four (on the right lan).  You should recoginze this as substantially similar to the link setup from the point-to-point example.

//
// Create the point-to-point link to connect the two lans.
//
  Ptr<PointToPointChannel> link = PointToPointTopology::AddPointToPointLink (
    n3, n4, DataRate (38400), MilliSeconds (20));

  PointToPointTopology::AddIpv4Addresses (link, n3, "10.1.3.1",
    n4, "10.1.3.2");

Then we configure data flows.  We create four echo clients that send UDP packets from the left side lan to servers created on the right side lan. Notice that we send 100 packets with an inter-packet gap of ten milliseconds instead of the single packet we have previously used.  This data rate is sufficient to saturate the point-to-point link and will cause packets to be dropped when the queue on the link net devices overflows (the default maximum queue depth is 100 packets).  Note that we stagger the start of the echo clients to slowly bring up the data rates.

//
// Create data flows across the link:
//   n0 ==> n4 ==> n0
//   n1 ==> n5 ==> n1
//   n2 ==> n6 ==> n2
//   n3 ==> n7 ==> n3
//
  uint16_t port = 7;

  Ptr<UdpEchoClient> client0 = Create<UdpEchoClient> (n0, "10.1.2.1", port,
    100, Seconds(.01), 1024);
  Ptr<UdpEchoClient> client1 = Create<UdpEchoClient> (n1, "10.1.2.2", port,
    100, Seconds(.01), 1024);
  Ptr<UdpEchoClient> client2 = Create<UdpEchoClient> (n2, "10.1.2.3", port,
    100, Seconds(.01), 1024);
  Ptr<UdpEchoClient> client3 = Create<UdpEchoClient> (n3, "10.1.2.4", port,
    100, Seconds(.01), 1024);

  Ptr<UdpEchoServer> server4 = Create<UdpEchoServer> (n4, port);
  Ptr<UdpEchoServer> server5 = Create<UdpEchoServer> (n5, port);
  Ptr<UdpEchoServer> server6 = Create<UdpEchoServer> (n6, port);
  Ptr<UdpEchoServer> server7 = Create<UdpEchoServer> (n7, port);

  server4->Start(Seconds(1.));
  server5->Start(Seconds(1.));
  server6->Start(Seconds(1.));
  server7->Start(Seconds(1.));

  client0->Start(Seconds(2.));
  client1->Start(Seconds(2.1));
  client2->Start(Seconds(2.2));
  client3->Start(Seconds(2.3));

  server4->Stop (Seconds(10.));
  server5->Stop (Seconds(10.));
  server6->Stop (Seconds(10.));
  server7->Stop (Seconds(10.));

  client0->Stop (Seconds(10.));
  client1->Stop (Seconds(10.));
  client2->Stop (Seconds(10.));
  client3->Stop (Seconds(10.));

The remainder of the file should be quite familiar to you.  Go ahead and run linear-dumbbell.  Now take a look at the trace (tutorial.tr) file.  You will now see trace lines that begin with d.  Alternatively you can search for the string ``queue-drop'' which is the expansion of the drop code.

Interpretation of a dropped packet is straightforward.  We have expanded the first queue-drop trace for you below.  See the section on ASCII tracing for details.

  00 d
  01 2.40938
  02 nodeid=3
  03 device=1
  04 queue-drop
  05 pkt-uid=124
  06 LLCSNAP(type 0x800)
  07   IPV4(
  08     tos 0x0
  09     ttl 63
  10     id 20
  11     offset 0
  12     flags [none]
  13     length: 1052) 10.1.1.3 > 10.1.2.3
  14     UDP(length: 1032)
  15       49153 > 7
  16       DATA (length 1024)

We leave it as an exercise to examine the trace files in more detail.

Nonlinear Thinking
******************

One thing that all of our examples so far have in common is that they are composed of a linear collection of calls into the ns-3 system.  The programmers among the readers may have wondered why there is not as much as a for-loop in all of the examples.  The answer is that we wanted to introduce you to ns-3 scripting with a minimum of conceptual overhead.  We're going to remedy that situation shortly.

We have written a number of ns-3 scripts in C++.  Although we have been perfectly linear in our script implementations, just like any other C++ program, an ns-3 script can use any features of the language you desire.  If you will look back at the linear-dumbbell.cc example, you may notice that the code to create the left and right sides of the dumbbell is operationally identical --- only the names change.  An obvious improvement of this program would be to use subroutines to create the sides. Since we are working with C++, we should probably do this in an object-oriented way.  Since object-oriented design is somewhat of a black art to some people, we'll take some time here and outline a simple methodology you can follow.

Object Design 101 --- Class Ipv4BusNetwork
==========================================

If you are a master of object oriented design, feel free to skip or skim this section, in which we derive a simplistic but fully operational bus network class.

So you want to create a BusNetwork class.  Often the biggest hurdle in a design is figuring out how to get started.  One of the simplest and most straightforward ways to do an object decomposition of a problem is to simply write down a description of the problem and take a look at the words you used.  Let's take some time and do that, first at a very high level.

     A bus network is an implementation of a particular network topology that
     contains some number of nodes.  Each of these nodes is attached to a single
     multi-drop channel.  The network itself has some attributes independent of
     the topology such as a network mask, network number (prefix) and base IP
     address.

The first thing to do is to focus on the nouns and adjectives.  These will give you a starting point for required classes and member variables.

Immediately we can notice that at the highest level we are talking about the noun network.  This probably won't surprise you.  We also have an adjective that modifies the noun --- bus.  This should lead us to our first class defintion.  Usually class names are constructed in the same way as an English language sentence would be spoken.  For example, one would speak of a bus network in conversation, so we would normally create a class BusNetwork to represent it.

One thing to note is that we have used two words in our description quite naturally: is and has.  When you see these words should should immediately think of the object-oriented concepts of ISA (inheritance) and HASA (containment) respectively.  We wrote that a bus network is an implementation of a particular network topology.  Perhaps you will agree that there is a natural base class called Network that has the attributes discussed above.  The fact that a BusNetwork ISA kind of Network suggests inheritance.  Let's capture that thought right away remembering that we're focused on IP version four here:

  class Ipv4Network
  {
  public:
    Ipv4Address m_network;
    Ipv4Mask m_mask;
    Ipv4Address m_baseAddress;
  };

  class Ipv4BusNetwork : public Ipv4Network
  {
  };

Let's take a look at the HASA relationships of the bus network.  Clearly it will have a reference to the underlying channel that implements the actual communications medium.  We use smart pointers for those references, so one member variable is obvious:

  Ptr<CsmaChannel> m_channel;

A bus network will also need to contain references to all of the nodes we eventually want to create.  If you are working in C++ and see the words contain or container, you should immediately think of the Standard Template Library or STL.  A quick search of the available containers there will probably lead you to consider the vector class.  A vector is a container that looks like an array.  This is just what we need here.  Again, we want to use smart pointers to reference our nodes, so the declaration of the vector would look like,

  std::vector<Ptr<Node> > m_nodes;

It will save you headaches in the future if you notice that the space between the two right brackets is required to differentiate this situation from a right-shift operator.  So we have a pretty good start already after just a little work.  Now we need to turn our attention to actions.  Let's write another little description of the things you consider doing to a Bus network.

     We need to be able to create a bus network.  We need to be able to delete a
     bus network.  We need to be able to get a handle to a node in order to add
     applications.  We need to be able to set the network, mask and base address
     somehow, specify how many nodes to create and provide the underlying channel
     its required bandwidth and delay parameters.

We now look at the verbs in that sentence.  These will give a good starting point for the methods of the classes.  For example, the verbs create and delete should suggest constructor and destructor.  The verb get leads us to providing a method called GetNode.  We have to provide a number of parameters so we can either provide setters or we can simply pass them in as parameters to our constructors.  Since this is a simple example, we won't bother to implement getters and setters (methods to get and set member variables to enhance data hiding).  Let's use this guidance to finish up our class declarations:

  class Ipv4Network
  {
  public:
    Ipv4Network (Ipv4Address network, Ipv4Mask mask, Ipv4Address address);
    virtual ~Ipv4Network ();

    Ipv4Address m_network;
    Ipv4Mask m_mask;
    Ipv4Address m_baseAddress;
  };

  class Ipv4BusNetwork : public Ipv4Network
  {
  public:
    Ipv4BusNetwork (
      Ipv4Address   network,
      Ipv4Mask      mask,
      Ipv4Address   startAddress,
      DataRate      bps,
      Time          delay,
      uint32_t      n);

    virtual ~Ipv4BusNetwork ();

    Ptr<Node> GetNode (uint32_t n);

  private:
    std::vector<Ptr<Node> > m_nodes;
    Ptr<CsmaChannel> m_channel;
  };

That's it.  We have actually already walked through almost all of the code required to construct a bus network in our csma-echo.cc example, so let's just jump forward and take a look at an implementation of this thing.  We provide an implementation for you in the files ipv4-bus-network.h and ipv4-bus-network.cc located in the tutorial directory.  We also provide an example that uses the new class in the file bus-network.cc.

The interesting method from our current perspective is the Ipv4BusNetwork constructor, shown below:

  Ipv4BusNetwork::Ipv4BusNetwork (
    Ipv4Address   network,
    Ipv4Mask      mask,
    Ipv4Address   baseAddress,
    DataRate      bps,
    Time          delay,
    uint32_t      n)
  :
    Ipv4Network (network, mask, baseAddress)
  {
    Ipv4AddressGenerator::SeedNetwork (mask, network);
    Ipv4AddressGenerator::SeedAddress (mask, baseAddress);

    m_channel = CsmaTopology::CreateCsmaChannel (bps, delay);

    for (uint32_t i = 0; i < n; ++i)
      {
        Ptr<Node> node = Create<InternetNode> ();
        uint32_t nd = CsmaIpv4Topology::AddIpv4CsmaNetDevice (node, m_channel,
          Mac48Address::Allocate ());
        Ipv4Address address = Ipv4AddressGenerator::AllocateAddress (mask,
          network);
        CsmaIpv4Topology::AddIpv4Address (node, nd, address, mask);
        m_nodes.push_back (node);
      }
  }

Notice that we do the simple and straightforward thing and pass all of our parameters to the constructor.  For those unfamiliar with C++, the line after the colon and before the opening brace (shown below),

  :
    Ipv4Network (network, mask, baseAddress)
  {

Passes the appropriate parameters to the constructor of the base class Ipv4Network.  There are two new calls that we haven't seen immediately after this initialization.  They are:

    Ipv4AddressGenerator::SeedNetwork (mask, network);
    Ipv4AddressGenerator::SeedAddress (mask, baseAddress);

We provide an IP address generator class to allow us to programatically allocate IP addresses.  The first call to SeedNetwork gives the address generator a starting network number to use when generating addresses. The second call to SeedAddress gives the address generator a starting IP address to use.  There is a starting network and starting address for each of the 32 possible network masks.  Later in the for loop, you will see a call to AllocateAddress in which the IP address for each node created in the loop is actually generated.

The only unfamiliar call in the reset of the constructor will be:

  m_nodes.push_back (node);

This is the STL code to add the newly created node to the vector of nodes attached to the bus.

For your convenience, we reproduce the entire bus network implementation below:

  /* -*-  Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
  /*
   * Copyright (c) 2007 University of Washington
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License version 2 as
   * published by the Free Software Foundation;
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */

  #include "ns3/mac48-address.h"
  #include "ns3/csma-net-device.h"
  #include "ns3/csma-topology.h"
  #include "ns3/csma-ipv4-topology.h"

  #include "ipv4-bus-network.h"
  #include "ipv4-address-generator.h"

  namespace ns3 {

  Ipv4Network::Ipv4Network (
    Ipv4Address   network,
    Ipv4Mask      mask,
    Ipv4Address   address)
  :
    m_network (network), m_mask (mask), m_baseAddress (address)
  {
  }

  Ipv4Network::~Ipv4Network ()
  {
  }

  Ipv4BusNetwork::Ipv4BusNetwork (
    Ipv4Address   network,
    Ipv4Mask      mask,
    Ipv4Address   baseAddress,
    DataRate      bps,
    Time          delay,
    uint32_t      n)
  :
    Ipv4Network (network, mask, baseAddress)
  {
    Ipv4AddressGenerator::SeedNetwork (mask, network);
    Ipv4AddressGenerator::SeedAddress (mask, baseAddress);

    m_channel = CsmaTopology::CreateCsmaChannel (bps, delay);

    for (uint32_t i = 0; i < n; ++i)
      {
        Ptr<Node> node = Create<InternetNode> ();
        uint32_t nd = CsmaIpv4Topology::AddIpv4CsmaNetDevice (node, m_channel,
          Mac48Address::Allocate ());
        Ipv4Address address = Ipv4AddressGenerator::AllocateAddress (mask,
          network);
        CsmaIpv4Topology::AddIpv4Address (node, nd, address, mask);
        m_nodes.push_back (node);
      }
  }

  Ipv4BusNetwork::~Ipv4BusNetwork ()
  {
  }

    Ptr<Node>
  Ipv4BusNetwork::GetNode (uint32_t n)
  {
    return m_nodes[n];
  }

  }; // namespace ns3

Using Ipv4BusNetwork
====================

If all you ever want to do with a bus network can be captured in a topology with four nodes on the bus, the preceeding section may seem like a colossal waste of time.  This is probably not the case, though.  Now that we have a relatively abstract bus class, we can create bus networks with 4, 40 or 4000 nodes with no additional effort.

A use of the bus network class is shown in the file bus-netowrk.cc located in the tutorial directory.  The interesting code is,

  Ipv4BusNetwork bus ("10.1.0.0", "255.255.0.0", "0.0.0.3",
    DataRate(10000000), MilliSeconds(20), 10);

Here we create a bus network with the network number ``10.1.0.0'' and the network mask ``255.255.0.0'' that completes the IP network definition.  You can consider these together as ``10.1.0.0/16'' if you prefer.  The next parameter tells the bus to start numbering IP addresses of contained nodes at ``10.1.0.3'' (remember the network number will be combined).  We provided a data rate of 10 megabits per second and a latency of 20 milliseconds. Finally, we ask the Ipv4BusNetwork object to create ten nodes in the network.

If you are feeling brave, go ahead and change the number of nodes to be 100, 1000, 10,000 or more to generate larger and larger networks.  Before you go too far, remember that a trace file will be generated when you run your resulting program and ee asked the trace facility to trace all net device receive events.  This will include the reception of the broadcast ARP request by all of the nodes in the simulation, so this can add up quickly.

Summary
*******

This concludes the first part of the tutorial.  We have focused on using the ns-3 system to construct various network topologies and to simulate sendng data across the networks; and we've shown you how to use the trace facility to get access to simulation results.

We now encourage you to play with the system a little.  Experiment with what we have provided.  Build a hierarchical network simulation.  Perhaps exercise your object design skills and create a new Ipv4DumbbellNetwork class to create dumbbell networks using the Ipv4BusNetwork class we just created. Hint:  An Ipv4DumbbellNetwork has two Ipv4BusNetwork objects; a left side and a right side.

In the next part of the tutorial we are going to drop down a level and begin examining the lower levels of the system in more detail.  We are going to explain how to change the behavior of the system and eventually how to write new models and applications.  This is a good time to make sure that you thorougly understand what we've gone over so far.

Object Model
************

There are two distinctly different meanings associated with the term Object Model.  The first speaks to the implementation of an object system --- a system view; and the second speaks to the application programming interface (classes or objects) one uses to access some service or system --- an application view.

As an example of the system view sense of the term, the C++ language has an associated object model that describes how objects are laid out in memory, how virtual functions work, how inheritance is implemented, constructor and destructor execution ordering, template instantiation, etc.

In the case of the application view, the Document Object Model is a good example.  In the words of W3C, the Document Object Model (DOM) is an application programming interface (API) for HTML and XML documents. It defines the logical structure of documents and the way a document is accessed and manipulated.

The Component Object Model (COM) from Microsoft actually spans both meanings of the term and extends further into policy statements.  From a system perspective, COM specifies an interface definition language, the layout of objects virtual function tables, the formats of Globally Unique Identifiers and also specifies lifetime management mechanisms for objects via reference counting.  From the point of view of the API, COM specifies a number of Interfaces as well as functions such as CoCreateInstance and various threading models.  The COM specification extends to policy by disallowing implementation inheritance.

The ns-3 object model takes the C++ language (system level) object model as its basis, and extends that model by providing an API for software componentry.  You will find terms like Component, Interface and QueryInterface in the following discussion.  It is important to understand from the outset that this is the ns-3 object model, and not any other object model. Richard Feynman (an American physicist) once described the behavior of matter and light on a very small scale in the following way,

     ``They do not behave like waves, they do not behave like particles, they do not behave like clouds, or billiard balls, or weights on springs, or like anything that you have ever seen.''

Just as students of quantum mechanics must rid themselves of preconceptions regarding the behavior of matter at small scales, you should rid yourself of any preconceptions you may have about components, interfaces and APIs for software componentry before continuing.  To paraphrase Feynman, ns-3 components do not behave like COM Components, or Java Beans, or CORBA objects, or clouds or weights on springs, or like anything that you have ever seen  they are ns-3 components.

The C++ Object Model is the Root of all Things
==============================================

Ns-3 is primarily a C++ system.  The system is written in C++ and one can use standard C++ mechanisms for creating and using ns-3 objects.  We do not change this at all, nor do we make any pronouncements about the superiority of one mechanism or another.  What we will do is provide convenience functions that we think will make creating and managing simulation objects easier.

Previously, you have seen objects created using the template function Create as in the following example:

  Ptr<Node> n0 = Create<InternetNode> ();

This line of code, while it may be unfamiliar to some, is pure C++.  If you were to look in the header file ptr.h, you would find the following definition of the Create template.

  template <typename T>
  Ptr<T> Create (void)
  {
    T *obj = new T ();
    Ptr<T> p = obj;
    obj->Unref ();
    return p;
  }

As you can see, this template creates objects of type T using the operator new.  Its a little harder to find the corresponding delete --- it's in the file object.cc inside the method Object::MaybeDelete, but when that Ptr which you see above goes out of scope it will call Unref and ultimately the C++ delete operator will be called.

The ns-3 system uses the C++ new and delete operators, so there is really no reason that you as a user of the ns-3 system are forbidden from using these or any other C++ mechanism.  If you so desire, you can take on the responsibility for managing object lifetime (i.e., do not use the Ptr smart pointer), work directly with the new and delete operators and call methods like any C++ object as in the following example:

  MyClass *obj = new MyClass ();
  obj->Method();
  delete obj;

You, as a competent model author, are encouraged to use whatever methods you think are appropriate in your private code.  Remember, however, that the public ns-3 APIs do use smart pointers to pass objects around in an effort to reduce the burden of object lifetime management.  If you do intend to export an API publicly, you should use the same object lifetime management approaches as those found in the ns-3 public API if only for consistency.

These APIs are there for convenience and consistency, but do not change the fact that in ns-3 all of the objects are really just C++ objects, ultimately created using the C++ new operator with C++ constructor semantics and are ultimately deleted using the C++ delete operator, following C++ destructor semantics.  Although it may sometimes appear so, there is really no system- level magic going on in ns-3.  Ns-3 components and interfaces are C++ objects just like any other object and our object model is simply a collection of APIs built on the normal C++ object model.

Interfaces
==========

There are many different ideas floating around of what exactly the term Interface means.  Originally an interface just meant a communication boundary between two entities.  As the concepts of object oriented programming (OOP) were surfacing in the 1980s, the term interface was applied to the collection of access methods for the modular entities that were being defined.

Two distinct approaches developed regarding specifying access mechanisms for objects.  The OOP purists were very concerned about object reuse and were led to Abstract Data Types (ADT).  These were eventually implemented in the case of C++, as pure virtual methods in Abstract Base Classes (ABC).  Another group of folks was more interested in simply specifying object access methods in one place and using inheritance as the primary reuse mechanism.

Bjarne Stroustroup, the creator of C++, embraced both approaches.  He makes the following interesting observation:

     ``Many classes [...] are useful both as themselves and also as bases for derived classes. [...] Some classes, such as class Shape, represent abstract concepts for which objects cannot exist.''

Ns-3 does not pick and enforce a particular approach.  In ns-3 an interface is determined completely by a class declaration just as any C++ object interface is declared.  If you think of an object as an abstract concept that should be implemented by derived classes, by all means, use the Abstract Base Class approach to interface declaration.  If you think that an object should be completely concrete and you foresee no need to ever modify its behavior, feel free to avoid declaring any methods virtual. If you think that an object could be useful as a base class, feel free to declare its methods virtual.  If you like to use the PIMPL idiom, again, feel free.  If you want to use any combination of these techniques, feel free. We make no restrictions.

When we speak of an ns-3 interface, we do not worry about interface definition languages, or pure virtual classes, or registries  we just think about C++ object declarations and their associated methods.  When we instantiate an ns-3 Interface, it is the C++ object model that dictates how that object is brought into existence.  When a method is called on an ns-3 Interface, it is the C++ object model that dictates how that method is dispatched.

The only difference between a vanilla C++ object and an ns-3 Interface, is that an object acting as an ns-3 Interface must inherit from the base class Object.  This inheritance gives the Interface object a very useful capability.

The Ns-3 Capital I Interface and QueryInterface
===============================================

One thing that Microsoft got right in the Component Object Model was the idea of Interface aggregation and discovery via QueryInterface.  We have embraced these ideas in ns-3.  This was done primarily to address a common problem in large software systems.  A good example of this problem happens in the ns-3 Node class.

If one were to take the standard OOP view of specializing a Node into an internet host, for example, one would typically inherit from the Node base class and include functionality to implement such things as internet routing and a TCP / IP protocol stack.  Other types of Nodes might inherit from the node class and specialize in different ways, or further specialize the internet host class, treating it as a base class.  This can result in a complicated inheritance tree in which some specializations are simply not available to other branches of the tree which can make reuse difficult or impossible.  This is known as the weak base class problem and creates pressure to drive functionality up the inheritance tree into the base classes.  This, in turn, results in base class bloat and the resulting swiss army knife base classes which end up trying to do everything in one place.

Even if one successfully avoided these swiss army knife base classes, one would also want to be able to treat new specializations of Node generically in the system.  This means one would pass references to the base class (Node) across public APIs.  This introduces upcasts prior to passing across public APIs and corresponding downcasts on the other side in order to gain access to required specialized functions.  As the inheritance tree becomes more complicated, this approach can cause another related problem known as the fragile base class problem.  This happens when changes to the base class cause unexpected problems in the various and sundry subclasses.

These effects seem always to result in a positive feedback loop driving everything into the base class and destroying much of the encapsulation which is a hallmark of the object oriented approach.

Interface Composition
---------------------

There is a completely different way to address the Node specialization problem.  Instead of approaching the situation using inheritance, one can look at the problem as one of composition.  We can look at the Node class as a container of sorts that holds other objects.  In this case, the objects would be instances of the classes implementing the internetwork routing code, or the TCP / IP protocol stack described above.  This approach preserves the encapsulation and solves the weak base class, base class bloat and fragile base class problems; but the question of method dispatch immediately comes to mind.

In many systems, delegation is used.  The base class, Node, in this approach would provide methods that simply forward to the objects implementing the desired functionality.  This situation clearly does not address the base class bloat problem since dispatch methods must be added to the base class.  The situation is mitigated somewhat by pushing the implementation of the dispatch methods to contained objects, but the fundamental problems are still present.  What is really needed is a way to compose objects but at the same time keep the interfaces to those objects separated.

Composition, usually called aggregation, along with runtime Interface discovery is the solution that Microsoft originally championed and that ns-3 has adopted.  In our example a Node would contain separate Interface objects implementing internetwork routing and TCP/IP. These contained objects have interfaces in the C++ sense of collections of method signatures.  When objects are capable of participating in this aggregation process, they are called ns-3 Interfaces and they receive the functionality required for this participation by inheriting from the base class Object.

Object, interfaces and Interfaces
---------------------------------

As mentioned above, the class that implements the aggregation mechanism for ns-3 objects is called Object.  The class named Object is simply a base class that you will inherit from if you want your objects to support aggregation and QueryInterface.  Many systems have a base class that implements common functionality and these base classes are typically called Object.  The ns-3 version of this object base class relates primarily to Interface aggregation, although it does provide methods to help with intrusive reference counting and tracing as well.

When a C++ object inherits from the ns-3 Object base class, it is conceptually promoted to an ns-3 Interface (note the capital I in Interface) irrespective of how the object was declared (e.g., as an abstract base class, concrete class, with virtual methods, etc.).   In ns-3, you should associate inheritance from the class named Object with promotion of an object to the status of Interface rather than the form of the Interface declaration.

When you inherit from Object, you will get new methods and an Interface Identifier.  The Interface Identifer, or iid, is the ns-3 version of the Universally Unique ID (UUID) or Globally Unique ID (GUID) found in other systems.  Unlike the GUID, it is really a dynamically created process-local ID.  For now, consider it as simply a number which the system will generate for you that uniquely identifies an Interface class within the ns-3 system and allows you to specify an interface type to QueryInterface.

To summarize, when you instantiate an object that inherits from the Object class, you will have a C++ object that has four important properties:

  * The object has a C++ interface defined by the collection of method signatures in its inheritance tree;

  * The object has an Interface ID that uniquely identifies the C++ interface of its class;

  * The object is a container that has the ability to aggregate other interfaces;

  * The object exports a method that allows for discovery of aggregated interfaces (QueryInterface) according to Interface ID.

It is crucially important to understand what we have described here.  A given C++ class has an object access interface that is essentially the collection of method signatures specified in its inheritance tree.  This is a C++ object model thing.  Ns-3 provides a base class from which the class in question can inherit and be promoted to the status of Interface.  Once a class becomes an Interface it has inherited the ability to set its own interface identifier (iid), and exports methods to aggregate and search other Interfaces that are added to its aggregation.

That last detail is important.  In ns-3 Interfaces are both containers and specifications for object method access.  We have previously mentioned the Node class acts as a container.  In fact, the Node class inherits from Object and is itself also an ns-3 Interface.  When the Node object is created it is really an aggregation of one Interface, the Node Interface.  This is generally true --- Interfaces are both containers and Interfaces.

Aggregations
------------

The figure below shows how an Interface could be illustrated in detail.  The line with the circle at the top of the diagram represents the appearance of the Interface to the external world.  This circle and line are called a lollipop because of its superficial similarity to a kind of childs candy.

You could declare this interface quite simply using a non-virtual class as follows,

  class A : public Object {
  public:
    static const InterfaceId iid;
    void MethodA (void);
  };

The methods that are then available via the Interface labeled A in the figure above are the methods inherited from the Object base class ( QueryInterface, Ref, and Unref) and those from class A (MethodA).  Note that you must declare an InterfaceId for your Interface class, and it must be declared static to make it class-wide in scope.  This iid can be thought of as a kind of type information that uniquely identifies objects as being instantiated from this class.

You can think of the arc and arrow device coming off each side of the Interface as part of a connector.  These connectors allow QueryInterface to search aggregations for a particular iid.  The figure below shows an aggregation of three Interfaces: A, B and C.  The class declarations for classes B and C are substantially similar to that of class A.

You can visualize these Interfaces as being snapped together like Lego building blocks if you like.  When the Interfaces are aggregated, a QueryInterface search path is formed through the connectors.  In order to create this aggregation we first need to create the Interface objects. These are just normal, everyday C++ objects that we can create using the Create template function and manage using smart pointers.  The following code should be obvious to you by now:

  Ptr<A> a = Create<A> ();
  Ptr<B> b = Create<B> ();
  Ptr<C> c = Create<C> ();

When you create an aggregation, you pick one of the Interfaces to act as the container.  In this case well pick Interface A.  In order to aggregate an Interface, you simply call the method AddInterface that your class inherited from Object.  The following code will aggregate Interface B and Interface C onto the Interface (and container) A.

  a->AddInterface (b);
  a->AddInterface (c);

Thats all there is to it.  Now that you have those connectors snapped together, you can ask each of the Interfaces in the aggregation for any of the Interfaces in the aggregation.  Lets look at a simple example:

  Ptr<B> newB = a->QueryInterface<B> (B:iid);

The left hand side of this assignment declares a smart pointer to the class B to help with memory management of the returned Interface pointer. Object lifetime management is very important when dealing with Interfaces and our smart pointer will simply take care of it all for you.

The right hand side illustrates the basic idea of QueryInterface.  We take a take a (smart) pointer to Interface A and ask it to search the aggregation for an interface associated with an interface identifier with the value of B:iid which is passed as a parameter.  Recall that B::iid is the static InterfaceId of the Interface class B.  Observe that QueryInterface is a template function and the type specified in the angle brackets, here <B>, tells it what kind of smart pointer to return.  In this case QueryInterface will find an Interface object of type B::iid in its list of Interfaces and return a smart pointer to B as instructed.

Now that you have those connectors snapped together, you can ask each of the Interfaces in the aggregation for any of the Interfaces in the aggregation.  For example we could walk the Interfaces asking each for the next in the aggregation.  First we would ask the Interface pointed to by the smart pointer a to look for the InterfaceId representing B:

  Ptr<B> newB = a->QueryInterface<B> (B:iid);

Next, we can ask the Interface pointed to by the smart pointer newB to look for the InterfaceId representing C:

  Ptr<C> newC = newB->QueryInterface<C> (C:iid);

Then, we can ask the Interface pointed to by the smart pointer newC to look for the InterfaceId representing A and complete our circuit of the aggregation:

  Ptr<A> newA = newC->QueryInterface<A> (A:iid);

QueryInterface (often abbreviated QI) has some important properties that we need to go over.  Technically, QI is a symmetric, reflexive and transitive operation with respect to the set of aggregated Interfaces.

Symmetry
........

The symmetric nature of QI guarantees that if one performs a QI on a given Interface for the Interface Id of that same interface, that QueryInterface must succeed.  The existence of interface A in the aggregation implies the reachability of Interface A in the aggregation.  This is usually written (by Microsoft) as,

must succeed (A >> A)

We can illustrate this property with the code snippet,

  Ptr<A> symmetricA = a->QueryInterface<A> (A:iid);
  NS_ASSERT (symmetricA);

Here we take as given an interface (smart) pointer named a on which we perform a QI looking for the InterfaceId of that same Interface.  This call must always succeed and a smart pointer to the Interface a is returned by QI.

Reflexivity
...........

Calls to QI must also be reflexive.  This means that if you successfully QI for interface B from interface A, then you must always be able to QI for A from B.  This is usually written as,

must succeed (A >> B, then B >> A)

This property can be illustrated with the code snippet,

  Ptr<B> b = a->QueryInterface<B> (B:iid);
  Ptr<A> reflexiveA = b->QueryInterface<A> (A:iid);
  NS_ASSERT (reflexiveA);

If the first QueryInterface on Interface A looking for Interface B succeeds, then a QueryInterface on Interface B looking for Interface A must succeed.

Transitivity
............

QueryInteface must also be transitive.  This means that if one can find Interface B from Interface A, and Interface C from Interface B, then one must also be able to find interface C from Interface A.  This is usually written as,

must succeed (A >> B, and B >> C, then A >> C)

This property can be illustrated with the code snippet,

  Ptr<B> b = a->QueryInterface<B> (B:iid);
  Ptr<C> c = b->QueryInterface<C> (C:iid);
  Ptr<C> transitiveC = a->QueryInterface<C> (C:iid);
  NS_ASSERT (transitiveC);

If you can get to Interface B from Interface A, and you can get to Interface C from Interface B, then a QueryInterface on Interface A looking for Interface C must also succeed.

The final piece of this puzzle is to locate where the interface Ids actually come from.  The answer is from a static initializer that must be located in the .cc file corresponding to the Interface.  For example, to initialize the Interface Id for the class A above, you would simply add the following code to the source file that implements class A,

  const InterfaceId A::iid =
    MakeInterfaceId (``A'', Object::iid);

This code is guaranteed by the C++ language definition to be executed before your main procedure is entered.  The call to MakeInterfaceId will assign a process-local unique identifier to your class and associate your interface with the name (string) ``A.''  This allows you to look up an InterfaceId by a human readable string.

An advanced ns-3 specific feature of QueryInterface is exposed here. MakeInterfaceId takes an InterfaceId as a parameter.  This is the iid of the base class from which you inherited.  In most cases this will be Object::iid, which is the InterfaceId of the Object base class.  In ns-3, the Object base class has its own iid and you can QI for that iid.  The Object base class has a rough equivalence to the IUnknown Interface in Microsofts COM, so you can QI for Object::iid in ns-3f just as you might QI for IID_IUnknown in COM.

The InterfaceId you pass to MakeInterfaceId is used to create an inheritance tree in the ns-3 interface manager.  This inheritance tree is also walked in QueryInterface Interface searches.  Consider a simple case of a base class and a derived class as shown below,

  class Base : public Object
  {
  public:
    static const InterfaceId iid;
    ...
  };

  class Derived : public Base
  {
  public:
    static const InterfaceId iid;
    ...
  };

To assign the InterfaceId for each of these classes, we could add two calls to MakeInterfaceId reflecting the class hierarchy we just created.

  const InterfaceId Base::iid =
    MakeInterfaceId (``Base'', Object::iid);

  const InterfaceId Derived::iid =
    MakeInterfaceId (``Derived'', Base::iid);

The first Interface is shown to inherit from class Object and the second inherits from class Base.  We could create these interfaces as we usually do,

Ptr<Base> base = Create<Base> ();
Ptr<Derived> derived = Create<Derived> ();

The derived and base InterfaceIds are either present or not present based on the inheritance tree.  For example, a QI for the Base InterfaceId must succeed when done against a Ptr<Base>; but a QI for the Derived InterfaceId must fail when done against a Ptr<Base>. However, a QI for the Base InterfaceId must succeed when done against a Ptr<Derived>; and a QI for the Derived InterfaceId must succeed when done against a Ptr<Derived>.

This feature allows you to use implementation inheritance to easily create new Interfaces.  You are prevented from doing so in Microsoft COM, but this was almost universally identified as a problem.

A Real Example
--------------

At this point you may be asking yourself what the point of all of this is, since you already had those pointers laying around when you created the objects.  The typical case is that you would forget about the pointers to the contained objects and only export a single Interface.  Other Interfaces could be discovered using QI.

Generally one tends to think of one of the Interfaces in the aggregation as being the container and other Interfaces being aggregated to that container.  In the case of a Node, for example, it is quite natural to think of the Node as being the container which contains Interfaces for the protocol stacks, internet routing, etc.  So, lets start developing an example by calling the container Interface Node instead of A.  The creation of this Interface is found all over our example programs.  For example, you will find code like the following in samples/simple-point-to-point.cc:

  Ptr<Node> n = Create<InternetNode> ();

This code is described in detail in previous sections, but the important thing to realize here is that the resulting Node is an ns-3 Interface.  This is not at all obvious -- you must look at the source code to see that this is true.  Take a look at src/node/node.h and find the class declaration for class Node.  There you will find,

  class Node : public Object
  {
  public:
    static const InterfaceId iid;
    ...
  };

Class Node inherits from class Object and provides an InterfaceId, therefore it is an ns-3 interface.  You now know you can use AddInterface for aggregation and QueryInterface for Interface discovery against any Node in the system.

We spoke of a protocol stack that is aggregated to a Node in our discussions above, what we see in the real ns-3 code is that this is represented by the Ipv4 Interface.  If you look in src/node/ipv4.h you will find,

  class Ipv4 : public Object
  {
  public:
    static const InterfaceId iid;
    ...
  };

Since class Ipv4 inherits from class Object and has a static InterfaceId, it is an ns-3 Interface.  If you look in src/node/ipv4.cc you will find,

  const InterfaceId Ipv4::iid =
    MakeInterfaceId (``Ipv4'', Object::iid);

After all of this reading you now know that this code snippet is asking the system to create a unique InterfaceId for the Ipv4 class and declares that Ipv4 inherits from class Object.

It turns out that the Ipv4 class is an abstract base class (ABC).  There are a number of pure virtual methods declared in that class.  This means that an Ipv4 object may not be instantiated.  What is instantiated is an implementation class, called Ipv4Impl.  This class inherits from Ipv4 and provides the required virtual methods.  This is where understanding what is an Interface and what is not gets tricky.  The Interface is the Ipv4 class since that is where the InterfaceId is found.  The fact that you see ipv4::iid tells you that the Ipv4 class is the Interface and has the associated InterfaceId. The class Ipv4Impl provides an implementation for the pure virtual methods in Ipv4.  Since class Ipv4 cannot be instantiated, one instantiates the Ipv4Impl class to create an Ipv4 Interface. Once the Ipv4Impl class is instantiated, the pointer to it is immediately cast to an Ipv4 pointer.  Clients will then use the Ipv4 object access methods (see ipv4.h) to talk to the Ipv4Impl object over the Ipv4 Interface.  I urge you to not go any further until you thoroughly understand what youve just read.

If you now look in the file, src/internet-node/internet-node.cc you will see the following code in InternetNode::Construct that creates the Ipv4 Interface and aggregates it to the Node interface (recall that class Node is an Interface and class InternetNode inherits from class Node):

  Ptr<Ipv4Impl> ipv4Impl = Create<Ipv4Impl> (ipv4);
  ...
  Object::AddInterface (ipv4Impl);

Note that the parameter ipv4 passed to the Create template function is actually a pointer to an Ipv4L3Protocol which you can ignore at this point --- it doesn't really have anything to do with the Ipv4 Interface.

This last example does illustrate that the fact that whether an ns-3 object is or is not an Interface can be quite well hidden.  The designers of the system had long and involved discussions on this issue and in the end decided that mnemonic aids such as Hungarian notation were a stylistic thing and you should just refer to the system documentation to determine what objects are ns-3 Interfaces and what those Interfaces actually are (RTFM --- Read the Fine Manual).

In this case, you know that the class Ipv4Impl inherits from some Interface since there is a call to AddInterface that refers to it. You can go to the header file src/internet-node/ipv4-impl.h and find that Ipv4Impl inherits from class Ipv4.  You then go to file src/node/ipv4.h and see that it inherits from Object and contains an InterfaceId.  Thus the Interface added is really the Ipv4 Interface with the interface Id Ipv4::iid.

Returning to some ns-3 example code, lets take a look at src/examples/simple-point-to-point.cc again.  You will find the following code:

  Ptr<Node> n0 = Create<InternetNode> ();
  ...
  Ptr<Ipv4> ipv4;
  ipv4 = n0->QueryInterface<Ipv4> (Ipv4::iid);
  ipv4->SetDefaultRoute (Ipv4Address (``10.1.1.2''), 1);

The first line creates an InternetNode object and casts the resulting smart pointer to a Node.  The next line declares a smart pointer to an Ipv4 object.  Because youve been through the code with us, you know that both the Node and the Ipv4 objects are Interfaces.  They should be able to participate in a QueryInterface.

The next line confirms it.  We do a QueryInterface on the Node, looking for the Ipv4 Interface (Ipv4::iid). QueryInterface then returns a smart pointer to its aggregated Ipv4 Interface.  [Recall that this Interface was aggregated in InternetNode::Construct.  We knew to start looking for the aggregation in InternetNode since we originally created an InternetNode in the Create template function and then implicitly cast it to a Node.]

Once you have the Ipv4 smart pointer, you simply use it as if it were any other C++ object.  The last line shows this by setting the default route for the node.

Caveats
=======

There are a few things that you should remember but which may not be immediately obvious.

Interface Ids are Associated with Classes not Objects
-----------------------------------------------------

Interfaces are identified by an InterfaceId that is associated with the Interface class, not the Interface object.  That is indicated by the static keyword in the declaration of the iid in the class.  The interface Id for a given Interface class exists independently of any objects of that class that you may instantiate; and all objects of a given Interface type share the same InterfaceId.

You cannot add more than one Interface of a given type (iid) to an aggregation.  If you need to contain a number of Interfaces of the same type in the same aggregation, you will need to provide a separate container over which you can iterate.  For example, the Node class provides methods,

  uint32_t GetNDevices (void) const;
  Ptr<NetDevice> GetDevice (uint32_t index) const;

that are used iterate over the multiple NetDevice Interfaces associated with it.

Interface Ids do not identify objects.

Dont use QI to Check Your Own Type.
-----------------------------------

It is tempting to use QueryInterface as a form of runtime type information.  Dont do it.  You have no control over what other object may be added to your aggregation and this may cause problems.  Someone else may have appropriated (reimplemented) your type and aggregated themselves onto your aggregation.

Consider a socket factory implementation.  Sockets can be either UDP sockets or TCP sockets.  A socket factory will have a generic SocketFactory Interface and either a UDP specific interface for setting UDP parameters or a similar TCP-specific interface.

Consider what might happen if you declared your socket factory as a partially abstract base class, and then provided separate implementations for UDP and TCP specific methods of this factory in separate concrete classes.  Now consider what might happen if you used QueryInterface in your base class to determine if you were a UDP or a TCP factory.

If a factory, say the UDP version, were not aggregated to any other Interface, the base class could QueryInterface on itself for the UDP-specific interface. It could then infer that it was a UDP implementation and would then do any UDP-specific tasks it could.  [Experienced C++ folks are cringing about how horrible this design is, but bear with me --- its a simple illustration of a specific and perhaps not-too-obvious problem.]

If another factory, say the TCP version, were not aggregated to any other Interface, the base class could QueryInterface on itself for the UDP-specific interface.  If this failed, it could then infer that it had a TCP implementation and would then do any TCP-specific tasks it could.

Now, what happens when these two working objects are aggregated together. Since the Interfaces are conceptually snapped together the TCP implementation would suddenly begin finding the UDP Interface from the other class factory and fail.

Interface Ids should not be used as run-time type information.

Connecting the Dots
===================

This may all sound very complicated to you if this is your first exposure to these concepts.  It may be annoying if I tell you that its really not as hard as it sounds.  Rest assured that if you take some time, look at and understand the examples and write a little test code it will all come together for you. Grep around the system for AddInterface and QueryInterface and take a look at how we have used them.  This will also give you a good idea of what our core Interfaces are.  If you grep for ::iid you will find most, if not all of the interface declarations in the system.  The more you see this idiom in use, the more comfortable you will be with the idea and the more you will see how this addresses the weak base class, swiss army knife base class, and fragile base class problems I explained at the beginning.

As I alluded to earlier, the developers had long discussions regarding how to make navigating the QueryInterface environment easier.  The primary issue was how we could make it easier to convey to you, the model writer, that an object was an Interface.  One suggestion was to adopt the convention that classes that implement Interfaces begin with the letter I.  Microsoft does this, as exemplified by the class IUnknown.  We also toyed with the idea of beginning our header files with i- as in i-ipv4.h. We considered forcing some structure on Interfaces with a pure virtual class specification, the names of which begin with an I; and corresponding implementations, the names of which begin with a C.

In the end we decided that we were really discussing issues of programming style, and we really could not come up with a strong reason to impose any particular solution.  In the end, we decided that we would not impose any structure on the source code, nor impose any naming convention.  We will rely on our documentation system (Doxygen) to break out all objects with InterfaceIds in their class hierarchy into a separate section.  For now, until this is implemented, grep is your friend.

The Doxygen Documentation System
********************************

How to Change Things
********************

How to Set Default Values
*************************