diff --git a/src/simu5g/corenetwork/gtp/GtpUser.cc b/src/simu5g/corenetwork/gtp/GtpUser.cc index 3503fc4d7f..58e7bb37da 100644 --- a/src/simu5g/corenetwork/gtp/GtpUser.cc +++ b/src/simu5g/corenetwork/gtp/GtpUser.cc @@ -307,6 +307,7 @@ void GtpUser::handleFromTrafficFlowFilter(Packet *datagram) gtpPacket->insertAtFront(header); auto data = datagram->peekData(); gtpPacket->insertAtBack(data); + gtpPacket->copyRegionTags(*datagram, datagram->getFrontOffset(), header->getChunkLength(), datagram->getDataLength()); delete datagram; @@ -346,6 +347,7 @@ void GtpUser::handleFromUdp(Packet *pkt) auto originalPacket = new Packet(pkt->getName()); auto gtpUserMsg = pkt->popAtFront(); originalPacket->insertAtBack(pkt->peekData()); + originalPacket->copyRegionTags(*pkt, pkt->getFrontOffset(), b(0), pkt->getDataLength()); // The tunnel names the session the T-PDU belongs to, and so what it carries (a // base station knows every tunnel ending here); a relay between a MEC host's UPF @@ -464,6 +466,7 @@ void GtpUser::tunnelDownlink(Packet *datagram, const FTeid& tunnel, Qfi qfi) gtpMsg->insertAtFront(header); auto data = datagram->peekData(); gtpMsg->insertAtBack(data); + gtpMsg->copyRegionTags(*datagram, datagram->getFrontOffset(), header->getChunkLength(), datagram->getDataLength()); delete datagram; socket_.sendTo(gtpMsg, tunnel.address, tunnelPeerPort_); diff --git a/tests/module/GtpRegionTags.test b/tests/module/GtpRegionTags.test new file mode 100644 index 0000000000..7878776fa7 --- /dev/null +++ b/tests/module/GtpRegionTags.test @@ -0,0 +1,206 @@ +%description: +The tunnels of the user plane carry the region tags of a packet with its data, uplink +and downlink: a base station tunnels the packets of its UEs, a UPF those of the data +network, and each end decapsulates what it receives. GtpUser creates a new Packet for +every GTP-U message, so it has to copy the region tags of the data it carries. + +The test uses an Ethernet PDU session. ue[0] and dnStation, a host on the UPF's N6 +Ethernet LAN, each send one frame. RegionTagProbe adds a region tag (CreationTimeTag) +to the whole frame where the frame is about to enter the tunnel, and counts the bytes +of the frame that carry it where the frame has left the tunnel: + - downlink: tagged as the Ethernet layer of the UPF's bridge has finished the frame, + counted as the PDCP entity of the gNB receives it from the upper layer; + - uplink: tagged as the PDCP entity of the gNB passes the frame up, counted as the + Ethernet layer of the UPF's bridge receives it. +The stack around the tunnel does not keep the tag on every byte (the uplink frame loses +two bytes in the base station), so the test expects some bytes tagged, not all of them. + +%#-------------------------------------------------------------------------------------------------------------- +%file: test.ned +import inet.node.ethernet.Eth1G; +import inet.node.inet.StandardHost; +import simu5g.test.moduletest.lib.NrSessionTestNetwork; + +simple RegionTagProbe +{ + @class(RegionTagProbe); +} + +network GtpRegionTagsNetwork extends NrSessionTestNetwork +{ + submodules: + dnStation: StandardHost { + @display("p=650,75"); + } + probe: RegionTagProbe; + connections: + upf.dnEthg <--> Eth1G <--> dnStation.ethg++; +} + +%#-------------------------------------------------------------------------------------------------------------- +%file: RegionTagProbe.cc +#include +#include + +#include +#include + +using namespace omnetpp; +using namespace inet; + +namespace GtpRegionTags { // the test's NED namespace (opp_test) + +class RegionTagProbe : public cSimpleModule, public cListener +{ + protected: + simsignal_t pushedIn_ = registerSignal("packetPushedIn"); + simsignal_t pushedOut_ = registerSignal("packetPushedOut"); + simsignal_t pdcpReceivedFromUpper_ = registerSignal("receivedPacketFromUpperLayer"); + simsignal_t pdcpSentToUpper_ = registerSignal("sentPacketToUpperLayer"); + + void initialize() override + { + for (auto signal : {pushedIn_, pushedOut_, pdcpReceivedFromUpper_, pdcpSentToUpper_}) + getSystemModule()->subscribe(signal, this); + } + + void handleMessage(cMessage *msg) override { throw cRuntimeError("no messages"); } + + static bool contains(cComponent *component, const char *part) + { + return component->getFullPath().find(part) != std::string::npos; + } + + static bool endsWith(cComponent *component, const char *suffix) + { + std::string path = component->getFullPath(); + std::string s = suffix; + return path.size() >= s.size() && path.compare(path.size() - s.size(), s.size(), s) == 0; + } + + // the number of bytes of the packet that carry a CreationTimeTag of the probe + static long taggedBytes(Packet *packet) + { + long bytes = 0; + packet->mapAllRegionTags(b(0), packet->getTotalLength(), [&] (b offset, b length, const Ptr& tag) { + if (tag->getCreationTime() == 0.123) + bytes += B(length).get(); + }); + return bytes; + } + + // the frame is about to enter the tunnel: the whole frame gets a region tag + void tag(Packet *packet, const char *direction) + { + packet->addRegionTag(b(0), packet->getTotalLength())->setCreationTime(0.123); + std::cout << "RegionTagProbe: " << direction << " tagged " << taggedBytes(packet) << " of " << B(packet->getTotalLength()).get() << " bytes" << std::endl; + } + + // the frame has left the tunnel + void count(Packet *packet, const char *direction) + { + std::cout << "RegionTagProbe: " << direction << " arrived with " << taggedBytes(packet) << " of " << B(packet->getTotalLength()).get() << " bytes tagged" << std::endl; + } + + void receiveSignal(cComponent *, simsignal_t, intval_t, cObject *) override {} + void receiveSignal(cComponent *, simsignal_t, uintval_t, cObject *) override {} + void receiveSignal(cComponent *, simsignal_t, bool, cObject *) override {} + void receiveSignal(cComponent *, simsignal_t, double, cObject *) override {} + void receiveSignal(cComponent *, simsignal_t, const SimTime&, cObject *) override {} + void receiveSignal(cComponent *, simsignal_t, const char *, cObject *) override {} + + void receiveSignal(cComponent *source, simsignal_t signal, cObject *obj, cObject *details) override + { + auto *packet = dynamic_cast(obj); + if (packet == nullptr) + return; + std::string name = packet->getName(); + if (name == "dl-probe") { + if (signal == pushedOut_ && endsWith(source, "upf.ethBridge.ethernet.fcsInserter")) + tag(packet, "downlink"); + else if (signal == pdcpReceivedFromUpper_ && contains(source, "gnb.cellularNic.pdcp-") && endsWith(source, ".tx")) + count(packet, "downlink"); + } + else if (name == "ul-probe") { + if (signal == pdcpSentToUpper_ && contains(source, "gnb.cellularNic.pdcp-") && endsWith(source, ".rx")) + tag(packet, "uplink"); + else if (signal == pushedIn_ && endsWith(source, "upf.ethBridge.ethernet.fcsChecker")) + count(packet, "uplink"); + } + } +}; + +Define_Module(RegionTagProbe); + +} + +%#-------------------------------------------------------------------------------------------------------------- +%inifile: omnetpp.ini +[General] +network = GtpRegionTagsNetwork +cmdenv-express-mode = false +**.cmdenv-log-level = off + +*.ue[*].servingNodeId = 0 +*.ue[*].nrServingNodeId = 1 + +*.upf.hasEthernetBridge = true +*.ue[*].sessionType = "Ethernet" +*.ue[*].hasIpv4 = false +*.numUe = 1 +*.ue[0].cellularNic.address = "02-00-00-00-0E-01" + +*.dnStation.hasIpv4 = false +*.dnStation.eth[*].typename = "LayeredEthernetInterface" +*.dnStation.eth[0].address = "02-00-00-00-0D-01" +*.dnStation.eth[*].bitrate = 1Gbps +*.dnStation.ethernet.typename = "EthernetLayer" +*.dnStation.ethernet.hasSocketSupport = true +*.dnStation.ieee8021q.typename = "Ieee8021qProtocol" +*.dnStation.llc.typename = "" + +*.bearerConfigurator.staticDrbs = [ + {coreNetwork: "5gc", ue: "ue[0]", drbId: 1, isDefault: true, lcg: 3, rlcMode: "UM"} + ] + +*.ue[0].numApps = 2 +*.ue[0].app[0].typename = "EthernetSourceApp" +*.ue[0].app[0].source.packetNameFormat = "ul-probe" +*.ue[0].app[1].typename = "EthernetSinkApp" +*.ue[0].app[1].io.localAddress = "02-00-00-00-0E-01" +*.ue[0].app[1].io.steal = true +*.ue[0].app[1].io.remoteAddress = "" + +*.dnStation.numApps = 2 +*.dnStation.app[0].typename = "EthernetSourceApp" +*.dnStation.app[0].source.packetNameFormat = "dl-probe" +*.dnStation.app[1].typename = "EthernetSinkApp" +*.dnStation.app[1].io.localAddress = "02-00-00-00-0D-01" +*.dnStation.app[1].io.steal = true +*.dnStation.app[1].io.remoteAddress = "" + +*.ue[*].app[*].io.remoteAddress = "02-00-00-00-0D-01" +*.dnStation.app[*].io.remoteAddress = "02-00-00-00-0E-01" +*.ue[*].app[*].io.interface = "cellular" +*.dnStation.app[*].io.interface = "eth0" +**.app[*].source.productionInterval = 10s +**.app[*].source.initialProductionOffset = 0.1s +**.app[*].source.packetLength = 100B + +include ../../lib/defaults.ini + +%#-------------------------------------------------------------------------------------------------------------- +%exitcode: 0 +%#-------------------------------------------------------------------------------------------------------------- +%contains-regex: stdout +RegionTagProbe: downlink tagged \d+ of \d+ bytes +%#-------------------------------------------------------------------------------------------------------------- +%contains-regex: stdout +RegionTagProbe: uplink tagged \d+ of \d+ bytes +%#-------------------------------------------------------------------------------------------------------------- +%contains-regex: stdout +RegionTagProbe: downlink arrived with [1-9]\d* of \d+ bytes tagged +%#-------------------------------------------------------------------------------------------------------------- +%contains-regex: stdout +RegionTagProbe: uplink arrived with [1-9]\d* of \d+ bytes tagged +%#-------------------------------------------------------------------------------------------------------------- diff --git a/tests/module/GtpRegionTagsIp.test b/tests/module/GtpRegionTagsIp.test new file mode 100644 index 0000000000..05e577ddc2 --- /dev/null +++ b/tests/module/GtpRegionTagsIp.test @@ -0,0 +1,160 @@ +%description: +The tunnels of the user plane carry the region tags of a packet with its data in an +IP PDU session, too, including the UPF's relay from one tunnel into another. ue[0], +served by gnb, sends a UDP datagram to ue[1], served by gnb2. The datagram goes up the +tunnel of ue[0]'s session (gnb tunnels it, the UPF decapsulates it) and down the tunnel +of ue[1]'s session (the UPF tunnels it again, gnb2 decapsulates it). GtpUser creates a +new Packet for every GTP-U message, so it has to copy the region tags of the data it +carries at each of these four steps. + +RegionTagProbe adds a region tag (CreationTimeTag) to the whole datagram as the PDCP +entity of gnb passes it up, and counts the bytes of the datagram that carry the tag as +the PDCP entity of gnb2 receives it from the upper layer. + +%#-------------------------------------------------------------------------------------------------------------- +%file: test.ned +import inet.node.ethernet.Eth10G; +import simu5g.nodes.gNodeB; +import simu5g.test.moduletest.lib.NrSessionTestNetwork; + +simple RegionTagProbe +{ + @class(RegionTagProbe); +} + +network GtpRegionTagsIpNetwork extends NrSessionTestNetwork +{ + submodules: + gnb2: gNodeB; + probe: RegionTagProbe; + connections: + upf.pppg++ <--> Eth10G <--> gnb2.ppp; +} + +%#-------------------------------------------------------------------------------------------------------------- +%file: RegionTagProbe.cc +#include +#include + +#include +#include + +using namespace omnetpp; +using namespace inet; + +namespace GtpRegionTagsIp { // the test's NED namespace (opp_test) + +class RegionTagProbe : public cSimpleModule, public cListener +{ + protected: + simsignal_t pdcpReceivedFromUpper_ = registerSignal("receivedPacketFromUpperLayer"); + simsignal_t pdcpSentToUpper_ = registerSignal("sentPacketToUpperLayer"); + + void initialize() override + { + for (auto signal : {pdcpReceivedFromUpper_, pdcpSentToUpper_}) + getSystemModule()->subscribe(signal, this); + } + + void handleMessage(cMessage *msg) override { throw cRuntimeError("no messages"); } + + static bool contains(cComponent *component, const char *part) + { + return component->getFullPath().find(part) != std::string::npos; + } + + static bool endsWith(cComponent *component, const char *suffix) + { + std::string path = component->getFullPath(); + std::string s = suffix; + return path.size() >= s.size() && path.compare(path.size() - s.size(), s.size(), s) == 0; + } + + // the number of bytes of the packet that carry a CreationTimeTag of the probe + static long taggedBytes(Packet *packet) + { + long bytes = 0; + packet->mapAllRegionTags(b(0), packet->getTotalLength(), [&] (b offset, b length, const Ptr& tag) { + if (tag->getCreationTime() == 0.123) + bytes += B(length).get(); + }); + return bytes; + } + + // the frame is about to enter the tunnel: the whole frame gets a region tag + void tag(Packet *packet, const char *direction) + { + packet->addRegionTag(b(0), packet->getTotalLength())->setCreationTime(0.123); + std::cout << "RegionTagProbe: " << direction << " tagged " << taggedBytes(packet) << " of " << B(packet->getTotalLength()).get() << " bytes" << std::endl; + } + + // the frame has left the tunnel + void count(Packet *packet, const char *direction) + { + std::cout << "RegionTagProbe: " << direction << " arrived with " << taggedBytes(packet) << " of " << B(packet->getTotalLength()).get() << " bytes tagged" << std::endl; + } + + void receiveSignal(cComponent *, simsignal_t, intval_t, cObject *) override {} + void receiveSignal(cComponent *, simsignal_t, uintval_t, cObject *) override {} + void receiveSignal(cComponent *, simsignal_t, bool, cObject *) override {} + void receiveSignal(cComponent *, simsignal_t, double, cObject *) override {} + void receiveSignal(cComponent *, simsignal_t, const SimTime&, cObject *) override {} + void receiveSignal(cComponent *, simsignal_t, const char *, cObject *) override {} + + void receiveSignal(cComponent *source, simsignal_t signal, cObject *obj, cObject *details) override + { + auto *packet = dynamic_cast(obj); + if (packet == nullptr || std::string(packet->getName()) != "ue2ue-probe" || !contains(source, "cellularNic.pdcp-")) + return; + if (signal == pdcpSentToUpper_ && contains(source, ".gnb.") && endsWith(source, ".rx")) + tag(packet, "uplink"); + else if (signal == pdcpReceivedFromUpper_ && contains(source, ".gnb2.") && endsWith(source, ".tx")) + count(packet, "downlink"); + } +}; + +Define_Module(RegionTagProbe); + +} + +%#-------------------------------------------------------------------------------------------------------------- +%inifile: omnetpp.ini +[General] +network = GtpRegionTagsIpNetwork +cmdenv-express-mode = false +**.cmdenv-log-level = off + +*.numUe = 2 + +# ue[0] is served by gnb, ue[1] by gnb2: a datagram from one to the other goes through the UPF +*.ue[*].servingNodeId = 0 +*.ue[0].nrServingNodeId = 1 +*.ue[1].nrServingNodeId = 2 +*.gnb2.mobility.initialX = 850m +*.gnb2.mobility.initialY = 300m +*.ue[1].mobility.initialX = 850m + +*.ue[0].numApps = 1 +*.ue[0].app[0].typename = "UdpSourceApp" +*.ue[0].app[0].io.destAddress = "ue[1]" +*.ue[0].app[0].io.destPort = 1000 +*.ue[0].app[0].source.packetNameFormat = "ue2ue-probe" +*.ue[0].app[0].source.packetLength = 100B +*.ue[0].app[0].source.productionInterval = 10s +*.ue[0].app[0].source.initialProductionOffset = 0.1s + +*.ue[1].numApps = 1 +*.ue[1].app[0].typename = "UdpSinkApp" +*.ue[1].app[0].io.localPort = 1000 + +include ../../lib/defaults.ini + +%#-------------------------------------------------------------------------------------------------------------- +%exitcode: 0 +%#-------------------------------------------------------------------------------------------------------------- +%contains-regex: stdout +RegionTagProbe: uplink tagged \d+ of \d+ bytes +%#-------------------------------------------------------------------------------------------------------------- +%contains-regex: stdout +RegionTagProbe: downlink arrived with [1-9]\d* of \d+ bytes tagged +%#--------------------------------------------------------------------------------------------------------------