[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.
This commit is contained in:
Abtin Keshavarzian
2025-03-27 22:44:35 -07:00
committed by GitHub
parent 36c9d14a34
commit 8a14243dc8
9 changed files with 307 additions and 1 deletions
+1 -1
View File
@@ -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
+47
View File
@@ -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;
+36
View File
@@ -7555,6 +7555,7 @@ template <> otError Interpreter::Process<Cmd("networkdiagnostic")>(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));
+1
View File
@@ -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);
+94
View File
@@ -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<Mle::Mle>().IsRouterOrLeader());
Get<RouterTable>().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<Mle::Mle>().MatchesRouterId(routerId))
{
entry.InitAsSelf();
}
else
{
entry.InitFrom(*Get<RouterTable>().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;
+1
View File
@@ -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,
@@ -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<uint16_t>(aRouter.GetLinkQualityOut()) << kLinkQualityOutOffset);
data |= (static_cast<uint16_t>(aRouter.GetLinkQualityIn()) << kLinkQualityInOffset);
}
data |= ((static_cast<uint16_t>(aRouter.GetNextHop()) & kNextHopMask) << kNextHopOffset);
data |= ((static_cast<uint16_t>(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<uint8_t>((data >> kLinkQualityOutOffset) & kLinkQualityMask);
aParseInfo.mLinkQualityIn = static_cast<uint8_t>((data >> kLinkQualityInOffset) & kLinkQualityMask);
aParseInfo.mNextHop = static_cast<uint8_t>((data >> kNextHopOffset) & kNextHopMask);
aParseInfo.mNextHopCost = static_cast<uint8_t>((data >> kNextHopCostOffset) & kCostMask);
}
#if OPENTHREAD_FTD
void ChildTlv::InitFrom(const Child &aChild)
@@ -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.
*/
+44
View File
@@ -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