From 5df29f74e11ee73b725269b6abe4a868695824bb Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Sun, 15 Mar 2026 02:39:57 -0500 Subject: [PATCH] [nexus] implement infra interface for backbone simulation (#12683) Implement the InfraIf class and associated platform logic to simulate a shared infrastructure link (backbone) between Border Routers and external hosts within the Nexus simulation environment. Infra interface simulation: - Implement shared Ethernet-like link for IPv6 traffic delivery. - Add automated SLAAC address configuration based on ICMPv6 RAs. - Support sending/receiving ICMPv6 Neighbor Discovery (RS, RA, NS, NA). - Implement manual ICMPv6 checksum calculation for raw packets. - Add infrastructure-level loop prevention and destination filtering. - Provide helper methods to find nodes by infrastructure addresses. Platform integration: - Implement otPlatInfraIf APIs and integrate with otInstance. - Use native Message and MessageQueue for pending traffic management. - Add support for custom test variables in SaveTestInfo() JSON output. - Update Node::Reset() to properly clear pending infra interface tasks. Core enhancements: - Add MulticastListenersTable::Has() to check for address presence. - Add PrefixInfoOption::GetPrefixLength() and SetPrefixLength(). - Enable MLR and Backbone Router multicast routing in Nexus config. --- .../multicast_listeners_table.cpp | 17 + .../multicast_listeners_table.hpp | 10 + src/core/net/nd6.hpp | 14 + tests/nexus/openthread-core-nexus-config.h | 3 + tests/nexus/platform/nexus_core.cpp | 83 +++- tests/nexus/platform/nexus_core.hpp | 11 + tests/nexus/platform/nexus_infra_if.cpp | 432 +++++++++++++++++- tests/nexus/platform/nexus_infra_if.hpp | 89 ++++ tests/nexus/platform/nexus_node.cpp | 46 +- tests/nexus/platform/nexus_node.hpp | 14 +- tests/nexus/platform/nexus_settings.cpp | 4 +- tests/nexus/test_nat64_translator.cpp | 3 + 12 files changed, 711 insertions(+), 15 deletions(-) create mode 100644 tests/nexus/platform/nexus_infra_if.hpp diff --git a/src/core/backbone_router/multicast_listeners_table.cpp b/src/core/backbone_router/multicast_listeners_table.cpp index 1cb9e40fb..a1ea50355 100644 --- a/src/core/backbone_router/multicast_listeners_table.cpp +++ b/src/core/backbone_router/multicast_listeners_table.cpp @@ -130,6 +130,23 @@ void MulticastListenersTable::Expire(void) CheckInvariants(); } +bool MulticastListenersTable::Has(const Ip6::Address &aAddress) const +{ + bool has = false; + + for (uint16_t i = 0; i < mNumValidListeners; i++) + { + if (mListeners[i].GetAddress() == aAddress) + { + has = true; + ExitNow(); + } + } + +exit: + return has; +} + #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_DEBG) void MulticastListenersTable::Log(Action aAction, const Ip6::Address &aAddress, diff --git a/src/core/backbone_router/multicast_listeners_table.hpp b/src/core/backbone_router/multicast_listeners_table.hpp index 880832c8e..fd0c72717 100644 --- a/src/core/backbone_router/multicast_listeners_table.hpp +++ b/src/core/backbone_router/multicast_listeners_table.hpp @@ -148,6 +148,16 @@ public: */ uint16_t Count(void) const { return mNumValidListeners; } + /** + * Indicates whether or not the Multicast Listeners Table contains the given address. + * + * @param[in] aAddress The Multicast Listener address. + * + * @retval True If the Multicast Listeners Table contains the given address. + * @retval False If the Multicast Listeners Table does not contain the given address. + */ + bool Has(const Ip6::Address &aAddress) const; + /** * Enables range-based `for` loop iteration over all Multicast Listeners. * diff --git a/src/core/net/nd6.hpp b/src/core/net/nd6.hpp index af330f301..819c8e210 100644 --- a/src/core/net/nd6.hpp +++ b/src/core/net/nd6.hpp @@ -279,6 +279,20 @@ public: */ uint32_t GetPreferredLifetime(void) const { return BigEndian::HostSwap32(mPreferredLifetime); } + /** + * Returns the prefix length (in bits). + * + * @returns The prefix length (in bits). + */ + uint8_t GetPrefixLength(void) const { return mPrefixLength; } + + /** + * Sets the prefix length (in bits). + * + * @param[in] aPrefixLength The prefix length (in bits). + */ + void SetPrefixLength(uint8_t aPrefixLength) { mPrefixLength = aPrefixLength; } + /** * Sets the prefix. * diff --git a/tests/nexus/openthread-core-nexus-config.h b/tests/nexus/openthread-core-nexus-config.h index cd257c019..9e981d77d 100644 --- a/tests/nexus/openthread-core-nexus-config.h +++ b/tests/nexus/openthread-core-nexus-config.h @@ -82,6 +82,7 @@ #define OPENTHREAD_CONFIG_JAM_DETECTION_ENABLE 1 #define OPENTHREAD_CONFIG_JOINER_ENABLE 1 #define OPENTHREAD_CONFIG_LOG_LEVEL OT_LOG_LEVEL_INFO + #define OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE 1 #define OPENTHREAD_CONFIG_LOG_LEVEL_INIT OT_LOG_LEVEL_CRIT #define OPENTHREAD_CONFIG_LOG_OUTPUT OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED @@ -102,6 +103,8 @@ #define OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE 1 #define OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE 1 #define OPENTHREAD_CONFIG_MLE_MAX_CHILDREN 128 +#define OPENTHREAD_CONFIG_MLR_ENABLE 1 +#define OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE 1 #define OPENTHREAD_CONFIG_MULTICAST_DNS_AUTO_ENABLE_ON_INFRA_IF 1 #define OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE 1 #define OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE 1 diff --git a/tests/nexus/platform/nexus_core.cpp b/tests/nexus/platform/nexus_core.cpp index 806bb72ba..f95a7b082 100644 --- a/tests/nexus/platform/nexus_core.cpp +++ b/tests/nexus/platform/nexus_core.cpp @@ -28,8 +28,8 @@ #include "nexus_core.hpp" +#include #include -#include #include "mac_frame.h" #include "nexus_node.hpp" @@ -224,7 +224,13 @@ void Core::SaveTestInfo(const char *aFilename, Node *aLeaderNode) { Ip6::Prefix prefix; prefix.Set(leaderNode->Get().GetMeshLocalPrefix()); - fprintf(file, " \"mesh_local_prefix\": \"%s\"\n", prefix.ToString().AsCString()); + fprintf(file, " \"mesh_local_prefix\": \"%s\"%s\n", prefix.ToString().AsCString(), + mTestVars.IsEmpty() ? "" : ","); + } + for (const TestVar &var : mTestVars) + { + fprintf(file, " \"%s\": \"%s\"%s\n", var.mName.AsCString(), var.mValue.AsCString(), + (&var == mTestVars.Back()) ? "" : ","); } fprintf(file, " }\n"); @@ -239,6 +245,14 @@ exit: void Core::AddNetworkKey(const NetworkKey &aKey) { SuccessOrQuit(mNetworkKeys.PushBack(aKey)); } +void Core::AddTestVar(const char *aName, const char *aValue) +{ + TestVar *var = mTestVars.PushBack(); + VerifyOrQuit(var != nullptr); + var->mName.Clear().Append("%s", aName); + var->mValue.Clear().Append("%s", aValue); +} + Core::~Core(void) { sInUse = false; } Node &Core::CreateNode(void) @@ -250,10 +264,14 @@ Node &Core::CreateNode(void) node->GetInstance().SetId(mCurNodeId++); + node->mInfraIf.Init(*node); + mNodes.Push(*node); node->GetInstance().AfterInit(); + otIp6SetReceiveCallback(&node->GetInstance(), Node::HandleIp6Receive, node); + return *node; } @@ -322,10 +340,13 @@ void Core::AdvanceTime(uint32_t aDuration) void Core::Process(Node &aNode) { + SetActiveNode(&aNode); + otTaskletsProcess(&aNode.GetInstance()); ProcessRadio(aNode); ProcessMdns(aNode); + ProcessInfraIf(aNode); #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE ProcessTrel(aNode); #endif @@ -341,6 +362,8 @@ void Core::Process(Node &aNode) aNode.mAlarmMicro.mScheduled = false; otPlatAlarmMicroFired(&aNode.GetInstance()); } + + SetActiveNode(nullptr); } void Core::ProcessRadio(Node &aNode) @@ -505,6 +528,62 @@ void Core::ProcessMdns(Node &aNode) aNode.mMdns.mPendingTxList.Free(); } +void Core::ProcessInfraIf(Node &aNode) +{ + // Deliver pending packets on the infrastructure interface. + + Message *message; + + while ((message = aNode.mInfraIf.mPendingTxQueue.GetHead()) != nullptr) + { + Ip6::Header header; + Node *targetNode = nullptr; + Heap::Data msgData; + + aNode.mInfraIf.mPendingTxQueue.Dequeue(*message); + + SuccessOrQuit(message->Read(0, header)); + + VerifyOrQuit(message->GetLength() >= sizeof(Ip6::Header) && header.IsVersion6()); + + SuccessOrQuit(msgData.SetFrom(*message, 0, message->GetLength())); + mPcap.WritePacket(msgData.GetBytes(), msgData.GetLength(), mNow); + + if (!header.GetDestination().IsMulticast()) + { + targetNode = FindNodeByInfraIfAddress(header.GetDestination()); + } + + for (Node &rxNode : mNodes) + { + if (targetNode != nullptr && &rxNode != targetNode) + { + continue; + } + + rxNode.mInfraIf.Receive(aNode, header, *message); + } + + message->Free(); + } +} + +Node *Core::FindNodeByInfraIfAddress(const Ip6::Address &aAddress) +{ + Node *matchedNode = nullptr; + + for (Node &node : mNodes) + { + if (node.mInfraIf.HasAddress(aAddress)) + { + matchedNode = &node; + break; + } + } + + return matchedNode; +} + #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE void Core::ProcessTrel(Node &aNode) diff --git a/tests/nexus/platform/nexus_core.hpp b/tests/nexus/platform/nexus_core.hpp index cf016a213..e35afb99d 100644 --- a/tests/nexus/platform/nexus_core.hpp +++ b/tests/nexus/platform/nexus_core.hpp @@ -65,6 +65,7 @@ public: void SaveTestInfo(const char *aFilename, Node *aLeaderNode = nullptr); void AddNetworkKey(const NetworkKey &aKey); + void AddTestVar(const char *aName, const char *aValue); void SendAndVerifyEchoRequest(Node &aSender, const Ip6::Address &aDestination, uint16_t aPayloadSize = 0, @@ -101,13 +102,22 @@ private: bool mResponseReceived; }; + struct TestVar + { + String<32> mName; + String<64> mValue; + }; + void Process(Node &aNode); void ProcessRadio(Node &aNode); void ProcessMdns(Node &aNode); + void ProcessInfraIf(Node &aNode); #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE void ProcessTrel(Node &aNode); #endif + Node *FindNodeByInfraIfAddress(const Ip6::Address &aAddress); + static void HandleIcmpResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, @@ -119,6 +129,7 @@ private: OwningList mNodes; Pcap mPcap; Array mNetworkKeys; + Array mTestVars; uint16_t mCurNodeId; bool mPendingAction; uint64_t mNow; diff --git a/tests/nexus/platform/nexus_infra_if.cpp b/tests/nexus/platform/nexus_infra_if.cpp index d95732fde..92fc40ccc 100644 --- a/tests/nexus/platform/nexus_infra_if.cpp +++ b/tests/nexus/platform/nexus_infra_if.cpp @@ -26,21 +26,443 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include +#include "nexus_infra_if.hpp" + +#include "nexus_core.hpp" +#include "nexus_node.hpp" namespace ot { namespace Nexus { +InfraIf::InfraIf(void) + : mNode(nullptr) + , mNodeId(0) + , mIfIndex(0) +{ +} + +void InfraIf::Init(Node &aNode) +{ + Ip6::Address address; + otPlatInfraIfLinkLayerAddress mac; + Ip6::InterfaceIdentifier iid; + + mIfIndex = 1; + mNode = &aNode; + mNodeId = aNode.GetId(); + + GetLinkLayerAddress(mac); + + iid.mFields.m8[0] = mac.mAddress[0] ^ 0x02; + iid.mFields.m8[1] = mac.mAddress[1]; + iid.mFields.m8[2] = mac.mAddress[2]; + iid.mFields.m8[3] = 0xff; + iid.mFields.m8[4] = 0xfe; + iid.mFields.m8[5] = mac.mAddress[3]; + iid.mFields.m8[6] = mac.mAddress[4]; + iid.mFields.m8[7] = mac.mAddress[5]; + + address.SetToLinkLocalAddress(iid); + + AddAddress(address); +} + +bool InfraIf::HasAddress(const Ip6::Address &aAddress) const { return mAddresses.Contains(aAddress); } + +void InfraIf::AddAddress(const Ip6::Address &aAddress) +{ + VerifyOrExit(!HasAddress(aAddress)); + + SuccessOrQuit(mAddresses.PushBack(aAddress)); + +exit: + return; +} + +void InfraIf::RemoveAddress(const Ip6::Address &aAddress) +{ + for (uint16_t index = 0; index < mAddresses.GetLength(); index++) + { + if (mAddresses[index] == aAddress) + { + mAddresses[index] = *mAddresses.Back(); + mAddresses.PopBack(); + break; + } + } +} + +const Ip6::Address *InfraIf::FindAddress(const char *aPrefix) const +{ + Ip6::Prefix prefix; + const Ip6::Address *matchedAddress = nullptr; + + SuccessOrQuit(prefix.FromString(aPrefix)); + + for (const Ip6::Address &address : mAddresses) + { + if (address.MatchesPrefix(prefix)) + { + matchedAddress = &address; + break; + } + } + + return matchedAddress; +} + +const Ip6::Address &InfraIf::FindMatchingAddress(const char *aPrefix) const +{ + const Ip6::Address *matchedAddress = FindAddress(aPrefix); + + VerifyOrQuit(matchedAddress != nullptr, "no matching address found on infrastructure interface"); + + return *matchedAddress; +} + +void InfraIf::SendIcmp6Nd(const Ip6::Address &aDestAddress, const uint8_t *aBuffer, uint16_t aBufferLength) +{ + Message *message = GetNode().Get().Allocate(Message::kTypeIp6); + Ip6::Header ip6Header; + + VerifyOrQuit(message != nullptr); + + ip6Header.InitVersionTrafficClassFlow(); + ip6Header.SetPayloadLength(aBufferLength); + ip6Header.SetNextHeader(Ip6::kProtoIcmp6); + ip6Header.SetHopLimit(255); + ip6Header.SetSource(GetLinkLocalAddress()); + ip6Header.SetDestination(aDestAddress); + + SuccessOrQuit(message->Append(ip6Header)); + SuccessOrQuit(message->AppendBytes(aBuffer, aBufferLength)); + + // Calculate ICMPv6 checksum + message->SetOffset(sizeof(Ip6::Header)); + Checksum::UpdateMessageChecksum(*message, ip6Header.GetSource(), ip6Header.GetDestination(), Ip6::kProtoIcmp6); + + mPendingTxQueue.Enqueue(*message); +} + +void InfraIf::ProcessIcmp6Nd(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength) +{ + Ip6::Nd::Icmp6Packet packet; + + OT_UNUSED_VARIABLE(aSrcAddress); + + packet.Init(aBuffer, aBufferLength); + + VerifyOrExit(packet.GetLength() >= sizeof(Ip6::Icmp::Header)); + + switch (reinterpret_cast(packet.GetBytes())->GetType()) + { + case Ip6::Icmp::Header::kTypeRouterAdvert: + { + Ip6::Nd::RouterAdvert::RxMessage raMessage(packet); + + VerifyOrExit(raMessage.IsValid()); + + for (const Ip6::Nd::Option &option : raMessage) + { + if (option.GetType() == Ip6::Nd::Option::kTypePrefixInfo) + { + HandlePrefixInfoOption(static_cast(option)); + } + } + break; + } + + case Ip6::Icmp::Header::kTypeRouterSolicit: + case Ip6::Icmp::Header::kTypeNeighborAdvert: + case Ip6::Icmp::Header::kTypeNeighborSolicit: + // TODO: Handle other ND messages as needed for the simulation. + break; + + default: + break; + } + +exit: + return; +} + +void InfraIf::HandlePrefixInfoOption(const Ip6::Nd::PrefixInfoOption &aPio) +{ + Ip6::Prefix prefix; + Ip6::Address address; + otPlatInfraIfLinkLayerAddress mac; + + VerifyOrExit(aPio.IsAutoAddrConfigFlagSet()); + + aPio.GetPrefix(prefix); + + // Generate address using SLAAC (EUI-64 style for simplicity in test) + + address = AsCoreType(&prefix.mPrefix); + GetLinkLayerAddress(mac); + + address.mFields.m8[8] = mac.mAddress[0] ^ 0x02; + address.mFields.m8[9] = mac.mAddress[1]; + address.mFields.m8[10] = mac.mAddress[2]; + address.mFields.m8[11] = 0xff; + address.mFields.m8[12] = 0xfe; + address.mFields.m8[13] = mac.mAddress[3]; + address.mFields.m8[14] = mac.mAddress[4]; + address.mFields.m8[15] = mac.mAddress[5]; + + if (aPio.GetValidLifetime() == 0) + { + if (HasAddress(address)) + { + RemoveAddress(address); + Log("Node %lu (%s) removed address %s from RA (lifetime 0)", ToUlong(GetNode().GetId()), + GetNode().GetName(), address.ToString().AsCString()); + } + ExitNow(); + } + + AddAddress(address); + Log("Node %lu (%s) auto-configured address %s from RA", ToUlong(GetNode().GetId()), GetNode().GetName(), + address.ToString().AsCString()); + +exit: + return; +} + +void InfraIf::SendIp6(const Ip6::Address &aSrcAddress, + const Ip6::Address &aDestAddress, + const uint8_t *aBuffer, + uint16_t aBufferLength) +{ + Message *message = GetNode().Get().Allocate(Message::kTypeIp6); + + VerifyOrQuit(message != nullptr); + + Log("InfraIf::SendIp6 from %s to %s (len:%u)", aSrcAddress.ToString().AsCString(), + aDestAddress.ToString().AsCString(), aBufferLength); + + SuccessOrQuit(message->AppendBytes(aBuffer, aBufferLength)); + + mPendingTxQueue.Enqueue(*message); +} + +void InfraIf::SendEchoRequest(const Ip6::Address &aSrcAddress, + const Ip6::Address &aDestAddress, + uint16_t aIdentifier, + uint16_t aPayloadSize) +{ + Message *message; + Ip6::Header ip6Header; + Ip6::Icmp::Header icmpHeader; + + message = GetNode().Get().Allocate(Message::kTypeIp6); + VerifyOrQuit(message != nullptr); + + ip6Header.Clear(); + ip6Header.InitVersionTrafficClassFlow(); + ip6Header.SetPayloadLength(sizeof(Ip6::Icmp::Header) + aPayloadSize); + ip6Header.SetNextHeader(Ip6::kProtoIcmp6); + ip6Header.SetHopLimit(64); + ip6Header.SetSource(aSrcAddress); + ip6Header.SetDestination(aDestAddress); + + icmpHeader.Clear(); + icmpHeader.SetType(Ip6::Icmp::Header::kTypeEchoRequest); + icmpHeader.SetCode(static_cast(0)); + icmpHeader.SetId(aIdentifier); + icmpHeader.SetSequence(0); + + SuccessOrQuit(message->SetLength(aPayloadSize)); + SuccessOrQuit(message->Prepend(icmpHeader)); + message->SetOffset(0); + + Checksum::UpdateMessageChecksum(*message, aSrcAddress, aDestAddress, Ip6::kProtoIcmp6); + + SuccessOrQuit(message->Prepend(ip6Header)); + + mPendingTxQueue.Enqueue(*message); +} + +void InfraIf::Receive(Node &aSrcNode, const Ip6::Header &aHeader, Message &aMessage) +{ + Node &node = GetNode(); + bool isIcmp6Nd = false; + bool isEchoRequest = false; + + VerifyOrExit(!node.mInfraIf.HasAddress(aHeader.GetSource())); + VerifyOrExit(!node.Get().IsOnMesh(aHeader.GetSource())); + +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE + if (aHeader.GetDestination().IsMulticastLargerThanRealmLocal()) + { + VerifyOrExit(node.Get().IsPrimary()); + VerifyOrExit(node.Get().Has(aHeader.GetDestination())); + } +#endif + + Core::Get().SetActiveNode(&node); + + if (aHeader.GetNextHeader() == Ip6::kProtoIcmp6 && + aMessage.GetLength() >= sizeof(Ip6::Header) + sizeof(Ip6::Icmp::Header) && + (aHeader.GetDestination() == Ip6::Address::GetLinkLocalAllNodesMulticast() || + aHeader.GetDestination() == Ip6::Address::GetLinkLocalAllRoutersMulticast() || + node.mInfraIf.HasAddress(aHeader.GetDestination()))) + { + Ip6::Icmp::Header icmpHeader; + + SuccessOrQuit(aMessage.Read(sizeof(Ip6::Header), icmpHeader)); + + switch (icmpHeader.GetType()) + { + case Ip6::Icmp::Header::kTypeRouterAdvert: + case Ip6::Icmp::Header::kTypeRouterSolicit: + case Ip6::Icmp::Header::kTypeNeighborAdvert: + case Ip6::Icmp::Header::kTypeNeighborSolicit: + isIcmp6Nd = true; + break; + case Ip6::Icmp::Header::kTypeEchoRequest: + isEchoRequest = true; + break; + default: + break; + } + } + + if (isIcmp6Nd) + { + Heap::Data payload; + uint16_t offset = sizeof(Ip6::Header); + + SuccessOrQuit(payload.SetFrom(aMessage, offset, aMessage.GetLength() - offset)); + + otPlatInfraIfRecvIcmp6Nd(&node.GetInstance(), mIfIndex, + reinterpret_cast(&aHeader.GetSource()), payload.GetBytes(), + payload.GetLength()); + node.mInfraIf.ProcessIcmp6Nd(aHeader.GetSource(), payload.GetBytes(), payload.GetLength()); + } + else if (isEchoRequest) + { + HandleEchoRequest(aHeader, aMessage); + } + else + { + // We also deliver generic IPv6 packets to the stack if they are NOT ICMPv6 ND packets. + // (ND packets were already delivered via otPlatInfraIfRecvIcmp6Nd above). + OwnedPtr messagePtr; + + messagePtr.Reset(node.Get().NewMessage()); + VerifyOrQuit(messagePtr != nullptr); + + SuccessOrQuit(messagePtr->AppendBytesFromMessage(aMessage, 0, aMessage.GetLength())); + messagePtr->SetOrigin(Message::kOriginHostUntrusted); + messagePtr->SetLoopbackToHostAllowed(false); + + SuccessOrQuit(node.Get().SendRaw(messagePtr.PassOwnership())); + } + + Core::Get().SetActiveNode(&aSrcNode); + +exit: + return; +} + +void InfraIf::HandleEchoRequest(const Ip6::Header &aHeader, Message &aMessage) +{ + Node &node = GetNode(); + Message *replyMessage; + Ip6::Header replyHeader; + Ip6::Icmp::Header replyIcmp; + uint16_t payloadLen = aMessage.GetLength() - sizeof(Ip6::Header); + + replyMessage = node.Get().Allocate(Message::kTypeIp6); + VerifyOrQuit(replyMessage != nullptr); + + SuccessOrQuit(replyMessage->SetLength(payloadLen)); + replyMessage->WriteBytesFromMessage(0, aMessage, sizeof(Ip6::Header), payloadLen); + + SuccessOrQuit(replyMessage->Read(0, replyIcmp)); + + replyHeader.InitVersionTrafficClassFlow(); + replyHeader.SetPayloadLength(payloadLen); + replyHeader.SetNextHeader(Ip6::kProtoIcmp6); + replyHeader.SetHopLimit(64); + replyHeader.SetSource(aHeader.GetDestination()); + replyHeader.SetDestination(aHeader.GetSource()); + + replyIcmp.SetType(Ip6::Icmp::Header::kTypeEchoReply); + replyMessage->Write(0, replyIcmp); + + // Recalculate ICMPv6 checksum + replyMessage->SetOffset(0); + Checksum::UpdateMessageChecksum(*replyMessage, replyHeader.GetSource(), replyHeader.GetDestination(), + Ip6::kProtoIcmp6); + + SuccessOrQuit(replyMessage->Prepend(replyHeader)); + + mPendingTxQueue.Enqueue(*replyMessage); +} + +void InfraIf::GetLinkLayerAddress(otPlatInfraIfLinkLayerAddress &aLinkLayerAddress) const +{ + // Use a unique MAC address based on Node ID + ClearAllBytes(aLinkLayerAddress); + aLinkLayerAddress.mLength = 6; + aLinkLayerAddress.mAddress[0] = 0x02; + aLinkLayerAddress.mAddress[5] = static_cast(mNodeId); +} + +Node &InfraIf::GetNode(void) +{ + OT_ASSERT(mNode != nullptr); + return *mNode; +} + +const Node &InfraIf::GetNode(void) const +{ + OT_ASSERT(mNode != nullptr); + return *mNode; +} + extern "C" { -bool otPlatInfraIfHasAddress(otInstance *, uint32_t, const otIp6Address *) { return false; } - -otError otPlatInfraIfSendIcmp6Nd(otInstance *, uint32_t, const otIp6Address *, const uint8_t *, uint16_t) +bool otPlatInfraIfHasAddress(otInstance *aInstance, uint32_t aInfraIfIndex, const otIp6Address *aAddress) { + OT_UNUSED_VARIABLE(aInfraIfIndex); + + return AsNode(aInstance).mInfraIf.HasAddress(AsCoreType(aAddress)); +} + +otError otPlatInfraIfSendIcmp6Nd(otInstance *aInstance, + uint32_t aInfraIfIndex, + const otIp6Address *aDestAddress, + const uint8_t *aBuffer, + uint16_t aBufferLength) +{ + OT_UNUSED_VARIABLE(aInfraIfIndex); + + AsNode(aInstance).mInfraIf.SendIcmp6Nd(AsCoreType(aDestAddress), aBuffer, aBufferLength); + return OT_ERROR_NONE; } -otError otPlatInfraIfDiscoverNat64Prefix(otInstance *, uint32_t) { return OT_ERROR_NOT_IMPLEMENTED; } +otError otPlatInfraIfDiscoverNat64Prefix(otInstance *aInstance, uint32_t aInfraIfIndex) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aInfraIfIndex); + + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatGetInfraIfLinkLayerAddress(otInstance *aInstance, + uint32_t aInfraIfIndex, + otPlatInfraIfLinkLayerAddress *aLinkLayerAddress) +{ + OT_UNUSED_VARIABLE(aInfraIfIndex); + + AsNode(aInstance).mInfraIf.GetLinkLayerAddress(*aLinkLayerAddress); + + return OT_ERROR_NONE; +} } // extern "C" diff --git a/tests/nexus/platform/nexus_infra_if.hpp b/tests/nexus/platform/nexus_infra_if.hpp new file mode 100644 index 000000000..55c1d72d8 --- /dev/null +++ b/tests/nexus/platform/nexus_infra_if.hpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2025, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef OT_NEXUS_PLATFORM_NEXUS_INFRA_IF_HPP_ +#define OT_NEXUS_PLATFORM_NEXUS_INFRA_IF_HPP_ + +#include +#include "instance/instance.hpp" + +namespace ot { +namespace Nexus { + +class Node; + +class InfraIf +{ +public: + InfraIf(void); + + void Init(Node &aNode); + + bool IsInitialized(void) const { return mIfIndex != 0; } + + bool HasAddress(const Ip6::Address &aAddress) const; + void AddAddress(const Ip6::Address &aAddress); + void RemoveAddress(const Ip6::Address &aAddress); + + const Ip6::Address *FindAddress(const char *aPrefix) const; + const Ip6::Address &FindMatchingAddress(const char *aPrefix) const; + + const Ip6::Address &GetLinkLocalAddress(void) const { return mAddresses[0]; } + + void SendIcmp6Nd(const Ip6::Address &aDestAddress, const uint8_t *aBuffer, uint16_t aBufferLength); + void SendIp6(const Ip6::Address &aSrcAddress, + const Ip6::Address &aDestAddress, + const uint8_t *aBuffer, + uint16_t aBufferLength); + void SendEchoRequest(const Ip6::Address &aSrcAddress, + const Ip6::Address &aDestAddress, + uint16_t aIdentifier, + uint16_t aPayloadSize); + void Receive(Node &aSrcNode, const Ip6::Header &aHeader, Message &aMessage); + void GetLinkLayerAddress(otPlatInfraIfLinkLayerAddress &aLinkLayerAddress) const; + + Node &GetNode(void); + const Node &GetNode(void) const; + + MessageQueue mPendingTxQueue; + +private: + void ProcessIcmp6Nd(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength); + void HandlePrefixInfoOption(const Ip6::Nd::PrefixInfoOption &aPio); + void HandleEchoRequest(const Ip6::Header &aHeader, Message &aMessage); + + Node *mNode; + uint32_t mNodeId; + uint32_t mIfIndex; + Heap::Array mAddresses; +}; + +} // namespace Nexus +} // namespace ot + +#endif // OT_NEXUS_PLATFORM_NEXUS_INFRA_IF_HPP_ diff --git a/tests/nexus/platform/nexus_node.cpp b/tests/nexus/platform/nexus_node.cpp index c2bc0d1de..f2e4a5f97 100644 --- a/tests/nexus/platform/nexus_node.cpp +++ b/tests/nexus/platform/nexus_node.cpp @@ -27,7 +27,6 @@ */ #include "nexus_node.hpp" -#include "nexus_utils.hpp" namespace ot { namespace Nexus { @@ -41,7 +40,10 @@ void Node::Reset(void) mAlarmMilli.Reset(); mAlarmMicro.Reset(); mMdns.Reset(); + mInfraIf.mPendingTxQueue.DequeueAndFreeAll(); mPendingTasklet = false; + + otIp6SetReceiveCallback(&GetInstance(), HandleIp6Receive, &GetInstance()); #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE mTrel.Reset(); #endif @@ -109,7 +111,8 @@ void Node::UnallowList(Node &aNode) { Get().RemoveAddress(aNode.Get void Node::SendEchoRequest(const Ip6::Address &aDestination, uint16_t aIdentifier, uint16_t aPayloadSize, - uint8_t aHopLimit) + uint8_t aHopLimit, + const Ip6::Address *aSrcAddress) { Message *message; Ip6::MessageInfo messageInfo; @@ -122,6 +125,11 @@ void Node::SendEchoRequest(const Ip6::Address &aDestination, messageInfo.SetPeerAddr(aDestination); messageInfo.SetHopLimit(aHopLimit); + if (aSrcAddress != nullptr) + { + messageInfo.SetSockAddr(*aSrcAddress); + } + Log("Sending Echo Request from Node %lu (%s) to %s (payload-size:%u)", ToUlong(GetId()), GetName(), aDestination.ToString().AsCString(), aPayloadSize); @@ -130,6 +138,40 @@ void Node::SendEchoRequest(const Ip6::Address &aDestination, void Node::SetName(const char *aPrefix, uint16_t aIndex) { mName.Clear().Append("%s_%u", aPrefix, aIndex); } +void Node::HandleIp6Receive(otMessage *aMessage, void *aContext) +{ + static_cast(aContext)->HandleReceive(aMessage); +} + +void Node::HandleReceive(otMessage *aMessage) +{ + uint16_t length = otMessageGetLength(aMessage); + uint8_t buffer[1500]; + const Ip6::Header *header; + + VerifyOrExit(length <= sizeof(buffer)); + VerifyOrQuit(otMessageRead(aMessage, 0, buffer, length) == length); + + VerifyOrExit(length >= sizeof(Ip6::Header)); + header = reinterpret_cast(buffer); + + // Forward packets to InfraIf if they are intended for the backbone. + // We avoid forwarding link-local and realm-local scope packets. + + VerifyOrExit(mInfraIf.IsInitialized()); + VerifyOrExit(header->GetHopLimit() > 1); + VerifyOrExit(header->GetDestination().GetScope() > Ip6::Address::kRealmLocalScope); + + Core::Get().SetActiveNode(this); + + VerifyOrExit(Get().IsOnMesh(header->GetSource())); + + mInfraIf.SendIp6(header->GetSource(), header->GetDestination(), buffer, length); + +exit: + otMessageFree(aMessage); +} + #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE void Node::GetTrelSockAddr(Ip6::SockAddr &aSockAddr) const { diff --git a/tests/nexus/platform/nexus_node.hpp b/tests/nexus/platform/nexus_node.hpp index 95cfc3ba9..21b0fee16 100644 --- a/tests/nexus/platform/nexus_node.hpp +++ b/tests/nexus/platform/nexus_node.hpp @@ -33,6 +33,7 @@ #include "nexus_alarm.hpp" #include "nexus_core.hpp" +#include "nexus_infra_if.hpp" #include "nexus_mdns.hpp" #include "nexus_radio.hpp" #include "nexus_settings.hpp" @@ -49,6 +50,7 @@ public: Alarm mAlarmMilli; Alarm mAlarmMicro; Mdns mMdns; + InfraIf mInfraIf; Settings mSettings; #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE Trel mTrel; @@ -62,7 +64,7 @@ protected: } }; -class Node : public Platform, public Heap::Allocatable, public LinkedListEntry, private Instance +class Node : public Platform, public Heap::Allocatable, public LinkedListEntry, public Instance { friend class Heap::Allocatable; @@ -88,9 +90,11 @@ public: void AllowList(Node &aNode); void UnallowList(Node &aNode); void SendEchoRequest(const Ip6::Address &aDestination, - uint16_t aIdentifier, + uint16_t aIdentifier = 0, uint16_t aPayloadSize = 0, - uint8_t aHopLimit = 64); + uint8_t aHopLimit = 64, + const Ip6::Address *aSrcAddress = nullptr); + #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE void GetTrelSockAddr(Ip6::SockAddr &aSockAddr) const; #endif @@ -122,8 +126,12 @@ public: static Node &From(otInstance *aInstance) { return static_cast(*aInstance); } + static void HandleIp6Receive(otMessage *aMessage, void *aContext); + void HandleReceive(otMessage *aMessage); + using Platform::mAlarmMicro; using Platform::mAlarmMilli; + using Platform::mInfraIf; using Platform::mMdns; using Platform::mPendingTasklet; using Platform::mRadio; diff --git a/tests/nexus/platform/nexus_settings.cpp b/tests/nexus/platform/nexus_settings.cpp index 8d46a2788..692f012f1 100644 --- a/tests/nexus/platform/nexus_settings.cpp +++ b/tests/nexus/platform/nexus_settings.cpp @@ -26,10 +26,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include - -#include "nexus_node.hpp" #include "nexus_settings.hpp" +#include "nexus_node.hpp" namespace ot { namespace Nexus { diff --git a/tests/nexus/test_nat64_translator.cpp b/tests/nexus/test_nat64_translator.cpp index bc9b61976..e1ab842b9 100644 --- a/tests/nexus/test_nat64_translator.cpp +++ b/tests/nexus/test_nat64_translator.cpp @@ -406,6 +406,9 @@ Message *PrepareIcmp6Message(Node &aNode, SuccessOrQuit(message->Append(static_cast(i & 0xff))); } + message->SetOffset(sizeof(Ip6::Header)); + Checksum::UpdateMessageChecksum(*message, ip6Header.GetSource(), ip6Header.GetDestination(), Ip6::kProtoIcmp6); + return message; }