[history-tracker] add DHCPv6 PD state and prefix history (#11848)

This change introduces a new history list to record the DHCPv6 Prefix
Delegation (PD) state and the delegated prefix.

The recorded history can be iterated using the new API
`otHistoryTrackerIterateDhcp6PdHistory()` and viewed from the CLI
using the new `history dhcp6pd` command.
This commit is contained in:
Abtin Keshavarzian
2025-08-26 16:14:46 -07:00
committed by GitHub
parent 819313474e
commit 7bb1ed0a7f
12 changed files with 267 additions and 18 deletions
+28
View File
@@ -30,6 +30,7 @@
#include <stdbool.h>
#include <stdint.h>
#include <openthread/border_routing.h>
#include <openthread/instance.h>
#include <openthread/ip6.h>
#include <openthread/message.h>
@@ -314,6 +315,15 @@ typedef struct otHistoryTrackerFavoredOnLinkPrefix
bool mIsLocal : 1; ///< `true` if the prefix is the local on-link prefix; `false` otherwise.
} otHistoryTrackerFavoredOnLinkPrefix;
/**
* Represents the DHCPv6-PD state and delegated prefix (if any) by a device acting as Border Router (BR).
*/
typedef struct otHistoryTrackerDhcp6PdInfo
{
otIp6Prefix mPrefix; ///< The delegated prefix if any. If none, it is set to `::/0`.
otBorderRoutingDhcp6PdState mState; ///< The DHCPv6 state.
} otHistoryTrackerDhcp6PdInfo;
/**
* Defines events for discovered routers on an Adjacent Infrastructure Link (AIL).
*
@@ -590,6 +600,24 @@ const otHistoryTrackerFavoredOnLinkPrefix *otHistoryTrackerIterateFavoredOnLinkP
otHistoryTrackerIterator *aIterator,
uint32_t *aEntryAge);
/**
* Iterates over the entries in the DHCPv6-PD history list.
*
* Requires both `OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE` and `OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE`.
*
* @param[in] aInstance A pointer to the OpenThread instance.
* @param[in,out] 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 `otHistoryTrackerDhcp6PdInfo` entry or `NULL` if no more entries in the list.
*/
const otHistoryTrackerDhcp6PdInfo *otHistoryTrackerIterateDhcp6PdHistory(otInstance *aInstance,
otHistoryTrackerIterator *aIterator,
uint32_t *aEntryAge);
/**
* Iterates over the entries in the BR AIL routers history list.
*
+1 -1
View File
@@ -52,7 +52,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (528)
#define OPENTHREAD_API_VERSION (529)
/**
* @addtogroup api-instance
+35
View File
@@ -12,6 +12,7 @@ Usage : `history [command] ...`
- [help](#help)
- [ailrouters](#ailrouters)
- [dhcp6pd](#dhcp6pd)
- [dnssrpaddr](#dnssrpaddr)
- [ipaddr](#ipaddr)
- [ipmaddr](#ipmaddr)
@@ -118,6 +119,40 @@ Print the history as a list.
Done
```
### dhcp6pd
Usage `history dhcp6pd [list] [<num-entries>]`
Requires `OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE` and `OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE`.
Print the DHCPv6-PD history. Each entry provides:
- State: Possible states are `disabled`, `stopped`, `running`, `idle`.
- Prefix: The delegated prefix if any. If none `-` is printed.
Print the DHCPv6-PD history as a table.
```bash
> history dhcp6pd
| Age | State | Prefix |
+----------------------+----------+-----------------------------------------------+
| 00:03:20.128 | running | 2001:dc78:510b:1::/64 |
| 00:03:30.152 | running | - |
| 00:03:47.038 | disabled | - |
Done
```
Print the DHCPv6-PD history as a list.
```bash
> history dhcp6pd list
00:03:38.172 -> state:running prefix:2001:dc78:510b:1::/64
00:03:48.191 -> state:running prefix:-
00:04:05.077 -> state:disabled prefix:-
00:05:02.857 -> state:running prefix:-
Done
```
### dnssrpaddr
Usage `history dnssrpaddr [list] [<num-entries>]`
+19 -17
View File
@@ -768,6 +768,24 @@ exit:
}
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
const char *Br::Dhcp6PdStateToString(otBorderRoutingDhcp6PdState aState)
{
static const char *const kDhcpv6PdStateStrings[] = {
"disabled", // (0) OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED
"stopped", // (1) OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED
"running", // (2) OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING
"idle", // (3) OT_BORDER_ROUTING_DHCP6_PD_STATE_IDLE
};
static_assert(0 == OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED, "DHCP6_PD_STATE_DISABLED value is incorrect");
static_assert(1 == OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED, "DHCP6_PD_STATE_STOPPED value is incorrect");
static_assert(2 == OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING, "DHCP6_PD_STATE_RUNNING value is incorrect");
static_assert(3 == OT_BORDER_ROUTING_DHCP6_PD_STATE_IDLE, "DHCP6_PD_STATE_IDLE value is incorrect");
return Stringify(aState, kDhcpv6PdStateStrings);
}
template <> otError Br::Process<Cmd("pd")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
@@ -801,23 +819,7 @@ template <> otError Br::Process<Cmd("pd")>(Arg aArgs[])
*/
else if (aArgs[0] == "state")
{
static const char *const kDhcpv6PdStateStrings[] = {
"disabled", // (0) OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED
"stopped", // (1) OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED
"running", // (2) OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING
"idle", // (3) OT_BORDER_ROUTING_DHCP6_PD_STATE_IDLE
};
static_assert(0 == OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED,
"OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED value is not expected!");
static_assert(1 == OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED,
"OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED value is not expected!");
static_assert(2 == OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING,
"OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING value is not expected!");
static_assert(3 == OT_BORDER_ROUTING_DHCP6_PD_STATE_IDLE,
"OT_BORDER_ROUTING_DHCP6_PD_STATE_IDLE value is not expected!");
OutputLine("%s", Stringify(otBorderRoutingDhcp6PdGetState(GetInstancePtr()), kDhcpv6PdStateStrings));
OutputLine("%s", Dhcp6PdStateToString(otBorderRoutingDhcp6PdGetState(GetInstancePtr())));
}
/**
* @cli br pd omrprefix
+9
View File
@@ -39,6 +39,7 @@
#include <openthread/border_routing.h>
#include "cli/cli_config.h"
#include "cli/cli_history.hpp"
#include "cli/cli_utils.hpp"
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
@@ -51,6 +52,10 @@ namespace Cli {
*/
class Br : private Utils
{
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
friend class History;
#endif
public:
/**
* Constructor
@@ -94,6 +99,10 @@ private:
otError ParsePrefixTypeArgs(Arg aArgs[], PrefixType &aFlags);
void OutputRouterInfo(const otBorderRoutingRouterEntry &aEntry, RouterOutputMode aMode);
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
static const char *Dhcp6PdStateToString(otBorderRoutingDhcp6PdState aState);
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE
static void HandleMultiAilDetected(bool aDetected, void *aContext);
void HandleMultiAilDetected(bool aDetected);
+81
View File
@@ -38,6 +38,7 @@
#include <string.h>
#include "cli/cli.hpp"
#include "cli/cli_br.hpp"
namespace ot {
namespace Cli {
@@ -1541,6 +1542,83 @@ exit:
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
/**
* @cli history dhcp6pd
* @code
* history dhcp6pd
* | Age | State | Prefix |
* +----------------------+----------+-----------------------------------------------+
* | 00:03:20.128 | running | 2001:dc78:510b:1::/64 |
* | 00:03:30.152 | running | - |
* | 00:03:47.038 | disabled | - |
* Done
* @endcode
* @code
* history dhcp6pd list
* 00:03:38.172 -> state:running prefix:2001:dc78:510b:1::/64
* 00:03:48.191 -> state:running prefix:-
* 00:04:05.077 -> state:disabled prefix:-
* 00:05:02.857 -> state:running prefix:-
* Done
* @endcode
* @cparam history dhcp6pd [@ca{list}] [@ca{num-entries}]
* * Use the `list` option to display the output in list format. Otherwise, the output is shown in table format.
* * Use the `num-entries` option to limit the output to the number of most-recent entries specified. If this option
* is not used, all stored entries are shown in the output.
* @par
* Displays the DHCPv6-PD history in table or list format.
* @par
* Each table or list entry provides:
* * Age: Time elapsed since the command was issued, and given in the format:
* `hours`:`minutes`:`seconds`.`milliseconds`
* * State: Possible states are `disabled`, `stopped`, `running`, `idle`.
* * Prefix: The delegated prefix if any. If none `-` is outputted.
* @sa otHistoryTrackerIterateDhcp6PdHistory
*/
template <> otError History::Process<Cmd("dhcp6pd")>(Arg aArgs[])
{
otError error;
bool isList;
uint16_t numEntries;
otHistoryTrackerIterator iterator;
const otHistoryTrackerDhcp6PdInfo *info;
uint32_t entryAge;
char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
char prefixString[OT_IP6_PREFIX_STRING_SIZE];
SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
if (!isList)
{
static const char *const kTitles[] = {"Age", "State", "Prefix"};
static const uint8_t kColumnWidths[] = {22, 10, 47};
OutputTableHeader(kTitles, kColumnWidths);
}
otHistoryTrackerInitIterator(&iterator);
for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
{
info = otHistoryTrackerIterateDhcp6PdHistory(GetInstancePtr(), &iterator, &entryAge);
VerifyOrExit(info != nullptr);
otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
otIp6PrefixToString(&info->mPrefix, prefixString, sizeof(prefixString));
OutputLine(isList ? "%s -> state:%s prefix:%s" : "| %20s | %-8s | %-45s |", ageString,
Br::Dhcp6PdStateToString(info->mState), (info->mPrefix.mLength != 0) ? prefixString : "-");
}
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
/**
* @cli history omrprefix
* @code
@@ -1828,6 +1906,9 @@ otError History::Process(Arg aArgs[])
static constexpr Command kCommands[] = {
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
CmdEntry("ailrouters"),
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
CmdEntry("dhcp6pd"),
#endif
#endif
CmdEntry("dnssrpaddr"), CmdEntry("ipaddr"), CmdEntry("ipmaddr"),
CmdEntry("neighbor"), CmdEntry("netinfo"),
+11
View File
@@ -186,6 +186,17 @@ const otHistoryTrackerAilRouter *otHistoryTrackerIterateAilRoutersHistory(otInst
*aEntryAge);
}
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
const otHistoryTrackerDhcp6PdInfo *otHistoryTrackerIterateDhcp6PdHistory(otInstance *aInstance,
otHistoryTrackerIterator *aIterator,
uint32_t *aEntryAge)
{
AssertPointerIsNotNull(aEntryAge);
return AsCoreType(aInstance).Get<HistoryTracker::Local>().IterateDhcp6PdHistory(AsCoreType(aIterator), *aEntryAge);
}
#endif
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
void otHistoryTrackerEntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize)
@@ -4539,6 +4539,9 @@ RoutingManager::PdPrefixManager::PdPrefixManager(Instance &aInstance)
, mNumPlatformRaReceived(0)
, mLastPlatformRaTime(0)
, mTimer(aInstance)
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
, mRecordHistoryTask(aInstance)
#endif
{
}
@@ -4618,6 +4621,10 @@ void RoutingManager::PdPrefixManager::SetState(State aState)
break;
}
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
mRecordHistoryTask.Post();
#endif
mStateCallback.InvokeIfSet(MapEnum(mState));
exit:
@@ -4664,6 +4671,10 @@ void RoutingManager::PdPrefixManager::WithdrawPrefix(void)
Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kImmediately);
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
mRecordHistoryTask.Post();
#endif
exit:
return;
}
@@ -4749,6 +4760,10 @@ void RoutingManager::PdPrefixManager::ApplyFavoredPrefix(const PdPrefix &aFavore
mPrefix = aFavoredPrefix;
LogInfo("DHCPv6 PD prefix set to %s", mPrefix.GetPrefix().ToString().AsCString());
Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kImmediately);
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
mRecordHistoryTask.Post();
#endif
}
if (HasPrefix())
@@ -4799,6 +4814,15 @@ exit:
return;
}
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
void RoutingManager::PdPrefixManager::HandleRecordHistoryTask(void)
{
Get<HistoryTracker::Local>().RecordDhcp6Pd(mState, mPrefix.GetPrefix());
}
#endif
bool RoutingManager::PdPrefixManager::PdPrefix::IsValidPdPrefix(void) const
{
// We should accept ULA prefix since it could be used by the internet infrastructure like NAT64.
@@ -75,6 +75,7 @@
#include "common/owning_list.hpp"
#include "common/pool.hpp"
#include "common/string.hpp"
#include "common/tasklet.hpp"
#include "common/timer.hpp"
#include "crypto/sha256.hpp"
#include "net/ip6.hpp"
@@ -1724,6 +1725,9 @@ private:
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
void HandlePdPrefixManagerTimer(void) { mPdPrefixManager.HandleTimer(); }
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
void HandlePdPrefixManagerTask(void) { mPdPrefixManager.HandleRecordHistoryTask(); }
#endif
class PdPrefixManager : public InstanceLocator
{
@@ -1752,6 +1756,9 @@ private:
void HandleTimer(void) { WithdrawPrefix(); }
void SetStateCallback(Dhcp6PdCallback aCallback, void *aContext) { mStateCallback.Set(aCallback, aContext); }
void Evaluate(void);
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
void HandleRecordHistoryTask(void);
#endif
private:
class PdPrefix : public OnLinkPrefix
@@ -1773,6 +1780,9 @@ private:
using PrefixTimer = TimerMilliIn<RoutingManager, &RoutingManager::HandlePdPrefixManagerTimer>;
using StateCallback = Callback<Dhcp6PdCallback>;
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
using RecordHistoryTask = TaskletIn<RoutingManager, &RoutingManager::HandlePdPrefixManagerTask>;
#endif
State mState;
uint32_t mNumPlatformPioProcessed;
@@ -1781,6 +1791,9 @@ private:
StateCallback mStateCallback;
PrefixTimer mTimer;
PdPrefix mPrefix;
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
RecordHistoryTask mRecordHistoryTask;
#endif
};
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
+11
View File
@@ -204,6 +204,17 @@
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_ON_LINK_PREFIX_LIST_SIZE 16
#endif
/**
* @def OPENTHREAD_CONFIG_HISTORY_TRACKER_DHCP6_PD_LIST_SIZE
*
* Specifies the maximum number of entries in DHCPv6-PD history.
*
* Can be set to zero to configure History Tracker module not to collect any DHCPv6-PD info.
*/
#ifndef OPENTHREAD_CONFIG_HISTORY_TRACKER_DHCP6_PD_LIST_SIZE
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_DHCP6_PD_LIST_SIZE 16
#endif
/**
* @def OPENTHREAD_CONFIG_HISTORY_TRACKER_AIL_ROUTER_LIST_SIZE
*
+18
View File
@@ -610,6 +610,21 @@ exit:
AilRouter *Local::RecordAilRouterEvent(void) { return mAilRoutersHistory.AddNewEntry(); }
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
void Local::RecordDhcp6Pd(BorderRouter::RoutingManager::Dhcp6PdState aState, const Ip6::Prefix &aPrefix)
{
Dhcp6PdInfo *entry = mDhcp6PdHistory.AddNewEntry();
VerifyOrExit(entry != nullptr);
entry->mState = MapEnum(aState);
entry->mPrefix = aPrefix;
exit:
return;
}
#endif
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
void Local::HandleNotifierEvents(Events aEvents)
@@ -646,6 +661,9 @@ void Local::HandleTimer(void)
mFavoredOmrPrefixHistory.UpdateAgedEntries();
mFavoredOnLinkPrefixHistory.UpdateAgedEntries();
mAilRoutersHistory.UpdateAgedEntries();
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
mDhcp6PdHistory.UpdateAgedEntries();
#endif
#endif
mTimer.Start(kAgeCheckPeriod);
}
+17
View File
@@ -114,6 +114,7 @@ typedef otHistoryTrackerBorderAgentEpskcEvent EpskcEvent; ///< Border Agent ePSK
typedef otHistoryTrackerFavoredOmrPrefix FavoredOmrPrefix; ///< Favored OMR Prefix
typedef otHistoryTrackerFavoredOnLinkPrefix FavoredOnLinkPrefix; ///< Favored On-link Prefix
typedef otHistoryTrackerAilRouter AilRouter; ///< An AIL router tracked when acting as BR
typedef otHistoryTrackerDhcp6PdInfo Dhcp6PdInfo; ///< DHPCv6-PD info.
#endif
/**
@@ -277,6 +278,7 @@ public:
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
const FavoredOmrPrefix *IterateFavoredOmrPrefixHistory(Iterator &aIterator, uint32_t &aEntryAge) const
{
return mFavoredOmrPrefixHistory.Iterate(aIterator, aEntryAge);
@@ -291,8 +293,16 @@ public:
{
return mAilRoutersHistory.Iterate(aIterator, aEntryAge);
}
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
const Dhcp6PdInfo *IterateDhcp6PdHistory(Iterator &aIterator, uint32_t &aEntryAge) const
{
return mDhcp6PdHistory.Iterate(aIterator, aEntryAge);
}
#endif
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
/**
* Converts a given entry age to a human-readable string.
*
@@ -329,6 +339,7 @@ private:
static constexpr uint16_t kOmrPrefixListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_OMR_PREFIX_LIST_SIZE;
static constexpr uint16_t kOnLinkPrefixListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_ON_LINK_PREFIX_LIST_SIZE;
static constexpr uint16_t kAilRouterListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_AIL_ROUTER_LIST_SIZE;
static constexpr uint16_t kDhcp6PdListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_DHCP6_PD_LIST_SIZE;
typedef otHistoryTrackerAddressEvent AddressEvent;
@@ -520,6 +531,9 @@ private:
bool aIsLocal);
void RecordFavoredOnLinkPrefix(const Ip6::Prefix &aPrefix, bool aIsLocal);
AilRouter *RecordAilRouterEvent(void);
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
void RecordDhcp6Pd(BorderRouter::RoutingManager::Dhcp6PdState aState, const Ip6::Prefix &aPrefix);
#endif
#endif
using TrackerTimer = TimerMilliIn<Local, &Local::HandleTimer>;
@@ -541,6 +555,9 @@ private:
EntryList<FavoredOmrPrefix, kOmrPrefixListSize> mFavoredOmrPrefixHistory;
EntryList<FavoredOnLinkPrefix, kOnLinkPrefixListSize> mFavoredOnLinkPrefixHistory;
EntryList<AilRouter, kAilRouterListSize> mAilRoutersHistory;
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
EntryList<Dhcp6PdInfo, kDhcp6PdListSize> mDhcp6PdHistory;
#endif
#endif
TrackerTimer mTimer;