mirror of
https://github.com/espressif/openthread.git
synced 2026-08-15 15:17:46 +00:00
[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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user