Files
Abtin Keshavarzian d1147d62b9 [trel] detect and handle socket addr discrepancy for TREL peers (#10869)
This commit introduces a new mechanism for the TREL link to detect and
handle discrepancies between the IPv6 address and port used by a TREL
peer in a received TREL packet, and the information previously
reported by the platform layer (through DNS-SD discovery) for the
same peer.

Ideally, the platform underlying DNS-SD should detect changes to
advertised ports and addresses by peers. However, there are
situations where this is not detected reliably.

As a received frame over the TREL radio link is processed by the MAC
or MLE layers, if the frame passes receive security checks at either
layer (indicating it is a secure and authenticated/fresh frame from a
valid neighbor), the TREL peer socket address is automatically
updated from the received TREL packet info. This ensures the TREL
peer table is updated correctly if there are changes to TREL peer
addresses of valid Thread neighbors upon rx from such neighbor,
increasing the robustness of the TREL link.

This commit also introduces a new `otPlatTrel` platform API,
`otPlatTrelNotifyPeerSocketAddressDifference()`. The TREL
implementation now notifies the platform layer whenever it detects a
discrepancy in a TREL peer's socket address, regardless of whether
the peer table is automatically updated. This allows the platform
layer to take any appropriate action, such as restarting or
confirming DNS-SD service resolution query for the peer service
instance and/or address resolution query for its associated host
name.

This commit also adds a new test that validates the newly added
behavior, including the auto-update of peer table information and
notification of the platform through the new API, triggered by either
MLE or MAC messages over the TREL radio link.
2024-12-18 20:35:58 -08:00

488 lines
14 KiB
C

/*
* Copyright (c) 2019-21, 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 "platform-simulation.h"
#include <openthread/ip6.h>
#include <openthread/random_noncrypto.h>
#include <openthread/platform/trel.h>
#include "simul_utils.h"
#include "lib/platform/exit_code.h"
#include "utils/code_utils.h"
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
// Change DEBUG_LOG to all extra logging
#define DEBUG_LOG 0
#define TREL_SIM_PORT 9200
#define TREL_MAX_PACKET_SIZE 1800
#define TREL_MAX_PENDING_TX 64
#define TREL_MAX_SERVICE_TXT_DATA_LEN 128
typedef enum MessageType
{
TREL_DATA_MESSAGE,
TREL_DNSSD_BROWSE_MESSAGE,
TREL_DNSSD_ADD_SERVICE_MESSAGE,
TREL_DNSSD_REMOVE_SERVICE_MESSAGE,
} MessageType;
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;
static uint8_t sNumPendingTx = 0;
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 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)
{
const uint8_t *buffer = (const uint8_t *)aBuffer;
fprintf(stderr, "[ (len:%d) ", aLength);
while (aLength--)
{
fprintf(stderr, "%02x ", *buffer++);
}
fprintf(stderr, "]");
}
static const char *messageTypeToString(MessageType aType)
{
const char *str = "unknown";
switch (aType)
{
case TREL_DATA_MESSAGE:
str = "data";
break;
case TREL_DNSSD_BROWSE_MESSAGE:
str = "browse";
break;
case TREL_DNSSD_ADD_SERVICE_MESSAGE:
str = "add-service";
break;
case TREL_DNSSD_REMOVE_SERVICE_MESSAGE:
str = "remove-service";
break;
}
return str;
}
#endif
static uint16_t getMessageSize(const Message *aMessage)
{
return (uint16_t)(&aMessage->mData[aMessage->mDataLength] - (const uint8_t *)aMessage);
}
static void sendPendingTxMessages(void)
{
for (uint8_t i = 0; i < sNumPendingTx; i++)
{
#if DEBUG_LOG
fprintf(stderr, "\r\n[trel-sim] Sending message (num:%d, type:%s, port:%u)\r\n", i,
messageTypeToString(sPendingTx[i].mType), sPendingTx[i].mSockAddr.mPort);
#endif
utilsSendOverSocket(&sSocket, &sPendingTx[i], getMessageSize(&sPendingTx[i]));
}
sNumPendingTx = 0;
}
static void sendBrowseMessage(void)
{
Message *message;
assert(sNumPendingTx < TREL_MAX_PENDING_TX);
message = &sPendingTx[sNumPendingTx++];
message->mType = TREL_DNSSD_BROWSE_MESSAGE;
message->mDataLength = 0;
#if DEBUG_LOG
fprintf(stderr, "\r\n[trel-sim] sendBrowseMessage()\r\n");
#endif
}
static void sendServiceMessage(MessageType aType)
{
Message *message;
assert((aType == TREL_DNSSD_ADD_SERVICE_MESSAGE) || (aType == TREL_DNSSD_REMOVE_SERVICE_MESSAGE));
assert(sNumPendingTx < TREL_MAX_PENDING_TX);
message = &sPendingTx[sNumPendingTx++];
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", sSockAddr.mPort, sServiceTxtLength);
#endif
}
static void processMessage(otInstance *aInstance, Message *aMessage, uint16_t aLength)
{
otPlatTrelPeerInfo peerInfo;
#if DEBUG_LOG
fprintf(stderr, "\r\n[trel-sim] processMessage(len:%u, type:%s, port:%u)\r\n", aLength,
messageTypeToString(aMessage->mType), aMessage->mSockAddr.mPort);
#endif
otEXPECT(aLength > 0);
otEXPECT(getMessageSize(aMessage) == aLength);
switch (aMessage->mType)
{
case TREL_DATA_MESSAGE:
otEXPECT(aMessage->mSockAddr.mPort == sSocket.mPort);
otEXPECT(otIp6IsAddressEqual(&aMessage->mSockAddr.mAddress, &sSockAddr.mAddress));
otPlatTrelHandleReceived(aInstance, aMessage->mData, aMessage->mDataLength, &aMessage->mSourceAddr);
break;
case TREL_DNSSD_BROWSE_MESSAGE:
sendServiceMessage(TREL_DNSSD_ADD_SERVICE_MESSAGE);
break;
case TREL_DNSSD_ADD_SERVICE_MESSAGE:
case TREL_DNSSD_REMOVE_SERVICE_MESSAGE:
memset(&peerInfo, 0, sizeof(peerInfo));
peerInfo.mRemoved = (aMessage->mType == TREL_DNSSD_REMOVE_SERVICE_MESSAGE);
peerInfo.mTxtData = aMessage->mData;
peerInfo.mTxtLength = (uint8_t)(aMessage->mDataLength);
peerInfo.mSockAddr = aMessage->mSockAddr;
otPlatTrelHandleDiscoveredPeerInfo(aInstance, &peerInfo);
break;
}
exit:
return;
}
//---------------------------------------------------------------------------------------------------------------------
// otPlatTrel
void otPlatTrelEnable(otInstance *aInstance, uint16_t *aUdpPort)
{
OT_UNUSED_VARIABLE(aInstance);
*aUdpPort = sSocket.mPort;
#if DEBUG_LOG
fprintf(stderr, "\r\n[trel-sim] otPlatTrelEnable() *aUdpPort=%u\r\n", *aUdpPort);
#endif
if (!sEnabled)
{
sEnabled = true;
sendBrowseMessage();
}
}
void otPlatTrelDisable(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
#if DEBUG_LOG
fprintf(stderr, "\r\n[trel-sim] otPlatTrelDisable()\r\n");
#endif
if (sEnabled)
{
sEnabled = false;
if (sServiceRegistered)
{
sendServiceMessage(TREL_DNSSD_REMOVE_SERVICE_MESSAGE);
sServiceRegistered = false;
}
}
}
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);
assert(aTxtLength <= TREL_MAX_SERVICE_TXT_DATA_LEN);
if (sServiceRegistered)
{
sendServiceMessage(TREL_DNSSD_REMOVE_SERVICE_MESSAGE);
}
sServiceRegistered = true;
sSockAddr.mPort = aPort;
sServiceTxtLength = aTxtLength;
memcpy(sServiceTxtData, aTxtData, aTxtLength);
sendServiceMessage(TREL_DNSSD_ADD_SERVICE_MESSAGE);
#if DEBUG_LOG
fprintf(stderr, "\r\n[trel-sim] otPlatTrelRegisterService(aPort:%d, aTxtData:", aPort);
dumpBuffer(aTxtData, aTxtLength);
fprintf(stderr, ")\r\n");
#endif
}
void otPlatTrelSend(otInstance *aInstance,
const uint8_t *aUdpPayload,
uint16_t aUdpPayloadLen,
const otSockAddr *aDestSockAddr)
{
OT_UNUSED_VARIABLE(aInstance);
Message *message;
assert(sNumPendingTx < TREL_MAX_PENDING_TX);
assert(aUdpPayloadLen <= TREL_MAX_PACKET_SIZE);
message = &sPendingTx[sNumPendingTx++];
message->mType = TREL_DATA_MESSAGE;
message->mSockAddr = *aDestSockAddr;
message->mSourceAddr = sSockAddr;
message->mDataLength = aUdpPayloadLen;
memcpy(message->mData, aUdpPayload, aUdpPayloadLen);
#if DEBUG_LOG
fprintf(stderr, "\r\n[trel-sim] otPlatTrelSend(len:%u, port:%u)\r\n", aUdpPayloadLen, aDestSockAddr->mPort);
#endif
++sCounters.mTxPackets;
sCounters.mTxBytes += aUdpPayloadLen;
}
//---------------------------------------------------------------------------------------------------------------------
// platformTrel system
void platformTrelInit(uint32_t aSpeedUpFactor)
{
char *str;
str = getenv("PORT_OFFSET");
if (str != NULL)
{
char *endptr;
sPortOffset = (uint16_t)strtol(str, &endptr, 0);
if (*endptr != '\0')
{
fprintf(stderr, "\r\nInvalid PORT_OFFSET: %s\r\n", str);
DieNow(OT_EXIT_FAILURE);
}
sPortOffset *= (MAX_NETWORK_SIZE + 1);
}
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);
}
void platformTrelDeinit(void) { utilsDeinitSocket(&sSocket); }
void platformTrelUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, struct timeval *aTimeout, int *aMaxFd)
{
OT_UNUSED_VARIABLE(aTimeout);
// Always ready to receive
utilsAddSocketRxFd(&sSocket, aReadFdSet, aMaxFd);
if (sNumPendingTx > 0)
{
utilsAddSocketTxFd(&sSocket, aWriteFdSet, aMaxFd);
}
}
void platformTrelProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet)
{
if ((sNumPendingTx > 0) && utilsCanSocketSend(&sSocket, aWriteFdSet))
{
sendPendingTxMessages();
}
if (utilsCanSocketReceive(&sSocket, aReadFdSet))
{
Message message;
uint16_t len;
message.mDataLength = 0;
len = utilsReceiveFromSocket(&sSocket, &message, sizeof(message), NULL);
if (len > 0)
{
processMessage(aInstance, &message, len);
}
}
}
const otPlatTrelCounters *otPlatTrelGetCounters(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return &sCounters;
}
void otPlatTrelResetCounters(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(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,
const otSockAddr *aSenderAddr)
{
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aBuffer);
OT_UNUSED_VARIABLE(aLength);
OT_UNUSED_VARIABLE(aSenderAddr);
assert(false);
}
OT_TOOL_WEAK void otPlatTrelHandleDiscoveredPeerInfo(otInstance *aInstance, const otPlatTrelPeerInfo *aInfo)
{
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aInfo);
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