Creation of a simple ethernet network
Introduction
To perform a simulation with ns-3, you need to write a C++ program that describes the simulation to be performed. In this chapter, we will create such a simulation script able to simulate an Ethernet network composed of one switch to which three end stations are connected.
First simulation script
Let’s start by creating a script that runs a 10-second simulation. To do this, create a file called chapter1.cc in the scratch folder located in the ns-3 folder. In this file, copy the following lines:
#include "ns3/simulator.h"
using namespace ns3;
NS_LOG_COMPONENT_DEFINE("Chapter 1");
int
main(int argc, char* argv[])
{
//Enable logging
LogComponentEnable("Chapter 1", LOG_LEVEL_INFO);
//Execute the simulation
NS_LOG_INFO("Start of the simulation");
Simulator::Stop(Seconds(10));
Simulator::Run();
Simulator::Destroy();
NS_LOG_INFO("End of the simulation");
return 0;
}
To run it, simply execute the following command from the folder where ns-3 is installed.
./ns3 run scratch/chapter1.cc
Congratulations, you have run your first simulation with ns-3! However, this simulation does not simulate anything.
Topology description
Before simulating our Ethernet network, we must first describe it. To do this, let’s start by adding the necessary dependencies.
#include "ns3/simulator.h"
#include "ns3/core-module.h"
#include "ns3/node.h"
#include "ns3/drop-tail-queue.h"
#include "ns3/ethernet-net-device.h"
#include "ns3/ethernet-channel.h"
#include "ns3/switch-net-device.h"
[...]
Next, the topology is described as follows:
[...]
//Enable logging
LogComponentEnable("Chapter 1", LOG_LEVEL_INFO);
//Create four nodes
Ptr<Node> n0 = CreateObject<Node>();
Names::Add("ES1", n0);
Ptr<Node> n1 = CreateObject<Node>();
Names::Add("ES2", n1);
Ptr<Node> n2 = CreateObject<Node>();
Names::Add("ES3", n2);
Ptr<Node> n3 = CreateObject<Node>();
Names::Add("SW", n3);
//Create and add a netDevice to each end-station node
Ptr<EthernetNetDevice> net0 = CreateObject<EthernetNetDevice>();
n0->AddDevice(net0);
Names::Add("ES1#01", net0);
Ptr<EthernetNetDevice> net1 = CreateObject<EthernetNetDevice>();
n1->AddDevice(net1);
Names::Add("ES2#01", net1);
Ptr<EthernetNetDevice> net2 = CreateObject<EthernetNetDevice>();
n2->AddDevice(net2);
Names::Add("ES3#01", net2);
//Create and add a netDevice to each switch port
Ptr<EthernetNetDevice> swnet0 = CreateObject<EthernetNetDevice>();
n3->AddDevice(swnet0);
Names::Add("SW#01", swnet0);
Ptr<EthernetNetDevice> swnet1 = CreateObject<EthernetNetDevice>();
n3->AddDevice(swnet1);
Names::Add("SW#02", swnet1);
Ptr<EthernetNetDevice> swnet2 = CreateObject<EthernetNetDevice>();
n3->AddDevice(swnet2);
Names::Add("SW#03", swnet2);
//Create Ethernet Channels and connect switch to the end-stations
Ptr<EthernetChannel> channel0 = CreateObject<EthernetChannel>();
net0->Attach(channel0);
swnet0->Attach(channel0);
Ptr<EthernetChannel> channel1 = CreateObject<EthernetChannel>();
net1->Attach(channel1);
swnet1->Attach(channel1);
Ptr<EthernetChannel> channel2 = CreateObject<EthernetChannel>();
net2->Attach(channel2);
swnet2->Attach(channel2);
//Create and add a switch net device to the switch node
Ptr<SwitchNetDevice> sw = CreateObject<SwitchNetDevice>();
n3->AddDevice(sw);
sw->AddSwitchPort(swnet0);
sw->AddSwitchPort(swnet1);
sw->AddSwitchPort(swnet2);
//Allocate Mac addresses to the netDevices
net0->SetAddress(Mac48Address::Allocate());
net1->SetAddress(Mac48Address::Allocate());
net2->SetAddress(Mac48Address::Allocate());
sw->SetAddress(Mac48Address::Allocate());
//Create two output port FIFOs for each netDevice.
for (int i=0; i<2; i++){
net0->SetQueue(CreateObject<DropTailQueue<Packet>>());
net1->SetQueue(CreateObject<DropTailQueue<Packet>>());
net2->SetQueue(CreateObject<DropTailQueue<Packet>>());
swnet0->SetQueue(CreateObject<DropTailQueue<Packet>>());
swnet1->SetQueue(CreateObject<DropTailQueue<Packet>>());
swnet2->SetQueue(CreateObject<DropTailQueue<Packet>>());
}
[...]
In this code, the following six steps are performed:
- Creating nodes: In this example, four nodes are created.
- Creating NetDevices: A NetDevice is a network interface in ns-3 terminology. Here, one NetDevice is created and added to each end-station node, and three NetDevices are created and added to the switch node.
- Creating channels: A channel is a link in ns-3 terminology in the case of the wired network considered here. Thus, three channels are created and attached to different NetDevices to connect the three end stations to the switch.
- Creating SwitchNetDevices: The SwitchNetDevice is the object that will perform switching between the switch’s NetDevices. Here, a SwitchNetDevice object is created and attached to the switch node. The switch ports are also attached to it.
- Allocation of Mac addresses: For our example, four mac addresses are allocated using the iterator provided by ns-3.
- Creation of output port queues: For our example, two FIFOs are instantiated per NetDevices.
At this stage, it is possible to run a simulation, but the output will be the same since no traffic is simulated.
Traffic simulation
To send Ethernet frames in our network, we need to instantiate applications. To instantiate these applications, we must first add the following dependency.
#include “ns3/ethernet-generator.h”
This dependency is part of Eden-sim and is used to transmit Q-Tagged Ethernet frames.
Next, let’s add an application that sends a burst of two frames with a payload of 1400 bytes, a priority of 1, and a VLAN ID of 1 every 5 seconds. This application is hosted on node ES1. The frames are destined for ES3. Here is the code used to instantiate such an application:
[...]
swnet2->SetQueue(CreateObject<DropTailQueue<Packet>>());
}
//Application description
//ES1 -> ES3 with priority 1
Ptr<EthernetGenerator> app0 = CreateObject<EthernetGenerator>();
app0->Setup(net0);
app0->SetAttribute("Address", AddressValue(net2->GetAddress()));
app0->SetAttribute("BurstSize", UintegerValue(2));
app0->SetAttribute("PayloadSize", UintegerValue(1400));
app0->SetAttribute("Period", TimeValue(Seconds(5)));
app0->SetAttribute("VlanID", UintegerValue(1));
app0->SetAttribute("PCP", UintegerValue(1));
n0->AddApplication(app0);
app0->SetStartTime(Seconds(0));
app0->SetStopTime(Seconds(10));
[...]
With this simulation script complete, you can run a simulation… And you should see nothing new.
Indeed, there is no indication for ns-3 to produce any output. So our frames are sent in our simulated network, but we have no output to confirm it. To display logs about our frames in the console, let’s add a callback for the transmission and one for the reception.
[...]
NS_LOG_COMPONENT_DEFINE("Chapter 1");
//A callback to log the pkt emission
static void
MacTxCallback(std::string context, Ptr<const Packet> p)
{
NS_LOG_INFO((Simulator::Now()).As(Time::S) << " \t" << context << " : Pkt #" << p->GetUid() << " sent!");
}
//A callback to log the pkt reception
static void
MacRxCallback(std::string context, Ptr<const Packet> p)
{
NS_LOG_INFO((Simulator::Now()).As(Time::S) << " \t" << context << " : Pkt #" << p->GetUid() << " received!");
}
[...]
[...]
app0->SetStopTime(Seconds(10));
//Callback declarations
//Callback to display the packet sent log
std::string context = Names::FindName(n0) + ":" + Names::FindName(net0);
net0->TraceConnectWithoutContext("MacTx", MakeBoundCallback(&MacTxCallback, context));
//Callback to display the packet received log
context = Names::FindName(n2) + ":" + Names::FindName(net2);
net2->TraceConnectWithoutContext("MacRx", MakeBoundCallback(&MacRxCallback, context));
[...]
If you run the simulation again, you should get the following output.
$ ./ns3 run scratch/book.cc
[0/2] Re-checking globbed directories...
[2/2] Linking CXX executable ../build/scratch/ns3.40-book-default
Start of the simulation
+0s ES1:ES1#01 : Pkt #0 sent!
+0s ES1:ES1#01 : Pkt #1 sent!
+5s ES1:ES1#01 : Pkt #2 sent!
+5s ES1:ES1#01 : Pkt #3 sent!
End of the simulation
We can see that four frames are successfully sent by ES1 but are never received by ES3. This is because, after the first hop, the frames are received by the switch but it does not know which port to send them to. It is therefore necessary to add a static forwarding configuration to our switch.
Network configuration
Unlike plug-and-play switches, the switches simulated by Eden-sim are designed for critical embedded networks. To guarantee network determinism, these switches do not implement dynamic mechanisms such as mac learning. They must therefore be configured statically before the simulation begins.
Here, the only configuration missing to transmit frames to ES3 is the configuration of the static switching table. It is possible to add an entry to this table as follows:
[...]
swnet2->SetQueue(CreateObject<DropTailQueue<Packet>>());
}
//Add a forwarding table entry
sw->AddForwardingTableEntry(Mac48Address::ConvertFrom(net2->GetAddress()), 1, {swnet2});
[...]
After this configuration, running the simulation produces the following output, which finally indicates the transmission and reception of frames.
$ ./ns3 run scratch/book.cc
[0/2] Re-checking globbed directories...
[2/2] Linking CXX executable ../build/scratch/ns3.40-book-default
Start of the simulation
+0s ES1:ES1#01 : Pkt #0 sent!
+0s ES1:ES1#01 : Pkt #1 sent!
+2.293e-05s ES3:ES3#01 : Pkt #0 received!
+3.4466e-05s ES3:ES3#01 : Pkt #1 received!
+5s ES1:ES1#01 : Pkt #2 sent!
+5s ES1:ES1#01 : Pkt #3 sent!
+5.00002s ES3:ES3#01 : Pkt #2 received!
+5.00003s ES3:ES3#01 : Pkt #3 received!
End of the simulation
Final simulation script
Here is the simulation script you should have at the end of this chapter.
#include "ns3/simulator.h"
#include "ns3/core-module.h"
#include "ns3/node.h"
#include "ns3/drop-tail-queue.h"
#include "ns3/ethernet-net-device.h"
#include "ns3/ethernet-channel.h"
#include "ns3/switch-net-device.h"
#include "ns3/ethernet-generator.h"
using namespace ns3;
NS_LOG_COMPONENT_DEFINE("Chapter 1");
//A callback to log the pkt emission
static void
MacTxCallback(std::string context, Ptr<const Packet> p)
{
NS_LOG_INFO((Simulator::Now()).As(Time::S) << " \t" << context << " : Pkt #" << p->GetUid() << " sent!");
}
//A callback to log the pkt reception
static void
MacRxCallback(std::string context, Ptr<const Packet> p)
{
NS_LOG_INFO((Simulator::Now()).As(Time::S) << " \t" << context << " : Pkt #" << p->GetUid() << " received!");
}
int
main(int argc, char* argv[])
{
//Enable logging
LogComponentEnable("Chapter 1", LOG_LEVEL_INFO);
//Create four nodes
Ptr<Node> n0 = CreateObject<Node>();
Names::Add("ES1", n0);
Ptr<Node> n1 = CreateObject<Node>();
Names::Add("ES2", n1);
Ptr<Node> n2 = CreateObject<Node>();
Names::Add("ES3", n2);
Ptr<Node> n3 = CreateObject<Node>();
Names::Add("SW", n3);
//Create and add a netDevice to each end-station node
Ptr<EthernetNetDevice> net0 = CreateObject<EthernetNetDevice>();
n0->AddDevice(net0);
Names::Add("ES1#01", net0);
Ptr<EthernetNetDevice> net1 = CreateObject<EthernetNetDevice>();
n1->AddDevice(net1);
Names::Add("ES2#01", net1);
Ptr<EthernetNetDevice> net2 = CreateObject<EthernetNetDevice>();
n2->AddDevice(net2);
Names::Add("ES3#01", net2);
//Create and add a netDevice to each switch port
Ptr<EthernetNetDevice> swnet0 = CreateObject<EthernetNetDevice>();
n3->AddDevice(swnet0);
Names::Add("SW#01", swnet0);
Ptr<EthernetNetDevice> swnet1 = CreateObject<EthernetNetDevice>();
n3->AddDevice(swnet1);
Names::Add("SW#02", swnet1);
Ptr<EthernetNetDevice> swnet2 = CreateObject<EthernetNetDevice>();
n3->AddDevice(swnet2);
Names::Add("SW#03", swnet2);
//Create Ethernet Channels and connect switch to the end-stations
Ptr<EthernetChannel> channel0 = CreateObject<EthernetChannel>();
net0->Attach(channel0);
swnet0->Attach(channel0);
Ptr<EthernetChannel> channel1 = CreateObject<EthernetChannel>();
net1->Attach(channel1);
swnet1->Attach(channel1);
Ptr<EthernetChannel> channel2 = CreateObject<EthernetChannel>();
net2->Attach(channel2);
swnet2->Attach(channel2);
//Create and add a switch net device to the switch node
Ptr<SwitchNetDevice> sw = CreateObject<SwitchNetDevice>();
n3->AddDevice(sw);
sw->AddSwitchPort(swnet0);
sw->AddSwitchPort(swnet1);
sw->AddSwitchPort(swnet2);
//Allocate Mac addresses to the netDevices
net0->SetAddress(Mac48Address::Allocate());
net1->SetAddress(Mac48Address::Allocate());
net2->SetAddress(Mac48Address::Allocate());
sw->SetAddress(Mac48Address::Allocate());
//Create 2 output port FIFOs for each netDevice.
for (int i=0; i<2; i++){
net0->SetQueue(CreateObject<DropTailQueue<Packet>>());
net1->SetQueue(CreateObject<DropTailQueue<Packet>>());
net2->SetQueue(CreateObject<DropTailQueue<Packet>>());
swnet0->SetQueue(CreateObject<DropTailQueue<Packet>>());
swnet1->SetQueue(CreateObject<DropTailQueue<Packet>>());
swnet2->SetQueue(CreateObject<DropTailQueue<Packet>>());
}
//Add a forwarding table entry
sw->AddForwardingTableEntry(Mac48Address::ConvertFrom(net2->GetAddress()), 1, {swnet2});
//Application description
//ES1 -> ES3 with priority 1
Ptr<EthernetGenerator> app0 = CreateObject<EthernetGenerator>();
app0->Setup(net0);
app0->SetAttribute("Address", AddressValue(net2->GetAddress()));
app0->SetAttribute("BurstSize", UintegerValue(2));
app0->SetAttribute("PayloadSize", UintegerValue(1400));
app0->SetAttribute("Period", TimeValue(Seconds(5)));
app0->SetAttribute("VlanID", UintegerValue(1));
app0->SetAttribute("PCP", UintegerValue(1));
n0->AddApplication(app0);
app0->SetStartTime(Seconds(0));
app0->SetStopTime(Seconds(10));
//Callback declarations
//Callback to display the packet sent log
std::string context = Names::FindName(n0) + ":" + Names::FindName(net0);
net0->TraceConnectWithoutContext("MacTx", MakeBoundCallback(&MacTxCallback, context));
//Callback to display the packet received log
context = Names::FindName(n2) + ":" + Names::FindName(net2);
net2->TraceConnectWithoutContext("MacRx", MakeBoundCallback(&MacRxCallback, context));
//Execute the simulation
NS_LOG_INFO("Start of the simulation");
Simulator::Stop(Seconds(10));
Simulator::Run();
Simulator::Destroy();
NS_LOG_INFO("End of the simulation");
return 0;
}