Visualizing NS3 Simulations: A Step-by-Step Guide with NetAnim

Fathan PranayaFathan Pranaya
6 min read

In this post, we'll walk through a practical example of how to visualize a TCP star-server topology using NS3 and NetAnim. We'll set up a network with one central server and multiple clients, run the simulation, and then visualize it.

The Scenario: TCP Star Server

We'll use an example script that creates a star network topology. In this setup, several client nodes connect to a single central server node. The clients will send TCP traffic to the server.

The Simulation Code

Here is the complete C++ code for our tcp-star-server.cc simulation. It defines the network topology, installs the necessary protocols, sets up the applications, and most importantly, includes the logic to generate the animation file.

C++

/*
 * SPDX-License-Identifier: GPL-2.0-only
 */

// Default Network topology, 9 nodes in a star
/*
          n2 n3 n4
           \ | /
            \|/
     n1---n0---n5
            /| \
           / | \
          n8 n7 n6
*/
// - CBR Traffic goes from the star "arms" to the "hub"
// - Tracing of queues and packet receptions to file
//   "tcp-star-server.tr"
// - pcap traces also generated in the following files
//   "tcp-star-server-$n-$i.pcap" where n and i represent node and interface
//   numbers respectively
// Usage examples for things you might want to tweak:
//       ./ns3 run "tcp-star-server"
//       ./ns3 run "tcp-star-server --nNodes=25"
//       ./ns3 run "tcp-star-server --ns3::OnOffApplication::DataRate=10000"
//       ./ns3 run "tcp-star-server --ns3::OnOffApplication::PacketSize=500"
// See the ns-3 tutorial for more info on the command line:
// https://www.nsnam.org/docs/tutorial/html/index.html

#include "ns3/applications-module.h"
#include "ns3/core-module.h"
#include "ns3/internet-module.h"
#include "ns3/ipv4-global-routing-helper.h"
#include "ns3/network-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/netanim-module.h"
#include "ns3/mobility-module.h"

#include <cassert>
#include <fstream>
#include <iostream>
#include <string>

using namespace ns3;

NS_LOG_COMPONENT_DEFINE("TcpServer");

int
main(int argc, char* argv[])
{
    // Users may find it convenient to turn on explicit debugging
    // for selected modules; the below lines suggest how to do this

    // LogComponentEnable ("TcpServer", LOG_LEVEL_INFO);
    // LogComponentEnable ("TcpL4Protocol", LOG_LEVEL_ALL);
    // LogComponentEnable ("TcpSocketImpl", LOG_LEVEL_ALL);
    // LogComponentEnable ("PacketSink", LOG_LEVEL_ALL);

    // Set up some default values for the simulation.
    Config::SetDefault("ns3::OnOffApplication::PacketSize", UintegerValue(250));
    Config::SetDefault("ns3::OnOffApplication::DataRate", StringValue("5kb/s"));
    uint32_t N = 9; // number of nodes in the star

    // Allow the user to override any of the defaults and the above
    // Config::SetDefault()s at run-time, via command-line arguments
    CommandLine cmd(__FILE__);
    cmd.AddValue("nNodes", "Number of nodes to place in the star", N);
    cmd.Parse(argc, argv);

    // Here, we will create N nodes in a star.
    NS_LOG_INFO("Create nodes.");
    NodeContainer serverNode;
    NodeContainer clientNodes;
    serverNode.Create(1);
    clientNodes.Create(N - 1);
    NodeContainer allNodes = NodeContainer(serverNode, clientNodes);

    // Install network stacks on the nodes
    InternetStackHelper internet;
    internet.Install(allNodes);

    // Set constant positions for all nodes
    MobilityHelper mobility;
    mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
    mobility.Install(allNodes);

    // Collect an adjacency list of nodes for the p2p topology
    std::vector<NodeContainer> nodeAdjacencyList(N - 1);
    for (uint32_t i = 0; i < nodeAdjacencyList.size(); ++i)
    {
        nodeAdjacencyList[i] = NodeContainer(serverNode, clientNodes.Get(i));
    }

    // We create the channels first without any IP addressing information
    NS_LOG_INFO("Create channels.");
    PointToPointHelper p2p;
    p2p.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
    p2p.SetChannelAttribute("Delay", StringValue("2ms"));
    std::vector<NetDeviceContainer> deviceAdjacencyList(N - 1);
    for (uint32_t i = 0; i < deviceAdjacencyList.size(); ++i)
    {
        deviceAdjacencyList[i] = p2p.Install(nodeAdjacencyList[i]);
    }

    // Later, we add IP addresses.
    NS_LOG_INFO("Assign IP Addresses.");
    Ipv4AddressHelper ipv4;
    std::vector<Ipv4InterfaceContainer> interfaceAdjacencyList(N - 1);
    for (uint32_t i = 0; i < interfaceAdjacencyList.size(); ++i)
    {
        std::ostringstream subnet;
        subnet << "10.1." << i + 1 << ".0";
        ipv4.SetBase(subnet.str().c_str(), "255.255.255.0");
        interfaceAdjacencyList[i] = ipv4.Assign(deviceAdjacencyList[i]);
    }

    // Turn on global static routing
    Ipv4GlobalRoutingHelper::PopulateRoutingTables();

    // Create a packet sink on the star "hub" to receive these packets
    uint16_t port = 50000;
    Address sinkLocalAddress(InetSocketAddress(Ipv4Address::GetAny(), port));
    PacketSinkHelper sinkHelper("ns3::TcpSocketFactory", sinkLocalAddress);
    ApplicationContainer sinkApp = sinkHelper.Install(serverNode);
    sinkApp.Start(Seconds(1));
    sinkApp.Stop(Seconds(10));

    // Create the OnOff applications to send TCP to the server
    OnOffHelper clientHelper("ns3::TcpSocketFactory", Address());
    clientHelper.SetAttribute("OnTime", StringValue("ns3::ConstantRandomVariable[Constant=1]"));
    clientHelper.SetAttribute("OffTime", StringValue("ns3::ConstantRandomVariable[Constant=0]"));

    // normally wouldn't need a loop here but the server IP address is different
    // on each p2p subnet
    ApplicationContainer clientApps;
    for (uint32_t i = 0; i < clientNodes.GetN(); ++i)
    {
        AddressValue remoteAddress(
            InetSocketAddress(interfaceAdjacencyList[i].GetAddress(0), port));
        clientHelper.SetAttribute("Remote", remoteAddress);
        clientApps.Add(clientHelper.Install(clientNodes.Get(i)));
    }
    clientApps.Start(Seconds(1));
    clientApps.Stop(Seconds(10));

    // NetAnim output
    AnimationInterface anim("tcp-star-server.xml");
    anim.EnablePacketMetadata(true);
    anim.UpdateNodeDescription(serverNode.Get(0), "Server");
    anim.UpdateNodeColor(serverNode.Get(0), 0, 255, 0); // Green for server
    anim.SetConstantPosition(serverNode.Get(0), 30, 30); // Center position for server

    for (uint32_t i = 0; i < N - 1; ++i)
    {
        anim.UpdateNodeDescription(clientNodes.Get(i), "Client " + std::to_string(i + 1));
        anim.UpdateNodeColor(clientNodes.Get(i), 255, 0, 0); // Red for clients

        // Arrange clients in a circular pattern around the server
        double angle = 2 * M_PI * i / (N - 1);
        double x = 30 + 30 * cos(angle); // Radius of 30 units
        double y = 30 + 30 * sin(angle);
        anim.SetConstantPosition(clientNodes.Get(i), x, y);
    }

    NS_LOG_INFO("Run Simulation.");
    Simulator::Run();
    Simulator::Destroy();
    NS_LOG_INFO("Done.");

    return 0;
}

Explaining the Magic: The AnimationInterface

The key to the visualization lies within the // NetAnim output section of the code. Let's break it down:

  • #include "ns3/netanim-module.h": This line includes the necessary header file to use the NetAnim features.

  • AnimationInterface anim("tcp-star-server.xml");: This is the crucial first step. It creates an AnimationInterface object named anim and tells it to write all the animation data to a file named tcp-star-server.xml. This XML file contains a time-stamped log of all network events, such as node creation, packet transmission, and node movement.

  • anim.UpdateNodeDescription(...): This function sets a display name for a node in the animation. We're naming the central node "Server" and the others "Client 1", "Client 2", and so on.

  • anim.UpdateNodeColor(...): To easily distinguish between the server and clients, we assign them different colors. We've made the server green (RGB: 0, 255, 0) and all the clients red (RGB: 255, 0, 0).

  • anim.SetConstantPosition(...): Since our nodes are stationary, we explicitly set their positions on the animation canvas.

    • The server is placed at coordinates (30, 30).

    • The client nodes are placed in a circle around the server using some simple trigonometry (cos and sin). This ensures a clean, easy-to-understand star layout, regardless of the number of clients.

Building and Running the Simulation

  1. Save the Code: Save the code above into a file named tcp-star-server.cc inside your ns-3-dev/scratchdirectory.

  2. Build NS3: Open your terminal, navigate to your ns-3-dev directory, and run the build command:

    Bash

     ./ns3 build
    
  3. Run the Simulation: Now, run the simulation. We'll use the --nNodes command-line argument to specify that we want 20 nodes (1 server and 19 clients).

    Bash

     ./ns3 run "scratch/tcp-star-server --nNodes=20"
    

    After the simulation finishes, you will find a new file named tcp-star-server.xml in your ns-3-dev directory. This is the animation file we need.

Visualizing with NetAnim

  1. Launch NetAnim: Navigate to your NetAnim directory (usually netanim-3.108 or similar within your NS3 installation) and launch the application.

    Bash

     ./NetAnim
    
  2. Open the XML File: Once NetAnim is open, click the "Open" button or go to File > Open. Navigate to your ns-3-dev directory and select the tcp-star-server.xml file you just created.

  3. You can now use the timeline slider at the top to play the simulation. You will see packets flowing from the red client nodes to the central green server node, providing a clear visual representation of the network traffic.

0
Subscribe to my newsletter

Read articles from Fathan Pranaya directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Fathan Pranaya
Fathan Pranaya