Tutorial Goals
**************

The goal of this ns-3 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;

  * Understand the key software conventions of ns-3;

  * Modify configuration parameters of existing scripts;

  * Change the simulation output (tracing, logging, statistics);

  * Extend the simulator to use new objects

  * Write new ns-3 applications;

  * See how to port code from ns-2;

  * ... (more to follow)

Overview
********

The ns-3 simulator is a discrete-event network simulator targeted primarily for research and educational use. The ns-3 project, started in 2006, is an open-source project.  The goal of the project is to build a new network simulator primarily for research and educational use.

Primary documentation for the ns-3 project is available in three forms:

  * ns-3 Doxygen/Manual:  Documentation of the public APIs of the simulator

  * Tutorial (this document)

  * ns-3 wiki

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.

A few key points are worth noting at the onset:

  * ns-3 is not an extension of ns-2; it is a new simulator.  The two simulators are both written in C++ but ns-3 is a new simulator that does not support the ns-2 APIs. Some models from ns-2 have already been ported from ns-2 to ns-3. The project will continue to maintain ns-2 while ns-3 is being built, and will study transition and integration mechanisms.

  * ns-3 is open-source, and the project strives to maintain an open environment for researchers to contribute and share their software.

For ns-2 users
==============

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 can be visualized using the Network Animator nam.  In ns-3 there is currently no visualization module, and Python bindings have been developed (Tcl bindings have been prototyped using SWIG, but are not supported by the current development team). In this tutorial, we will concentrate on scripting directly in C++ and interpreting results via trace files.

But there are similarities as well (both, for example, are based on C++ objects, and some code from ns-2 has already been ported to ns-3).  We will try to highlight differences between ns-2 and ns-3 as we proceed in this tutorial.

Contributing
============

ns-3 is a research and educational simulator, by and for the research community.  It will rely on the ongoing contributions of the community to develop new models, debug or maintain existing ones, and share results.  There are a few policies that we hope will encourage people to contribute to ns-3 like they have for ns-2:

  * open source licensing based on GNU GPLv2 compatibility

  * wiki

  * Contributed Code page, similar to ns-2's popular Contributed Code page

  * src/contrib directory (we will host your contributed code)

  * open bug tracker

  * ns-3 developers will gladly help potential contributors to get started with the simulator (please contact one of us)

If you are an ns user, please consider to provide your feedback, bug fixes, or code to the project.

Tutorial organization
=====================

The tutorial assumes that new users might follow a path such as follows:

  * browse the source code and documentation, to get a feel for the simulator and what it might be like to handle;

  * try to download and build a copy;

  * try to run a few sample programs, and perhaps change some configurations;

  * look at simulation output, and try to adjust it

  * study the software architecture of the system, to consider hacking it or extending it;

  * write new models or port existing code to ns-3, and eventually post those models back to the community.

As a result, we have tried to organize the tutorial along the above broad sequences of events.

Browsing ns-3
*************

Source code
===========

The most recent code can be browsed on our web server at the following link: http://code.nsnam.org/?sort=lastchange.  If you click on the bold repository names on the left of the page, you will see changelogs for these repositories, and links to the manifest.  From the manifest links, one can browse the source tree.

The top-level directory will look something like:
  AUTHORS  RELEASE_NOTES  examples/  src/       waf*
  LICENSE  VERSION        ns3/       tutorial/  waf.bat*
  README   doc/           samples/   utils/     wscript
The source code is mainly in the src directory.  Example scripts are in the examples directory.  Both are good directories to start browsing some code.

For ns-2 users, who may be familiar with the simple.tcl example script in the ns-2 documentation, an analogous script is found in examples/simple-point-to-point.cc with a Python equivalent found in (pending Python merge).

Doxygen
=======

We document all of APIs using Doxygen.  Current builds of this documentation are available at: http://www.nsnam.org/doxygen/index.html, which are worth an initial look.

Other documentation
===================

See:  http://www.nsnam.org/documents.html.

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 an 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.  Cygwin provides many of the popular Linux system commands. 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, we 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 within the simulation core, 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.

Downloading and Compiling
*************************

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.

One practice is to create a directory called repos in one's home directory under which one can keep local Mercurial repositories. Hint:  we will assume you do this later in the tutorial. If you adopt that approach, you can get a copy of any of the development versions of ns-3 by typing the following into your Linux shell (assuming you have installed Mercurial):

  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 check

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

  Compilation finished successfully

and you will see a number of software unit tests subsequently execute.

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

If you want to run programs under another tool such as gdb or valgrind, see this wiki entry.

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 = CreateObject<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 = CreateObject<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.

  ... = CreateObject<InternetNode> ();

This turns out to be an instance of use of a C++ function template. The definition of the CreateObject<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;
  ... = CreateObject<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;
  ... = CreateObject<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 = CreateObject<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 = CreateObject<InternetNode> ();

or,

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

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

  Ptr<Node> p = CreateObject<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 = CreateObject<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-stack.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-stack.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 = CreateObject<InternetNode> ();
  Ptr<Node> n1 = CreateObject<InternetNode> ();
  Ptr<Node> n2 = CreateObject<InternetNode> ();
  Ptr<Node> n3 = CreateObject<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-stack.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 = CreateObject<InternetNode> ();
    Ptr<Node> n1 = CreateObject<InternetNode> ();
    Ptr<Node> n2 = CreateObject<InternetNode> ();
    Ptr<Node> n3 = CreateObject<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 = CreateObject<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 = CreateObject<UdpEchoClient> (n0, addr, port,
    maxPacketCount, interPacketInterval, packetSize);

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

  Ptr<UdpEchoClient> client = CreateObject<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 = CreateObject<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 = CreateObject<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-stack.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 = CreateObject<InternetNode> ();
    Ptr<Node> n1 = CreateObject<InternetNode> ();
    Ptr<Node> n2 = CreateObject<InternetNode> ();
    Ptr<Node> n3 = CreateObject<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 = CreateObject<UdpEchoClient> (n0, "10.1.1.2",
      port, maxPacketCount, interPacketInterval, packetSize);

    Ptr<UdpEchoServer> server = CreateObject<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-stack.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 = CreateObject<InternetNode> ();
    Ptr<Node> n1 = CreateObject<InternetNode> ();
    Ptr<Node> n2 = CreateObject<InternetNode> ();
    Ptr<Node> n3 = CreateObject<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 = CreateObject<UdpEchoClient> (n0, "10.1.1.2",
      port, maxPacketCount, interPacketInterval, packetSize);

    Ptr<UdpEchoServer> server = CreateObject<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 tutorial-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 tutorial-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('tutorial-csma-echo')
    obj.source = 'tutorial-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 tutorial-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 tutorial-csma-echo
Entering directory `~/repos/ns-3-dev/build'
Compilation finished successfully
UDP Echo Simulation
~/repos/ns-3-dev/tutorial >