int main(int argc, char* argv[])
{
uint16_t gNbNum = 2; //The number of gNbs in multiple-ue topology
uint16_t ueNumPergNb = 2; //The number of UE per gNb in multiple-ue topology
bool logging = false; //Enable logging
bool doubleOperationalBand = true; //If true, simulate two operational bands with one CC for each band,
uint32_t udpPacketSizeULL = 100; //packet size in bytes to be used by ultra low latency traffic
uint32_t udpPacketSizeBe = 1252; //Packet size in bytes to be used by best effort traffic
uint32_t lambdaULL = 10000; //Number of UDP packets in one second for ultra low latency traffic
uint32_t lambdaBe = 10000; //Number of UDP packets in one second for best effor traffic
Time simTime = MilliSeconds(100);
Time udpAppStartTime = MilliSeconds(40);
// NR parameters.
uint16_t numerologyBwp1 = 4; //The numerology to be used in bandwidth part 1 (SCS = 240 kHz) --Fastest
double centralFrequencyBand1 = 28e9; //The system frequency to be used in band 1
double bandwidthBand1 = 100e6; //The system bandwidth to be used in band 1
uint16_t numerologyBwp2 = 1; //The numerology to be used in bandwidth part 2 (SCS = 30 kHz)
double centralFrequencyBand2 = 28.2e9; //The system frequency to be used in band 2
double bandwidthBand2 = 100e6;//The system bandwidth to be used in band 2
double totalTxPower = 4; //total tx power
// Where we will store the output files.
std::string simTag = "default";
std::string outputDir = "./";
CommandLine cmd(__FILE__);
cmd.Parse(argc, argv);
NS_ABORT_IF(centralFrequencyBand1 > 100e9);
NS_ABORT_IF(centralFrequencyBand2 > 100e9);
if (logging)
{
LogComponentEnable("UdpClient", LOG_LEVEL_INFO);
LogComponentEnable("UdpServer", LOG_LEVEL_INFO);
LogComponentEnable("LtePdcp", LOG_LEVEL_INFO);
}
Config::SetDefault("ns3::LteRlcUm::MaxTxBufferSize", UintegerValue(999999999));
int64_t randomStream = 1;
GridScenarioHelper gridScenario;
gridScenario.SetRows(1);
gridScenario.SetColumns(gNbNum);
gridScenario.SetHorizontalBsDistance(30.0); //Distance between gnb
gridScenario.SetVerticalBsDistance(0); //Distance between gnb
gridScenario.SetBsHeight(1.5);
gridScenario.SetUtHeight(1.5);
// must be set before BS number
gridScenario.SetSectorization(GridScenarioHelper::SINGLE); //Sets the number of sectors of every site. Site Sectorization type = Site with a 360º-width sector.
gridScenario.SetBsNumber(gNbNum);
gridScenario.SetUtNumber(ueNumPergNb * gNbNum);
gridScenario.SetScenarioHeight(6); // Create a 3x3 scenario where the UE will
gridScenario.SetScenarioLength(6); // be distribuited.
randomStream += gridScenario.AssignStreams(randomStream); //gridScenario.AssignStreams(randomStream) can be zero. Hence, it is initialized to 1.
gridScenario.CreateScenario();
NodeContainer ueLowLatContainer;
NodeContainer ueVoiceContainer;
for (uint32_t j = 0; j < gridScenario.GetUserTerminals().GetN(); ++j)
{
Ptr<Node> ue = gridScenario.GetUserTerminals().Get(j);
NN++;
if (j % 2 == 0)
{
ueLowLatContainer.Add(ue); //Adding UE to Low latency container for every even UE
}
else
{
ueVoiceContainer.Add(ue); //Adding the odd numbered UE to voice container
}
}
Ptr<NrPointToPointEpcHelper> epcHelper = CreateObject<NrPointToPointEpcHelper>();
Ptr<IdealBeamformingHelper> idealBeamformingHelper = CreateObject<IdealBeamformingHelper>();
//Difference between real and ideal BF is provided in https://www.nsnam.org/wp-content/uploads/2021/wns3-session-3/wns3_2021_bojovic.pdf
Ptr<NrHelper> nrHelper = CreateObject<NrHelper>();
// Put the pointers inside nrHelper
nrHelper->SetBeamformingHelper(idealBeamformingHelper);
nrHelper->SetEpcHelper(epcHelper);
BandwidthPartInfoPtrVector allBwps;
CcBwpCreator ccBwpCreator;
const uint8_t numCcPerBand = 1; // in this example, both bands have a single CC
// Create the configuration for the CcBwpHelper. SimpleOperationBandConf creates
// a single BWP per CC
CcBwpCreator::SimpleOperationBandConf bandConf1(centralFrequencyBand1,
bandwidthBand1,
numCcPerBand,
BandwidthPartInfo::UMi_StreetCanyon);
CcBwpCreator::SimpleOperationBandConf bandConf2(centralFrequencyBand2,
bandwidthBand2,
numCcPerBand,
BandwidthPartInfo::UMi_StreetCanyon);
// By using the configuration created, it is time to make the operation bands
OperationBandInfo band1 = ccBwpCreator.CreateOperationBandContiguousCc(bandConf1);
OperationBandInfo band2 = ccBwpCreator.CreateOperationBandContiguousCc(bandConf2);
/*
* The configured spectrum division is:
* ------------Band1--------------|--------------Band2-----------------
* ------------CC1----------------|--------------CC2-------------------
* ------------BWP1---------------|--------------BWP2------------------
*/
Config::SetDefault("ns3::ThreeGppChannelModel::UpdatePeriod", TimeValue(MilliSeconds(0)));
nrHelper->SetChannelConditionModelAttribute("UpdatePeriod", TimeValue(MilliSeconds(0)));
nrHelper->SetPathlossAttribute("ShadowingEnabled", BooleanValue(false));
//Shadowing is the effect that the received signal power fluctuates due to objects
//obstructing the propagation path between transmitter and receiver.
nrHelper->InitializeOperationBand(&band1);
double x = pow(10, totalTxPower / 10);
double totalBandwidth = bandwidthBand1;
/*
* if not single band simulation, initialize and setup power in the second band
*/
if (doubleOperationalBand)
{
// Initialize channel and pathloss, plus other things inside band2
//Band 1 is already initialized outside the if else statement
nrHelper->InitializeOperationBand(&band2);
totalBandwidth += bandwidthBand2;
allBwps = CcBwpCreator::GetAllBwps({band1, band2}); //Get all the BWP pointers
//from the specified vector of operation bands.
}
else
{
allBwps = CcBwpCreator::GetAllBwps({band1});
}
/*
* allBwps contains all the spectrum configuration needed for the nrHelper.
*
* Now, we can setup the attributes. We can have three kind of attributes:
* (i) parameters that are valid for all the bandwidth parts and applies to
* all nodes, (ii) parameters that are valid for all the bandwidth parts
* and applies to some node only, and (iii) parameters that are different for
* every bandwidth parts. The approach is:
*
* - for (i): Configure the attribute through the helper, and then install;
* - for (ii): Configure the attribute through the helper, and then install
* for the first set of nodes. Then, change the attribute through the helper,
* and install again;
* - for (iii): Install, and then configure the attributes by retrieving
* the pointer needed, and calling "SetAttribute" on top of such pointer.
*
*/
Packet::EnableChecking();
Packet::EnablePrinting();
/*
* Case (i): Attributes valid for all the nodes
*/
// Beamforming method
idealBeamformingHelper->SetAttribute("BeamformingMethod",
TypeIdValue(DirectPathBeamforming::GetTypeId()));
// Core latency
epcHelper->SetAttribute("S1uLinkDelay", TimeValue(MilliSeconds(0)));
// Antennas for all the UEs - 2 x 4 antennas at UE
nrHelper->SetUeAntennaAttribute("NumRows", UintegerValue(2));
nrHelper->SetUeAntennaAttribute("NumColumns", UintegerValue(4));
nrHelper->SetUeAntennaAttribute("AntennaElement",
PointerValue(CreateObject<IsotropicAntennaModel>()));
// Antennas for all the gNbs - 4 x 8 antennas at gNB
nrHelper->SetGnbAntennaAttribute("NumRows", UintegerValue(4));
nrHelper->SetGnbAntennaAttribute("NumColumns", UintegerValue(8));
nrHelper->SetGnbAntennaAttribute("AntennaElement",
PointerValue(CreateObject<IsotropicAntennaModel>()));
//Isotrophic antenna is ideal antenna whose gain is the same in all directions
uint32_t bwpIdForLowLat = 0;
uint32_t bwpIdForVoice = 0;
if (doubleOperationalBand)
{
bwpIdForVoice = 1;
bwpIdForLowLat = 0;
}
// gNb routing between Bearer and bandwidh part
// Set EPS bearer for gNB
nrHelper->SetGnbBwpManagerAlgorithmAttribute("NGBR_LOW_LAT_EMBB",
UintegerValue(bwpIdForLowLat));
nrHelper->SetGnbBwpManagerAlgorithmAttribute("GBR_CONV_VOICE", UintegerValue(bwpIdForVoice));
// Ue routing between Bearer and bandwidth part
nrHelper->SetUeBwpManagerAlgorithmAttribute("NGBR_LOW_LAT_EMBB", UintegerValue(bwpIdForLowLat));
nrHelper->SetUeBwpManagerAlgorithmAttribute("GBR_CONV_VOICE", UintegerValue(bwpIdForVoice));
NetDeviceContainer enbNetDev =
nrHelper->InstallGnbDevice(gridScenario.GetBaseStations(), allBwps);
NN++;
NetDeviceContainer ueLowLatNetDev = nrHelper->InstallUeDevice(ueLowLatContainer, allBwps);
NetDeviceContainer ueVoiceNetDev = nrHelper->InstallUeDevice(ueVoiceContainer, allBwps);
randomStream += nrHelper->AssignStreams(enbNetDev, randomStream);
randomStream += nrHelper->AssignStreams(ueLowLatNetDev, randomStream);
randomStream += nrHelper->AssignStreams(ueVoiceNetDev, randomStream);
/*
* Case (iii): Go node for node and change the attributes we have to setup
* per-node.
*/
///How to change the attributes --> See below
// Get the first netdevice (enbNetDev.Get (0)) and the first bandwidth part (0)
// and set the attribute.
nrHelper->GetGnbPhy(enbNetDev.Get(0), 0)
->SetAttribute("Numerology", UintegerValue(numerologyBwp1));
nrHelper->GetGnbPhy(enbNetDev.Get(0), 0)
->SetAttribute("TxPower", DoubleValue(10 * log10((bandwidthBand1 / totalBandwidth) * x)));
if (doubleOperationalBand)
{
// Get the first netdevice (enbNetDev.Get (0)) and the second bandwidth part (1)
// and set the attribute.
//This gets skipped if we dont have 2 operational bands
nrHelper->GetGnbPhy(enbNetDev.Get(0), 1)
->SetAttribute("Numerology", UintegerValue(numerologyBwp2));
nrHelper->GetGnbPhy(enbNetDev.Get(0), 1)
->SetTxPower(10 * log10((bandwidthBand2 / totalBandwidth) * x));
}
// When all the configuration is done, explicitly call UpdateConfig ()
for (auto it = enbNetDev.Begin(); it != enbNetDev.End(); ++it)
{
DynamicCast<NrGnbNetDevice>(*it)->UpdateConfig();
}
for (auto it = ueLowLatNetDev.Begin(); it != ueLowLatNetDev.End(); ++it)
{
DynamicCast<NrUeNetDevice>(*it)->UpdateConfig();
}
for (auto it = ueVoiceNetDev.Begin(); it != ueVoiceNetDev.End(); ++it)
{
DynamicCast<NrUeNetDevice>(*it)->UpdateConfig();
}
// From here, it is standard NS3. In the future, we will create helpers
// for this part as well.
// create the internet and install the IP stack on the UEs
// get SGW/PGW and create a single RemoteHost
Ptr<Node> pgw = epcHelper->GetPgwNode(); //Connect the EPC and PGW
NN++;
// Create a single RemoteHost. This needs to be connect to P-GW at later stage after setting up.
NodeContainer remoteHostContainer;
remoteHostContainer.Create(1);
Ptr<Node> remoteHost = remoteHostContainer.Get(0);
NN++;
//aggregate IP/TCP/UDP functionality to existing Nodes.This helper enables tracing of events in the
//internet stack associated with a node
//Setting up the remote host in this code.
InternetStackHelper internet;
internet.Install(remoteHostContainer); //This works only if the NodeContainer has IPv4 object attached to it. So, we do that next.
// connect a remoteHost to pgw. Setup routing too
//// Create the internet. Set up P2P connection attributes
PointToPointHelper p2ph;
p2ph.SetDeviceAttribute("DataRate", DataRateValue(DataRate("100Gb/s")));
p2ph.SetDeviceAttribute("Mtu", UintegerValue(2500));
p2ph.SetChannelAttribute("Delay", TimeValue(Seconds(0.01)));
//Connect remote host to the PGW using P2P wireless backhaul
NetDeviceContainer internetDevices = p2ph.Install(pgw, remoteHost);
//Assign IPv4 address to PGW and remote host
Ipv4AddressHelper ipv4h;
Ipv4StaticRoutingHelper ipv4RoutingHelper;
ipv4h.SetBase("1.0.0.0", "255.0.0.0");
Ipv4InterfaceContainer internetIpIfaces = ipv4h.Assign(internetDevices);//Assign IP addresses
//What does this do? Creating Routing table where the remote host routing table has the address of the EPC.
Ptr<Ipv4StaticRouting> remoteHostStaticRouting =
ipv4RoutingHelper.GetStaticRouting(remoteHost->GetObject<Ipv4>());
remoteHostStaticRouting->AddNetworkRouteTo(Ipv4Address("7.0.0.0"), Ipv4Mask("255.0.0.0"), 1);
internet.Install(gridScenario.GetUserTerminals());
//Assign separate IP address for LLC services and voice communications to make sure they are segregated
Ipv4InterfaceContainer ueLowLatIpIface =
epcHelper->AssignUeIpv4Address(NetDeviceContainer(ueLowLatNetDev));
Ipv4InterfaceContainer ueVoiceIpIface =
epcHelper->AssignUeIpv4Address(NetDeviceContainer(ueVoiceNetDev));
// Set the default gateway for the UEs
// Set IP addresses for UEs. We had done this previously for the remote host.
for (uint32_t j = 0; j < gridScenario.GetUserTerminals().GetN(); ++j)
{
Ptr<Ipv4StaticRouting> ueStaticRouting = ipv4RoutingHelper.GetStaticRouting(
gridScenario.GetUserTerminals().Get(j)->GetObject<Ipv4>());
ueStaticRouting->SetDefaultRoute(epcHelper->GetUeDefaultGatewayAddress(), 1);
//This method tells the routing system what to do in the case where a specific route to a
//destination is not found.
//The system forwards packets to the specified node in the hope that it knows better
//how to route the packet.
}
// attach UEs to the closest eNB
// nrHelper->AttachToClosestEnb(ueLowLatNetDev, enbNetDev);
// nrHelper->AttachToClosestEnb(ueVoiceNetDev, enbNetDev);
nrHelper->AttachToEnb(ueLowLatNetDev.Get(0), enbNetDev.Get(0));
nrHelper->AttachToEnb(ueVoiceNetDev.Get(0), enbNetDev.Get(0));
nrHelper->AttachToEnb(ueLowLatNetDev.Get(1), enbNetDev.Get(1));
nrHelper->AttachToEnb(ueVoiceNetDev.Get(1), enbNetDev.Get(1));
/*
* Traffic part. Install two kind of traffic: low-latency and voice, each
* identified by a particular source port.
*/
uint16_t dlPortLowLat = 1234;
uint16_t dlPortVoice = 1235;
ApplicationContainer serverApps;
// The sink will always listen to the specified ports
UdpServerHelper dlPacketSinkLowLat(dlPortLowLat);
UdpServerHelper dlPacketSinkVoice(dlPortVoice);
// The server, that is the application which is listening, is installed in the UE
//The Install function will create a UDP server application for
//each node in the Nodecontainer
//The add function appends the content of the another ApplicationContainer
// at the end of the serverApps container
serverApps.Add(dlPacketSinkLowLat.Install(ueLowLatContainer));
serverApps.Add(dlPacketSinkVoice.Install(ueVoiceContainer));
/*
* Configure attributes for the different generators, using user-provided
* parameters for generating a CBR traffic
*
* Low-Latency configuration and object creation:
* We had initially created UDP application server
* Now we create UDP application client
*/
UdpClientHelper dlClientLowLat;
dlClientLowLat.SetAttribute("RemotePort", UintegerValue(dlPortLowLat));
dlClientLowLat.SetAttribute("MaxPackets", UintegerValue(0xFFFFFFFF));
dlClientLowLat.SetAttribute("PacketSize", UintegerValue(udpPacketSizeULL));
dlClientLowLat.SetAttribute("Interval", TimeValue(Seconds(1.0 / lambdaULL)));
// The bearer that will carry low latency traffic
EpsBearer lowLatBearer(EpsBearer::NGBR_LOW_LAT_EMBB);
// The filter for the low-latency traffic
Ptr<EpcTft> lowLatTft = Create<EpcTft>();
EpcTft::PacketFilter dlpfLowLat;
dlpfLowLat.localPortStart = dlPortLowLat; //start of the port number range of the UE
dlpfLowLat.localPortEnd = dlPortLowLat; //end of the port number range of the UE
lowLatTft->Add(dlpfLowLat);
// Voice configuration and object creation:
UdpClientHelper dlClientVoice;
dlClientVoice.SetAttribute("RemotePort", UintegerValue(dlPortVoice));
dlClientVoice.SetAttribute("MaxPackets", UintegerValue(0xFFFFFFFF));
dlClientVoice.SetAttribute("PacketSize", UintegerValue(udpPacketSizeBe));
dlClientVoice.SetAttribute("Interval", TimeValue(Seconds(1.0 / lambdaBe)));
// The bearer that will carry voice traffic
EpsBearer voiceBearer(EpsBearer::GBR_CONV_VOICE);
// The filter for the voice traffic
Ptr<EpcTft> voiceTft = Create<EpcTft>();
EpcTft::PacketFilter dlpfVoice;
dlpfVoice.localPortStart = dlPortVoice;
dlpfVoice.localPortEnd = dlPortVoice;
voiceTft->Add(dlpfVoice);
/*
* Let's install the applications!
* Create UDP application at remote host location
* Adds to the client Apps container
*/
ApplicationContainer clientApps;
for (uint32_t i = 0; i < ueLowLatContainer.GetN(); ++i)
{
Ptr<Node> ue = ueLowLatContainer.Get(i);
NN++;
Ptr<NetDevice> ueDevice = ueLowLatNetDev.Get(i);
Address ueAddress = ueLowLatIpIface.GetAddress(i);
cout<<"IP Address for UE " <<i<<" for Low Latency: "<<ueAddress<<endl;
// The client, who is transmitting, is installed in the remote host,
// with destination address set to the address of the UE
dlClientLowLat.SetAttribute("RemoteAddress", AddressValue(ueAddress));
clientApps.Add(dlClientLowLat.Install(remoteHost));
// Activate a dedicated bearer for the traffic type
nrHelper->ActivateDedicatedEpsBearer(ueDevice, lowLatBearer, lowLatTft);
}
//Repeat this process for voice communication
for (uint32_t i = 0; i < ueVoiceContainer.GetN(); ++i)
{
Ptr<Node> ue = ueVoiceContainer.Get(i);
NN++;
Ptr<NetDevice> ueDevice = ueVoiceNetDev.Get(i);
Address ueAddress = ueVoiceIpIface.GetAddress(i);
// The client, who is transmitting, is installed in the remote host,
// with destination address set to the address of the UE
dlClientVoice.SetAttribute("RemoteAddress", AddressValue(ueAddress));
clientApps.Add(dlClientVoice.Install(remoteHost));
// Activate a dedicated bearer for the traffic type
nrHelper->ActivateDedicatedEpsBearer(ueDevice, voiceBearer, voiceTft);
}
// start UDP server and client apps
serverApps.Start(udpAppStartTime);
clientApps.Start(udpAppStartTime);
serverApps.Stop(simTime);
clientApps.Stop(simTime);
// enable the traces provided by the nr module
//nrHelper->EnableTraces();
FlowMonitorHelper flowmonHelper;
NodeContainer endpointNodes;
endpointNodes.Add(remoteHost); //LL+VC
endpointNodes.Add(gridScenario.GetUserTerminals());//LL+VC+UEs
endpointNodes.Add(gridScenario.GetBaseStations());//LL+VC+UEs+gNB
Ptr<ns3::FlowMonitor> monitor = flowmonHelper.Install(endpointNodes);
monitor->SetAttribute("DelayBinWidth", DoubleValue(0.001));
monitor->SetAttribute("JitterBinWidth", DoubleValue(0.001));
monitor->SetAttribute("PacketSizeBinWidth", DoubleValue(20));
//Set up Animation
AnimationInterface anim ("animation.xml");
Simulator::Stop(simTime);
Simulator::Run();
/*
* To check what was installed in the memory, i.e., BWPs of eNb Device, and its configuration.
* Example is: Node 1 -> Device 0 -> BandwidthPartMap -> {0,1} BWPs -> NrGnbPhy -> Numerology,
GtkConfigStore config;
config.ConfigureAttributes ();
*/
// Print per-flow statistics
monitor->CheckForLostPackets();
Ptr<Ipv4FlowClassifier> classifier =
DynamicCast<Ipv4FlowClassifier>(flowmonHelper.GetClassifier());
FlowMonitor::FlowStatsContainer stats = monitor->GetFlowStats();
double averageFlowThroughput = 0.0;
double averageFlowDelay = 0.0;
std::ofstream outFile;
std::string filename = outputDir + "/" + simTag;
outFile.open(filename.c_str(), std::ofstream::out | std::ofstream::trunc);
if (!outFile.is_open())
{
std::cerr << "Can't open file " << filename << std::endl;
return 1;
}
outFile.setf(std::ios_base::fixed);
double flowDuration = (simTime - udpAppStartTime).GetSeconds();
for (std::map<FlowId, FlowMonitor::FlowStats>::const_iterator i = stats.begin();
i != stats.end();
++i)
{
Ipv4FlowClassifier::FiveTuple t = classifier->FindFlow(i->first);
//FiveTuple is Structure to classify a packet.- Destination addr,
//port, protocol, source addr, source port
std::stringstream protoStream;
protoStream << (uint16_t)t.protocol; //Insert the name of protocol in the object protostream
if (t.protocol == 6)
{
protoStream.str("TCP");
}
if (t.protocol == 17)
{
protoStream.str("UDP");
}
outFile << "Flow " << i->first << " (" << t.sourceAddress << ":" << t.sourcePort << " -> "
<< t.destinationAddress << ":" << t.destinationPort << ") protocol used: "
<< protoStream.str() << "\n";
outFile << " Tx Packets: " << i->second.txPackets << "\n";
outFile << " Tx Bytes: " << i->second.txBytes << "\n";
outFile << " TxOffered: " << i->second.txBytes * 8.0 / flowDuration / 1000.0 / 1000.0
<< " Mbps\n"; //Multiplied by 8 x 10^-6/time for Mbps
outFile << " Rx Bytes: " << i->second.rxBytes << "\n";
outFile << " Rx Packets: " << i->second.rxPackets << "\n";
if (i->second.rxPackets > 0)
{
// Measure the duration of the flow from receiver's perspective
averageFlowThroughput += i->second.rxBytes * 8.0 / flowDuration / 1000 / 1000; ////Multiplied by 8 x 10^-6/time for Mbps
averageFlowDelay += 1000 * i->second.delaySum.GetSeconds() / i->second.rxPackets;
outFile << " Throughput: " << i->second.rxBytes * 8.0 / flowDuration / 1000 / 1000
<< " Mbps\n";
outFile << " Mean delay: "
<< 1000 * i->second.delaySum.GetSeconds() / i->second.rxPackets << " ms\n";
// outFile << " Mean upt: " << i->second.uptSum / i->second.rxPackets / 1000/1000 << "
// Mbps \n";
outFile << " Mean jitter: "
<< 1000 * i->second.jitterSum.GetSeconds() / i->second.rxPackets << " ms\n";
}
else
{
outFile << " Throughput: 0 Mbps\n";
outFile << " Mean delay: 0 ms\n";
outFile << " Mean jitter: 0 ms\n";
}
}
double meanFlowThroughput = averageFlowThroughput / stats.size();
double meanFlowDelay = averageFlowDelay / stats.size();
outFile << "\n\n Mean flow throughput: " << meanFlowThroughput << "\n";
outFile << " Mean flow delay: " << meanFlowDelay << "\n";
outFile.close();
std::ifstream f(filename.c_str());
if (f.is_open())
{
std::cout << f.rdbuf();
}
Simulator::Destroy();
}