[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
#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))
{
Get<Ip6::Mpl>().HandleTimeTick();
-1
View File
@@ -67,7 +67,6 @@ public:
kChildSupervisor, ///< `ChildSupervisor`
kIp6FragmentReassembler, ///< `Ip6::Ip6` (handling of fragmented messages)
kDuaManager, ///< `DuaManager`
kMlrManager, ///< `MlrManager`
kNetworkDataNotifier, ///< `NetworkData::Notifier`
kIp6Mpl, ///< `Ip6::Mpl`
kBbrLocal, ///< `BackboneRouter::Local`
+254 -143
View File
@@ -44,15 +44,82 @@ RegisterLogModule("MlrManager");
Manager::Manager(Instance &aInstance)
: InstanceLocator(aInstance)
, mReregistrationDelay(0)
, mSendDelay(0)
, mPending(false)
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
, mRegisterPending(false)
#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)
{
#if OPENTHREAD_CONFIG_MLR_ENABLE
@@ -62,43 +129,90 @@ void Manager::HandleNotifierEvents(Events aEvents)
}
#endif
if (aEvents.Contains(kEventThreadRoleChanged) && Get<Mle::Mle>().IsChild())
if (aEvents.Contains(kEventThreadRoleChanged))
{
ScheduleNextRegistration(kReregister);
UpdateState();
}
}
void Manager::HandleBackboneRouterPrimaryUpdate(BackboneRouter::PrimaryEvent aEvent)
{
RegistrationRequest request = kRenew;
UpdateState();
switch (aEvent)
{
case BackboneRouter::kPrimaryAdded:
case BackboneRouter::kPrimaryUpdatedReregister:
request = kReregister;
case BackboneRouter::kPrimaryRemoved:
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;
}
ScheduleNextRegistration(request);
exit:
return;
}
#if OPENTHREAD_CONFIG_MLR_ENABLE
void Manager::HandleEventIp6MulticastSubscribed(void)
{
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
bool hasUnregistered = false;
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
ScheduleSend(0);
hasUnregistered = true;
}
}
if (hasUnregistered)
{
ScheduleNewAddrRegistration(0, kMaxNewNetifAddrRegistraionDelay);
}
}
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
bool Manager::IsAddressRegisteredByAnyChild(const Ip6::Address &aAddress) const
{
return IsAddressRegisteredByAnyChildExcept(aAddress, nullptr);
}
bool Manager::IsAddressRegisteredByAnyChildExcept(const Ip6::Address &aAddress, const Child *aExceptChild) const
{
bool isRegistered = false;
@@ -169,7 +288,7 @@ void Manager::UpdateProxiedSubscriptions(Child &aChild, const ChildAddressArray
if (hasUnregistered)
{
ScheduleSend(Random::NonCrypto::GenerateInClosedRange<uint16_t>(1, BackboneRouter::kParentAggregateDelay));
ScheduleNewAddrRegistration(kMinNewChildAddrRegistrationDelay, kMaxNewChildAddrRegistrationDelay);
}
exit:
@@ -178,52 +297,39 @@ exit:
#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)
{
mSendDelay = 0;
Send();
}
else if (mSendDelay == 0 || mSendDelay > aDelay)
{
mSendDelay = aDelay;
case kStateRegistered:
case kStateNewAddrToRegister:
EnterState(kStateNewAddrToRegister);
break;
case kStateStopped:
case kStateToRegisterAll:
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:
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
{
aAddresses.Clear();
@@ -255,22 +361,63 @@ exit:
return;
}
void Manager::Send(void)
void Manager::SendNextRequest(void)
{
Error error;
AddressArray addresses;
VerifyOrExit(!mPending, error = kErrorBusy);
VerifyOrExit(Get<Mle::Mle>().IsAttached(), error = kErrorInvalidState);
VerifyOrExit(ShouldRegister(), error = kErrorInvalidState);
IgnoreError(Get<Tmf::Agent>().AbortTransaction(HandleResponse, this));
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.
// 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:
if (error == kErrorNoBufs)
{
ScheduleSend(1);
}
return;
}
#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)
{
VerifyOrExit(GetState() == kStateRegistering);
ProcessResponse(aMsg, aResult);
exit:
return;
}
void Manager::ProcessResponse(Coap::Msg *aMsg, Error aResult)
{
Error error;
uint8_t status;
@@ -398,8 +551,6 @@ void Manager::HandleResponse(Coap::Msg *aMsg, Error aResult)
AddressArray registeredAddresses;
OwnedPtr<Coap::Message> requestMsg;
mPending = false;
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Parse the response message
@@ -460,18 +611,14 @@ void Manager::HandleResponse(Coap::Msg *aMsg, Error aResult)
exit:
if ((error == kErrorNone) && (status == kStatusSuccess))
{
// Send an MLR request for any remaining unregistered addresses.
ScheduleSend(0);
SendNextRequest();
}
else
{
// If a registration attempt fails, retry it after a random
// delay (same as re-registration delay).
if (Get<BackboneRouter::Leader>().HasPrimary())
{
ScheduleSend(Get<BackboneRouter::Leader>().GetConfig().SelectRandomReregistrationDelay());
}
ScheduleTimerForReregistrationDelay();
}
}
@@ -527,94 +674,58 @@ exit:
return error;
}
void Manager::HandleTimeTick(void)
void Manager::ScheduleTimerForReregistrationDelay(void)
{
if (mSendDelay > 0 && --mSendDelay == 0)
{
Send();
}
if (mReregistrationDelay > 0 && --mReregistrationDelay == 0)
{
Reregister();
}
UpdateTimeTickerRegistration();
mTimer.Start(Time::SecToMsec(Get<BackboneRouter::Leader>().GetConfig().SelectRandomReregistrationDelay()));
}
void Manager::Reregister(void)
void Manager::HandleTimer(void)
{
LogInfo("Reregister");
#if OPENTHREAD_CONFIG_MLR_ENABLE
for (Ip6::Netif::MulticastAddress &addr : Get<ThreadNetif>().GetMulticastAddresses())
switch (GetState())
{
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
// 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
// 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);
}
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();
mRenewTime =
mStartTime + Random::NonCrypto::GenerateInClosedRange((timeout / 2) + 1, timeout - kRenewGuardTimeInMsec);
}
} // namespace Mlr
+35 -29
View File
@@ -49,7 +49,6 @@
#include "common/locator.hpp"
#include "common/non_copyable.hpp"
#include "common/notifier.hpp"
#include "common/time_ticker.hpp"
#include "common/timer.hpp"
#include "net/netif.hpp"
#include "thread/child.hpp"
@@ -79,7 +78,6 @@ namespace Mlr {
class Manager : public InstanceLocator, private NonCopyable
{
friend class ot::Notifier;
friend class ot::TimeTicker;
public:
typedef otIp6RegisterMulticastListenersCallback RegisterCallback;
@@ -142,19 +140,40 @@ public:
#endif
private:
static constexpr uint32_t kLongRenewTimeout = 4 * Time::kOneHourInSec; // `MLR_TIMEOUT_LONG` (in sec)
static constexpr uint32_t kRenewGuardTime = 9; // (in sec).
// Delays (in msec) applied before registration attempts when new
// `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,
kRenew,
kStateStopped, // Manager is stopped (e.g., no PBBR).
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);
bool ShouldRegister(void) 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,
uint8_t aAddressNum,
const uint32_t *aTimeout,
@@ -162,8 +181,6 @@ private:
DeclareTmfResponseHandlerIn(Manager, HandleResponse);
uint32_t DetermineRenewDelay(void);
static Error ParseResponse(Error aResult, Coap::Msg *aMsg, uint8_t &aStatus, AddressArray &aFailedAddresses);
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
@@ -176,31 +193,20 @@ private:
#endif
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
bool IsAddressRegisteredByAnyChild(const Ip6::Address &aAddress) const
{
return IsAddressRegisteredByAnyChildExcept(aAddress, nullptr);
}
bool IsAddressRegisteredByAnyChild(const Ip6::Address &aAddress) const;
bool IsAddressRegisteredByAnyChildExcept(const Ip6::Address &aAddress, const Child *aExceptChild) const;
#endif
void ScheduleSend(uint16_t aDelay);
void UpdateTimeTickerRegistration(void);
void ScheduleNextRegistration(RegistrationRequest aRequest);
void Reregister(void);
void HandleTimeTick(void);
using DelayTimer = TimerMilliIn<Manager, &Manager::HandleTimer>;
#if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE) && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
Callback<RegisterCallback> mRegisterCallback;
bool mRegisterPending;
#endif
uint32_t mReregistrationDelay;
uint16_t mSendDelay;
bool mPending : 1;
#if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE) && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
bool mRegisterPending : 1;
#endif
State mState;
TimeMilli mStartTime;
TimeMilli mRenewTime;
DelayTimer mTimer;
};
} // 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_blocking_downgrade "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(netdata_publisher "core;nexus")
ot_nexus_test(on_mesh_prefix "core;nexus")
@@ -37,7 +37,8 @@ namespace ot {
namespace Nexus {
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;
struct Context
@@ -45,7 +46,7 @@ struct Context
Context(void) { mMlrReqCount = 0; }
uint32_t mMlrReqCount;
Ip6::Address mSharedAddress;
Ip6::Address mTargetAddress;
};
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());
}
if (addresses.Contains(context->mSharedAddress))
if (addresses.Contains(context->mTargetAddress))
{
context->mMlrReqCount++;
}
@@ -115,20 +116,25 @@ void TestMlrRedundant(void)
VerifyOrQuit(br.Get<Mle::Mle>().IsLeader());
router.Join(br, Node::kAsFtd);
nexus.AdvanceTime(kAttachToRouterTime);
nexus.AdvanceTime(kAttachAsRouterTime);
VerifyOrQuit(router.Get<Mle::Mle>().IsRouter());
sed1.Join(router, Node::kAsSed);
sed2.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(sed2.Get<Mle::Mle>().IsChild());
VerifyOrQuit(sed3.Get<Mle::Mle>().IsChild());
SuccessOrQuit(sharedAddress.FromString("ff04::1"));
context.mSharedAddress = sharedAddress;
context.mTargetAddress = sharedAddress;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Set an interceptor on leader to count incoming MLR.req messages");
@@ -164,12 +170,87 @@ void TestMlrRedundant(void)
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 ot
int main(void)
{
ot::Nexus::TestMlrRedundant();
ot::Nexus::TestMlrState();
printf("All tests passed\n");
return 0;
}