Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8c12f97
GtpUser: add forceTunnelPeer for FRER inter-session/DC transport dive…
MohamedSeliem Jul 15, 2026
b7bd2a8
MAC: guard against unsigned underflow in macSduRequest when grant < M…
MohamedSeliem Jul 15, 2026
b751c10
IHandoverPacketFilter: add eNB-specific handover gate
MohamedSeliem Jul 15, 2026
0954a69
Binder: add DC secondary registration for FRER dual connectivity
MohamedSeliem Jul 17, 2026
dad8e52
HandoverController: DC-secondary-aware Binder registration
MohamedSeliem Jul 17, 2026
77f36a0
LtePhyEnb: accept frames from UEs where this gNB is the DC secondary
MohamedSeliem Jul 17, 2026
61177d4
LtePhyBase: sender-aware receive-gate routing for DC secondary legs
MohamedSeliem Jul 17, 2026
0fd7b09
Rrc: add conditional nrHandoverController2 for NR-NR dual connectivity
MohamedSeliem Jul 17, 2026
6473cb2
BearerManagement: third-leg entity creation for DC secondary DRBs
MohamedSeliem Jul 17, 2026
6204a25
Binder: skip native EN-DC split-bearer for nascTime-managed DC-second…
MohamedSeliem Jul 17, 2026
0f75569
LtePhyUe: recognize nrPhy2 as NR-typed, not just nrPhy
MohamedSeliem Jul 17, 2026
cce38ae
HandoverController: DC-secondary attach-selectivity + AMC registratio…
MohamedSeliem Jul 19, 2026
1c8d236
BearerManagement: fix mac/rlcMux selection audit, setRlcEntityParams …
MohamedSeliem Jul 19, 2026
90715f6
LtePhyUe: fix isNr_ operator-precedence bug (nrPhy/nrPhy2 recognition)
MohamedSeliem Jul 19, 2026
1c2bcb0
LteMacUe: recognize nrMac2 as NR-typed, not just nrMac
MohamedSeliem Jul 19, 2026
8f1f76b
LteDlFeedbackGenerator: recognize nrDlFbGen2 as NR-typed, not just nr…
MohamedSeliem Jul 19, 2026
7a1c198
Binder: getDeployedUes() DC-secondary extension (scoped months ago, a…
MohamedSeliem Jul 19, 2026
d0d2392
LtePhyEnbD2D: DC-secondary exception for UL frame-source validation
MohamedSeliem Jul 19, 2026
d768ad9
LtePhyBase: C++-level direct gate routing to nrRadioIn2 for DC-second…
MohamedSeliem Jul 19, 2026
e771ab8
Ip2Nic/HandoverPacketHolder: DC-secondary exceptions, third leg state…
MohamedSeliem Jul 19, 2026
81846d0
LteAmc: DC-secondary exception in getServingNodeOrSelf()
MohamedSeliem Jul 19, 2026
4dd4c04
QoSAwareScheduler: add debug instrumentation (remove before further w…
MohamedSeliem Jul 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 56 additions & 5 deletions src/simu5g/common/binder/Binder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ void Binder::registerCarrierUe(GHz carrierFrequency, unsigned int numerologyInde
if (it == carrierUeMap_.end())
throw cRuntimeError("Binder::registerCarrierUe - Carrier [%gGHz] not found (missing registerCarrier call?)", carrierFrequency.get());

EV_INFO << "DEBUG_CARRIERUE registering ueId=" << ueId << " carrierFreq=" << carrierFrequency << endl;
carrierUeMap_[carrierFrequency].insert(ueId);

if (ueNumerologyIndex_.find(ueId) == ueNumerologyIndex_.end()) {
Expand Down Expand Up @@ -257,6 +258,39 @@ void Binder::registerMasterNode(MacNodeId masterId, MacNodeId slaveId)
secondaryNodeToMasterNodeOrSelf_[num(slaveId)] = (masterId != NODEID_NONE) ? masterId : slaveId; // the "or self" bit
}

void Binder::registerDcSecondary(MacNodeId secondaryEnbId, MacNodeId ueId)
{
Enter_Method_Silent("registerDcSecondary");
ASSERT(getNodeTypeById(secondaryEnbId) == NODEB);
ASSERT(getNodeTypeById(ueId) == UE);

EV << "Binder::registerDcSecondary secondary=" << secondaryEnbId
<< " ue=" << ueId << endl;

dcSecondaryNextHop_[ueId] = secondaryEnbId;

if (dcPrimaryNextHop_.find(ueId) == dcPrimaryNextHop_.end())
dcPrimaryNextHop_[ueId] = getServingNode(ueId);
}

void Binder::unregisterDcSecondary(MacNodeId ueId)
{
Enter_Method_Silent("unregisterDcSecondary");
dcSecondaryNextHop_.erase(ueId);
}

MacNodeId Binder::getDcSecondaryNextHop(MacNodeId ueId) const
{
auto it = dcSecondaryNextHop_.find(ueId);
return (it != dcSecondaryNextHop_.end()) ? it->second : NODEID_NONE;
}

MacNodeId Binder::getDcPrimaryNextHop(MacNodeId ueId)
{
auto it = dcPrimaryNextHop_.find(ueId);
return (it != dcPrimaryNextHop_.end()) ? it->second : getServingNodeOrSelf(ueId);
}

inline ostream& operator<<(ostream& os, const L3Address& addr) { return os << addr.str(); }
inline ostream& operator<<(ostream& os, const UeInfo& info) { return os << info.str(); }
inline ostream& operator<<(ostream& os, const EnbInfo& info) { return os << info.str(); }
Expand Down Expand Up @@ -474,12 +508,15 @@ cModule *Binder::getModuleByMacNodeId(MacNodeId nodeId)
std::vector<MacNodeId> Binder::getDeployedUes(MacNodeId enbNodeId)
{
ASSERT(getNodeTypeById(enbNodeId) == NODEB);

std::vector<MacNodeId> connectedUes;
for (auto& [nodeId, nodeInfo] : nodeInfoMap_)
if (nodeInfo.moduleRef != nullptr && getNodeTypeById(nodeId) == UE && servingNode_.size() > num(nodeId) && servingNode_[num(nodeId)] == enbNodeId)
for (auto& [nodeId, nodeInfo] : nodeInfoMap_) {
if (nodeInfo.moduleRef == nullptr || getNodeTypeById(nodeId) != UE)
continue;
bool isPrimary = servingNode_.size() > num(nodeId) && servingNode_[num(nodeId)] == enbNodeId;
bool isDcSecondary = getDcSecondaryNextHop(nodeId) == enbNodeId;
if (isPrimary || isDcSecondary)
connectedUes.push_back(nodeId);

}
return connectedUes;
}

Expand Down Expand Up @@ -982,7 +1019,21 @@ bool Binder::isDualConnectivityRequired(FlowControlInfo *info)

void Binder::establishUnidirectionalDataConnection(FlowControlInfo *info)
{
bool dualConnected = isDualConnectivityRequired(info);
// nascTime / FRER: skip native EN-DC split-bearer entirely for any UE
// managed by our own per-DRB secondary-leg routing (see
// BearerManagement::dcSecondaryDrbIds_). Without this guard, native
// split-bearer fires for EVERY DRB of EVERY dual-tech UE served by a
// gNB registered as anyone's Binder-level secondary -- not just the
// one DRB nascTime intends to route there. Confirmed via direct trace:
// isDualConnectivityRequired() returns true whenever
// getMasterNodeOrSelf(nodeB) != nodeB, which registerMasterNode(gnb,
// gnb2) makes unconditionally true for gnb2, for all its traffic.
MacNodeId ueId = (getNodeTypeById(info->getSourceId()) == UE) ? info->getSourceId()
: (getNodeTypeById(info->getDestId()) == UE) ? info->getDestId()
: NODEID_NONE;
bool nascTimeManagedDc = (ueId != NODEID_NONE) && (getDcSecondaryNextHop(ueId) != NODEID_NONE);

bool dualConnected = !nascTimeManagedDc && isDualConnectivityRequired(info);
if (!dualConnected) {
createConnection(info, true);
}
Expand Down
31 changes: 31 additions & 0 deletions src/simu5g/common/binder/Binder.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ class Binder : public cSimpleModule
std::vector<MacNodeId> servingNode_; // ueId -> servingEnbId
std::vector<MacNodeId> secondaryNodeToMasterNodeOrSelf_;

// nascTime / FRER: per-UE DC secondary tracking (see registerDcSecondary())
std::map<MacNodeId, MacNodeId> dcSecondaryNextHop_; // ueId -> secondary gNB
std::map<MacNodeId, MacNodeId> dcPrimaryNextHop_; // ueId -> primary gNB (cached)

// stores the IP address of the MEC hosts in the simulation
std::set<inet::L3Address> mecHostAddress_;

Expand Down Expand Up @@ -312,6 +316,33 @@ class Binder : public cSimpleModule
*/
virtual MacNodeId getSecondaryNode(MacNodeId masterEnbId);

/**
* nascTime / FRER: per-UE DC secondary registration. Does NOT touch
* servingNode_ (the MCG/primary slot) -- tracks SCG/secondary
* attachment separately and non-destructively, so a UE can have both
* a primary and a secondary gNB registered simultaneously without
* either overwriting the other.
*/
virtual void registerDcSecondary(MacNodeId secondaryEnbId, MacNodeId ueId);

/**
* Removes the DC secondary registration for the given UE, if any.
*/
virtual void unregisterDcSecondary(MacNodeId ueId);

/**
* Returns the UE's currently registered DC secondary gNB, or
* NODEID_NONE if it has none.
*/
virtual MacNodeId getDcSecondaryNextHop(MacNodeId ueId) const;

/**
* Returns the UE's primary (MCG) gNB, as cached at the time DC
* secondary registration first occurred, falling back to the current
* servingNode_ entry if no DC secondary was ever registered.
*/
virtual MacNodeId getDcPrimaryNextHop(MacNodeId ueId);

/**
* Returns the MacNodeId for the given IP address
*
Expand Down
27 changes: 27 additions & 0 deletions src/simu5g/corenetwork/gtp/GtpUser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ void GtpUser::initialize(int stage)
}
}


// DC FRER: optional forced tunnel peer (must be outside the ownerType check
// because upf2 IS a UPF and needs this)
const char *forcedPeer = par("forceTunnelPeer").stringValue();
if (strlen(forcedPeer) > 0) {
std::string peerPath = binder_->getNetworkName() + "." + forcedPeer;
forcedPeerAddress_ = L3AddressResolver().resolve(peerPath.c_str());
hasForcedPeer_ = true;
EV_INFO << "GtpUser: forced tunnel peer = " << peerPath
<< " (" << forcedPeerAddress_ << ")" << endl;
}

if (isBaseStation(ownerType_))
myMacNodeID = MacNodeId(networkNode_->par("macNodeId").intValue());
else
Expand Down Expand Up @@ -158,6 +170,21 @@ void GtpUser::handleFromTrafficFlowFilter(Packet *datagram)
return;
}

// DC FRER: bypass normal routing when forced peer is configured
if (hasForcedPeer_&& (ownerType_ == UPF || ownerType_ == PGW)) {
auto header = makeShared<GtpUserMsg>();
header->setTeid(0);
header->setQfi(qfi);
header->setChunkLength(B(8));
auto gtpPacket = new Packet(datagram->getName());
gtpPacket->insertAtFront(header);
gtpPacket->insertAtBack(datagram->peekData());
delete datagram;
EV_INFO << "GtpUser: tunneling to forced peer " << forcedPeerAddress_ << endl;
socket_.sendTo(gtpPacket, forcedPeerAddress_, tunnelPeerPort_);
return;
}

// If we are on the eNB and the flowId represents the ID of this eNB, forward the packet locally
if (flowId == TFT_LOCAL_DELIVERY) {
// local delivery
Expand Down
20 changes: 11 additions & 9 deletions src/simu5g/corenetwork/gtp/GtpUser.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,6 @@ using namespace omnetpp;
*/
class GtpUser : public cSimpleModule
{
inet::UdpSocket socket_;
int localPort_;

// reference to the LTE Binder module
inet::ModuleRefByPar<Binder> binder_;

// the GTP protocol Port
unsigned int tunnelPeerPort_;

// IP address of the gateway to the Internet
inet::L3Address gwAddress_;

Expand All @@ -64,6 +55,17 @@ class GtpUser : public cSimpleModule
CoreNodeType selectOwnerType(const char *type);

protected:
inet::UdpSocket socket_;
int localPort_;

// reference to the LTE Binder module
inet::ModuleRefByPar<Binder> binder_;

// the GTP protocol Port
unsigned int tunnelPeerPort_;

inet::L3Address forcedPeerAddress_;
bool hasForcedPeer_ = false;

int numInitStages() const override { return inet::NUM_INIT_STAGES; }
void initialize(int stage) override;
Expand Down
1 change: 1 addition & 0 deletions src/simu5g/corenetwork/gtp/GtpUser.ned
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ simple GtpUser
int tunnelPeerPort = default(31);
string gateway = default(""); // optional

string forceTunnelPeer = default(""); // empty = normal routing, set = force all tunneling to this node
@display("i=block/tunnel");

gates:
Expand Down
12 changes: 9 additions & 3 deletions src/simu5g/stack/ip2nic/HandoverPacketHolderEnb.cc
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,20 @@ void HandoverPacketHolderEnb::fromIpBs(Packet *pkt)
return;
}

// if UE has moved to another gNB (e.g. late packet after handover), forward via X2
// nascTime / FRER: a UE's registered DC secondary must be able to
// deliver packets locally, not have them treated as stale/mid-handover
// traffic just because it isn't the UE's primary serving node. Without
// this, every DL packet destined for a DC-secondary DRB gets diverted
// to native X2 forwarding, which was never configured for this topology.
MacNodeId servingNode = binder_->getServingNodeOrSelf(destId);
if (servingNode != NODEID_NONE && servingNode != nodeId_) {
bool isDcSecondaryForThisUe = (binder_->getDcSecondaryNextHop(destId) == nodeId_);
if (isDcSecondaryForThisUe)
EV_INFO << "HandoverPacketHolderEnb::fromIpBs - UE " << destId << " delivered locally as DC secondary (patch 10 active)" << endl;
if (servingNode != NODEID_NONE && servingNode != nodeId_ && !isDcSecondaryForThisUe) {
EV << "Ip2Nic::fromIpBs - UE " << destId << " is served by gNB " << servingNode << ", forwarding via X2" << endl;
sendTunneledPacketOnHandover(pkt, servingNode);
return;
}

toStackBs(pkt);
}

Expand Down
Loading
Loading