From 8a14243dc8069bcd0db1b3379f8538f0b4cb56fb Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Thu, 27 Mar 2025 22:44:35 -0700 Subject: [PATCH] [netdiag] add support for Enhanced Route TLV (#11211) This commit adds support for the Network Diagnostics Enhanced Route TLV (TLV number 37). This TLV provides information about established links between routers, including the next hop and associated cost for routes to all routers. This commit also adds CLI support and test coverage for the new TLV. --- include/openthread/instance.h | 2 +- include/openthread/netdiag.h | 47 +++++++++++ src/cli/cli.cpp | 36 ++++++++ src/cli/cli.hpp | 1 + src/core/thread/network_diagnostic.cpp | 94 +++++++++++++++++++++ src/core/thread/network_diagnostic.hpp | 1 + src/core/thread/network_diagnostic_tlvs.cpp | 29 +++++++ src/core/thread/network_diagnostic_tlvs.hpp | 54 ++++++++++++ tests/toranj/cli/test-023-mesh-diag.py | 44 ++++++++++ 9 files changed, 307 insertions(+), 1 deletion(-) diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 462f12355..c53d6a2b0 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 (489) +#define OPENTHREAD_API_VERSION (490) /** * @addtogroup api-instance diff --git a/include/openthread/netdiag.h b/include/openthread/netdiag.h index 762f81dff..98e1450e5 100644 --- a/include/openthread/netdiag.h +++ b/include/openthread/netdiag.h @@ -77,6 +77,7 @@ extern "C" { #define OT_NETWORK_DIAGNOSTIC_TLV_QUERY_ID 33 ///< Query ID TLV #define OT_NETWORK_DIAGNOSTIC_TLV_MLE_COUNTERS 34 ///< MLE Counters TLV #define OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_APP_URL 35 ///< Vendor App URL TLV +#define OT_NETWORK_DIAGNOSTIC_TLV_ENHANCED_ROUTE 37 ///< Enhanced Route TLV #define OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_NAME_TLV_LENGTH 32 ///< Max length of Vendor Name TLV. #define OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_MODEL_TLV_LENGTH 32 ///< Max length of Vendor Model TLV. @@ -128,6 +129,51 @@ typedef struct otNetworkDiagRoute otNetworkDiagRouteData mRouteData[OT_NETWORK_MAX_ROUTER_ID + 1]; ///< Link Quality and Routing Cost data. } otNetworkDiagRoute; +/** + * Represents a Network Diagnostic Enhanced Route data. + */ +typedef struct otNetworkDiagEnhRouteData +{ + uint8_t mRouterId; ///< The Router ID. + bool mIsSelf : 1; ///< This is the queried device itself. If set, the other fields should be ignored. + bool mHasLink : 1; ///< Indicates whether the queried device has a direct link with router. + uint8_t mLinkQualityOut : 2; ///< Link Quality Out (applicable when `mHasLink`). + uint8_t mLinkQualityIn : 2; ///< Link Quality In (applicable when `mHasLink`). + + /** + * The next hop Router ID tracked towards this router. + * + * This field indicates the next hop router towards `mRouterId` when using multi-hop forwarding. + * + * If the device has no direct link with the router (`mHasLink == false`), this field indicates the next hop router + * that would be used to forward messages destined to `mRouterId`. + * + * If the device has a direct link with the router (`mHasLink == true`), this field indicates the alternate + * multi-hop path that may be used. Note that whether the direct link or this alternate path through the next hop + * is used to forward messages depends on their associated total path costs. + * + * If there is no next hop, then `OT_NETWORK_MAX_ROUTER_ID + 1` is used. + */ + uint8_t mNextHop; + + /** + * The route cost associated with forwarding to `mRouterId` using `mNextHop` (when valid). + * + * This is the route cost `mNextHop` has claimed to have towards `mRouterId`. Importantly, it does not include the + * link cost to send to `mNextHop` itself. + */ + uint8_t mNextHopCost; +} otNetworkDiagEnhRouteData; + +/** + * Represents a Network Diagnostic Enhanced Route TLV value. + */ +typedef struct otNetworkDiagEnhRoute +{ + uint8_t mRouteCount; ///< Number of `mRouteData` entries. + otNetworkDiagEnhRouteData mRouteData[OT_NETWORK_MAX_ROUTER_ID + 1]; ///< Route Data per router. +} otNetworkDiagEnhRoute; + /** * Represents a Network Diagnostic Mac Counters value. * @@ -195,6 +241,7 @@ typedef struct otNetworkDiagTlv uint32_t mTimeout; otNetworkDiagConnectivity mConnectivity; otNetworkDiagRoute mRoute; + otNetworkDiagEnhRoute mEnhRoute; otLeaderData mLeaderData; otNetworkDiagMacCounters mMacCounters; otNetworkDiagMleCounters mMleCounters; diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index b1ec724a1..25eb15a94 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -7555,6 +7555,7 @@ template <> otError Interpreter::Process(Arg aArgs[]) * - `29`: Child TLV * - `34`: MLE Counters TLV * - `35`: Vendor App URL TLV + * - `37`: Enhanced Route TLV * @par * Sends a network diagnostic request to retrieve specified Type Length Values (TLVs) * for the specified addresses(es). @@ -7663,6 +7664,10 @@ void Interpreter::HandleDiagnosticGetResponse(otError aError, OutputLine("Route:"); OutputRoute(kIndentSize, diagTlv.mData.mRoute); break; + case OT_NETWORK_DIAGNOSTIC_TLV_ENHANCED_ROUTE: + OutputLine("EnhRoute:"); + OutputEnhRoute(kIndentSize, diagTlv.mData.mEnhRoute); + break; case OT_NETWORK_DIAGNOSTIC_TLV_LEADER_DATA: OutputLine("Leader Data:"); OutputLeaderData(kIndentSize, diagTlv.mData.mLeaderData); @@ -7756,6 +7761,7 @@ void Interpreter::OutputConnectivity(uint8_t aIndentSize, const otNetworkDiagCon OutputLine(aIndentSize, "SedBufferSize: %u", aConnectivity.mSedBufferSize); OutputLine(aIndentSize, "SedDatagramCount: %u", aConnectivity.mSedDatagramCount); } + void Interpreter::OutputRoute(uint8_t aIndentSize, const otNetworkDiagRoute &aRoute) { OutputLine(aIndentSize, "IdSequence: %u", aRoute.mIdSequence); @@ -7778,6 +7784,36 @@ void Interpreter::OutputRouteData(uint8_t aIndentSize, const otNetworkDiagRouteD OutputLine(aIndentSize, "RouteCost: %u", aRouteData.mRouteCost); } +void Interpreter::OutputEnhRoute(uint8_t aIndentSize, const otNetworkDiagEnhRoute &aEnhRoute) +{ + static constexpr uint8_t kInvalidRouterId = OT_NETWORK_MAX_ROUTER_ID + 1; + + for (uint8_t index = 0; index < aEnhRoute.mRouteCount; index++) + { + const otNetworkDiagEnhRouteData &routeData = aEnhRoute.mRouteData[index]; + + OutputFormat(aIndentSize, "- RouterId:%-2u", routeData.mRouterId); + + if (routeData.mIsSelf) + { + OutputLine(" The queried device"); + continue; + } + + OutputFormat(" HasLink:%-3s LinkQualityOut:%u LinkQualityIn:%u ", routeData.mHasLink ? "yes" : "no", + routeData.mLinkQualityOut, routeData.mLinkQualityIn); + + if (routeData.mNextHop == kInvalidRouterId) + { + OutputLine("NextHop:na NextHopCost:na"); + } + else + { + OutputLine("NextHop:%-2u NextHopCost:%u", routeData.mNextHop, routeData.mNextHopCost); + } + } +} + void Interpreter::OutputLeaderData(uint8_t aIndentSize, const otLeaderData &aLeaderData) { OutputLine(aIndentSize, "PartitionId: 0x%08lx", ToUlong(aLeaderData.mPartitionId)); diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 71a4b1b21..46d714577 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -262,6 +262,7 @@ private: void OutputConnectivity(uint8_t aIndentSize, const otNetworkDiagConnectivity &aConnectivity); void OutputRoute(uint8_t aIndentSize, const otNetworkDiagRoute &aRoute); void OutputRouteData(uint8_t aIndentSize, const otNetworkDiagRouteData &aRouteData); + void OutputEnhRoute(uint8_t aIndentSize, const otNetworkDiagEnhRoute &aEnhRoute); void OutputLeaderData(uint8_t aIndentSize, const otLeaderData &aLeaderData); void OutputNetworkDiagMacCounters(uint8_t aIndentSize, const otNetworkDiagMacCounters &aMacCounters); void OutputNetworkDiagMleCounters(uint8_t aIndentSize, const otNetworkDiagMleCounters &aMleCounters); diff --git a/src/core/thread/network_diagnostic.cpp b/src/core/thread/network_diagnostic.cpp index 7f879c4bd..ed3c14485 100644 --- a/src/core/thread/network_diagnostic.cpp +++ b/src/core/thread/network_diagnostic.cpp @@ -196,6 +196,47 @@ Error Server::AppendChildTable(Message &aMessage) exit: return error; } + +Error Server::AppendEnhancedRoute(Message &aMessage) +{ + Error error = kErrorNone; + Tlv tlv; + Mle::RouterIdSet routerIdSet; + EnhancedRouteTlvEntry entry; + + VerifyOrExit(Get().IsRouterOrLeader()); + + Get().GetRouterIdSet(routerIdSet); + + tlv.SetType(Tlv::kEnhancedRoute); + tlv.SetLength(sizeof(Mle::RouterIdSet) + routerIdSet.GetNumberOfAllocatedIds() * sizeof(entry)); + + SuccessOrExit(error = aMessage.Append(tlv)); + SuccessOrExit(error = aMessage.Append(routerIdSet)); + + for (uint8_t routerId = 0; routerId <= Mle::kMaxRouterId; routerId++) + { + if (!routerIdSet.Contains(routerId)) + { + continue; + } + + if (Get().MatchesRouterId(routerId)) + { + entry.InitAsSelf(); + } + else + { + entry.InitFrom(*Get().FindRouterById(routerId)); + } + + SuccessOrExit(error = aMessage.Append(entry)); + } + +exit: + return error; +} + #endif // OPENTHREAD_FTD Error Server::AppendMacCounters(Message &aMessage) @@ -368,6 +409,10 @@ Error Server::AppendDiagTlv(uint8_t aTlvType, Message &aMessage) break; } + case Tlv::kEnhancedRoute: + error = AppendEnhancedRoute(aMessage); + break; + case Tlv::kChildTable: error = AppendChildTable(aMessage); break; @@ -1000,6 +1045,51 @@ static void ParseRoute(const RouteTlv &aRouteTlv, otNetworkDiagRoute &aNetworkDi aNetworkDiagRoute.mIdSequence = aRouteTlv.GetRouterIdSequence(); } +static Error ParseEnhancedRoute(const Message &aMessage, uint16_t aOffset, otNetworkDiagEnhRoute &aNetworkDiagEnhRoute) +{ + Error error; + OffsetRange offsetRange; + Tlv tlv; + Mle::RouterIdSet routerIdSet; + uint8_t index; + + SuccessOrExit(error = aMessage.Read(aOffset, tlv)); + + VerifyOrExit(!tlv.IsExtended(), error = kErrorParse); + VerifyOrExit(tlv.GetType() == Tlv::kEnhancedRoute, error = kErrorParse); + + aOffset += sizeof(tlv); + offsetRange.Init(aOffset, tlv.GetLength()); + + SuccessOrExit(error = aMessage.Read(offsetRange, routerIdSet)); + offsetRange.AdvanceOffset(sizeof(routerIdSet)); + + index = 0; + + for (uint8_t routerId = 0; routerId <= Mle::kMaxRouterId; routerId++) + { + EnhancedRouteTlvEntry entry; + + if (!routerIdSet.Contains(routerId)) + { + continue; + } + + SuccessOrExit(error = aMessage.Read(offsetRange, entry)); + offsetRange.AdvanceOffset(sizeof(entry)); + + aNetworkDiagEnhRoute.mRouteData[index].mRouterId = routerId; + entry.Parse(aNetworkDiagEnhRoute.mRouteData[index]); + + index++; + } + + aNetworkDiagEnhRoute.mRouteCount = index; + +exit: + return error; +} + static inline void ParseMacCounters(const MacCountersTlv &aMacCountersTlv, otNetworkDiagMacCounters &aMacCounters) { aMacCounters.mIfInUnknownProtos = aMacCountersTlv.GetIfInUnknownProtos(); @@ -1092,6 +1182,10 @@ Error Client::GetNextDiagTlv(const Coap::Message &aMessage, Iterator &aIterator, break; } + case Tlv::kEnhancedRoute: + SuccessOrExit(error = ParseEnhancedRoute(aMessage, offset, aTlvInfo.mData.mEnhRoute)); + break; + case Tlv::kLeaderData: { LeaderDataTlv leaderDataTlv; diff --git a/src/core/thread/network_diagnostic.hpp b/src/core/thread/network_diagnostic.hpp index 23ed352f6..beeab1ae6 100644 --- a/src/core/thread/network_diagnostic.hpp +++ b/src/core/thread/network_diagnostic.hpp @@ -205,6 +205,7 @@ private: Error AppendRouterNeighborTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo); Error AppendChildTableIp6AddressList(Coap::Message *&aAnswer, AnswerInfo &aInfo); Error AppendChildIp6AddressListTlv(Coap::Message &aAnswer, const Child &aChild); + Error AppendEnhancedRoute(Message &aMessage); static void HandleAnswerResponse(void *aContext, otMessage *aMessage, diff --git a/src/core/thread/network_diagnostic_tlvs.cpp b/src/core/thread/network_diagnostic_tlvs.cpp index 9d4fc6651..e4141547f 100644 --- a/src/core/thread/network_diagnostic_tlvs.cpp +++ b/src/core/thread/network_diagnostic_tlvs.cpp @@ -38,6 +38,35 @@ namespace ot { namespace NetworkDiagnostic { +void EnhancedRouteTlvEntry::InitFrom(const Router &aRouter) +{ + uint16_t data = 0; + + if (aRouter.IsStateValid()) + { + data |= kLinkFlag; + data |= (static_cast(aRouter.GetLinkQualityOut()) << kLinkQualityOutOffset); + data |= (static_cast(aRouter.GetLinkQualityIn()) << kLinkQualityInOffset); + } + + data |= ((static_cast(aRouter.GetNextHop()) & kNextHopMask) << kNextHopOffset); + data |= ((static_cast(aRouter.GetCost()) & kCostMask) << kNextHopCostOffset); + + SetRouteData(data); +} + +void EnhancedRouteTlvEntry::Parse(ParseInfo &aParseInfo) const +{ + uint16_t data = GetRouteData(); + + aParseInfo.mIsSelf = (data & kSelfFlag); + aParseInfo.mHasLink = (data & kLinkFlag); + aParseInfo.mLinkQualityOut = static_cast((data >> kLinkQualityOutOffset) & kLinkQualityMask); + aParseInfo.mLinkQualityIn = static_cast((data >> kLinkQualityInOffset) & kLinkQualityMask); + aParseInfo.mNextHop = static_cast((data >> kNextHopOffset) & kNextHopMask); + aParseInfo.mNextHopCost = static_cast((data >> kNextHopCostOffset) & kCostMask); +} + #if OPENTHREAD_FTD void ChildTlv::InitFrom(const Child &aChild) diff --git a/src/core/thread/network_diagnostic_tlvs.hpp b/src/core/thread/network_diagnostic_tlvs.hpp index 4776c798d..08d9e81c8 100644 --- a/src/core/thread/network_diagnostic_tlvs.hpp +++ b/src/core/thread/network_diagnostic_tlvs.hpp @@ -95,6 +95,7 @@ public: kQueryId = OT_NETWORK_DIAGNOSTIC_TLV_QUERY_ID, kMleCounters = OT_NETWORK_DIAGNOSTIC_TLV_MLE_COUNTERS, kVendorAppUrl = OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_APP_URL, + kEnhancedRoute = OT_NETWORK_DIAGNOSTIC_TLV_ENHANCED_ROUTE, }; /** @@ -954,6 +955,59 @@ private: #endif // OPENTHREAD_FTD +/** + * Represents an Enhanced Route TLV Entry + */ +OT_TOOL_PACKED_BEGIN +class EnhancedRouteTlvEntry +{ +public: + typedef otNetworkDiagEnhRouteData ParseInfo; ///< Parse entry info + + /** + * Initializes the entry as self (associated with device itself). + */ + void InitAsSelf(void) { SetRouteData(kSelfFlag); } + + /** + * Initializes the entry from given `router`. + * + * @param[in] aRouter Router entry to use for initialization. + */ + void InitFrom(const Router &aRouter); + + /** + * Parses the entry and populate the information in given `ParseInfo` struct. + * + * @parma[out] aParseInfo The `ParseInfo` structure to populate. + */ + void Parse(ParseInfo &aParseInfo) const; + +private: + // Format: + // + // 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + // | S | L | LQOut | LQIn | NextHop (6-bit) | NHCost(4 bits)| + // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + static constexpr uint16_t kSelfFlag = 1 << 15; + static constexpr uint16_t kLinkFlag = 1 << 14; + static constexpr uint8_t kLinkQualityOutOffset = 12; + static constexpr uint8_t kLinkQualityInOffset = 10; + static constexpr uint8_t kNextHopOffset = 4; + static constexpr uint8_t kNextHopCostOffset = 0; + + static constexpr uint16_t kLinkQualityMask = 0x3; + static constexpr uint16_t kNextHopMask = 0x3f; + static constexpr uint16_t kCostMask = 0xf; + + uint16_t GetRouteData(void) const { return BigEndian::HostSwap16(mRouteData); } + void SetRouteData(uint16_t aRouteData) { mRouteData = BigEndian::HostSwap16(aRouteData); } + + uint16_t mRouteData; +} OT_TOOL_PACKED_END; + /** * Implements Answer TLV generation and parsing. */ diff --git a/tests/toranj/cli/test-023-mesh-diag.py b/tests/toranj/cli/test-023-mesh-diag.py index 418813e43..1dfe53715 100755 --- a/tests/toranj/cli/test-023-mesh-diag.py +++ b/tests/toranj/cli/test-023-mesh-diag.py @@ -160,6 +160,50 @@ verify(len([line for line in neightable if line.startswith('rloc16')]) == 2) neightable = r3.cli('meshdiag routerneighbortable', r1_rloc) verify(len([line for line in neightable if line.startswith('rloc16')]) == 1) +# Validate network diagnostics enhanced route TLV + +r1_router_id = int(r1_rloc / 1024) +r2_router_id = int(r2_rloc / 1024) +r3_router_id = int(r3_rloc / 1024) + +enh_routes = r1.cli('networkdiagnostic get', r3.get_rloc_ip_addr(), 37) + +verify(enh_routes[1] == 'EnhRoute:') +verify(len(enh_routes) == 5) + +for line in enh_routes[2:]: + line = line.strip() + verify(line.startswith('- RouterId:')) + rid = int(line[11:].split()[0]) + if (rid == r1_router_id): + verify('HasLink:no' in line) + verify(f'NextHop:{r2_router_id}' in line) + elif (rid == r2_router_id): + verify('HasLink:yes' in line) + verify('NextHop:na' in line) + elif (rid == r3_router_id): + verify('The queried device' in line) + else: + verify(False) + +enh_routes = r3.cli('networkdiagnostic get', r2.get_rloc_ip_addr(), 37) + +verify(enh_routes[1] == 'EnhRoute:') +verify(len(enh_routes) == 5) + +for line in enh_routes[2:]: + line = line.strip() + verify(line.startswith('- RouterId:')) + rid = int(line[11:].split()[0]) + if (rid == r1_router_id): + verify('HasLink:yes' in line) + elif (rid == r2_router_id): + verify('The queried device' in line) + elif (rid == r3_router_id): + verify('HasLink:yes' in line) + else: + verify(False) + # ----------------------------------------------------------------------------------------------------------------------- # Test finished