[Top] [Contents] [Index] [ ? ]

ns-3 Manual (html version)

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

This is an ns-3 reference manual. Primary documentation for the ns-3 project is available in four forms:

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

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

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

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


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1. Random variables

ns-3 contains a built-in pseudo-random number generator (PRNG). It is important for serious users of the simulator to understand the functionality, configuration, and usage of this PRNG, and to decide whether it is sufficient for his or her research use.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.1 Quick Overview

ns-3 random numbers are provided via instances of class RandomVariable.

Read further for more explanation about the random number facility for ns-3.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.2 Background

Simulations use a lot of random numbers; the study in [cite] found that most network simulations spend as much as 50% of the CPU generating random numbers. Simulation users need to be concerned with the quality of the (pseudo) random numbers and the independence between different streams of random numbers.

Users need to be concerned with a few issues, such as:

We will introduce a few terms here: a RNG provides a long sequence of (pseudo) random numbers. The length of this sequence is called the cycle length or period, after which the RNG will repeat itself. This sequence can be partitioned into disjoint streams. A stream of a RNG is a contiguous subset or block of the RNG sequence. For instance, if the RNG period is of length N, and two streams are provided from this RNG, then the first stream might use the first N/2 values and the second stream might produce the second N/2 values. An important property here is that the two streams are uncorrelated. Likewise, each stream can be partitioned disjointly to a number of uncorrelated substreams. The underlying RNG hopefully produces a pseudo-random sequence of numbers with a very long cycle length, and partitions this into streams and substreams in an efficient manner.

ns-3 uses the same underlying random number generator as does ns-2: the MRG32k3a generator from Pierre L'Ecuyer. A detailed description can be found in http://www.iro.umontreal.ca/~lecuyer/myftp/papers/streams00.pdf. The MRG32k3a generator provides 1.8x10^19 independent streams of random numbers, each of which consists of 2.3x10^15 substreams. Each substream has a period (i.e., the number of random numbers before overlap) of 7.6x10^22. The period of the entire generator is 3.1x10^57. Figure ref-streams provides a graphical idea of how the streams and substreams fit together.

Class ns3::RandomVariable is the public interface to this underlying random number generator. When users create new RandomVariables (such as UniformVariable, ExponentialVariable, etc.), they create an object that uses one of the distinct, independent streams of the random number generator. Therefore, each object of type RandomVariable has, conceptually, its own "virtual" RNG. Furthermore, each RandomVariable can be configured to use one of the set of substreams drawn from the main stream.

An alternate implementation would be to allow each RandomVariable to have its own (differently seeded) RNG. However, we cannot guarantee as strongly that the different sequences would be uncorrelated in such a case; hence, we prefer to use a single RNG and streams and substreams from it.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.3 Seeding and independent replications

ns-3 simulations can be configured to produce deterministic or random results. If the ns-3 simulation is configured to use a fixed, deterministic seed with the same run number, it should give the same output each time it is run.

By default, ns-3 simulations use random seeds where the seeding is drawn from /dev/random (if it is available) or else from the time of day. A user who wants to fix the initial seeding of the PRNG must call the following static method during simulation configuration:

RandomVariable::UseGlobalSeed (uint32_t s0, s1, s2, s3, s4, s5);

where the six parameters are each of type uint32_t.

A typical use case is to run a simulation as a sequence of independent trials, so as to compute statistics on a large number of independent runs. The user can either change the global seed and rerun the simulation, or can advance the substream state of the RNG. This seeding and substream state setting must be called before any random variables are created; e.g.

  RandomVariable::UseGlobalSeed(1,2,3,4,5,6);
  int N = atol(argv[1]); //read in run number from command line
  RandomVariable::SetRunNumber(N);
  // Now, create random variables
  UniformVariable x(0,10);
  ExponentialVariable y(2902);
  ...

Which is better, setting a new seed or advancing the substream state? There is no guarantee that the streams produced by two random seeds will not overlap. The only way to guarantee that two streams do not overlap is to use the substream capability provided by the RNG implementation. Therefore, use the substream capability to produce multiple independent runs of the same simulation. In other words, the more statistically rigorous way to configure multiple independent replications is not to simply ignore the seeding (and use /dev/random to seed the generator each time) but instead to use a fixed seed and to iterate the run number. This implementation allows for a maximum of 2.3x10^15 independent replications using the substreams.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.4 class RandomVariable

All random variables should derive from class RandomVariable. This base class provides a few static methods for globally configuring the behavior of the random number generator. Derived classes provide API for drawing random variates from the particular distribution being supported.

Each RandomVariable created in the simulation is given a generator that is a new RNGStream from the underlying PRNG. Used in this manner, the L'Ecuyer implementation allows for a maximum of 1.8x10^19 random variables. Each random variable in a single replication can produce up to 7.6x10^22 random numbers before overlapping.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.5 Base class public API

Below are excerpted a few public methods of class RandomVariable that deal with the global configuration and state of the RNG.

  /**
   * \brief Set seeding behavior
   * 
   * Specify whether the POSIX device /dev/random is to
   * be used for seeding.  When this is used, the underlying
   * generator is seeded with data from /dev/random instead of
   * being seeded based upon the time of day.   Defaults to true.
   */
  static  void UseDevRandom(bool udr = true);

   /**
   * \brief Use the global seed to force precisely reproducible results.
   */ 
  static void UseGlobalSeed(uint32_t s0, uint32_t s1, uint32_t s2, 
                            uint32_t s3, uint32_t s4, uint32_t s5);

  /**
   * \brief Set the run number of this simulation
   */
  static void SetRunNumber(uint32_t n);

  /**
   * \brief Get the internal state of the RNG
   *
   * This function is for power users who understand the inner workings
   * of the underlying RngStream method used.  It returns the internal
   * state of the RNG via the input parameter.
   * \param seed Output parameter; gets overwritten with the internal state
   * of the RNG.
   */
  void GetSeed(uint32_t seed[6]) const;

We have already described the seeding configuration above.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.6 Types of RandomVariables

The following types of random variables are provided, and are documented in the ns-3 Doxygen or by reading src/core/random-variable.h. Users can also create their own custom random variables by deriving from class RandomVariable.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.7 Semantics of RandomVariable objects

RandomVariable objects have value semantics. This means that they can be passed by value to functions. The can also be passed by reference to const. RandomVariables do not derive from ns3::Object and we do not use smart pointers to manage them; they are either allocated on the stack or else users explicitly manage any heap-allocated RandomVariables.

RandomVariable objects can also be used in ns-3 attributes, which means that values can be set for them through the ns-3 attribute system. An example is in the propagation models for WifiNetDevice:

TypeId
RandomPropagationDelayModel::GetTypeId (void)
{ 
  static TypeId tid = TypeId ("ns3::RandomPropagationDelayModel")
    .SetParent<PropagationDelayModel> ()
    .AddConstructor<RandomPropagationDelayModel> ()
    .AddAttribute ("Variable",
                   "The random variable which generates random delays (s).",
                   RandomVariableValue (UniformVariable (0.0, 1.0)),
         MakeRandomVariableAccessor (&RandomPropagationDelayModel::m_variable), 
                   MakeRandomVariableChecker ())
    ;
  return tid;
}

Here, the ns-3 user can change the default random variable for this delay model (which is a UniformVariable ranging from 0 to 1) through the attribute system.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.8 Using other PRNG

There is presently no support for substituting a different underlying random number generator (e.g., the GNU Scientific Library or the Akaroa package). Patches are welcome.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.9 More advanced usage

To be completed


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.10 Publishing your results

When you publish simulation results, a key piece of configuration information that you should always state is how you used the the random number generator.

It is incumbent on the researcher publishing results to include enough information to allow others to reproduce his or her results. It is also incumbent on the researcher to convince oneself that the random numbers used were statistically valid, and to state in the paper why such confidence is assumed.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.11 Summary

Let's review what things you should do when creating a simulation.

The program samples/main-random.cc has some examples of usage.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2. Callbacks

Some new users to ns-3 are unfamiliar with an extensively used programming idiom used throughout the code: the “ns-3 callback”. This chapter provides some motivation on the callback, guidance on how to use it, and details on its implementation.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.1 Motivation

Consider that you have two simulation models A and B, and you wish to have them pass information between them during the simulation. One way that you can do that is that you can make A and B each explicitly knowledgable about the other, so that they can invoke methods on each other.

class A {
public:
  void ReceiveInput ( // parameters );
  ...
}

(in another source file:)

class B {
public:
  void ReceiveInput ( // parameters);
  void DoSomething (void);
  ...

private:
  A* a_instance; // pointer to an A
}

void
B::DoSomething()
{
  // Tell a_instance that something happened
  a_instance->ReceiveInput ( // parameters);
  ...
}

This certainly works, but it has the drawback that it introduces a dependency on A and B to know about the other at compile time (this makes it harder to have independent compilation units in the simulator) and is not generalized; if in a later usage scenario, B needs to talk to a completely different C object, the source code for B needs to be changed to add a “c_instance” and so forth. It is easy to see that this is a brute force mechanism of communication that can lead to programming cruft in the models.

This is not to say that objects should not know about one another if there is a hard dependency between them, but that often the model can be made more flexible if its interactions are less constrained at compile time.

This is not an abstract problem for network simulation research, but rather it has been a source of problems in previous simulators, when researchers want to extend or modify the system to do different things (as they are apt to do in research). Consider, for example, a user who wants to add an IPsec security protocol sublayer between TCP and IP:

------------                   -----------
|   TCP    |                   |  TCP    |
------------                   -----------
     |           becomes ->        |
-----------                    -----------
|   IP    |                    | IPsec   |
-----------                    -----------
                                   |
                               -----------
                               |   IP    |
                               -----------

If the simulator has made assumptions, and hard coded into the code, that IP always talks to a transport protocol above, the user may be forced to hack the system to get the desired interconnections.

An alternative that provides this flexibility is to use a level of indirection that is commonly known in programming as a callback. A callback function is not invoked explicitly by the caller but is rather delegated to another function that receives the callback function's address and can call it.

You may be familiar with function pointers in C or C++; these can be used to implement callbacks. For more information on introductory callbacks, an online reference is: Declaring Function Pointers and Implementing Callbacks and Callback (computer science)– Wikipedia.

The callback API in ns-3 is designed to minimize the overall coupling between various pieces of of the simulator by making each module depend on the callback API itself rather than depend on other modules. It acts as a sort of third-party to which work is delegated and which forwards this work to the proper target module. This callback API, being based on C++ templates, is type-safe; that is, it performs static type checks to enforce proper signature compatibility between callers and callees. It is therefore more type-safe to use than traditional function pointers, but the syntax may look imposing at first. This section is designed to walk you through the callback system so that you can be comfortable using it in ns-3.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2 Using the Callback API

The Callback API is fairly minimal, providing only two services:

This is best observed via walking through an example, based on samples/main-callback.cc.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2.1 Using the Callback API with static functions

Consider a function:

static double
CbOne (double a, double b)
{
  std::cout << "invoke cbOne a=" << a << ", b=" << b << std::endl;
  return a;
}

Consider also the following main program snippett:

int main (int argc, char *argv[])
{
  // return type: double
  // first arg type: double
  // second arg type: double
  Callback<double, double, double> one;
}

This class template Callback implements what is known as the Functor Design Pattern. It is used to declare the type of a callback. It contains one mandatory argument (the return type of the function to be assigned to this callback) and up to five optional arguments, which each specify the type of the arguments (if your function has more than five arguments, then this can be handled by extending the callback implementation).

So in the above, we have a declared a callback named "one" that will eventually hold a function pointer. The function that it will hold must return double and must support two double arguments. If one tries to pass a function whose signature does not match the declared callback, the compilation will fail.

Now, we need to tie together this callback instance and the actual target function (CbOne). Notice above that CbOne has the same function signature types as the callback– this is important. We can pass in any such properly-typed function to this callback. Let's look at this more closely:

static double CbOne (double a, double b) {}
          ^           ^          ^
          |        ---|    ------|
          |        |       | 
Callback<double, double, double> one;

You can only bind a function to a callback if they have the matching signature. The first template argument is the return type, and the additional template arguments are the types of the arguments of the function signature.

Now, let's bind our callback "one" to the function that matches its signature:

  // build callback instance which points to cbOne function
  one = MakeCallback (&CbOne);

Then, later in the program, if the callback is to be used, it can be used as follows:

// this is not a null callback
  NS_ASSERT (!one.IsNull ());
  // invoke cbOne function through callback instance
  double retOne;
  retOne = one (10.0, 20.0);

The check IsNull() ensures that the callback is not null; that there is a function to call behind this callback. Then, one() returns the same result as if CbOne() had been called directly.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2.2 Using the Callback API with member functions

Generally, you will not be calling static functions but instead public member functions of an object. In this case, an extra argument is needed to the MakeCallback function, to tell the system on which object the function should be invoked. Consider this example, also from main-callback.cc:

class MyCb {
public:
  int CbTwo (double a) {
      std::cout << "invoke cbTwo a=" << a << std::endl;
      return -5;
  }
};

int main ()
{
  ...
  // return type: int
  // first arg type: double
  Callback<int, double> two;
  MyCb cb;
  // build callback instance which points to MyCb::cbTwo
  two = MakeCallback (&MyCb::CbTwo, &cb);
  ...
}

Here, we pass a (raw) pointer to the MakeCallback<> function, that says, when two () is invoked, to call the CbTwo function on the object pointed to by &cb.

A variation of this is used when objects are referred to by ns-3 smart pointers. The MakeCallback API takes a raw pointer, so we need to call PeekPointer () to obtain this raw pointer. So the example above would look like:

class MyCb : public Object {
public:
  int CbTwo (double a) {
      std::cout << "invoke cbTwo a=" << a << std::endl;
      return -5;
  }
};

int main ()
{
  ...
  // return type: int
  // first arg type: double
  Callback<int, double> two;
  Ptr<MyCb> cb = CreateObject<MyCb> ();
  // build callback instance which points to MyCb::cbTwo
  two = MakeCallback (&MyCb::CbTwo, PeekPointer (cb));
  ...
}

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2.3 Building Null Callbacks

It is possible for callbacks to be null; hence it may be wise to check before using them. There is a special construct for a null callback, which is preferable to simply passing "0" as an argument; it is the MakeNullCallback<> construct:

  two = MakeNullCallback<int, double> ();
  // invoking a null callback is just like
  // invoking a null function pointer:
  // it will crash at runtime.
  //int retTwoNull = two (20.0);
  NS_ASSERT (two.IsNull ());

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.3 Callback locations in ns-3

Where are callbacks frequently used in ns-3? Here are some of the more visible ones to typical users:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.3.1 Socket API


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.3.2 Layer-2/Layer-3 API


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.3.3 Tracing subsystem


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.3.4 Routing

Route Reply


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.4 Implementation details

This section is advanced explanation for C++ experts interested in the implementation, and may be skipped by most users.

This code was originally written based on the techniques described here. It was subsequently rewritten to follow the architecture outlined in Modern C++ Design: Generic Programming and Design Patterns Applied– Alexandrescu, chapter 5, "Generalized Functors".

This code uses:

This code most notably departs from the Alexandrescu implementation in that it does not use type lists to specify and pass around the types of the callback arguments. Of course, it also does not use copy-destruction semantics and relies on a reference list rather than autoPtr to hold the pointer.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3. Attributes

In ns-3 simulations, there are two main aspects to configuration:

This chapter focuses on the second item above: how the many values in use in ns-3 are organized, documented, and modifiable by ns-3 users. The ns-3 attribute system is also the underpinning of how traces and statistics are gathered in the simulator.

Before delving into details of the attribute value system, it will help to review some basic properties of class ns3::Object.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.1 Object Overview

ns-3 is fundamentally a C++ object-based system. By this we mean that new C++ classes (types) can be declared, defined, and subclassed as usual.

Many ns-3 objects inherit from the ns3::Object base class. These objects have some additional properties that we exploit for organizing the system and improving the memory management of our objects:

ns-3 objects that use the attribute system derive from either ns3::Object or ns3::ObjectBase. Most ns-3 objects we will discuss derive from ns3::Object, but a few that are outside the smart pointer memory management framework derive from ns3::ObjectBase.

Let's review a couple of properties of these objects.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.1.1 Smart pointers

As introduced in the ns-3 tutorial, ns-3 objects are memory managed by a reference counting smart pointer implementation, class ns3::Ptr.

Smart pointers are used extensively in the ns-3 APIs, to avoid passing references to heap-allocated objects that may cause memory leaks. For most basic usage (syntax), treat a smart pointer like a regular pointer:

  Ptr<WifiNetDevice> nd = ...;
  nd->CallSomeFunction ();
  // etc.

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.1.2 CreateObject

As we discussed above in @ref{Object Creation}, at the lowest-level API, objects of type ns3::Object are not instantiated using operator new as usual but instead by a templated function called CreateObject().

A typical way to create such an object is as follows:

  Ptr<WifiNetDevice> nd = CreateObject<WifiNetDevice> ();

You can think of this as being functionally equivalent to:

  WifiNetDevice* nd = new WifiNetDevice ();

Objects that derive from ns3::Object must be allocated on the heap using CreateObject(). Those deriving from ns3::ObjectBase, such as ns-3 helper functions and packet headers and trailers, can be allocated on the stack.

In some scripts, you may not see a lot of CreateObject() calls in the code; this is because there are some helper objects in effect that are doing the CreateObject()s for you.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.1.3 TypeId

ns-3 classes that derive from class ns3::Object can include a metadata class called TypeId that records meta-information about the class, for use in the object aggregation and component manager systems:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.1.4 Object Summary

Putting all of these concepts together, let's look at a specific example: class ns3::Node.

The public header file node.h has a declaration that includes a static GetTypeId function call:

class Node : public Object
{
public:
  static TypeId GetTypeId (void);
  ...

This is defined in the node.cc file as follows:

TypeId 
Node::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::Node")
    .SetParent<Object> ()
    ;
  return tid;
}

Finally, when users want to create Nodes, they call:

  Ptr<Node> n = CreateObject<Node> ();

We next discuss how attributes (values associated with member variables or functions of the class) are plumbed into the above TypeId.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.2 Attribute Overview

The goal of the attribute system is to organize the access of internal member objects of a simulation. This goal arises because, typically in simulation, users will cut and paste/modify existing simulation scripts, or will use higher-level simulation constructs, but often will be interested in studying or tracing particular internal variables. For instance, use cases such as:

Similarly, users may want fine-grained access to internal variables in the simulation, or may want to broadly change the initial value used for a particular parameter in all subsequently created objects. Finally, users may wish to know what variables are settable and retrievable in a simulation configuration. This is not just for direct simulation interaction on the command line; consider also a (future) graphical user interface that would like to be able to provide a feature whereby a user might right-click on an node on the canvas and see a hierarchical, organized list of parameters that are settable on the node and its constituent member objects, and help text and default values for each parameter.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.2.1 Functional overview

We provide a way for users to access values deep in the system, without having to plumb accessors (pointers) through the system and walk pointer chains to get to them. Consider a class DropTailQueue that has a member variable that is an unsigned integer m_maxPackets; this member variable controls the depth of the queue.

If we look at the declaration of DropTailQueue, we see the following:

class DropTailQueue : public Queue {
public:
  static TypeId GetTypeId (void);
  ...

private:
  std::queue<Ptr<Packet> > m_packets;
  uint32_t m_maxPackets;
};

Let's consider things that a user may want to do with the value of m_maxPackets:

The above things typically require providing Set() and Get() functions, and some type of global default value.

In the ns-3 attribute system, these value definitions and accessor functions are moved into the TypeId class; e.g.:

TypeId DropTailQueue::GetTypeId (void) 
{
  static TypeId tid = TypeId ("ns3::DropTailQueue")
    .SetParent<Queue> ()
    .AddConstructor<DropTailQueue> ()
    .AddAttribute ("MaxPackets", 
                   "The maximum number of packets accepted by this DropTailQueue.",
                   UintegerValue (100),
                   MakeUintegerAccessor (&DropTailQueue::m_maxPackets),
                   MakeUintegerChecker<uint32_t> ())
    ;
  
  return tid;
}

The AddAttribute() method is performing a number of things with this value:

The key point is that now the value of this variable and its default value are accessible in the attribute namespace, which is based on strings such as "MaxPackets" and TypeId strings. In the next section, we will provide an example script that shows how users may manipulate these values.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.2.2 Basic usage

Let's look at how a user script might access these values. This is based on the script found at samples/main-attribute-value.cc, with some details stripped out.

//
// This is a basic example of how to use the attribute system to
// set and get a value in the underlying system; namely, an unsigned
// integer of the maximum number of packets in a queue
//

int 
main (int argc, char *argv[])
{

  // By default, the MaxPackets attribute has a value of 100 packets
  // (this default can be observed in the function DropTailQueue::GetTypeId)
  // 
  // Here, we set it to 80 packets.  We could use one of two value types:
  // a string-based value or a Uinteger value
  Config::SetDefault ("ns3::DropTailQueue::MaxPackets", StringValue ("80"));
  // The below function call is redundant
  Config::SetDefault ("ns3::DropTailQueue::MaxPackets", UintegerValue (80));

  // Allow the user to override any of the defaults and the above
  // SetDefaults() at run-time, via command-line arguments
  CommandLine cmd;
  cmd.Parse (argc, argv);

The main thing to notice in the above are the two calls to Config::SetDefault. This is how we set the default value for all subsequently instantiated DropTailQueues. We illustrate that two types of Value classes, a StringValue and a UintegerValue class, can be used to assign the value to the attribute named by "ns3::DropTailQueue::MaxPackets".

Now, we will create a few objects using the low-level API; here, our newly created queues will not have a m_maxPackets initialized to 100 packets but to 80 packets, because of what we did above with default values.

  Ptr<Node> n0 = CreateObject<Node> ();

  Ptr<PointToPointNetDevice> net0 = CreateObject<PointToPointNetDevice> ();
  n0->AddDevice (net0);

  Ptr<Queue> q = CreateObject<DropTailQueue> ();
  net0->AddQueue(q);

At this point, we have created a single node (Node 0) and a single PointToPointNetDevice (NetDevice 0) and added a DropTailQueue to it.

Now, we can manipulate the MaxPackets value of the already instantiated DropTailQueue. Here are various ways to do that.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.2.2.1 Pointer-based access

We assume that a smart pointer (Ptr) to a relevant network device is in hand; here, it is the net0 pointer.

One way to change the value is to access a pointer to the underlying queue and modify its attribute.

First, we observe that we can get a pointer to the (base class) queue via the PointToPointNetDevice attributes, where it is called TxQueue

  PointerValue tmp;
  net0->GetAttribute ("TxQueue", tmp);
  Ptr<Object> txQueue = tmp.GetObject ();

Using the GetObject function, we can perform a safe downcast to a DropTailQueue, where MaxPackets is a member

  Ptr<DropTailQueue> dtq = txQueue->GetObject <DropTailQueue> ();
  NS_ASSERT (dtq != 0);

Next, we can get the value of an attribute on this queue. We have introduced wrapper "Value" classes for the underlying data types, similar to Java wrappers around these types, since the attribute system stores values and not disparate types. Here, the attribute value is assigned to a UintegerValue, and the Get() method on this value produces the (unwrapped) uint32_t.

  UintegerValue limit;
  dtq->GetAttribute ("MaxPackets", limit);
  NS_LOG_INFO ("1.  dtq limit: " << limit.Get () << " packets");

Note that the above downcast is not really needed; we could have done the same using the Ptr<Queue> even though the attribute is a member of the subclass

  txQueue->GetAttribute ("MaxPackets", limit);
  NS_LOG_INFO ("2.  txQueue limit: " << limit.Get () << " packets");

Now, let's set it to another value (60 packets)

  txQueue->SetAttribute("MaxPackets", UintegerValue (60));
  txQueue->GetAttribute ("MaxPackets", limit);
  NS_LOG_INFO ("3.  txQueue limit changed: " << limit.Get () << " packets");

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.2.2.2 Namespace-based access

An alternative way to get at the attribute is to use the configuration namespace. Here, this attribute resides on a known path in this namespace; this approach is useful if one doesn't have access to the underlying pointers and would like to configure a specific attribute with a single statement.

  Config::Set ("/NodeList/0/DeviceList/0/TxQueue/MaxPackets", UintegerValue (25));
  txQueue->GetAttribute ("MaxPackets", limit); 
  NS_LOG_INFO ("4.  txQueue limit changed through namespace: " << 
    limit.Get () << " packets");

We could have also used wildcards to set this value for all nodes and all net devices (which in this simple example has the same effect as the previous Set())

  Config::Set ("/NodeList/*/DeviceList/*/TxQueue/MaxPackets", UintegerValue (15));
  txQueue->GetAttribute ("MaxPackets", limit); 
  NS_LOG_INFO ("5.  txQueue limit changed through wildcarded namespace: " << 
    limit.Get () << " packets");

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.2.3 Setting through constructors helper classes

Arbitrary combinations of attributes can be set and fetched from the helper and low-level APIs; either from the constructors themselves:

Ptr<Object> p = CreateObject<MyNewObject> ("n1", v1, "n2", v2, ...);

or from the higher-level helper APIs, such as:

  mobility.SetPositionAllocator ("GridPositionAllocator",
                                 "MinX", DoubleValue (-100.0),
                                 "MinY", DoubleValue (-100.0),
                                 "DeltaX", DoubleValue (5.0),
                                 "DeltaY", DoubleValue (20.0),
                                 "GridWidth", UintegerValue (20),
                                 "LayoutType", StringValue ("RowFirst"));

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.2.4 Value classes

Readers will note the new FooValue classes which are subclasses of the AttributeValue base class. These can be thought of as an intermediate class that can be used to convert from raw types to the Values that are used by the attribute system. Recall that this database is holding objects of many types with a single generic type. Conversions to this type can either be done using an intermediate class (IntegerValue, DoubleValue for "floating point") or via strings. Direct implicit conversion of types to Value is not really practical. So in the above, users have a choice of using strings or values:

p->Set ("cwnd", StringValue ("100")); // string-based setter
p->Set ("cwnd", IntegerValue (100)); // integer-based setter

The system provides some macros that help users declare and define new AttributeValue subclasses for new types that they want to introduce into the attribute system:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3 Extending attributes

The ns-3 system will place a number of internal values under the attribute system, but undoubtedly users will want to extend this to pick up ones we have missed, or to add their own classes to this.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3.1 Adding an existing internal variable to the metadata system

Consider this variable in class TcpSocket:

 uint32_t m_cWnd;   // Congestion window

Suppose that someone working with Tcp wanted to get or set the value of that variable using the metadata system. If it were not already provided by ns-3, the user could declare the following addition in the metadata system (to the TypeId declaration for TcpSocket):

    .AddParameter ("Congestion window", 
                   "Tcp congestion window (bytes)",
                   Uinteger (1),
                   MakeUintegerAccessor (&TcpSocket::m_cWnd),
                   MakeUintegerChecker<uint16_t> ());

Now, the user with a pointer to the TcpSocket can perform operations such as setting and getting the value, without having to add these functions explicitly. Furthermore, access controls can be applied, such as allowing the parameter to be read and not written, or bounds checking on the permissible values can be applied.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.3.2 Adding a new TypeId

Here, we discuss the impact on a user who wants to add a new class to ns-3; what additional things must be done to hook it into this system.

We've already introduced what a TypeId definition looks like:

TypeId
RandomWalk2dMobilityModel::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::RandomWalk2dMobilityModel")
    .SetParent<MobilityModel> ()
    .SetGroupName ("Mobility")
    .AddConstructor<RandomWalk2dMobilityModel> ()
    .AddAttribute ("Bounds",
                   "Bounds of the area to cruise.",
                   RectangleValue (Rectangle (0.0, 0.0, 100.0, 100.0)),
                   MakeRectangleAccessor (&RandomWalk2dMobilityModel::m_bounds),
                   MakeRectangleChecker ())
    .AddAttribute ("Time",
                   "Change current direction and speed after moving for this delay.",
                   TimeValue (Seconds (1.0)),
                   MakeTimeAccessor (&RandomWalk2dMobilityModel::m_modeTime),
                   MakeTimeChecker ())
    // etc (more parameters).
    ;
  return tid;
}

The declaration for this in the class declaration is one-line public member method:

 public:
  static TypeId GetTypeId (void);

Typical mistakes here involve:

None of these mistakes can be detected by the ns-3 codebase so, users are advised to check carefully multiple times that they got these right.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.4 Adding new class type to the attribute system

From the perspective of the user who writes a new class in the system and wants to hook it in to the attribute system, there is mainly the matter of writing the conversions to/from strings and attribute values. Most of this can be copy/pasted with macro-ized code. For instance, consider class Rectangle in the src/mobility/ directory:

One line is added to the class declaration:

/**
 * \brief a 2d rectangle
 */
class Rectangle
{
...

};

One macro call and two operators, are added below the class declaration:

std::ostream &operator << (std::ostream &os, const Rectangle &rectangle);
std::istream &operator >> (std::istream &is, Rectangle &rectangle);

ATTRIBUTE_HELPER_HEADER (Rectangle);

In the class definition, the code looks like this:

ATTRIBUTE_HELPER_CPP (Rectangle);

std::ostream &
operator << (std::ostream &os, const Rectangle &rectangle)
{
  os << rectangle.xMin << "|" << rectangle.xMax << "|" << rectangle.yMin << "|" << rectangle.yMax;
  return os;
}
std::istream &
operator >> (std::istream &is, Rectangle &rectangle)
 {
  char c1, c2, c3;
  is >> rectangle.xMin >> c1 >> rectangle.xMax >> c2 >> rectangle.yMin >> c3 >> rectangle.yMax;
  if (c1 != '|' ||
      c2 != '|' ||
      c3 != '|')
    {
      is.setstate (std::ios_base::failbit);
    }
  return is;
}

These stream operators simply convert from a string representation of the Rectangle ("xMin|xMax|yMin|yMax") to the underlying Rectangle, and the modeler must specify these operators and the string syntactical representation of an instance of the new class.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.5 ConfigStore

Feedback requested: This is an experimental feature of ns-3. It is not in the main tree. If you like this feature and would like to provide feedback on it, please email us.

Values for ns-3 attributes can be stored in an ascii text file and loaded into a future simulation. This feature is known as the ns-3 ConfigStore. The ConfigStore code is in src/contrib/. It is not yet main-tree code, because we are seeking some user feedback.

We can explore this system by using an example. Copy the csma-bridge.cc file to the scratch directory:

  cp examples/csma-bridge.cc scratch/
  ./waf

Let's edit it to add the ConfigStore feature. First, add an include statement, and then add these lines:

#include "contrib-module.h"
...
int main (...)
{
  // setup topology

  // Invoke just before entering Simulator::Run ()
  ConfigStore config;
  config.Configure ();

  Simulator::Run ();
}

There is an attribute that governs whether the Configure() call either stores a simulation configuration in a file and exits, or whether it loads a simulation configuration file annd proceeds. First, the LoadFilename attribute is checked, and if non-empty, the program loads the configuration from the filename provided. If LoadFilename is empty, and if the StoreFilename attribute is populated, the configuration will be written to the output filename specified.

While it is possible to generate a sample config file and lightly edit it to change a couple of values, there are cases where this process will not work because the same value on the same object can appear multiple times in the same automatically-generated configuration file under different configuration paths.

As such, the best way to use this class is to use it to generate an initial configuration file, extract from that configuration file only the strictly necessary elements, and move these minimal elements to a new configuration file which can then safely be edited and loaded in a subsequent simulation run.

So, let's do that as an example. We'lll run the program once to create a configure file, and look at it. If you are running bash shell, the below command should work (which illustrates how to set an attribute from the command line):

./build/debug/scratch/csma-bridge --ns3::ConfigStore::StoreFilename=test.config

or, if the above does not work (the above requires rpath support), try this:

./waf --command-template="%s --ns3::ConfigStore::StoreFilename=test.config" --run scratch/csma-bridge

Running the program should yield a "test.config" output configuration file that looks like this:

/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNetDevice/Addre
ss 00:00:00:00:00:01
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNetDevice/Frame
Size 1518
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNetDevice/SendE
nable true
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNetDevice/Recei
veEnable true
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNetDevice/TxQue
ue/$ns3::DropTailQueue/MaxPackets 100
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNetDevice/Mtu 1
500
...

The above lists, for each object in the script topology, the value of each registered attribute. The syntax of this file is that the unique name of the attribute (in the attribute namespace) is specified on each line, followed by a value.

This file is intended to be a convenient record of the parameters that were used in a given simulation run, and can be stored with simulation output files. Additionally, this file can also be used to parameterize a simulation, instead of editing the script or passing in command line arguments. For instance, a person wanting to run the simulation can examine and tweak the values in a pre-existing configuration file, and pass the file to the program. In this case, the relevant commands are:

./build/debug/scratch/csma-bridge --ns3::ConfigStore::LoadFilename=test.config

or, if the above does not work (the above requires rpath support), try this:

./waf --command-template="%s --ns3::ConfigStore::LoadFilename=test.config" --run scratch/csma-bridge

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.5.1 GTK-based ConfigStore

There is a GTK-based front end for the ConfigStore. This allows users to use a GUI to access and change variables. Screenshots of this feature are available in the ns-3 Overview presentation.

To use this feature, one must install libgtk and libgtk-dev; an example Ubuntu installation command is:

sudo apt-get install libgtk2.0-0 libgtk2.0-dev

To check whether it is configured or not, check the output of the ./waf configure step:

---- Summary of optional NS-3 features:
Threading Primitives          : enabled
Real Time Simulator           : enabled
GtkConfigStore                : not enabled (library 'gtk+-2.0 >= 2.12' not found)

In the above example, it was not enabled, so it cannot be used until a suitable version is installed and ./waf configure; ./waf is rerun.

Usage is almost the same as the non-GTK-based version:

  // Invoke just before entering Simulator::Run ()
  GtkConfigStore config;
  config.Configure ();

Now, when you run the script, a GUI should pop up, allowing you to open menus of attributes on different nodes/objects, and then launch the simulation execution when you are done.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.5.2 Future work

There are a couple of possible improvements:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4. Real-Time Scheduler

ns-3 has been designed for integration into testbed and virtual machine environments. To integrate with real network stacks and emit/consume packets, a real-time scheduler is needed to try to lock the simulation clock with the hardware clock. We describe here a component of this: the RealTime scheduler.

The purpose of the realtime scheduler is to cause the progression of the simulation clock to occur synchronously with respect to some external time base. Without the presence of an external time base (wall clock), simulation time jumps instantly from one simulated time to the next.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.1 Behavior

When using a non-realtime scheduler (the default in ns-3), the simulator advances the simulation time to the next scheduled event. During event execution, simulation time is frozen. With the realtime scheduler, the behavior is similar from the perspective of simulation models (i.e., simulation time is frozen during event execution), but between events, the simulator will attempt to keep the simulation clock aligned with the machine clock.

When an event is finished executing, and the scheduler moves to the next event, the scheduler compares the next event execution time with the machine clock. If the next event is scheduled for a future time, the simulator sleeps until that realtime is reached and then executes the next event.

It may happen that, due to the processing inherent in the execution of simulation events, that the simulator cannot keep up with realtime. In such a case, it is up to the user configuration what to do. There are two ns-3 attributes that govern the behavior. The first is ns3::RealTimeSimulatorImpl::SynchronizationMode. The two entries possible for this attribute are BestEffort (the default) or HardLimit. In "BestEffort" mode, the simulator will just try to catch up to realtime by executing events until it reaches a point where the next event is in the (realtime) future, or else the simulation ends. In BestEffort mode, then, it is possible for the simulation to consume more time than the wall clock time. The other option "HardLimit" will cause the simulation to abort if the tolerance threshold is exceeded. This attribute is ns3::RealTimeSimulatorImpl::HardLimit and the default is 0.1 seconds.

A different mode of operation is one in which simulated time is not frozen during an event execution. This mode of realtime simulation was implemented but removed from the ns-3 tree because of questions of whether it would be useful. If users are interested in a realtime simulator for which simulation time does not freeze during event execution (i.e., every call to Simulator::Now() returns the current wall clock time, not the time at which the event started executing), please contact the ns-developers mailing list.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.2 Usage

The usage of the realtime simulator is straightforward, from a scripting perspective. Users just need to set the attribute SimulatorImplementationType to the Realtime simulator, such as follows:

  GlobalValue::Bind ("SimulatorImplementationType",
    StringValue ("ns3::RealtimeSimulatorImpl"));

There is a script in examples/realtime-udp-echo.cc that has an example of how to configure the realtime behavior. Try:

./waf --run realtime-udp-echo

Whether the simulator will work in a best effort or hard limit policy fashion is governed by the attributes explained in the previous section.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.3 Implementation

The implementation is contained in the following files:

In order to create a realtime scheduler, to a first approximation you just want to cause simulation time jumps to consume real time. We propose doing this using a combination of sleep- and busy- waits. Sleep-waits cause the calling process (thread) to yield the processor for some amount of time. Even though this specified amount of time can be passed to nanosecond resolution, it is actually converted to an OS-specific granularity. In Linux, the granularity is called a Jiffy. Typically this resolution is insufficient for our needs (on the order of a ten milliseconds), so we round down and sleep for some smaller number of Jiffies. The process is then awakened after the specified number of Jiffies has passed. At this time, we have some residual time to wait. This time is generally smaller than the minimum sleep time, so we busy-wait for the remainder of the time. This means that the thread just sits in a for loop consuming cycles until the desired time arrives. After the combination of sleep- and busy-waits, the elapsed realtime (wall) clock should agree with the simulation time of the next event and the simulation proceeds.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5. Packets

The design of the Packet framework of ns was heavily guided by a few important use-cases:

ns Packet objects contain a buffer of bytes: protocol headers and trailer