From 00f3043a066c28215a65bee0c427b6f36f171fe0 Mon Sep 17 00:00:00 2001 From: Simon Lin Date: Wed, 22 Jul 2020 23:38:05 +0800 Subject: [PATCH] [mlr] send MLR.req for local multicast addresses (#5248) This commit includes: - Adding compile configurations necessary for MLR features - Adding source/header files necessary for MLR features - Send MLR.req for local MAs on ThreadNetif - At most one MLR.req is outstanding - Reregistration according to PBBR configuration - Renewing periods according to PBBR configuration - BBRs receive MLR.req with minimal processing and send MLR.rsp - Add a test to verify that MLR.req is sent for local MAs. --- Android.mk | 2 + BUILD.gn | 2 + etc/cmake/options.cmake | 5 + examples/common-switches.mk | 5 + script/test | 2 + src/core/CMakeLists.txt | 2 + src/core/Makefile.am | 5 + src/core/api/border_router_api.cpp | 1 - src/core/backbone_router/bbr_leader.cpp | 4 + src/core/backbone_router/bbr_manager.cpp | 125 ++++++ src/core/backbone_router/bbr_manager.hpp | 99 +++++ src/core/common/instance.hpp | 12 + .../config/openthread-core-config-check.h | 4 + .../config/openthread-core-default-config.h | 10 + src/core/net/netif.cpp | 9 +- src/core/net/netif.hpp | 191 ++++++++- src/core/thread/mle_types.hpp | 6 +- src/core/thread/mlr_manager.cpp | 373 ++++++++++++++++ src/core/thread/mlr_manager.hpp | 138 ++++++ src/core/thread/mlr_types.hpp | 64 +++ src/core/thread/thread_netif.cpp | 4 + src/core/thread/thread_netif.hpp | 11 +- src/core/thread/thread_tlvs.hpp | 64 +++ src/core/thread/thread_uri_paths.hpp | 8 + tests/scripts/thread-cert/config.py | 3 + tests/scripts/thread-cert/network_layer.py | 20 +- tests/scripts/thread-cert/node.py | 5 + tests/scripts/thread-cert/simulator.py | 2 +- ..._2_test_multicast_listener_registration.py | 400 ++++++++++++++++++ 29 files changed, 1564 insertions(+), 12 deletions(-) create mode 100644 src/core/backbone_router/bbr_manager.cpp create mode 100644 src/core/backbone_router/bbr_manager.hpp create mode 100644 src/core/thread/mlr_manager.cpp create mode 100644 src/core/thread/mlr_manager.hpp create mode 100644 src/core/thread/mlr_types.hpp create mode 100755 tests/scripts/thread-cert/v1_2_test_multicast_listener_registration.py diff --git a/Android.mk b/Android.mk index 234dd5abf..259e3b075 100644 --- a/Android.mk +++ b/Android.mk @@ -176,6 +176,7 @@ LOCAL_SRC_FILES := \ src/core/api/udp_api.cpp \ src/core/backbone_router/bbr_leader.cpp \ src/core/backbone_router/bbr_local.cpp \ + src/core/backbone_router/bbr_manager.cpp \ src/core/coap/coap.cpp \ src/core/coap/coap_message.cpp \ src/core/coap/coap_secure.cpp \ @@ -254,6 +255,7 @@ LOCAL_SRC_FILES := \ src/core/thread/mle.cpp \ src/core/thread/mle_router.cpp \ src/core/thread/mle_types.cpp \ + src/core/thread/mlr_manager.cpp \ src/core/thread/network_data.cpp \ src/core/thread/network_data_leader.cpp \ src/core/thread/network_data_leader_ftd.cpp \ diff --git a/BUILD.gn b/BUILD.gn index df3b616f5..7fa3f39c8 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -68,6 +68,7 @@ static_library("lib-ot-core") { "src/core/api/udp_api.cpp", "src/core/backbone_router/bbr_leader.cpp", "src/core/backbone_router/bbr_local.cpp", + "src/core/backbone_router/bbr_manager.cpp", "src/core/coap/coap.cpp", "src/core/coap/coap_message.cpp", "src/core/coap/coap_secure.cpp", @@ -149,6 +150,7 @@ static_library("lib-ot-core") { "src/core/thread/mle.cpp", "src/core/thread/mle_router.cpp", "src/core/thread/mle_types.cpp", + "src/core/thread/mlr_manager.cpp", "src/core/thread/network_data.cpp", "src/core/thread/network_data_leader.cpp", "src/core/thread/network_data_leader_ftd.cpp", diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 0cadec375..157590bba 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -130,6 +130,11 @@ if(OT_DUA) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_DUA_ENABLE=1") endif() +option(OT_MLR "enable Multicast Listener Registration feature for Thread 1.2") +if(OT_MLR) + target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_MLR_ENABLE=1") +endif() + option(OT_EXTERNAL_HEAP "enable external heap support") if(OT_EXTERNAL_HEAP) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE=1") diff --git a/examples/common-switches.mk b/examples/common-switches.mk index cf0e7bb13..ad55fb470 100644 --- a/examples/common-switches.mk +++ b/examples/common-switches.mk @@ -61,6 +61,7 @@ endif LINK_RAW ?= 0 MAC_FILTER ?= 0 MLE_LONG_ROUTES ?= 0 +MLR ?= 0 MTD_NETDIAG ?= 0 MULTIPLE_INSTANCE ?= 0 OTNS ?= 0 @@ -205,6 +206,10 @@ ifeq ($(MLE_LONG_ROUTES),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_MLE_LONG_ROUTES_ENABLE=1 endif +ifeq ($(MLR),1) +COMMONCFLAGS += -DOPENTHREAD_CONFIG_MLR_ENABLE=1 +endif + ifeq ($(MTD_NETDIAG),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE=1 endif diff --git a/script/test b/script/test index a3bad2dc8..fd541eb62 100755 --- a/script/test +++ b/script/test @@ -52,6 +52,7 @@ build_simulation() if [[ ${version} == "1.2" ]]; then options+=("-DOT_DUA=ON") + options+=("-DOT_MLR=ON") fi if [[ ${VIRTUAL_TIME} == 1 ]]; then @@ -89,6 +90,7 @@ build_posix() if [[ ${version} == "1.2" ]]; then options+=("-DOT_DUA=ON") + options+=("-DOT_MLR=ON") fi if [[ ${VIRTUAL_TIME} == 1 ]]; then diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a860e1e49..6cb90642b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -104,6 +104,7 @@ set(COMMON_SOURCES api/udp_api.cpp backbone_router/bbr_leader.cpp backbone_router/bbr_local.cpp + backbone_router/bbr_manager.cpp coap/coap.cpp coap/coap_message.cpp coap/coap_secure.cpp @@ -185,6 +186,7 @@ set(COMMON_SOURCES thread/mle.cpp thread/mle_router.cpp thread/mle_types.cpp + thread/mlr_manager.cpp thread/network_data.cpp thread/network_data_leader.cpp thread/network_data_leader_ftd.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index aefdccd7f..86176c90b 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -146,6 +146,7 @@ SOURCES_COMMON = \ api/udp_api.cpp \ backbone_router/bbr_leader.cpp \ backbone_router/bbr_local.cpp \ + backbone_router/bbr_manager.cpp \ coap/coap.cpp \ coap/coap_message.cpp \ coap/coap_secure.cpp \ @@ -227,6 +228,7 @@ SOURCES_COMMON = \ thread/mle.cpp \ thread/mle_router.cpp \ thread/mle_types.cpp \ + thread/mlr_manager.cpp \ thread/network_data.cpp \ thread/network_data_leader.cpp \ thread/network_data_leader_ftd.cpp \ @@ -317,6 +319,7 @@ HEADERS_COMMON = \ openthread-core-config.h \ backbone_router/bbr_leader.hpp \ backbone_router/bbr_local.hpp \ + backbone_router/bbr_manager.hpp \ coap/coap.hpp \ coap/coap_message.hpp \ coap/coap_secure.hpp \ @@ -439,6 +442,8 @@ HEADERS_COMMON = \ thread/mle_router.hpp \ thread/mle_tlvs.hpp \ thread/mle_types.hpp \ + thread/mlr_manager.hpp \ + thread/mlr_types.hpp \ thread/network_data.hpp \ thread/network_data_leader.hpp \ thread/network_data_leader_ftd.hpp \ diff --git a/src/core/api/border_router_api.cpp b/src/core/api/border_router_api.cpp index 1a2048dac..ea52592cc 100644 --- a/src/core/api/border_router_api.cpp +++ b/src/core/api/border_router_api.cpp @@ -39,7 +39,6 @@ #include "common/debug.hpp" #include "common/instance.hpp" -#include "common/locator-getters.hpp" using namespace ot; diff --git a/src/core/backbone_router/bbr_leader.cpp b/src/core/backbone_router/bbr_leader.cpp index ee09bf138..096fdbf7a 100644 --- a/src/core/backbone_router/bbr_leader.cpp +++ b/src/core/backbone_router/bbr_leader.cpp @@ -226,6 +226,10 @@ void Leader::UpdateBackboneRouterPrimary(void) #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE Get().HandleBackboneRouterPrimaryUpdate(state, mConfig); #endif + +#if OPENTHREAD_CONFIG_MLR_ENABLE + Get().HandleBackboneRouterPrimaryUpdate(state, mConfig); +#endif } void Leader::UpdateDomainPrefixConfig(void) diff --git a/src/core/backbone_router/bbr_manager.cpp b/src/core/backbone_router/bbr_manager.cpp new file mode 100644 index 000000000..7f3016fb0 --- /dev/null +++ b/src/core/backbone_router/bbr_manager.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements Backbone Router management. + */ + +#include "bbr_manager.hpp" + +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + +#include "common/code_utils.hpp" +#include "common/instance.hpp" +#include "common/locator-getters.hpp" +#include "common/logging.hpp" +#include "common/random.hpp" +#include "thread/mle_types.hpp" +#include "thread/thread_netif.hpp" +#include "thread/thread_uri_paths.hpp" + +namespace ot { + +namespace BackboneRouter { + +Manager::Manager(Instance &aInstance) + : InstanceLocator(aInstance) + , Notifier::Receiver(aInstance, Manager::HandleNotifierEvents) + , mMulticastListenerRegistration(OT_URI_PATH_MLR, Manager::HandleMulticastListenerRegistration, this) +{ +} + +void Manager::HandleNotifierEvents(Events aEvents) +{ + if (aEvents.Contains(kEventThreadBackboneRouterStateChanged)) + { + if (Get().GetState() == OT_BACKBONE_ROUTER_STATE_DISABLED) + { + Get().RemoveResource(mMulticastListenerRegistration); + } + else + { + Get().AddResource(mMulticastListenerRegistration); + } + } +} + +void Manager::HandleMulticastListenerRegistration(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + otError error = OT_ERROR_NONE; + bool isPrimary = Get().IsPrimary(); + ThreadStatusTlv::MlrStatus status = ThreadStatusTlv::kMlrSuccess; + + VerifyOrExit(aMessage.IsConfirmable() && aMessage.GetCode() == OT_COAP_CODE_POST, error = OT_ERROR_PARSE); + + VerifyOrExit(isPrimary, status = ThreadStatusTlv::kMlrBbrNotPrimary); + + // TODO: (MLR) send configured MLR response for Reference Device + // TODO: (MLR) handle Commissioner Session TLV + // TODO: (MLR) handle Timeout TLV + // TODO: (MLR) handle addresses, updated listener groups, send BMLR.req + +exit: + if (error == OT_ERROR_NONE) + { + SendMulticastListenerRegistrationResponse(aMessage, aMessageInfo, status); + } +} + +void Manager::SendMulticastListenerRegistrationResponse(const Coap::Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + ThreadStatusTlv::MlrStatus aStatus) +{ + otError error = OT_ERROR_NONE; + Coap::Message *message = nullptr; + + VerifyOrExit((message = Get().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS); + + SuccessOrExit(message->SetDefaultResponseHeader(aMessage)); + SuccessOrExit(message->SetPayloadMarker()); + + SuccessOrExit(Tlv::AppendUint8Tlv(*message, ThreadTlv::kStatus, aStatus)); + + // TODO: (MLR) append the failed addresses + SuccessOrExit(error = Get().SendMessage(*message, aMessageInfo)); + +exit: + if (error != OT_ERROR_NONE && message != nullptr) + { + message->Free(); + } + + otLogInfoBbr("Sent MLR.rsp (status=%d): %s", aStatus, otThreadErrorToString(error)); +} + +} // namespace BackboneRouter + +} // namespace ot + +#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE diff --git a/src/core/backbone_router/bbr_manager.hpp b/src/core/backbone_router/bbr_manager.hpp new file mode 100644 index 000000000..1657c0eec --- /dev/null +++ b/src/core/backbone_router/bbr_manager.hpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for Backbone Router management. + */ + +#ifndef BACKBONE_ROUTER_MANAGER_HPP_ +#define BACKBONE_ROUTER_MANAGER_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE +#include +#include + +#include "backbone_router/bbr_leader.hpp" +#include "common/locator.hpp" +#include "net/netif.hpp" +#include "thread/network_data.hpp" + +namespace ot { + +namespace BackboneRouter { + +/** + * This class implements the definitions for Backbone Router management. + * + */ +class Manager : public InstanceLocator, public Notifier::Receiver +{ +public: + /** + * This constructor initializes the Backbone Router manager. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit Manager(Instance &aInstance); + +private: + static void HandleMulticastListenerRegistration(void * aContext, + otMessage * aMessage, + const otMessageInfo *aMessageInfo) + { + static_cast(aContext)->HandleMulticastListenerRegistration( + *static_cast(aMessage), *static_cast(aMessageInfo)); + } + void HandleMulticastListenerRegistration(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + void SendMulticastListenerRegistrationResponse(const Coap::Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + ThreadStatusTlv::MlrStatus aStatus); + + static void HandleNotifierEvents(Notifier::Receiver &aReceiver, Events aEvents) + { + static_cast(aReceiver).HandleNotifierEvents(aEvents); + } + void HandleNotifierEvents(Events aEvents); + + Coap::Resource mMulticastListenerRegistration; +}; + +} // namespace BackboneRouter + +/** + * @} + */ + +} // namespace ot + +#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + +#endif // BACKBONE_ROUTER_MANAGER_HPP_ diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index df6f7b522..0f8c524ec 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -736,6 +736,11 @@ template <> inline BackboneRouter::Local &Instance::Get(void) { return mThreadNetif.mBackboneRouterLocal; } +template <> inline BackboneRouter::Manager &Instance::Get(void) +{ + return mThreadNetif.mBackboneRouterManager; +} + #endif #if OPENTHREAD_CONFIG_DUA_ENABLE @@ -745,6 +750,13 @@ template <> inline DuaManager &Instance::Get(void) } #endif +#if OPENTHREAD_CONFIG_MLR_ENABLE +template <> inline MlrManager &Instance::Get(void) +{ + return mThreadNetif.mMlrManager; +} +#endif + #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) #if OPENTHREAD_CONFIG_OTNS_ENABLE diff --git a/src/core/config/openthread-core-config-check.h b/src/core/config/openthread-core-config-check.h index dedd88f0b..50a13dd10 100644 --- a/src/core/config/openthread-core-config-check.h +++ b/src/core/config/openthread-core-config-check.h @@ -521,6 +521,10 @@ #error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_DUA_ENABLE" #endif +#if OPENTHREAD_CONFIG_MLR_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2) +#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MLR_ENABLE" +#endif + #ifdef OPENTHREAD_CONFIG_LOG_OUTPUT_NCP_SPINEL #error "OPENTHREAD_CONFIG_LOG_OUTPUT_NCP_SPINEL is removed, use OPENTHREAD_CONFIG_LOG_OUTPUT_APP instead" #endif diff --git a/src/core/config/openthread-core-default-config.h b/src/core/config/openthread-core-default-config.h index f9a842331..d67c500fb 100644 --- a/src/core/config/openthread-core-default-config.h +++ b/src/core/config/openthread-core-default-config.h @@ -460,4 +460,14 @@ #define OPENTHREAD_CONFIG_DUA_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_MLR_ENABLE + * + * Define as 1 to support Thread 1.2 Multicast Listener Registration feature. + * + */ +#ifndef OPENTHREAD_CONFIG_MLR_ENABLE +#define OPENTHREAD_CONFIG_MLR_ENABLE 0 +#endif + #endif // OPENTHREAD_CORE_DEFAULT_CONFIG_H_ diff --git a/src/core/net/netif.cpp b/src/core/net/netif.cpp index a639fcd31..57d1e7197 100644 --- a/src/core/net/netif.cpp +++ b/src/core/net/netif.cpp @@ -285,7 +285,7 @@ exit: bool Netif::IsMulticastAddressExternal(const NetifMulticastAddress &aAddress) const { - return mExtMulticastAddressPool.IsPoolEntry(aAddress); + return mExtMulticastAddressPool.IsPoolEntry(static_cast(aAddress)); } void Netif::SubscribeMulticast(NetifMulticastAddress &aAddress) @@ -319,7 +319,7 @@ otError Netif::SubscribeExternalMulticast(const Address &aAddress) otError error = OT_ERROR_NONE; NetifMulticastAddress &linkLocalAllRoutersAddress = static_cast( const_cast(kLinkLocalAllRoutersMulticastAddress)); - NetifMulticastAddress *entry; + ExternalNetifMulticastAddress *entry; VerifyOrExit(!IsMulticastSubscribed(aAddress), error = OT_ERROR_ALREADY); @@ -336,6 +336,9 @@ otError Netif::SubscribeExternalMulticast(const Address &aAddress) VerifyOrExit(entry != nullptr, error = OT_ERROR_NO_BUFS); entry->mAddress = aAddress; +#if OPENTHREAD_CONFIG_MLR_ENABLE + entry->mMlrState = kMlrStateToRegister; +#endif mMulticastAddresses.Push(*entry); Get().Signal(kEventIp6MulticastSubscribed); @@ -356,7 +359,7 @@ otError Netif::UnsubscribeExternalMulticast(const Address &aAddress) mMulticastAddresses.PopAfter(prev); - mExtMulticastAddressPool.Free(*entry); + mExtMulticastAddressPool.Free(static_cast(*entry)); Get().Signal(kEventIp6MulticastUnsubscribed); diff --git a/src/core/net/netif.hpp b/src/core/net/netif.hpp index fa2d81cfa..21a21d536 100644 --- a/src/core/net/netif.hpp +++ b/src/core/net/netif.hpp @@ -46,6 +46,7 @@ #include "mac/mac_types.hpp" #include "net/ip6_address.hpp" #include "net/socket.hpp" +#include "thread/mlr_types.hpp" namespace ot { namespace Ip6 { @@ -156,6 +157,41 @@ private: bool Matches(const Address &aAddress) const { return GetAddress() == aAddress; } }; +class ExternalNetifMulticastAddress : public NetifMulticastAddress +{ + friend class Netif; + friend class LinkedList; + +public: +#if OPENTHREAD_CONFIG_MLR_ENABLE + /** + * This method returns the current Multicast Listener Registration (MLR) state. + * + * @returns The current Multicast Listener Registration state. + * + */ + MlrState GetMlrState(void) const { return mMlrState; } + + /** + * This method sets the Multicast Listener Registration (MLR) state. + * + * @param[in] aState The new Multicast Listener Registration state. + * + */ + void SetMlrState(MlrState aState) { mMlrState = aState; } +#endif + +private: + ExternalNetifMulticastAddress *GetNext(void) + { + return static_cast(const_cast(mNext)); + } + +#if OPENTHREAD_CONFIG_MLR_ENABLE + MlrState mMlrState : 2; +#endif +}; + /** * This class implements an IPv6 network interface. * @@ -164,8 +200,125 @@ class Netif : public InstanceLocator, public LinkedListEntry, private Non { friend class Ip6; friend class Address; + class ExternalMulticastAddressIteratorBuilder; public: + /** + * This class represents an iterator for iterating external multicast addresses in a Netif instance. + * + */ + class ExternalMulticastAddressIterator + { + friend class ExternalMulticastAddressIteratorBuilder; + + public: + /** + * This constructor initializes an `ExternalMulticastAddressIterator` instance to start from the first external + * multicast address. + * + * @param[in] aNetif A reference to the Netif instance. + * + */ + explicit ExternalMulticastAddressIterator(Netif &aNetif) + : mNetif(aNetif) + { + AdvanceFrom(mNetif.GetMulticastAddresses()); + } + + /** + * This method overloads `++` operator (pre-increment) to advance the iterator. + * + * The iterator is moved to point to the next entry. If there are no more entries matching the iterator becomes + * empty. + * + */ + void operator++(void) { AdvanceFrom(mCurrent->GetNext()); } + + /** + * This method overloads `++` operator (post-increment) to advance the iterator. + * + * The iterator is moved to point to the next entry. If there are no more entries matching the iterator becomes + * empty. + * + */ + void operator++(int) { AdvanceFrom(mCurrent->GetNext()); } + + /** + * This method overloads the `*` dereference operator and gets a reference to `ExternalNetifMulticastAddress` + * entry to which the iterator is currently pointing. + * + * This method MUST be used when the iterator is not empty. + * + * @returns A reference to the `ExternalNetifMulticastAddress` entry currently pointed by the iterator. + * + */ + ExternalNetifMulticastAddress &operator*(void) { return *mCurrent; } + + /** + * This method overloads the `->` dereference operator and gets a pointer to `ExternalNetifMulticastAddress` + * entry to which the iterator is current pointing. + * + * @returns A pointer to the `ExternalNetifMulticastAddress` entry associated with the iterator, or `nullptr` if + * iterator is empty. + * + */ + ExternalNetifMulticastAddress *operator->(void) { return mCurrent; } + + /** + * This method overloads operator `==` to evaluate whether or not two `ExternalMulticastAddressIterator` + * instances point to the same `ExternalNetifMulticastAddress` entry. + * + * @param[in] aOther The other `Iterator` to compare with. + * + * @retval TRUE If the two `ExternalMulticastAddressIterator` objects point to the same + * `ExternalNetifMulticastAddress` entry or both are done. + * @retval FALSE If the two `ExternalMulticastAddressIterator` objects do not point to the same + * `ExternalNetifMulticastAddress` entry. + * + */ + bool operator==(const ExternalMulticastAddressIterator &aOther) { return mCurrent == aOther.mCurrent; } + + /** + * This method overloads operator `!=` to evaluate whether or not two `ExternalMulticastAddressIterator` + * instances point to the same `ExternalNetifMulticastAddress` entry. + * + * @param[in] aOther The other `ExternalMulticastAddressIterator` to compare with. + * + * @retval TRUE If the two `ExternalMulticastAddressIterator` objects do not point to the same + * `ExternalNetifMulticastAddress` entry. + * @retval FALSE If the two `ExternalMulticastAddressIterator` objects point to the same + * `ExternalNetifMulticastAddress` entry or both are done. + * + */ + bool operator!=(const ExternalMulticastAddressIterator &aOther) { return mCurrent != aOther.mCurrent; } + + private: + enum IteratorType + { + kEndIterator, + }; + + ExternalMulticastAddressIterator(Netif &aNetif, IteratorType) + : mNetif(aNetif) + , mCurrent(nullptr) + { + } + + void AdvanceFrom(const NetifMulticastAddress *aAddr) + { + while (aAddr != nullptr && !mNetif.IsMulticastAddressExternal(*aAddr)) + { + aAddr = aAddr->GetNext(); + } + + mCurrent = + const_cast(static_cast(aAddr)); + } + + Netif & mNetif; + ExternalNetifMulticastAddress *mCurrent; + }; + /** * This constructor initializes the network interface. * @@ -400,6 +553,22 @@ public: */ void SetMulticastPromiscuous(bool aEnabled) { mMulticastPromiscuous = aEnabled; } + /** + * This method enables range-based `for` loop iteration over all external multicast addresses on the Netif. + * + * This method should be used like follows: + * + * for (Ip6::ExternalNetifMulticastAddress &addr : Get().IterateExternalMulticastAddresses()) + * { ... } + * + * @returns An `ExternalMulticastAddressIteratorBuilder` instance. + * + */ + ExternalMulticastAddressIteratorBuilder IterateExternalMulticastAddresses(void) + { + return ExternalMulticastAddressIteratorBuilder(*this); + } + protected: /** * This method subscribes the network interface to the realm-local all MPL forwarders, link-local, and realm-local @@ -423,6 +592,24 @@ private: kMulticastPrefixLength = 128, ///< Multicast prefix length used to notify internal address changes. }; + class ExternalMulticastAddressIteratorBuilder + { + public: + ExternalMulticastAddressIteratorBuilder(Netif &aNetif) + : mNetif(aNetif) + { + } + + ExternalMulticastAddressIterator begin(void) { return ExternalMulticastAddressIterator(mNetif); } + ExternalMulticastAddressIterator end(void) + { + return ExternalMulticastAddressIterator(mNetif, ExternalMulticastAddressIterator::kEndIterator); + } + + private: + Netif &mNetif; + }; + LinkedList mUnicastAddresses; LinkedList mMulticastAddresses; bool mMulticastPromiscuous; @@ -430,8 +617,8 @@ private: otIp6AddressCallback mAddressCallback; void * mAddressCallbackContext; - Pool mExtUnicastAddressPool; - Pool mExtMulticastAddressPool; + Pool mExtUnicastAddressPool; + Pool mExtMulticastAddressPool; static const otNetifMulticastAddress kRealmLocalAllMplForwardersMulticastAddress; static const otNetifMulticastAddress kLinkLocalAllNodesMulticastAddress; diff --git a/src/core/thread/mle_types.hpp b/src/core/thread/mle_types.hpp index ce865f813..6920a8ff8 100644 --- a/src/core/thread/mle_types.hpp +++ b/src/core/thread/mle_types.hpp @@ -249,10 +249,10 @@ enum kServiceMaxId = 0x0f, ///< Maximal Service ID. }; -#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE +#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE || OPENTHREAD_CONFIG_MLR_ENABLE /** - * Backbone Router constants + * Backbone Router / MLR constants * */ enum @@ -266,7 +266,7 @@ enum static_assert(kMlrTimeoutDefault >= kMlrTimeoutMin, "kMlrTimeoutDefault must be larger than or equal to kMlrTimeoutMin"); -#endif +#endif // OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE || OPENTHREAD_CONFIG_MLR_ENABLE /** * This type represents a MLE device mode. diff --git a/src/core/thread/mlr_manager.cpp b/src/core/thread/mlr_manager.cpp new file mode 100644 index 000000000..db04d5d35 --- /dev/null +++ b/src/core/thread/mlr_manager.cpp @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements MLR management. + */ + +#include "mlr_manager.hpp" + +#if OPENTHREAD_CONFIG_MLR_ENABLE + +#include "common/code_utils.hpp" +#include "common/instance.hpp" +#include "common/locator-getters.hpp" +#include "common/logging.hpp" +#include "net/ip6_address.hpp" +#include "thread/thread_netif.hpp" +#include "thread/thread_uri_paths.hpp" +#include "utils/slaac_address.hpp" + +namespace ot { + +MlrManager::MlrManager(Instance &aInstance) + : InstanceLocator(aInstance) + , Notifier::Receiver(aInstance, MlrManager::HandleNotifierEvents) + , mTimer(aInstance, MlrManager::HandleTimer, this) + , mReregistrationDelay(0) + , mSendDelay(0) + , mMlrPending(false) +{ +} + +void MlrManager::HandleNotifierEvents(Events aEvents) +{ + if (aEvents.Contains(kEventIp6MulticastSubscribed)) + { + ScheduleSend(0); + } + + if (aEvents.Contains(kEventThreadRoleChanged) && Get().IsChild()) + { + // Reregistration after re-attach + UpdateReregistrationDelay(true); + } +} + +void MlrManager::HandleBackboneRouterPrimaryUpdate(BackboneRouter::Leader::State aState, + const BackboneRouter::BackboneRouterConfig &aConfig) +{ + OT_UNUSED_VARIABLE(aConfig); + + bool needRereg = + aState == BackboneRouter::Leader::kStateAdded || aState == BackboneRouter::Leader::kStateToTriggerRereg; + + UpdateReregistrationDelay(needRereg); +} + +void MlrManager::ScheduleSend(uint16_t aDelay) +{ + OT_ASSERT(!mMlrPending || mSendDelay == 0); + + VerifyOrExit(!mMlrPending, OT_NOOP); + + if (aDelay == 0) + { + mSendDelay = 0; + SendMulticastListenerRegistration(); + } + else if (mSendDelay == 0 || mSendDelay > aDelay) + { + mSendDelay = aDelay; + } + + ResetTimer(); +exit: + return; +} + +void MlrManager::ResetTimer(void) +{ + if (mSendDelay == 0 && mReregistrationDelay == 0) + { + mTimer.Stop(); + } + else if (!mTimer.IsRunning()) + { + mTimer.Start(kTimerInterval); + } +} + +void MlrManager::SendMulticastListenerRegistration(void) +{ + otError error = OT_ERROR_NONE; + Mle::MleRouter & mle = Get(); + Coap::Message * message = nullptr; + Ip6::MessageInfo messageInfo; + IPv6AddressesTlv addressesTlv; + uint8_t num; + + 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().HasPrimary(), error = OT_ERROR_INVALID_STATE); + + num = static_cast(CountNetifMulticastAddressesToRegister()); + VerifyOrExit(num > 0, error = OT_ERROR_NOT_FOUND); + if (num > kIPv6AddressesNumMax) + { + num = kIPv6AddressesNumMax; + } + + VerifyOrExit((message = Get().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS); + + message->Init(OT_COAP_TYPE_CONFIRMABLE, OT_COAP_CODE_POST); + SuccessOrExit(message->SetToken(Coap::Message::kDefaultTokenLength)); + SuccessOrExit(message->AppendUriPathOptions(OT_URI_PATH_MLR)); + SuccessOrExit(message->SetPayloadMarker()); + + addressesTlv.Init(); + addressesTlv.SetLength(sizeof(Ip6::Address) * num); + SuccessOrExit(error = message->Append(&addressesTlv, sizeof(addressesTlv))); + + for (Ip6::ExternalNetifMulticastAddress &addr : Get().IterateExternalMulticastAddresses()) + { + if (addr.GetAddress().IsMulticastLargerThanRealmLocal() && addr.GetMlrState() == kMlrStateToRegister) + { + SuccessOrExit(error = message->Append(&addr.GetAddress(), sizeof(Ip6::Address))); + addr.SetMlrState(kMlrStateRegistering); + + if (--num == 0) + { + break; + } + } + } + + if (!mle.IsFullThreadDevice() && mle.GetParent().IsThreadVersion1p1()) + { + uint8_t pbbrServiceId; + + SuccessOrExit(error = Get().GetServiceId(pbbrServiceId)); + SuccessOrExit(error = mle.GetServiceAloc(pbbrServiceId, messageInfo.GetPeerAddr())); + } + else + { + messageInfo.GetPeerAddr().SetToRoutingLocator(mle.GetMeshLocalPrefix(), + Get().GetServer16()); + } + + messageInfo.SetPeerPort(kCoapUdpPort); + messageInfo.SetSockAddr(mle.GetMeshLocal16()); + + SuccessOrExit(error = Get().SendMessage( + *message, messageInfo, &MlrManager::HandleMulticastListenerRegistrationResponse, this)); + + mMlrPending = true; + + // TODO: not enable fast polls for SSED + if (!Get().IsRxOnWhenIdle()) + { + Get().SendFastPolls(DataPollSender::kDefaultFastPolls); + } + +exit: + if (error != OT_ERROR_NONE) + { + if (message != nullptr) + { + message->Free(); + } + + SetNetifMulticastAddressMlrState(kMlrStateRegistering, kMlrStateToRegister); + + if (error == OT_ERROR_NO_BUFS) + { + ScheduleSend(1); + } + } + + otLogInfoMlr("Send MLR.req: %s", otThreadErrorToString(error)); + LogMulticastAddresses(); +} + +void MlrManager::HandleMulticastListenerRegistrationResponse(Coap::Message * aMessage, + const Ip6::MessageInfo *aMessageInfo, + otError aResult) +{ + OT_UNUSED_VARIABLE(aMessageInfo); + + uint8_t status = ThreadStatusTlv::MlrStatus::kMlrGeneralFailure; + otError error; + + mMlrPending = false; + + VerifyOrExit(aResult == OT_ERROR_NONE && aMessage != nullptr, error = OT_ERROR_PARSE); + VerifyOrExit(aMessage->GetCode() == OT_COAP_CODE_CHANGED, error = OT_ERROR_PARSE); + + SuccessOrExit(error = Tlv::FindUint8Tlv(*aMessage, ThreadTlv::kStatus, status)); + +exit: + + if (status == ThreadStatusTlv::MlrStatus::kMlrSuccess) + { + SetNetifMulticastAddressMlrState(kMlrStateRegistering, kMlrStateRegistered); + // keep sending until all multicast addresses are registered. + ScheduleSend(0); + } + else + { + 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 + // TODO (MLR): reregister only failed addresses. + if (Get().GetConfig(config) == OT_ERROR_NONE) + { + reregDelay = config.mReregistrationDelay > 1 + ? Random::NonCrypto::GetUint16InRange(1, config.mReregistrationDelay) + : 1; + ScheduleSend(reregDelay); + } + } + + otLogInfoMlr("Receive MLR.rsp, result=%s, status=%d, error=%s", otThreadErrorToString(aResult), status, + otThreadErrorToString(error)); +} + +void MlrManager::HandleTimer(void) +{ + if (mSendDelay > 0 && --mSendDelay == 0) + { + SendMulticastListenerRegistration(); + } + + if (mReregistrationDelay > 0 && --mReregistrationDelay == 0) + { + Reregister(); + } + + ResetTimer(); +} + +void MlrManager::Reregister(void) +{ + SetNetifMulticastAddressMlrState(kMlrStateRegistered, kMlrStateToRegister); + ScheduleSend(0); + + // Schedule for the next renewing. + UpdateReregistrationDelay(false); +} + +void MlrManager::UpdateReregistrationDelay(bool aRereg) +{ + Mle::MleRouter &mle = Get(); + + bool needSendMlr = (mle.IsFullThreadDevice() || mle.GetParent().IsThreadVersion1p1()) && + Get().HasPrimary(); + + if (!needSendMlr) + { + mReregistrationDelay = 0; + } + else + { + BackboneRouter::BackboneRouterConfig config; + uint32_t reregDelay; + uint32_t effectiveMlrTimeout; + + IgnoreError(Get().GetConfig(config)); + + if (aRereg) + { + reregDelay = config.mReregistrationDelay > 1 + ? Random::NonCrypto::GetUint16InRange(1, config.mReregistrationDelay) + : 1; + } + else + { + // Calculate renewing period according to Thread Spec. 5.24.2.3.2 + // The random time t SHOULD be chosen such that (0.5* MLR-Timeout) < t < (MLR-Timeout – 9 seconds). + effectiveMlrTimeout = config.mMlrTimeout > Mle::kMlrTimeoutMin ? config.mMlrTimeout + : static_cast(Mle::kMlrTimeoutMin); + reregDelay = Random::NonCrypto::GetUint32InRange((effectiveMlrTimeout >> 1u) + 1, effectiveMlrTimeout - 9); + } + + if (mReregistrationDelay == 0 || mReregistrationDelay > reregDelay) + { + mReregistrationDelay = reregDelay; + } + } + + ResetTimer(); + + otLogDebgMlr("MlrManager::UpdateReregistrationDelay: rereg=%d, needSendMlr=%d, ReregDelay=%lu", aRereg, needSendMlr, + mReregistrationDelay); +} + +void MlrManager::LogMulticastAddresses(void) +{ +#if OPENTHREAD_CONFIG_LOG_BBR && OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_DEBG + otLogDebgMlr("-------- Multicast Addresses --------"); + + for (const Ip6::ExternalNetifMulticastAddress &addr : Get().IterateExternalMulticastAddresses()) + { + if (addr.GetAddress().IsMulticastLargerThanRealmLocal()) + { + MlrState state = addr.GetMlrState(); + + otLogDebgMlr("%-32s%c", addr.GetAddress().ToString().AsCString(), "-rR"[state]); + } + } +#endif +} + +uint16_t MlrManager::CountNetifMulticastAddressesToRegister(void) const +{ + uint16_t count = 0; + + for (const Ip6::ExternalNetifMulticastAddress &addr : Get().IterateExternalMulticastAddresses()) + { + if (addr.GetAddress().IsMulticastLargerThanRealmLocal() && addr.GetMlrState() == kMlrStateToRegister) + { + count++; + } + } + + return count; +} + +void MlrManager::SetNetifMulticastAddressMlrState(MlrState aFromState, MlrState aToState) +{ + for (Ip6::ExternalNetifMulticastAddress &addr : Get().IterateExternalMulticastAddresses()) + { + if (addr.GetAddress().IsMulticastLargerThanRealmLocal() && addr.GetMlrState() == aFromState) + { + addr.SetMlrState(aToState); + } + } +} + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_MLR_ENABLE diff --git a/src/core/thread/mlr_manager.hpp b/src/core/thread/mlr_manager.hpp new file mode 100644 index 000000000..10681a799 --- /dev/null +++ b/src/core/thread/mlr_manager.hpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for managing Multicast Listener Registration feature defined in Thread 1.2. + */ + +#ifndef MLR_MANAGER_HPP_ +#define MLR_MANAGER_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_MLR_ENABLE + +#include "backbone_router/bbr_leader.hpp" +#include "coap/coap_message.hpp" +#include "common/locator.hpp" +#include "common/notifier.hpp" +#include "common/timer.hpp" +#include "net/netif.hpp" + +namespace ot { + +/** + * @addtogroup core-mlr + * + * @brief + * This module includes definitions for Multicast Listener Registration. + * + * @{ + * + * @defgroup core-mlr Mlr + * + * @} + * + */ + +/** + * This class implements MLR management. + * + */ +class MlrManager : public InstanceLocator, public Notifier::Receiver +{ +public: + /** + * This constructor initializes the object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit MlrManager(Instance &aInstance); + + /** + * This method notifies Primary Backbone Router status. + * + * @param[in] aState The state or state change of Primary Backbone Router. + * @param[in] aConfig The Primary Backbone Router service. + * + */ + void HandleBackboneRouterPrimaryUpdate(BackboneRouter::Leader::State aState, + const BackboneRouter::BackboneRouterConfig &aConfig); + +private: + enum + { + kTimerInterval = 1000, + }; + + static void HandleNotifierEvents(Notifier::Receiver &aReceiver, Events aEvents) + { + static_cast(aReceiver).HandleNotifierEvents(aEvents); + } + void HandleNotifierEvents(Events aEvents); + + void SendMulticastListenerRegistration(void); + + static void HandleMulticastListenerRegistrationResponse(void * aContext, + otMessage * aMessage, + const otMessageInfo *aMessageInfo, + otError aResult) + { + static_cast(aContext)->HandleMulticastListenerRegistrationResponse( + static_cast(aMessage), static_cast(aMessageInfo), aResult); + } + + void HandleMulticastListenerRegistrationResponse(Coap::Message * aMessage, + const Ip6::MessageInfo *aMessageInfo, + otError aResult); + + uint16_t CountNetifMulticastAddressesToRegister(void) const; + void SetNetifMulticastAddressMlrState(MlrState aFromState, MlrState aToState); + + void ScheduleSend(uint16_t aDelay); + void ResetTimer(void); + void UpdateReregistrationDelay(bool aRereg); + void Reregister(void); + + static void HandleTimer(Timer &aTimer) { aTimer.GetOwner().HandleTimer(); } + void HandleTimer(void); + + void LogMulticastAddresses(void); + + TimerMilli mTimer; + uint32_t mReregistrationDelay; + uint16_t mSendDelay; + bool mMlrPending : 1; +}; + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_MLR_ENABLE +#endif // MLR_MANAGER_HPP_ diff --git a/src/core/thread/mlr_types.hpp b/src/core/thread/mlr_types.hpp new file mode 100644 index 000000000..be6e11691 --- /dev/null +++ b/src/core/thread/mlr_types.hpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for Multicast Listener Registration states of multicast addresses. + */ + +#ifndef MLR_TYPES_HPP_ +#define MLR_TYPES_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_MLR_ENABLE + +namespace ot { + +/** + * @addtogroup core-mlr + * + * @{ + * + */ + +/** + * Multicast Listener Registration state for multicast addresses. + * + */ +typedef 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 // MLR_TYPES_HPP_ diff --git a/src/core/thread/thread_netif.cpp b/src/core/thread/thread_netif.cpp index 1bb64ac17..16fd3a190 100644 --- a/src/core/thread/thread_netif.cpp +++ b/src/core/thread/thread_netif.cpp @@ -109,9 +109,13 @@ ThreadNetif::ThreadNetif(Instance &aInstance) #endif #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE , mBackboneRouterLocal(aInstance) + , mBackboneRouterManager(aInstance) #endif #if OPENTHREAD_CONFIG_DUA_ENABLE , mDuaManager(aInstance) +#endif +#if OPENTHREAD_CONFIG_MLR_ENABLE + , mMlrManager(aInstance) #endif , mChildSupervisor(aInstance) , mSupervisionListener(aInstance) diff --git a/src/core/thread/thread_netif.hpp b/src/core/thread/thread_netif.hpp index 55843b17f..5e35a6b81 100644 --- a/src/core/thread/thread_netif.hpp +++ b/src/core/thread/thread_netif.hpp @@ -52,12 +52,17 @@ #endif #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE #include "backbone_router/bbr_local.hpp" +#include "backbone_router/bbr_manager.hpp" #endif #if OPENTHREAD_CONFIG_DUA_ENABLE #include "thread/dua_manager.hpp" #endif +#if OPENTHREAD_CONFIG_MLR_ENABLE +#include "thread/mlr_manager.hpp" +#endif + #include "meshcop/dataset_manager.hpp" #if OPENTHREAD_CONFIG_JOINER_ENABLE @@ -253,10 +258,14 @@ private: BackboneRouter::Leader mBackboneRouterLeader; #endif #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE - BackboneRouter::Local mBackboneRouterLocal; + BackboneRouter::Local mBackboneRouterLocal; + BackboneRouter::Manager mBackboneRouterManager; #endif #if OPENTHREAD_CONFIG_DUA_ENABLE DuaManager mDuaManager; +#endif +#if OPENTHREAD_CONFIG_MLR_ENABLE + MlrManager mMlrManager; #endif Utils::ChildSupervisor mChildSupervisor; Utils::SupervisionListener mSupervisionListener; diff --git a/src/core/thread/thread_tlvs.hpp b/src/core/thread/thread_tlvs.hpp index e3de8ed35..565107f4c 100644 --- a/src/core/thread/thread_tlvs.hpp +++ b/src/core/thread/thread_tlvs.hpp @@ -51,6 +51,10 @@ using ot::Encoding::BigEndian::HostSwap32; enum { kCoapUdpPort = 61631, + + // Thread 1.2.0 5.19.13 limits the number of IPv6 addresses should be [1, 15]. + kIPv6AddressesNumMin = 1, + kIPv6AddressesNumMax = 15, }; /** @@ -77,6 +81,7 @@ public: kNDOption = 8, ///< ND Option TLV kNDData = 9, ///< ND Data TLV kThreadNetworkData = 10, ///< Thread Network Data TLV + kIPv6Addresses = 14, ///< IPv6 Addresses TLV }; /** @@ -116,6 +121,20 @@ public: kHaveChildIdRequest = 3, ///< Address Solicit due to child ID request. kParentPartitionChange = 4, ///< Address Solicit due to parent partition change }; + + /** + * Multicast Listener Registration (MLR) Status values + * + */ + enum MlrStatus + { + kMlrSuccess = 0, ///< Successful (de)registration of all IPv6 addresses. + kMlrInvalid = 2, ///< Invalid IPv6 address(es) in request. + kMlrNoPersistent = 3, ///< This device does not support persistent registrations. + kMlrNoResources = 4, ///< BBR resource shortage. + kMlrBbrNotPrimary = 5, ///< BBR is not Primary at this moment. + kMlrGeneralFailure = 6, ///< Reason(s) for failure are not further specified. + }; }; /** @@ -225,6 +244,51 @@ private: uint8_t mTlvs[kMaxSize]; } OT_TOOL_PACKED_END; +#if OPENTHREAD_CONFIG_MLR_ENABLE + +/** + * This class implements IPv6 Addresses TLV generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class IPv6AddressesTlv : public ThreadTlv +{ +public: + /** + * This method initializes the TLV. + * + */ + void Init(void) { SetType(kIPv6Addresses); } + + /** + * This method indicates whether or not the TLV appears to be well-formed. + * + * @retval TRUE If the TLV appears to be well-formed. + * @retval FALSE If the TLV does not appear to be well-formed. + * + */ + bool IsValid(void) const + { + return GetLength() >= sizeof(Ip6::Address) * kIPv6AddressesNumMin && + GetLength() <= sizeof(Ip6::Address) * kIPv6AddressesNumMax && (GetLength() % sizeof(Ip6::Address)) == 0; + } + + /** + * This method returns a pointer to the IPv6 address entry. + * + * @param[in] aIndex The index into the IPv6 address list. + * + * @returns A reference to the IPv6 address. + * + */ + const Ip6::Address &GetIp6Address(uint8_t aIndex) const + { + return *reinterpret_cast(GetValue() + (aIndex * sizeof(Ip6::Address))); + } +} OT_TOOL_PACKED_END; + +#endif // OPENTHREAD_CONFIG_MLR_ENABLE + } // namespace ot #endif // THREAD_TLVS_HPP_ diff --git a/src/core/thread/thread_uri_paths.hpp b/src/core/thread/thread_uri_paths.hpp index ef7190596..10958206e 100644 --- a/src/core/thread/thread_uri_paths.hpp +++ b/src/core/thread/thread_uri_paths.hpp @@ -292,6 +292,14 @@ namespace ot { */ #define OT_URI_PATH_DIAGNOSTIC_RESET "d/dr" +/** + * @def OT_URI_PATH_MLR + * + * The URI Path for Multicast Listener Registration. + * + */ +#define OT_URI_PATH_MLR "n/mr" + } // namespace ot #endif // THREAD_URIS_HPP_ diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index 767948ae2..a406ee9be 100644 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -336,6 +336,8 @@ def create_deafult_network_tlvs_factories(): # which is in fact MLE Route64 TLV. Thread specificaton v1.1. - Chapter 5.20 network_layer.TlvType.MLE_ROUTING: create_default_mle_tlv_route64_factory(), + network_layer.TlvType.IPv6_ADDRESSES: + network_layer.IPv6AddressesFactory(), } @@ -488,6 +490,7 @@ def create_default_uri_path_based_payload_factories(): '/d/dg': network_diag_tlvs_factory, '/d/dq': network_diag_tlvs_factory, '/d/dr': network_diag_tlvs_factory, + '/n/mr': network_layer_tlvs_factory, } diff --git a/tests/scripts/thread-cert/network_layer.py b/tests/scripts/thread-cert/network_layer.py index 4febac5dd..7f220e40e 100644 --- a/tests/scripts/thread-cert/network_layer.py +++ b/tests/scripts/thread-cert/network_layer.py @@ -27,8 +27,8 @@ # POSSIBILITY OF SUCH DAMAGE. # +import ipaddress import struct - from binascii import hexlify from enum import IntEnum @@ -47,6 +47,7 @@ class TlvType(IntEnum): ND_DATA = 9 THREAD_NETWORK_DATA = 10 MLE_ROUTING = 11 + IPv6_ADDRESSES = 14 XTAL_ACCURACY = 254 @@ -318,3 +319,20 @@ class ThreadNetworkDataFactory(object): def parse(self, data, message_info): tlvs = self._network_data_tlvs_factory.parse(data, message_info) return ThreadNetworkData(tlvs) + + +class IPv6Addresses(list): + pass + + +class IPv6AddressesFactory(object): + + def parse(self, data, message_info): + data = bytes(data.read()) + assert len(data) % 16 == 0, data + addrs = IPv6Addresses() + for i in range(0, len(data), 16): + addr = ipaddress.IPv6Address(data[i:i + 16]) + addrs.append(addr) + + return addrs diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 8df2b38f7..6573fcad5 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -727,6 +727,11 @@ class Node: self.send_command(cmd) self._expect('Done') + def del_ipmaddr(self, ipmaddr): + cmd = 'ipmaddr del %s' % ipmaddr + self.send_command(cmd) + self._expect('Done') + def get_addrs(self): self.send_command('ipaddr') diff --git a/tests/scripts/thread-cert/simulator.py b/tests/scripts/thread-cert/simulator.py index 1fc38daf3..3970fe18d 100644 --- a/tests/scripts/thread-cert/simulator.py +++ b/tests/scripts/thread-cert/simulator.py @@ -135,7 +135,7 @@ class VirtualTime(BaseSimulator): BASE_PORT = 9000 MAX_NODES = 34 MAX_MESSAGE = 1024 - END_OF_TIME = 0x7FFFFFFF + END_OF_TIME = float('inf') PORT_OFFSET = int(os.getenv('PORT_OFFSET', '0')) BLOCK_TIMEOUT = 10 diff --git a/tests/scripts/thread-cert/v1_2_test_multicast_listener_registration.py b/tests/scripts/thread-cert/v1_2_test_multicast_listener_registration.py new file mode 100755 index 000000000..83c2d13c7 --- /dev/null +++ b/tests/scripts/thread-cert/v1_2_test_multicast_listener_registration.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +import ipaddress +import unittest + +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) + +WAIT_ATTACH = 5 +WAIT_REDUNDANCE = 3 +ROUTER_SELECTION_JITTER = 1 +BBR_REGISTRATION_JITTER = 5 +SED_POLL_PERIOD = 1000 # 1s + +REREG_DELAY = 10 +MLR_TIMEOUT = 300 + +MA1 = 'ff04::1234:777a:1' +MA1g = 'ff0e::1234:777a:1' +MA2 = 'ff05::1234:777a:2' +MA3 = 'ff0e::1234:777a:3' +MA4 = 'ff05::1234:777a:4' + +MA5 = 'ff03::1234:777a:5' +MA6 = 'ff02::10' +""" + Initial topology: + + BBR_1---BBR_2 + | / \ | + | / \ | + ROUTER_1_2 ROUTER_1_1 + | | \ | + | | \ | + SED_1 MED_1 FED_1 +""" + + +class TestMulticastListenerRegistration(thread_cert.TestCase): + TOPOLOGY = { + BBR_1: { + 'version': '1.2', + 'whitelist': [BBR_2, ROUTER_1_2], + 'is_bbr': True, + 'router_selection_jitter': 1, + }, + BBR_2: { + 'version': '1.2', + 'whitelist': [BBR_1, ROUTER_1_2, ROUTER_1_1], + 'is_bbr': True, + 'router_selection_jitter': 1, + }, + ROUTER_1_2: { + 'version': '1.2', + 'whitelist': [BBR_1, BBR_2, SED_1, MED_1, FED_1], + 'router_selection_jitter': 1, + }, + ROUTER_1_1: { + 'version': '1.1', + 'whitelist': [BBR_2], + 'router_selection_jitter': 1, + }, + MED_1: { + 'mode': 'rsn', + 'version': '1.2', + 'whitelist': [ROUTER_1_2], + 'timeout': config.DEFAULT_CHILD_TIMEOUT, + }, + SED_1: { + 'mode': 'sn', + 'version': '1.2', + 'whitelist': [ROUTER_1_2], + 'timeout': config.DEFAULT_CHILD_TIMEOUT, + }, + FED_1: { + 'mode': 'rsdn', + 'version': '1.2', + 'whitelist': [ROUTER_1_2], + 'router_upgrade_threshold': 0, + 'timeout': config.DEFAULT_CHILD_TIMEOUT, + }, + } + """All nodes are created with default configurations""" + + def test(self): + # starting context id + context_id = 1 + + # Bring up BBR_1, BBR_1 becomes Leader and Primary Backbone Router + self.nodes[BBR_1].set_router_selection_jitter(ROUTER_SELECTION_JITTER) + self.nodes[BBR_1].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER) + self.nodes[BBR_1].set_backbone_router(seqno=1, + reg_delay=REREG_DELAY, + mlr_timeout=MLR_TIMEOUT) + self.nodes[BBR_1].start() + WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[BBR_1].get_state(), 'leader') + self.nodes[BBR_1].enable_backbone_router() + WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(), + 'Primary') + + self.pbbr_seq = 1 + + # Bring up BBR_2, BBR_2 becomes Router and Secondary Backbone Router + self.nodes[BBR_2].set_router_selection_jitter(ROUTER_SELECTION_JITTER) + self.nodes[BBR_2].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER) + self.nodes[BBR_2].set_backbone_router(seqno=2, + reg_delay=REREG_DELAY, + mlr_timeout=MLR_TIMEOUT) + self.nodes[BBR_2].start() + WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[BBR_2].get_state(), 'router') + self.nodes[BBR_2].enable_backbone_router() + WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(), + 'Secondary') + + self.simulator.set_lowpan_context(context_id, config.DOMAIN_PREFIX) + domain_prefix_cid = context_id + + # Bring up ROUTER_1_2 + self.nodes[ROUTER_1_2].set_router_selection_jitter( + ROUTER_SELECTION_JITTER) + self.nodes[ROUTER_1_2].start() + WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[ROUTER_1_2].get_state(), 'router') + + # Bring up ROUTER_1_1 + self.nodes[ROUTER_1_1].set_router_selection_jitter( + ROUTER_SELECTION_JITTER) + self.nodes[ROUTER_1_1].start() + WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[ROUTER_1_1].get_state(), 'router') + + # Bring up FED_1 + self.nodes[FED_1].start() + self.simulator.go(WAIT_ATTACH) + self.assertEqual(self.nodes[FED_1].get_state(), 'child') + + # Bring up MED_1 + self.nodes[MED_1].start() + self.simulator.go(WAIT_ATTACH) + self.assertEqual(self.nodes[MED_1].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') + + # 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) + self.__check_mlr_ok(MED_1, is_ftd=False) + self.__check_mlr_ok(SED_1, is_ftd=False) + + # Switch to parent 1.1 + self.__switch_to_1_1_parent() + + # Verify MLR.req for each device when parent is 1.1 + self.__check_mlr_ok(FED_1, is_ftd=True, is_parent_1p1=True) + self.__check_mlr_ok(MED_1, is_ftd=False, is_parent_1p1=True) + self.__check_mlr_ok(SED_1, is_ftd=False, is_parent_1p1=True) + + # Switch to parent 1.2 + self.__switch_to_1_2_parent() + + 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 + print( + "======== checking MLR: Node%d (%s), Parent=%s ========" % + (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 + + for addr in [MA1, MA1g, MA2, MA3, MA4]: + self.__check_ipmaddr_add(id, addr, expect_mlr_req=expect_mlr_req) + + for addr in [MA5, MA6]: + self.__check_ipmaddr_add(id, addr, expect_mlr_req=False) + print('=' * 120) + + def __check_ipmaddr_add(self, id, addr, expect_mlr_req=True): + """Check MLR works for the added multicast address""" + self.flush_all() + self.nodes[id].add_ipmaddr(addr) + self.assertTrue(self.nodes[id].has_ipmaddr(addr)) + self.simulator.go(3) + + reg_mas = self.__get_registered_MAs(id) + if expect_mlr_req: + assert ipaddress.IPv6Address(addr) in reg_mas, (addr, reg_mas) + + 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.nodes[id].del_ipmaddr(addr) + self.simulator.go(1) + + def __get_registered_MAs(self, id): + """Get MAs registered via MLR.req by the node""" + messages = self.simulator.get_messages_sent_by(id) + reg_mas = [] + while True: + msg = messages.next_coap_message('0.02', + '/n/mr', + assert_enabled=False) + if not msg: + break + addrs = msg.get_coap_message_tlv(network_layer.IPv6Addresses) + reg_mas.extend(addrs) + + print('Node %d registered MAs: %s' % (id, reg_mas)) + return reg_mas + + def __check_renewing(self, id, addr): + """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) + + def __check_rereg(self, id, addr): + """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) + + def __check_rereg_seqno(self, id, addr): + """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() + self.pbbr_seq = (self.pbbr_seq + 10) % 256 + 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) + + def __check_rereg_pbbr_change(self, id, addr): + """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() + self.nodes[BBR_1].disable_backbone_router() + self.simulator.go(BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE) + 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) + + # Restore BBR_1 to be Primary and BBR_2 to be Secondary + self.nodes[BBR_2].disable_backbone_router() + self.nodes[BBR_1].enable_backbone_router() + self.simulator.go(BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE) + self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(), + 'Primary') + self.nodes[BBR_2].enable_backbone_router() + self.simulator.go(BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE) + self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(), + 'Secondary') + + def __switch_to_1_1_parent(self): + """Check if MLR works when nodes are switching to a 1.1 parent""" + # Add MA1 to EDs to prepare for parent switching + print("=" * 10, "switching to 1.1 parent", '=' * 10) + + self.flush_all() + + self.nodes[FED_1].add_ipmaddr(MA1) + self.nodes[MED_1].add_ipmaddr(MA1) + self.nodes[SED_1].add_ipmaddr(MA1) + + self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE) + self.assertIn(ipaddress.IPv6Address(MA1), + self.__get_registered_MAs(FED_1)) + self.assertNotIn(ipaddress.IPv6Address(MA1), + self.__get_registered_MAs(MED_1)) + self.assertNotIn(ipaddress.IPv6Address(MA1), + self.__get_registered_MAs(SED_1)) + + self.flush_all() + + # Turn off Router 1.2 and turn on Router 1.1 + self.nodes[ROUTER_1_2].stop() + for id in [FED_1, MED_1, SED_1]: + self.nodes[ROUTER_1_1].add_whitelist(self.nodes[id].get_addr64()) + self.nodes[id].add_whitelist(self.nodes[ROUTER_1_1].get_addr64()) + self.simulator.go(config.DEFAULT_CHILD_TIMEOUT + WAIT_REDUNDANCE) + + self.assertEqual(self.nodes[id].get_state(), 'child') + self.assertEqual(self.nodes[id].get_router_id(), + self.nodes[ROUTER_1_1].get_router_id()) + + self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE) + + # Verify all FED send MLR.req within REREG_DELAY when parent is 1.1 + self.assertIn(ipaddress.IPv6Address(MA1), + self.__get_registered_MAs(FED_1)) + self.assertIn(ipaddress.IPv6Address(MA1), + self.__get_registered_MAs(MED_1)) + self.assertIn(ipaddress.IPv6Address(MA1), + self.__get_registered_MAs(SED_1)) + + self.nodes[FED_1].del_ipmaddr(MA1) + self.nodes[MED_1].del_ipmaddr(MA1) + self.nodes[SED_1].del_ipmaddr(MA1) + + self.simulator.go(WAIT_REDUNDANCE) + + def __switch_to_1_2_parent(self): + """Check if MLR works when nodes are switching to a 1.2 parent""" + # Add MA1 to EDs to prepare for parent switching + print("=" * 10, "switching to 1.2 parent", '=' * 10) + + self.flush_all() + + self.nodes[FED_1].add_ipmaddr(MA1) + self.nodes[MED_1].add_ipmaddr(MA1) + self.nodes[SED_1].add_ipmaddr(MA1) + + self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE) + self.assertIn(ipaddress.IPv6Address(MA1), + self.__get_registered_MAs(FED_1)) + self.assertIn(ipaddress.IPv6Address(MA1), + self.__get_registered_MAs(MED_1)) + self.assertIn(ipaddress.IPv6Address(MA1), + self.__get_registered_MAs(SED_1)) + + self.flush_all() + + # Turn off Router 1.1 and turn on Router 1.2 + self.nodes[ROUTER_1_1].stop() + self.nodes[ROUTER_1_2].start() + for id in [FED_1, MED_1, SED_1]: + self.simulator.go(config.DEFAULT_CHILD_TIMEOUT + WAIT_REDUNDANCE) + + self.assertEqual(self.nodes[id].get_state(), 'child') + self.assertEqual(self.nodes[id].get_router_id(), + self.nodes[ROUTER_1_2].get_router_id()) + + self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE) + + # Verify only FTD sends MLR.req within REREG_DELAY when parent is 1.2 + self.assertIn(ipaddress.IPv6Address(MA1), + self.__get_registered_MAs(FED_1)) + + # MED and SED might still send MLR.req during this period because it could be sending to it's 1.2 parent. + + self.nodes[FED_1].del_ipmaddr(MA1) + self.nodes[MED_1].del_ipmaddr(MA1) + self.nodes[SED_1].del_ipmaddr(MA1) + + self.simulator.go(WAIT_REDUNDANCE) + + +if __name__ == '__main__': + unittest.main()