diff --git a/examples/platforms/simulation/infra_if.c b/examples/platforms/simulation/infra_if.c index 9596406d9..ccb5e9be7 100644 --- a/examples/platforms/simulation/infra_if.c +++ b/examples/platforms/simulation/infra_if.c @@ -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 +#include +#include #include -#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 diff --git a/examples/platforms/simulation/platform-simulation.h b/examples/platforms/simulation/platform-simulation.h index 0592b1429..343821cef 100644 --- a/examples/platforms/simulation/platform-simulation.h +++ b/examples/platforms/simulation/platform-simulation.h @@ -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_ diff --git a/examples/platforms/simulation/system.c b/examples/platforms/simulation/system.c index 535cb1cac..bb648b5a3 100644 --- a/examples/platforms/simulation/system.c +++ b/examples/platforms/simulation/system.c @@ -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) { diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index 8d0d5d4b9..789c6f654 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -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 diff --git a/tests/toranj/cli/test-500-two-brs-two-networks.py b/tests/toranj/cli/test-500-two-brs-two-networks.py new file mode 100755 index 000000000..12c61a7e7 --- /dev/null +++ b/tests/toranj/cli/test-500-two-brs-two-networks.py @@ -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)) diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index ade9245f4..606e97d2c 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -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.