[simulation] implement otPlatInfraIf APIs in simulation platform (#9895)

This commit implements a subset of `otPlatInfraIf` platform APIs in
simulation platforms using the `utilsSocket` to emulate communication
between BRs. This can be used for testing.

This commit also adds one (simple) example test case with two BRs
attached to two different network on same infra-if.
This commit is contained in:
Abtin Keshavarzian
2024-03-06 13:26:06 -08:00
committed by GitHub
parent 036f6eb649
commit 39fcc5ea53
6 changed files with 462 additions and 10 deletions
+287 -10
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, The OpenThread Authors.
* Copyright (c) 2024, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -28,15 +28,154 @@
#include "platform-simulation.h"
#include <openthread/icmp6.h>
#include <openthread/ip6.h>
#include <openthread/logging.h>
#include <openthread/platform/infra_if.h>
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
#include "simul_utils.h"
#include "utils/code_utils.h"
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE && !OPENTHREAD_RADIO
#define DEBUG_LOG 0
#if DEBUG_LOG
#define LOG(...) otLogNotePlat("[infra-if] "__VA_ARGS__)
#else
#define LOG(...) \
do \
{ \
} while (0)
#endif
#define INFRA_IF_SIM_PORT 9800
#define INFRA_IF_MAX_PACKET_SIZE 1800
#define INFRA_IF_MAX_PENDING_TX 64
#define INFRA_IF_NEIGHBOR_ADVERT_SIZE 24
typedef struct Message
{
uint32_t mIfIndex;
otIp6Address mSrc;
otIp6Address mDst;
uint16_t mDataLength;
uint8_t mData[INFRA_IF_MAX_PACKET_SIZE];
} Message;
static bool sInitialized = false;
static otIp6Address sIp6Address;
static otIp6Address sLinkLocalAllNodes;
static otIp6Address sLinkLocalAllRouters;
static utilsSocket sSocket;
static uint16_t sPortOffset = 0;
static uint8_t sNumPendingTx = 0;
static Message sPendingTx[INFRA_IF_MAX_PENDING_TX];
//---------------------------------------------------------------------------------------------------------------------
static bool addressesMatch(const otIp6Address *aFirstAddr, const otIp6Address *aSecondAddr)
{
return memcmp(aFirstAddr, aSecondAddr, sizeof(otIp6Address)) == 0;
}
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++)
{
utilsSendOverSocket(&sSocket, &sPendingTx[i], getMessageSize(&sPendingTx[i]));
}
sNumPendingTx = 0;
}
static void sendNeighborAdvert(const Message *aNsMessage)
{
Message *message;
uint8_t index;
assert(sNumPendingTx < INFRA_IF_MAX_PENDING_TX);
message = &sPendingTx[sNumPendingTx++];
message->mIfIndex = aNsMessage->mIfIndex;
message->mSrc = sIp6Address;
message->mDst = aNsMessage->mSrc;
// Neighbor Advertisement Message (RFC 4861)
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Type | Code | Checksum |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |R|S|O| Reserved |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// + +
// | |
// + Target Address +
// | |
// + +
// | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
index = 0;
memset(message->mData, 0, INFRA_IF_NEIGHBOR_ADVERT_SIZE);
message->mData[index++] = OT_ICMP6_TYPE_NEIGHBOR_ADVERT; // Type.
index += 3; // Code is zero. Checksum (uint16) as zero.
message->mData[index++] = 0xd0; // Flags, set R and S bits.
index += 3; // Skip over the reserved bytes.
memcpy(&message->mData[index], &sIp6Address, sizeof(sIp6Address)); // Set the target address field.
index += sizeof(sIp6Address);
assert(index == INFRA_IF_NEIGHBOR_ADVERT_SIZE);
message->mDataLength = INFRA_IF_NEIGHBOR_ADVERT_SIZE;
}
static void processMessage(otInstance *aInstance, Message *aMessage, uint16_t aLength)
{
OT_UNUSED_VARIABLE(aInstance);
otEXPECT(aLength > 0);
otEXPECT(getMessageSize(aMessage) == aLength);
otEXPECT(aMessage->mDataLength > 0);
// Validate the dest address.
otEXPECT(addressesMatch(&aMessage->mDst, &sIp6Address) || addressesMatch(&aMessage->mDst, &sLinkLocalAllNodes) ||
addressesMatch(&aMessage->mDst, &sLinkLocalAllRouters));
if (aMessage->mData[0] == OT_ICMP6_TYPE_NEIGHBOR_SOLICIT)
{
LOG("Received NS, responding with NA");
sendNeighborAdvert(aMessage);
}
else
{
LOG("Received msg, len:%u", aMessage->mDataLength);
otPlatInfraIfRecvIcmp6Nd(aInstance, aMessage->mIfIndex, &aMessage->mSrc, aMessage->mData,
aMessage->mDataLength);
}
exit:
return;
}
//---------------------------------------------------------------------------------------------------------------------
// otPlatInfraIf
bool otPlatInfraIfHasAddress(uint32_t aInfraIfIndex, const otIp6Address *aAddress)
{
OT_UNUSED_VARIABLE(aInfraIfIndex);
OT_UNUSED_VARIABLE(aAddress);
return false;
return addressesMatch(aAddress, &sIp6Address);
}
otError otPlatInfraIfSendIcmp6Nd(uint32_t aInfraIfIndex,
@@ -44,12 +183,27 @@ otError otPlatInfraIfSendIcmp6Nd(uint32_t aInfraIfIndex,
const uint8_t *aBuffer,
uint16_t aBufferLength)
{
OT_UNUSED_VARIABLE(aInfraIfIndex);
OT_UNUSED_VARIABLE(aDestAddress);
OT_UNUSED_VARIABLE(aBuffer);
OT_UNUSED_VARIABLE(aBufferLength);
otError error = OT_ERROR_FAILED;
Message *message;
return OT_ERROR_NONE;
otEXPECT(sInitialized);
otEXPECT(sNumPendingTx < INFRA_IF_MAX_PENDING_TX);
message = &sPendingTx[sNumPendingTx++];
message->mIfIndex = aInfraIfIndex;
message->mSrc = sIp6Address;
message->mDst = *aDestAddress;
assert(aBufferLength <= INFRA_IF_MAX_PACKET_SIZE);
message->mDataLength = aBufferLength;
memcpy(message->mData, aBuffer, aBufferLength);
error = OT_ERROR_NONE;
LOG("otPlatInfraIfSendIcmp6Nd() msg-len:%u", aBufferLength);
exit:
return error;
}
otError otPlatInfraIfDiscoverNat64Prefix(uint32_t aInfraIfIndex)
@@ -58,4 +212,127 @@ otError otPlatInfraIfDiscoverNat64Prefix(uint32_t aInfraIfIndex)
return OT_ERROR_NONE;
}
#endif
//---------------------------------------------------------------------------------------------------------------------
// platformInfraIf
void platformInfraIfInit(void)
{
char *str;
otEXPECT(!sInitialized);
sInitialized = true;
memset(&sIp6Address, 0, sizeof(sIp6Address));
sIp6Address.mFields.m8[0] = 0xfe;
sIp6Address.mFields.m8[1] = 0x80;
sIp6Address.mFields.m8[15] = (uint8_t)(gNodeId & 0xff);
// "ff02::01"
memset(&sLinkLocalAllNodes, 0, sizeof(sLinkLocalAllNodes));
sLinkLocalAllNodes.mFields.m8[0] = 0xff;
sLinkLocalAllNodes.mFields.m8[1] = 0x02;
sLinkLocalAllNodes.mFields.m8[15] = 0x01;
// "ff02::02"
memset(&sLinkLocalAllRouters, 0, sizeof(sLinkLocalAllRouters));
sLinkLocalAllRouters.mFields.m8[0] = 0xff;
sLinkLocalAllRouters.mFields.m8[1] = 0x02;
sLinkLocalAllRouters.mFields.m8[15] = 0x02;
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);
exit(EXIT_FAILURE);
}
sPortOffset *= (MAX_NETWORK_SIZE + 1);
}
utilsInitSocket(&sSocket, INFRA_IF_SIM_PORT + sPortOffset);
exit:
return;
}
void platformInfraIfDeinit(void)
{
otEXPECT(sInitialized);
sInitialized = false;
utilsDeinitSocket(&sSocket);
exit:
return;
}
void platformInfraIfUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd)
{
otEXPECT(sInitialized);
utilsAddSocketRxFd(&sSocket, aReadFdSet, aMaxFd);
if (sNumPendingTx > 0)
{
utilsAddSocketTxFd(&sSocket, aWriteFdSet, aMaxFd);
}
exit:
return;
}
void platformInfraIfProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet)
{
OT_UNUSED_VARIABLE(aInstance);
otEXPECT(sInitialized);
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);
processMessage(aInstance, &message, len);
}
exit:
return;
}
//---------------------------------------------------------------------------------------------------------------------
// Provide weak implementation (used for RCP builds).
// `OPENTHREAD_RADIO` is not available in simulation platform
OT_TOOL_WEAK void otPlatInfraIfRecvIcmp6Nd(otInstance *aInstance,
uint32_t aInfraIfIndex,
const otIp6Address *aSrcAddress,
const uint8_t *aBuffer,
uint16_t aBufferLength)
{
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aInfraIfIndex);
OT_UNUSED_VARIABLE(aSrcAddress);
OT_UNUSED_VARIABLE(aBuffer);
OT_UNUSED_VARIABLE(aBufferLength);
fprintf(stderr, "\n\r Weak otPlatInfraIfRecvIcmp6Nd is being used\n\r");
exit(1);
}
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
@@ -305,4 +305,40 @@ void platformTrelProcess(otInstance *aInstance, const fd_set *aReadFdSet, const
#endif // OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
/**
* Initializes the platform infra-if module.
*
*/
void platformInfraIfInit(void);
/**
* Shuts down the platform infra-if module.
*
*/
void platformInfraIfDeinit(void);
/**
* Updates the file descriptor sets with file descriptors used by the infra-if module
*
* @param[in,out] aReadFdSet A pointer to the read file descriptors.
* @param[in,out] aWriteFdSet A pointer to the write file descriptors.
* @param[in,out] aMaxFd A pointer to the max file descriptor.
*
*/
void platformInfraIfUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd);
/**
* Performs infra-if module processing.
*
* @param[in] aInstance The OpenThread instance structure.
* @param[in] aReadFdSet A pointer to the read file descriptors.
* @param[in] aWriteFdSet A pointer to the write file descriptors.
*
*/
void platformInfraIfProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet);
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
#endif // PLATFORM_SIMULATION_H_
+12
View File
@@ -188,6 +188,9 @@ void otSysInit(int aArgCount, char *aArgVector[])
platformRadioInit();
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
platformTrelInit(speedUpFactor);
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
platformInfraIfInit();
#endif
platformRandomInit();
}
@@ -199,6 +202,9 @@ void otSysDeinit(void)
platformRadioDeinit();
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
platformTrelDeinit();
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
// platformInfrIfDeinit();
#endif
platformLoggingDeinit();
}
@@ -222,6 +228,9 @@ void otSysProcessDrivers(otInstance *aInstance)
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
platformTrelUpdateFdSet(&read_fds, &write_fds, &timeout, &max_fd);
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
platformInfraIfUpdateFdSet(&read_fds, &write_fds, &max_fd);
#endif
if (otTaskletsArePending(aInstance))
{
@@ -246,6 +255,9 @@ void otSysProcessDrivers(otInstance *aInstance)
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
platformTrelProcess(aInstance, &read_fds, &write_fds);
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
platformInfraIfProcess(aInstance, &read_fds, &write_fds);
#endif
if (gTerminate)
{
+15
View File
@@ -760,6 +760,18 @@ class Node(object):
def br_get_state(self):
return self._cli_single_output('br state')
def br_get_favored_omrprefix(self):
return self._cli_single_output('br omrprefix favored')
def br_get_local_omrprefix(self):
return self._cli_single_output('br omrprefix local')
def br_get_favored_onlinkprefix(self):
return self._cli_single_output('br onlinkprefix favored')
def br_get_local_onlinkprefix(self):
return self._cli_single_output('br onlinkprefix local')
def br_get_routeprf(self):
return self._cli_single_output('br routeprf')
@@ -769,6 +781,9 @@ class Node(object):
def br_clear_routeprf(self):
self._cli_no_output('br routeprf clear')
def br_get_routers(self):
return self.cli('br routers')
# ------------------------------------------------------------------------------------------------------------------
# Helper methods
+111
View File
@@ -0,0 +1,111 @@
#!/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:
#
# Two BRs on two different Thread networks.
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Nodes` instances
speedup = 60
cli.Node.set_time_speedup_factor(speedup)
br1 = cli.Node()
br2 = cli.Node()
WAIT_TIME = 5
IF_INDEX = 1
# -----------------------------------------------------------------------------------------------------------------------
# Test implementation
# Start first BR with its own Thread network
br1.form('net1')
verify(br1.get_state() == 'leader')
br1.br_init(IF_INDEX, 1)
br1.br_enable()
time.sleep(1)
verify(br1.br_get_state() == 'running')
br1_local_omr = br1.br_get_local_omrprefix()
br1_favored_omr = br1.br_get_favored_omrprefix().split()[0]
verify(br1_local_omr == br1_favored_omr)
br1_local_onlink = br1.br_get_local_onlinkprefix()
br1_favored_onlink = br1.br_get_favored_onlinkprefix().split()[0]
verify(br1_local_onlink == br1_favored_onlink)
# Start second BR with its own Thread network.
br2.form('net2')
verify(br2.get_state() == 'leader')
br2.br_init(IF_INDEX, 1)
br2.br_enable()
time.sleep(1)
verify(br2.br_get_state() == 'running')
br2_local_omr = br2.br_get_local_omrprefix()
br2_favored_omr = br2.br_get_favored_omrprefix().split()[0]
verify(br2_local_omr == br2_favored_omr)
br2_local_onlink = br2.br_get_local_onlinkprefix()
br2_favored_onlink = br2.br_get_favored_onlinkprefix().split()[0]
verify(br2_local_onlink != br2_favored_onlink)
# BR2 should see and favor the on-link prefix already advertised by BR1.
verify(br1_favored_onlink == br2_favored_onlink)
br1_routers = br1.br_get_routers()
br2_routers = br2.br_get_routers()
verify(len(br1_routers) > 0)
verify(len(br2_routers) > 0)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+1
View File
@@ -194,6 +194,7 @@ if [ "$TORANJ_CLI" = 1 ]; then
run cli/test-027-slaac-address.py
run cli/test-028-border-agent-ephemeral-key.py
run cli/test-400-srp-client-server.py
run cli/test-500-two-brs-two-networks.py
run cli/test-601-channel-manager-channel-change.py
# Skip the "channel-select" test on a TREL only radio link, since it
# requires energy scan which is not supported in this case.