diff --git a/src/core/net/udp6.cpp b/src/core/net/udp6.cpp index 1e2ce44f9..bb48debd7 100644 --- a/src/core/net/udp6.cpp +++ b/src/core/net/udp6.cpp @@ -223,6 +223,9 @@ Error Udp::Open(SocketHandle &aSocket, NetifIdentifier aNetifId, ReceiveHandler aSocket.Clear(); aSocket.SetNetifId(aNetifId); +#if OPENTHREAD_PLATFORM_NEXUS && OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE + aSocket.mHandle = &GetInstance(); +#endif aSocket.mHandler = aHandler; aSocket.mContext = aContext; diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 209163f64..8d63c34f4 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -53,6 +53,7 @@ add_library(ot-nexus-platform platform/nexus_radio.cpp platform/nexus_settings.cpp platform/nexus_trel.cpp + platform/nexus_udp.cpp ../../examples/platforms/utils/mac_frame.cpp ) diff --git a/tests/nexus/openthread-core-nexus-config.h b/tests/nexus/openthread-core-nexus-config.h index f964d7cb8..2728b117c 100644 --- a/tests/nexus/openthread-core-nexus-config.h +++ b/tests/nexus/openthread-core-nexus-config.h @@ -68,6 +68,7 @@ #define OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE 1 #define OPENTHREAD_CONFIG_DIAG_ENABLE 0 #define OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE 1 +#define OPENTHREAD_CONFIG_DNS_CLIENT_BIND_UDP_TO_THREAD_NETIF 1 #define OPENTHREAD_CONFIG_DNS_DSO_ENABLE 0 #define OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE 1 #define OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE 1 @@ -121,6 +122,9 @@ #define OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS 256 #define OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION 0 #define OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE 0 +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +#define OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE 1 +#endif #define OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE 0 #define OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE 0 #define OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE 0 diff --git a/tests/nexus/platform/nexus_core.cpp b/tests/nexus/platform/nexus_core.cpp index 1ad88e1b5..f5de1e2df 100644 --- a/tests/nexus/platform/nexus_core.cpp +++ b/tests/nexus/platform/nexus_core.cpp @@ -665,6 +665,36 @@ void Core::ProcessInfraIf(Node &aNode) } } +Node *Core::FindNodeByAddress(const Ip6::Address &aAddress) +{ + Node *matchedNode = FindNodeByThreadAddress(aAddress); + + if (matchedNode == nullptr) + { + matchedNode = FindNodeByInfraIfAddress(aAddress); + } + + return matchedNode; +} + +bool Core::IsThreadAddress(const Ip6::Address &aAddress) { return FindNodeByThreadAddress(aAddress) != nullptr; } + +Node *Core::FindNodeByThreadAddress(const Ip6::Address &aAddress) +{ + Node *matchedNode = nullptr; + + for (Node &node : mNodes) + { + if (node.Get().HasUnicastAddress(aAddress)) + { + matchedNode = &node; + break; + } + } + + return matchedNode; +} + Node *Core::FindNodeByInfraIfAddress(const Ip6::Address &aAddress) { Node *matchedNode = nullptr; diff --git a/tests/nexus/platform/nexus_core.hpp b/tests/nexus/platform/nexus_core.hpp index 2d54e83b6..224689f45 100644 --- a/tests/nexus/platform/nexus_core.hpp +++ b/tests/nexus/platform/nexus_core.hpp @@ -81,6 +81,11 @@ public: void UpdateNextAlarmMicro(const Alarm &aAlarm); void MarkPendingAction(void) { mPendingAction = true; } + Node *FindNodeByAddress(const Ip6::Address &aAddress); + bool IsThreadAddress(const Ip6::Address &aAddress); + Node *FindNodeByThreadAddress(const Ip6::Address &aAddress); + Node *FindNodeByInfraIfAddress(const Ip6::Address &aAddress); + private: static constexpr int8_t kDefaultRxRssi = -20; static constexpr uint8_t kDefaultRxLqi = 255; @@ -113,8 +118,6 @@ private: void ProcessRadio(Node &aNode); void ProcessInfraIf(Node &aNode); - Node *FindNodeByInfraIfAddress(const Ip6::Address &aAddress); - static void HandleIcmpResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, diff --git a/tests/nexus/platform/nexus_infra_if.cpp b/tests/nexus/platform/nexus_infra_if.cpp index 0656b6e70..5f13f72e8 100644 --- a/tests/nexus/platform/nexus_infra_if.cpp +++ b/tests/nexus/platform/nexus_infra_if.cpp @@ -486,6 +486,11 @@ void InfraIf::Receive(Message &aMessage) } #endif + if (headers.IsUdp() && node.mUdp.HandleReceive(aMessage, headers)) + { + ExitNow(); + } + { // We also deliver generic IPv6 packets to the stack if they are NOT ICMPv6 ND packets. // (ND packets were already delivered via otPlatInfraIfRecvIcmp6Nd above). diff --git a/tests/nexus/platform/nexus_node.hpp b/tests/nexus/platform/nexus_node.hpp index cd08cd789..cb3f0daac 100644 --- a/tests/nexus/platform/nexus_node.hpp +++ b/tests/nexus/platform/nexus_node.hpp @@ -39,6 +39,7 @@ #include "nexus_radio.hpp" #include "nexus_settings.hpp" #include "nexus_trel.hpp" +#include "nexus_udp.hpp" #include "nexus_utils.hpp" namespace ot { @@ -53,6 +54,7 @@ public: Logging mLogging; Mdns mMdns; InfraIf mInfraIf; + Udp mUdp; Settings mSettings; #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE Trel mTrel; @@ -62,6 +64,7 @@ public: protected: explicit Platform(Instance &aInstance) : mInfraIf(aInstance) + , mUdp(aInstance) , mPendingTasklet(false) { } @@ -142,6 +145,7 @@ public: using Platform::mPendingTasklet; using Platform::mRadio; using Platform::mSettings; + using Platform::mUdp; #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE using Platform::mTrel; #endif diff --git a/tests/nexus/platform/nexus_udp.cpp b/tests/nexus/platform/nexus_udp.cpp new file mode 100644 index 000000000..577e722dc --- /dev/null +++ b/tests/nexus/platform/nexus_udp.cpp @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2026, 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. + */ + +#include "nexus_udp.hpp" + +#include + +#include "nexus_core.hpp" +#include "nexus_node.hpp" +#include "common/logging.hpp" +#include "net/checksum.hpp" +#include "thread/network_data_leader.hpp" + +namespace ot { +namespace Nexus { + +Udp::Udp(Instance &aInstance) + : InstanceLocator(aInstance) +{ +} + +Error Udp::Socket(Ip6::Udp::SocketHandle &aSocket) +{ + OT_UNUSED_VARIABLE(aSocket); + return kErrorNone; +} + +Error Udp::Close(Ip6::Udp::SocketHandle &aSocket) +{ + OT_UNUSED_VARIABLE(aSocket); + return kErrorNone; +} + +Error Udp::Bind(Ip6::Udp::SocketHandle &aSocket) +{ + OT_UNUSED_VARIABLE(aSocket); + return kErrorNone; +} + +Error Udp::BindToNetif(Ip6::Udp::SocketHandle &aSocket, Ip6::NetifIdentifier aNetifIdentifier) +{ + OT_UNUSED_VARIABLE(aSocket); + OT_UNUSED_VARIABLE(aNetifIdentifier); + return kErrorNone; +} + +Error Udp::Connect(Ip6::Udp::SocketHandle &aSocket) +{ + OT_UNUSED_VARIABLE(aSocket); + return kErrorNone; +} + +Error Udp::Send(Ip6::Udp::SocketHandle &aSocket, Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + Error error = kErrorNone; + Ip6::Address srcAddr = aMessageInfo.GetSockAddr(); + Ip6::NetifIdentifier netifId = aSocket.GetNetifId(); + + if (netifId == Ip6::kNetifUnspecified) + { + if (aMessageInfo.IsHostInterface()) + { + netifId = Ip6::kNetifBackbone; + } + else if (aMessageInfo.GetPeerAddr().IsMulticast()) + { + netifId = Ip6::kNetifThreadHost; + } + else if (GetNode().Get().IsAttached() && + GetNode().Get().IsOnMesh(aMessageInfo.GetPeerAddr())) + { + // If we are attached and the peer is on-mesh, we use the Thread interface. + netifId = Ip6::kNetifThreadHost; + } + else if (Core::Get().FindNodeByInfraIfAddress(aMessageInfo.GetPeerAddr()) != nullptr) + { + netifId = Ip6::kNetifBackbone; + } + else + { + // For all other communication (e.g. unjoined nodes or external commissioners), + // we prefer using the Backbone interface for better reliability. + netifId = Ip6::kNetifBackbone; + } + } + + if (srcAddr.IsUnspecified() && (netifId == Ip6::kNetifBackbone)) + { + if (Core::Get().IsThreadAddress(aMessageInfo.GetPeerAddr())) + { + srcAddr = GetNode().Get().GetLinkLocalAddress(); + } + else + { + srcAddr = GetNode().mInfraIf.GetLinkLocalAddress(); + } + } + + if (netifId == Ip6::kNetifBackbone) + { + GetNode().mInfraIf.SendUdp(srcAddr, aMessageInfo.GetPeerAddr(), aMessageInfo.GetSockPort(), + aMessageInfo.GetPeerPort(), aMessage); + } + else + { + Ip6::MessageInfo messageInfo(aMessageInfo); + + error = GetNode().Get().SendDatagram(aMessage, messageInfo); + } + + return error; +} + +Error Udp::JoinMulticastGroup(Ip6::Udp::SocketHandle &aSocket, + Ip6::NetifIdentifier aNetifIdentifier, + const Ip6::Address &aAddress) +{ + OT_UNUSED_VARIABLE(aSocket); + OT_UNUSED_VARIABLE(aNetifIdentifier); + OT_UNUSED_VARIABLE(aAddress); + return kErrorNone; +} + +Error Udp::LeaveMulticastGroup(Ip6::Udp::SocketHandle &aSocket, + Ip6::NetifIdentifier aNetifIdentifier, + const Ip6::Address &aAddress) +{ + OT_UNUSED_VARIABLE(aSocket); + OT_UNUSED_VARIABLE(aNetifIdentifier); + OT_UNUSED_VARIABLE(aAddress); + return kErrorNone; +} + +bool Udp::HandleReceive(const Message &aMessage, const Ip6::Headers &aHeaders) +{ + bool handled = false; + bool isThreadSource = (Core::Get().IsThreadAddress(aHeaders.GetSourceAddress())); + + if (!GetNode().mInfraIf.HasAddress(aHeaders.GetDestinationAddress()) && + !GetNode().Get().HasUnicastAddress(aHeaders.GetDestinationAddress()) && + !aHeaders.GetDestinationAddress().IsMulticast()) + { + ExitNow(); + } + + // If the node is attached and the source is on-mesh, we expect the packet + // via Radio. We drop it here to prevent duplicate delivery when the + // packet is forwarded to the Infra interface. + if (GetNode().Get().IsAttached() && + GetNode().Get().IsOnMesh(aHeaders.GetSourceAddress())) + { + ExitNow(); + } + + for (Ip6::Udp::SocketHandle *socket = GetNode().Get().GetUdpSockets(); socket != nullptr; + socket = socket->GetNext()) + { + Ip6::MessageInfo messageInfo; + + if (!socket->ShouldUsePlatformUdp()) + { + continue; + } + + if (socket->GetSockName().GetPort() != aHeaders.GetDestinationPort()) + { + continue; + } + + if (socket->GetSockName().GetAddress().IsUnspecified() || + socket->GetSockName().GetAddress() == aHeaders.GetDestinationAddress()) + { + // Found a matching socket. + } + else + { + continue; + } + + if (socket->GetPeerName().GetPort() != 0) + { + if (socket->GetPeerName().GetPort() != aHeaders.GetSourcePort() || + socket->GetPeerName().GetAddress() != aHeaders.GetSourceAddress()) + { + continue; + } + } + + if (socket->mHandler == nullptr) + { + continue; + } + + messageInfo.SetPeerAddr(aHeaders.GetSourceAddress()); + messageInfo.SetPeerPort(aHeaders.GetSourcePort()); + messageInfo.SetSockAddr(aHeaders.GetDestinationAddress()); + messageInfo.SetSockPort(aHeaders.GetDestinationPort()); + messageInfo.SetHopLimit(aHeaders.GetIp6Header().GetHopLimit()); + messageInfo.SetIsHostInterface(!isThreadSource); + + { + Message *payload = aMessage.Clone(); + + VerifyOrExit(payload != nullptr); + payload->RemoveHeader(sizeof(Ip6::Header) + sizeof(Ip6::Udp::Header)); + + socket->mHandler(socket->mContext, payload, &messageInfo); + payload->Free(); + } + + handled = true; + if (!aHeaders.GetDestinationAddress().IsMulticast()) + { + break; + } + } + +exit: + return handled; +} + +Node &Udp::GetNode(otUdpSocket *aUdpSocket) { return AsNode(static_cast(aUdpSocket->mHandle)); } + +Node &Udp::GetNode(void) { return static_cast(GetInstance()); } + +const Node &Udp::GetNode(void) const { return static_cast(GetInstance()); } + +} // namespace Nexus +} // namespace ot + +extern "C" { + +otError otPlatUdpSocket(otUdpSocket *aUdpSocket) +{ + return ot::Nexus::Udp::GetNode(aUdpSocket).mUdp.Socket(ot::AsCoreType(aUdpSocket)); +} + +otError otPlatUdpClose(otUdpSocket *aUdpSocket) +{ + return ot::Nexus::Udp::GetNode(aUdpSocket).mUdp.Close(ot::AsCoreType(aUdpSocket)); +} + +otError otPlatUdpBind(otUdpSocket *aUdpSocket) +{ + return ot::Nexus::Udp::GetNode(aUdpSocket).mUdp.Bind(ot::AsCoreType(aUdpSocket)); +} + +otError otPlatUdpBindToNetif(otUdpSocket *aUdpSocket, otNetifIdentifier aNetifIdentifier) +{ + return ot::Nexus::Udp::GetNode(aUdpSocket) + .mUdp.BindToNetif(ot::AsCoreType(aUdpSocket), ot::MapEnum(aNetifIdentifier)); +} + +otError otPlatUdpConnect(otUdpSocket *aUdpSocket) +{ + return ot::Nexus::Udp::GetNode(aUdpSocket).mUdp.Connect(ot::AsCoreType(aUdpSocket)); +} + +otError otPlatUdpSend(otUdpSocket *aUdpSocket, otMessage *aMessage, const otMessageInfo *aMessageInfo) +{ + return ot::Nexus::Udp::GetNode(aUdpSocket) + .mUdp.Send(ot::AsCoreType(aUdpSocket), ot::AsCoreType(aMessage), ot::AsCoreType(aMessageInfo)); +} + +otError otPlatUdpJoinMulticastGroup(otUdpSocket *aUdpSocket, + otNetifIdentifier aNetifIdentifier, + const otIp6Address *aAddress) +{ + return ot::Nexus::Udp::GetNode(aUdpSocket) + .mUdp.JoinMulticastGroup(ot::AsCoreType(aUdpSocket), ot::MapEnum(aNetifIdentifier), ot::AsCoreType(aAddress)); +} + +otError otPlatUdpLeaveMulticastGroup(otUdpSocket *aUdpSocket, + otNetifIdentifier aNetifIdentifier, + const otIp6Address *aAddress) +{ + return ot::Nexus::Udp::GetNode(aUdpSocket) + .mUdp.LeaveMulticastGroup(ot::AsCoreType(aUdpSocket), ot::MapEnum(aNetifIdentifier), ot::AsCoreType(aAddress)); +} + +} // extern "C" diff --git a/tests/nexus/platform/nexus_udp.hpp b/tests/nexus/platform/nexus_udp.hpp new file mode 100644 index 000000000..d63d5c058 --- /dev/null +++ b/tests/nexus/platform/nexus_udp.hpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2026, 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_UDP_HPP_ +#define OT_NEXUS_PLATFORM_NEXUS_UDP_HPP_ + +#include "common/locator.hpp" +#include "instance/instance.hpp" + +struct otUdpSocket; + +namespace ot { +namespace Nexus { + +class Node; + +class Udp : public InstanceLocator +{ +public: + explicit Udp(Instance &aInstance); + + static Node &GetNode(otUdpSocket *aUdpSocket); + + Error Socket(Ip6::Udp::SocketHandle &aSocket); + Error Close(Ip6::Udp::SocketHandle &aSocket); + Error Bind(Ip6::Udp::SocketHandle &aSocket); + Error BindToNetif(Ip6::Udp::SocketHandle &aSocket, Ip6::NetifIdentifier aNetifIdentifier); + Error Connect(Ip6::Udp::SocketHandle &aSocket); + Error Send(Ip6::Udp::SocketHandle &aSocket, Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + Error JoinMulticastGroup(Ip6::Udp::SocketHandle &aSocket, + Ip6::NetifIdentifier aNetifIdentifier, + const Ip6::Address &aAddress); + Error LeaveMulticastGroup(Ip6::Udp::SocketHandle &aSocket, + Ip6::NetifIdentifier aNetifIdentifier, + const Ip6::Address &aAddress); + + bool HandleReceive(const Message &aMessage, const Ip6::Headers &aHeaders); + + Node &GetNode(void); + const Node &GetNode(void) const; +}; +} // namespace Nexus +} // namespace ot + +#endif // OT_NEXUS_PLATFORM_NEXUS_UDP_HPP_ diff --git a/tests/nexus/test_1_4_DNS_TC_1.cpp b/tests/nexus/test_1_4_DNS_TC_1.cpp index 554593303..dc411cdb8 100644 --- a/tests/nexus/test_1_4_DNS_TC_1.cpp +++ b/tests/nexus/test_1_4_DNS_TC_1.cpp @@ -62,7 +62,7 @@ static constexpr uint16_t kServicePort = 55556; void SendMultiQuestionQuery(Node &aNode, const Ip6::Address &aDest, const char *aName, uint16_t aType1, uint16_t aType2) { Ip6::Udp::Socket socket(aNode, nullptr, nullptr); - SuccessOrQuit(socket.Open(Ip6::kNetifUnspecified)); + SuccessOrQuit(socket.Open(Ip6::kNetifThreadInternal)); Message *message = socket.NewMessage(); VerifyOrQuit(message != nullptr); @@ -91,7 +91,7 @@ void SendMultiQuestionQuery(Node &aNode, const Ip6::Address &aDest, const char * void SendSingleQuestionQuery(Node &aNode, const Ip6::Address &aDest, const char *aName, uint16_t aType) { Ip6::Udp::Socket socket(aNode, nullptr, nullptr); - SuccessOrQuit(socket.Open(Ip6::kNetifUnspecified)); + SuccessOrQuit(socket.Open(Ip6::kNetifThreadInternal)); Message *message = socket.NewMessage(); VerifyOrQuit(message != nullptr);