[mlr] Parent send MLR.req on behalf of Children (#5236)

This commit implements parents sending MLR.req on behave of Children.

- Parent sends MLR.req for Children after delaying for a random time <
  PARENT_AGGREGATE_DELAY (5s).
- Update test script to check for proxied MLR.req
- Parent proxy MLR.req for Children after a delay t in [0, 5]
This commit is contained in:
Simon Lin
2020-07-27 20:29:08 -07:00
committed by GitHub
parent cbf1a19483
commit 1081b45bec
15 changed files with 709 additions and 105 deletions
+1 -1
View File
@@ -227,7 +227,7 @@ void Leader::UpdateBackboneRouterPrimary(void)
Get<BackboneRouter::Local>().HandleBackboneRouterPrimaryUpdate(state, mConfig);
#endif
#if OPENTHREAD_CONFIG_MLR_ENABLE
#if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
Get<MlrManager>().HandleBackboneRouterPrimaryUpdate(state, mConfig);
#endif
}
+2
View File
@@ -86,6 +86,7 @@ public:
void Set(uint16_t aIndex)
{
OT_ASSERT(aIndex < N);
mMask[aIndex / 8] |= 0x80 >> (aIndex % 8);
}
@@ -98,6 +99,7 @@ public:
void Clear(uint16_t aIndex)
{
OT_ASSERT(aIndex < N);
mMask[aIndex / 8] &= ~(0x80 >> (aIndex % 8));
}
+1 -1
View File
@@ -750,7 +750,7 @@ template <> inline DuaManager &Instance::Get(void)
}
#endif
#if OPENTHREAD_CONFIG_MLR_ENABLE
#if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
template <> inline MlrManager &Instance::Get(void)
{
return mThreadNetif.mMlrManager;
+10
View File
@@ -172,4 +172,14 @@
#define OPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE 0
#endif
/**
*
* This setting configures the Multicast Listener Registration parent proxing in Thread 1.2.
*
* This is compulsory for 1.2 FTD.
*
*/
#define OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE \
((OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) && OPENTHREAD_FTD)
#endif // CONFIG_TMF_H_
+32
View File
@@ -2040,11 +2040,34 @@ otError MleRouter::UpdateChildAddresses(const Message &aMessage, uint16_t aOffse
uint16_t offset = 0;
uint16_t end = 0;
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
Ip6::Address oldMlrRegisteredAddresses[OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD - 1];
uint16_t oldMlrRegisteredAddressNum = 0;
#endif
VerifyOrExit(aMessage.Read(aOffset, sizeof(tlv), &tlv) == sizeof(tlv), error = OT_ERROR_PARSE);
VerifyOrExit(tlv.GetLength() <= (aMessage.GetLength() - aOffset - sizeof(tlv)), error = OT_ERROR_PARSE);
offset = aOffset + sizeof(tlv);
end = offset + tlv.GetLength();
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
// Retrieve registered multicast addresses of the Child
if (aChild.HasAnyMlrRegisteredAddress())
{
OT_ASSERT(aChild.IsStateValid());
for (const Ip6::Address &childAddress :
aChild.IterateIp6Addresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
{
if (aChild.GetAddressMlrState(childAddress) == kMlrStateRegistered)
{
oldMlrRegisteredAddresses[oldMlrRegisteredAddressNum++] = childAddress;
}
}
}
#endif
aChild.ClearIp6Addresses();
while (offset < end)
@@ -2134,6 +2157,10 @@ otError MleRouter::UpdateChildAddresses(const Message &aMessage, uint16_t aOffse
Get<AddressResolver>().Remove(address);
}
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
Get<MlrManager>().UpdateProxiedSubscriptions(aChild, oldMlrRegisteredAddresses, oldMlrRegisteredAddressNum);
#endif
if (registeredCount == 0)
{
otLogInfoMle("Child 0x%04x has no registered IPv6 address", aChild.GetRloc16());
@@ -4626,6 +4653,11 @@ void MleRouter::SetChildStateToValid(Child &aChild)
aChild.SetState(Neighbor::kStateValid);
IgnoreError(StoreChild(aChild));
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
Get<MlrManager>().UpdateProxiedSubscriptions(aChild, nullptr, 0);
#endif
Signal(OT_NEIGHBOR_TABLE_EVENT_CHILD_ADDED, aChild);
exit:
+5 -2
View File
@@ -249,7 +249,7 @@ enum
kServiceMaxId = 0x0f, ///< Maximal Service ID.
};
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE || OPENTHREAD_CONFIG_MLR_ENABLE
#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
/**
* Backbone Router / MLR constants
@@ -261,12 +261,15 @@ enum
kMlrTimeoutDefault = 3600, //< In seconds.
kMlrTimeoutMin = 300, //< In seconds.
kBackboneRouterRegistrationJitter = 5, //< In seconds.
kParentAggregateDelay = 5, //< In seconds.
};
static_assert(kMlrTimeoutDefault >= kMlrTimeoutMin,
"kMlrTimeoutDefault must be larger than or equal to kMlrTimeoutMin");
#endif // OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE || OPENTHREAD_CONFIG_MLR_ENABLE
static_assert(Mle::kParentAggregateDelay > 1, "kParentAggregateDelay should be larger than 1 second");
#endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
/**
* This type represents a MLE device mode.
+214 -52
View File
@@ -33,7 +33,7 @@
#include "mlr_manager.hpp"
#if OPENTHREAD_CONFIG_MLR_ENABLE
#if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
#include "common/code_utils.hpp"
#include "common/instance.hpp"
@@ -58,10 +58,12 @@ MlrManager::MlrManager(Instance &aInstance)
void MlrManager::HandleNotifierEvents(Events aEvents)
{
#if OPENTHREAD_CONFIG_MLR_ENABLE
if (aEvents.Contains(kEventIp6MulticastSubscribed))
{
ScheduleSend(0);
UpdateLocalSubscriptions();
}
#endif
if (aEvents.Contains(kEventThreadRoleChanged) && Get<Mle::MleRouter>().IsChild())
{
@@ -81,6 +83,130 @@ void MlrManager::HandleBackboneRouterPrimaryUpdate(BackboneRouter::Leader::State
UpdateReregistrationDelay(needRereg);
}
#if OPENTHREAD_CONFIG_MLR_ENABLE
void MlrManager::UpdateLocalSubscriptions(void)
{
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
// Check multicast addresses are newly listened against Children
for (Ip6::ExternalNetifMulticastAddress &addr :
Get<ThreadNetif>().IterateExternalMulticastAddresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
{
if (addr.GetMlrState() == kMlrStateToRegister && IsAddressMlrRegisteredByAnyChild(addr.GetAddress()))
{
addr.SetMlrState(kMlrStateRegistered);
}
}
#endif
ScheduleSend(0);
}
bool MlrManager::IsAddressMlrRegisteredByNetif(const Ip6::Address &aAddress) const
{
bool ret = false;
OT_ASSERT(aAddress.IsMulticastLargerThanRealmLocal());
for (const Ip6::ExternalNetifMulticastAddress &addr : Get<ThreadNetif>().IterateExternalMulticastAddresses())
{
if (addr.GetAddress() == aAddress && addr.GetMlrState() == kMlrStateRegistered)
{
ExitNow(ret = true);
}
}
exit:
return ret;
}
void MlrManager::SetNetifMulticastAddressMlrState(MlrState aFromState, MlrState aToState)
{
for (Ip6::ExternalNetifMulticastAddress &addr :
Get<ThreadNetif>().IterateExternalMulticastAddresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
{
if (addr.GetMlrState() == aFromState)
{
addr.SetMlrState(aToState);
}
}
}
#endif // OPENTHREAD_CONFIG_MLR_ENABLE
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
bool MlrManager::IsAddressMlrRegisteredByAnyChildExcept(const Ip6::Address &aAddress, const Child *aExceptChild) const
{
bool ret = false;
OT_ASSERT(aAddress.IsMulticastLargerThanRealmLocal());
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
{
if (&child != aExceptChild && child.HasMlrRegisteredAddress(aAddress))
{
ExitNow(ret = true);
}
}
exit:
return ret;
}
void MlrManager::UpdateProxiedSubscriptions(Child & aChild,
const Ip6::Address *aOldMlrRegisteredAddresses,
uint16_t aOldMlrRegisteredAddressNum)
{
VerifyOrExit(aChild.IsStateValid(), OT_NOOP);
// Search the new multicast addresses and set its flag accordingly
for (const Ip6::Address &address : aChild.IterateIp6Addresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
{
bool isMlrRegistered = false;
// Check if it's a new multicast address against old addresses
for (size_t i = 0; i < aOldMlrRegisteredAddressNum; i++)
{
if (aOldMlrRegisteredAddresses[i] == address)
{
isMlrRegistered = true;
break;
}
}
#if OPENTHREAD_CONFIG_MLR_ENABLE
// Check if it's a new multicast address against parent Netif
isMlrRegistered = isMlrRegistered || IsAddressMlrRegisteredByNetif(address);
#endif
// Check if it's a new multicast address against other Children
isMlrRegistered = isMlrRegistered || IsAddressMlrRegisteredByAnyChildExcept(address, &aChild);
aChild.SetAddressMlrState(address, isMlrRegistered ? kMlrStateRegistered : kMlrStateToRegister);
}
exit:
LogMulticastAddresses();
if (aChild.HasAnyMlrToRegisterAddress())
{
ScheduleSend(Random::NonCrypto::GetUint16InRange(1, Mle::kParentAggregateDelay));
}
}
void MlrManager::SetChildMulticastAddressMlrState(MlrState aFromState, MlrState aToState)
{
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
{
for (const Ip6::Address &address : child.IterateIp6Addresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
{
if (child.GetAddressMlrState(address) == aFromState)
{
child.SetAddressMlrState(address, aToState);
}
}
}
}
#endif // OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
void MlrManager::ScheduleSend(uint16_t aDelay)
{
OT_ASSERT(!mMlrPending || mSendDelay == 0);
@@ -121,19 +247,63 @@ void MlrManager::SendMulticastListenerRegistration(void)
Coap::Message * message = nullptr;
Ip6::MessageInfo messageInfo;
IPv6AddressesTlv addressesTlv;
uint8_t num;
Ip6::Address addresses[kIPv6AddressesNumMax];
uint8_t addressesNum = 0;
VerifyOrExit(!mMlrPending, error = OT_ERROR_BUSY);
VerifyOrExit(mle.IsAttached(), error = OT_ERROR_INVALID_STATE);
VerifyOrExit(mle.IsFullThreadDevice() || mle.GetParent().IsThreadVersion1p1(), error = OT_ERROR_INVALID_STATE);
VerifyOrExit(Get<BackboneRouter::Leader>().HasPrimary(), error = OT_ERROR_INVALID_STATE);
num = static_cast<uint8_t>(CountNetifMulticastAddressesToRegister());
VerifyOrExit(num > 0, error = OT_ERROR_NOT_FOUND);
if (num > kIPv6AddressesNumMax)
#if OPENTHREAD_CONFIG_MLR_ENABLE
// Append Netif multicast addresses
for (Ip6::ExternalNetifMulticastAddress &addr :
Get<ThreadNetif>().IterateExternalMulticastAddresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
{
num = kIPv6AddressesNumMax;
if (addressesNum >= kIPv6AddressesNumMax)
{
break;
}
if (addr.GetMlrState() == kMlrStateToRegister)
{
AppendToUniqueAddressList(addresses, addressesNum, addr.GetAddress());
addr.SetMlrState(kMlrStateRegistering);
}
}
#endif
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
// Append Child multicast addresses
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
{
if (addressesNum >= kIPv6AddressesNumMax)
{
break;
}
if (!child.HasAnyMlrToRegisterAddress())
{
continue;
}
for (const Ip6::Address &address : child.IterateIp6Addresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
{
if (addressesNum >= kIPv6AddressesNumMax)
{
break;
}
if (child.GetAddressMlrState(address) == kMlrStateToRegister)
{
AppendToUniqueAddressList(addresses, addressesNum, address);
child.SetAddressMlrState(address, kMlrStateRegistering);
}
}
}
#endif
VerifyOrExit(addressesNum > 0, error = OT_ERROR_NOT_FOUND);
VerifyOrExit((message = Get<Coap::Coap>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
@@ -143,23 +313,9 @@ void MlrManager::SendMulticastListenerRegistration(void)
SuccessOrExit(message->SetPayloadMarker());
addressesTlv.Init();
addressesTlv.SetLength(sizeof(Ip6::Address) * num);
addressesTlv.SetLength(sizeof(Ip6::Address) * addressesNum);
SuccessOrExit(error = message->Append(&addressesTlv, sizeof(addressesTlv)));
for (Ip6::ExternalNetifMulticastAddress &addr :
Get<ThreadNetif>().IterateExternalMulticastAddresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
{
if (addr.GetMlrState() == kMlrStateToRegister)
{
SuccessOrExit(error = message->Append(&addr.GetAddress(), sizeof(Ip6::Address)));
addr.SetMlrState(kMlrStateRegistering);
if (--num == 0)
{
break;
}
}
}
SuccessOrExit(error = message->Append(&addresses, sizeof(Ip6::Address) * addressesNum));
if (!mle.IsFullThreadDevice() && mle.GetParent().IsThreadVersion1p1())
{
@@ -196,7 +352,7 @@ exit:
message->Free();
}
SetNetifMulticastAddressMlrState(kMlrStateRegistering, kMlrStateToRegister);
SetMulticastAddressMlrState(kMlrStateRegistering, kMlrStateToRegister);
if (error == OT_ERROR_NO_BUFS)
{
@@ -225,10 +381,15 @@ void MlrManager::HandleMulticastListenerRegistrationResponse(Coap::Message *
SuccessOrExit(error = Tlv::FindUint8Tlv(*aMessage, ThreadTlv::kStatus, status));
exit:
SetMulticastAddressMlrState(kMlrStateRegistering, status == ThreadStatusTlv::MlrStatus::kMlrSuccess
? kMlrStateRegistered
: kMlrStateToRegister);
otLogInfoBbr("Receive MLR.rsp, result=%s, status=%d, error=%s", otThreadErrorToString(aResult), status,
otThreadErrorToString(error));
LogMulticastAddresses();
if (status == ThreadStatusTlv::MlrStatus::kMlrSuccess)
{
SetNetifMulticastAddressMlrState(kMlrStateRegistering, kMlrStateRegistered);
// keep sending until all multicast addresses are registered.
ScheduleSend(0);
}
@@ -237,8 +398,6 @@ exit:
otBackboneRouterConfig config;
uint16_t reregDelay;
SetNetifMulticastAddressMlrState(kMlrStateRegistering, kMlrStateToRegister);
// The Device has just attempted a Multicast Listener Registration which failed, and it retries the same
// registration with a random time delay chosen in the interval [0, Reregistration Delay].
// This is required by Thread 1.2 Specification 5.24.2.3
@@ -251,9 +410,6 @@ exit:
ScheduleSend(reregDelay);
}
}
otLogInfoMlr("Receive MLR.rsp, result=%s, status=%d, error=%s", otThreadErrorToString(aResult), status,
otThreadErrorToString(error));
}
void MlrManager::HandleTimer(void)
@@ -273,7 +429,8 @@ void MlrManager::HandleTimer(void)
void MlrManager::Reregister(void)
{
SetNetifMulticastAddressMlrState(kMlrStateRegistered, kMlrStateToRegister);
SetMulticastAddressMlrState(kMlrStateRegistered, kMlrStateToRegister);
ScheduleSend(0);
// Schedule for the next renewing.
@@ -328,45 +485,50 @@ void MlrManager::UpdateReregistrationDelay(bool aRereg)
void MlrManager::LogMulticastAddresses(void)
{
#if OPENTHREAD_CONFIG_LOG_BBR && OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_DEBG
#if OPENTHREAD_CONFIG_LOG_MLR && OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_DEBG
otLogDebgMlr("-------- Multicast Addresses --------");
for (const Ip6::ExternalNetifMulticastAddress &addr :
Get<ThreadNetif>().IterateExternalMulticastAddresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
#if OPENTHREAD_CONFIG_MLR_ENABLE
for (const Ip6::ExternalNetifMulticastAddress &addr : Get<ThreadNetif>().IterateExternalMulticastAddresses())
{
MlrState state = addr.GetMlrState();
otLogDebgMlr("%-32s%c", addr.GetAddress().ToString().AsCString(), "-rR"[state]);
otLogDebgMlr("%-32s%c", addr.GetAddress().ToString().AsCString(), "-rR"[addr.GetMlrState()]);
}
#endif
}
uint16_t MlrManager::CountNetifMulticastAddressesToRegister(void) const
{
uint16_t count = 0;
for (const Ip6::ExternalNetifMulticastAddress &addr :
Get<ThreadNetif>().IterateExternalMulticastAddresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
{
if (addr.GetMlrState() == kMlrStateToRegister)
for (const Ip6::Address &address : child.IterateIp6Addresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
{
count++;
otLogDebgMlr("%-32s%c %04x", address.ToString().AsCString(), "-rR"[child.GetAddressMlrState(address)],
child.GetRloc16());
}
}
#endif
return count;
#endif // OPENTHREAD_CONFIG_LOG_MLR && OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_DEBG
}
void MlrManager::SetNetifMulticastAddressMlrState(MlrState aFromState, MlrState aToState)
void MlrManager::AppendToUniqueAddressList(Ip6::Address (&aAddresses)[kIPv6AddressesNumMax],
uint8_t & aAddressNum,
const Ip6::Address &aAddress)
{
for (Ip6::ExternalNetifMulticastAddress &addr :
Get<ThreadNetif>().IterateExternalMulticastAddresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
for (uint8_t i = 0; i < aAddressNum; i++)
{
if (addr.GetMlrState() == aFromState)
if (aAddresses[i] == aAddress)
{
addr.SetMlrState(aToState);
ExitNow();
}
}
#endif
aAddresses[aAddressNum++] = aAddress;
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
exit:
#endif
return;
}
} // namespace ot
+47 -4
View File
@@ -36,7 +36,7 @@
#include "openthread-core-config.h"
#if OPENTHREAD_CONFIG_MLR_ENABLE
#if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
#include "backbone_router/bbr_leader.hpp"
#include "coap/coap_message.hpp"
@@ -44,6 +44,8 @@
#include "common/notifier.hpp"
#include "common/timer.hpp"
#include "net/netif.hpp"
#include "thread/thread_tlvs.hpp"
#include "thread/topology.hpp"
namespace ot {
@@ -86,6 +88,21 @@ public:
void HandleBackboneRouterPrimaryUpdate(BackboneRouter::Leader::State aState,
const BackboneRouter::BackboneRouterConfig &aConfig);
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
/**
* This method updates the Multicast Subscription Table according to the Child information.
*
* @param[in] aChild A reference to the child information.
* @param[in] aOldMlrRegisteredAddresses A pointer to an array of the Child's previously registered Ip6
* addresses.
* @param[in] aOldMlrRegisteredAddressNum The number of previously registered Ip6 addresses.
*
*/
void UpdateProxiedSubscriptions(Child & aChild,
const Ip6::Address *aOldMlrRegisteredAddresses,
uint16_t aOldMlrRegisteredAddressNum);
#endif
private:
enum
{
@@ -113,8 +130,34 @@ private:
const Ip6::MessageInfo *aMessageInfo,
otError aResult);
uint16_t CountNetifMulticastAddressesToRegister(void) const;
void SetNetifMulticastAddressMlrState(MlrState aFromState, MlrState aToState);
#if OPENTHREAD_CONFIG_MLR_ENABLE
void UpdateLocalSubscriptions(void);
void SetNetifMulticastAddressMlrState(MlrState aFromState, MlrState aToState);
bool IsAddressMlrRegisteredByNetif(const Ip6::Address &aAddress) const;
#endif
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
void SetChildMulticastAddressMlrState(MlrState aFromState, MlrState aToState);
bool IsAddressMlrRegisteredByAnyChild(const Ip6::Address &aAddress) const
{
return IsAddressMlrRegisteredByAnyChildExcept(aAddress, nullptr);
}
bool IsAddressMlrRegisteredByAnyChildExcept(const Ip6::Address &aAddress, const Child *aExceptChild) const;
#endif
void SetMulticastAddressMlrState(MlrState aFromState, MlrState aToState)
{
#if OPENTHREAD_CONFIG_MLR_ENABLE
SetNetifMulticastAddressMlrState(aFromState, aToState);
#endif
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
SetChildMulticastAddressMlrState(aFromState, aToState);
#endif
}
void AppendToUniqueAddressList(Ip6::Address (&aAddresses)[kIPv6AddressesNumMax],
uint8_t & aAddressNum,
const Ip6::Address &aAddress);
void ScheduleSend(uint16_t aDelay);
void ResetTimer(void);
@@ -134,5 +177,5 @@ private:
} // namespace ot
#endif // OPENTHREAD_CONFIG_MLR_ENABLE
#endif // OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
#endif // MLR_MANAGER_HPP_
+4 -4
View File
@@ -36,7 +36,7 @@
#include "openthread-core-config.h"
#if OPENTHREAD_CONFIG_MLR_ENABLE
#if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
namespace ot {
@@ -51,14 +51,14 @@ namespace ot {
* Multicast Listener Registration state for multicast addresses.
*
*/
typedef enum MlrState
enum MlrState
{
kMlrStateToRegister, ///< The multicast address is to be registered.
kMlrStateRegistering, ///< The multicast address is being registered.
kMlrStateRegistered, ///< The multicast address is registered.
} MlrState;
};
} // namespace ot
#endif // OPENTHREAD_CONFIG_MLR_ENABLE
#endif // OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
#endif // MLR_TYPES_HPP_
+1 -1
View File
@@ -114,7 +114,7 @@ ThreadNetif::ThreadNetif(Instance &aInstance)
#if OPENTHREAD_CONFIG_DUA_ENABLE
, mDuaManager(aInstance)
#endif
#if OPENTHREAD_CONFIG_MLR_ENABLE
#if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
, mMlrManager(aInstance)
#endif
, mChildSupervisor(aInstance)
+2 -2
View File
@@ -59,7 +59,7 @@
#include "thread/dua_manager.hpp"
#endif
#if OPENTHREAD_CONFIG_MLR_ENABLE
#if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
#include "thread/mlr_manager.hpp"
#endif
@@ -264,7 +264,7 @@ private:
#if OPENTHREAD_CONFIG_DUA_ENABLE
DuaManager mDuaManager;
#endif
#if OPENTHREAD_CONFIG_MLR_ENABLE
#if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
MlrManager mMlrManager;
#endif
Utils::ChildSupervisor mChildSupervisor;
+2 -2
View File
@@ -244,7 +244,7 @@ private:
uint8_t mTlvs[kMaxSize];
} OT_TOOL_PACKED_END;
#if OPENTHREAD_CONFIG_MLR_ENABLE
#if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
/**
* This class implements IPv6 Addresses TLV generation and parsing.
@@ -287,7 +287,7 @@ public:
}
} OT_TOOL_PACKED_END;
#endif // OPENTHREAD_CONFIG_MLR_ENABLE
#endif // OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
} // namespace ot
+64
View File
@@ -156,6 +156,10 @@ void Child::ClearIp6Addresses(void)
{
mMeshLocalIid.Clear();
memset(mIp6Address, 0, sizeof(mIp6Address));
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
mMlrToRegisterMask.ClearAll();
mMlrRegisteredMask.ClearAll();
#endif
}
otError Child::GetMeshLocalIp6Address(Ip6::Address &aAddress) const
@@ -274,6 +278,66 @@ void Child::GenerateChallenge(void)
IgnoreError(Random::Crypto::FillBuffer(mAttachChallenge, sizeof(mAttachChallenge)));
}
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
bool Child::HasMlrRegisteredAddress(const Ip6::Address &aAddress) const
{
bool has = false;
VerifyOrExit(mMlrRegisteredMask.HasAny(), OT_NOOP);
for (const Ip6::Address &address : IterateIp6Addresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
{
if (GetAddressMlrState(address) == kMlrStateRegistered && address == aAddress)
{
ExitNow(has = true);
}
}
exit:
return has;
}
MlrState Child::GetAddressMlrState(const Ip6::Address &aAddress) const
{
uint16_t addressIndex;
OT_ASSERT(&mIp6Address[0] <= &aAddress && &aAddress < OT_ARRAY_END(mIp6Address));
addressIndex = static_cast<uint16_t>(&aAddress - mIp6Address);
return mMlrToRegisterMask.Get(addressIndex)
? kMlrStateToRegister
: (mMlrRegisteredMask.Get(addressIndex) ? kMlrStateRegistered : kMlrStateRegistering);
}
void Child::SetAddressMlrState(const Ip6::Address &aAddress, MlrState aState)
{
uint16_t addressIndex;
OT_ASSERT(&mIp6Address[0] <= &aAddress && &aAddress < OT_ARRAY_END(mIp6Address));
addressIndex = static_cast<uint16_t>(&aAddress - mIp6Address);
if (aState == kMlrStateToRegister)
{
mMlrToRegisterMask.Set(addressIndex);
}
else
{
mMlrToRegisterMask.Clear(addressIndex);
}
if (aState == kMlrStateRegistered)
{
mMlrRegisteredMask.Set(addressIndex);
}
else
{
mMlrRegisteredMask.Clear(addressIndex);
}
}
#endif // OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
void Router::Clear(void)
{
Instance &instance = GetInstance();
+63 -2
View File
@@ -862,6 +862,60 @@ public:
#endif // #if OPENTHREAD_CONFIG_CHILD_SUPERVISION_ENABLE
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
/**
* This method returns MLR state of an Ip6 multicast address.
*
* @note The @p aAdddress reference MUST be from `IterateIp6Addresses()` or `AddressIterator`.
*
* @param[in] aAddress The Ip6 multicast address.
*
* @returns MLR state of the Ip6 multicast address.
*
*/
MlrState GetAddressMlrState(const Ip6::Address &aAddress) const;
/**
* This method sets MLR state of an Ip6 multicast address.
*
* @note The @p aAdddress reference MUST be from `IterateIp6Addresses()` or `AddressIterator`.
*
* @param[in] aAddress The Ip6 multicast address.
* @param[in] aState The target MLR state.
*
*/
void SetAddressMlrState(const Ip6::Address &aAddress, MlrState aState);
/**
* This method returns if the Child has Ip6 address @p aAddress of MLR state `kMlrStateRegistered`.
*
* @param[in] aAddress The Ip6 address.
*
* @retval true If the Child has Ip6 address @p aAddress of MLR state `kMlrStateRegistered`.
* @retval false If the Child does not have Ip6 address @p aAddress of MLR state `kMlrStateRegistered`.
*
*/
bool HasMlrRegisteredAddress(const Ip6::Address &aAddress) const;
/**
* This method returns if the Child has any Ip6 address of MLR state `kMlrStateRegistered`.
*
* @retval true If the Child has any Ip6 address of MLR state `kMlrStateRegistered`.
* @retval false If the Child does not have any Ip6 address of MLR state `kMlrStateRegistered`.
*
*/
bool HasAnyMlrRegisteredAddress(void) const { return mMlrRegisteredMask.HasAny(); }
/**
* This method returns if the Child has any Ip6 address of MLR state `kMlrStateToRegister`.
*
* @retval true If the Child has any Ip6 address of MLR state `kMlrStateToRegister`.
* @retval false If the Child does not have any Ip6 address of MLR state `kMlrStateToRegister`.
*
*/
bool HasAnyMlrToRegisterAddress(void) const { return mMlrToRegisterMask.HasAny(); }
#endif // OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
private:
#if OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD < 2
#error OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD should be at least set to 2.
@@ -872,6 +926,8 @@ private:
kNumIp6Addresses = OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD - 1,
};
typedef BitVector<kNumIp6Addresses> ChildIp6AddressMask;
class AddressIteratorBuilder
{
public:
@@ -889,11 +945,16 @@ private:
Ip6::Address::TypeFilter mFilter;
};
uint8_t mNetworkDataVersion; ///< Current Network Data version
Ip6::InterfaceIdentifier mMeshLocalIid; ///< IPv6 address IID for mesh-local address
Ip6::Address mIp6Address[kNumIp6Addresses]; ///< Registered IPv6 addresses
uint32_t mTimeout; ///< Child timeout
uint32_t mTimeout; ///< Child timeout
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
ChildIp6AddressMask mMlrToRegisterMask;
ChildIp6AddressMask mMlrRegisteredMask;
#endif
uint8_t mNetworkDataVersion; ///< Current Network Data version
union
{
@@ -29,12 +29,13 @@
import ipaddress
import unittest
from typing import Union, List
import config
import network_layer
import thread_cert
_, BBR_1, BBR_2, ROUTER_1_2, ROUTER_1_1, SED_1, MED_1, FED_1 = range(8)
_, BBR_1, BBR_2, ROUTER_1_2, ROUTER_1_1, SED_1, MED_1, MED_2, FED_1 = range(9)
WAIT_ATTACH = 5
WAIT_REDUNDANCE = 3
@@ -44,6 +45,7 @@ SED_POLL_PERIOD = 1000 # 1s
REREG_DELAY = 10
MLR_TIMEOUT = 300
PARENT_AGGREGATE_DELAY = 5
MA1 = 'ff04::1234:777a:1'
MA1g = 'ff0e::1234:777a:1'
@@ -60,9 +62,9 @@ MA6 = 'ff02::10'
| / \ |
| / \ |
ROUTER_1_2 ROUTER_1_1
| | \ |
| | \ |
SED_1 MED_1 FED_1
| | \____ |
| | \ |
SED_1 MED_1/2 FED_1
"""
@@ -82,7 +84,7 @@ class TestMulticastListenerRegistration(thread_cert.TestCase):
},
ROUTER_1_2: {
'version': '1.2',
'whitelist': [BBR_1, BBR_2, SED_1, MED_1, FED_1],
'whitelist': [BBR_1, BBR_2, SED_1, MED_1, MED_2, FED_1],
'router_selection_jitter': 1,
},
ROUTER_1_1: {
@@ -96,6 +98,12 @@ class TestMulticastListenerRegistration(thread_cert.TestCase):
'whitelist': [ROUTER_1_2],
'timeout': config.DEFAULT_CHILD_TIMEOUT,
},
MED_2: {
'mode': 'rsn',
'version': '1.2',
'whitelist': [ROUTER_1_2],
'timeout': config.DEFAULT_CHILD_TIMEOUT,
},
SED_1: {
'mode': 'sn',
'version': '1.2',
@@ -112,7 +120,7 @@ class TestMulticastListenerRegistration(thread_cert.TestCase):
}
"""All nodes are created with default configurations"""
def test(self):
def _bootstrap(self):
# starting context id
context_id = 1
@@ -179,12 +187,20 @@ class TestMulticastListenerRegistration(thread_cert.TestCase):
self.simulator.go(WAIT_ATTACH)
self.assertEqual(self.nodes[MED_1].get_state(), 'child')
# Bring up MED_2
self.nodes[MED_2].start()
self.simulator.go(WAIT_ATTACH)
self.assertEqual(self.nodes[MED_2].get_state(), 'child')
# Bring up SED_1
self.nodes[SED_1].set_pollperiod(SED_POLL_PERIOD)
self.nodes[SED_1].start()
self.simulator.go(WAIT_ATTACH)
self.assertEqual(self.nodes[SED_1].get_state(), 'child')
def test(self):
self._bootstrap()
# Verify MLR.req for each device when parent is 1.2
self.__check_mlr_ok(ROUTER_1_2, is_ftd=True)
self.__check_mlr_ok(FED_1, is_ftd=True)
@@ -202,6 +218,18 @@ class TestMulticastListenerRegistration(thread_cert.TestCase):
# Switch to parent 1.2
self.__switch_to_1_2_parent()
def testParentMergeMedMlrReq(self):
self._bootstrap()
# Make sure Parent registers multiple MAs of MED Children in one MLR.req
self.__check_parent_merge_med_mlr_req([MED_1, MED_2], ROUTER_1_2)
def testNotSendMlrReqIfSubscribed(self):
self._bootstrap()
# Make sure Parent does not send MLR.req of Child if it's already subscribed by Netif or other Children
self.__check_not_send_mlr_req_if_subscribed([MED_1, MED_2], ROUTER_1_2)
def __check_mlr_ok(self, id, is_ftd, is_parent_1p1=False):
"""Check if MLR works for the node"""
# Add MA1 and send MLR.req
@@ -210,36 +238,94 @@ class TestMulticastListenerRegistration(thread_cert.TestCase):
(id, 'FTD' if is_ftd else 'MTD', '1.1' if is_parent_1p1 else '1.2'))
expect_mlr_req = is_ftd or is_parent_1p1
if id == ROUTER_1_2:
parent_id = None
else:
parent_id = ROUTER_1_1 if is_parent_1p1 else ROUTER_1_2
for addr in [MA1, MA1g, MA2, MA3, MA4]:
self.__check_ipmaddr_add(id, addr, expect_mlr_req=expect_mlr_req)
self.__check_ipmaddr_add(
id,
parent_id,
addr,
expect_mlr_req=expect_mlr_req,
expect_mlr_req_proxied=(not expect_mlr_req))
for addr in [MA5, MA6]:
self.__check_ipmaddr_add(id, addr, expect_mlr_req=False)
self.__check_ipmaddr_add(id,
parent_id,
addr,
expect_mlr_req=False,
expect_mlr_req_proxied=False)
print('=' * 120)
def __check_ipmaddr_add(self, id, addr, expect_mlr_req=True):
def __check_ipmaddr_add(self,
id,
parent_id,
addr,
expect_mlr_req=True,
expect_mlr_req_proxied=False):
"""Check MLR works for the added multicast address"""
print("Node %d: ipmaddr %s" % (id, addr))
self.flush_all()
self.nodes[id].add_ipmaddr(addr)
self.assertTrue(self.nodes[id].has_ipmaddr(addr))
self.simulator.go(3)
self.simulator.go(PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE)
reg_mas = self.__get_registered_MAs(id)
if expect_mlr_req:
assert ipaddress.IPv6Address(addr) in reg_mas, (addr, reg_mas)
self.__check_send_mlr_req(id,
addr,
should_send=expect_mlr_req,
expect_mlr_rsp=expect_mlr_req)
# Parent should either forward or proxy the MLR.req
if parent_id:
self.__check_send_mlr_req(parent_id,
addr,
should_send=expect_mlr_req or
expect_mlr_req_proxied,
expect_mlr_rsp=expect_mlr_req_proxied)
messages = self.simulator.get_messages_sent_by(BBR_1)
messages.next_coap_message('2.04')
self.__check_rereg(id, addr)
self.__check_renewing(id, addr)
else:
assert ipaddress.IPv6Address(addr) not in reg_mas, (addr, reg_mas)
self.__check_rereg(id,
parent_id,
addr,
expect_mlr_req=expect_mlr_req,
expect_mlr_req_proxied=expect_mlr_req_proxied)
self.__check_renewing(id,
parent_id,
addr,
expect_mlr_req=expect_mlr_req,
expect_mlr_req_proxied=expect_mlr_req_proxied)
self.nodes[id].del_ipmaddr(addr)
self.simulator.go(1)
def __get_registered_MAs(self, id):
def __check_send_mlr_req(self,
id,
addrs: Union[List[str], str],
should_send=True,
expect_mlr_rsp=False,
expect_mlr_req_num=None,
expect_unique_reg=False):
if isinstance(addrs, str):
addrs = [addrs]
reg_mas = self.__get_registered_MAs(
id, expect_mlr_req_num=expect_mlr_req_num)
if should_send:
for addr in addrs:
self.assertIn(ipaddress.IPv6Address(addr), reg_mas)
if expect_unique_reg:
self.assertEqual(1,
reg_mas.count(ipaddress.IPv6Address(addr)))
# BBR should send MLR.rsp ACK
if expect_mlr_rsp:
messages = self.simulator.get_messages_sent_by(BBR_1)
messages.next_coap_message('2.04')
else:
for addr in addrs:
self.assertNotIn(ipaddress.IPv6Address(addr), reg_mas)
def __get_registered_MAs(self, id, expect_mlr_req_num=None):
"""Get MAs registered via MLR.req by the node"""
messages = self.simulator.get_messages_sent_by(id)
reg_mas = []
@@ -250,25 +336,65 @@ class TestMulticastListenerRegistration(thread_cert.TestCase):
if not msg:
break
addrs = msg.get_coap_message_tlv(network_layer.IPv6Addresses)
reg_mas.extend(addrs)
reg_mas.append(addrs)
print('Node %d registered MAs: %s' % (id, reg_mas))
if expect_mlr_req_num is not None:
self.assertEqual(len(reg_mas), expect_mlr_req_num)
# expand from [[...], [...], ...] to [...]
reg_mas = [ma for mas in reg_mas for ma in mas]
return reg_mas
def __check_renewing(self, id, addr):
def __check_renewing(self,
id,
parent_id,
addr,
expect_mlr_req=True,
expect_mlr_req_proxied=False):
"""Check if MLR works that a node can renew it's registered MAs"""
self.flush_all()
self.simulator.go(MLR_TIMEOUT + WAIT_REDUNDANCE)
reg_mas = self.__get_registered_MAs(id)
assert ipaddress.IPv6Address(addr) in reg_mas, (addr, reg_mas)
self.__check_send_mlr_req(id,
addr,
should_send=expect_mlr_req,
expect_mlr_rsp=expect_mlr_req)
# Parent should either forward or proxy the MLR.req
if parent_id:
self.__check_send_mlr_req(parent_id,
addr,
should_send=expect_mlr_req or
expect_mlr_req_proxied,
expect_mlr_rsp=expect_mlr_req_proxied)
def __check_rereg(self, id, addr):
def __check_rereg(self,
id,
parent_id,
addr,
expect_mlr_req=True,
expect_mlr_req_proxied=False):
"""Check if MLR works that a node can do MLR reregistration when necessary"""
self.__check_rereg_seqno(id, addr)
self.__check_rereg_pbbr_change(id, addr)
self.__check_rereg_seqno(id,
parent_id,
addr,
expect_mlr_req=expect_mlr_req,
expect_mlr_req_proxied=expect_mlr_req_proxied)
self.__check_rereg_pbbr_change(
id,
parent_id,
addr,
expect_mlr_req=expect_mlr_req,
expect_mlr_req_proxied=expect_mlr_req_proxied)
def __check_rereg_seqno(self, id, addr):
def __check_rereg_seqno(self,
id,
parent_id,
addr,
expect_mlr_req=True,
expect_mlr_req_proxied=False):
"""Check if MLR works that a node can do MLR reregistration when PBBR seqno changes"""
# Change seq on PBBR and expect MLR.req within REREG_DELAY
self.flush_all()
@@ -276,10 +402,24 @@ class TestMulticastListenerRegistration(thread_cert.TestCase):
self.nodes[BBR_1].set_backbone_router(seqno=self.pbbr_seq)
self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE)
reg_mas = self.__get_registered_MAs(id)
assert ipaddress.IPv6Address(addr) in reg_mas, (addr, reg_mas)
self.__check_send_mlr_req(id,
addr,
should_send=expect_mlr_req,
expect_mlr_rsp=expect_mlr_req)
# Parent should either forward or proxy the MLR.req
if parent_id:
self.__check_send_mlr_req(parent_id,
addr,
should_send=expect_mlr_req or
expect_mlr_req_proxied,
expect_mlr_rsp=expect_mlr_req_proxied)
def __check_rereg_pbbr_change(self, id, addr):
def __check_rereg_pbbr_change(self,
id,
parent_id,
addr,
expect_mlr_req=True,
expect_mlr_req_proxied=False):
"""Check if MLR works that a node can do MLR reregistration when PBBR changes"""
# Make BBR_2 to be Primary and expect MLR.req within REREG_DELAY
self.flush_all()
@@ -288,8 +428,18 @@ class TestMulticastListenerRegistration(thread_cert.TestCase):
self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(),
'Primary')
self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE)
reg_mas = self.__get_registered_MAs(id)
assert ipaddress.IPv6Address(addr) in reg_mas, (addr, reg_mas)
self.__check_send_mlr_req(id,
addr,
should_send=expect_mlr_req,
expect_mlr_rsp=expect_mlr_req)
# Parent should either forward or proxy the MLR.req
if parent_id:
self.__check_send_mlr_req(parent_id,
addr,
should_send=expect_mlr_req or
expect_mlr_req_proxied,
expect_mlr_rsp=expect_mlr_req_proxied)
# Restore BBR_1 to be Primary and BBR_2 to be Secondary
self.nodes[BBR_2].disable_backbone_router()
@@ -395,6 +545,83 @@ class TestMulticastListenerRegistration(thread_cert.TestCase):
self.simulator.go(WAIT_REDUNDANCE)
def __check_parent_merge_med_mlr_req(self, meds, parent_id):
"""Check that the 1.2 parent merge multiple multicast addresses for MED children."""
self.flush_all()
for med in meds:
self.nodes[med].add_ipmaddr(MA1)
self.nodes[meds[0]].add_ipmaddr(MA2)
self.nodes[meds[1]].add_ipmaddr(MA3)
self.simulator.go(PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE)
self.__check_send_mlr_req(parent_id, [MA1, MA2, MA3],
should_send=True,
expect_mlr_rsp=True,
expect_mlr_req_num=1,
expect_unique_reg=True)
# restore
self.nodes[meds[0]].del_ipmaddr(MA2)
self.nodes[meds[1]].del_ipmaddr(MA3)
for med in meds:
self.nodes[med].del_ipmaddr(MA1)
self.simulator.go(WAIT_REDUNDANCE)
def __check_not_send_mlr_req_if_subscribed(self, meds, parent_id):
"""Check that the 1.2 parent does not send MLR.req if the MA is already subscribed."""
# Parent should register MA1 on Netif
self.flush_all()
self.nodes[parent_id].add_ipmaddr(MA1)
self.simulator.go(WAIT_REDUNDANCE)
self.__check_send_mlr_req(parent_id,
MA1,
should_send=True,
expect_mlr_rsp=True)
# Parent should not register MA1 of Child 1 because it's already registerd
self.flush_all()
self.nodes[meds[0]].add_ipmaddr(MA1)
self.simulator.go(PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE)
self.__check_send_mlr_req(parent_id, MA1, should_send=False)
# Parent should register MA2 of Child 1 because it's new
self.flush_all()
self.nodes[meds[0]].add_ipmaddr(MA2)
self.simulator.go(PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE)
self.__check_send_mlr_req(parent_id, MA2, should_send=True)
# Parent should not register MA2 of Child 2 because it's already registered for Child 1
self.flush_all()
self.nodes[meds[1]].add_ipmaddr(MA2)
self.simulator.go(PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE)
self.__check_send_mlr_req(parent_id, MA2, should_send=False)
# Parent should register MA3 of Child 2 because it's new
self.flush_all()
self.nodes[meds[1]].add_ipmaddr(MA3)
self.simulator.go(PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE)
self.__check_send_mlr_req(parent_id, MA3, should_send=True)
# Parent should not register MA2 and MA3 because they are already registered for Child2 itself
self.flush_all()
self.nodes[meds[1]].del_ipmaddr(MA2)
self.nodes[meds[1]].del_ipmaddr(MA3)
self.nodes[meds[1]].add_ipmaddr(MA2)
self.nodes[meds[1]].add_ipmaddr(MA3)
self.simulator.go(PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE)
self.__check_send_mlr_req(parent_id, [MA2, MA3], should_send=False)
# Restore
self.nodes[parent_id].del_ipmaddr(MA1)
self.nodes[meds[0]].del_ipmaddr(MA1)
self.nodes[meds[0]].del_ipmaddr(MA2)
self.nodes[meds[1]].del_ipmaddr(MA2)
self.nodes[meds[1]].del_ipmaddr(MA3)
self.simulator.go(WAIT_REDUNDANCE)
if __name__ == '__main__':
unittest.main()