Though NS-3 provides nice examples and descriptions, unfortunately, they were written in a zigzag pattern and appear less straightforward to me during the practice. It also takes time to sort out the mesh relationships among components and aggregate the scattered information to form a linear logic pattern. Hence, this tutorial is to depict a step-by-step pipeline to conduct an NS-3 simulation.

S0: Prerequisites

  • Why simulation? It is necessary to pick the right methodology (simulation, emulation, testbed, real-world deployment) for the target networking problem.
  • Why NS-3? There is a bunch of open-source simulators (NS-2, NS-3...). For me, I picked NS-3 since I like C++/Python programming and it is convenient to interface NS-3 with emulations and machine learning frameworks.
  • C++ coding style: I found it helpful to follow the NS-3 coding style from the very beginning, e.g., Camel cases for classes and camelBack for variables.
  • Documentation: The NS-3 manual, NS-3 tutorial are must reads. Besides, the NS-3 source code is the best documentation for me and provides answers to specific questions not mentioned in the NS-3 wiki, doxygen. For example, when setting OnOffApplication data rate attribute, it is unclear from the NS-3 documentation if such rate is an averaged rate. One could easily peek into the source code and figure out from both the attribute system and the calculation of the next scheduling event that it refers to the rate during the on-state transmission:
    // A snapshot of {NS-3 tree prefix}/src/applications/model/onoff-application.cc
    TypeId
    OnOffApplication::GetTypeId (void)
    {
      static TypeId tid = TypeId ("ns3::OnOffApplication")
        .SetParent<Application> ()
        .SetGroupName("Applications")
        .AddConstructor<OnOffApplication> ()
        .AddAttribute ("DataRate", "The data rate in on state.",
                       DataRateValue (DataRate ("500kb/s")),
                       MakeDataRateAccessor (&OnOffApplication::m_cbrRate),
                       MakeDataRateChecker ())
        //...
    }
    void OnOffApplication::ScheduleNextTx ()
    {
      NS_LOG_FUNCTION (this);
    
      if (m_maxBytes == 0 || m_totBytes < m_maxBytes)
        {
          uint32_t bits = m_pktSize * 8 - m_residualBits;
          NS_LOG_LOGIC ("bits = " << bits);
          Time nextTime (Seconds (bits /
                                  static_cast<double>(m_cbrRate.GetBitRate ()))); // Time till next packet
          NS_LOG_LOGIC ("nextTime = " << nextTime);
          m_sendEvent = Simulator::Schedule (nextTime,
                                             &OnOffApplication::SendPacket, this);
        }
      else
        { // All done, cancel any pending events
          StopApplication ();
        }
    }

    S1: Crystallization of Simulation Details

    I summarized the following list of components whose details are worth consideration prior simulation.

    • Nodes: How many nodes will exist in the simulation? It is helpful to classify them a priori since typically nodes of the same category will be stored in the same NodeContainer.
    • Links: Each link corresponds to a pair of NetDevices and the connecting Channel. One node could possess multiple NetDevices interfacing the network, as in real-world systems. Since both components are highly coupled (e.g., WifiNetDevice-WifiChannel, CsmaNetDevice-CsmaChannel), one will typically determine the features of the NetDevices and the Channel jointly. NS-3 reflects such an idea by introducing the PointToPointHelper and its subclasses. One needs to decide on the type of link depending on the network context.
    • Queues: New versions of NS-3 introduce the traffic control layer facing the IP interface. Hence, one needs to differentiate between NetDeviceQueue (Linux struct netdev_queue equivalent) and QueueDisc (Linux qdisc equivalent) for simulation.
    • Applications: Flows and packets are generated by Applications which are installed at the source nodes. One should clearly define the details of the workloads in the simulation, e.g., flow size distribution, packet size, intervals and so forth.
    • Protocols: Each pair of communicating node will agree on a specific protocol. It is necessary to detail the protocol(s) for each communicating sessions.
    • Addressing: One needs to make sure that the addressing mechanism assigned to each nodes will not collide.
    • Routing: Typically, one just apply NS-3 built-in helpers such as Ipv4GlobalRoutingHelper.
    • Metrics: The whole point of the simulation is to evaluate the network performances, measured by the corresponding metrics. It is crucial to decide on the metrics of the network performances before the simulation.

    S2: Implementation and Analysis

    • Launcher program location: For a simple simulation program, one could simply place it at path {NS-3 tree prefix}/scratch/{name}.cc. For complicated ones, one could set up a folder and provides a .cc file inside with point of entry function (main). waf system (similar to GNU Make) will automatically compile/link the program. One could, e.g., modify {NS-3 tree prefix}/wscript to change the behaviors of the build system.
    • Network translation: The implementation is about translating the target network in the form of NS-3 models. In general, it is about create nodes => configure attributes of the add-ons (NetDevices, channel, Stacks, Applications, Addresses ...) => install add-ons on the nodes => configure tracing => trigger simulation engine.
    • Out-of-tree extension: What if the standard modules are not sufficient to build the simulation? One could add functions/attributes to existing NS-3 classes or build a new module.
    • Execution automation: One might need to run a series of experiments with different random seeds (for controllable reproduction), hyper-parameters. One could introduce NS-3 command line arguments and write a shell script to automate the tests.
    • Result analysis: Flow Monitor module could be used to aggregate flow-level statistics. Tracing system could enable flexible tracing sinks to collect simulation outputs. One could further derive custom metrics, e.g., p99 FCT for short flows, average FCT for large flows, with statistical post-processing. One might also visualize the animation of the simulation with NetAnim.