\input texinfo @c -*-texinfo-*-
@c %**start of header
@setfilename ns-3.info
@settitle ns-3 tutorial
@c @setchapternewpage odd
@c %**end of header
@ifinfo
This @command{ns-3} project document is one of a set of project documents:
@itemize @bullet
@item Software Architecture
@item Manual
@item Tutorial (this document)
@end itemize
This document is written in GNU Texinfo and is to be maintained in
revision control on the @command{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.
@end ifinfo
@copying
This @command{ns-3} project document is one of a set of project documents:
@itemize @bullet
@item Software Architecture
@item Manual
@item Tutorial (this document)
@end itemize
This document is written in GNU Texinfo and is to be maintained in
revision control on the @command{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 @uref{http://www.gnu.org/licenses/}.
@end copying
@titlepage
@title ns-3 Tutorial
@author ns-3 project
@author feedback: ns-developers@@isi.edu
@today{}
@c @page
@vskip 0pt plus 1filll
@insertcopying
@end titlepage
@c So the toc is printed at the start.
@anchor{Full Table of Contents}
@contents
@ifnottex
@node Top, Preface, Full Table of Contents
@top ns-3 Tutorial (html version)
For a pdf version of this manual,
see @uref{http://www.nsnam.org/docs/tutorial.pdf}.
@insertcopying
@end ifnottex
@menu
* Preface::
* Introduction::
* Resources::
* The-Basics::
* Some-Prerequisites::
* A-First-ns-3-Script::
* Tracing-at-a-Glance::
* Other-network-topologies::
* Nonlinear-Thinking::
* Summary::
* Object-Model::
* The-Doxygen-Documentation-System::
* How-To-Change-Things::
* How-To-Set-Default-Values::
* How-To-Write-A-New-Application::
@end menu
@node Preface
@chapter Preface
The purpose of this tutorial is to introduce new @command{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 @command{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 @command{ns-3} project documents:
@itemize @bullet
@item Software Architecture
@item Manual
@item Tutorial (this document)
@end itemize
This document is written in Texinfo and is to be maintained in
revision control on the @command{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.
@c ========================================================================
@c Begin document body here
@c ========================================================================
@c ========================================================================
@c Introduction
@c ========================================================================
@node Introduction
@chapter Introduction
The @command{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
@command{ns-2}.
For those familiar with @command{ns-2}, the most visible outward change
when moving to @command{ns-3} is the choice of scripting language.
@command{ns-2} is typically scripted in Tcl and results of simulations are
often visualized using the Network Animator @command{nam}. In
@command{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 @command{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 @command{ns-3} system. We provide an overview of some of the
important abstractions, design patterns and idioms used when writing
@command{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:
@itemize @bullet
@item Find documentation resources in the distribution and on the web;
@item Download and compile the @command{ns-3} system;
@item Use the provided devices to author network simulations of fairly
significant complexity;
@item Use the default value system to configure simulations;
@item Write new @command{ns-3} applications;
@item Use the tracing subsystem.
@end itemize
@c ========================================================================
@c Resources
@c ========================================================================
@node Resources
@chapter Resources
@menu
* The-Web::
* Mercurial::
* Waf::
* Environment-Idioms-Design-Patterns::
* Socket-Programming::
@end menu
@node The-Web
@section The Web
@cindex www.nsnam.org
There are several important resources of which any @command{ns-3} user must be
aware. The main web site is located at @uref{http://www.nsnam.org}
and provides access to basic information about the @command{ns-3} system.
Detailed documentation is available through the main web site at
@uref{http://www.nsnam.org/documents.html}.
@cindex documentation
@cindex architecture
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
@uref{http://www.stack.nl/~dimitri/doxygen/,,Doxygen}. There is a Wiki that
complements the main @command{ns-3} web site which you will find at
@uref{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 @uref{http://code.nsnam.org/}.
@cindex repository!ns-3-dev
@cindex repository!releases
There you will find the current development tree in the repository named
@code{ns-3-dev}. Past releases and experimental repositories of the core
developers may also be found there.
@node Mercurial
@section 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.
@cindex software configuration management
@cindex Mercurial
The @command{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
@uref{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
@uref{http://www.selenic.com/mercurial/wiki/index.cgi/Tutorial/},
and a QuickStart guide at
@uref{http://www.selenic.com/mercurial/wiki/index.cgi/QuickStart/}.
You can also find vital information about using Mercurial and @command{ns-3}
on the main @command{ns-3} web site.
@node Waf
@section Waf
@cindex Waf
@cindex make
@cindex build
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 @code{make}. Along
with being the most famous, @code{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 @code{Waf} is used on the @command{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 @command{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 @uref{http://freehackers.org/\~tnagy/waf.html}.
@node Environment-Idioms-Design-Patterns
@section Environment, Idioms, and Design Patterns
@cindex C++
As mentioned above, scripting in @command{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.
@subsection Environment
@cindex toolchain
@cindex GNU
The @command{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,
@uref{http://en.wikipedia.org/wiki/GNU_toolchain}.
@cindex Linux
Typically a @command{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 @command{ns-3}
project supports development in the Cygwin and the MinGW environments for
these users. See @uref{http://www.cygwin.com/} and
@uref{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.
@cindex Cygwin
@cindex MinGW
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 @uref{http://www.mingw.org/MinGWiki/index.php/FAQ,,MinGW FAQ}.
@cindex Logitech
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 @code{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.
@subsection Idioms and Design Patterns
@cindex idiom
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 @command{ns-3} system relies on several classic design
patterns.
@cindex design pattern
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:
@verbatim
for (;;)
@end verbatim
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 @command{ns-3} system, you will begin to recognize and be
comfortable with the C++ implementations (idioms) of several important design
patterns.
@cindex functor
@cindex callback
@cindex smart pointer
The @command{ns-3} code relies heavily on
@emph{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 @command{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: @uref{http://www.amazon.com/Design-Patterns-Object-Oriented-Addison-Wesley-Professional/dp/0201633612/,,Design Patterns: Elements of Reusable Object-Oriented Software, Gamma et. al.} and
@uref{http://www.amazon.com/exec/obidos/ASIN/0201704315,,Modern C++ Design: Generic Programming and Design Patterns Applied, Alexandrescu}.
Gamma addresses the abstract design patterns, and Alexandrescu addresses the
C++ idioms you will often see throughout the @command{ns-3} code.
@cindex template
Almost any use of @command{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 @command{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,
@uref{http://www.amazon.com/Templates-Complete-Guide-David-Vandevoorde/dp/0201734842/,,C++ Templates: The Complete Guide, Vandevoorde and Josuttis}.
@node Socket-Programming
@section Socket Programming
@cindex sockets
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 @uref{http://www.elsevier.com/wps/product/cws_home/680765,,Practical TCP/IP Sockets in C, Donahoo and Calvert}.
There is an associated web site that includes source for the examples in the
book, which you can find at:
@uref{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,
@uref{http://www.elsevier.com/wps/product/cws_home/700736,,Multicast Sockets, Makofske and Almeroth}.
that covers material you may need to understand for the multicast examples.
@c ========================================================================
@c The Basics
@c ========================================================================
@node The-Basics
@chapter The Basics
@cindex Linux
@cindex Cygwin
@cindex GNU
@cindex toolchain
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.
@cindex Mercurial
@cindex Waf
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
@command{ns-3} web site: @uref{http://www.nsnam.org/getting_started.html}.
@section Downloading
@cindex tarball
The @command{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.
@cindex repository
If you go to the following link: @uref{http://code.nsnam.org/},
you will see a number of repositories. Many are the private repositories of
the @command{ns-3} development team. The repositories of interest to you
will be
prefixed with ``ns-3''. The current development snapshot (unreleased) of
@command{ns-3} may be found at: @uref{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 @command{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 @code{repos} in my home directory under
which I keep all of my local Mercurial repositories. @emph{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 @command{ns-3} by typing
the following into your Linux shell (I use bash).
@verbatim
cd
mkdir repos
cd !$
hg clone http://code.nanam.org/ns-3-dev
@end verbatim
As the hg command executes, you should see something like the following,
@verbatim
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
@end verbatim
After the clone command completes, you should have a directory called
ns-3-dev under your @code{~/repos} directory, the contents of which should
look something like the following:
@verbatim
AUTHORS RELEASE_NOTES examples/ src/ waf*
LICENSE VERSION ns3/ tutorial/ waf.bat*
README doc/ samples/ utils/ wscript
@end verbatim
You are now ready to build the @command{ns-3} distribution.
@section Building
@cindex Waf!build
@cindex Waf!configure
@cindex Waf!debug
@cindex Waf!compile
We use Waf to build the @command{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,
@verbatim
./waf -d debug configure
@end verbatim
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,
@verbatim
~/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 >
@end verbatim
The build system is now configured and you can build the debug versions of
the @command{ns-3} programs by simply typing,
@verbatim
./waf
@end verbatim
You will see many Waf status messages displayed as the system compiles. The
most important is the last one,
@verbatim
Compilation finished successfully
@end verbatim
@section Running a Script
@cindex Waf!run
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
@code{run} option in Waf. Let's run the @command{ns-3} equivalent of the hello
world program by typing the following:
@verbatim
./waf --run hello-simulator
@end verbatim
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.
@verbatim
Hello Simulator
@end verbatim
@emph{Congratulations. You are now an @command{ns-3} user.}
@c ========================================================================
@c Some Prerequisites
@c ========================================================================
@node Some-Prerequisites
@chapter 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.
@section Abstractions
In this section, we'll review some terms that are commonly used in
networking, but have a specific meaning in @command{ns-3}.
@subsection Node
@cindex Node
In Internet jargon, a computing device that connects to a network is called
a @emph{host} or sometimes an @emph{end system}. Because @command{ns-3} is a
@emph{network} simulator, not specifically an @emph{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 @emph{node}.
@cindex Node!class
In @command{ns-3} the basic computing device abstraction is called the
node. This abstraction is represented in C++ by the class @code{Node}. The
@code{Node} class provides methods for managing the representations of
computing devices in simulations. Developers are expected to specialize the
@code{Node} in the object-oriented programming sense to create new computing
device models. In this tutorial, we will use a specialization of class
@code{Node} called @code{InternetNode}. As you might expect, the
@code{InternetNode} is a class that represents a host in the Internet sense,
and automatically provides core IPv4 networking protocols.
You should think of a @code{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 @command{ns-3}.
@subsection Application
@cindex Application
Typically, computer software is divided into two broad classes. @emph{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 @emph{application} that acquires
and uses the resources controlled by the system software to accomplish some
goal.
@cindex system call
Often, the line of separation between system and application software is made
at the privilege level change that happens in operating system traps.
In @command{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,'' @command{ns-3} applications run on
@command{ns-3} @code{Node}s to drive simulations in the simulated world.
@cindex Application!class
In @command{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 @code{Application}. The @code{Application} class provides
methods for managing the representations of our version of user-level
applications in simulations. Developers are expected to specialize the
@code{Application} in the object-oriented programming sense to create new
applications. In this tutorial, we will use specializations of class
@code{Application} called @code{UdpEchoClient} and @code{UdpEchoServer}.
As you might expect, these applications compose a client/server application set
used to generate and echo simulated network packets
@subsection Channel
@cindex 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 @emph{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 @command{ns-3} one connects a @code{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 @code{Channel}.
The @code{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 @code{Channel}
specialization may model something as simple as a wire. The specialized
@code{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 @code{Channel} called
@code{CsmaChannel} and @code{PointToPointChannel} in this tutorial. The
@code{CsmaChannel}, for example, models a version of a communication subnetwork
that implements a @emph{carrier sense multiple access} communication medium.
This gives us Ethernet-like functionality.
@subsection Net Device
@cindex NetDevice
@cindex Ethernet
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 @emph{peripheral card} that needed to be installed in
your computer. These cards were called Network Interface Cards, or
@emph{NIC}s. 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
@emph{device}. Devices are controlled using @emph{device drivers}, and network
devices (NICs) are controlled using @emph{network device drivers}
collectively known as @emph{net devices}. In Unix and Linux you refer
to these net devices by names such as @emph{eth0}.
In @command{ns-3} the @emph{net device} abstraction covers both the software
driver and the simulated hardware. A net device is ``attached'' to a
@code{Node} in order to enable the @code{Node} to communicate with other
@code{Node}s in the simulation via @code{Channel}s. Just as in a real
computer, a @code{Node} may be connected to more than one @code{Channel} via
multiple @code{NetDevice}s.
The net device abstraction is represented in C++ by the class @code{NetDevice}.
The @code{NetDevice} class provides methods for managing connections to
@code{Node} and @code{Channel} objects; and may be specialized by developers
in the object-oriented programming sense. We will use the specialized version
of the @code{NetDevice} called the @code{CsmaNetDevice} in this tutorial.
Just as an Ethernet NIC is designed to work with an Ethernet network, the
@code{CsmaNetDevice} is designed to work with a @code{CsmaChannel}.
@subsection Topology Helpers
In a real network, you will find host computers with added (or built-in)
NICs. In @command{ns-3} we would say that you will find @code{Nodes} with
attached @code{NetDevices}. In a large simulated network you will need to
arrange many connections between @code{Node}s, @code{NetDevice}s and
@code{Channel}s.
Since connecting a @code{NetDevice} to a @code{Node}, and a @code{NetDevice}
to a @code{Channel} is such a common task in @command{ns-3} we provide what we
call @emph{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 @code{Node} and configure
the protocol stack, and then connect the @code{NetDevice} to a @code{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.''
@section Important Idioms
Now that we have identified that there are C++ classes in the system called
@code{Node} and @code{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.
@cindex InternetNode
@cindex Create
@cindex Ptr
In @command{ns-3}, if we want to create an @code{InternetNode} in a
script, we will
typically do something like the following example:
@verbatim
Ptr<Node> p = Create<InternetNode> ();
@end verbatim
@cindex smart pointer
To some, it may seem intuitively obvious that we're creating an
@code{InternetNode} object and assigning responsibility for managing the
object to a smart pointer named @code{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.
@subsection Templates 101
@cindex template
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++ @emph{templates}.
@verbatim
Ptr<Node> p = Create<InternetNode> ();
@end verbatim
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.
@cindex template!declaration
@cindex template!definition
@cindex template!use
In C++, just as with most language constructs, templates are @emph{declared},
@emph{defined} and @emph{used}. A declaration of a template might look
something like,
@verbatim
template <typename T> T Add (T first, T second);
@end verbatim
@cindex template!typename
This line uses the keyword @code{template} followed by a declaration of a
type name (in this case @code{T}) in angle brackets. The angle brackets
should indicate to you that a template is being declared, defined or used.
The type name @code{T} can be thought of as a string that will be substitited
during the use phase of the template. For example, the @code{T} may be
replaced by the word @code{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 @code{Add} that will
add arbitrary types together. The @code{T} will be eventually replaced by
a C++ data type name. For example,
@verbatim
T Add (T first, T second);
@end verbatim
might eventually become
@verbatim
int Add (int first, int second);
@end verbatim
If the template has been declared, we need to @emph{define} what that piece of
code will actually do. That might look something like,
@verbatim
template <typename T>
T Add (T first, T second)
{
return first + second;
}
@end verbatim
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 @code{operator+}.
The puzzle all comes together when you understand that @emph{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,
@verbatim
int x, y, z;
z = Add<int> (x, y);
@end verbatim
@cindex template!instantiate
When the compiler sees @code{Add<int>} it understands that it needs to make
sure that code is instantiated (created) to perform the @code{Add} using the
specified type @code{<int>}. To a first approximation, the compiler will
replace the typename @code{T} with the specified type @code{int} and
automagically generate code equivalent to,
@verbatim
int Add (int first, int second)
{
return first + second;
}
@end verbatim
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,
@verbatim
float x, y, z;
z = Add<float> (x, y);
@end verbatim
In this case, the compiler would automatically generate code that looked like,
@verbatim
float Add (float first, float second)
{
return first + second;
}
@end verbatim
@cindex template!function
This particular kind of template programming uses what are called
@emph{function templates}. They are called function templates since you
are @emph{templating} function declarations and definitions.
@cindex template!class
Templates can also be used in conjunction with classes, in which case you are
said to be using, not too surprisingly, @emph{class templates}. The syntax and
use is similar. To declare a class template you might use something like,
@verbatim
template <typename T>
class MyStack
{
void Push (T data);
T Pop (void);
};
@end verbatim
The methods can be defined separately in a method similar to function template
definitions,
@verbatim
template <typename T> void MyStack<T>::Push (T data)
{
...
};
@end verbatim
You can then use the new templated class in the following way,
@verbatim
int x, y;
MyStack<int> stack;
stack.Push (x);
y = stack.Pop ();
@end verbatim
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 @code{<int>}.
@subsection 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.
@cindex smart pointer
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 @emph{smart pointer}.
@verbatim
Ptr<Node> p = ...
@end verbatim
To a first approximation, you can think of @code{Ptr<Node>} as the a new kind
of declaration of a pointer to a @code{Node} object. The difference is that
a smart pointer is a user-defined data type (instantiated via a templated
class) that @emph{simulates} a classical pointer but provides additional
features. As an aside, you typically pronounce @code{Ptr<Node>} as
``pooter node'' where pooter rhymes with footer.
@cindex memory management
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 @code{Ptr<Ipv4>} and @code{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
@emph{scope} and @emph{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.
@verbatim
void SimpleSubroutine (void)
{
Ptr<Node> p;
}
@end verbatim
@cindex scope
The variable named @code{p} has a scope limited to the subroutine itself. The
variable is said to @emph{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 @emph{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.
@cindex reference counting!intrusive
The @command{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 @command{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
@emph{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 @emph{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 @command{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 @code{MyClass} exists.
Further assume that @code{MyClass} provides one method called @code{method}.
Using built-in pointers, you could do something like the following:
@verbatim
MyClass *p = ...
p->method ();
@end verbatim
@cindex smart pointer
One of the key design points of smart pointers is that they should simulate
built-in pointers. In C++ this is done by overloading @code{operator->},
@code{operator=} and @code{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,
@cindex template
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 @code{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:
@verbatim
SmartPointer<MyClass> p = ...
p->method ();
@end verbatim
@subsection Object Creation
@cindex Create
On the right hand side of the line of code we're examining (reproduced below
for convenience) is the creation of an @code{InternetNode} object.
@verbatim
... = Create<InternetNode> ();
@end verbatim
@cindex template!function
This turns out to be an instance of use of a C++ @emph{function template}. The
definition of the @code{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., @code{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 @code{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 @code{Create} function like this,
@verbatim
int parm = 1;
... = Create<MyClass> (parm);
@end verbatim
We provide Create templates with up to seven parameters, so you could
conceivably use the @code{Create} template in situations such as,
@verbatim
int parm = 1;
... = Create<MyClass> (p1, p2, p3, p4, p5, p6, p7);
@end verbatim
@subsection 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).
@verbatim
Ptr<Node> p = Create<InternetNode> ();
@end verbatim
@cindex smart pointer
@cindex Node
@cindex Create
You may have noticed that the smart pointer on the left hand side of the
assignment is associated with the type @code{Node} and the @code{Create}
template on the right hand side creates an @code{InternetNode} object and
returns a @code{Ptr<InternetNode>} smart pointer. For this assignment of a
@code{Ptr<InternetNode>} to a @code{Ptr<Node>} to work, there must be some
kind of type conversion going on.
@cindex implicit conversion
Many programmers use @code{implicit conversions} without even realizing it
since they are sometimes so intuitive. For example, in the following code,
@verbatim
int i = 1;
double d = 2.;
if (n == d) ...
@end verbatim
@cindex standard conversion
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++
@emph{standard conversion}. There are a number of standard conversions defined
by the C++ standard. Among them are,
@itemize @bullet
@item Integral Promotions
@item Integral Conversions
@item Floating Conversions
@item Pointer Conversions
@item Reference Conversions
@end itemize
@cindex assignment operator
@cindex Ptr
For the case of interest here, we need to know what happens in the
assignment operator (@code{operator=}) of our smart pointer @code{Ptr<Node>}.
This operator takes a reference to a @code{Ptr<Node>} and not a reference to
a @code{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
@emph{cast} from one type to the other automatically.
To summarize: The magic happens in the assignment operator. Class
@code{InternetNode} inherits from class @code{Node}. The reference to the
@code{InternetNode} object in question is, in essence, a pointer to an
@code{InternetNode} object. The @code{InternetNode} class inherits from the
@code{Node} base class in a way that makes @code{Node} visible and unambiguous.
Therefore, there exists a standard conversion from an @code{InternetNode *}
to a @code{Node *} and by extension from an @code{InternetNode &} to a
@code{Node &}. This conversion is applied automatically (and invisibly)
during paramater passing in the assignment operator we are examining.
@cindex base class
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
@verbatim
Ptr<Node> p = Create<InternetNode> ();
@end verbatim
or,
@verbatim
Ptr<Channel> p = Create<CsmaChannel> ();
@end verbatim
will work just fine. Of course, if you try something @emph{bad} (TM), like:
@verbatim
Ptr<Node> p = Create<CsmaChannel> ();
@end verbatim
the compiler will quite appropriately complain that there is no conversion
between these completely unrelated objects (CsmaChannel and Node).
@subsection Summary
Going back to our infamous first line of @command{ns-3} code, we said that if
we want to create an InternetNode in a script, we will typically do something
like:
@verbatim
Ptr<Node> p = Create<InternetNode> ();
@end verbatim
@cindex Create
@cindex InternetNode
@cindex smart pointer
Now we know that this is really a simple statement. We create an
@code{InternetNode} object on the heap (indirecly using operator @code{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 @code{Node} object, so there was a hidden cast from
@code{InternetNode} to a @code{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.
@c ========================================================================
@c A First ns-3 script
@c ========================================================================
@node A-First-ns-3-Script
@chapter A First ns-3 script
@cindex design pattern
@cindex idiom
Lets build a simple network using the @command{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 @command{ns-3} in a directory
called @code{repos} under your home directory. Change into that directory,
where you should see a directory structure something like the following.
@verbatim
AUTHORS RELEASE_NOTES examples/ src/ waf*
LICENSE VERSION ns3/ tutorial/ waf.bat*
README doc/ samples/ utils/ wscript
@end verbatim
@cindex hello-simulator.cc
Change into the tutorial directory. You should see a file named
@code{hello-simulator.cc} located there. Copy this file into one named
@code{simple.cc}. If you open this new file in your favorite editor you will
see some copyright information and the following C++ code:
@verbatim
#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");
}
@end verbatim
This is the @command{ns-3} version of the ubiquitous hello-world program. It
uses the @command{ns-3} Log module to print ``Hello Simulator'' into the
standard error output stream.
@cindex logging
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 @code{NS_LOG_INFO} to be
a kind of fancy printf to the standard error.
@section A Simple Network
@cindex InternetNode
Let's create a simple network of @code{InternetNode} elements. In order to
actually create an @code{InternetNode}, you will have to include some header
files. Put the following code after the include statement in @code{simple.cc}.
@verbatim
#include "ns3/ptr.h"
#include "ns3/internet-node.h"
@end verbatim
@cindex include files
The @command{ns-3} build system places the core include files it needs into a
directory called @code{ns-3} and so whenever you need to include one of the
core files you need to explicitly code this. The file @code{ptr.h} defines
the generic smart pointer that we use. The file @code{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 @code{InternetNode}s by adding the following lines
of code after the call to @code{NS_LOG_INFO} in the simple.cc file right
after the call to @code{NS_LOG_INFO}.
@verbatim
Ptr<Node> n0 = Create<InternetNode> ();
Ptr<Node> n1 = Create<InternetNode> ();
Ptr<Node> n2 = Create<InternetNode> ();
Ptr<Node> n3 = Create<InternetNode> ();
@end verbatim
As we now understand, this will create four @code{InternetNode} objects on
the heap and create four @code{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.
@cindex Channel
@cindex CsmaChannel
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:
@verbatim
#include "ns3/csma-channel.h"
@end verbatim
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 @code{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.
@verbatim
Ptr<CsmaChannel> lan =
CsmaTopology::CreateCsmaChannel (DataRate (5000000), MilliSeconds (2));
@end verbatim
@cindex idiom!unnamed parameter
You may be unfamiliar with the @emph{unnamed parameter} idiom used here.
When added to a list of parameters, the code @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:
@verbatim
DataRate rate (5000000);
Time latency (MilliSeconds (2));
Ptr<CsmaChannel> lan = CsmaTopology::CreateCsmaChannel (rate, latency);
@end verbatim
@cindex constructor
@cindex constructor!Time
We should pause for a moment and discuss the constructor to the @code{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 @code{s, ms, us, ns, ps} or @code{fs}, so this could have been
written,
@verbatim
Time latency ("2ms");
@end verbatim
There are also helper functions available that create time units (one of these
was used in the example):
@itemize @bullet
@item @code{Seconds (double)}
@item @code{MilliSeconds (uint64_t)}
@item @code{MicroSeconds (uint64_t)}
@item @code{NanoSeconds (uint64_t)}
@item @code{PicoSeconds (uint64_t)}
@item @code{FemtoSeconds (uint64_t)}
@end itemize
C++ will attempt to promote parameters appropriately, but you will typically
see constructions that respect the type corrrectness of the constructor, as
in @code{Seconds (1.)} and @code{MilliSeconds (2)}. Notice that the code
@code{Seconds (1)} will work just as well as @code{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
@code{MilliSeconds (2.)} since a @emph{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.
@cindex MAC!address
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:
@verbatim
#include "ns3/mac48-address.h"
#include "ns3/csma-net-device.h"
#include "ns3/csma-topology.h"
#include "ns3/csma-ipv4-topology.h"
@end verbatim
Now, all that is left is to do the ``wiring'':
@verbatim
uint32_t nd0 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n0, lan,
Mac48Address("08:00:2e:00:00:00"));
@end verbatim
[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
(@code{n0}) connecting the device to the channel named (@code{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).
@verbatim
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"));
@end verbatim
@cindex IP!address
@cindex IP!network mask
@cindex multihome
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 @command{ns-3}
class @code{Ipv4Address} which takes a dotted decimal string as a constructor
parameter. We also provide a network mask using the @command{ns-3} class
@code{Ipv4Mask} which also takes a dotted decimal string. The code to
perform the IP address assignment, then, looks like the following:
@verbatim
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"));
@end verbatim
We have now constructed a simulated network. Your code should now look
something like the following,
@verbatim
#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"));
}
@end verbatim
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.
@section Using Applications
@cindex Create
As mentioned above, we use @code{Application}s in @command{ns-3} to generate
the data used to drive simulations. An @code{Application} is added to a
@command{ns-3} node conceptually just as if you would add an application to a
computer. When an application is created (using the @code{Create} template)
we tell the application which @code{Node} it belongs to (and therefore on
which node it is running) by passing a smart pointer to that @code{Node} in
the constructor arguments.
@subsection 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,
@verbatim
#include "ns3/udp-echo-client.h"
@end verbatim
In order to create the UDP echo client application we will need to add the
following code:
@verbatim
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);
@end verbatim
@cindex packet
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 (@code{packetSize = 1024}); and to send these
packets to port 7 (@code{port = 7;}). The application is told to send at most
one packet (@code{maxPacketCount = 1;}); and to delay for one second between
packet sends (@code{interpacketInterval = Seconds(1.)}) which is not used since
only one packet is sent. We will defer addressing the type @code{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 @code{UdpEchoClient} application uses the
same creation idiom as we have used previously. Notice that we have a case
where the @code{Create} template is used to pass parameters to the constructor
of the underlying object.
@cindex implicit conversion sequence
Notice that a string is passed as the second parameter. The formal parameter
to the constructor of the @code{UdpEchoClient} object is actually an
@code{Ipv4Address}. We get away with this since C++ allows what are called
@emph{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 @code{char const *} as a parameter. C++ notices
that @code{"10.1.1.2"} refers to a @code{char const *} and knows that it
needs to get from there to an @code{Ipv4Address}. The compiler notices that
there is an @code{Ipv4Address} constructor that takes a @code{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:
@verbatim
Ipv4Address addr ("10.1.1.2");
...
Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n0, addr, port,
maxPacketCount, interPacketInterval, packetSize);
@end verbatim
@cindex idiom|unnamed parameter
You can use the unnamed parameter idiom that we have previously seen:
@verbatim
Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n0,
Ipv4Address ("10.1.1.2"), port, maxPacketCount, interPacketInterval,
packetSize);
@end verbatim
Or you can rely on implicit conversion sequences as we just saw:
@verbatim
Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n0, "10.1.1.2", port,
maxPacketCount, interPacketInterval, packetSize);
@end verbatim
Which approach to take is a matter of style, really, and you will probably
see all three approaches taken in the @command{ns-3} code. You should be
comfortable seeing and using all three methods.
@subsection A UDP Echo Server Application
As usual, to use the UDP echo server we need to add a line to define the
application:
@verbatim
#include "ns3/udp-echo-server.h"
@end verbatim
In order to create the UDP echo server application we will need to add the
following code:
@verbatim
Ptr<UdpEchoServer> server = Create<UdpEchoServer> (n1, port);
@end verbatim
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
@code{UdpEchoServer} application uses the now quite familiar @command{ns-3} object
creation idiom.
@subsection 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).
@verbatim
#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);
}
@end verbatim
@section Using the Simulation Engine
@cindex model
@cindex simulation executive
You could say that the heart of the @command{ns-3} system is the
@emph{simulation engine} (sometimes called the simulation executive in other
systems).
In a computer simulation, a computer @emph{model} of a real world @emph{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
@command{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.
@section Models
@cindex CsmaNetDevice
@cindex CsmaChannel
@cindex InternetNode
@cindex NIC
@cindex CSMA
We have already encountered several @command{ns-3} models without specifically
calling them so. The @code{InternetNode}, @code{CsmaNetDevice} and
@code{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.
@cindex model
@cindex CSMA/CD
It is important to note that the @code{Csma} net devices and the @code{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
@code{CsmaNetDevice} does not simulate collision detection (CD). It does
implement carrier sense and performs collision @emph{avoidance} using global
spatial knowledge available in the channel. This would be impossible in any
channel residing in our universe.
@cindex Ethernet
No model will fully implement @emph{all} of the behaviors of a piece of
hardware. It is important to understand what is being modeled by the
@command{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.
@section Time, Events and Callbacks
@cindex time
@cindex event
In a @emph{discrete event simulator} time is not something that @emph{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 @emph{event}. An event is some action in the system that is
@emph{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.
@cindex packet
For example, to start the flow of a packet through the system, one would have
to schedule an event with the simulation engine @emph{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 @emph{event queue}. It would immediately jump time forward by ten
seconds and @emph{fire} the scheduled event --- that is, make the event happen.
@cindex functor
@cindex function object
@cindex callback
@cindex Callback
In @command{ns-3} an event is basically a pre-packaged function call called a
@emph{functor}. Functors are also known as @emph{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 @emph{deferred execution} of a function or method. The
most commonly encoutered form of deferred execution is in a @emph{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 @emph{call you back}
when they are done.] Events in the @command{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 @command{ns-3}
deferred exectution mechanism is via a class called @code{Callback}.
@cindex Time
@cindex Callback
The internal details of the classes representing @code{Time} and
@code{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.
@section Driving the Simulation
@cindex Application
As mentioned previously, time is the driving force behind the progress of
a @command{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 @code{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:
@verbatim
#include "ns3/simulator.h"
#include "ns3/nstime.h"
@end verbatim
@cindex Application
As we have seen, we need to ``seed'' the simulation with at least one event.
In the case of an @code{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:
@verbatim
server->Start(Seconds(1.));
client->Start(Seconds(2.));
server->Stop (Seconds(10.));
client->Stop (Seconds(10.));
@end verbatim
@cindex Application
@cindex time
@cindex Time
@cindex socket
@cindex event
In the case of the UdpEchoServer, the call to @code{server->Start ()} gives
the @code{Application} the chance to schedule an event that will perform the
usual @emph{sockets} server sequence of socket creation, binding and
recvfrom (see Donahoo's UDPEchoServer.c).
In the case of the UdpEchoClient, the call to @code{client->Start ()} gives
the @code{Application} the chance to schedule an event that will perform the
usual @emph{sockets} client sequence of socket creation, sendto and recvfrom
(see Donahoo's UDPEchoClient.c).
@cindex event
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.
@cindex socket!sendto
The @command{ns-3} equivalent of the call to @code{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 @code{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:
@verbatim
Simulator::Run ();
Simulator::Destroy ();
@end verbatim
We now have the makings of a complete @command{ns-3} network simulation. The
source code for the script should look like the following:
@verbatim
#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 ();
}
@end verbatim
@cindex csma-echo.cc
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 @code{tutorial} subdirectory of the @command{ns-3} distribution as
@code{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.]
@section Building the Script
@cindex Waf
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 @command{ns-3} distribution in which there are
source files, you will find two files: one will be named @code{waf} and one
will be named @code{wscript}. The former, @code{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 @code{wscript}.
@cindex wscript
Open the file @code{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 @code{repos} in you home directory.]
@cindex Python
You should see the following Python code (after an emacs mode line).
@verbatim
def build(bld):
obj = bld.create_ns3_program('hello-simulator')
obj.source = 'hello-simulator.cc'
@end verbatim
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
@code{bld.create_ns3_program} tells the build system to create an object
file that is a program (executable) named @code{hello-simulator}. The
following line, with the method @code{obj.source} tells the build system that
the source file for the program is the file @code{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 @code{hello-simulator}
program and change the names to @code{csma-echo}. You can see these lines
in the @code{wscript} file,
@verbatim
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'
...
@end verbatim
When you built the system above, you actually already built this new
simulation and a number of other examples. Since you have already configured
@code{Waf} and built the @code{csma-echo} script, you can run the simulation
in the same way as you ran the @code{hello-simulator} script using the
@code{waf --run} command:
@verbatim
~/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 >
@end verbatim
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.
@c ========================================================================
@c Tracing at a Glance
@c ========================================================================
@node Tracing-at-a-Glance
@chapter 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?
@cindex toaster
The @command{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.''
@section ASCII Trace Wrapper
@cindex ASCII
The ASCII trace wrapper is a wrapper around the @command{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.
@subsection Tracing Queue Operations
@cindex queue
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):
@verbatim
#include "ns3/ascii-trace.h"
@end verbatim
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
@code{Simulator::Run ();}:
@verbatim
AsciiTrace asciitrace ("tutorial.tr");
asciitrace.TraceAllQueues ();
@end verbatim
@cindex AsciiTrace
@cindex AsciiTrace!TraceAllQueues
The first line declares an object of type @code{AsciiTrace} named
@code{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, @code{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.
@cindex csma-echo-ascii-trace.cc
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 @code{csma-echo-ascii-trace.cc} and is
located in the @code{tutorial} directory.
@cindex Waf
You can just type the following to run the trace version of the echo program:
@verbatim
./waf --run csma-echo-ascii-trace
@end verbatim
@cindex tutorial.tr
Just as you have seen previously, you will see some messages from @emph{Waf}
and then (I feel joy) the ``Compilation finished successfully'' message. The
next message, @code{UDP Echo Simulation} is from the running program. When
it ran, the program will have created a file named @code{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
@code{tutorial.tr} in your favorite editor.
@cindex trace event
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 @emph{trace event}. A trace event
happens whenever specific conditions happen in the simulation. In this case
we are tracing events on the @emph{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
@emph{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:
@cindex enqueue
@cindex dequeue
@cindex drop
@itemize @bullet
@item @code{+}: An enqueue operation occurred on the device queue;
@item @code{-}: A dequeue operation occurred on the device queue;
@item @code{d}: A packet was dropped, typically because the queue was full.
@end itemize
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:
@verbatim
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
@end verbatim
@cindex trace event
@cindex simulation time
The first line of this expanded trace event (reference number 00) is the
queue operation. We have a @code{+} character, so this corresponds to an
@emph{enqueue} operation. The second line (reference 01) is the simulation
time expressed in seconds. You may recall that we asked the
@code{UdpEchoClient} to start sending packets at two seconds. Here we see
confirmation that this is, indeed, happening.
@cindex node number
@cindex net device number
@cindex smart pointer
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, @code{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 @code{n0}. Not
too surpsisingly, this is also the node to which we attached the
@code{UdpEchoClient}. The device number is local to each node, and so the
device given by @code{device=0} is the first net device that we added to the
node in question. In our simulation, this corresponds to the
@code{CsmaNetDevice} we added to node zero (@code{n0}).
@cindex uid
@cindex unique ID
@cindex packet
The next line (reference 04) is a more readable form of the operation code
seen in the first line --- i.e., the character @code{+} means
@code{queue-enqueue}. Reference number 05 indicates that the @emph{unique id}
of the packet being enqueued is @code{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.
@cindex Ethernet
@cindex MAC address
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.
@cindex Address Resolution Protocol
@cindex ARP
@cindex ARP|request
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 @code{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),
@verbatim
00 -
01 2
02 nodeid=0
03 device=0
04 queue-dequeue
05 pkt-uid=9
...
@end verbatim
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:
@verbatim
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
@end verbatim
@cindex simulation time
@cindex ARP|response
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
@code{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.
@cindex queue
@cindex queue|transmit
@cindex echo
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 @emph{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
(@code{tutorial.tr}) is the first UDP echo packet being sent to the net device.
@verbatim
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
@end verbatim
@cindex simulation time
@cindex echo
@cindex ARP
@cindex ARP|request
@cindex ARP|response
@cindex IP
@cindex Ipv4
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
@code{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 @code{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,
@verbatim
+ 2.00786 nodeid=1 ... ARP(request ...
- 2.00786 nodeid=1 ... ARP(request ...
+ 2.00994 nodeid=0 ... ARP(reply ...
- 2.00994 nodeid=0 ... ARP(reply ...
@end verbatim
The final two trace events in the @code{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).
@subsection 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 @code{csma-echo.cc} to
enable tracing of net device receive operations. The code is already in the
file, but disabled.
@cindex AsciiTrace!TraceAllNetDeviceRx
@verbatim
#if 0
asciitrace.TraceAllNetDeviceRx ();
#endif
asciitrace.TraceAllNetDeviceRx ();
@end verbatim
Change the @code{#if 0} to @code{#if 1} using your favorite editor and compile
and run the file using waf as we have done previously:
@verbatim
./waf --run csma-echo-ascii-trace
@end verbatim
@cindex ARP!request
Now if you look at the trace file (@code{tutorial.tr}) you will see some new
entries. These entries all begin with the character @code{r} indicating a
@emph{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,
@verbatim
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 ...
@end verbatim
@cindex unique ID
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.
@section PCAP Trace Wrapper
@cindex pcap
@cindex Wireshark
The @command{ns-3} @emph{pcap trace wrapper} is used to create trace files in
@code{.pcap} format. The acronym pcap (usually written in lower case) stands
for @emph{p}acket @emph{cap}ture, and is actually an API that includes the
definition of a @code{.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: @uref{http://www.wireshark.org/}.
@cindex csma-echo-ascii-trace.cc
@cindex csma-echo-pcap-trace.cc
The code used to enable pcap tracing is similar to that for ASCII tracing.
We have provided another file, @code{csma-echo-pcap-trace.cc} that uses the
pcap trace wrapper. We have added the code to include the pcap trace wrapper
defintions:
@verbatim
#include "ns3/pcap-trace.h"
@end verbatim
And then added the following code below the AsciiTrace methods:
@cindex PcapTrace
@cindex PcapTrace!TraceAllIp
@verbatim
PcapTrace pcaptrace ("tutorial.pcap");
pcaptrace.TraceAllIp ();
@end verbatim
The first line of the code immediately above declares an object of type
@code{PcapTrace} named @code{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 @code{PcamTrace}
object to trace all IP activity in all of the nodes present in the simulation.
@cindex interface index
Trace files are not created until trace activity is detected. Each file name
is composed of the base file name, followed by a @code{'-'}, a node id followed
by a @code{'-}', and an IP interface index. You will soon see a file named
@code{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:
@cindex Waf
@verbatim
./waf --run csma-echo-pcap-trace
@end verbatim
If you look at the top level directory of your distribution, you should now
see three log files: @code{tutorial.tr} is the ASCII trace file we have
previously examined. @code{tutorial.pcap-0-1} and @code{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.
@cindex Wireshark
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
@emph{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).
@c ========================================================================
@c Other Network Topologies
@c ========================================================================
@node Other-network-topologies
@chapter Other Network Topologies
@cindex topology
@cindex Channel
@cindex NetDevice
@cindex topology!bus
@cindex topology!point-to-point
@cindex PointToPointChannel
@cindex PointToPointNetDevice
@emph{Network topology} is the study of the arrangement of of the elements
(in @command{ns-3} represented by the classes @code{Channel} and @code{Node})
of a network. Two fundamental types of physical topologies are the
@emph{point-to-point} and @emph{bus} topologies. We have already been exposed
to the @command{ns-3} channel specialization named @code{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 @command{ns-3} base classes
@code{NetDevice} and @code{Channel} and are called @code{PointToPointNetDevice}
and @code{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.
@section 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.
@sp 1
@center @image{pp,,,,png}
We have provided a file for you in the @code{tutorial}
directory called @code{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:
@verbatim
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");
@end verbatim
You can see that we created two @code{InternetNode} objects in the usual way.
Then, instead of creating a @code{CsmaChannel} we create a
@code{PointToPointChannel}. This point-to-point channel, which we call
@code{link}, connects node zero (@code{n0}) and node one (@code{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 @code{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
@code{/30} subnet and assigns an appropriate net mask for you. It then then
@emph{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,
@verbatim
Ipv4Mask netmask("255.255.255.252");
@end verbatim
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 @code{point-to-point.cc} is reproduced here for your
convenience:
@verbatim
/* -*- 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 ();
}
@end verbatim
@section 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 @code{tutorial} directory called @code{star.cc}
that implements a simple star network as seen below.
@sp 1
@center @image{star,,,,png}
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 @code{PointToPointTopology} topology helper does not allow one to
do this. We provided a separate topology helper class, the
@code{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.
@verbatim
#include "ns3/point-to-point-ipv4-topology.h"
@end verbatim
The star that we're going to create has a node in the center (@code{n0}) with
six nodes surrounding (@code{n1} - @code{n6}). You should be able to easily
find and understand the code that creates these nodes.
@verbatim
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> ();
@end verbatim
Next, we get into the differences between the @code{PointToPointTopology}
helper and the @code{PointToPointIpv4Topology} helper. The
@code{PointToPointIpv4Topology} helper looks and feels a little like the
@code{CsmaIpv4Topology} helper. Just like you created a CSMA channel
previously, you need to create a point-to-point channel. The following
code creates a @code{PointToPointChannel} and calls it @code{link01}. You can
interpret this name as being the channel (or @emph{link}) from node zero to
node one.
@verbatim
Ptr<PointToPointChannel> link01 =
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
MilliSeconds (20));
@end verbatim
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:
@verbatim
uint32_t nd01 = PointToPointIpv4Topology::AddNetDevice (n0,
link01);
@end verbatim
We call the @code{PointToPointIpv4Topology} helper and ask it to add a net
device to node zero (@code{n0}) and connect it to the appropriate
point-to-point link (@code{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:
@verbatim
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);
@end verbatim
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 (@code{n1}) and
connecting that net device to the channel named @code{link01}. Remember that
we created the channel @code{link01} as the channel connecting node zero and
node one. We previously created a net device on node zero and attached that
device to @code{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 @code{n0}
and a channel called @code{link01}. We added a net device to @code{n0} and
remembered the net device index as the @code{uint32_t nd01}. This meant
the net device @emph{nd} on node @emph{0} that we connected to node @emph{1}.
We call @code{AddAddress} to add an IP address (10.1.1.1) to the net device
on node zero identified by the net device index @code{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.
@verbatim
PointToPointIpv4Topology::AddAddress (n0, nd01, "10.1.1.1",
``255.255.255.252'');
PointToPointIpv4Topology::AddAddress (n1, nd1, "10.1.1.2",
``255.255.255.252'');
@end verbatim
The following code repeats this pattern assining similar IP addresses to the
remaining net devices. Note that there are no @code{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 @code{star.cc} is reproduced here for your convenience:
@verbatim
/* -*- 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 ();
}
@end verbatim
@subsection 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 @code{tutorial} directory called
@code{star-routing.cc} to show you how this is done. This extremely
tricky and difficult change is shown below:
@verbatim
GlobalRouteManager::PopulateRoutingTables ();
@end verbatim
This one-line addition, located just before the simulation runs, tells the
@command{ns-3} @emph{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:
@verbatim
Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n4, "10.1.1.2", port,
1, Seconds(1.), 1024);
@end verbatim
Now if you build and run @code{star-routing.cc} you can examine the
@code{tutorial.tr} file and see that your UDP echo packets are now correctly
routed through the topology.
@section 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
@emph{choke point}.
The following is a representation of the topology.
@sp 1
@center @image{dumbbell,,,,png}
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 @code{linear-dumbbell.cc} and is located in the
@code{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.
@verbatim
//
// 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");
@end verbatim
The code to generate the CSMA lan on the right side is similar; only the names
have been changed.
@verbatim
//
// 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");
@end verbatim
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 @code{point-to-point} example.
@verbatim
//
// 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");
@end verbatim
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.
@verbatim
//
// 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.));
@end verbatim
The remainder of the file should be quite familiar to you. Go ahead and
run @code{linear-dumbbell}. Now take a look at the trace (@code{tutorial.tr})
file. You will now see trace lines that begin with @code{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 @code{queue-drop} trace for you below. See the section on ASCII
tracing for details.
@verbatim
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)
@end verbatim
We leave it as an exercise to examine the trace files in more detail.
@c ========================================================================
@c Nonlinear Thinking
@c ========================================================================
@node Nonlinear-Thinking
@chapter 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 @command{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 @command{ns-3} scripting with a minimum of conceptual
overhead. We're going to remedy that situation shortly.
We have written a number of @command{ns-3} scripts in C++. Although we have
been perfectly linear in our script implementations, just like any other C++
program, an @command{ns-3} script can use any features of the language you
desire. If you will look back at the @code{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.
@section 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.
@example
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.
@end example
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 @emph{network}. This probably won't surprise you. We also have an
adjective that modifies the noun --- @emph{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 @emph{bus network} in conversation, so we would normally create a
@code{class BusNetwork} to represent it.
One thing to note is that we have used two words in our description quite
naturally: @emph{is} and @emph{has}. When you see these words should should
immediately think of the object-oriented concepts of @emph{ISA} (inheritance)
and @emph{HASA} (containment) respectively. We wrote that a bus network
@emph{is} an implementation of a particular network topology. Perhaps you
will agree that there is a natural base class called @code{Network} that
@emph{has} the attributes discussed above. The fact that a @code{BusNetwork}
@emph{ISA} kind of @code{Network} suggests inheritance. Let's capture that
thought right away remembering that we're focused on IP version four here:
@verbatim
class Ipv4Network
{
public:
Ipv4Address m_network;
Ipv4Mask m_mask;
Ipv4Address m_baseAddress;
};
class Ipv4BusNetwork : public Ipv4Network
{
};
@end verbatim
Let's take a look at the @emph{HASA} relationships of the bus network. Clearly
it will @emph{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:
@verbatim
Ptr<CsmaChannel> m_channel;
@end verbatim
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,
@verbatim
std::vector<Ptr<Node> > m_nodes;
@end verbatim
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.
@example
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.
@end example
We now look at the @emph{verbs} in that sentence. These will give a good
starting point for the methods of the classes. For example, the verbs
@emph{create} and @emph{delete} should suggest @emph{constructor} and
@emph{destructor}. The verb @emph{get} leads us to providing a method called
@code{GetNode}. We have to provide a number of parameters so we can either
provide @emph{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:
@verbatim
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;
};
@end verbatim
That's it. We have actually already walked through almost all of the code
required to construct a bus network in our @code{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
@code{ipv4-bus-network.h} and @code{ipv4-bus-network.cc} located in the
@code{tutorial} directory. We also provide an example that uses the new
class in the file @code{bus-network.cc}.
The interesting method from our current perspective is the Ipv4BusNetwork
constructor, shown below:
@verbatim
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);
}
}
@end verbatim
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),
@verbatim
:
Ipv4Network (network, mask, baseAddress)
{
@end verbatim
Passes the appropriate parameters to the constructor of the base class
@code{Ipv4Network}. There are two new calls that we haven't seen immediately
after this initialization. They are:
@verbatim
Ipv4AddressGenerator::SeedNetwork (mask, network);
Ipv4AddressGenerator::SeedAddress (mask, baseAddress);
@end verbatim
We provide an IP address generator class to allow us to programatically
allocate IP addresses. The first call to @code{SeedNetwork} gives the
address generator a starting network number to use when generating addresses.
The second call to @code{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 @code{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:
@verbatim
m_nodes.push_back (node);
@end verbatim
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:
@verbatim
/* -*- 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
@end verbatim
@section 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
@code{bus-netowrk.cc} located in the @code{tutorial} directory. The
interesting code is,
@verbatim
Ipv4BusNetwork bus ("10.1.0.0", "255.255.0.0", "0.0.0.3",
DataRate(10000000), MilliSeconds(20), 10);
@end verbatim
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 @code{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.
@c ========================================================================
@c Summary
@c ========================================================================
@node Summary
@chapter Summary
This concludes the first part of the tutorial. We have focused on
using the @command{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 @code{Ipv4DumbbellNetwork} class
to create dumbbell networks using the Ipv4BusNetwork class we just created.
Hint: An Ipv4DumbbellNetwork @emph{has} two @code{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.
@c ========================================================================
@c Object Model
@c ========================================================================
@node Object-Model
@chapter 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 @command{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 @command{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,
@quotation
``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.''
@end quotation
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, @command{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 @command{ns-3} components.
@section The C++ Object Model is the Root of all Things
@command{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
@code{Create} as in the following example:
@verbatim
Ptr<Node> n0 = Create<InternetNode> ();
@end verbatim
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 @code{Create} template.
@verbatim
template <typename T>
Ptr<T> Create (void)
{
T *obj = new T ();
Ptr<T> p = obj;
obj->Unref ();
return p;
}
@end verbatim
As you can see, this template creates objects of type @code{T} using the
operator @code{new}. Its a little harder to find the corresponding delete ---
it's in the file @code{object.cc} inside the method @code{Object::MaybeDelete},
but when that @code{Ptr} which you see above goes out of scope it will call
@code{Unref} and ultimately the C++ @code{delete} operator will be called.
The ns-3 system uses the C++ @code{new} and @code{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
@code{Ptr} smart pointer), work directly with the @code{new} and @code{delete}
operators and call methods like any C++ object as in the following example:
@verbatim
MyClass *obj = new MyClass ();
obj->Method();
delete obj;
@end verbatim
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.
@section Interfaces
There are many different ideas floating around of what exactly the term
@emph{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:
@quotation
``Many classes [@dots{}] are useful both as themselves and also as bases for
derived classes. [@dots{}] Some classes, such as class @strong{Shape},
represent abstract concepts for which objects cannot exist.''
@end quotation
@command{Ns-3} does not pick and enforce a particular approach. In
@command{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
@command{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 @command{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.
@section 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 @command{ns-3}. This was done primarily to address a common
problem in large software systems. A good example of this problem happens
in the @command{ns-3} Node class.
If one were to take the standard OOP view of specializing a @code{Node} into
an internet host, for example, one would typically inherit from the @code{Node}
base class and include functionality to implement such things as internet
routing and a TCP / IP protocol stack. Other types of @code{Node}s 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 @emph{weak base class} problem
and creates pressure to drive functionality up the inheritance tree into the
base classes. This, in turn, results in @emph{base class bloat} and the
resulting @emph{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 @code{Node}
generically in the system. This means one would pass references to the base
class (@code{Node}) across public APIs. This introduces @emph{upcasts} prior
to passing across public APIs and corresponding @emph{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 @emph{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.
@subsection 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 @code{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, @emph{delegation} is used. The base class, @code{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 @emph{aggregation}, along with runtime Interface
discovery is the solution that Microsoft originally championed and that
@command{ns-3} has adopted. In our example a @code{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 @command{ns-3} Interfaces and they
receive the functionality required for this participation by inheriting
from the base class @code{Object}.
@subsection Object, interfaces and Interfaces
As mentioned above, the class that implements the aggregation mechanism for
@command{ns-3} objects is called @code{Object}. The class named @code{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 @command{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 @code{Object} with promotion of an object to
the status of Interface rather than the form of the Interface declaration.
When you inherit from @code{Object}, you will get new methods and an
Interface Identifier. The Interface Identifer, or @emph{iid}, is the
@command{ns-3} version of the @emph{Universally Unique ID} (UUID) or
@emph{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 @code{QueryInterface}.
To summarize, when you instantiate an object that inherits from the
@code{Object} class, you will have a C++ object that has four important
properties:
@itemize @bullet
@item The object has a C++ interface defined by the collection of method signatures in its inheritance tree;
@item The object has an Interface ID that uniquely identifies the C++ interface of its class;
@item The object is a container that has the ability to aggregate other interfaces;
@item The object exports a method that allows for discovery of aggregated interfaces (@code{QueryInterface}) according to Interface ID.
@end itemize
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
(@code{iid}), and exports methods to aggregate and search other Interfaces
that are added to its aggregation.
That last detail is important. In @command{ns-3} Interfaces are both
containers and specifications for object method access. We have previously
mentioned the @code{Node} class acts as a container. In fact, the @code{Node}
class inherits from @code{Object} and is itself also an @command{ns-3}
Interface. When the @code{Node} object is created it is really an aggregation
of one Interface, the @code{Node} Interface. This is generally true ---
Interfaces are both containers and Interfaces.
@subsection 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.
@sp 1
@center @image{oneif,,,,png}
You could declare this interface quite simply using a non-virtual class as
follows,
@verbatim
class A : public Object {
public:
static const InterfaceId iid;
void MethodA (void);
};
@end verbatim
The methods that are then available via the Interface labeled @code{A} in the
figure above are the methods inherited from the @code{Object} base class (
@code{QueryInterface}, @code{Ref}, and @code{Unref}) and those from class
@code{A} (@code{MethodA}). Note that you must declare an @code{InterfaceId}
for your Interface class, and it must be declared static to make it class-wide
in scope. This @code{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 @code{QueryInterface}
to search aggregations for a particular @code{iid}. The figure below shows an
aggregation of three Interfaces: A, B and C. The class declarations for
classes @code{B} and @code{C} are substantially similar to that of class
@code{A}.
@sp 1
@center @image{threeif,,,,png}
You can visualize these Interfaces as being snapped together like Lego
building blocks if you like. When the Interfaces are aggregated, a
@code{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
@code{Create} template function and manage using smart pointers. The
following code should be obvious to you by now:
@verbatim
Ptr<A> a = Create<A> ();
Ptr<B> b = Create<B> ();
Ptr<C> c = Create<C> ();
@end verbatim
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 @code{AddInterface} that your class
inherited from @code{Object}. The following code will aggregate Interface
@code{B} and Interface @code{C} onto the Interface (and container) @code{A}.
@verbatim
a->AddInterface (b);
a->AddInterface (c);
@end verbatim
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:
@verbatim
Ptr<B> newB = a->QueryInterface<B> (B:iid);
@end verbatim
The left hand side of this assignment declares a smart pointer to the class
@code{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 @code{QueryInterface}. We
take a take a (smart) pointer to Interface @code{A} and ask it to search the
aggregation for an interface associated with an interface identifier with
the value of @code{B:iid} which is passed as a parameter. Recall that
@code{B::iid} is the @code{static InterfaceId} of the Interface class
@code{B}. Observe that @code{QueryInterface} is a template function and the
type specified in the angle brackets, here @code{<B>}, tells it what kind of
smart pointer to return. In this case @code{QueryInterface} will find an
Interface object of type @code{B::iid} in its list of Interfaces and return a
smart pointer to @code{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 @code{B}:
@verbatim
Ptr<B> newB = a->QueryInterface<B> (B:iid);
@end verbatim
Next, we can ask the Interface pointed to by the smart pointer @code{newB}
to look for the @code{InterfaceId} representing @code{C}:
@verbatim
Ptr<C> newC = newB->QueryInterface<C> (C:iid);
@end verbatim
Then, we can ask the Interface pointed to by the smart pointer @code{newC}
to look for the InterfaceId representing A and complete our circuit of the
aggregation:
@verbatim
Ptr<A> newA = newC->QueryInterface<A> (A:iid);
@end verbatim
@code{QueryInterface} (often abbreviated QI) has some important properties
that we need to go over. Technically, QI is a @emph{symmetric},
@emph{reflexive} and @emph{transitive} operation with respect to the set of
aggregated Interfaces.
@subsubsection 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
@code{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,
@center must succeed (A >> A)
We can illustrate this property with the code snippet,
@verbatim
Ptr<A> symmetricA = a->QueryInterface<A> (A:iid);
NS_ASSERT (symmetricA);
@end verbatim
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.
@subsubsection 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,
@center must succeed (A >> B, then B >> A)
This property can be illustrated with the code snippet,
@verbatim
Ptr<B> b = a->QueryInterface<B> (B:iid);
Ptr<A> reflexiveA = b->QueryInterface<A> (A:iid);
NS_ASSERT (reflexiveA);
@end verbatim
If the first @code{QueryInterface} on Interface A looking for Interface B
succeeds, then a @code{QueryInterface} on Interface B looking for Interface A
must succeed.
@subsubsection Transitivity
@code{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,
@center must succeed (A >> B, and B >> C, then A >> C)
This property can be illustrated with the code snippet,
@verbatim
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);
@end verbatim
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.
@subsection Creating the InterfaceId
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 @code{.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,
@verbatim
const InterfaceId A::iid =
MakeInterfaceId (``A'', Object::iid);
@end verbatim
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.
@code{MakeInterfaceId} takes an @code{InterfaceId} as a parameter. This is
the @code{iid} of the base class from which you inherited. In most cases
this will be @code{Object::iid}, which is the @code{InterfaceId} of the
@code{Object} base class. In @command{ns-3}, the @code{Object} base class
has its own @code{iid} and you can QI for that @code{iid}. The @code{Object}
base class has a rough equivalence to the @emph{IUnknown} Interface in
Microsofts COM, so you can QI for @code{Object::iid} in @command{ns-3}f just
as you might QI for IID_IUnknown in COM.
The InterfaceId you pass to @code{MakeInterfaceId} is used to create an
inheritance tree in the ns-3 interface manager. This inheritance tree is also
walked in @code{QueryInterface} Interface searches. Consider a simple case
of a base class and a derived class as shown below,
@verbatim
class Base : public Object
{
public:
static const InterfaceId iid;
...
};
class Derived : public Base
{
public:
static const InterfaceId iid;
...
};
@end verbatim
To assign the InterfaceId for each of these classes, we could add two calls
to @code{MakeInterfaceId} reflecting the class hierarchy we just created.
@verbatim
const InterfaceId Base::iid =
MakeInterfaceId (``Base'', Object::iid);
const InterfaceId Derived::iid =
MakeInterfaceId (``Derived'', Base::iid);
@end verbatim
The first Interface is shown to inherit from class @code{Object} and the
second inherits from class @code{Base}. We could create these interfaces
as we usually do,
@verbatim
Ptr<Base> base = Create<Base> ();
Ptr<Derived> derived = Create<Derived> ();
@end verbatim
The derived and base @code{InterfaceIds} are either present or not present
based on the inheritance tree. For example, a QI for the @code{Base
InterfaceId} must succeed when done against a @code{Ptr<Base>}; but a QI for
the @code{Derived InterfaceId} must fail when done against a @code{Ptr<Base>}.
However, a QI for the @code{Base InterfaceId} must succeed when done against a
@code{Ptr<Derived>}; and a QI for the @code{Derived InterfaceId} must succeed
when done against a @code{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.
@subsection 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 @code{samples/simple-point-to-point.cc}:
@verbatim
Ptr<Node> n = Create<InternetNode> ();
@end verbatim
This code is described in detail in previous sections, but the important thing
to realize here is that the resulting @code{Node} is an @command{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 @code{src/node/node.h} and find the
class declaration for class @code{Node}. There you will find,
@verbatim
class Node : public Object
{
public:
static const InterfaceId iid;
...
};
@end verbatim
Class @code{Node} inherits from class @code{Object} and provides an
@code{InterfaceId}, therefore it is an @command{ns-3} interface. You now
know you can use @code{AddInterface} for aggregation and @code{QueryInterface}
for Interface discovery against any @code{Node} in the system.
We spoke of a protocol stack that is aggregated to a @code{Node} in our
discussions above, what we see in the real @command{ns-3} code is that this
is represented by the @code{Ipv4} Interface. If you look in
@code{src/node/ipv4.h} you will find,
@verbatim
class Ipv4 : public Object
{
public:
static const InterfaceId iid;
...
};
@end verbatim
Since class @code{Ipv4} inherits from class @code{Object} and has a
@code{static InterfaceId}, it is an @command{ns-3} Interface. If you look in
@code{src/node/ipv4.cc} you will find,
@verbatim
const InterfaceId Ipv4::iid =
MakeInterfaceId (``Ipv4'', Object::iid);
@end verbatim
After all of this reading you now know that this code snippet is asking the
system to create a unique @code{InterfaceId} for the @code{Ipv4} class and
declares that @code{Ipv4} inherits from class @code{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 @code{Ipv4} object may not be instantiated. What is instantiated is an
implementation class, called @code{Ipv4Impl}. This class inherits from
@code{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 @code{Ipv4} class since that is where the @code{InterfaceId}
is found. The fact that you see @code{ipv4::iid} tells you that the
@code{Ipv4} class is the Interface and has the associated @code{InterfaceId}.
The class @code{Ipv4Impl} provides an implementation for the pure virtual
methods in @code{Ipv4}. Since class @code{Ipv4} cannot be instantiated, one
instantiates the @code{Ipv4Impl} class to create an @code{Ipv4} Interface.
Once the @code{Ipv4Impl} class is instantiated, the pointer to it is
immediately cast to an @code{Ipv4} pointer. Clients will then use the
@code{Ipv4} object access methods (see @code{ipv4.h}) to talk to the
@code{Ipv4Impl} object over the @code{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, @code{src/internet-node/internet-node.cc} you
will see the following code in @code{InternetNode::Construct} that creates the
@code{Ipv4} Interface and aggregates it to the @code{Node} interface (recall
that class @code{Node} is an Interface and class @code{InternetNode} inherits
from class @code{Node}):
@verbatim
Ptr<Ipv4Impl> ipv4Impl = Create<Ipv4Impl> (ipv4);
...
Object::AddInterface (ipv4Impl);
@end verbatim
Note that the parameter @code{ipv4} passed to the @code{Create} template
function is actually a pointer to an @code{Ipv4L3Protocol} which you can
ignore at this point --- it doesn't really have anything to do with the
@code{Ipv4} Interface.
This last example does illustrate that the fact that whether an @command{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 @code{Ipv4Impl} inherits from some
Interface since there is a call to @code{AddInterface} that refers to it.
You can go to the header file @code{src/internet-node/ipv4-impl.h} and find
that @code{Ipv4Impl} inherits from class @code{Ipv4}. You then go to file
@code{src/node/ipv4.h} and see that it inherits from @code{Object} and
contains an @code{InterfaceId}. Thus the Interface added is really the
@code{Ipv4} Interface with the interface Id @code{Ipv4::iid}.
Returning to some @command{ns-3} example code, lets take a look at
@code{src/examples/simple-point-to-point.cc} again. You will find the
following code:
@verbatim
Ptr<Node> n0 = Create<InternetNode> ();
...
Ptr<Ipv4> ipv4;
ipv4 = n0->QueryInterface<Ipv4> (Ipv4::iid);
ipv4->SetDefaultRoute (Ipv4Address (``10.1.1.2''), 1);
@end verbatim
The first line creates an @code{InternetNode} object and casts the resulting
smart pointer to a @code{Node}. The next line declares a smart pointer to an
@code{Ipv4} object. Because youve been through the code with us, you know
that both the @code{Node} and the @code{Ipv4} objects are Interfaces. They
should be able to participate in a @code{QueryInterface}.
The next line confirms it. We do a @code{QueryInterface} on the @code{Node},
looking for the @code{Ipv4} Interface (@code{Ipv4::iid}).
@code{QueryInterface} then returns a smart pointer to its aggregated
@code{Ipv4} Interface. [Recall that this Interface was aggregated in
@code{InternetNode::Construct}. We knew to start looking for the aggregation
in @code{InternetNode} since we originally created an @code{InternetNode} in
the @code{Create} template function and then implicitly cast it to a
@code{Node}.]
Once you have the @code{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.
@section Caveats
There are a few things that you should remember but which may not be
immediately obvious.
@subsection Interface Ids are Associated with Classes not Objects
Interfaces are identified by an @code{InterfaceId} that is associated with
the Interface class, not the Interface object. That is indicated by the
@code{static} keyword in the declaration of the @code{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 @code{InterfaceId}.
You cannot add more than one Interface of a given type (@code{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 @code{Node} class provides methods,
@verbatim
uint32_t GetNDevices (void) const;
Ptr<NetDevice> GetDevice (uint32_t index) const;
@end verbatim
that are used iterate over the multiple @code{NetDevice} Interfaces associated
with it.
@emph{Interface Ids do not identify objects.}
@subsection Dont use QI to Check Your Own Type.
It is tempting to use @code{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 @code{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.
@emph{Interface Ids should not be used as run-time type information.}
@section 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 @code{::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.
@c ========================================================================
@c Doxygen
@c ========================================================================
@node The-Doxygen-Documentation-System
@chapter The Doxygen Documentation System
@node How-To-Change-Things
@chapter How to Change Things
@node How-To-Set-Default-Values
@chapter How to Set Default Values
@node How-To-Write-A-New-Application
@chapter How to Write a New Application
@printindex cp
@bye