[history-tracker] add support for neighbor table history (#6947)

This commit adds support in `HistoryTracker` to record the history of
changes to the neighbor table. An entry is recorded when a child or
router neighbor is added, removed, changed, or being restored (the
restored entries are only used for the previous children list
retrieved from non-volatile settings on the device (re)start). Each
entry provides:

- Timestamp
- Type: Child or Router
- Event: Added, Removed, Changed
- Extended Address
- RLOC16
- MLE Link Mode (rx-on-when-idle, FTD/MDT, full-netdata).
- Average RSS (in dBm) of received frames from the neighbor at
  the time the entry was recorded.

This commit also updates CLI and adds `history neighbor` command
to get the neighbor history. It also updates the documentation.
This commit is contained in:
Abtin Keshavarzian
2021-08-24 15:07:02 -07:00
committed by GitHub
parent d53e4994d0
commit ee807d3001
11 changed files with 296 additions and 14 deletions
+51 -2
View File
@@ -61,6 +61,8 @@ extern "C" {
*/
#define OT_HISTORY_TRACKER_MAX_AGE (49 * 24 * 60 * 60 * 1000u)
#define OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE 21 ///< Recommended size for string representation of an entry age.
/**
* This type represents an iterator to iterate through a history list.
*
@@ -124,6 +126,38 @@ typedef struct otHistoryTrackerMessageInfo
bool mRadioTrelUdp6 : 1; ///< Indicates whether msg was sent/received over a TREL radio link.
} otHistoryTrackerMessageInfo;
/**
* This enumeration defines the events in a neighbor info (i.e. whether neighbor is added, removed, or changed).
*
* Event `OT_HISTORY_TRACKER_NEIGHBOR_EVENT_RESTORING` is applicable to child neighbors only. It is triggered after
* the device (re)starts and when the previous children list is retrieved from non-volatile settings and the device
* tries to restore connection to them.
*
*/
typedef enum
{
OT_HISTORY_TRACKER_NEIGHBOR_EVENT_ADDED = 0, ///< Neighbor is added.
OT_HISTORY_TRACKER_NEIGHBOR_EVENT_REMOVED = 1, ///< Neighbor is removed.
OT_HISTORY_TRACKER_NEIGHBOR_EVENT_CHANGED = 2, ///< Neighbor changed (e.g., device mode flags changed).
OT_HISTORY_TRACKER_NEIGHBOR_EVENT_RESTORING = 3, ///< Neighbor is being restored (applicable to child only).
} otHistoryTrackerNeighborEvent;
/**
* This structure represents a neighbor info.
*
*/
typedef struct otHistoryTrackerNeighborInfo
{
otExtAddress mExtAddress; ///< Neighbor's Extended Address.
uint16_t mRloc16; ///< Neighbor's RLOC16.
int8_t mAverageRssi; ///< Average RSSI of rx frames from neighbor at the time of recording entry.
uint8_t mEvent : 2; ///< Indicates the event (`OT_HISTORY_TRACKER_NEIGHBOR_EVENT_*` enumeration).
bool mRxOnWhenIdle : 1; ///< Rx-on-when-idle.
bool mFullThreadDevice : 1; ///< Full Thread Device.
bool mFullNetworkData : 1; ///< Full Network Data.
bool mIsChild : 1; ///< Indicates whether or not the neighbor is a child.
} otHistoryTrackerNeighborInfo;
/**
* This function initializes an `otHistoryTrackerIterator`.
*
@@ -191,7 +225,22 @@ const otHistoryTrackerMessageInfo *otHistoryTrackerIterateTxHistory(otInstance *
otHistoryTrackerIterator *aIterator,
uint32_t * aEntryAge);
#define OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE 21 ///< Recommended size for string representation of an entry age.
/**
* This function iterates over the entries in the neighbor history list.
*
* @param[in] aInstance A pointer to the OpenThread instance.
* @param[inout] aIterator A pointer to an iterator. MUST be initialized or the behavior is undefined.
* @param[out] aEntryAge A pointer to a variable to output the entry's age. MUST NOT be NULL.
* Age is provided as the duration (in milliseconds) from when entry was recorded to
* @p aIterator initialization time. It is set to `OT_HISTORY_TRACKER_MAX_AGE` for entries
* older than max age.
*
* @returns The `otHistoryTrackerNeighborInfo` entry or `NULL` if no more entries in the list.
*
*/
const otHistoryTrackerNeighborInfo *otHistoryTrackerIterateNeighborHistory(otInstance * aInstance,
otHistoryTrackerIterator *aIterator,
uint32_t * aEntryAge);
/**
* This function converts a given entry age to a human-readable string.
@@ -204,7 +253,7 @@ const otHistoryTrackerMessageInfo *otHistoryTrackerIterateTxHistory(otInstance *
*
* @param[in] aEntryAge The entry age (duration in msec).
* @param[out] aBuffer A pointer to a char array to output the string (MUST NOT be NULL).
* @param[in] aSize The size of @p aBuffer (in bytes). Recommended to use `OT_IP6_ADDRESS_STRING_SIZE`.
* @param[in] aSize The size of @p aBuffer. Recommended to use `OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE`.
*
*/
void otHistoryTrackerEntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize);
+1 -1
View File
@@ -53,7 +53,7 @@ extern "C" {
* @note This number versions both OpenThread platform and user APIs.
*
*/
#define OPENTHREAD_API_VERSION (156)
#define OPENTHREAD_API_VERSION (157)
/**
* @addtogroup api-instance
+51 -1
View File
@@ -11,6 +11,7 @@ The number of entries recorded for each history list is configurable through a s
Usage : `history [command] ...`
- [help](#help)
- [neighbor](#neighbor)
- [netinfo](#netinfo)
- [rx](#rx)
- [rxtx](#rxtx)
@@ -45,6 +46,7 @@ Print SRP client help menu.
```bash
> history help
help
neighbor
netinfo
rx
rxtx
@@ -53,11 +55,59 @@ Done
>
```
### neighbor
Usage `history neighbor [list] [<num-entries>]`
Print the neighbor table history. Each entry provides:
- Type: Child or Router
- Event: Added, Removed, Changed (e.g., mode change).
- Extended Address
- RLOC16
- MLE Link Mode
- Average RSS (in dBm) of received frames from neighbor at the time the entry was recorded
Print the neighbor history as a table.
```bash
> history neighbor
| Age | Type | Event | Extended Address | RLOC16 | Mode | Ave RSS |
+----------------------+--------+-----------+------------------+--------+------+---------+
| 00:00:29.233 | Child | Added | ae5105292f0b9169 | 0x8404 | - | -20 |
| 00:01:38.368 | Child | Removed | ae5105292f0b9169 | 0x8401 | - | -20 |
| 00:04:27.181 | Child | Changed | ae5105292f0b9169 | 0x8401 | - | -20 |
| 00:04:51.236 | Router | Added | 865c7ca38a5fa960 | 0x9400 | rdn | -20 |
| 00:04:51.587 | Child | Removed | 865c7ca38a5fa960 | 0x8402 | rdn | -20 |
| 00:05:22.764 | Child | Changed | ae5105292f0b9169 | 0x8401 | rn | -20 |
| 00:06:40.764 | Child | Added | 4ec99efc874a1841 | 0x8403 | r | -20 |
| 00:06:44.060 | Child | Added | 865c7ca38a5fa960 | 0x8402 | rdn | -20 |
| 00:06:49.515 | Child | Added | ae5105292f0b9169 | 0x8401 | - | -20 |
Done
```
Print the neighbor history as a list.
```bash
> history neighbor list
00:00:34.753 -> type:Child event:Added extaddr:ae5105292f0b9169 rloc16:0x8404 mode:- rss:-20
00:01:43.888 -> type:Child event:Removed extaddr:ae5105292f0b9169 rloc16:0x8401 mode:- rss:-20
00:04:32.701 -> type:Child event:Changed extaddr:ae5105292f0b9169 rloc16:0x8401 mode:- rss:-20
00:04:56.756 -> type:Router event:Added extaddr:865c7ca38a5fa960 rloc16:0x9400 mode:rdn rss:-20
00:04:57.107 -> type:Child event:Removed extaddr:865c7ca38a5fa960 rloc16:0x8402 mode:rdn rss:-20
00:05:28.284 -> type:Child event:Changed extaddr:ae5105292f0b9169 rloc16:0x8401 mode:rn rss:-20
00:06:46.284 -> type:Child event:Added extaddr:4ec99efc874a1841 rloc16:0x8403 mode:r rss:-20
00:06:49.580 -> type:Child event:Added extaddr:865c7ca38a5fa960 rloc16:0x8402 mode:rdn rss:-20
00:06:55.035 -> type:Child event:Added extaddr:ae5105292f0b9169 rloc16:0x8401 mode:- rss:-20
Done
```
### netinfo
Usage `history netinfo [list] [<num-entries>]`
Print the Network Info history. Each Network Info provides
Print the Network Info history. Each Network Info provides:
- Device Role
- MLE Link Mode
+65
View File
@@ -80,6 +80,71 @@ otError History::ParseArgs(Arg aArgs[], bool &aIsList, uint16_t &aNumEntries) co
return aArgs[0].IsEmpty() ? OT_ERROR_NONE : OT_ERROR_INVALID_ARGS;
}
otError History::ProcessNeighbor(Arg aArgs[])
{
static const char *const kEventString[] = {
/* (0) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_ADDED -> */ "Added",
/* (1) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_REMOVED -> */ "Removed",
/* (2) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_CHANGED -> */ "Changed",
/* (3) OT_HISTORY_TRACKER_NEIGHBOR_EVENT_RESTORING -> */ "Restoring",
};
otError error;
bool isList;
uint16_t numEntries;
otHistoryTrackerIterator iterator;
const otHistoryTrackerNeighborInfo *info;
uint32_t entryAge;
char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
otLinkModeConfig mode;
char linkModeString[Interpreter::kLinkModeStringSize];
static_assert(0 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_ADDED, "NEIGHBOR_EVENT_ADDED value is incorrect");
static_assert(1 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_REMOVED, "NEIGHBOR_EVENT_REMOVED value is incorrect");
static_assert(2 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_CHANGED, "NEIGHBOR_EVENT_CHANGED value is incorrect");
static_assert(3 == OT_HISTORY_TRACKER_NEIGHBOR_EVENT_RESTORING, "NEIGHBOR_EVENT_RESTORING value is incorrect");
SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
if (!isList)
{
// | Age | Type | Event | Extended Address | RLOC16 | Mode | Ave RSS |
// +----------------------+--------+-----------+------------------+--------+------+---------+
static const char *const kNeighborInfoTitles[] = {
"Age", "Type", "Event", "Extended Address", "RLOC16", "Mode", "Ave RSS",
};
static const uint8_t kNeighborInfoColumnWidths[] = {22, 8, 11, 18, 8, 6, 9};
mInterpreter.OutputTableHeader(kNeighborInfoTitles, kNeighborInfoColumnWidths);
}
otHistoryTrackerInitIterator(&iterator);
for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
{
info = otHistoryTrackerIterateNeighborHistory(mInterpreter.mInstance, &iterator, &entryAge);
VerifyOrExit(info != nullptr);
otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
mode.mRxOnWhenIdle = info->mRxOnWhenIdle;
mode.mDeviceType = info->mFullThreadDevice;
mode.mNetworkData = info->mFullNetworkData;
Interpreter::LinkModeToString(mode, linkModeString);
mInterpreter.OutputFormat(isList ? "%s -> type:%s event:%s extaddr:" : "| %20s | %-6s | %-9s | ", ageString,
info->mIsChild ? "Child" : "Router", kEventString[info->mEvent]);
mInterpreter.OutputExtAddress(info->mExtAddress);
mInterpreter.OutputLine(isList ? " rloc16:0x%04x mode:%s rss:%d" : " | 0x%04x | %-4s | %7d |", info->mRloc16,
linkModeString, info->mAverageRssi);
}
exit:
return error;
}
otError History::ProcessNetInfo(Arg aArgs[])
{
otError error;
+3 -2
View File
@@ -97,6 +97,7 @@ private:
otError ProcessHelp(Arg aArgs[]);
otError ProcessNetInfo(Arg aArgs[]);
otError ProcessNeighbor(Arg aArgs[]);
otError ProcessRx(Arg aArgs[]);
otError ProcessRxTx(Arg aArgs[]);
otError ProcessTx(Arg aArgs[]);
@@ -111,8 +112,8 @@ private:
static const char *MessageTypeToString(const otHistoryTrackerMessageInfo &aInfo);
static constexpr Command sCommands[] = {
{"help", &History::ProcessHelp}, {"netinfo", &History::ProcessNetInfo}, {"rx", &History::ProcessRx},
{"rxtx", &History::ProcessRxTx}, {"tx", &History::ProcessTx},
{"help", &History::ProcessHelp}, {"neighbor", &History::ProcessNeighbor}, {"netinfo", &History::ProcessNetInfo},
{"rx", &History::ProcessRx}, {"rxtx", &History::ProcessRxTx}, {"tx", &History::ProcessTx},
};
static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted");
+10
View File
@@ -78,6 +78,16 @@ const otHistoryTrackerMessageInfo *otHistoryTrackerIterateTxHistory(otInstance *
*static_cast<Utils::HistoryTracker::Iterator *>(aIterator), *aEntryAge);
}
const otHistoryTrackerNeighborInfo *otHistoryTrackerIterateNeighborHistory(otInstance * aInstance,
otHistoryTrackerIterator *aIterator,
uint32_t * aEntryAge)
{
Instance &instance = *static_cast<Instance *>(aInstance);
return instance.Get<Utils::HistoryTracker>().IterateNeighborHistory(
*static_cast<Utils::HistoryTracker::Iterator *>(aIterator), *aEntryAge);
}
void otHistoryTrackerEntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize)
{
Utils::HistoryTracker::EntryAgeToString(aEntryAge, aBuffer, aSize);
+12
View File
@@ -91,4 +91,16 @@
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_EXCLUDE_THREAD_CONTROL_MESSAGES 1
#endif
/**
* @def OPENTHREAD_CONFIG_HISTORY_TRACKER_NEIGHBOR_LIST_SIZE
*
* Specifies the maximum number of entries in neighbor table history list.
*
* Can be set to zero to configure History Tracker module not to collect any neighbor table history.
*
*/
#ifndef OPENTHREAD_CONFIG_HISTORY_TRACKER_NEIGHBOR_LIST_SIZE
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_NEIGHBOR_LIST_SIZE 64
#endif
#endif // CONFIG_HISTORY_TRACKER_H_
+1
View File
@@ -248,6 +248,7 @@ void ChildTable::Restore(void)
child->SetLastHeard(TimerMilli::GetNow());
child->SetVersion(static_cast<uint8_t>(childInfo.GetVersion()));
Get<IndirectSender>().SetChildUseShortAddress(*child, true);
Get<NeighborTable>().Signal(NeighborTable::kChildAdded, *child);
numChildren++;
}
+10 -1
View File
@@ -257,7 +257,9 @@ exit:
void NeighborTable::Signal(Event aEvent, const Neighbor &aNeighbor)
{
#if !OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
if (mCallback != nullptr)
#endif
{
EntryInfo info;
@@ -279,7 +281,14 @@ void NeighborTable::Signal(Event aEvent, const Neighbor &aNeighbor)
break;
}
mCallback(static_cast<otNeighborTableEvent>(aEvent), &info);
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
Get<Utils::HistoryTracker>().RecordNeighborEvent(aEvent, info);
if (mCallback != nullptr)
#endif
{
mCallback(static_cast<otNeighborTableEvent>(aEvent), &info);
}
}
#if OPENTHREAD_CONFIG_OTNS_ENABLE
+62
View File
@@ -193,6 +193,67 @@ exit:
return;
}
void HistoryTracker::RecordNeighborEvent(NeighborTable::Event aEvent, const NeighborTable::EntryInfo &aInfo)
{
NeighborInfo *entry = mNeighborHistory.AddNewEntry();
VerifyOrExit(entry != nullptr);
switch (aEvent)
{
case NeighborTable::kChildAdded:
case NeighborTable::kChildRemoved:
case NeighborTable::kChildModeChanged:
entry->mExtAddress = aInfo.mInfo.mChild.mExtAddress;
entry->mRloc16 = aInfo.mInfo.mChild.mRloc16;
entry->mAverageRssi = aInfo.mInfo.mChild.mAverageRssi;
entry->mRxOnWhenIdle = aInfo.mInfo.mChild.mRxOnWhenIdle;
entry->mFullThreadDevice = aInfo.mInfo.mChild.mFullThreadDevice;
entry->mFullNetworkData = aInfo.mInfo.mChild.mFullNetworkData;
entry->mIsChild = true;
break;
case NeighborTable::kRouterAdded:
case NeighborTable::kRouterRemoved:
entry->mExtAddress = aInfo.mInfo.mRouter.mExtAddress;
entry->mRloc16 = aInfo.mInfo.mRouter.mRloc16;
entry->mAverageRssi = aInfo.mInfo.mRouter.mAverageRssi;
entry->mRxOnWhenIdle = aInfo.mInfo.mRouter.mRxOnWhenIdle;
entry->mFullThreadDevice = aInfo.mInfo.mRouter.mFullThreadDevice;
entry->mFullNetworkData = aInfo.mInfo.mRouter.mFullNetworkData;
entry->mIsChild = false;
break;
}
switch (aEvent)
{
case NeighborTable::kChildAdded:
if (aInfo.mInfo.mChild.mIsStateRestoring)
{
entry->mEvent = kNeighborRestoring;
break;
}
OT_FALL_THROUGH;
case NeighborTable::kRouterAdded:
entry->mEvent = kNeighborAdded;
break;
case NeighborTable::kChildRemoved:
case NeighborTable::kRouterRemoved:
entry->mEvent = kNeighborRemoved;
break;
case NeighborTable::kChildModeChanged:
entry->mEvent = kNeighborChanged;
break;
}
exit:
return;
}
void HistoryTracker::HandleNotifierEvents(Events aEvents)
{
if (aEvents.ContainsAny(kEventThreadRoleChanged | kEventThreadRlocAdded | kEventThreadRlocRemoved |
@@ -212,6 +273,7 @@ void HistoryTracker::HandleTimer(void)
mNetInfoHistory.UpdateAgedEntries();
mRxHistory.UpdateAgedEntries();
mTxHistory.UpdateAgedEntries();
mNeighborHistory.UpdateAgedEntries();
mTimer.Start(kAgeCheckPeriod);
}
+30 -7
View File
@@ -50,6 +50,7 @@
#include "thread/mesh_forwarder.hpp"
#include "thread/mle.hpp"
#include "thread/mle_types.hpp"
#include "thread/neighbor_table.hpp"
namespace ot {
namespace Utils {
@@ -63,6 +64,7 @@ class HistoryTracker : public InstanceLocator, private NonCopyable
friend class ot::MeshForwarder;
friend class ot::Notifier;
friend class ot::Mle::Mle;
friend class ot::NeighborTable;
public:
/**
@@ -117,6 +119,12 @@ public:
*/
typedef otHistoryTrackerMessageInfo MessageInfo;
/**
* This type represents a neighbor info.
*
*/
typedef otHistoryTrackerNeighborInfo NeighborInfo;
/**
* This constructor initializes the `HistoryTracker`.
*
@@ -173,6 +181,11 @@ public:
return mTxHistory.Iterate(aIterator, aEntryAge);
}
const NeighborInfo *IterateNeighborHistory(Iterator &aIterator, uint32_t &aEntryAge) const
{
return mNeighborHistory.Iterate(aIterator, aEntryAge);
}
/**
* This static method converts a given entry age to a human-readable string.
*
@@ -201,13 +214,21 @@ private:
static constexpr uint32_t kAgeCheckPeriod = 16 * kOneHourInMsec;
static constexpr uint16_t kNetInfoListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_INFO_LIST_SIZE;
static constexpr uint16_t kRxListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_RX_LIST_SIZE;
static constexpr uint16_t kTxListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_TX_LIST_SIZE;
static constexpr uint16_t kNetInfoListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_INFO_LIST_SIZE;
static constexpr uint16_t kRxListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_RX_LIST_SIZE;
static constexpr uint16_t kTxListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_TX_LIST_SIZE;
static constexpr uint16_t kNeighborListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_NEIGHBOR_LIST_SIZE;
static constexpr int8_t kInvalidRss = OT_RADIO_RSSI_INVALID;
static constexpr uint16_t kInvalidRloc16 = Mac::kShortAddrInvalid;
typedef otHistoryTrackerNeighborEvent NeighborEvent;
static constexpr NeighborEvent kNeighborAdded = OT_HISTORY_TRACKER_NEIGHBOR_EVENT_ADDED;
static constexpr NeighborEvent kNeighborRemoved = OT_HISTORY_TRACKER_NEIGHBOR_EVENT_REMOVED;
static constexpr NeighborEvent kNeighborChanged = OT_HISTORY_TRACKER_NEIGHBOR_EVENT_CHANGED;
static constexpr NeighborEvent kNeighborRestoring = OT_HISTORY_TRACKER_NEIGHBOR_EVENT_RESTORING;
class Timestamp
{
public:
@@ -301,14 +322,16 @@ private:
void RecordNetworkInfo(void);
void RecordMessage(const Message &aMessage, const Mac::Address &aMacAddress, MessageType aType);
void RecordNeighborEvent(NeighborTable::Event aEvent, const NeighborTable::EntryInfo &aInfo);
void HandleNotifierEvents(Events aEvents);
static void HandleTimer(Timer &aTimer);
void HandleTimer(void);
EntryList<NetworkInfo, kNetInfoListSize> mNetInfoHistory;
EntryList<MessageInfo, kRxListSize> mRxHistory;
EntryList<MessageInfo, kTxListSize> mTxHistory;
TimerMilli mTimer;
EntryList<NetworkInfo, kNetInfoListSize> mNetInfoHistory;
EntryList<MessageInfo, kRxListSize> mRxHistory;
EntryList<MessageInfo, kTxListSize> mTxHistory;
EntryList<NeighborInfo, kNeighborListSize> mNeighborHistory;
TimerMilli mTimer;
};
} // namespace Utils