[nexus] implement platform UDP support (#12822)

This commit implements the otPlatUdp API for the Nexus simulation
environment and updates core UDP logic to facilitate it.

Changes in src/core:
- Initialize mHandle in Udp::Open with the current Instance pointer
  when Nexus platform UDP is enabled. This allows the platform UDP
  implementation to retrieve the instance context directly from the
  otUdpSocket handle.

Changes in Nexus platform:
- Implement otPlatUdp API in nexus_udp.cpp/hpp. The implementation
  routes UDP traffic through the simulated infrastructure interface
  (InfraIf).
- Integrate Udp class into Nexus::Node and Nexus::Platform.
- Update InfraIf::Receive to dispatch incoming UDP packets to the
  new platform UDP implementation.
- Enable OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE and related configs
  in Nexus.

Changes in Nexus tests:
- Update test_border_admitter, test_border_agent, test_dtls, and
  test_1_4_DNS_TC_1 to align with the new platform UDP and address/
  netif usage.
- Add nexus_udp.cpp to CMakeLists.txt.
This commit is contained in:
Jonathan Hui
2026-04-06 15:11:58 -05:00
committed by GitHub
parent bf6da19fbe
commit 5439f80c7b
10 changed files with 430 additions and 4 deletions
+3
View File
@@ -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;
+1
View File
@@ -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
)
@@ -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
+30
View File
@@ -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<ThreadNetif>().HasUnicastAddress(aAddress))
{
matchedNode = &node;
break;
}
}
return matchedNode;
}
Node *Core::FindNodeByInfraIfAddress(const Ip6::Address &aAddress)
{
Node *matchedNode = nullptr;
+5 -2
View File
@@ -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,
+5
View File
@@ -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).
+4
View File
@@ -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
+306
View File
@@ -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 <openthread/platform/udp.h>
#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<Mle::Mle>().IsAttached() &&
GetNode().Get<NetworkData::Leader>().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<Mle::Mle>().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<Ip6::Udp>().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<ThreadNetif>().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<Mle::Mle>().IsAttached() &&
GetNode().Get<NetworkData::Leader>().IsOnMesh(aHeaders.GetSourceAddress()))
{
ExitNow();
}
for (Ip6::Udp::SocketHandle *socket = GetNode().Get<Ip6::Udp>().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<kNoReservedHeader>();
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<otInstance *>(aUdpSocket->mHandle)); }
Node &Udp::GetNode(void) { return static_cast<Node &>(GetInstance()); }
const Node &Udp::GetNode(void) const { return static_cast<const Node &>(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"
+70
View File
@@ -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_
+2 -2
View File
@@ -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);