diff --git a/src/cli/BUILD.gn b/src/cli/BUILD.gn index 2c214d87b..0c75e74ad 100644 --- a/src/cli/BUILD.gn +++ b/src/cli/BUILD.gn @@ -55,6 +55,8 @@ openthread_cli_sources = [ "cli_mac_filter.hpp", "cli_mdns.cpp", "cli_mdns.hpp", + "cli_mesh_diag.cpp", + "cli_mesh_diag.hpp", "cli_network_data.cpp", "cli_network_data.hpp", "cli_ping.cpp", diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 3d9a32a21..933375a0b 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -45,6 +45,7 @@ set(COMMON_SOURCES cli_link_metrics.cpp cli_mac_filter.cpp cli_mdns.cpp + cli_mesh_diag.cpp cli_network_data.cpp cli_ping.cpp cli_srp_client.cpp diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index b5d3931c1..9fcd562c6 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -133,6 +133,9 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE , mPing(aInstance, *this) #endif +#if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD + , mMeshDiag(aInstance, *this) +#endif #if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE , mLocateInProgress(false) #endif @@ -5022,448 +5025,7 @@ exit: #if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE -template <> otError Interpreter::Process(Arg aArgs[]) -{ - otError error = OT_ERROR_NONE; - - /** - * @cli meshdiag topology - * @code - * meshdiag topology - * id:02 rloc16:0x0800 ext-addr:8aa57d2c603fe16c ver:4 - me - leader - * 3-links:{ 46 } - * id:46 rloc16:0xb800 ext-addr:fe109d277e0175cc ver:4 - * 3-links:{ 02 51 57 } - * id:33 rloc16:0x8400 ext-addr:d2e511a146b9e54d ver:4 - * 3-links:{ 51 57 } - * id:51 rloc16:0xcc00 ext-addr:9aab43ababf05352 ver:4 - * 3-links:{ 33 57 } - * 2-links:{ 46 } - * id:57 rloc16:0xe400 ext-addr:dae9c4c0e9da55ff ver:4 - * 3-links:{ 46 51 } - * 1-links:{ 33 } - * Done - * @endcode - * @par - * Discover network topology (list of routers and their connections). - * Parameters are optional and indicate additional items to discover. Can be added in any order. - * * `ip6-addrs` to discover the list of IPv6 addresses of every router. - * * `children` to discover the child table of every router. - * @par - * Information per router: - * * Router ID - * * RLOC16 - * * Extended MAC address - * * Thread Version (if known) - * * Whether the router is this device is itself (`me`) - * * Whether the router is the parent of this device when device is a child (`parent`) - * * Whether the router is `leader` - * * Whether the router acts as a border router providing external connectivity (`br`) - * * List of routers to which this router has a link: - * * `3-links`: Router IDs to which this router has a incoming link with link quality 3 - * * `2-links`: Router IDs to which this router has a incoming link with link quality 2 - * * `1-links`: Router IDs to which this router has a incoming link with link quality 1 - * * If a list if empty, it is omitted in the out. - * * If `ip6-addrs`, list of IPv6 addresses of the router - * * If `children`, list of all children of the router. Information per child: - * * RLOC16 - * * Incoming Link Quality from perspective of parent to child (zero indicates unknown) - * * Child Device mode (`r` rx-on-when-idle, `d` Full Thread Device, `n` Full Network Data, `-` no flags set) - * * Whether the child is this device itself (`me`) - * * Whether the child acts as a border router providing external connectivity (`br`) - * @cparam meshdiag topology [@ca{ip6-addrs}] [@ca{children}] - * @sa otMeshDiagDiscoverTopology - */ - if (aArgs[0] == "topology") - { - otMeshDiagDiscoverConfig config; - - config.mDiscoverIp6Addresses = false; - config.mDiscoverChildTable = false; - - aArgs++; - - for (; !aArgs->IsEmpty(); aArgs++) - { - if (*aArgs == "ip6-addrs") - { - config.mDiscoverIp6Addresses = true; - } - else if (*aArgs == "children") - { - config.mDiscoverChildTable = true; - } - else - { - ExitNow(error = OT_ERROR_INVALID_ARGS); - } - } - - SuccessOrExit(error = otMeshDiagDiscoverTopology(GetInstancePtr(), &config, HandleMeshDiagDiscoverDone, this)); - error = OT_ERROR_PENDING; - } - /** - * @cli meshdiag childtable - * @code - * meshdiag childtable 0x6400 - * rloc16:0x6402 ext-addr:8e6f4d323bbed1fe ver:4 - * timeout:120 age:36 supvn:129 q-msg:0 - * rx-on:yes type:ftd full-net:yes - * rss - ave:-20 last:-20 margin:80 - * err-rate - frame:11.51% msg:0.76% - * conn-time:00:11:07 - * csl - sync:no period:0 timeout:0 channel:0 - * rloc16:0x6403 ext-addr:ee24e64ecf8c079a ver:4 - * timeout:120 age:19 supvn:129 q-msg:0 - * rx-on:no type:mtd full-net:no - * rss - ave:-20 last:-20 margin:80 - * err-rate - frame:0.73% msg:0.00% - * conn-time:01:08:53 - * csl - sync:no period:0 timeout:0 channel:0 - * Done - * @endcode - * @par - * Start a query for child table of a router with a given RLOC16. - * Output lists all child entries. Information per child: - * - RLOC16 - * - Extended MAC address - * - Thread Version - * - Timeout (in seconds) - * - Age (seconds since last heard) - * - Supervision interval (in seconds) - * - Number of queued messages (in case child is sleepy) - * - Device Mode - * - RSS (average and last) - * - Error rates: frame tx (at MAC layer), IPv6 message tx (above MAC) - * - Connection time (seconds since link establishment `{dd}d.{hh}:{mm}:{ss}` format) - * - CSL info: - * - If synchronized - * - Period (in unit of 10-symbols-time) - * - Timeout (in seconds) - * - * @cparam meshdiag childtable @ca{router-rloc16} - * @sa otMeshDiagQueryChildTable - */ - else if (aArgs[0] == "childtable") - { - uint16_t routerRloc16; - - SuccessOrExit(error = aArgs[1].ParseAsUint16(routerRloc16)); - VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); - - SuccessOrExit(error = otMeshDiagQueryChildTable(GetInstancePtr(), routerRloc16, - HandleMeshDiagQueryChildTableResult, this)); - - error = OT_ERROR_PENDING; - } - /** - * @cli meshdiag childip6 - * @code - * meshdiag childip6 0xdc00 - * child-rloc16: 0xdc02 - * fdde:ad00:beef:0:ded8:cd58:b73:2c21 - * fd00:2:0:0:c24a:456:3b6b:c597 - * fd00:1:0:0:120b:95fe:3ecc:d238 - * child-rloc16: 0xdc03 - * fdde:ad00:beef:0:3aa6:b8bf:e7d6:eefe - * fd00:2:0:0:8ff8:a188:7436:6720 - * fd00:1:0:0:1fcf:5495:790a:370f - * Done - * @endcode - * @par - * Send a query to a parent to retrieve the IPv6 addresses of all its MTD children. - * @cparam meshdiag childip6 @ca{parent-rloc16} - * @sa otMeshDiagQueryChildrenIp6Addrs - */ - else if (aArgs[0] == "childip6") - { - uint16_t parentRloc16; - - SuccessOrExit(error = aArgs[1].ParseAsUint16(parentRloc16)); - VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); - - SuccessOrExit(error = otMeshDiagQueryChildrenIp6Addrs(GetInstancePtr(), parentRloc16, - HandleMeshDiagQueryChildIp6Addrs, this)); - - error = OT_ERROR_PENDING; - } - /** - * @cli meshdiag routerneighbortable - * @code - * meshdiag routerneighbortable 0x7400 - * rloc16:0x9c00 ext-addr:764788cf6e57a4d2 ver:4 - * rss - ave:-20 last:-20 margin:80 - * err-rate - frame:1.38% msg:0.00% - * conn-time:01:54:02 - * rloc16:0x7c00 ext-addr:4ed24fceec9bf6d3 ver:4 - * rss - ave:-20 last:-20 margin:80 - * err-rate - frame:0.72% msg:0.00% - * conn-time:00:11:27 - * Done - * @endcode - * @par - * Start a query for router neighbor table of a router with a given RLOC16. - * Output lists all router neighbor entries. Information per entry: - * - RLOC16 - * - Extended MAC address - * - Thread Version - * - RSS (average and last) and link margin - * - Error rates, frame tx (at MAC layer), IPv6 message tx (above MAC) - * - Connection time (seconds since link establishment `{dd}d.{hh}:{mm}:{ss}` format) - * @cparam meshdiag routerneighbortable @ca{router-rloc16} - * @sa otMeshDiagQueryRouterNeighborTable - */ - else if (aArgs[0] == "routerneighbortable") - { - uint16_t routerRloc16; - - SuccessOrExit(error = aArgs[1].ParseAsUint16(routerRloc16)); - VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); - - SuccessOrExit(error = otMeshDiagQueryRouterNeighborTable(GetInstancePtr(), routerRloc16, - HandleMeshDiagQueryRouterNeighborTableResult, this)); - - error = OT_ERROR_PENDING; - } - else - { - error = OT_ERROR_INVALID_COMMAND; - } - -exit: - return error; -} - -void Interpreter::HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo, void *aContext) -{ - reinterpret_cast(aContext)->HandleMeshDiagDiscoverDone(aError, aRouterInfo); -} - -void Interpreter::HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo) -{ - VerifyOrExit(aRouterInfo != nullptr); - - OutputFormat("id:%02u rloc16:0x%04x ext-addr:", aRouterInfo->mRouterId, aRouterInfo->mRloc16); - OutputExtAddress(aRouterInfo->mExtAddress); - - if (aRouterInfo->mVersion != OT_MESH_DIAG_VERSION_UNKNOWN) - { - OutputFormat(" ver:%u", aRouterInfo->mVersion); - } - - if (aRouterInfo->mIsThisDevice) - { - OutputFormat(" - me"); - } - - if (aRouterInfo->mIsThisDeviceParent) - { - OutputFormat(" - parent"); - } - - if (aRouterInfo->mIsLeader) - { - OutputFormat(" - leader"); - } - - if (aRouterInfo->mIsBorderRouter) - { - OutputFormat(" - br"); - } - - OutputNewLine(); - - for (uint8_t linkQuality = 3; linkQuality > 0; linkQuality--) - { - bool hasLinkQuality = false; - - for (uint8_t entryQuality : aRouterInfo->mLinkQualities) - { - if (entryQuality == linkQuality) - { - hasLinkQuality = true; - break; - } - } - - if (hasLinkQuality) - { - OutputFormat(kIndentSize, "%u-links:{ ", linkQuality); - - for (uint8_t id = 0; id < static_cast(OT_ARRAY_LENGTH(aRouterInfo->mLinkQualities)); id++) - { - if (aRouterInfo->mLinkQualities[id] == linkQuality) - { - OutputFormat("%02u ", id); - } - } - - OutputLine("}"); - } - } - - if (aRouterInfo->mIp6AddrIterator != nullptr) - { - otIp6Address ip6Address; - - OutputLine(kIndentSize, "ip6-addrs:"); - - while (otMeshDiagGetNextIp6Address(aRouterInfo->mIp6AddrIterator, &ip6Address) == OT_ERROR_NONE) - { - OutputSpaces(kIndentSize * 2); - OutputIp6AddressLine(ip6Address); - } - } - - if (aRouterInfo->mChildIterator != nullptr) - { - otMeshDiagChildInfo childInfo; - char linkModeString[kLinkModeStringSize]; - bool isFirst = true; - - while (otMeshDiagGetNextChildInfo(aRouterInfo->mChildIterator, &childInfo) == OT_ERROR_NONE) - { - if (isFirst) - { - OutputLine(kIndentSize, "children:"); - isFirst = false; - } - - OutputFormat(kIndentSize * 2, "rloc16:0x%04x lq:%u, mode:%s", childInfo.mRloc16, childInfo.mLinkQuality, - LinkModeToString(childInfo.mMode, linkModeString)); - - if (childInfo.mIsThisDevice) - { - OutputFormat(" - me"); - } - - if (childInfo.mIsBorderRouter) - { - OutputFormat(" - br"); - } - - OutputNewLine(); - } - - if (isFirst) - { - OutputLine(kIndentSize, "children: none"); - } - } - -exit: - OutputResult(aError); -} - -void Interpreter::HandleMeshDiagQueryChildTableResult(otError aError, - const otMeshDiagChildEntry *aChildEntry, - void *aContext) -{ - reinterpret_cast(aContext)->HandleMeshDiagQueryChildTableResult(aError, aChildEntry); -} - -void Interpreter::HandleMeshDiagQueryChildTableResult(otError aError, const otMeshDiagChildEntry *aChildEntry) -{ - PercentageStringBuffer stringBuffer; - char string[OT_DURATION_STRING_SIZE]; - - VerifyOrExit(aChildEntry != nullptr); - - OutputFormat("rloc16:0x%04x ext-addr:", aChildEntry->mRloc16); - OutputExtAddress(aChildEntry->mExtAddress); - OutputLine(" ver:%u", aChildEntry->mVersion); - - OutputLine(kIndentSize, "timeout:%lu age:%lu supvn:%u q-msg:%u", ToUlong(aChildEntry->mTimeout), - ToUlong(aChildEntry->mAge), aChildEntry->mSupervisionInterval, aChildEntry->mQueuedMessageCount); - - OutputLine(kIndentSize, "rx-on:%s type:%s full-net:%s", aChildEntry->mRxOnWhenIdle ? "yes" : "no", - aChildEntry->mDeviceTypeFtd ? "ftd" : "mtd", aChildEntry->mFullNetData ? "yes" : "no"); - - OutputLine(kIndentSize, "rss - ave:%d last:%d margin:%d", aChildEntry->mAverageRssi, aChildEntry->mLastRssi, - aChildEntry->mLinkMargin); - - if (aChildEntry->mSupportsErrRate) - { - OutputFormat(kIndentSize, "err-rate - frame:%s%% ", - PercentageToString(aChildEntry->mFrameErrorRate, stringBuffer)); - OutputLine("msg:%s%% ", PercentageToString(aChildEntry->mMessageErrorRate, stringBuffer)); - } - - otConvertDurationInSecondsToString(aChildEntry->mConnectionTime, string, sizeof(string)); - OutputLine(kIndentSize, "conn-time:%s", string); - - OutputLine(kIndentSize, "csl - sync:%s period:%u timeout:%lu channel:%u", - aChildEntry->mCslSynchronized ? "yes" : "no", aChildEntry->mCslPeriod, ToUlong(aChildEntry->mCslTimeout), - aChildEntry->mCslChannel); - -exit: - OutputResult(aError); -} - -void Interpreter::HandleMeshDiagQueryRouterNeighborTableResult(otError aError, - const otMeshDiagRouterNeighborEntry *aNeighborEntry, - void *aContext) -{ - reinterpret_cast(aContext)->HandleMeshDiagQueryRouterNeighborTableResult(aError, aNeighborEntry); -} - -void Interpreter::HandleMeshDiagQueryRouterNeighborTableResult(otError aError, - const otMeshDiagRouterNeighborEntry *aNeighborEntry) -{ - PercentageStringBuffer stringBuffer; - char string[OT_DURATION_STRING_SIZE]; - - VerifyOrExit(aNeighborEntry != nullptr); - - OutputFormat("rloc16:0x%04x ext-addr:", aNeighborEntry->mRloc16); - OutputExtAddress(aNeighborEntry->mExtAddress); - OutputLine(" ver:%u", aNeighborEntry->mVersion); - - OutputLine(kIndentSize, "rss - ave:%d last:%d margin:%d", aNeighborEntry->mAverageRssi, aNeighborEntry->mLastRssi, - aNeighborEntry->mLinkMargin); - - if (aNeighborEntry->mSupportsErrRate) - { - OutputFormat(kIndentSize, "err-rate - frame:%s%% ", - PercentageToString(aNeighborEntry->mFrameErrorRate, stringBuffer)); - OutputLine("msg:%s%% ", PercentageToString(aNeighborEntry->mMessageErrorRate, stringBuffer)); - } - - otConvertDurationInSecondsToString(aNeighborEntry->mConnectionTime, string, sizeof(string)); - OutputLine(kIndentSize, "conn-time:%s", string); - -exit: - OutputResult(aError); -} - -void Interpreter::HandleMeshDiagQueryChildIp6Addrs(otError aError, - uint16_t aChildRloc16, - otMeshDiagIp6AddrIterator *aIp6AddrIterator, - void *aContext) -{ - reinterpret_cast(aContext)->HandleMeshDiagQueryChildIp6Addrs(aError, aChildRloc16, aIp6AddrIterator); -} - -void Interpreter::HandleMeshDiagQueryChildIp6Addrs(otError aError, - uint16_t aChildRloc16, - otMeshDiagIp6AddrIterator *aIp6AddrIterator) -{ - otIp6Address ip6Address; - - VerifyOrExit(aError == OT_ERROR_NONE || aError == OT_ERROR_PENDING); - VerifyOrExit(aIp6AddrIterator != nullptr); - - OutputLine("child-rloc16: 0x%04x", aChildRloc16); - - while (otMeshDiagGetNextIp6Address(aIp6AddrIterator, &ip6Address) == OT_ERROR_NONE) - { - OutputSpaces(kIndentSize); - OutputIp6AddressLine(ip6Address); - } - -exit: - OutputResult(aError); -} +template <> otError Interpreter::Process(Arg aArgs[]) { return mMeshDiag.Process(aArgs); } #endif // OPENTHREAD_CONFIG_MESH_DIAG_ENABLE diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index fc65dd8bc..73cc0368b 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -48,7 +48,6 @@ #include #include #include -#include #include #include #include @@ -70,6 +69,7 @@ #include "cli/cli_link_metrics.hpp" #include "cli/cli_mac_filter.hpp" #include "cli/cli_mdns.hpp" +#include "cli/cli_mesh_diag.hpp" #include "cli/cli_network_data.hpp" #include "cli/cli_ping.hpp" #include "cli/cli_srp_client.hpp" @@ -113,6 +113,7 @@ class Interpreter : public OutputImplementer, public Utils friend class Joiner; friend class LinkMetrics; friend class Mdns; + friend class MeshDiag; friend class NetworkData; friend class PingSender; friend class SrpClient; @@ -231,27 +232,6 @@ private: uint16_t aRloc16); void HandleLocateResult(otError aError, const otIp6Address *aMeshLocalAddress, uint16_t aRloc16); #endif -#if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD - static void HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo, void *aContext); - void HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo); - static void HandleMeshDiagQueryChildTableResult(otError aError, - const otMeshDiagChildEntry *aChildEntry, - void *aContext); - void HandleMeshDiagQueryChildTableResult(otError aError, const otMeshDiagChildEntry *aChildEntry); - static void HandleMeshDiagQueryChildIp6Addrs(otError aError, - uint16_t aChildRloc16, - otMeshDiagIp6AddrIterator *aIp6AddrIterator, - void *aContext); - void HandleMeshDiagQueryChildIp6Addrs(otError aError, - uint16_t aChildRloc16, - otMeshDiagIp6AddrIterator *aIp6AddrIterator); - static void HandleMeshDiagQueryRouterNeighborTableResult(otError aError, - const otMeshDiagRouterNeighborEntry *aNeighborEntry, - void *aContext); - void HandleMeshDiagQueryRouterNeighborTableResult(otError aError, - const otMeshDiagRouterNeighborEntry *aNeighborEntry); - -#endif #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE static void HandleMlrRegResult(void *aContext, otError aError, @@ -428,6 +408,9 @@ private: #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE PingSender mPing; #endif +#if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD + MeshDiag mMeshDiag; +#endif #endif // OPENTHREAD_FTD || OPENTHREAD_MTD #if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE diff --git a/src/cli/cli_mesh_diag.cpp b/src/cli/cli_mesh_diag.cpp new file mode 100644 index 000000000..d80d87afc --- /dev/null +++ b/src/cli/cli_mesh_diag.cpp @@ -0,0 +1,539 @@ +/* + * Copyright (c) 2025, 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. + */ + +/** + * @file + * This file implements the CLI interpreter for Mesh Diagnostics function. + */ + +#include "cli_mesh_diag.hpp" + +#include + +#include "cli/cli.hpp" +#include "cli/cli_utils.hpp" +#include "common/code_utils.hpp" + +#if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD + +namespace ot { +namespace Cli { + +MeshDiag::MeshDiag(otInstance *aInstance, OutputImplementer &aOutputImplementer) + : Utils(aInstance, aOutputImplementer) +{ +} + +template <> otError MeshDiag::Process(Arg aArgs[]) +{ + /** + * @cli meshdiag topology + * @code + * meshdiag topology + * id:02 rloc16:0x0800 ext-addr:8aa57d2c603fe16c ver:4 - me - leader + * 3-links:{ 46 } + * id:46 rloc16:0xb800 ext-addr:fe109d277e0175cc ver:4 + * 3-links:{ 02 51 57 } + * id:33 rloc16:0x8400 ext-addr:d2e511a146b9e54d ver:4 + * 3-links:{ 51 57 } + * id:51 rloc16:0xcc00 ext-addr:9aab43ababf05352 ver:4 + * 3-links:{ 33 57 } + * 2-links:{ 46 } + * id:57 rloc16:0xe400 ext-addr:dae9c4c0e9da55ff ver:4 + * 3-links:{ 46 51 } + * 1-links:{ 33 } + * Done + * @endcode + * @par + * Discover network topology (list of routers and their connections). + * Parameters are optional and indicate additional items to discover. Can be added in any order. + * * `ip6-addrs` to discover the list of IPv6 addresses of every router. + * * `children` to discover the child table of every router. + * @par + * Information per router: + * * Router ID + * * RLOC16 + * * Extended MAC address + * * Thread Version (if known) + * * Whether the router is this device is itself (`me`) + * * Whether the router is the parent of this device when device is a child (`parent`) + * * Whether the router is `leader` + * * Whether the router acts as a border router providing external connectivity (`br`) + * * List of routers to which this router has a link: + * * `3-links`: Router IDs to which this router has a incoming link with link quality 3 + * * `2-links`: Router IDs to which this router has a incoming link with link quality 2 + * * `1-links`: Router IDs to which this router has a incoming link with link quality 1 + * * If a list if empty, it is omitted in the out. + * * If `ip6-addrs`, list of IPv6 addresses of the router + * * If `children`, list of all children of the router. Information per child: + * * RLOC16 + * * Incoming Link Quality from perspective of parent to child (zero indicates unknown) + * * Child Device mode (`r` rx-on-when-idle, `d` Full Thread Device, `n` Full Network Data, `-` no flags set) + * * Whether the child is this device itself (`me`) + * * Whether the child acts as a border router providing external connectivity (`br`) + * @cparam meshdiag topology [@ca{ip6-addrs}] [@ca{children}] + * @sa otMeshDiagDiscoverTopology + */ + otError error = OT_ERROR_NONE; + otMeshDiagDiscoverConfig config; + + config.mDiscoverIp6Addresses = false; + config.mDiscoverChildTable = false; + + for (; !aArgs->IsEmpty(); aArgs++) + { + if (*aArgs == "ip6-addrs") + { + config.mDiscoverIp6Addresses = true; + } + else if (*aArgs == "children") + { + config.mDiscoverChildTable = true; + } + else + { + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + } + + SuccessOrExit(error = otMeshDiagDiscoverTopology(GetInstancePtr(), &config, HandleMeshDiagDiscoverDone, this)); + error = OT_ERROR_PENDING; + +exit: + return error; +} + +template <> otError MeshDiag::Process(Arg aArgs[]) +{ + /** + * @cli meshdiag childtable + * @code + * meshdiag childtable 0x6400 + * rloc16:0x6402 ext-addr:8e6f4d323bbed1fe ver:4 + * timeout:120 age:36 supvn:129 q-msg:0 + * rx-on:yes type:ftd full-net:yes + * rss - ave:-20 last:-20 margin:80 + * err-rate - frame:11.51% msg:0.76% + * conn-time:00:11:07 + * csl - sync:no period:0 timeout:0 channel:0 + * rloc16:0x6403 ext-addr:ee24e64ecf8c079a ver:4 + * timeout:120 age:19 supvn:129 q-msg:0 + * rx-on:no type:mtd full-net:no + * rss - ave:-20 last:-20 margin:80 + * err-rate - frame:0.73% msg:0.00% + * conn-time:01:08:53 + * csl - sync:no period:0 timeout:0 channel:0 + * Done + * @endcode + * @par + * Start a query for child table of a router with a given RLOC16. + * Output lists all child entries. Information per child: + * - RLOC16 + * - Extended MAC address + * - Thread Version + * - Timeout (in seconds) + * - Age (seconds since last heard) + * - Supervision interval (in seconds) + * - Number of queued messages (in case child is sleepy) + * - Device Mode + * - RSS (average and last) + * - Error rates: frame tx (at MAC layer), IPv6 message tx (above MAC) + * - Connection time (seconds since link establishment `{dd}d.{hh}:{mm}:{ss}` format) + * - CSL info: + * - If synchronized + * - Period (in unit of 10-symbols-time) + * - Timeout (in seconds) + * + * @cparam meshdiag childtable @ca{router-rloc16} + * @sa otMeshDiagQueryChildTable + */ + otError error = OT_ERROR_NONE; + uint16_t routerRloc16; + + SuccessOrExit(error = aArgs[0].ParseAsUint16(routerRloc16)); + VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit( + error = otMeshDiagQueryChildTable(GetInstancePtr(), routerRloc16, HandleMeshDiagQueryChildTableResult, this)); + + error = OT_ERROR_PENDING; + +exit: + return error; +} + +template <> otError MeshDiag::Process(Arg aArgs[]) +{ + /** + * @cli meshdiag childip6 + * @code + * meshdiag childip6 0xdc00 + * child-rloc16: 0xdc02 + * fdde:ad00:beef:0:ded8:cd58:b73:2c21 + * fd00:2:0:0:c24a:456:3b6b:c597 + * fd00:1:0:0:120b:95fe:3ecc:d238 + * child-rloc16: 0xdc03 + * fdde:ad00:beef:0:3aa6:b8bf:e7d6:eefe + * fd00:2:0:0:8ff8:a188:7436:6720 + * fd00:1:0:0:1fcf:5495:790a:370f + * Done + * @endcode + * @par + * Send a query to a parent to retrieve the IPv6 addresses of all its MTD children. + * @cparam meshdiag childip6 @ca{parent-rloc16} + * @sa otMeshDiagQueryChildrenIp6Addrs + */ + otError error = OT_ERROR_NONE; + uint16_t parentRloc16; + + SuccessOrExit(error = aArgs[0].ParseAsUint16(parentRloc16)); + VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit(error = otMeshDiagQueryChildrenIp6Addrs(GetInstancePtr(), parentRloc16, + HandleMeshDiagQueryChildIp6Addrs, this)); + + error = OT_ERROR_PENDING; + +exit: + return error; +} + +template <> otError MeshDiag::Process(Arg aArgs[]) +{ + /** + * @cli meshdiag routerneighbortable + * @code + * meshdiag routerneighbortable 0x7400 + * rloc16:0x9c00 ext-addr:764788cf6e57a4d2 ver:4 + * rss - ave:-20 last:-20 margin:80 + * err-rate - frame:1.38% msg:0.00% + * conn-time:01:54:02 + * rloc16:0x7c00 ext-addr:4ed24fceec9bf6d3 ver:4 + * rss - ave:-20 last:-20 margin:80 + * err-rate - frame:0.72% msg:0.00% + * conn-time:00:11:27 + * Done + * @endcode + * @par + * Start a query for router neighbor table of a router with a given RLOC16. + * Output lists all router neighbor entries. Information per entry: + * - RLOC16 + * - Extended MAC address + * - Thread Version + * - RSS (average and last) and link margin + * - Error rates, frame tx (at MAC layer), IPv6 message tx (above MAC) + * - Connection time (seconds since link establishment `{dd}d.{hh}:{mm}:{ss}` format) + * @cparam meshdiag routerneighbortable @ca{router-rloc16} + * @sa otMeshDiagQueryRouterNeighborTable + */ + otError error = OT_ERROR_NONE; + uint16_t routerRloc16; + + SuccessOrExit(error = aArgs[0].ParseAsUint16(routerRloc16)); + VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit(error = otMeshDiagQueryRouterNeighborTable(GetInstancePtr(), routerRloc16, + HandleMeshDiagQueryRouterNeighborTableResult, this)); + + error = OT_ERROR_PENDING; + +exit: + return error; +} + +otError MeshDiag::Process(Arg aArgs[]) +{ +#define CmdEntry(aCommandString) \ + { \ + aCommandString, &MeshDiag::Process \ + } + + static constexpr Command kCommands[] = { + CmdEntry("childip6"), + CmdEntry("childtable"), + CmdEntry("routerneighbortable"), + CmdEntry("topology"), + }; + + static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted"); + + otError error = OT_ERROR_INVALID_COMMAND; + const Command *command; + + if (aArgs[0].IsEmpty() || (aArgs[0] == "help")) + { + OutputCommandTable(kCommands); + ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE); + } + + command = BinarySearch::Find(aArgs[0].GetCString(), kCommands); + VerifyOrExit(command != nullptr); + + error = (this->*command->mHandler)(aArgs + 1); + +exit: + return error; +} + +void MeshDiag::HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo, void *aContext) +{ + reinterpret_cast(aContext)->HandleMeshDiagDiscoverDone(aError, aRouterInfo); +} + +void MeshDiag::HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo) +{ + VerifyOrExit(aRouterInfo != nullptr); + + OutputFormat("id:%02u rloc16:0x%04x ext-addr:", aRouterInfo->mRouterId, aRouterInfo->mRloc16); + OutputExtAddress(aRouterInfo->mExtAddress); + + if (aRouterInfo->mVersion != OT_MESH_DIAG_VERSION_UNKNOWN) + { + OutputFormat(" ver:%u", aRouterInfo->mVersion); + } + + if (aRouterInfo->mIsThisDevice) + { + OutputFormat(" - me"); + } + + if (aRouterInfo->mIsThisDeviceParent) + { + OutputFormat(" - parent"); + } + + if (aRouterInfo->mIsLeader) + { + OutputFormat(" - leader"); + } + + if (aRouterInfo->mIsBorderRouter) + { + OutputFormat(" - br"); + } + + OutputNewLine(); + + for (uint8_t linkQuality = 3; linkQuality > 0; linkQuality--) + { + bool hasLinkQuality = false; + + for (uint8_t entryQuality : aRouterInfo->mLinkQualities) + { + if (entryQuality == linkQuality) + { + hasLinkQuality = true; + break; + } + } + + if (hasLinkQuality) + { + OutputFormat(kIndentSize, "%u-links:{ ", linkQuality); + + for (uint8_t id = 0; id < static_cast(OT_ARRAY_LENGTH(aRouterInfo->mLinkQualities)); id++) + { + if (aRouterInfo->mLinkQualities[id] == linkQuality) + { + OutputFormat("%02u ", id); + } + } + + OutputLine("}"); + } + } + + if (aRouterInfo->mIp6AddrIterator != nullptr) + { + otIp6Address ip6Address; + + OutputLine(kIndentSize, "ip6-addrs:"); + + while (otMeshDiagGetNextIp6Address(aRouterInfo->mIp6AddrIterator, &ip6Address) == OT_ERROR_NONE) + { + OutputSpaces(kIndentSize * 2); + OutputIp6AddressLine(ip6Address); + } + } + + if (aRouterInfo->mChildIterator != nullptr) + { + otMeshDiagChildInfo childInfo; + char linkModeString[kLinkModeStringSize]; + bool isFirst = true; + + while (otMeshDiagGetNextChildInfo(aRouterInfo->mChildIterator, &childInfo) == OT_ERROR_NONE) + { + if (isFirst) + { + OutputLine(kIndentSize, "children:"); + isFirst = false; + } + + OutputFormat(kIndentSize * 2, "rloc16:0x%04x lq:%u, mode:%s", childInfo.mRloc16, childInfo.mLinkQuality, + LinkModeToString(childInfo.mMode, linkModeString)); + + if (childInfo.mIsThisDevice) + { + OutputFormat(" - me"); + } + + if (childInfo.mIsBorderRouter) + { + OutputFormat(" - br"); + } + + OutputNewLine(); + } + + if (isFirst) + { + OutputLine(kIndentSize, "children: none"); + } + } + +exit: + OutputResult(aError); +} + +void MeshDiag::HandleMeshDiagQueryChildTableResult(otError aError, + const otMeshDiagChildEntry *aChildEntry, + void *aContext) +{ + reinterpret_cast(aContext)->HandleMeshDiagQueryChildTableResult(aError, aChildEntry); +} + +void MeshDiag::HandleMeshDiagQueryChildTableResult(otError aError, const otMeshDiagChildEntry *aChildEntry) +{ + PercentageStringBuffer stringBuffer; + char string[OT_DURATION_STRING_SIZE]; + + VerifyOrExit(aChildEntry != nullptr); + + OutputFormat("rloc16:0x%04x ext-addr:", aChildEntry->mRloc16); + OutputExtAddress(aChildEntry->mExtAddress); + OutputLine(" ver:%u", aChildEntry->mVersion); + + OutputLine(kIndentSize, "timeout:%lu age:%lu supvn:%u q-msg:%u", ToUlong(aChildEntry->mTimeout), + ToUlong(aChildEntry->mAge), aChildEntry->mSupervisionInterval, aChildEntry->mQueuedMessageCount); + + OutputLine(kIndentSize, "rx-on:%s type:%s full-net:%s", aChildEntry->mRxOnWhenIdle ? "yes" : "no", + aChildEntry->mDeviceTypeFtd ? "ftd" : "mtd", aChildEntry->mFullNetData ? "yes" : "no"); + + OutputLine(kIndentSize, "rss - ave:%d last:%d margin:%d", aChildEntry->mAverageRssi, aChildEntry->mLastRssi, + aChildEntry->mLinkMargin); + + if (aChildEntry->mSupportsErrRate) + { + OutputFormat(kIndentSize, "err-rate - frame:%s%% ", + PercentageToString(aChildEntry->mFrameErrorRate, stringBuffer)); + OutputLine("msg:%s%% ", PercentageToString(aChildEntry->mMessageErrorRate, stringBuffer)); + } + + otConvertDurationInSecondsToString(aChildEntry->mConnectionTime, string, sizeof(string)); + OutputLine(kIndentSize, "conn-time:%s", string); + + OutputLine(kIndentSize, "csl - sync:%s period:%u timeout:%lu channel:%u", + aChildEntry->mCslSynchronized ? "yes" : "no", aChildEntry->mCslPeriod, ToUlong(aChildEntry->mCslTimeout), + aChildEntry->mCslChannel); + +exit: + OutputResult(aError); +} + +void MeshDiag::HandleMeshDiagQueryRouterNeighborTableResult(otError aError, + const otMeshDiagRouterNeighborEntry *aNeighborEntry, + void *aContext) +{ + reinterpret_cast(aContext)->HandleMeshDiagQueryRouterNeighborTableResult(aError, aNeighborEntry); +} + +void MeshDiag::HandleMeshDiagQueryRouterNeighborTableResult(otError aError, + const otMeshDiagRouterNeighborEntry *aNeighborEntry) +{ + PercentageStringBuffer stringBuffer; + char string[OT_DURATION_STRING_SIZE]; + + VerifyOrExit(aNeighborEntry != nullptr); + + OutputFormat("rloc16:0x%04x ext-addr:", aNeighborEntry->mRloc16); + OutputExtAddress(aNeighborEntry->mExtAddress); + OutputLine(" ver:%u", aNeighborEntry->mVersion); + + OutputLine(kIndentSize, "rss - ave:%d last:%d margin:%d", aNeighborEntry->mAverageRssi, aNeighborEntry->mLastRssi, + aNeighborEntry->mLinkMargin); + + if (aNeighborEntry->mSupportsErrRate) + { + OutputFormat(kIndentSize, "err-rate - frame:%s%% ", + PercentageToString(aNeighborEntry->mFrameErrorRate, stringBuffer)); + OutputLine("msg:%s%% ", PercentageToString(aNeighborEntry->mMessageErrorRate, stringBuffer)); + } + + otConvertDurationInSecondsToString(aNeighborEntry->mConnectionTime, string, sizeof(string)); + OutputLine(kIndentSize, "conn-time:%s", string); + +exit: + OutputResult(aError); +} + +void MeshDiag::HandleMeshDiagQueryChildIp6Addrs(otError aError, + uint16_t aChildRloc16, + otMeshDiagIp6AddrIterator *aIp6AddrIterator, + void *aContext) +{ + reinterpret_cast(aContext)->HandleMeshDiagQueryChildIp6Addrs(aError, aChildRloc16, aIp6AddrIterator); +} + +void MeshDiag::HandleMeshDiagQueryChildIp6Addrs(otError aError, + uint16_t aChildRloc16, + otMeshDiagIp6AddrIterator *aIp6AddrIterator) +{ + otIp6Address ip6Address; + + VerifyOrExit(aError == OT_ERROR_NONE || aError == OT_ERROR_PENDING); + VerifyOrExit(aIp6AddrIterator != nullptr); + + OutputLine("child-rloc16: 0x%04x", aChildRloc16); + + while (otMeshDiagGetNextIp6Address(aIp6AddrIterator, &ip6Address) == OT_ERROR_NONE) + { + OutputSpaces(kIndentSize); + OutputIp6AddressLine(ip6Address); + } + +exit: + OutputResult(aError); +} + +void MeshDiag::OutputResult(otError aError) { Interpreter::GetInterpreter().OutputResult(aError); } + +} // namespace Cli +} // namespace ot + +#endif // OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD diff --git a/src/cli/cli_mesh_diag.hpp b/src/cli/cli_mesh_diag.hpp new file mode 100644 index 000000000..23410af2a --- /dev/null +++ b/src/cli/cli_mesh_diag.hpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2025, 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. + */ + +/** + * @file + * This file contains definitions for the CLI interpreter for Mesh Diagnostics function. + */ + +#ifndef CLI_MESH_DIAG_HPP_ +#define CLI_MESH_DIAG_HPP_ + +#include "openthread-core-config.h" + +#include + +#include "cli/cli_utils.hpp" + +#if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD + +namespace ot { +namespace Cli { + +/** + * Implements the Mesh Diagnostics CLI interpreter. + */ +class MeshDiag : private Utils +{ +public: + /** + * Constructor + * + * @param[in] aInstance The OpenThread Instance. + * @param[in] aOutputImplementer An `OutputImplementer`. + */ + MeshDiag(otInstance *aInstance, OutputImplementer &aOutputImplementer); + + /** + * Processes a CLI sub-command. + * + * @param[in] aArgs An array of command line arguments. + * + * @retval OT_ERROR_NONE Successfully executed the CLI command. + * @retval OT_ERROR_PENDING The CLI command was successfully started but final result is pending. + * @retval OT_ERROR_INVALID_COMMAND Invalid or unknown CLI command. + * @retval OT_ERROR_INVALID_ARGS Invalid arguments. + * @retval ... Error during execution of the CLI command. + */ + otError Process(Arg aArgs[]); + +private: + static constexpr uint8_t kIndentSize = 4; + + using Command = CommandEntry; + + template otError Process(Arg aArgs[]); + + static void HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo, void *aContext); + + void HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo); + + static void HandleMeshDiagQueryChildTableResult(otError aError, + const otMeshDiagChildEntry *aChildEntry, + void *aContext); + + void HandleMeshDiagQueryChildTableResult(otError aError, const otMeshDiagChildEntry *aChildEntry); + + static void HandleMeshDiagQueryRouterNeighborTableResult(otError aError, + const otMeshDiagRouterNeighborEntry *aNeighborEntry, + void *aContext); + + void HandleMeshDiagQueryRouterNeighborTableResult(otError aError, + const otMeshDiagRouterNeighborEntry *aNeighborEntry); + + static void HandleMeshDiagQueryChildIp6Addrs(otError aError, + uint16_t aChildRloc16, + otMeshDiagIp6AddrIterator *aIp6AddrIterator, + void *aContext); + + void HandleMeshDiagQueryChildIp6Addrs(otError aError, + uint16_t aChildRloc16, + otMeshDiagIp6AddrIterator *aIp6AddrIterator); + + void OutputResult(otError aError); +}; + +} // namespace Cli +} // namespace ot + +#endif // OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD + +#endif // CLI_MESH_DIAG_HPP_