diff --git a/examples/apps/cli/main.c b/examples/apps/cli/main.c index b6bd454d6..2ede597cd 100644 --- a/examples/apps/cli/main.c +++ b/examples/apps/cli/main.c @@ -72,6 +72,9 @@ static otError ProcessExit(void *aContext, uint8_t aArgsLength, char *aArgs[]) #if OPENTHREAD_EXAMPLES_SIMULATION extern otError ProcessNodeIdFilter(void *aContext, uint8_t aArgsLength, char *aArgs[]); +#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE +extern otError ProcessTrelTest(void *aContext, uint8_t aArgsLength, char *aArgs[]); +#endif #endif static const otCliCommand kCommands[] = { @@ -92,6 +95,9 @@ static const otCliCommand kCommands[] = { * - `nodeidfilter` : Outputs filter mode (allow-list or deny-list) and filtered node IDs. */ {"nodeidfilter", ProcessNodeIdFilter}, +#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE + {"treltest", ProcessTrelTest}, +#endif #endif }; #endif // OPENTHREAD_POSIX && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) diff --git a/examples/platforms/simulation/trel.c b/examples/platforms/simulation/trel.c index 00ea9e4f9..f412f7944 100644 --- a/examples/platforms/simulation/trel.c +++ b/examples/platforms/simulation/trel.c @@ -28,6 +28,7 @@ #include "platform-simulation.h" +#include #include #include @@ -60,6 +61,7 @@ typedef struct Message { MessageType mType; otSockAddr mSockAddr; // Destination (when TREL_DATA_MESSAGE), or peer addr (when DNS-SD service) + otSockAddr mSourceAddr; // Source address (used when TREL_DATA_MESSAGE) uint16_t mDataLength; // mData length uint8_t mData[TREL_MAX_PACKET_SIZE]; // TREL UDP packet (when TREL_DATA_MESSAGE), or service TXT data. } Message; @@ -70,12 +72,13 @@ static Message sPendingTx[TREL_MAX_PENDING_TX]; static utilsSocket sSocket; static uint16_t sPortOffset = 0; static bool sEnabled = false; +static otSockAddr sSockAddr; static bool sServiceRegistered = false; -static uint16_t sServicePort; static uint8_t sServiceTxtLength; static char sServiceTxtData[TREL_MAX_SERVICE_TXT_DATA_LEN]; static otPlatTrelCounters sCounters; +static uint16_t sNotifyAddressDiffCounter = 0; #if DEBUG_LOG static void dumpBuffer(const void *aBuffer, uint16_t aLength) @@ -158,15 +161,14 @@ static void sendServiceMessage(MessageType aType) assert(sNumPendingTx < TREL_MAX_PENDING_TX); message = &sPendingTx[sNumPendingTx++]; - message->mType = aType; - memset(&message->mSockAddr, 0, sizeof(otSockAddr)); - message->mSockAddr.mPort = sServicePort; - message->mDataLength = sServiceTxtLength; + message->mType = aType; + message->mSockAddr = sSockAddr; + message->mDataLength = sServiceTxtLength; memcpy(message->mData, sServiceTxtData, sServiceTxtLength); #if DEBUG_LOG fprintf(stderr, "\r\n[trel-sim] sendServiceMessage(%s): service-port:%u, txt-len:%u\r\n", - aType == TREL_DNSSD_ADD_SERVICE_MESSAGE ? "add" : "remove", sServicePort, sServiceTxtLength); + aType == TREL_DNSSD_ADD_SERVICE_MESSAGE ? "add" : "remove", sSockAddr.mPort, sServiceTxtLength); #endif } @@ -186,7 +188,8 @@ static void processMessage(otInstance *aInstance, Message *aMessage, uint16_t aL { case TREL_DATA_MESSAGE: otEXPECT(aMessage->mSockAddr.mPort == sSocket.mPort); - otPlatTrelHandleReceived(aInstance, aMessage->mData, aMessage->mDataLength); + otEXPECT(otIp6IsAddressEqual(&aMessage->mSockAddr.mAddress, &sSockAddr.mAddress)); + otPlatTrelHandleReceived(aInstance, aMessage->mData, aMessage->mDataLength, &aMessage->mSourceAddr); break; case TREL_DNSSD_BROWSE_MESSAGE: @@ -248,6 +251,21 @@ void otPlatTrelDisable(otInstance *aInstance) } } +void otPlatTrelNotifyPeerSocketAddressDifference(otInstance *aInstance, + const otSockAddr *aPeerSockAddr, + const otSockAddr *aRxSockAddr) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aPeerSockAddr); + OT_UNUSED_VARIABLE(aRxSockAddr); + + sNotifyAddressDiffCounter++; + +#if DEBUG_LOG + fprintf(stderr, "\r\n[trel-sim] otPlatTrelNotifyPeerSocketAddressDifference()\r\n"); +#endif +} + void otPlatTrelRegisterService(otInstance *aInstance, uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength) { OT_UNUSED_VARIABLE(aInstance); @@ -260,7 +278,7 @@ void otPlatTrelRegisterService(otInstance *aInstance, uint16_t aPort, const uint } sServiceRegistered = true; - sServicePort = aPort; + sSockAddr.mPort = aPort; sServiceTxtLength = aTxtLength; memcpy(sServiceTxtData, aTxtData, aTxtLength); @@ -289,6 +307,7 @@ void otPlatTrelSend(otInstance *aInstance, message->mType = TREL_DATA_MESSAGE; message->mSockAddr = *aDestSockAddr; + message->mSourceAddr = sSockAddr; message->mDataLength = aUdpPayloadLen; memcpy(message->mData, aUdpPayload, aUdpPayloadLen); @@ -325,6 +344,10 @@ void platformTrelInit(uint32_t aSpeedUpFactor) utilsInitSocket(&sSocket, TREL_SIM_PORT + sPortOffset); + memset(&sSockAddr, 0, sizeof(otSockAddr)); + sSockAddr.mAddress.mFields.m32[3] = gNodeId; + sSockAddr.mPort = sSocket.mPort; + OT_UNUSED_VARIABLE(aSpeedUpFactor); } @@ -378,14 +401,59 @@ void otPlatTrelResetCounters(otInstance *aInstance) memset(&sCounters, 0, sizeof(sCounters)); } +//--------------------------------------------------------------------------------------------------------------------- +// CLI `treltest` command + +OT_TOOL_WEAK void otCliOutputFormat(const char *aFmt, ...) { OT_UNUSED_VARIABLE(aFmt); } + +otError ProcessTrelTest(void *aContext, uint8_t aArgsLength, char *aArgs[]) +{ + OT_UNUSED_VARIABLE(aContext); + + otError error = OT_ERROR_NONE; + + otEXPECT_ACTION(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS); + + if (!strcmp(aArgs[0], "sockaddr")) + { + char string[OT_IP6_SOCK_ADDR_STRING_SIZE]; + + otIp6SockAddrToString(&sSockAddr, string, sizeof(string)); + otCliOutputFormat("%s\r\n", string); + } + else if (!strcmp(aArgs[0], "changesockaddr")) + { + sSockAddr.mAddress.mFields.m32[2]++; + } + else if (!strcmp(aArgs[0], "changesockport")) + { + sSockAddr.mPort++; + } + else if (!strcmp(aArgs[0], "notifyaddrcounter")) + { + otCliOutputFormat("%u\r\n", sNotifyAddressDiffCounter); + } + else + { + error = OT_ERROR_INVALID_COMMAND; + } + +exit: + return error; +} + //--------------------------------------------------------------------------------------------------------------------- // This is added for RCP build to be built ok -OT_TOOL_WEAK void otPlatTrelHandleReceived(otInstance *aInstance, uint8_t *aBuffer, uint16_t aLength) +OT_TOOL_WEAK void otPlatTrelHandleReceived(otInstance *aInstance, + uint8_t *aBuffer, + uint16_t aLength, + const otSockAddr *aSenderAddr) { OT_UNUSED_VARIABLE(aInstance); OT_UNUSED_VARIABLE(aBuffer); OT_UNUSED_VARIABLE(aLength); + OT_UNUSED_VARIABLE(aSenderAddr); assert(false); } @@ -398,4 +466,22 @@ OT_TOOL_WEAK void otPlatTrelHandleDiscoveredPeerInfo(otInstance *aInstance, cons assert(false); } +OT_TOOL_WEAK void otIp6SockAddrToString(const otSockAddr *aSockAddr, char *aBuffer, uint16_t aSize) +{ + OT_UNUSED_VARIABLE(aSockAddr); + OT_UNUSED_VARIABLE(aBuffer); + OT_UNUSED_VARIABLE(aSize); + + assert(false); +} + +OT_TOOL_WEAK bool otIp6IsAddressEqual(const otIp6Address *aFirst, const otIp6Address *aSecond) +{ + OT_UNUSED_VARIABLE(aFirst); + OT_UNUSED_VARIABLE(aSecond); + + assert(false); + return false; +} + #endif // OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE diff --git a/include/openthread/instance.h b/include/openthread/instance.h index e6cfce321..3517b85f1 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (467) +#define OPENTHREAD_API_VERSION (468) /** * @addtogroup api-instance diff --git a/include/openthread/platform/trel.h b/include/openthread/platform/trel.h index c953a5873..8c9ff64ef 100644 --- a/include/openthread/platform/trel.h +++ b/include/openthread/platform/trel.h @@ -135,6 +135,23 @@ typedef struct otPlatTrelPeerInfo */ extern void otPlatTrelHandleDiscoveredPeerInfo(otInstance *aInstance, const otPlatTrelPeerInfo *aInfo); +/** + * Notifies platform that a TREL packet is received from a peer using a different socket address than the one reported + * earlier from `otPlatTrelHandleDiscoveredPeerInfo()`. + * + * Ideally the platform underlying DNS-SD should detect changes to advertised port and addresses by peers, however, + * there are situations where this is not detected reliably. This function signals to the platform layer than we + * received a packet from a peer with it using a different port or address. This can be used by the playroom layer to + * restart/confirm the DNS-SD service/address resolution for the peer service and/or take any other relevant actions. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aPeerSockAddr The address of the peer, reported from `otPlatTrelHandleDiscoveredPeerInfo()` call. + * @param[in] aRxSockAddr The address of received packet from the same peer (differs from @p aPeerSockAddr). + */ +void otPlatTrelNotifyPeerSocketAddressDifference(otInstance *aInstance, + const otSockAddr *aPeerSockAddr, + const otSockAddr *aRxSockAddr); + /** * Registers a new service to be advertised using DNS-SD [RFC6763]. * @@ -181,8 +198,12 @@ void otPlatTrelSend(otInstance *aInstance, * @param[in] aInstance The OpenThread instance structure. * @param[in] aBuffer A buffer containing the received UDP payload. * @param[in] aLength UDP payload length (number of bytes). + * @param[in] aSockAddr The sender address. */ -extern void otPlatTrelHandleReceived(otInstance *aInstance, uint8_t *aBuffer, uint16_t aLength); +extern void otPlatTrelHandleReceived(otInstance *aInstance, + uint8_t *aBuffer, + uint16_t aLength, + const otSockAddr *aSenderAddr); /** * Represents a group of TREL related counters in the platform layer. diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp index ada1c9405..225ad30f5 100644 --- a/src/core/mac/mac.cpp +++ b/src/core/mac/mac.cpp @@ -2139,6 +2139,30 @@ exit: break; } } + +#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE +#if OPENTHREAD_CONFIG_MULTI_RADIO + if (aFrame->GetRadioType() == kRadioTypeTrel) +#endif + { + if (error == kErrorNone) + { + // If the received frame is using TREL and is successfully + // processed, check for any discrepancy between the socket + // address of the received TREL packet and the information + // saved in the corresponding TREL peer, and signal this to + // the platform layer. + // + // If the frame used link security and was successfully + // processed, we allow the `Peer` entry socket information + // to be updated directly. + + Get().CheckPeerAddrOnRxSuccess(aFrame->GetSecurityEnabled() + ? Trel::Link::kAllowPeerSockAddrUpdate + : Trel::Link::kDisallowPeerSockAddrUpdate); + } + } +#endif // OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE } void Mac::UpdateNeighborLinkInfo(Neighbor &aNeighbor, const RxFrame &aRxFrame) diff --git a/src/core/radio/trel_interface.cpp b/src/core/radio/trel_interface.cpp index 70df41f87..71f16f3fa 100644 --- a/src/core/radio/trel_interface.cpp +++ b/src/core/radio/trel_interface.cpp @@ -109,6 +109,16 @@ exit: return; } +Interface::Peer *Interface::FindPeer(const Mac::ExtAddress &aExtAddress) +{ + return mPeerTable.FindMatching(aExtAddress); +} + +void Interface::NotifyPeerSocketAddressDifference(const Ip6::SockAddr &aPeerSockAddr, const Ip6::SockAddr &aRxSockAddr) +{ + otPlatTrelNotifyPeerSocketAddressDifference(&GetInstance(), &aPeerSockAddr, &aRxSockAddr); +} + void Interface::HandleExtAddressChange(void) { VerifyOrExit(mInitialized && mEnabled); @@ -188,7 +198,7 @@ void Interface::HandleDiscoveredPeerInfo(const Peer::Info &aInfo) if (aInfo.IsRemoved()) { - entry = mPeerTable.FindMatching(extAddress); + entry = FindPeer(extAddress); VerifyOrExit(entry != nullptr); RemovePeerEntry(*entry); ExitNow(); @@ -380,25 +390,28 @@ exit: return error; } -extern "C" void otPlatTrelHandleReceived(otInstance *aInstance, uint8_t *aBuffer, uint16_t aLength) +extern "C" void otPlatTrelHandleReceived(otInstance *aInstance, + uint8_t *aBuffer, + uint16_t aLength, + const otSockAddr *aSenderAddress) { Instance &instance = AsCoreType(aInstance); VerifyOrExit(instance.IsInitialized()); - instance.Get().HandleReceived(aBuffer, aLength); + instance.Get().HandleReceived(aBuffer, aLength, AsCoreType(aSenderAddress)); exit: return; } -void Interface::HandleReceived(uint8_t *aBuffer, uint16_t aLength) +void Interface::HandleReceived(uint8_t *aBuffer, uint16_t aLength, const Ip6::SockAddr &aSenderAddr) { LogDebg("HandleReceived(aLength:%u)", aLength); VerifyOrExit(mInitialized && mEnabled && !mFiltered); mRxPacket.Init(aBuffer, aLength); - Get().ProcessReceivedPacket(mRxPacket); + Get().ProcessReceivedPacket(mRxPacket, aSenderAddr); exit: return; diff --git a/src/core/radio/trel_interface.hpp b/src/core/radio/trel_interface.hpp index 0b3a9984c..c87e14897 100644 --- a/src/core/radio/trel_interface.hpp +++ b/src/core/radio/trel_interface.hpp @@ -56,7 +56,10 @@ namespace Trel { class Link; -extern "C" void otPlatTrelHandleReceived(otInstance *aInstance, uint8_t *aBuffer, uint16_t aLength); +extern "C" void otPlatTrelHandleReceived(otInstance *aInstance, + uint8_t *aBuffer, + uint16_t aLength, + const otSockAddr *aSenderAddr); extern "C" void otPlatTrelHandleDiscoveredPeerInfo(otInstance *aInstance, const otPlatTrelPeerInfo *aInfo); /** @@ -70,7 +73,10 @@ typedef otTrelCounters Counters; class Interface : public InstanceLocator { friend class Link; - friend void otPlatTrelHandleReceived(otInstance *aInstance, uint8_t *aBuffer, uint16_t aLength); + friend void otPlatTrelHandleReceived(otInstance *aInstance, + uint8_t *aBuffer, + uint16_t aLength, + const otSockAddr *aSenderAddr); friend void otPlatTrelHandleDiscoveredPeerInfo(otInstance *aInstance, const otPlatTrelPeerInfo *aInfo); public: @@ -103,10 +109,17 @@ public: /** * Returns the IPv6 socket address of the discovered TREL peer. * - * @returns The IPv6 socket address of the TREP peer. + * @returns The IPv6 socket address of the TREL peer. */ const Ip6::SockAddr &GetSockAddr(void) const { return static_cast(mSockAddr); } + /** + * Set the IPv6 socket address of the discovered TREL peer. + * + * @param[in] aSockAddr The IPv6 socket address. + */ + void SetSockAddr(const Ip6::SockAddr &aSockAddr) { mSockAddr = aSockAddr; } + /** * Indicates whether the peer matches a given Extended Address. * @@ -139,7 +152,6 @@ public: void SetExtAddress(const Mac::ExtAddress &aExtAddress) { mExtAddress = aExtAddress; } void SetExtPanId(const MeshCoP::ExtendedPanId &aExtPanId) { mExtPanId = aExtPanId; } - void SetSockAddr(const Ip6::SockAddr &aSockAddr) { mSockAddr = aSockAddr; } void Log(const char *aAction) const; }; @@ -244,6 +256,24 @@ public: */ uint16_t GetUdpPort(void) const { return mUdpPort; } + /** + * Finds the TREL peer associated with a given Extended Address. + * + * @param[in] aExtAddress The extended address. + * + * @returns The peer associated with @ aExtAddress, or `nullptr` if not found. + */ + Peer *FindPeer(const Mac::ExtAddress &aExtAddress); + + /** + * Notifies platform that a TREL packet is received from a peer using a different socket address than the one + * reported earlier. + * + * @param[in] aPeerSockAddr The previously reported peer sock addr. + * @param[in] aRxSockAddr The address of received packet from the same peer. + */ + void NotifyPeerSocketAddressDifference(const Ip6::SockAddr &aPeerSockAddr, const Ip6::SockAddr &aRxSockAddr); + private: #if OPENTHREAD_CONFIG_TREL_PEER_TABLE_SIZE != 0 static constexpr uint16_t kPeerTableSize = OPENTHREAD_CONFIG_TREL_PEER_TABLE_SIZE; @@ -265,7 +295,7 @@ private: Error Send(const Packet &aPacket, bool aIsDiscovery = false); // Callbacks from `otPlatTrel`. - void HandleReceived(uint8_t *aBuffer, uint16_t aLength); + void HandleReceived(uint8_t *aBuffer, uint16_t aLength, const Ip6::SockAddr &aSenderAddr); void HandleDiscoveredPeerInfo(const Peer::Info &aInfo); void RegisterService(void); diff --git a/src/core/radio/trel_link.cpp b/src/core/radio/trel_link.cpp index 4b29151d5..10a356e3a 100644 --- a/src/core/radio/trel_link.cpp +++ b/src/core/radio/trel_link.cpp @@ -314,7 +314,7 @@ exit: return; } -void Link::ProcessReceivedPacket(Packet &aPacket) +void Link::ProcessReceivedPacket(Packet &aPacket, const Ip6::SockAddr &aSockAddr) { Header::Type type; @@ -342,6 +342,9 @@ void Link::ProcessReceivedPacket(Packet &aPacket) // Drop packets originating from same device. VerifyOrExit(aPacket.GetHeader().GetSource() != Get().GetExtAddress()); + mRxPacketSenderAddr = aSockAddr; + mRxPacketPeer = Get().FindPeer(aPacket.GetHeader().GetSource()); + if (type != Header::kTypeBroadcast) { VerifyOrExit(aPacket.GetHeader().GetDestination() == Get().GetExtAddress()); @@ -371,10 +374,44 @@ void Link::ProcessReceivedPacket(Packet &aPacket) mRxFrame.mInfo.mRxInfo.mLqi = OT_RADIO_LQI_NONE; mRxFrame.mInfo.mRxInfo.mAckedWithFramePending = true; + // As the received frame is processed by the MAC or MLE layers, + // `CheckPeerAddrOnRxSuccess()` may be called with different modes, + // depending on whether the frame passes receive security checks + // at either the MAC or MLE layers, allowing or disallowing peer + // socket address to be updated from received TREL packet info. + Get().HandleReceivedFrame(&mRxFrame, kErrorNone); exit: - return; + mRxPacketPeer = nullptr; +} + +void Link::CheckPeerAddrOnRxSuccess(PeerSockAddrUpdateMode aMode) +{ + Ip6::SockAddr prevSockAddr; + + VerifyOrExit(mState != kStateDisabled); + + VerifyOrExit(mRxPacketPeer != nullptr); + + prevSockAddr = mRxPacketPeer->GetSockAddr(); + VerifyOrExit(prevSockAddr != mRxPacketSenderAddr); + + LogNote("Peer %s rx sock-addr differs the previously saved one", + mRxPacketPeer->GetExtAddress().ToString().AsCString()); + LogNote(" Rcvd sock-addr:%s", mRxPacketSenderAddr.ToString().AsCString()); + LogNote(" Prev sock-addr:%s", prevSockAddr.ToString().AsCString()); + + if (aMode == kAllowPeerSockAddrUpdate) + { + LogNote("Updating the peer sock-addr to the newly received"); + mRxPacketPeer->SetSockAddr(mRxPacketSenderAddr); + } + + Get().NotifyPeerSocketAddressDifference(prevSockAddr, mRxPacketSenderAddr); + +exit: + mRxPacketPeer = nullptr; } void Link::HandleAck(Packet &aAckPacket) @@ -412,6 +449,8 @@ void Link::HandleAck(Packet &aAckPacket) } while (ackError == kErrorNoAck); + CheckPeerAddrOnRxSuccess(kDisallowPeerSockAddrUpdate); + exit: return; } diff --git a/src/core/radio/trel_link.hpp b/src/core/radio/trel_link.hpp index 3722c1e64..f5ccf9857 100644 --- a/src/core/radio/trel_link.hpp +++ b/src/core/radio/trel_link.hpp @@ -76,6 +76,16 @@ public: static constexpr uint16_t kMtuSize = 1280 - 48 - sizeof(Header); ///< MTU size for TREL frame. static constexpr uint8_t kFcsSize = 0; ///< FCS size for TREL frame. + /** + * Used as input by `CheckPeerAddrOnRxSuccess()` to determine whether the peer socket address can be updated based + * on a received TREL packet from the peer if there is a discrepancy. + */ + enum PeerSockAddrUpdateMode : uint8_t + { + kAllowPeerSockAddrUpdate, ///< Peer socket address can be updated. + kDisallowPeerSockAddrUpdate, ///< Peer socket address cannot be updated. + }; + /** * Initializes the `Link` object. * @@ -137,6 +147,17 @@ public: */ void Send(void); + /** + * Checks the address/port from the last received TREL packet against the ones recorded in the corresponding `Peer` + * entry and acts if there is a discrepancy. + * + * This method signals to the platform about the discrepancy. Based on @p aMode, it may also update the `Peer` + * entry information directly to match the new address/port information. + * + * @param[in] aMode Determines whether to update the `Peer` entry if there is a discrepancy. + */ + void CheckPeerAddrOnRxSuccess(PeerSockAddrUpdateMode aMode); + private: static constexpr uint16_t kMaxHeaderSize = sizeof(Header); static constexpr uint16_t k154AckFrameSize = 3 + kFcsSize; @@ -144,6 +165,8 @@ private: static constexpr uint32_t kAckWaitWindow = 750; // (in msec) static constexpr uint16_t kFcfFramePending = 1 << 4; + typedef Interface::Peer Peer; + enum State : uint8_t { kStateDisabled, @@ -157,7 +180,7 @@ private: void BeginTransmit(void); void InvokeSendDone(Error aError) { InvokeSendDone(aError, nullptr); } void InvokeSendDone(Error aError, Mac::RxFrame *aAckFrame); - void ProcessReceivedPacket(Packet &aPacket); + void ProcessReceivedPacket(Packet &aPacket, const Ip6::SockAddr &aSockAddr); void HandleAck(Packet &aAckPacket); void SendAck(Packet &aRxPacket); void ReportDeferredAckStatus(Neighbor &aNeighbor, Error aError); @@ -171,18 +194,20 @@ private: using TxTasklet = TaskletIn; using TimeoutTimer = TimerMilliIn; - State mState; - uint8_t mRxChannel; - Mac::PanId mPanId; - uint32_t mTxPacketNumber; - TxTasklet mTxTasklet; - TimeoutTimer mTimer; - Interface mInterface; - Mac::RxFrame mRxFrame; - Mac::TxFrame mTxFrame; - uint8_t mTxPacketBuffer[kMaxHeaderSize + kMtuSize]; - uint8_t mAckPacketBuffer[kMaxHeaderSize]; - uint8_t mAckFrameBuffer[k154AckFrameSize]; + State mState; + uint8_t mRxChannel; + Mac::PanId mPanId; + uint32_t mTxPacketNumber; + TxTasklet mTxTasklet; + TimeoutTimer mTimer; + Interface mInterface; + Ip6::SockAddr mRxPacketSenderAddr; + Peer *mRxPacketPeer; + Mac::RxFrame mRxFrame; + Mac::TxFrame mTxFrame; + uint8_t mTxPacketBuffer[kMaxHeaderSize + kMtuSize]; + uint8_t mAckPacketBuffer[kMaxHeaderSize]; + uint8_t mAckFrameBuffer[k154AckFrameSize]; }; /** diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 6c15cc123..7831ee7a5 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -2441,12 +2441,17 @@ void Mle::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageIn OT_ASSERT(aMessage.IsRadioTypeSet()); Get().UpdateOnReceive(*neighbor, aMessage.GetRadioType(), /* IsDuplicate */ true); +#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE + CheckTrelPeerAddrOnSecureMleRx(aMessage); +#endif + // We intentionally exit without setting the error to // skip logging "Failed to process UDP" at the exit // label. Note that in multi-radio mode, receiving // duplicate MLE message (with one-off counter) would // be common and ok for broadcast MLE messages (e.g. // MLE Link Advertisements). + ExitNow(); } #endif @@ -2463,6 +2468,10 @@ void Mle::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageIn neighbor->SetMleFrameCounter(frameCounter + 1); } +#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE + CheckTrelPeerAddrOnSecureMleRx(aMessage); +#endif + #if OPENTHREAD_CONFIG_MULTI_RADIO if (neighbor != nullptr) { @@ -2673,6 +2682,20 @@ exit: return; } +#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE +void Mle::CheckTrelPeerAddrOnSecureMleRx(const Message &aMessage) +{ + OT_UNUSED_VARIABLE(aMessage); + +#if OPENTHREAD_CONFIG_MULTI_RADIO + if (aMessage.IsRadioTypeSet() && aMessage.GetRadioType() == Mac::kRadioTypeTrel) +#endif + { + Get().CheckPeerAddrOnRxSuccess(Trel::Link::kAllowPeerSockAddrUpdate); + } +} +#endif + void Mle::ReestablishLinkWithNeighbor(Neighbor &aNeighbor) { VerifyOrExit(IsAttached() && aNeighbor.IsStateValid()); diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index 32f81462c..8b5ec5241 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -1383,6 +1383,10 @@ private: void UpdateServiceAlocs(void); #endif +#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE + void CheckTrelPeerAddrOnSecureMleRx(const Message &aMessage); +#endif + #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE void HandleTimeSync(RxInfo &aRxInfo); #endif diff --git a/src/posix/platform/trel.cpp b/src/posix/platform/trel.cpp index 5103ef4dd..758a30288 100644 --- a/src/posix/platform/trel.cpp +++ b/src/posix/platform/trel.cpp @@ -285,9 +285,15 @@ static void ReceivePacket(int aSocket, otInstance *aInstance) if (sEnabled) { + otSockAddr senderAddr; + ++sCounters.mRxPackets; sCounters.mRxBytes += sRxPacketLength; - otPlatTrelHandleReceived(aInstance, sRxPacketBuffer, sRxPacketLength); + + memcpy(&senderAddr.mAddress, &sockAddr.sin6_addr, sizeof(otIp6Address)); + senderAddr.mPort = ntohs(sockAddr.sin6_port); + + otPlatTrelHandleReceived(aInstance, sRxPacketBuffer, sRxPacketLength, &senderAddr); } } @@ -423,6 +429,25 @@ OT_TOOL_WEAK void trelDnssdStopBrowse(void) // earlier call to `trelDnssdStartBrowse()`. } +OT_TOOL_WEAK void trelDnssdNotifyPeerSocketAddressDifference(const otSockAddr *aPeerSockAddr, + const otSockAddr *aRxSockAddr) +{ + // Notifies platform that a TREL packet was received from a previously + // discovered peer with `aPeerSockAddr` now using a different socket + // address `aRxSockAddr` compared to the one reported earlier by DNS-SD + // using the `otPlatTrelHandleDiscoveredPeerInfo()` callback. + // + // Ideally the platform DNS-SD should detect changes to advertised port + // and addresses by peers, however, there are situations where this is + // not detected reliably. This function signals to that we received a + // packet from a peer with it using a different port or address. This can + // be used to restart/confirm the DNS-SD service/address resolution for + // the peer service and/or take any other relevant actions. + + OT_UNUSED_VARIABLE(aPeerSockAddr); + OT_UNUSED_VARIABLE(aRxSockAddr); +} + OT_TOOL_WEAK void trelDnssdRegisterService(uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength) { // This function registers a new service to be advertised using @@ -542,6 +567,15 @@ exit: return; } +void otPlatTrelNotifyPeerSocketAddressDifference(otInstance *aInstance, + const otSockAddr *aPeerSockAddr, + const otSockAddr *aRxSockAddr) +{ + OT_UNUSED_VARIABLE(aInstance); + + trelDnssdNotifyPeerSocketAddressDifference(aPeerSockAddr, aRxSockAddr); +} + void otPlatTrelRegisterService(otInstance *aInstance, uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength) { OT_UNUSED_VARIABLE(aInstance); diff --git a/tests/gtest/fake_platform.cpp b/tests/gtest/fake_platform.cpp index 34f23f696..8bcae83a7 100644 --- a/tests/gtest/fake_platform.cpp +++ b/tests/gtest/fake_platform.cpp @@ -554,10 +554,11 @@ void otPlatFlashWrite(otInstance *, uint8_t aSwapIndex, uint32_t aOffset, const FakePlatform::CurrentPlatform().FlashWrite(aSwapIndex, aOffset, aData, aSize); } -void otPlatTrelEnable(otInstance *, uint16_t *) {} -void otPlatTrelDisable(otInstance *) {} -void otPlatTrelRegisterService(otInstance *, uint16_t, const uint8_t *, uint8_t) {} -void otPlatTrelSend(otInstance *, const uint8_t *, uint16_t, const otSockAddr *) {} +void otPlatTrelEnable(otInstance *, uint16_t *) {} +void otPlatTrelDisable(otInstance *) {} +void otPlatTrelNotifyPeerSocketAddressDifference(otInstance *, const otSockAddr *, const otSockAddr *) {} +void otPlatTrelRegisterService(otInstance *, uint16_t, const uint8_t *, uint8_t) {} +void otPlatTrelSend(otInstance *, const uint8_t *, uint16_t, const otSockAddr *) {} const otPlatTrelCounters *otPlatTrelGetCounters(otInstance *) { return nullptr; } void otPlatTrelResetCounters(otInstance *) {} diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index 921f5eb43..cb2a4ce65 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -844,6 +844,25 @@ class Node(object): def br_count_peers(self): return self._cli_single_output('br peers count') + #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # trel + + def trel_get_peers(self): + peers = self.cli('trel peers ') + return Node.parse_table(peers) + + def trel_test_get_sock_addr(self): + return self._cli_single_output('treltest sockaddr') + + def trel_test_change_sock_addr(self): + return self._cli_no_output('treltest changesockaddr') + + def trel_test_change_sock_port(self): + return self._cli_no_output('treltest changesockport') + + def trel_test_get_notify_addr_counter(self): + return self._cli_single_output('treltest notifyaddrcounter') + # ------------------------------------------------------------------------------------------------------------------ # Helper methods diff --git a/tests/toranj/cli/test-706-multi-radio-trel-peer-addr-port-change-discovery.py b/tests/toranj/cli/test-706-multi-radio-trel-peer-addr-port-change-discovery.py new file mode 100755 index 000000000..32b3a9395 --- /dev/null +++ b/tests/toranj/cli/test-706-multi-radio-trel-peer-addr-port-change-discovery.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024, 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. + +from cli import verify +from cli import verify_within +import cli +import time + +# ----------------------------------------------------------------------------------------------------------------------- +# Test description: This test validates the mechanism to discover and update the TREL peer info when processing +# a received TREL packet from a peer (when peer IPv6 address or port number gets changes). +# + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `cli.Node` instances + +speedup = 10 +cli.Node.set_time_speedup_factor(speedup) + +r1 = cli.Node(cli.RADIO_15_4_TREL) +r2 = cli.Node(cli.RADIO_15_4_TREL) +c1 = cli.Node(cli.RADIO_15_4_TREL) + +# ----------------------------------------------------------------------------------------------------------------------- +# Build network topology + +r1.form("trel-peer-disc") +c1.join(r1, cli.JOIN_TYPE_REED) +r2.join(r1) + +verify(r1.get_state() == 'leader') +verify(r2.get_state() == 'router') +verify(c1.get_state() == 'child') + +verify(r1.multiradio_get_radios() == '[15.4, TREL]') +verify(r2.multiradio_get_radios() == '[15.4, TREL]') +verify(c1.multiradio_get_radios() == '[15.4, TREL]') + +# ----------------------------------------------------------------------------------------------------------------------- +# Test Implementation + + +def check_trel_peers(node, peer_nodes): + # Validate that `node` has discovered `peer_nodes` as its TREL peers + # and validate that the correct TREL socket address is discovered + # and used for each. + peers = node.trel_get_peers() + verify(len(peers) == len(peer_nodes)) + peers_ext_addrs = [nd.get_ext_addr() for nd in peer_nodes] + for peer in peers: + for peer_node in peer_nodes: + if peer['Ext MAC Address'] == peer_node.get_ext_addr(): + verify(peer_node.trel_test_get_sock_addr() == peer['IPv6 Socket Address']) + break + else: + verify(False) # Did not find peer in `peer_nodes` + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Check that all nodes see each other as TREL peers with the correct +# TREL socket address. + +check_trel_peers(r1, [r2, c1]) +check_trel_peers(r2, [r1, c1]) +check_trel_peers(c1, [r1, r2]) + +verify(int(r1.trel_test_get_notify_addr_counter()) == 0) +verify(int(r2.trel_test_get_notify_addr_counter()) == 0) +verify(int(c1.trel_test_get_notify_addr_counter()) == 0) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Use test CLI command to force `r2` to change its TREL socket +# address (IPv6 address only). + +time.sleep(10 / speedup) + +old_sock_addr = r2.trel_test_get_sock_addr() +r2.trel_test_change_sock_addr() +verify(r2.trel_test_get_sock_addr() != old_sock_addr) + +# Wait for longer than the Link Advertisement interval for `r2` to +# send an advertisement. Other nodes that receive and process this +# adv should then update the socket address of `r2` in their TREL +# peer table. + +time.sleep(35 / speedup) + +check_trel_peers(r1, [r2, c1]) +check_trel_peers(r2, [r1, c1]) +check_trel_peers(c1, [r1, r2]) + +# Validate that the platform is notified of the socket address +# discrepancy on `r1` and `c1`. + +verify(int(r1.trel_test_get_notify_addr_counter()) == 1) +verify(int(r2.trel_test_get_notify_addr_counter()) == 0) +verify(int(c1.trel_test_get_notify_addr_counter()) == 1) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Use test CLI command to force `r2` to change its TREL socket +# address port. + +time.sleep(10 / speedup) + +old_sock_addr = r2.trel_test_get_sock_addr() +r2.trel_test_change_sock_port() +verify(r2.trel_test_get_sock_addr() != old_sock_addr) + +# Wait for longer than the Link Advertisement interval for `r2` to +# send an advertisement. Other nodes that receive and process this +# adv should then update the socket address of `r2` in their TREL +# peer table. + +time.sleep(35 / speedup) + +check_trel_peers(r1, [r2, c1]) +check_trel_peers(r2, [r1, c1]) +check_trel_peers(c1, [r1, r2]) + +# Validate that the platform is notified of the socket address +# discrepancy on `r1` and `c1`. + +verify(int(r1.trel_test_get_notify_addr_counter()) == 2) +verify(int(r2.trel_test_get_notify_addr_counter()) == 0) +verify(int(c1.trel_test_get_notify_addr_counter()) == 2) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Use test CLI command to force `c1` to change its TREL socket +# address (IPv6 address only). + +old_sock_addr = c1.trel_test_get_sock_addr() +c1.trel_test_change_sock_addr() +verify(c1.trel_test_get_sock_addr() != old_sock_addr) + +# Send a ping from `c1` to `r1` to trigger communication +# between `r1` and `c1`. The receipt of a secure data frame +# from `c1` should update the TREL socket address on `r1` + +c1.ping(r1.get_rloc_ip_addr()) + +check_trel_peers(r1, [r2, c1]) +verify(int(r1.trel_test_get_notify_addr_counter()) == 3) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test finished + +cli.Node.finalize_all_nodes() + +print('\'{}\' passed.'.format(test_name)) diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index 3d9add9c3..5a50b3f0a 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -161,6 +161,7 @@ if [ "$TORANJ_CLI" = 1 ]; then run cli/test-703-multi-radio-mesh-header-msg.py run cli/test-704-multi-radio-scan.py run cli/test-705-multi-radio-discover-scan.py + run cli/test-706-multi-radio-trel-peer-addr-port-change-discovery.py exit 0 fi diff --git a/tests/unit/test_platform.cpp b/tests/unit/test_platform.cpp index ac51e0718..f5a246f22 100644 --- a/tests/unit/test_platform.cpp +++ b/tests/unit/test_platform.cpp @@ -450,6 +450,8 @@ OT_TOOL_WEAK void otPlatTrelDisable(otInstance *) {} OT_TOOL_WEAK void otPlatTrelSend(otInstance *, const uint8_t *, uint16_t, const otSockAddr *) {} +OT_TOOL_WEAK void otPlatTrelNotifyPeerSocketAddressDifference(otInstance *, const otSockAddr *, const otSockAddr *) {} + OT_TOOL_WEAK void otPlatTrelRegisterService(otInstance *, uint16_t, const uint8_t *, uint8_t) {} OT_TOOL_WEAK const otPlatTrelCounters *otPlatTrelGetCounters(otInstance *) { return nullptr; }