From d13745fb4d64a3dbeec438c0287033f4f15ebaeb Mon Sep 17 00:00:00 2001 From: tabgab Date: Sat, 7 Nov 2026 12:35:31 +0200 Subject: [PATCH] gtpUser: fix: a GTP-U tunnel keeps the region tags of the packet it carries GtpUser creates a new Packet for every GTP-U message and copies the data of the original packet into it with insertAtBack(peekData()). That copies the chunks, so the tags of a chunk survive, but the region tags of the Packet, which are not part of its chunks, were lost at each end of a tunnel: a CreationTimeTag or a ResidenceTimeTag set before the gNB or the UPF was gone after the other end. In INET the region tags of a packet travel with its data through every encapsulation. The three places that build a Packet from another one now copy the region tags of the data: the encapsulation of a packet of the traffic flow filter, the decapsulation of a GTP-U message, and the encapsulation of a packet into a downlink tunnel. The tests tag a frame of an Ethernet PDU session before it enters the tunnel, uplink and downlink, and a datagram that the UPF relays from the tunnel of one UE into the tunnel of another, and count the bytes that carry the tag where the packet has left the tunnel. --- src/simu5g/corenetwork/gtp/GtpUser.cc | 3 + tests/module/GtpRegionTags.test | 206 ++++++++++++++++++++++++++ tests/module/GtpRegionTagsIp.test | 160 ++++++++++++++++++++ 3 files changed, 369 insertions(+) create mode 100644 tests/module/GtpRegionTags.test create mode 100644 tests/module/GtpRegionTagsIp.test 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 +%#--------------------------------------------------------------------------------------------------------------