[mlr] introduce state machine and use timer in Mlr::Manager (#13132)

This commit introduces a structured state machine to `Mlr::Manager` to
coordinate Multicast Listener Registration (MLR) activities more
efficiently. The previous implementation relied on independent delay
variables and the global `TimeTicker`, which could lead to redundant
or premature registrations, especially when a Primary Backbone Router
(PBBR) was newly discovered or updated.

The new state machine (`kStateStopped`, `kStateIdle`,
`kStateToRegisterAll`, `kStateRegistering`, `kStateRegistered`,
`kStateNewAddrToRegister`) provides explicit transitions for the
entire MLR lifecycle. This ensures that registrations are properly
aggregated and that periodic renewals are correctly rescheduled after
successful out-of-band registrations.

Additionally, the manager now uses a dedicated `TimerMilli` instead of
`TimeTicker`, reducing system-wide overhead and providing more
precise timing control.
This commit is contained in:
Abtin Keshavarzian
2026-05-26 08:38:18 -07:00
committed by GitHub
parent cf7e5bb2b3
commit 5783555d4c
6 changed files with 377 additions and 187 deletions
-7
View File
@@ -111,13 +111,6 @@ void TimeTicker::HandleTimer(void)
} }
#endif #endif
#if OPENTHREAD_CONFIG_MLR_ENABLE || (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE)
if (mReceivers & Mask(kMlrManager))
{
Get<Mlr::Manager>().HandleTimeTick();
}
#endif
if (mReceivers & Mask(kIp6Mpl)) if (mReceivers & Mask(kIp6Mpl))
{ {
Get<Ip6::Mpl>().HandleTimeTick(); Get<Ip6::Mpl>().HandleTimeTick();
-1
View File
@@ -67,7 +67,6 @@ public:
kChildSupervisor, ///< `ChildSupervisor` kChildSupervisor, ///< `ChildSupervisor`
kIp6FragmentReassembler, ///< `Ip6::Ip6` (handling of fragmented messages) kIp6FragmentReassembler, ///< `Ip6::Ip6` (handling of fragmented messages)
kDuaManager, ///< `DuaManager` kDuaManager, ///< `DuaManager`
kMlrManager, ///< `MlrManager`
kNetworkDataNotifier, ///< `NetworkData::Notifier` kNetworkDataNotifier, ///< `NetworkData::Notifier`
kIp6Mpl, ///< `Ip6::Mpl` kIp6Mpl, ///< `Ip6::Mpl`
kBbrLocal, ///< `BackboneRouter::Local` kBbrLocal, ///< `BackboneRouter::Local`
+254 -143
View File
@@ -44,15 +44,82 @@ RegisterLogModule("MlrManager");
Manager::Manager(Instance &aInstance) Manager::Manager(Instance &aInstance)
: InstanceLocator(aInstance) : InstanceLocator(aInstance)
, mReregistrationDelay(0)
, mSendDelay(0)
, mPending(false)
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
, mRegisterPending(false) , mRegisterPending(false)
#endif #endif
, mState(kStateStopped)
, mTimer(aInstance)
{ {
} }
void Manager::EnterState(State aState)
{
State oldState;
VerifyOrExit(mState != aState);
oldState = mState;
mState = aState;
if (oldState == kStateRegistering)
{
IgnoreError(Get<Tmf::Agent>().AbortTransaction(HandleResponse, this));
}
if (mState == kStateToRegisterAll)
{
// Clear "MlrRegistered" state on all addresses
#if OPENTHREAD_CONFIG_MLR_ENABLE
for (Ip6::Netif::MulticastAddress &addr : Get<ThreadNetif>().GetMulticastAddresses())
{
if (addr.IsMlrCandidate())
{
addr.SetMlrRegistered(false);
}
}
#endif
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
{
child.ClearMlrRegisteredStateOnAllIp6Addresses();
}
#endif
}
exit:
return;
}
void Manager::UpdateState(void)
{
// `UpdateState()` evaluates whether the MLR manager should be
// running or not. It handles starting or stopping the manager.
bool canRun = false;
if (Get<Mle::Mle>().IsAttached() && Get<BackboneRouter::Leader>().HasPrimary())
{
canRun = Get<Mle::Mle>().IsFullThreadDevice() || Get<Mle::Mle>().GetParent().IsThreadVersion1p1();
}
if (!IsRunning())
{
VerifyOrExit(canRun);
EnterState(kStateToRegisterAll);
ScheduleTimerForReregistrationDelay();
}
else
{
VerifyOrExit(!canRun);
EnterState(kStateStopped);
mTimer.Stop();
}
exit:
return;
}
void Manager::HandleNotifierEvents(Events aEvents) void Manager::HandleNotifierEvents(Events aEvents)
{ {
#if OPENTHREAD_CONFIG_MLR_ENABLE #if OPENTHREAD_CONFIG_MLR_ENABLE
@@ -62,43 +129,90 @@ void Manager::HandleNotifierEvents(Events aEvents)
} }
#endif #endif
if (aEvents.Contains(kEventThreadRoleChanged) && Get<Mle::Mle>().IsChild()) if (aEvents.Contains(kEventThreadRoleChanged))
{ {
ScheduleNextRegistration(kReregister); UpdateState();
} }
} }
void Manager::HandleBackboneRouterPrimaryUpdate(BackboneRouter::PrimaryEvent aEvent) void Manager::HandleBackboneRouterPrimaryUpdate(BackboneRouter::PrimaryEvent aEvent)
{ {
RegistrationRequest request = kRenew; UpdateState();
switch (aEvent) switch (aEvent)
{ {
case BackboneRouter::kPrimaryAdded: case BackboneRouter::kPrimaryAdded:
case BackboneRouter::kPrimaryUpdatedReregister: case BackboneRouter::kPrimaryRemoved:
request = kReregister;
break; break;
default:
case BackboneRouter::kPrimaryUpdatedReregister:
VerifyOrExit(IsRunning());
EnterState(kStateToRegisterAll);
ScheduleTimerForReregistrationDelay();
break;
case BackboneRouter::kPrimaryConfigParameterChanged:
// When PBBR config parameters (like MLR Timeout) change, we
// need to recalculate and update the renewal time for all
// currently registered addresses to ensure they don't expire
// prematurely.
switch (GetState())
{
case kStateStopped:
case kStateIdle:
case kStateToRegisterAll:
case kStateRegistering:
break;
case kStateRegistered:
mTimer.Stop();
OT_FALL_THROUGH;
case kStateNewAddrToRegister:
DetermineRenewTime();
mTimer.FireAtIfEarlier(mRenewTime);
break;
}
break; break;
} }
ScheduleNextRegistration(request); exit:
return;
} }
#if OPENTHREAD_CONFIG_MLR_ENABLE #if OPENTHREAD_CONFIG_MLR_ENABLE
void Manager::HandleEventIp6MulticastSubscribed(void) void Manager::HandleEventIp6MulticastSubscribed(void)
{ {
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE bool hasUnregistered = false;
for (Ip6::Netif::MulticastAddress &addr : Get<ThreadNetif>().GetMulticastAddresses()) for (Ip6::Netif::MulticastAddress &addr : Get<ThreadNetif>().GetMulticastAddresses())
{ {
if (addr.IsMlrCandidate() && !addr.IsMlrRegistered() && IsAddressRegisteredByAnyChild(addr.GetAddress())) if (!addr.IsMlrCandidate())
{ {
addr.SetMlrRegistered(true); continue;
} }
}
if (!addr.IsMlrRegistered())
{
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
if (IsAddressRegisteredByAnyChild(addr.GetAddress()))
{
addr.SetMlrRegistered(true);
continue;
}
#endif #endif
ScheduleSend(0); hasUnregistered = true;
}
}
if (hasUnregistered)
{
ScheduleNewAddrRegistration(0, kMaxNewNetifAddrRegistraionDelay);
}
} }
bool Manager::IsAddressRegisteredByNetif(const Ip6::Address &aAddress) const bool Manager::IsAddressRegisteredByNetif(const Ip6::Address &aAddress) const
@@ -121,6 +235,11 @@ bool Manager::IsAddressRegisteredByNetif(const Ip6::Address &aAddress) const
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
bool Manager::IsAddressRegisteredByAnyChild(const Ip6::Address &aAddress) const
{
return IsAddressRegisteredByAnyChildExcept(aAddress, nullptr);
}
bool Manager::IsAddressRegisteredByAnyChildExcept(const Ip6::Address &aAddress, const Child *aExceptChild) const bool Manager::IsAddressRegisteredByAnyChildExcept(const Ip6::Address &aAddress, const Child *aExceptChild) const
{ {
bool isRegistered = false; bool isRegistered = false;
@@ -169,7 +288,7 @@ void Manager::UpdateProxiedSubscriptions(Child &aChild, const ChildAddressArray
if (hasUnregistered) if (hasUnregistered)
{ {
ScheduleSend(Random::NonCrypto::GenerateInClosedRange<uint16_t>(1, BackboneRouter::kParentAggregateDelay)); ScheduleNewAddrRegistration(kMinNewChildAddrRegistrationDelay, kMaxNewChildAddrRegistrationDelay);
} }
exit: exit:
@@ -178,52 +297,39 @@ exit:
#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE #endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
void Manager::ScheduleSend(uint16_t aDelay) void Manager::ScheduleNewAddrRegistration(uint32_t aMinDelay, uint32_t aMaxDelay)
{ {
OT_ASSERT(!mPending || mSendDelay == 0); uint32_t delay;
VerifyOrExit(!mPending); switch (GetState())
{
case kStateIdle:
EnterState(kStateToRegisterAll);
break;
if (aDelay == 0) case kStateRegistered:
{ case kStateNewAddrToRegister:
mSendDelay = 0; EnterState(kStateNewAddrToRegister);
Send(); break;
}
else if (mSendDelay == 0 || mSendDelay > aDelay) case kStateStopped:
{ case kStateToRegisterAll:
mSendDelay = aDelay; case kStateRegistering:
ExitNow();
} }
UpdateTimeTickerRegistration(); delay = Random::NonCrypto::GenerateInClosedRange(aMinDelay, aMaxDelay);
// The timer may already be running for a periodic renewal or for a
// previously scheduled new address registration. We ensure the
// timer fires at the earliest requested time.
mTimer.FireAtIfEarlier(TimerMilli::GetNow() + delay);
exit: exit:
return; return;
} }
void Manager::UpdateTimeTickerRegistration(void)
{
if (mSendDelay == 0 && mReregistrationDelay == 0)
{
Get<TimeTicker>().UnregisterReceiver(TimeTicker::kMlrManager);
}
else
{
Get<TimeTicker>().RegisterReceiver(TimeTicker::kMlrManager);
}
}
bool Manager::ShouldRegister(void) const
{
bool shouldRegister = false;
VerifyOrExit(Get<Mle::Mle>().IsFullThreadDevice() || Get<Mle::Mle>().GetParent().IsThreadVersion1p1());
VerifyOrExit(Get<BackboneRouter::Leader>().HasPrimary());
shouldRegister = true;
exit:
return shouldRegister;
}
void Manager::DetermineAddressesToRegister(AddressArray &aAddresses) const void Manager::DetermineAddressesToRegister(AddressArray &aAddresses) const
{ {
aAddresses.Clear(); aAddresses.Clear();
@@ -255,22 +361,63 @@ exit:
return; return;
} }
void Manager::Send(void) void Manager::SendNextRequest(void)
{ {
Error error;
AddressArray addresses; AddressArray addresses;
VerifyOrExit(!mPending, error = kErrorBusy); IgnoreError(Get<Tmf::Agent>().AbortTransaction(HandleResponse, this));
VerifyOrExit(Get<Mle::Mle>().IsAttached(), error = kErrorInvalidState);
VerifyOrExit(ShouldRegister(), error = kErrorInvalidState);
DetermineAddressesToRegister(addresses); DetermineAddressesToRegister(addresses);
VerifyOrExit(!addresses.IsEmpty(), error = kErrorNotFound);
SuccessOrExit(error = SendMessage(addresses.GetArrayBuffer(), addresses.GetLength(), nullptr, HandleResponse)); if (addresses.IsEmpty())
{
// There are no (more) addresses to register. If we are still
// in `kStateToRegisterAll`, it indicates there is no multicast
// address to register at all, so we transition to `kStateIdle`.
// Otherwise, we at least sent one registration, and all addresses
// are now registered, so we transition to `kStateRegistered` and
// schedule the next periodic renewal.
mPending = true; switch (GetState())
{
case kStateToRegisterAll:
EnterState(kStateIdle);
break;
case kStateRegistering:
case kStateNewAddrToRegister:
EnterState(kStateRegistered);
mTimer.FireAt(mRenewTime);
break;
case kStateStopped:
case kStateIdle:
case kStateRegistered:
break;
}
ExitNow();
}
if (SendMessage(addresses.GetArrayBuffer(), addresses.GetLength(), nullptr, HandleResponse) != kErrorNone)
{
mTimer.Start(kSendFailureRetryDelay);
ExitNow();
}
// Transitioning from `kStateToRegisterAll` to `kStateRegistering`
// indicates the start of a full registration cycle. We record the
// start time and determine the renewal time. The start time is
// tracked so we can recalculate the renewal time if PBBR config
// parameters change later.
if (GetState() == kStateToRegisterAll)
{
mStartTime = TimerMilli::GetNow();
DetermineRenewTime();
}
EnterState(kStateRegistering);
// Generally Thread 1.2 Router would send MLR.req on behalf for MA (scope >=4) subscribed by its MTD child. // Generally Thread 1.2 Router would send MLR.req on behalf for MA (scope >=4) subscribed by its MTD child.
// When Thread 1.2 MTD attaches to Thread 1.1 parent, 1.2 MTD should send MLR.req to PBBR itself. // When Thread 1.2 MTD attaches to Thread 1.1 parent, 1.2 MTD should send MLR.req to PBBR itself.
@@ -281,10 +428,7 @@ void Manager::Send(void)
} }
exit: exit:
if (error == kErrorNoBufs) return;
{
ScheduleSend(1);
}
} }
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
@@ -391,6 +535,15 @@ exit:
} }
void Manager::HandleResponse(Coap::Msg *aMsg, Error aResult) void Manager::HandleResponse(Coap::Msg *aMsg, Error aResult)
{
VerifyOrExit(GetState() == kStateRegistering);
ProcessResponse(aMsg, aResult);
exit:
return;
}
void Manager::ProcessResponse(Coap::Msg *aMsg, Error aResult)
{ {
Error error; Error error;
uint8_t status; uint8_t status;
@@ -398,8 +551,6 @@ void Manager::HandleResponse(Coap::Msg *aMsg, Error aResult)
AddressArray registeredAddresses; AddressArray registeredAddresses;
OwnedPtr<Coap::Message> requestMsg; OwnedPtr<Coap::Message> requestMsg;
mPending = false;
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Parse the response message // Parse the response message
@@ -460,18 +611,14 @@ void Manager::HandleResponse(Coap::Msg *aMsg, Error aResult)
exit: exit:
if ((error == kErrorNone) && (status == kStatusSuccess)) if ((error == kErrorNone) && (status == kStatusSuccess))
{ {
// Send an MLR request for any remaining unregistered addresses. SendNextRequest();
ScheduleSend(0);
} }
else else
{ {
// If a registration attempt fails, retry it after a random // If a registration attempt fails, retry it after a random
// delay (same as re-registration delay). // delay (same as re-registration delay).
if (Get<BackboneRouter::Leader>().HasPrimary()) ScheduleTimerForReregistrationDelay();
{
ScheduleSend(Get<BackboneRouter::Leader>().GetConfig().SelectRandomReregistrationDelay());
}
} }
} }
@@ -527,94 +674,58 @@ exit:
return error; return error;
} }
void Manager::HandleTimeTick(void) void Manager::ScheduleTimerForReregistrationDelay(void)
{ {
if (mSendDelay > 0 && --mSendDelay == 0) mTimer.Start(Time::SecToMsec(Get<BackboneRouter::Leader>().GetConfig().SelectRandomReregistrationDelay()));
{
Send();
}
if (mReregistrationDelay > 0 && --mReregistrationDelay == 0)
{
Reregister();
}
UpdateTimeTickerRegistration();
} }
void Manager::Reregister(void) void Manager::HandleTimer(void)
{ {
LogInfo("Reregister"); switch (GetState())
#if OPENTHREAD_CONFIG_MLR_ENABLE
for (Ip6::Netif::MulticastAddress &addr : Get<ThreadNetif>().GetMulticastAddresses())
{ {
if (addr.IsMlrCandidate()) case kStateNewAddrToRegister:
if (mRenewTime > TimerMilli::GetNow())
{ {
addr.SetMlrRegistered(false); break;
} }
}
#endif
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
{
child.ClearMlrRegisteredStateOnAllIp6Addresses();
}
#endif
ScheduleSend(0); OT_FALL_THROUGH;
ScheduleNextRegistration(kRenew); case kStateRegistered:
EnterState(kStateToRegisterAll);
OT_FALL_THROUGH;
case kStateToRegisterAll:
case kStateRegistering:
break;
case kStateStopped:
case kStateIdle:
ExitNow();
}
SendNextRequest();
exit:
return;
} }
uint32_t Manager::DetermineRenewDelay(void) void Manager::DetermineRenewTime(void)
{ {
// As per Thread spec, the renew delay is randomly chosen // As per Thread spec, the renew delay is randomly chosen
// between (0.5 * MLR-Timeout) and (MLR-Timeout - 9 seconds). // between (0.5 * MLR-Timeout) and (MLR-Timeout - 9 seconds).
// The `kRenewGuardTime`(9 sec) allows time for transmission, // The `RenewGuardTime`(9 sec) allows time for transmission,
// potential retransmissions, and acknowledgment before the // potential retransmissions, and acknowledgment before the
// actual timeout. // actual timeout.
uint32_t timeout = Get<BackboneRouter::Leader>().GetConfig().GetMlrTimeout(); uint32_t timeout;
timeout = Clamp<uint32_t>(timeout, BackboneRouter::kMinMlrTimeout, kLongRenewTimeout); timeout = Get<BackboneRouter::Leader>().GetConfig().GetMlrTimeout();
timeout = Time::SecToMsec(Clamp<uint32_t>(timeout, BackboneRouter::kMinMlrTimeout, kLongRenewTimeout));
return Random::NonCrypto::GenerateInClosedRange<uint32_t>((timeout / 2) + 1, timeout - kRenewGuardTime); mRenewTime =
} mStartTime + Random::NonCrypto::GenerateInClosedRange((timeout / 2) + 1, timeout - kRenewGuardTimeInMsec);
void Manager::ScheduleNextRegistration(RegistrationRequest aRequest)
{
uint32_t delay;
if (!ShouldRegister())
{
mReregistrationDelay = 0;
ExitNow();
}
switch (aRequest)
{
case kReregister:
delay = Get<BackboneRouter::Leader>().GetConfig().SelectRandomReregistrationDelay();
break;
case kRenew:
delay = DetermineRenewDelay();
break;
default:
ExitNow();
}
if (mReregistrationDelay == 0 || mReregistrationDelay > delay)
{
mReregistrationDelay = delay;
LogDebg("ScheduleNextRegistration() delay:%lu", ToUlong(delay));
}
exit:
UpdateTimeTickerRegistration();
} }
} // namespace Mlr } // namespace Mlr
+35 -29
View File
@@ -49,7 +49,6 @@
#include "common/locator.hpp" #include "common/locator.hpp"
#include "common/non_copyable.hpp" #include "common/non_copyable.hpp"
#include "common/notifier.hpp" #include "common/notifier.hpp"
#include "common/time_ticker.hpp"
#include "common/timer.hpp" #include "common/timer.hpp"
#include "net/netif.hpp" #include "net/netif.hpp"
#include "thread/child.hpp" #include "thread/child.hpp"
@@ -79,7 +78,6 @@ namespace Mlr {
class Manager : public InstanceLocator, private NonCopyable class Manager : public InstanceLocator, private NonCopyable
{ {
friend class ot::Notifier; friend class ot::Notifier;
friend class ot::TimeTicker;
public: public:
typedef otIp6RegisterMulticastListenersCallback RegisterCallback; typedef otIp6RegisterMulticastListenersCallback RegisterCallback;
@@ -142,19 +140,40 @@ public:
#endif #endif
private: private:
static constexpr uint32_t kLongRenewTimeout = 4 * Time::kOneHourInSec; // `MLR_TIMEOUT_LONG` (in sec) // Delays (in msec) applied before registration attempts when new
static constexpr uint32_t kRenewGuardTime = 9; // (in sec). // `Netif` or child multicast addresses are added. The longer
// delay for child addresses allows the parent to aggregate
// multiple address updates into a single MLR request.
static constexpr uint32_t kMaxNewNetifAddrRegistraionDelay = 100;
static constexpr uint32_t kMinNewChildAddrRegistrationDelay = 750;
static constexpr uint32_t kMaxNewChildAddrRegistrationDelay = 5000;
enum RegistrationRequest : uint8_t static constexpr uint32_t kLongRenewTimeout = 4 * Time::kOneHourInSec; // `MLR_TIMEOUT_LONG` (in sec)
static constexpr uint32_t kRenewGuardTimeInMsec = 9 * Time::kOneSecondInMsec; // (in msec)
static constexpr uint32_t kSendFailureRetryDelay = 1000; // (in msec)
enum State : uint8_t
{ {
kReregister, kStateStopped, // Manager is stopped (e.g., no PBBR).
kRenew, kStateIdle, // Started but has no multicast addresses to register.
kStateToRegisterAll, // Waiting to register (or re-register) all multicast addresses.
kStateRegistering, // MLR.req is sent, waiting for MLR.rsp, or waiting to retry.
kStateRegistered, // All addresses are registered, waiting for periodic renewal.
kStateNewAddrToRegister, // All were registered, but new addresses are pending registration.
}; };
State GetState(void) const { return mState; }
bool IsRunning(void) const { return mState != kStateStopped; }
void EnterState(State aState);
void UpdateState(void);
void HandleNotifierEvents(Events aEvents); void HandleNotifierEvents(Events aEvents);
bool ShouldRegister(void) const;
void DetermineAddressesToRegister(AddressArray &aAddresses) const; void DetermineAddressesToRegister(AddressArray &aAddresses) const;
void Send(void); void SendNextRequest(void);
void DetermineRenewTime(void);
void ScheduleTimerForReregistrationDelay(void);
void ScheduleNewAddrRegistration(uint32_t aMinDelay, uint32_t aMaxDelay);
void ProcessResponse(Coap::Msg *aMsg, Error aResult);
void HandleTimer(void);
Error SendMessage(const Ip6::Address *aAddresses, Error SendMessage(const Ip6::Address *aAddresses,
uint8_t aAddressNum, uint8_t aAddressNum,
const uint32_t *aTimeout, const uint32_t *aTimeout,
@@ -162,8 +181,6 @@ private:
DeclareTmfResponseHandlerIn(Manager, HandleResponse); DeclareTmfResponseHandlerIn(Manager, HandleResponse);
uint32_t DetermineRenewDelay(void);
static Error ParseResponse(Error aResult, Coap::Msg *aMsg, uint8_t &aStatus, AddressArray &aFailedAddresses); static Error ParseResponse(Error aResult, Coap::Msg *aMsg, uint8_t &aStatus, AddressArray &aFailedAddresses);
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
@@ -176,31 +193,20 @@ private:
#endif #endif
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
bool IsAddressRegisteredByAnyChild(const Ip6::Address &aAddress) const bool IsAddressRegisteredByAnyChild(const Ip6::Address &aAddress) const;
{
return IsAddressRegisteredByAnyChildExcept(aAddress, nullptr);
}
bool IsAddressRegisteredByAnyChildExcept(const Ip6::Address &aAddress, const Child *aExceptChild) const; bool IsAddressRegisteredByAnyChildExcept(const Ip6::Address &aAddress, const Child *aExceptChild) const;
#endif #endif
void ScheduleSend(uint16_t aDelay); using DelayTimer = TimerMilliIn<Manager, &Manager::HandleTimer>;
void UpdateTimeTickerRegistration(void);
void ScheduleNextRegistration(RegistrationRequest aRequest);
void Reregister(void);
void HandleTimeTick(void);
#if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE) && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE #if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE) && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
Callback<RegisterCallback> mRegisterCallback; Callback<RegisterCallback> mRegisterCallback;
bool mRegisterPending;
#endif #endif
State mState;
uint32_t mReregistrationDelay; TimeMilli mStartTime;
uint16_t mSendDelay; TimeMilli mRenewTime;
DelayTimer mTimer;
bool mPending : 1;
#if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE) && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
bool mRegisterPending : 1;
#endif
}; };
} // namespace Mlr } // namespace Mlr
+1 -1
View File
@@ -426,7 +426,7 @@ ot_nexus_test(mac_scan "core;nexus")
ot_nexus_test(mle_router_role_allowed "core;nexus") ot_nexus_test(mle_router_role_allowed "core;nexus")
ot_nexus_test(mle_blocking_downgrade "core;nexus") ot_nexus_test(mle_blocking_downgrade "core;nexus")
ot_nexus_test(mle_msg_key_seq_jump "core;nexus") ot_nexus_test(mle_msg_key_seq_jump "core;nexus")
ot_nexus_test(mlr_redundant "core;nexus") ot_nexus_test(mlr_manager "core;nexus")
ot_nexus_test(nat64_translator "core;nexus") ot_nexus_test(nat64_translator "core;nexus")
ot_nexus_test(netdata_publisher "core;nexus") ot_nexus_test(netdata_publisher "core;nexus")
ot_nexus_test(on_mesh_prefix "core;nexus") ot_nexus_test(on_mesh_prefix "core;nexus")
@@ -37,7 +37,8 @@ namespace ot {
namespace Nexus { namespace Nexus {
static constexpr uint32_t kFormNetworkTime = 10 * 1000; static constexpr uint32_t kFormNetworkTime = 10 * 1000;
static constexpr uint32_t kAttachToRouterTime = 200 * 1000; static constexpr uint32_t kAttachAsRouterTime = 200 * 1000;
static constexpr uint32_t kAttachAsSedTime = 10 * 1000;
static constexpr uint32_t kMlrRegistrationTime = 10 * 1000; static constexpr uint32_t kMlrRegistrationTime = 10 * 1000;
struct Context struct Context
@@ -45,7 +46,7 @@ struct Context
Context(void) { mMlrReqCount = 0; } Context(void) { mMlrReqCount = 0; }
uint32_t mMlrReqCount; uint32_t mMlrReqCount;
Ip6::Address mSharedAddress; Ip6::Address mTargetAddress;
}; };
static Error BrCoapInterceptor(void *aContext, const Coap::Msg &aMsg) static Error BrCoapInterceptor(void *aContext, const Coap::Msg &aMsg)
@@ -70,7 +71,7 @@ static Error BrCoapInterceptor(void *aContext, const Coap::Msg &aMsg)
Log(" - %s", address.ToString().AsCString()); Log(" - %s", address.ToString().AsCString());
} }
if (addresses.Contains(context->mSharedAddress)) if (addresses.Contains(context->mTargetAddress))
{ {
context->mMlrReqCount++; context->mMlrReqCount++;
} }
@@ -115,20 +116,25 @@ void TestMlrRedundant(void)
VerifyOrQuit(br.Get<Mle::Mle>().IsLeader()); VerifyOrQuit(br.Get<Mle::Mle>().IsLeader());
router.Join(br, Node::kAsFtd); router.Join(br, Node::kAsFtd);
nexus.AdvanceTime(kAttachToRouterTime); nexus.AdvanceTime(kAttachAsRouterTime);
VerifyOrQuit(router.Get<Mle::Mle>().IsRouter()); VerifyOrQuit(router.Get<Mle::Mle>().IsRouter());
sed1.Join(router, Node::kAsSed); sed1.Join(router, Node::kAsSed);
sed2.Join(router, Node::kAsSed); sed2.Join(router, Node::kAsSed);
sed3.Join(router, Node::kAsSed); sed3.Join(router, Node::kAsSed);
nexus.AdvanceTime(kAttachToRouterTime);
SuccessOrQuit(sed1.Get<DataPollSender>().SetExternalPollPeriod(5 * Time::kOneSecondInMsec));
SuccessOrQuit(sed2.Get<DataPollSender>().SetExternalPollPeriod(5 * Time::kOneSecondInMsec));
SuccessOrQuit(sed3.Get<DataPollSender>().SetExternalPollPeriod(5 * Time::kOneSecondInMsec));
nexus.AdvanceTime(kAttachAsSedTime);
VerifyOrQuit(sed1.Get<Mle::Mle>().IsChild()); VerifyOrQuit(sed1.Get<Mle::Mle>().IsChild());
VerifyOrQuit(sed2.Get<Mle::Mle>().IsChild()); VerifyOrQuit(sed2.Get<Mle::Mle>().IsChild());
VerifyOrQuit(sed3.Get<Mle::Mle>().IsChild()); VerifyOrQuit(sed3.Get<Mle::Mle>().IsChild());
SuccessOrQuit(sharedAddress.FromString("ff04::1")); SuccessOrQuit(sharedAddress.FromString("ff04::1"));
context.mSharedAddress = sharedAddress; context.mTargetAddress = sharedAddress;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Set an interceptor on leader to count incoming MLR.req messages"); Log("Set an interceptor on leader to count incoming MLR.req messages");
@@ -164,12 +170,87 @@ void TestMlrRedundant(void)
VerifyOrQuit(context.mMlrReqCount == 1); VerifyOrQuit(context.mMlrReqCount == 1);
} }
void TestMlrState(void)
{
/**
* This test verifies the state machine transitions and timing behavior of the MLR manager.
*/
Core nexus;
Node &br = nexus.CreateNode();
Node &router = nexus.CreateNode();
Ip6::Address firstAddress;
Ip6::Address newAddress;
BackboneRouter::Config config;
Context context;
nexus.AdvanceTime(0);
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Form topology");
AllowLinkBetween(br, router);
br.Form();
nexus.AdvanceTime(kFormNetworkTime);
router.Join(br, Node::kAsFtd);
nexus.AdvanceTime(kAttachAsRouterTime);
SuccessOrQuit(firstAddress.FromString("ff04::1"));
context.mTargetAddress = firstAddress;
br.Get<Tmf::Agent>().SetInterceptor(BrCoapInterceptor, &context);
Log("Enable BBR and verify initial registration");
br.Get<BackboneRouter::Local>().SetEnabled(true);
SuccessOrQuit(router.Get<Ip6::Netif>().SubscribeExternalMulticast(firstAddress));
nexus.AdvanceTime(kMlrRegistrationTime);
VerifyOrQuit(context.mMlrReqCount == 1);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Add new address and verify quick registration");
SuccessOrQuit(newAddress.FromString("ff04::100"));
context.mTargetAddress = newAddress;
context.mMlrReqCount = 0;
SuccessOrQuit(router.Get<Ip6::Netif>().SubscribeExternalMulticast(newAddress));
// Verify it is registered quickly (within short aggregation window)
nexus.AdvanceTime(1000);
VerifyOrQuit(context.mMlrReqCount == 1);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Update MLR Timeout and verify renewal time update");
config = br.Get<BackboneRouter::Leader>().GetConfig();
Log("Initial MLR timeout: %lu", ToUlong(br.Get<BackboneRouter::Leader>().GetConfig().GetMlrTimeout()));
// We set a short MLR timeout (300 seconds is the minimum).
config.mReregistrationDelay = 10;
config.mMlrTimeout = 300;
SuccessOrQuit(br.Get<BackboneRouter::Local>().SetConfig(config));
context.mTargetAddress = firstAddress;
context.mMlrReqCount = 0;
Log("Wait for renewal registration");
// Renewal happens between (0.5 * 300) = 150s and (300 - 9) = 291s.
nexus.AdvanceTime(300 * Time::kOneSecondInMsec);
VerifyOrQuit(context.mMlrReqCount >= 1);
Log("Renewal registration successful");
}
} // namespace Nexus } // namespace Nexus
} // namespace ot } // namespace ot
int main(void) int main(void)
{ {
ot::Nexus::TestMlrRedundant(); ot::Nexus::TestMlrRedundant();
ot::Nexus::TestMlrState();
printf("All tests passed\n"); printf("All tests passed\n");
return 0; return 0;
} }