diff --git a/src/core/common/time_ticker.cpp b/src/core/common/time_ticker.cpp index dabe21afe..0723a931b 100644 --- a/src/core/common/time_ticker.cpp +++ b/src/core/common/time_ticker.cpp @@ -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().HandleTimeTick(); - } -#endif - if (mReceivers & Mask(kIp6Mpl)) { Get().HandleTimeTick(); diff --git a/src/core/common/time_ticker.hpp b/src/core/common/time_ticker.hpp index 5b70fc57d..5f3c8090a 100644 --- a/src/core/common/time_ticker.hpp +++ b/src/core/common/time_ticker.hpp @@ -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` diff --git a/src/core/thread/mlr_manager.cpp b/src/core/thread/mlr_manager.cpp index fe3a773ca..d850887a0 100644 --- a/src/core/thread/mlr_manager.cpp +++ b/src/core/thread/mlr_manager.cpp @@ -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().AbortTransaction(HandleResponse, this)); + } + + if (mState == kStateToRegisterAll) + { + // Clear "MlrRegistered" state on all addresses + +#if OPENTHREAD_CONFIG_MLR_ENABLE + for (Ip6::Netif::MulticastAddress &addr : Get().GetMulticastAddresses()) + { + if (addr.IsMlrCandidate()) + { + addr.SetMlrRegistered(false); + } + } +#endif +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE + for (Child &child : Get().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().IsAttached() && Get().HasPrimary()) + { + canRun = Get().IsFullThreadDevice() || Get().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().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().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(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().UnregisterReceiver(TimeTicker::kMlrManager); - } - else - { - Get().RegisterReceiver(TimeTicker::kMlrManager); - } -} - -bool Manager::ShouldRegister(void) const -{ - bool shouldRegister = false; - - VerifyOrExit(Get().IsFullThreadDevice() || Get().GetParent().IsThreadVersion1p1()); - VerifyOrExit(Get().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().IsAttached(), error = kErrorInvalidState); - VerifyOrExit(ShouldRegister(), error = kErrorInvalidState); + IgnoreError(Get().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 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().HasPrimary()) - { - ScheduleSend(Get().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().GetConfig().SelectRandomReregistrationDelay())); } -void Manager::Reregister(void) +void Manager::HandleTimer(void) { - LogInfo("Reregister"); - -#if OPENTHREAD_CONFIG_MLR_ENABLE - for (Ip6::Netif::MulticastAddress &addr : Get().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().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().GetConfig().GetMlrTimeout(); + uint32_t timeout; - timeout = Clamp(timeout, BackboneRouter::kMinMlrTimeout, kLongRenewTimeout); + timeout = Get().GetConfig().GetMlrTimeout(); + timeout = Time::SecToMsec(Clamp(timeout, BackboneRouter::kMinMlrTimeout, kLongRenewTimeout)); - return Random::NonCrypto::GenerateInClosedRange((timeout / 2) + 1, timeout - kRenewGuardTime); -} - -void Manager::ScheduleNextRegistration(RegistrationRequest aRequest) -{ - uint32_t delay; - - if (!ShouldRegister()) - { - mReregistrationDelay = 0; - ExitNow(); - } - - switch (aRequest) - { - case kReregister: - delay = Get().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 diff --git a/src/core/thread/mlr_manager.hpp b/src/core/thread/mlr_manager.hpp index ae208c0c1..63eb90b9c 100644 --- a/src/core/thread/mlr_manager.hpp +++ b/src/core/thread/mlr_manager.hpp @@ -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; #if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE) && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE Callback 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 diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 4cd29fad8..b47dd2d4b 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -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") diff --git a/tests/nexus/test_mlr_redundant.cpp b/tests/nexus/test_mlr_manager.cpp similarity index 64% rename from tests/nexus/test_mlr_redundant.cpp rename to tests/nexus/test_mlr_manager.cpp index d1bd4ad9b..a649bb1de 100644 --- a/tests/nexus/test_mlr_redundant.cpp +++ b/tests/nexus/test_mlr_manager.cpp @@ -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().IsLeader()); router.Join(br, Node::kAsFtd); - nexus.AdvanceTime(kAttachToRouterTime); + nexus.AdvanceTime(kAttachAsRouterTime); VerifyOrQuit(router.Get().IsRouter()); sed1.Join(router, Node::kAsSed); sed2.Join(router, Node::kAsSed); sed3.Join(router, Node::kAsSed); - nexus.AdvanceTime(kAttachToRouterTime); + + SuccessOrQuit(sed1.Get().SetExternalPollPeriod(5 * Time::kOneSecondInMsec)); + SuccessOrQuit(sed2.Get().SetExternalPollPeriod(5 * Time::kOneSecondInMsec)); + SuccessOrQuit(sed3.Get().SetExternalPollPeriod(5 * Time::kOneSecondInMsec)); + + nexus.AdvanceTime(kAttachAsSedTime); VerifyOrQuit(sed1.Get().IsChild()); VerifyOrQuit(sed2.Get().IsChild()); VerifyOrQuit(sed3.Get().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().SetInterceptor(BrCoapInterceptor, &context); + + Log("Enable BBR and verify initial registration"); + br.Get().SetEnabled(true); + SuccessOrQuit(router.Get().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().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().GetConfig(); + + Log("Initial MLR timeout: %lu", ToUlong(br.Get().GetConfig().GetMlrTimeout())); + + // We set a short MLR timeout (300 seconds is the minimum). + config.mReregistrationDelay = 10; + config.mMlrTimeout = 300; + SuccessOrQuit(br.Get().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; }