mirror of
https://github.com/espressif/openthread.git
synced 2026-08-04 01:47:47 +00:00
[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:
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user