diff --git a/examples/wireless/CMakeLists.txt b/examples/wireless/CMakeLists.txt index b6da631c6..996df4ff9 100644 --- a/examples/wireless/CMakeLists.txt +++ b/examples/wireless/CMakeLists.txt @@ -298,3 +298,14 @@ build_example( LIBRARIES_TO_LINK ${libwifi} ${libapplications} ) + +build_example( + NAME wifi-multicast + SOURCE_FILES wifi-multicast.cc + LIBRARIES_TO_LINK + ${libwifi} + ${libcore} + ${libnetwork} + ${libapplications} + ${libinternet} +) diff --git a/examples/wireless/wifi-multicast.cc b/examples/wireless/wifi-multicast.cc new file mode 100644 index 000000000..0f83fc836 --- /dev/null +++ b/examples/wireless/wifi-multicast.cc @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2023 DERONNE SOFTWARE ENGINEERING + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Sébastien Deronne + */ + +#include "ns3/boolean.h" +#include "ns3/command-line.h" +#include "ns3/config.h" +#include "ns3/data-rate.h" +#include "ns3/double.h" +#include "ns3/enum.h" +#include "ns3/error-model.h" +#include "ns3/inet-socket-address.h" +#include "ns3/internet-stack-helper.h" +#include "ns3/ipv4-address-helper.h" +#include "ns3/ipv4-l3-protocol.h" +#include "ns3/ipv4-list-routing-helper.h" +#include "ns3/ipv4-static-routing-helper.h" +#include "ns3/ipv4-static-routing.h" +#include "ns3/log.h" +#include "ns3/mobility-helper.h" +#include "ns3/mobility-model.h" +#include "ns3/names.h" +#include "ns3/node.h" +#include "ns3/on-off-helper.h" +#include "ns3/packet-sink-helper.h" +#include "ns3/packet-sink.h" +#include "ns3/simulator.h" +#include "ns3/socket.h" +#include "ns3/ssid.h" +#include "ns3/string.h" +#include "ns3/test.h" +#include "ns3/trace-helper.h" +#include "ns3/udp-socket-factory.h" +#include "ns3/udp-socket.h" +#include "ns3/uinteger.h" +#include "ns3/wifi-mac.h" +#include "ns3/wifi-net-device.h" +#include "ns3/wifi-psdu.h" +#include "ns3/yans-wifi-channel.h" +#include "ns3/yans-wifi-helper.h" + +#include +#include +#include + +// This is an example to verify performance of 802.11aa groupcast with retries (GCR) in comparison +// with the usual NoAck/NoRetry retransmission policy. In the latter, a groupcast frame is +// transmitted only once, whereas GCR allows to retransmit groupcast frames to improve reliability. +// +// The simulation considers a single 802.11ax AP and a configurable number of GCR-capable STAs in an +// infrastructure network. Multicast traffic is generated from the AP to all the non-AP STAs and +// artificial errors can be introduced to mimic interference on the wireless channel. +// +// There are a number of command-line options available to control the scenario under test. The list +// of available command-line options can be listed with the following command: +// ./ns3 run "wifi-multicast --help" +// +// The main command-line options are: +// --gcrRetransmissionPolicy: control the retransmission policy by selecting "NoAckNoRetry" for +// no retransmission, "GcrUr" for GCR with unsolicited retries or "GcrBlockAck" for GCR Block Ack +// --nStations: control the number of GCR-capable STAs associated to the AP +// --accessCategory: control the access category to use for the multicast traffic +// --multicastFrameErrorRate: set the artificial frame error rate for the groupcast traffic +// --nRetriesGcrUr: if GCR-UR is selected, this parameter controls the maximum number of retries +// --gcrProtection: select the protection mechanism for groupcast frames if GCR-UR or GCR-BA is +// used, either "Rts-Cts" or "Cts-To-Self" can be selected +// +// Example usage for NoAckNoRetry and a frame error rate of 20%: +// ./ns3 run "wifi-multicast --gcrRetransmissionPolicy=NoAckNoRetry --multicastFrameErrorRate=0.2" +// which outputs: +// Node TX packets TX bytes RX packets RX bytes Throughput (Mbit/s) +// AP 10 10000 0 0 11.1111 +// STA1 0 0 10 10000 10.992 +// +// Example usage for GCR-UR with up to 2 retries and the same frame error rate: +// ./ns3 run "wifi-multicast --gcrRetransmissionPolicy=GcrUr --nRetriesGcrUr=2 +// --multicastFrameErrorRate=0.2" +// which outputs: +// Node TX packets TX bytes RX packets RX bytes Throughput (Mbit/s) +// AP 10 10000 0 0 11.1111 +// STA1 0 0 10 10000 10.992 +// +// Example usage for GCR-BA with 4 STAs and the same frame error rate: +// ./ns3 run "wifi-multicast --gcrRetransmissionPolicy=GcrBlockAck --nStations=4" +// which outputs: +// Node TX packets TX bytes RX packets RX bytes Throughput (Mbit/s) +// AP 10 10000 0 0 11.1111 +// STA1 0 0 10 10000 8.26959 +// STA2 0 0 10 10000 8.26959 +// STA3 0 0 10 10000 8.26959 +// STA4 0 0 10 10000 8.26959 + +using namespace ns3; + +NS_LOG_COMPONENT_DEFINE("WifiMulticast"); + +uint64_t g_txBytes; //!< Number of generated bytes +Time g_firstTx; //!< Time at which first TX packet is generated +Time g_lastTx; //!< Time at which last TX packet is generated +Time g_lastRx; //!< Time at which last RX packet is received + +/** + * Parse context strings of the form "/NodeList/x/DeviceList/x/..." to extract the NodeId integer + * + * @param context The context to parse. + * @return the NodeId + */ +uint32_t +ContextToNodeId(const std::string& context) +{ + std::string sub = context.substr(10); + uint32_t pos = sub.find("/Device"); + return std::stoi(sub.substr(0, pos)); +} + +/** + * Socket sent packet. + * + * @param context The context. + * @param p The packet. + */ +void +SocketTxPacket(std::string context, Ptr p) +{ + if (g_txBytes == 0) + { + g_firstTx = Simulator::Now(); + } + g_txBytes += p->GetSize(); + g_lastTx = Simulator::Now(); +} + +/** + * + * @param context The context. + * @param p The packet. + * @param from sender address. + */ +void +SocketRxPacket(std::string context, Ptr p, const Address& from) +{ + g_lastRx = Simulator::Now(); +} + +/** + * Callback when a frame is transmitted. + * @param rxErrorModel the post reception error model on the receiver + * @param ranVar the random variable to determine whether the packet shall be corrupted + * @param errorRate the configured corruption error rate for multicast frames + * @param context the context + * @param psduMap the PSDU map + * @param txVector the TX vector + * @param txPowerW the tx power in Watts + */ +void +TxCallback(Ptr rxErrorModel, + Ptr ranVar, + double errorRate, + std::string context, + WifiConstPsduMap psduMap, + WifiTxVector txVector, + double txPowerW) +{ + auto psdu = psduMap.begin()->second; + if (psdu->GetHeader(0).GetAddr1().IsGroup() && !psdu->GetHeader(0).GetAddr1().IsBroadcast() && + psdu->GetHeader(0).IsQosData()) + { + NS_LOG_INFO("AP tx multicast: PSDU=" << *psdu << " TXVECTOR=" << txVector); + if (ranVar->GetValue() < errorRate) + { + auto uid = psdu->GetPayload(0)->GetUid(); + NS_LOG_INFO("Corrupt multicast frame with UID=" << uid); + rxErrorModel->SetList({uid}); + } + else + { + rxErrorModel->SetList({}); + } + } +} + +/** + * Callback when a frame is successfully received. + * + * @param context the context + * @param p the packet + * @param snr the SNR (in linear scale) + * @param mode the mode used to transmit the packet + * @param preamble the preamble + */ +void +RxCallback(std::string context, + Ptr p, + double snr, + WifiMode mode, + WifiPreamble preamble) +{ + Ptr packet = p->Copy(); + WifiMacHeader hdr; + packet->RemoveHeader(hdr); + if (hdr.GetAddr1().IsGroup() && !hdr.GetAddr1().IsBroadcast() && hdr.IsQosData()) + { + std::stringstream ss; + hdr.Print(ss); + NS_LOG_INFO("STA" << ContextToNodeId(context) << " rx multicast: " << ss.str()); + } +} + +int +main(int argc, char* argv[]) +{ + bool logging{false}; + bool verbose{false}; + bool pcap{false}; + std::size_t nStations{1}; + Time simulationTime{"10s"}; + uint32_t payloadSize{1000}; // bytes + DataRate dataRate{"10Mb/s"}; + uint32_t maxPackets{10}; + uint32_t rtsThreshold{std::numeric_limits::max()}; + std::string targetAddr{"239.192.100.1"}; + std::string accessCategory{"AC_BE"}; + std::string gcrRetransmissionPolicy{"NoAckNoRetry"}; + std::string rateManager{"Constant"}; + uint16_t constantRateMcs{11}; + uint16_t nRetriesGcrUr{7}; + std::string gcrProtection{"Rts-Cts"}; + double multicastFrameErrorRate{0.0}; + uint16_t maxAmpduLength{0}; + + CommandLine cmd(__FILE__); + cmd.AddValue("logging", "turn on example log components", logging); + cmd.AddValue("verbose", "turn on all wifi log components", verbose); + cmd.AddValue("pcap", "turn on pcap file output", pcap); + cmd.AddValue("nStations", "number of non-AP stations", nStations); + cmd.AddValue("simulationTime", "Simulation time", simulationTime); + cmd.AddValue("payloadSize", "The application payload size in bytes", payloadSize); + cmd.AddValue( + "maxPackets", + "The maximum number of packets to be generated by the application (0 for no limit)", + maxPackets); + cmd.AddValue("dataRate", "The application data rate", dataRate); + cmd.AddValue("rtsThreshold", "RTS threshold", rtsThreshold); + cmd.AddValue("rateManager", + "The rate adaptation manager to use (Constant, Ideal, MinstrelHt)", + rateManager); + cmd.AddValue("mcs", + "The MCS to use if Constant rate adaptation manager is used", + constantRateMcs); + cmd.AddValue("targetAddress", "multicast target address", targetAddr); + cmd.AddValue("accessCategory", + "select the multicast traffic access category (AC_BE, AC_BK, AC_VI, AC_VO)", + accessCategory); + cmd.AddValue( + "gcrRetransmissionPolicy", + "GCR retransmission policy for groupcast frames (NoAckNoRetry, GcrUr, GcrBlockAck)", + gcrRetransmissionPolicy); + cmd.AddValue("nRetriesGcrUr", + "number of retries per groupcast frame when GCR-UR retransmission policy is used", + nRetriesGcrUr); + cmd.AddValue("gcrProtection", + "protection to use for GCR (Rts-Cts or Cts-To-Self)", + gcrProtection); + cmd.AddValue("multicastFrameErrorRate", + "artificial error rate for multicast frame", + multicastFrameErrorRate); + cmd.AddValue("maxAmpduLength", "maximum length in bytes of an A-MPDU", maxAmpduLength); + cmd.Parse(argc, argv); + + Config::SetDefault("ns3::WifiMac::RobustAVStreamingSupported", BooleanValue(true)); + + // create nodes + NodeContainer wifiApNode; + wifiApNode.Create(1); + NodeContainer wifiStaNodes; + wifiStaNodes.Create(nStations); + + // configure PHY and MAC + WifiHelper wifi; + if (verbose) + { + WifiHelper::EnableLogComponents(); + } + if (logging) + { + LogComponentEnable("WifiMulticast", LOG_LEVEL_ALL); + } + wifi.SetStandard(WIFI_STANDARD_80211ax); + + YansWifiPhyHelper wifiPhy; + wifiPhy.SetPcapDataLinkType(WifiPhyHelper::DLT_IEEE802_11_RADIO); + + auto wifiChannel = YansWifiChannelHelper::Default(); + wifiChannel.SetPropagationDelay("ns3::ConstantSpeedPropagationDelayModel"); + wifiPhy.SetChannel(wifiChannel.Create()); + + WifiMacHelper wifiMac; + if (rateManager == "Constant") + { + wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager", + "DataMode", + StringValue("HeMcs" + std::to_string(constantRateMcs)), + "RtsCtsThreshold", + UintegerValue(rtsThreshold)); + } + else + { + wifi.SetRemoteStationManager("ns3::" + rateManager + "RateWifiManager", + "RtsCtsThreshold", + UintegerValue(rtsThreshold)); + } + + if (gcrRetransmissionPolicy != "NoAckNoRetry") + { + std::string retransmissionPolicyStr; + if (gcrRetransmissionPolicy == "GcrUr") + { + retransmissionPolicyStr = "GCR_UR"; + } + else if (gcrRetransmissionPolicy == "GcrBlockAck") + { + retransmissionPolicyStr = "GCR_BA"; + } + else + { + std::cout << "Wrong retransmission policy!" << std::endl; + return 0; + } + wifiMac.SetGcrManager("ns3::WifiDefaultGcrManager", + "RetransmissionPolicy", + StringValue(retransmissionPolicyStr), + "UnsolicitedRetryLimit", + UintegerValue(nRetriesGcrUr), + "GcrProtectionMode", + StringValue(gcrProtection)); + } + + Ssid ssid = Ssid("wifi-multicast"); + + // setup AP + wifiMac.SetType("ns3::ApWifiMac", "Ssid", SsidValue(ssid)); + auto apDevice = wifi.Install(wifiPhy, wifiMac, wifiApNode); + + // setup STAs + wifiMac.SetType("ns3::StaWifiMac", "Ssid", SsidValue(ssid)); + auto staDevices = wifi.Install(wifiPhy, wifiMac, wifiStaNodes); + + auto rxErrorModel = CreateObject(); + auto ranVar = CreateObject(); + ranVar->SetStream(1); + Config::Connect("/NodeList/0/DeviceList/*/$ns3::WifiNetDevice/Phys/0/PhyTxPsduBegin", + MakeCallback(TxCallback).Bind(rxErrorModel, ranVar, multicastFrameErrorRate)); + for (std::size_t i = 0; i < nStations; ++i) + { + auto wifiMac = DynamicCast(staDevices.Get(i))->GetMac(); + wifiMac->GetWifiPhy(0)->SetPostReceptionErrorModel(rxErrorModel); + } + Config::Connect("/NodeList/*/DeviceList/*/$ns3::WifiNetDevice/Phy/$ns3::WifiPhy/State/RxOk", + MakeCallback(RxCallback)); + + // mobility + MobilityHelper mobility; + Ptr positionAlloc = CreateObject(); + positionAlloc->Add(Vector(0.0, 0.0, 0.0)); + for (std::size_t i = 0; i < nStations; ++i) + { + positionAlloc->Add(Vector(i, 0.0, 0.0)); + } + mobility.SetPositionAllocator(positionAlloc); + mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel"); + mobility.Install(wifiApNode); + mobility.Install(wifiStaNodes); + + // setup static routes to facilitate multicast flood + Ipv4ListRoutingHelper listRouting; + Ipv4StaticRoutingHelper staticRouting; + listRouting.Add(staticRouting, 0); + + // configure IP stack + InternetStackHelper internet; + internet.SetIpv6StackInstall(false); + internet.SetIpv4ArpJitter(true); + internet.SetRoutingHelper(listRouting); + internet.Install(wifiApNode); + internet.Install(wifiStaNodes); + + Ipv4AddressHelper ipv4address; + ipv4address.SetBase("10.0.0.0", "255.255.255.0"); + auto apNodeInterface = ipv4address.Assign(apDevice); + auto staNodeInterfaces = ipv4address.Assign(staDevices); + + // add static route in AP + auto ipv4 = wifiApNode.Get(0)->GetObject(); + auto routing = staticRouting.GetStaticRouting(ipv4); + routing->AddHostRouteTo(targetAddr.c_str(), + ipv4->GetInterfaceForDevice(wifiApNode.Get(0)->GetDevice(0)), + 0); + + uint8_t tosValue; + std::string maxAmpduSizeAttribute; + if (accessCategory == "AC_BE") + { + tosValue = 0x70; + maxAmpduSizeAttribute = "BE_MaxAmpduSize"; + } + else if (accessCategory == "AC_BK") + { + tosValue = 0x28; + maxAmpduSizeAttribute = "BK_MaxAmpduSize"; + } + else if (accessCategory == "AC_VI") + { + tosValue = 0xb8; + maxAmpduSizeAttribute = "VI_MaxAmpduSize"; + } + else if (accessCategory == "AC_VO") + { + tosValue = 0xc0; + maxAmpduSizeAttribute = "VO_MaxAmpduSize"; + } + else + { + NS_ABORT_MSG("Invalid access category"); + } + auto apWifiMac = DynamicCast(apDevice.Get(0))->GetMac(); + apWifiMac->SetAttribute(maxAmpduSizeAttribute, UintegerValue(maxAmpduLength)); + + // sinks + InetSocketAddress sinkSocket(Ipv4Address::GetAny(), 90); + PacketSinkHelper sinkHelper("ns3::UdpSocketFactory", sinkSocket); + auto sinks = sinkHelper.Install(wifiStaNodes); + sinks.Start(Seconds(0)); + sinks.Stop(simulationTime + Seconds(2.0)); + + // source + InetSocketAddress sourceSocket(targetAddr.c_str(), 90); + OnOffHelper onoffHelper("ns3::UdpSocketFactory", sourceSocket); + onoffHelper.SetAttribute("DataRate", DataRateValue(dataRate)); + onoffHelper.SetAttribute("PacketSize", UintegerValue(payloadSize)); + onoffHelper.SetAttribute("MaxBytes", UintegerValue(maxPackets * payloadSize)); + onoffHelper.SetAttribute("Tos", UintegerValue(tosValue)); + onoffHelper.SetAttribute("OnTime", StringValue("ns3::ConstantRandomVariable[Constant=1]")); + onoffHelper.SetAttribute("OffTime", StringValue("ns3::ConstantRandomVariable[Constant=0]")); + auto source = onoffHelper.Install(wifiApNode); + source.Start(Seconds(1)); + source.Stop(simulationTime + Seconds(1.0)); + + // pcap + if (pcap) + { + wifiPhy.EnablePcap("wifi-multicast-AP", apDevice.Get(0)); + for (std::size_t i = 0; i < nStations; ++i) + { + wifiPhy.EnablePcap("wifi-multicast-STA" + std::to_string(i + 1), staDevices.Get(i)); + } + } + + g_txBytes = 0; + + Config::Connect("/NodeList/*/$ns3::Node/ApplicationList/*/$ns3::OnOffApplication/Tx", + MakeCallback(&SocketTxPacket)); + + Config::Connect("/NodeList/*/ApplicationList/*/$ns3::PacketSink/Rx", + MakeCallback(&SocketRxPacket)); + + // run simulation + Simulator::Stop(simulationTime + Seconds(2.0)); + Simulator::Run(); + + // check results + std::cout << "Node\t\t\tTX packets\t\tTX bytes\t\tRX packets\t\tRX bytes\t\tThroughput (Mbit/s)" + << std::endl; + const auto txRate = + (g_lastTx - g_firstTx).IsStrictlyPositive() + ? static_cast(g_txBytes * 8) / ((g_lastTx - g_firstTx).GetMicroSeconds()) + : 0.0; // Mbit/s + const auto txPackets = g_txBytes / payloadSize; + std::cout << "AP" + << "\t\t\t" << txPackets << "\t\t\t" << g_txBytes << "\t\t\t0\t\t\t0\t\t\t" << txRate + << "" << std::endl; + for (std::size_t i = 0; i < nStations; ++i) + { + const auto rxBytes = sinks.Get(0)->GetObject()->GetTotalRx(); + const auto rxPackets = rxBytes / payloadSize; + const auto throughput = + (g_lastRx - g_firstTx).IsStrictlyPositive() + ? static_cast(rxBytes * 8) / ((g_lastRx - g_firstTx).GetMicroSeconds()) + : 0.0; // Mbit/s + std::cout << "STA" << i + 1 << "\t\t\t0\t\t\t0\t\t\t" << rxPackets << "\t\t\t" << rxBytes + << "\t\t\t" << throughput << "" << std::endl; + } + + Simulator::Destroy(); + return 0; +}