From 82e7e32bb5526ffa3b0cca91ff7d37636417fa75 Mon Sep 17 00:00:00 2001 From: Rongli Sun Date: Fri, 31 Jul 2020 05:10:22 +0800 Subject: [PATCH] [dua] dua registration and re-registration (#5276) - registration workflow framework - registration and reregistration - registration and reregistration on behalf of children - add pseudo response status for test/certification purpose - add unit test --- include/openthread/backbone_router_ftd.h | 19 + include/openthread/instance.h | 2 +- script/test | 2 +- src/cli/README.md | 21 + src/cli/cli.cpp | 32 +- src/core/api/backbone_router_ftd_api.cpp | 12 + src/core/backbone_router/bbr_leader.cpp | 8 +- src/core/backbone_router/bbr_leader.hpp | 6 +- src/core/backbone_router/bbr_manager.cpp | 98 +++ src/core/backbone_router/bbr_manager.hpp | 33 + src/core/common/instance.hpp | 14 +- src/core/config/tmf.h | 10 + src/core/mac/data_poll_sender.hpp | 2 +- src/core/thread/dua_manager.cpp | 582 +++++++++++++++++- src/core/thread/dua_manager.hpp | 133 +++- src/core/thread/mle_router.cpp | 41 ++ src/core/thread/mle_types.hpp | 26 +- src/core/thread/thread_netif.cpp | 7 +- src/core/thread/thread_netif.hpp | 14 +- src/core/thread/thread_tlvs.hpp | 15 + src/core/thread/thread_uri_paths.hpp | 33 + src/core/thread/topology.cpp | 20 + src/core/thread/topology.hpp | 10 + tests/scripts/thread-cert/config.py | 2 + tests/scripts/thread-cert/node.py | 12 + .../v1_2_test_domain_unicast_address.py | 3 +- ...est_domain_unicast_address_registration.py | 379 ++++++++++++ 27 files changed, 1481 insertions(+), 55 deletions(-) create mode 100755 tests/scripts/thread-cert/v1_2_test_domain_unicast_address_registration.py diff --git a/include/openthread/backbone_router_ftd.h b/include/openthread/backbone_router_ftd.h index 4eca8f6f6..a4890d996 100644 --- a/include/openthread/backbone_router_ftd.h +++ b/include/openthread/backbone_router_ftd.h @@ -174,6 +174,25 @@ void otBackboneRouterSetRegistrationJitter(otInstance *aInstance, uint8_t aJitte */ otError otBackboneRouterGetDomainPrefix(otInstance *aInstance, otBorderRouterConfig *aConfig); +/** + * This method configures response status for next DUA registration. + * + * Note: available only when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled. + * Only used for test and certification. + * + * TODO: (DUA) support coap error code and corresponding process for certification purpose. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMlIid A pointer to the Mesh Local IID. If NULL, respond with @p aStatus for any + * coming DUA.req, otherwise only respond the one with matching @p aMlIid. + * @param[in] aStatus The status to respond. + * + * + */ +void otBackboneRouterConfigNextDuaRegistrationResponse(otInstance * aInstance, + const otIp6InterfaceIdentifier *aMlIid, + uint8_t aStatus); + /** * @} * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 60940ff0f..be53ca2c4 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (18) +#define OPENTHREAD_API_VERSION (19) /** * @addtogroup api-instance diff --git a/script/test b/script/test index fd541eb62..f5b30124f 100755 --- a/script/test +++ b/script/test @@ -48,7 +48,7 @@ readonly VERBOSE="${VERBOSE:-0}" build_simulation() { local version="$1" - local options=("-DOT_THREAD_VERSION=${version}" "-DBUILD_TESTING=ON") + local options=("-DOT_THREAD_VERSION=${version}" "-DBUILD_TESTING=ON" "-DOT_REFERENCE_DEVICE=ON") if [[ ${version} == "1.2" ]]; then options+=("-DOT_DUA=ON") diff --git a/src/cli/README.md b/src/cli/README.md index cbe631cf3..6cfabb9e9 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -118,6 +118,27 @@ BBR Primary: None Done ``` +### bbr mgmt dua \ [meshLocalIid] + +Configure the response status for DUA.req with meshLocalIid in payload. Without meshLocalIid, simply respond any coming DUA.req next with the specified status. + +Only for testing/reference device. + +known status value: + +- 0: ST_DUA_SUCCESS +- 1: ST_DUA_REREGISTER +- 2: ST_DUA_INVALID +- 3: ST_DUA_DUPLICATE +- 4: ST_DUA_NO_RESOURCES +- 5: ST_DUA_BBR_NOT_PRIMARY +- 6: ST_DUA_GENERAL_FAILURE + +```bash +> bbr mgmt dua 1 2f7c235e5025a2fd +Done +``` + ### bbr state Show local Backbone state ([`Disabled`,`Primary`, `Secondary`]) for Thread 1.2 FTD. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 7742da7fc..a4ed75d90 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -514,9 +514,37 @@ void Interpreter::ProcessBackboneRouter(uint8_t aArgsLength, char *aArgs[]) #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE else { - error = ProcessBackboneRouterLocal(aArgsLength, aArgs); +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + if (strcmp(aArgs[0], "mgmt") == 0) + { + unsigned long value; + + VerifyOrExit((aArgsLength == 3 || aArgsLength == 4), error = OT_ERROR_INVALID_ARGS); + + if (strcmp(aArgs[1], "dua") == 0) + { + otIp6InterfaceIdentifier *mlIid = nullptr; + otIp6InterfaceIdentifier iid; + + SuccessOrExit(error = ParseUnsignedLong(aArgs[2], value)); + + if (aArgsLength == 4) + { + VerifyOrExit(Hex2Bin(aArgs[3], iid.mFields.m8, sizeof(iid)) == sizeof(iid), + error = OT_ERROR_INVALID_ARGS); + mlIid = &iid; + } + + otBackboneRouterConfigNextDuaRegistrationResponse(mInstance, mlIid, static_cast(value)); + ExitNow(); + } + } +#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + SuccessOrExit(error = ProcessBackboneRouterLocal(aArgsLength, aArgs)); } -#endif + +exit: +#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE AppendResult(error); } diff --git a/src/core/api/backbone_router_ftd_api.cpp b/src/core/api/backbone_router_ftd_api.cpp index 0be7eef72..cf8d02f86 100644 --- a/src/core/api/backbone_router_ftd_api.cpp +++ b/src/core/api/backbone_router_ftd_api.cpp @@ -111,4 +111,16 @@ otError otBackboneRouterGetDomainPrefix(otInstance *aInstance, otBorderRouterCon *static_cast(aConfig)); } +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE +void otBackboneRouterConfigNextDuaRegistrationResponse(otInstance * aInstance, + const otIp6InterfaceIdentifier *aMlIid, + uint8_t aStatus) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().ConfigNextDuaRegistrationResponse( + static_cast(aMlIid), aStatus); +} +#endif + #endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE diff --git a/src/core/backbone_router/bbr_leader.cpp b/src/core/backbone_router/bbr_leader.cpp index 3240a73bb..da1cf2968 100644 --- a/src/core/backbone_router/bbr_leader.cpp +++ b/src/core/backbone_router/bbr_leader.cpp @@ -227,6 +227,10 @@ void Leader::UpdateBackboneRouterPrimary(void) #if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE Get().HandleBackboneRouterPrimaryUpdate(state, mConfig); #endif + +#if OPENTHREAD_CONFIG_DUA_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + Get().HandleBackboneRouterPrimaryUpdate(state, mConfig); +#endif } void Leader::UpdateDomainPrefixConfig(void) @@ -282,8 +286,8 @@ void Leader::UpdateDomainPrefixConfig(void) Get().UpdateAllDomainBackboneRouters(state); #endif -#if OPENTHREAD_CONFIG_DUA_ENABLE - Get().UpdateDomainUnicastAddress(state); +#if OPENTHREAD_CONFIG_DUA_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + Get().HandleDomainPrefixUpdate(state); #endif } diff --git a/src/core/backbone_router/bbr_leader.hpp b/src/core/backbone_router/bbr_leader.hpp index 5ba177002..e8dda3043 100644 --- a/src/core/backbone_router/bbr_leader.hpp +++ b/src/core/backbone_router/bbr_leader.hpp @@ -40,6 +40,8 @@ #include #include +#include "coap/coap.hpp" +#include "coap/coap_message.hpp" #include "common/locator.hpp" #include "net/ip6_address.hpp" @@ -160,8 +162,8 @@ public: * * @param[in] aAddress A reference to the address. * - * @retval true @p aAddress is a domain unicast address. - * @retval false @p aAddress is not a domain unicast address. + * @retval true @p aAddress is a Domain Unicast Address. + * @retval false @p aAddress is not a Domain Unicast Address. * */ bool IsDomainUnicast(const Ip6::Address &aAddress) const; diff --git a/src/core/backbone_router/bbr_manager.cpp b/src/core/backbone_router/bbr_manager.cpp index 7f3016fb0..e8ccb6f1b 100644 --- a/src/core/backbone_router/bbr_manager.cpp +++ b/src/core/backbone_router/bbr_manager.cpp @@ -42,6 +42,7 @@ #include "common/random.hpp" #include "thread/mle_types.hpp" #include "thread/thread_netif.hpp" +#include "thread/thread_tlvs.hpp" #include "thread/thread_uri_paths.hpp" namespace ot { @@ -52,6 +53,11 @@ Manager::Manager(Instance &aInstance) : InstanceLocator(aInstance) , Notifier::Receiver(aInstance, Manager::HandleNotifierEvents) , mMulticastListenerRegistration(OT_URI_PATH_MLR, Manager::HandleMulticastListenerRegistration, this) + , mDuaRegistration(OT_URI_PATH_DUA_REGISTRATION_REQUEST, Manager::HandleDuaRegistration, this) +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + , mDuaResponseStatus(ThreadStatusTlv::kDuaSuccess) + , mDuaResponseIsSpecified(false) +#endif { } @@ -62,10 +68,12 @@ void Manager::HandleNotifierEvents(Events aEvents) if (Get().GetState() == OT_BACKBONE_ROUTER_STATE_DISABLED) { Get().RemoveResource(mMulticastListenerRegistration); + Get().RemoveResource(mDuaRegistration); } else { Get().AddResource(mMulticastListenerRegistration); + Get().AddResource(mDuaRegistration); } } } @@ -118,6 +126,96 @@ exit: otLogInfoBbr("Sent MLR.rsp (status=%d): %s", aStatus, otThreadErrorToString(error)); } +void Manager::HandleDuaRegistration(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + otError error = OT_ERROR_NONE; + ThreadStatusTlv::DuaStatus status = ThreadStatusTlv::kDuaSuccess; + bool isPrimary = Get().IsPrimary(); + uint32_t lastTransactionTime = 0; + Ip6::Address target; + Ip6::InterfaceIdentifier meshLocalIid; + + VerifyOrExit(aMessage.IsConfirmable() && aMessage.GetCode() == OT_COAP_CODE_POST, error = OT_ERROR_PARSE); + + SuccessOrExit(error = Tlv::FindTlv(aMessage, ThreadTlv::kTarget, &target, sizeof(target))); + SuccessOrExit(error = Tlv::FindTlv(aMessage, ThreadTlv::kMeshLocalEid, &meshLocalIid, sizeof(meshLocalIid))); + +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + if (mDuaResponseIsSpecified && (mDuaResponseTargetMlIid.IsUnspecified() || mDuaResponseTargetMlIid == meshLocalIid)) + { + mDuaResponseIsSpecified = false; + ExitNow(status = mDuaResponseStatus); + } +#endif + + VerifyOrExit(isPrimary, status = ThreadStatusTlv::kDuaNotPrimary); + VerifyOrExit(Get().IsDomainUnicast(target), status = ThreadStatusTlv::kDuaInvalid); + + if (Tlv::FindUint32Tlv(aMessage, ThreadTlv::kLastTransactionTime, lastTransactionTime) == OT_ERROR_NONE) + { + // TODO: (DUA) hasLastTransactionTime = true; + } + + // TODO: (DUA) Add ND-PROXY table management + // TODO: (DUA) Add DAD process + // TODO: (DUA) Extended Address Query + +exit: + + otLogInfoBbr("Received DUA.req on %s: %s", (isPrimary ? "PBBR" : "SBBR"), otThreadErrorToString(error)); + + if (error == OT_ERROR_NONE) + { + SendDuaRegistrationResponse(aMessage, aMessageInfo, target, status); + } +} + +void Manager::SendDuaRegistrationResponse(const Coap::Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + const Ip6::Address & aTarget, + ThreadStatusTlv::DuaStatus 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)); + SuccessOrExit(Tlv::AppendTlv(*message, ThreadTlv::kTarget, &aTarget, sizeof(aTarget))); + + SuccessOrExit(error = Get().SendMessage(*message, aMessageInfo)); + +exit: + if (error != OT_ERROR_NONE && message != nullptr) + { + message->Free(); + } + + otLogInfoBbr("Sent DUA.rsp for DUA %s, status %d %s", aTarget.ToString().AsCString(), aStatus, + otThreadErrorToString(error)); +} + +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE +void Manager::ConfigNextDuaRegistrationResponse(const Ip6::InterfaceIdentifier *aMlIid, uint8_t aStatus) +{ + mDuaResponseIsSpecified = true; + + if (aMlIid) + { + mDuaResponseTargetMlIid = *aMlIid; + } + else + { + mDuaResponseTargetMlIid.Clear(); + } + + mDuaResponseStatus = static_cast(aStatus); +} +#endif + } // namespace BackboneRouter } // namespace ot diff --git a/src/core/backbone_router/bbr_manager.hpp b/src/core/backbone_router/bbr_manager.hpp index 1657c0eec..73308212d 100644 --- a/src/core/backbone_router/bbr_manager.hpp +++ b/src/core/backbone_router/bbr_manager.hpp @@ -64,6 +64,21 @@ public: */ explicit Manager(Instance &aInstance); +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + /** + * This method configures response status for next DUA registration. + * + * Note: available only when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled. + * Only used for test and certification. + * + * @param[in] aMlIid A pointer to the Mesh Local IID. If NULL, respond with @p aStatus for any + * coming DUA.req, otherwise only respond the one with matching @p aMlIid. + * @param[in] aStatus The status to respond. + * + */ + void ConfigNextDuaRegistrationResponse(const Ip6::InterfaceIdentifier *aMlIid, uint8_t aStatus); +#endif + private: static void HandleMulticastListenerRegistration(void * aContext, otMessage * aMessage, @@ -77,6 +92,17 @@ private: const Ip6::MessageInfo & aMessageInfo, ThreadStatusTlv::MlrStatus aStatus); + static void HandleDuaRegistration(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) + { + static_cast(aContext)->HandleDuaRegistration(*static_cast(aMessage), + *static_cast(aMessageInfo)); + } + void HandleDuaRegistration(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + void SendDuaRegistrationResponse(const Coap::Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + const Ip6::Address & aTarget, + ThreadStatusTlv::DuaStatus aStatus); + static void HandleNotifierEvents(Notifier::Receiver &aReceiver, Events aEvents) { static_cast(aReceiver).HandleNotifierEvents(aEvents); @@ -84,6 +110,13 @@ private: void HandleNotifierEvents(Events aEvents); Coap::Resource mMulticastListenerRegistration; + Coap::Resource mDuaRegistration; + +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + Ip6::InterfaceIdentifier mDuaResponseTargetMlIid; + ThreadStatusTlv::DuaStatus mDuaResponseStatus; + bool mDuaResponseIsSpecified : 1; +#endif }; } // namespace BackboneRouter diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index fb60b27cb..e6fc16ae2 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -743,13 +743,6 @@ template <> inline BackboneRouter::Manager &Instance::Get(void) #endif -#if OPENTHREAD_CONFIG_DUA_ENABLE -template <> inline DuaManager &Instance::Get(void) -{ - return mThreadNetif.mDuaManager; -} -#endif - #if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE template <> inline MlrManager &Instance::Get(void) { @@ -757,6 +750,13 @@ template <> inline MlrManager &Instance::Get(void) } #endif +#if OPENTHREAD_CONFIG_DUA_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE +template <> inline DuaManager &Instance::Get(void) +{ + return mThreadNetif.mDuaManager; +} +#endif + #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) #if OPENTHREAD_CONFIG_OTNS_ENABLE diff --git a/src/core/config/tmf.h b/src/core/config/tmf.h index 118564d59..14491b421 100644 --- a/src/core/config/tmf.h +++ b/src/core/config/tmf.h @@ -172,6 +172,16 @@ #define OPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + * + * Define to 1 for Thread 1.2 FTD device to register DUA of its MTD children registered + * even if it doesn't enable DUA feature itself. + * + */ +#define OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE \ + ((OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) && OPENTHREAD_FTD) + /** * * This setting configures the Multicast Listener Registration parent proxing in Thread 1.2. diff --git a/src/core/mac/data_poll_sender.hpp b/src/core/mac/data_poll_sender.hpp index 0aa0403d6..a292ed5a2 100644 --- a/src/core/mac/data_poll_sender.hpp +++ b/src/core/mac/data_poll_sender.hpp @@ -210,7 +210,7 @@ public: * @param[in] aNumFastPolls If non-zero, number of fast polls to send, if zero, default value is used instead. * */ - void SendFastPolls(uint8_t aNumFastPolls); + void SendFastPolls(uint8_t aNumFastPolls = 0); /** * This method asks data poll sender to stop fast polls when the expecting response is received. diff --git a/src/core/thread/dua_manager.cpp b/src/core/thread/dua_manager.cpp index 1b5e1c7a3..ceae3cda7 100644 --- a/src/core/thread/dua_manager.cpp +++ b/src/core/thread/dua_manager.cpp @@ -33,7 +33,7 @@ #include "dua_manager.hpp" -#if (OPENTHREAD_FTD || OPENTHREAD_MTD) && OPENTHREAD_CONFIG_DUA_ENABLE +#if OPENTHREAD_CONFIG_DUA_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE #include "common/code_utils.hpp" #include "common/instance.hpp" @@ -41,42 +41,94 @@ #include "common/logging.hpp" #include "common/settings.hpp" #include "net/ip6_address.hpp" +#include "thread/mle_types.hpp" #include "thread/thread_netif.hpp" +#include "thread/thread_tlvs.hpp" +#include "thread/thread_uri_paths.hpp" #include "utils/slaac_address.hpp" namespace ot { DuaManager::DuaManager(Instance &aInstance) : InstanceLocator(aInstance) + , Notifier::Receiver(aInstance, DuaManager::HandleNotifierEvents) + , mTimer(aInstance, DuaManager::HandleTimer, this) + , mRegistrationTask(aInstance, DuaManager::HandleRegistrationTask, this) + , mDuaNotification(OT_URI_PATH_DUA_REGISTRATION_NOTIFY, &DuaManager::HandleDuaNotification, this) + , mIsDuaPending(false) +#if OPENTHREAD_CONFIG_DUA_ENABLE + , mDuaState(kNotExist) , mDadCounter(0) + , mLastRegistrationTime(0) +#endif +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + , mChildIndexDuaRegistering(0) + , mRegisterCurrentChildIndex(false) +#endif { + mDelay.mValue = 0; + +#if OPENTHREAD_CONFIG_DUA_ENABLE mDomainUnicastAddress.Clear(); mDomainUnicastAddress.mAddressOrigin = OT_ADDRESS_ORIGIN_THREAD; - mDomainUnicastAddress.mPreferred = true; + mDomainUnicastAddress.mPreferred = false; mDomainUnicastAddress.mValid = true; mDomainUnicastAddress.mScopeOverride = Ip6::Address::kGlobalScope; mDomainUnicastAddress.mScopeOverrideValid = true; mFixedDuaInterfaceIdentifier.Clear(); +#endif + +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + mChildDuaMask.Clear(); + mChildDuaRegisteredMask.Clear(); +#endif + + Get().AddResource(mDuaNotification); } -void DuaManager::UpdateDomainUnicastAddress(BackboneRouter::Leader::DomainPrefixState aState) +void DuaManager::HandleDomainPrefixUpdate(BackboneRouter::Leader::DomainPrefixState aState) { - const Ip6::Prefix *prefix; + const Ip6::Prefix *prefix = nullptr; if ((aState == BackboneRouter::Leader::kDomainPrefixRemoved) || (aState == BackboneRouter::Leader::kDomainPrefixRefreshed)) { - Get().RemoveUnicastAddress(mDomainUnicastAddress); + if (mIsDuaPending) + { + IgnoreError(Get().AbortTransaction(&DuaManager::HandleDuaResponse, this)); + } + +#if OPENTHREAD_CONFIG_DUA_ENABLE + RemoveDomainUnicastAddress(); +#endif + +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + if (mChildDuaMask.HasAny()) + { + mChildDuaMask.Clear(); + mChildDuaRegisteredMask.Clear(); + mRegisterCurrentChildIndex = false; + } +#endif } - VerifyOrExit((aState == BackboneRouter::Leader::kDomainPrefixAdded) || - (aState == BackboneRouter::Leader::kDomainPrefixRefreshed), - OT_NOOP); +#if OPENTHREAD_CONFIG_DUA_ENABLE + switch (aState) + { + case BackboneRouter::Leader::kDomainPrefixUnchanged: + // In case removed for some reason e.g. the kDuaInvalid response from PBBR forcely + VerifyOrExit(!Get().HasUnicastAddress(GetDomainUnicastAddress()), OT_NOOP); - prefix = Get().GetDomainPrefix(); - - OT_ASSERT(prefix != nullptr); + // fall through + case BackboneRouter::Leader::kDomainPrefixRefreshed: + case BackboneRouter::Leader::kDomainPrefixAdded: + prefix = Get().GetDomainPrefix(); + OT_ASSERT(prefix != nullptr); + break; + default: + ExitNow(); + } mDomainUnicastAddress.mPrefixLength = prefix->GetLength(); mDomainUnicastAddress.GetAddress().Clear(); @@ -92,12 +144,14 @@ void DuaManager::UpdateDomainUnicastAddress(BackboneRouter::Leader::DomainPrefix SuccessOrExit(GenerateDomainUnicastAddressIid()); } - Get().AddUnicastAddress(mDomainUnicastAddress); + AddDomainUnicastAddress(); +#endif exit: return; } +#if OPENTHREAD_CONFIG_DUA_ENABLE otError DuaManager::GenerateDomainUnicastAddressIid(void) { otError error; @@ -133,9 +187,9 @@ otError DuaManager::SetFixedDuaInterfaceIdentifier(const Ip6::InterfaceIdentifie if (Get().HasUnicastAddress(GetDomainUnicastAddress())) { - Get().RemoveUnicastAddress(mDomainUnicastAddress); + RemoveDomainUnicastAddress(); mDomainUnicastAddress.GetAddress().SetIid(mFixedDuaInterfaceIdentifier); - Get().AddUnicastAddress(mDomainUnicastAddress); + AddDomainUnicastAddress(); } exit: @@ -150,11 +204,11 @@ void DuaManager::ClearFixedDuaInterfaceIdentifier(void) if (GetDomainUnicastAddress().GetIid() == mFixedDuaInterfaceIdentifier && Get().HasUnicastAddress(GetDomainUnicastAddress())) { - Get().RemoveUnicastAddress(mDomainUnicastAddress); + RemoveDomainUnicastAddress(); if (GenerateDomainUnicastAddressIid() == OT_ERROR_NONE) { - Get().AddUnicastAddress(mDomainUnicastAddress); + AddDomainUnicastAddress(); } } @@ -184,6 +238,500 @@ otError DuaManager::Store(void) return Get().SaveDadInfo(dadInfo); } +void DuaManager::AddDomainUnicastAddress(void) +{ + mDuaState = kToRegister; + mLastRegistrationTime = TimerMilli::GetNow(); + Get().AddUnicastAddress(mDomainUnicastAddress); +} + +void DuaManager::RemoveDomainUnicastAddress(void) +{ + if (mDuaState == kRegistering && mIsDuaPending) + { + IgnoreError(Get().AbortTransaction(&DuaManager::HandleDuaResponse, this)); + } + + mDuaState = kNotExist; + mDomainUnicastAddress.mPreferred = false; + Get().RemoveUnicastAddress(mDomainUnicastAddress); +} + +void DuaManager::UpdateRegistrationDelay(uint8_t aDelay) +{ + if (mDelay.mFields.mRegistrationDelay == 0 || mDelay.mFields.mRegistrationDelay > aDelay) + { + mDelay.mFields.mRegistrationDelay = aDelay; + + otLogDebgDua("update regdelay %d", mDelay.mFields.mRegistrationDelay); + ScheduleTimer(); + } +} +#endif + +void DuaManager::UpdateReregistrationDelay(void) +{ + uint16_t delay = 0; + otBackboneRouterConfig config; + + VerifyOrExit(Get().GetConfig(config) == OT_ERROR_NONE, OT_NOOP); + + delay = config.mReregistrationDelay > 1 ? Random::NonCrypto::GetUint16InRange(1, config.mReregistrationDelay) : 1; + + if (mDelay.mFields.mReregistrationDelay == 0 || mDelay.mFields.mReregistrationDelay > delay) + { + mDelay.mFields.mReregistrationDelay = delay; + ScheduleTimer(); + otLogDebgDua("update reregdelay %d", mDelay.mFields.mReregistrationDelay); + } + +exit: + return; +} + +void DuaManager::UpdateCheckDelay(uint8_t aDelay) +{ + if (mDelay.mFields.mCheckDelay == 0 || mDelay.mFields.mCheckDelay > aDelay) + { + mDelay.mFields.mCheckDelay = aDelay; + + otLogDebgDua("update checkdelay %d", mDelay.mFields.mCheckDelay); + ScheduleTimer(); + } +} + +void DuaManager::HandleNotifierEvents(Events aEvents) +{ + Mle::MleRouter &mle = Get(); + + if (aEvents.Contains(kEventThreadRoleChanged)) + { + if (mle.HasRestored()) + { + UpdateReregistrationDelay(); + } +#if OPENTHREAD_CONFIG_DUA_ENABLE && OPENTHREAD_FTD + else if (mle.IsRouter()) + { + // Wait for link establishment with neighboring routers. + UpdateRegistrationDelay(kNewRouterRegistrationDelay); + } + else if (mle.IsExpectedToBecomeRouter()) + { + // Will check again in case the device decides to stay REED when jitter timeout expires. + UpdateRegistrationDelay(mle.GetRouterSelectionJitterTimeout() + 1); + } +#endif + } + + if (aEvents.ContainsAny(kEventIp6AddressAdded)) + { + mRegistrationTask.Post(); + } +} + +void DuaManager::HandleBackboneRouterPrimaryUpdate(BackboneRouter::Leader::State aState, + const BackboneRouter::BackboneRouterConfig &aConfig) +{ + OT_UNUSED_VARIABLE(aConfig); + + if (aState == BackboneRouter::Leader::kStateAdded || aState == BackboneRouter::Leader::kStateToTriggerRereg) + { + UpdateReregistrationDelay(); + } +} + +void DuaManager::HandleTimer(void) +{ + bool attempt = false; + + otLogDebgDua("regdelay %d, reregdelay %d, checkdelay %d", mDelay.mFields.mRegistrationDelay, + mDelay.mFields.mReregistrationDelay, mDelay.mFields.mCheckDelay); + +#if OPENTHREAD_CONFIG_DUA_ENABLE + if (mDuaState != kNotExist && TimerMilli::GetNow() > mLastRegistrationTime + Mle::kDuaDadPeriod) + { + mDomainUnicastAddress.mPreferred = true; + } + + if ((mDelay.mFields.mRegistrationDelay > 0) && (--mDelay.mFields.mRegistrationDelay == 0)) + { + attempt = true; + } +#endif + + if ((mDelay.mFields.mCheckDelay > 0) && (--mDelay.mFields.mCheckDelay == 0)) + { + attempt = true; + } + + if ((mDelay.mFields.mReregistrationDelay > 0) && (--mDelay.mFields.mReregistrationDelay == 0)) + { +#if OPENTHREAD_CONFIG_DUA_ENABLE + if (mDuaState != kNotExist) + { + mDuaState = kToRegister; + } +#endif + +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + mChildDuaRegisteredMask.Clear(); +#endif + attempt = true; + } + + if (attempt) + { + mRegistrationTask.Post(); + } + + ScheduleTimer(); +} + +void DuaManager::ScheduleTimer(void) +{ + if (mDelay.mValue == 0) + { + mTimer.Stop(); + } + else if (!mTimer.IsRunning()) + { + mTimer.Start(kStateUpdatePeriod); + } +} + +void DuaManager::PerformNextRegistration(void) +{ + otError error = OT_ERROR_NONE; + Mle::MleRouter & mle = Get(); + Coap::Message * message = nullptr; + Ip6::MessageInfo messageInfo; + Ip6::Address dua; + + VerifyOrExit(mle.IsAttached(), error = OT_ERROR_INVALID_STATE); + VerifyOrExit(Get().HasPrimary(), error = OT_ERROR_INVALID_STATE); + + // Only allow one outgoing DUA.req + VerifyOrExit(!mIsDuaPending, error = OT_ERROR_BUSY); + + // Only send DUA.req when necessary +#if OPENTHREAD_CONFIG_DUA_ENABLE +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + VerifyOrExit(mle.IsRouterOrLeader() || !mle.IsExpectedToBecomeRouter(), error = OT_ERROR_INVALID_STATE); + VerifyOrExit((mDuaState == kToRegister && mDelay.mFields.mRegistrationDelay == 0) || + (mChildDuaMask.HasAny() && mChildDuaMask != mChildDuaRegisteredMask), + error = OT_ERROR_NOT_FOUND); +#else + VerifyOrExit(mDuaState == kToRegister && mDelay.mFields.mRegistrationDelay == 0, error = OT_ERROR_NOT_FOUND); +#endif // OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + + VerifyOrExit(mle.IsFullThreadDevice() || mle.GetParent().IsThreadVersion1p1(), error = OT_ERROR_INVALID_STATE); +#endif // OPENTHREAD_CONFIG_DUA_ENABLE + + // Prepare DUA.req + VerifyOrExit((message = Get().NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS); + + SuccessOrExit(error = + message->Init(OT_COAP_TYPE_CONFIRMABLE, OT_COAP_CODE_POST, OT_URI_PATH_DUA_REGISTRATION_REQUEST)); + SuccessOrExit(error = message->SetPayloadMarker()); + +#if OPENTHREAD_CONFIG_DUA_ENABLE + if (mDuaState == kToRegister && mDelay.mFields.mRegistrationDelay == 0) + { + dua = GetDomainUnicastAddress(); + SuccessOrExit(error = Tlv::AppendTlv(*message, ThreadTlv::kTarget, &dua, sizeof(dua))); + SuccessOrExit(error = Tlv::AppendTlv(*message, ThreadTlv::kMeshLocalEid, &mle.GetMeshLocal64().GetIid(), + sizeof(Ip6::InterfaceIdentifier))); + mDuaState = kRegistering; + mLastRegistrationTime = TimerMilli::GetNow(); + } + else +#endif // OPENTHREAD_CONFIG_DUA_ENABLE + { +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + uint32_t lastTransactionTime; + const Ip6::Address *duaPtr = nullptr; + Child * child = nullptr; + + if (!mRegisterCurrentChildIndex) + { + for (Child &iter : Get().Iterate(Child::kInStateValid)) + { + uint16_t childIndex = Get().GetChildIndex(iter); + + if (mChildDuaMask.Get(childIndex) && !mChildDuaRegisteredMask.Get(childIndex)) + { + mChildIndexDuaRegistering = childIndex; + break; + } + } + } + + child = Get().GetChildAtIndex(mChildIndexDuaRegistering); + duaPtr = child->GetDomainUnicastAddress(); + + OT_ASSERT(duaPtr != nullptr); + + dua = *duaPtr; + SuccessOrExit(error = Tlv::AppendTlv(*message, ThreadTlv::kTarget, &dua, sizeof(dua))); + SuccessOrExit(error = Tlv::AppendTlv(*message, ThreadTlv::kMeshLocalEid, &child->GetMeshLocalIid(), + sizeof(Ip6::InterfaceIdentifier))); + + lastTransactionTime = Time::MsecToSec(TimerMilli::GetNow() - child->GetLastHeard()); + SuccessOrExit(error = Tlv::AppendUint32Tlv(*message, ThreadTlv::kLastTransactionTime, lastTransactionTime)); +#endif // OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + } + + 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(Get().GetMeshLocal16()); + + SuccessOrExit(error = Get().SendMessage(*message, messageInfo, &DuaManager::HandleDuaResponse, this)); + + mIsDuaPending = true; + + // TODO: (DUA) need update when CSL is enabled. + if (!Get().IsRxOnWhenIdle()) + { + Get().SendFastPolls(); + } + +exit: + if (error == OT_ERROR_NO_BUFS) + { + UpdateCheckDelay(Mle::kNoBufDelay); + } + + if (error != OT_ERROR_NONE && message != nullptr) + { + message->Free(); + } + + otLogInfoDua("Sent DUA.req for DUA %s: %s", dua.ToString().AsCString(), otThreadErrorToString(error)); +} + +void DuaManager::HandleDuaResponse(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, otError aResult) +{ + OT_UNUSED_VARIABLE(aMessageInfo); + otError error; + + mIsDuaPending = false; + + if (aResult == OT_ERROR_RESPONSE_TIMEOUT) + { + UpdateCheckDelay(Mle::KResponseTimeoutDelay); + ExitNow(error = aResult); + } + + VerifyOrExit(aResult == OT_ERROR_NONE && aMessage.GetCode() == OT_COAP_CODE_CHANGED, error = OT_ERROR_PARSE); + + error = ProcessDuaResponse(aMessage); + +exit: + if (error != OT_ERROR_RESPONSE_TIMEOUT) + { + mRegistrationTask.Post(); + } + + otLogInfoDua("Received DUA.req: %s", otThreadErrorToString(error)); +} + +void DuaManager::HandleDuaNotification(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + OT_UNUSED_VARIABLE(aMessageInfo); + otError error; + + OT_UNUSED_VARIABLE(error); + + VerifyOrExit(aMessage.GetCode() == OT_COAP_CODE_POST, error = OT_ERROR_PARSE); + + if (aMessage.IsConfirmable() && Get().SendEmptyAck(aMessage, aMessageInfo) == OT_ERROR_NONE) + { + otLogInfoDua("Sent DUA.ntf acknowledgment"); + } + + error = ProcessDuaResponse(aMessage); + +exit: + otLogInfoDua("Received DUA.ntf: %d", otThreadErrorToString(error)); +} + +otError DuaManager::ProcessDuaResponse(Coap::Message &aMessage) +{ + otError error = OT_ERROR_NONE; + Ip6::Address target; + uint8_t status; + + SuccessOrExit(error = Tlv::FindUint8Tlv(aMessage, ThreadTlv::kStatus, status)); + SuccessOrExit(error = Tlv::FindTlv(aMessage, ThreadTlv::kTarget, &target, sizeof(target))); + +#if OPENTHREAD_CONFIG_DUA_ENABLE + if (Get().HasUnicastAddress(target)) + { + switch (static_cast(status)) + { + case ThreadStatusTlv::kDuaSuccess: + mLastRegistrationTime = TimerMilli::GetNow(); + mDuaState = kRegistered; + break; + case ThreadStatusTlv::kDuaReRegister: + mDuaState = kToRegister; + mDelay.mFields.mCheckDelay = Mle::kImmediateReRegisterDelay; + break; + case ThreadStatusTlv::kDuaInvalid: + // Domain Prefix might be invalid. + RemoveDomainUnicastAddress(); + break; + case ThreadStatusTlv::kDuaDuplicate: + RemoveDomainUnicastAddress(); + mDadCounter++; + + if (GenerateDomainUnicastAddressIid() == OT_ERROR_NONE) + { + AddDomainUnicastAddress(); + } + + break; + case ThreadStatusTlv::kDuaNoResources: + case ThreadStatusTlv::kDuaNotPrimary: + case ThreadStatusTlv::kDuaGeneralFailure: + UpdateReregistrationDelay(); + break; + } + } + else +#endif +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + { + Child *child = Get().GetChildAtIndex(mChildIndexDuaRegistering); + + VerifyOrExit(child != NULL, error = OT_ERROR_NOT_FOUND); + VerifyOrExit(child->HasIp6Address(target), error = OT_ERROR_NOT_FOUND); + + mRegisterCurrentChildIndex = false; + + switch (status) + { + case ThreadStatusTlv::kDuaSuccess: + // Mark as Registered + mChildDuaRegisteredMask.Set(mChildIndexDuaRegistering, true); + break; + case ThreadStatusTlv::kDuaReRegister: + mRegisterCurrentChildIndex = true; + mDelay.mFields.mCheckDelay = Mle::kImmediateReRegisterDelay; + break; + case ThreadStatusTlv::kDuaInvalid: + case ThreadStatusTlv::kDuaDuplicate: + SendAddressNotification(target, static_cast(status), *child); + IgnoreError(child->RemoveIp6Address(target)); + mChildDuaMask.Set(mChildIndexDuaRegistering, false); + mChildDuaRegisteredMask.Set(mChildIndexDuaRegistering, false); + break; + case ThreadStatusTlv::kDuaNoResources: + case ThreadStatusTlv::kDuaNotPrimary: + case ThreadStatusTlv::kDuaGeneralFailure: + UpdateReregistrationDelay(); + break; + } + } +#endif // OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + +exit: + ScheduleTimer(); + return error; +} + +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE +void DuaManager::SendAddressNotification(Ip6::Address & aAddress, + ThreadStatusTlv::DuaStatus aStatus, + const Child & aChild) +{ + Coap::Message * message = nullptr; + Ip6::MessageInfo messageInfo; + otError error; + + VerifyOrExit((message = Get().NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS); + + SuccessOrExit(error = + message->Init(OT_COAP_TYPE_CONFIRMABLE, OT_COAP_CODE_POST, OT_URI_PATH_DUA_REGISTRATION_NOTIFY)); + SuccessOrExit(error = message->SetPayloadMarker()); + + SuccessOrExit(error = Tlv::AppendUint8Tlv(*message, ThreadTlv::kStatus, static_cast(aStatus))); + SuccessOrExit(error = Tlv::AppendTlv(*message, ThreadTlv::kTarget, &aAddress, sizeof(aAddress))); + + messageInfo.GetPeerAddr().SetToRoutingLocator(Get().GetMeshLocalPrefix(), aChild.GetRloc16()); + messageInfo.SetPeerPort(kCoapUdpPort); + messageInfo.SetSockAddr(Get().GetMeshLocal16()); + + SuccessOrExit(error = Get().SendMessage(*message, messageInfo)); + + otLogInfoDua("Sent ADDR_NTF for child %04x DUA %s", aChild.GetRloc16(), aAddress.ToString().AsCString()); + +exit: + if (error != OT_ERROR_NONE) + { + // TODO: (DUA) (P4) may enhance to guarantee the delivery of DUA.ntf + otLogWarnDua("Sent ADDR_NTF for child %04x DUA %s Error %s", aChild.GetRloc16(), + aAddress.ToString().AsCString(), otThreadErrorToString(error)); + + if (message != NULL) + { + message->Free(); + } + } +} + +void DuaManager::UpdateChildDomainUnicastAddress(const Child &aChild, Mle::ChildDuaState aState) +{ + uint16_t childIndex = Get().GetChildIndex(aChild); + + if ((aState == Mle::ChildDuaState::kRemoved || aState == Mle::ChildDuaState::kChanged) && + mChildDuaMask.Get(childIndex)) + { + // Abort on going proxy DUA.req for this child +#if OPENTHREAD_CONFIG_DUA_ENABLE + if (mIsDuaPending && mDuaState != DuaState::kRegistering && mChildIndexDuaRegistering == childIndex) +#else + if (mIsDuaPending && mChildIndexDuaRegistering == childIndex) +#endif + { + IgnoreError(Get().AbortTransaction(&DuaManager::HandleDuaResponse, this)); + + // Reset mRegisterCurrentChildIndex properly + mRegisterCurrentChildIndex = mRegisterCurrentChildIndex && (aState == Mle::ChildDuaState::kRemoved); + } + + mChildDuaMask.Set(childIndex, false); + mChildDuaRegisteredMask.Set(childIndex, false); + } + + if (aState == Mle::ChildDuaState::kAdded || aState == Mle::ChildDuaState::kChanged) + { + if (mChildDuaMask == mChildDuaRegisteredMask) + { + UpdateCheckDelay(Random::NonCrypto::GetUint8InRange(1, Mle::kParentAggregateDelay)); + } + + mChildDuaMask.Set(childIndex, true); + mChildDuaRegisteredMask.Set(childIndex, false); + } + + return; +} +#endif // OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + } // namespace ot -#endif // (OPENTHREAD_FTD || OPENTHREAD_MTD) && OPENTHREAD_CONFIG_DUA_ENABLE +#endif // OPENTHREAD_CONFIG_DUA_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE diff --git a/src/core/thread/dua_manager.hpp b/src/core/thread/dua_manager.hpp index a0fb863ea..e6aa689fd 100644 --- a/src/core/thread/dua_manager.hpp +++ b/src/core/thread/dua_manager.hpp @@ -36,11 +36,19 @@ #include "openthread-core-config.h" -#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) +#if OPENTHREAD_CONFIG_DUA_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE #include "backbone_router/bbr_leader.hpp" +#include "coap/coap.hpp" +#include "coap/coap_message.hpp" #include "common/locator.hpp" +#include "common/notifier.hpp" +#include "common/tasklet.hpp" +#include "common/time.hpp" +#include "common/timer.hpp" #include "net/netif.hpp" +#include "thread/thread_tlvs.hpp" +#include "thread/topology.hpp" namespace ot { @@ -62,7 +70,7 @@ namespace ot { * This class implements managing DUA. * */ -class DuaManager : public InstanceLocator +class DuaManager : public InstanceLocator, public Notifier::Receiver { public: /** @@ -74,12 +82,24 @@ public: explicit DuaManager(Instance &aInstance); /** - * This method updates Domain Unicast Address. + * This method notifies Domain Prefix status. * * @param[in] aState The Domain Prefix state or state change. * */ - void UpdateDomainUnicastAddress(BackboneRouter::Leader::DomainPrefixState aState); + void HandleDomainPrefixUpdate(BackboneRouter::Leader::DomainPrefixState aState); + + /** + * 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); + +#if OPENTHREAD_CONFIG_DUA_ENABLE /** * This method returns a reference to the Domain Unicast Address. @@ -129,17 +149,118 @@ public: * */ void Restore(void); +#endif + +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + void UpdateChildDomainUnicastAddress(const Child &aChild, Mle::ChildDuaState aState); +#endif private: + enum + { + kNewRouterRegistrationDelay = 5, ///< Delay (in seconds) for waiting link establishment for a new Router. + kStateUpdatePeriod = 1000, ///< 1000ms period (i.e. 1s) + }; + +#if OPENTHREAD_CONFIG_DUA_ENABLE otError GenerateDomainUnicastAddressIid(void); otError Store(void); + void AddDomainUnicastAddress(void); + void RemoveDomainUnicastAddress(void); + void UpdateRegistrationDelay(uint8_t aDelay); +#endif + +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + void SendAddressNotification(Ip6::Address &aAddress, ThreadStatusTlv::DuaStatus aStatus, const Child &aChild); +#endif + + static void HandleNotifierEvents(Notifier::Receiver &aReceiver, Events aEvents) + { + static_cast(aReceiver).HandleNotifierEvents(aEvents); + } + + void HandleNotifierEvents(Events aEvents); + + static void HandleTimer(Timer &aTimer) { aTimer.GetOwner().HandleTimer(); } + + void HandleTimer(void); + + static void HandleRegistrationTask(Tasklet &aTasklet) { aTasklet.GetOwner().PerformNextRegistration(); } + + void ScheduleTimer(void); + + static void HandleDuaResponse(void * aContext, + otMessage * aMessage, + const otMessageInfo *aMessageInfo, + otError aResult) + { + static_cast(aContext)->HandleDuaResponse( + *static_cast(aMessage), *static_cast(aMessageInfo), aResult); + } + + void HandleDuaResponse(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, otError aResult); + + static void HandleDuaNotification(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) + { + static_cast(aContext)->HandleDuaNotification( + *static_cast(aMessage), *static_cast(aMessageInfo)); + } + + void HandleDuaNotification(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + otError ProcessDuaResponse(Coap::Message &aMessage); + + void PerformNextRegistration(void); + void UpdateReregistrationDelay(void); + void UpdateCheckDelay(uint8_t aDelay); + + TimerMilli mTimer; + Tasklet mRegistrationTask; + Coap::Resource mDuaNotification; + + bool mIsDuaPending : 1; + +#if OPENTHREAD_CONFIG_DUA_ENABLE + enum DuaState + { + kNotExist, ///< DUA is not avaiable. + kToRegister, ///< DUA is to be registered. + kRegistering, ///< DUA is being registered. + kRegistered, ///< DUA is registered. + }; + + DuaState mDuaState : 2; + uint8_t mDadCounter; + TimeMilli mLastRegistrationTime; ///< The time (in milliseconds) when sent last DUA.req or received DUA.rsp. Ip6::InterfaceIdentifier mFixedDuaInterfaceIdentifier; Ip6::NetifUnicastAddress mDomainUnicastAddress; - uint8_t mDadCounter; +#endif + + union + { + struct + { + uint16_t mReregistrationDelay; ///< Delay (in seconds) for DUA re-registration. + uint8_t mCheckDelay; ///< Delay (in seconds) for checking whether or not registration is required. +#if OPENTHREAD_CONFIG_DUA_ENABLE + uint8_t mRegistrationDelay; ///< Delay (in seconds) for DUA registration. +#endif + } mFields; + uint32_t mValue; ///< Non-zero indicates timer should start. + } mDelay; + +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + // TODO: (DUA) may re-evaluate the alternative option of distributing the flags into the child table: + // - Child class itself have some padding - may save some RAM + // - Avoid cross reference between a bit-vector and the child entry + ChildMask mChildDuaMask; ///< Child Mask for child who registers DUA via Child Update Request. + ChildMask mChildDuaRegisteredMask; ///< Child Mask for child's DUA that was registered by the parent on behalf. + uint16_t mChildIndexDuaRegistering : 15; ///< Child Index of the DUA being registered. + bool mRegisterCurrentChildIndex : 1; ///< Re-register the child just registered. +#endif }; } // namespace ot -#endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) +#endif // OPENTHREAD_CONFIG_DUA_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE #endif // DUA_MANAGER_HPP_ diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index ead540fdb..5d000b8c8 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -2039,6 +2039,11 @@ otError MleRouter::UpdateChildAddresses(const Message &aMessage, uint16_t aOffse uint8_t storedCount = 0; uint16_t offset = 0; uint16_t end = 0; +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + Ip6::Address oldDua; + const Ip6::Address *oldDuaPtr = nullptr; + bool hasDua = false; +#endif #if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE Ip6::Address oldMlrRegisteredAddresses[OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD - 1]; @@ -2051,6 +2056,13 @@ otError MleRouter::UpdateChildAddresses(const Message &aMessage, uint16_t aOffse offset = aOffset + sizeof(tlv); end = offset + tlv.GetLength(); +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + if ((oldDuaPtr = aChild.GetDomainUnicastAddress()) != nullptr) + { + oldDua = *oldDuaPtr; + } +#endif + #if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE // Retrieve registered multicast addresses of the Child if (aChild.HasAnyMlrRegisteredAddress()) @@ -2096,6 +2108,25 @@ otError MleRouter::UpdateChildAddresses(const Message &aMessage, uint16_t aOffse address.Clear(); address.SetPrefix(context.mPrefix); address.SetIid(entry.GetIid()); + +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + if (Get().IsDomainUnicast(address)) + { + hasDua = true; + + if (oldDuaPtr != nullptr) + { + if (oldDua != address) + { + Get().UpdateChildDomainUnicastAddress(aChild, ChildDuaState::kChanged); + } + } + else + { + Get().UpdateChildDomainUnicastAddress(aChild, ChildDuaState::kAdded); + } + } +#endif } else { @@ -2157,6 +2188,13 @@ otError MleRouter::UpdateChildAddresses(const Message &aMessage, uint16_t aOffse // Clear EID-to-RLOC cache for the unicast address registered by the child. Get().Remove(address); } +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + // Dua is removed + if (oldDuaPtr != nullptr && !hasDua) + { + Get().UpdateChildDomainUnicastAddress(aChild, ChildDuaState::kRemoved); + } +#endif #if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE Get().UpdateProxiedSubscriptions(aChild, oldMlrRegisteredAddresses, oldMlrRegisteredAddressNum); @@ -4771,6 +4809,9 @@ void MleRouter::Signal(otNeighborTableEvent aEvent, Neighbor &aNeighbor) case OT_NEIGHBOR_TABLE_EVENT_CHILD_REMOVED: Get().Signal(kEventThreadChildRemoved); +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + Get().UpdateChildDomainUnicastAddress(static_cast(aNeighbor), ChildDuaState::kRemoved); +#endif break; default: diff --git a/src/core/thread/mle_types.hpp b/src/core/thread/mle_types.hpp index 866a1940a..e12c67be6 100644 --- a/src/core/thread/mle_types.hpp +++ b/src/core/thread/mle_types.hpp @@ -257,11 +257,16 @@ enum */ enum { - kRegistrationDelayDefault = 1200, //< In seconds. - kMlrTimeoutDefault = 3600, //< In seconds. - kMlrTimeoutMin = 300, //< In seconds. - kBackboneRouterRegistrationJitter = 5, //< In seconds. - kParentAggregateDelay = 5, //< In seconds. + kRegistrationDelayDefault = 1200, ///< In seconds. + kMlrTimeoutDefault = 3600, ///< In seconds. + kMlrTimeoutMin = 300, ///< In seconds. + kBackboneRouterRegistrationJitter = 5, ///< In seconds. + kParentAggregateDelay = 5, ///< In seconds. + kNoBufDelay = 5, ///< In seconds. + kImmediateReRegisterDelay = 1, ///< In seconds. + KResponseTimeoutDelay = 30, ///< In seconds. + kDuaDadPeriod = 100, ///< In seconds. Time period after which the address + ///< becomes "Preferred" if no duplicate address error. }; static_assert(kMlrTimeoutDefault >= kMlrTimeoutMin, @@ -269,6 +274,17 @@ static_assert(kMlrTimeoutDefault >= kMlrTimeoutMin, static_assert(Mle::kParentAggregateDelay > 1, "kParentAggregateDelay should be larger than 1 second"); +/** + * State change of Child's DUA + * + */ +enum class ChildDuaState : uint8_t +{ + kAdded, ///< A new DUA registered by the Child via Address Registration. + kChanged, ///< A different DUA registered by the Child via Address Registration. + kRemoved, ///< DUA registered by the Child is removed and not in Address Registration. +}; + #endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 /** diff --git a/src/core/thread/thread_netif.cpp b/src/core/thread/thread_netif.cpp index f02864e73..3ab80dfd5 100644 --- a/src/core/thread/thread_netif.cpp +++ b/src/core/thread/thread_netif.cpp @@ -111,12 +111,13 @@ ThreadNetif::ThreadNetif(Instance &aInstance) , mBackboneRouterLocal(aInstance) , mBackboneRouterManager(aInstance) #endif -#if OPENTHREAD_CONFIG_DUA_ENABLE - , mDuaManager(aInstance) -#endif #if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE , mMlrManager(aInstance) #endif + +#if OPENTHREAD_CONFIG_DUA_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + , mDuaManager(aInstance) +#endif , mChildSupervisor(aInstance) , mSupervisionListener(aInstance) , mAnnounceBegin(aInstance) diff --git a/src/core/thread/thread_netif.hpp b/src/core/thread/thread_netif.hpp index 9cdb50f2b..8737f7044 100644 --- a/src/core/thread/thread_netif.hpp +++ b/src/core/thread/thread_netif.hpp @@ -55,14 +55,14 @@ #include "backbone_router/bbr_manager.hpp" #endif -#if OPENTHREAD_CONFIG_DUA_ENABLE -#include "thread/dua_manager.hpp" -#endif - #if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE #include "thread/mlr_manager.hpp" #endif +#if OPENTHREAD_CONFIG_DUA_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE +#include "thread/dua_manager.hpp" +#endif + #include "meshcop/dataset_manager.hpp" #if OPENTHREAD_CONFIG_JOINER_ENABLE @@ -261,11 +261,11 @@ private: BackboneRouter::Local mBackboneRouterLocal; BackboneRouter::Manager mBackboneRouterManager; #endif -#if OPENTHREAD_CONFIG_DUA_ENABLE - DuaManager mDuaManager; -#endif #if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE MlrManager mMlrManager; +#endif +#if OPENTHREAD_CONFIG_DUA_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + DuaManager mDuaManager; #endif Utils::ChildSupervisor mChildSupervisor; Utils::SupervisionListener mSupervisionListener; diff --git a/src/core/thread/thread_tlvs.hpp b/src/core/thread/thread_tlvs.hpp index 709e382b9..1b69f27b9 100644 --- a/src/core/thread/thread_tlvs.hpp +++ b/src/core/thread/thread_tlvs.hpp @@ -135,6 +135,21 @@ public: kMlrBbrNotPrimary = 5, ///< BBR is not Primary at this moment. kMlrGeneralFailure = 6, ///< Reason(s) for failure are not further specified. }; + + /** + * Domain Unicast Address (DUA) Registration Status values + * + */ + enum DuaStatus : uint8_t + { + kDuaSuccess = 0, ///< Successful registration. + kDuaReRegister = 1, ///< Registration was accepted but immediate reregistration is required to solve. + kDuaInvalid = 2, ///< Registration rejected (Fatal): Target EID is not a valid DUA. + kDuaDuplicate = 3, ///< Registration rejected (Fatal): DUA is already in use by another device. + kDuaNoResources = 4, ///< Registration rejected (Non-fatal): Backbone Router Resource shortage. + kDuaNotPrimary = 5, ///< Registration rejected (Non-fatal): Backbone Router is not primary at this moment. + kDuaGeneralFailure = 6, ///< Registration failure (Non-fatal): Reason(s) not futher specified. + }; }; /** diff --git a/src/core/thread/thread_uri_paths.hpp b/src/core/thread/thread_uri_paths.hpp index 10958206e..a2dd2c088 100644 --- a/src/core/thread/thread_uri_paths.hpp +++ b/src/core/thread/thread_uri_paths.hpp @@ -300,6 +300,39 @@ namespace ot { */ #define OT_URI_PATH_MLR "n/mr" +/** + * @def OT_URI_PATH_DUA_REGISTRATION_REQUEST + * + * The URI Path for Domain Unicast Address Registration Request (DUA.req). + * + */ +#define OT_URI_PATH_DUA_REGISTRATION_REQUEST "n/dr" + +/** + * @def OT_URI_PATH_DUA_REGISTRATION_NOTIFY + * + * The URI Path for Domain Unicast Address Registration Notification (DUA.ntf). + * + */ +#define OT_URI_PATH_DUA_REGISTRATION_NOTIFY "n/dn" + +/** + * @def OT_URI_PATH_BACKBONE_QUERY + * + * The URI Path for Backbone Query (BB.qry). + * + */ +#define OT_URI_PATH_BACKBONE_QUERY "b/bq" + +/** + * @def OT_URI_PATH_BACKBONE_ANSWER + * + * The URI Path for Backbone Answer with destination to a link-local unicast address (BB.ans), + * or Proactive Backbone Notification with destination to a link-local multicast address (PRO_BB.ntf). + * + */ +#define OT_URI_PATH_BACKBONE_ANSWER "b/ba" + } // namespace ot #endif // THREAD_URIS_HPP_ diff --git a/src/core/thread/topology.cpp b/src/core/thread/topology.cpp index e8791cefe..032ec392a 100644 --- a/src/core/thread/topology.cpp +++ b/src/core/thread/topology.cpp @@ -273,6 +273,26 @@ exit: return retval; } +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE +const Ip6::Address *Child::GetDomainUnicastAddress(void) const +{ + const Ip6::Address *addr = nullptr; + + for (const Ip6::Address &ip6Address : mIp6Address) + { + VerifyOrExit(!ip6Address.IsUnspecified(), OT_NOOP); + + if (Get().IsDomainUnicast(ip6Address)) + { + ExitNow(addr = &ip6Address); + } + } + +exit: + return addr; +} +#endif + void Child::GenerateChallenge(void) { IgnoreError(Random::Crypto::FillBuffer(mAttachChallenge, sizeof(mAttachChallenge))); diff --git a/src/core/thread/topology.hpp b/src/core/thread/topology.hpp index ee5b9cfbb..d24d69591 100644 --- a/src/core/thread/topology.hpp +++ b/src/core/thread/topology.hpp @@ -759,6 +759,16 @@ public: */ bool HasIp6Address(const Ip6::Address &aAddress) const; +#if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE + /** + * This method retrieves the Domain Unicast Address registered by the child. + * + * @returns A pointer to Domain Unicast Address registered by the child if there is. + * + */ + const Ip6::Address *GetDomainUnicastAddress(void) const; +#endif + /** * This method gets the child timeout. * diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index a406ee9be..0652be58a 100644 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -491,6 +491,8 @@ def create_default_uri_path_based_payload_factories(): '/d/dq': network_diag_tlvs_factory, '/d/dr': network_diag_tlvs_factory, '/n/mr': network_layer_tlvs_factory, + '/n/dr': network_layer_tlvs_factory, + '/n/dn': network_layer_tlvs_factory, } diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 6573fcad5..e4e0351c3 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -522,6 +522,13 @@ class Node: self.remove_prefix(prefix) self.register_netdata() + def set_next_dua_response(self, status, iid=None): + cmd = 'bbr mgmt dua {}'.format(status) + if iid is not None: + cmd += ' ' + str(iid) + self.send_command(cmd) + self._expect('Done') + def set_dua_iid(self, iid): cmd = 'dua iid {}'.format(iid) self.send_command(cmd) @@ -1481,6 +1488,11 @@ class Node: def udp_check_rx(self, bytes_should_rx): self._expect('%d bytes' % bytes_should_rx) + def set_routereligible(self, enable: bool): + cmd = f'routereligible {"enable" if enable else "disable"}' + self.send_command(cmd) + self._expect('Done') + def router_list(self): cmd = 'router list' self.send_command(cmd) diff --git a/tests/scripts/thread-cert/v1_2_test_domain_unicast_address.py b/tests/scripts/thread-cert/v1_2_test_domain_unicast_address.py index 85eba96eb..aa175adb3 100755 --- a/tests/scripts/thread-cert/v1_2_test_domain_unicast_address.py +++ b/tests/scripts/thread-cert/v1_2_test_domain_unicast_address.py @@ -136,7 +136,7 @@ class TestDomainUnicastAddress(thread_cert.TestCase): return ''.join(ipaddress.ip_address(address).exploded.split(':')[4:]) def __check_dua_registration(self, node, iid, dp_cid): - ''' Check whether or not the specified domain unicast address is registered in Address + ''' Check whether or not the specified Domain Unicast Address is registered in Address Registraion TLV. Args: @@ -311,6 +311,7 @@ class TestDomainUnicastAddress(thread_cert.TestCase): self.simulator.go(WAIT_TIME) WAIT_TIME = MED_TIMEOUT + self.simulator.go(WAIT_TIME) dua = self.nodes[MED_1_2].get_addr(config.DOMAIN_PREFIX) assert dua, 'Error: Expected DUA not found' assert ipaddress.ip_address(med_1_2_dua) == ipaddress.ip_address( diff --git a/tests/scripts/thread-cert/v1_2_test_domain_unicast_address_registration.py b/tests/scripts/thread-cert/v1_2_test_domain_unicast_address_registration.py new file mode 100755 index 000000000..98826b971 --- /dev/null +++ b/tests/scripts/thread-cert/v1_2_test_domain_unicast_address_registration.py @@ -0,0 +1,379 @@ +#!/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 unittest + +import command +import config +import ipaddress +import mle +import thread_cert + +BBR_1 = 1 # Collapsed with Leader Role +ROUTER_1_1 = 2 +ROUTER_1_2 = 3 + +FED_1_2_1 = 4 +MED_1_2_1 = 5 +SED_1_2_1 = 6 + +FED_1_2_2 = 7 +MED_1_2_2 = 8 +SED_1_2_2 = 9 + +WAIT_ATTACH = 5 +WAIT_REDUNDANCE = 3 +ROUTER_SELECTION_JITTER = 1 +BBR_REGISTRATION_JITTER = 5 +SED_POLL_PERIOD = 2000 # 2s +MED_TIMEOUT = 20 # 20s +PARENT_AGGREGATE_DELAY = 5 # 5s + +DUA_IID_MANUAL1 = '4444333322221111' + +ST_DUA_SUCCESS = 0 +ST_DUA_REREGISTER = 1 +ST_DUA_INVALID = 2 +ST_DUA_DUPLICATE = 3 +ST_DUA_NO_RESOURCES = 4 +ST_DUA_BBR_NOT_PRIMARY = 5 +ST_DUA_GENERAL_FAILURE = 6 + +BBR_REREGISTRATION_DELAY = 10 +""" + Topology + + + MED_1_2_1 SED_1_2_1 + \ | + \ | +FED_1_2_1 --- ROUTER_1_1 FED_1_2_2 MED_1_2_2 + | | / + | | / + BBR_1 (LEADER) --- ROUTER_1_2 --- SED_1_2_2 + + + 1) Bring up BBR_1, BBR_1 becomes Leader and Primary Backbone Router, with Domain + Prefix without `P_slaac`. + + 2) Test behaviors of ROUTER_1_2 under various response status: + a) Bring up ROUTER_1_2 with DUA_IID_MANUAL1, one DUA.req should happen to register DUA. + b) Remove DUA_IID_MANUAL1, one DUA.req should happen for the new generated DUA via SLAAC. + c) Configure BBR_1 to respond with the fatal error ST_DUA_INVALID, update BBR_1 with + BBR_REREGISTRATION_DELAY, ROUTER_1_2 should re-register its DUA within BBR_REREGISTRATION_DELAY. + - ROUTER_1_2 should remove its dua + - update network data, ROUTER_1_2 would regenerate and register the same dua + d) Configure BBR_1 to respond with the fatal error ST_DUA_DUPLICATE, update seqno to trigger reregistration. + After received DUA.rsp with ST_DUA_DUPLICATE, ROUTER_1_2 should + - increase dad counter + - regenerate a new DUA + - send DUA.req + e) (repeated) Configure BBR_1 to respond with per remaining error status: + - increase BBR seqno to trigger reregistration + - ROUTER_1_2 should re-register within BBR_REREGISTRATION_DELAY. For the not fatal errors, ROUTER_1_2 + should re-register within another BBR_REREGISTRATION_DELAY (with least delay if ST_DUA_REREGISTER) + 3) Bring up FED_1_2_1, MED_1_2_1, SED_1_2_1, they should send DUA.req by themselves as the parent + is of Thread 1.1 version. + 4) Bring up FED_1_2_2, it sends DUA.req itself as it it FTD. + 5) MED_1_2_2, SED_1_2_2, MTDs should should register their DUA to their parent + by Child Update Request, and the parent would send DUA.req for them on behalf. + 6) Increase seqno on BBR_1, within BBR_REREGISTRATION_DELAY, there should be one DUA.req from + per [FED_1_2_1, MED_1_2_1, SED_1_2_1, FED_1_2_2], and 3 DUA.req from ROUTER_1_2 among which + 2 DUA.req are for its MTD children. + +""" + + +class TestDomainUnicastAddressRegistration(thread_cert.TestCase): + TOPOLOGY = { + BBR_1: { + 'version': '1.2', + 'whitelist': [ROUTER_1_1, ROUTER_1_2], + 'is_bbr': True + }, + ROUTER_1_1: { + 'version': '1.1', + 'whitelist': [BBR_1, FED_1_2_1, MED_1_2_1, SED_1_2_1] + }, + ROUTER_1_2: { + 'version': '1.2', + 'whitelist': [BBR_1, FED_1_2_2, MED_1_2_2, SED_1_2_2] + }, + FED_1_2_1: { + 'version': '1.2', + 'whitelist': [ROUTER_1_1], + }, + MED_1_2_1: { + 'mode': 'rsn', + 'version': '1.2', + 'whitelist': [ROUTER_1_1], + }, + SED_1_2_1: { + 'mode': 'sn', + 'version': '1.2', + 'whitelist': [ROUTER_1_1], + }, + FED_1_2_2: { + 'version': '1.2', + 'whitelist': [ROUTER_1_2], + }, + MED_1_2_2: { + 'mode': 'rsn', + 'version': '1.2', + 'whitelist': [ROUTER_1_2], + }, + SED_1_2_2: { + 'mode': 'sn', + 'version': '1.2', + 'whitelist': [ROUTER_1_2], + }, + } + """All nodes are created with default configurations""" + + def __get_iid(self, address): + ''' Get the interface identifier of an IPv6 address. + + Args: + address (string): An IPv6 address; + ''' + return ''.join(ipaddress.ip_address(address).exploded.split(':')[4:]) + + def __check_dua_registration_tmf(self, node, occurences=1): + + messages = self.simulator.get_messages_sent_by(node) + for i in range(occurences): + msg = messages.next_coap_message('0.02', '/n/dr', False) + assert msg, 'Expected {}, but {}th not found\n node: {}(extaddr: {})'.format( + occurences, i + 1, node, self.nodes[node].get_addr64()) + + def test(self): + # starting context id + context_id = 1 + seq_num = 1 + + # 1) Bring up BBR_1, BBR_1 becomes Leader and Primary Backbone Router, with Domain + # Prefix without `P_slaac`. + 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=seq_num, reg_delay=BBR_REREGISTRATION_DELAY) + 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') + assert self.nodes[BBR_1].has_ipmaddr(config.ALL_NETWORK_BBRS_ADDRESS) + assert not self.nodes[BBR_1].has_ipmaddr(config.ALL_DOMAIN_BBRS_ADDRESS) + + self.nodes[BBR_1].set_domain_prefix(config.DOMAIN_PREFIX, 'prosD') + WAIT_TIME = WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + assert self.nodes[BBR_1].has_ipmaddr(config.ALL_DOMAIN_BBRS_ADDRESS) + + self.simulator.set_lowpan_context(context_id, config.DOMAIN_PREFIX) + domain_prefix_cid = context_id + + # 2) Test behaviors of ROUTER_1_2 under various response status: + # a) Bring up ROUTER_1_2 with DUA_IID_MANUAL1, one DUA.req should happen to register DUA. + + # Flush relative message queues. + self.flush_nodes([ROUTER_1_2]) + + self.nodes[ROUTER_1_2].set_dua_iid(DUA_IID_MANUAL1) + self.nodes[ROUTER_1_2].set_router_selection_jitter( + ROUTER_SELECTION_JITTER) + self.nodes[ROUTER_1_2].start() + WAIT_TIME = WAIT_ATTACH + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[ROUTER_1_2].get_state(), 'router') + + mliid = self.__get_iid(self.nodes[ROUTER_1_2].get_mleid()) + + WAIT_TIME = WAIT_ATTACH + WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + self.__check_dua_registration_tmf(ROUTER_1_2) + + # b) Remove DUA_IID_MANUAL1, one DUA.req should happen for the new generated DUA via SLAAC. + + # Flush relative message queues. + self.flush_nodes([ROUTER_1_2]) + self.nodes[ROUTER_1_2].clear_dua_iid() + WAIT_TIME = WAIT_ATTACH + WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + self.__check_dua_registration_tmf(ROUTER_1_2) + + #c) Configure BBR_1 to respond with the fatal error ST_DUA_INVALID, update BBR_1 with + # BBR_REREGISTRATION_DELAY, ROUTER_1_2 should re-register its DUA within BBR_REREGISTRATION_DELAY. + # - ROUTER_1_2 should remove its dua + # - update network data, ROUTER_1_2 would regenerate and register the same dua + + # Flush relative message queues. + self.flush_nodes([ROUTER_1_2]) + seq_num = seq_num + 1 + self.nodes[BBR_1].set_next_dua_response(ST_DUA_INVALID, mliid) + self.nodes[BBR_1].set_backbone_router(seqno=seq_num) + WAIT_TIME = BBR_REREGISTRATION_DELAY + WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + + self.__check_dua_registration_tmf(ROUTER_1_2, 1) + dua = self.nodes[ROUTER_1_2].get_addr(config.DOMAIN_PREFIX) + assert not dua, 'Error: Unexpected DUA ({}) found'.format(dua) + + # Retry after new network data is available + seq_num = seq_num + 1 + dua = self.nodes[ROUTER_1_2].get_addr(config.DOMAIN_PREFIX) + self.nodes[BBR_1].set_backbone_router(seqno=seq_num) + WAIT_TIME = BBR_REREGISTRATION_DELAY + WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + + self.__check_dua_registration_tmf(ROUTER_1_2, 1) + dua = self.nodes[ROUTER_1_2].get_addr(config.DOMAIN_PREFIX) + assert dua, 'Error: Expected DUA ({}) not found'.format(dua) + + #d) Configure BBR_1 to respond with the fatal error ST_DUA_DUPLICATE, update seqno to trigger reregistration. + # After received DUA.rsp with ST_DUA_DUPLICATE, ROUTER_1_2 should + # - increase dad counter + # - regenerate a new DUA + # - send DUA.req + + # Flush relative message queues. + self.flush_nodes([ROUTER_1_2]) + seq_num = seq_num + 1 + self.nodes[BBR_1].set_next_dua_response(ST_DUA_DUPLICATE, mliid) + self.nodes[BBR_1].set_backbone_router(seqno=seq_num) + WAIT_TIME = BBR_REREGISTRATION_DELAY * 2 + WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + + self.__check_dua_registration_tmf(ROUTER_1_2, 2) + + dua2 = self.nodes[ROUTER_1_2].get_addr(config.DOMAIN_PREFIX) + assert dua2, 'Error: Expected DUA ({}) not found'.format(dua2) + assert dua2 != dua, 'Error: Expected Different DUA not found, same DUA {}'.format( + dua2) + + # e) (repeated) Configure BBR_1 to respond with per remaining error status: + # - increase BBR seqno to trigger reregistration + # - ROUTER_1_2 should re-register within BBR_REREGISTRATION_DELAY. For the not fatal errors, ROUTER_1_2 + # should re-register within another BBR_REREGISTRATION_DELAY (with least delay if ST_DUA_REREGISTER) + for status in [ + ST_DUA_REREGISTER, ST_DUA_NO_RESOURCES, ST_DUA_BBR_NOT_PRIMARY, + ST_DUA_GENERAL_FAILURE + ]: + print('Testing Status %d...'.format(status)) + # Flush relative message queues. + self.flush_nodes([ROUTER_1_2]) + seq_num = seq_num + 1 + self.nodes[BBR_1].set_next_dua_response(status, mliid) + self.nodes[BBR_1].set_backbone_router(seqno=seq_num) + WAIT_TIME = BBR_REREGISTRATION_DELAY + WAIT_REDUNDANCE + if status != ST_DUA_REREGISTER: + WAIT_TIME += BBR_REREGISTRATION_DELAY + + self.simulator.go(WAIT_TIME) + + self.__check_dua_registration_tmf(ROUTER_1_2, 2) + + # 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 + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[ROUTER_1_1].get_state(), 'router') + + dua = self.nodes[ROUTER_1_1].get_addr(config.DOMAIN_PREFIX) + assert not dua, 'Error: Unexpected DUA ({}) found'.format(dua) + + # Configure children + for node in [FED_1_2_1, FED_1_2_2]: + self.nodes[node].set_routereligible(False) + + for node in [SED_1_2_1, SED_1_2_2]: + self.nodes[node].set_pollperiod(SED_POLL_PERIOD) + + for node in [MED_1_2_1, MED_1_2_2]: + self.nodes[node].set_timeout(MED_TIMEOUT) + + # 3) Bring up FED_1_2_1, MED_1_2_1, SED_1_2_1, they should send DUA.req by themselves as the parent + # is of Thread 1.1 version. + # 4) Bring up FED_1_2_2, it sends DUA.req itself as it it FTD. + for node in [FED_1_2_1, MED_1_2_1, SED_1_2_1, FED_1_2_2]: + print("Starting child {} (extaddr: {})...".format( + node, self.nodes[node].get_addr64())) + # Flush all message queues. + self.flush_all() + self.nodes[node].start() + WAIT_TIME = WAIT_ATTACH + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[node].get_state(), 'child') + WAIT_TIME = PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + self.__check_dua_registration_tmf(node) + + # 5) MED_1_2_2, SED_1_2_2, MTDs should should register their DUA to their parent + # by Child Update Request, and the parent would send DUA.req for them on behalf. + for node in [MED_1_2_2, SED_1_2_2]: + print("Starting child {} (extaddr: {})...".format( + node, self.nodes[node].get_addr64())) + # Flush all message queues. + self.flush_all() + self.nodes[node].start() + WAIT_TIME = WAIT_ATTACH + self.simulator.go(WAIT_TIME) + self.assertEqual(self.nodes[node].get_state(), 'child') + + WAIT_TIME = PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE + print("waiting {}".format(WAIT_TIME)) + self.simulator.go(WAIT_TIME) + self.__check_dua_registration_tmf(ROUTER_1_2, 1) + + # 6) Increase seqno on BBR_1, within BBR_REREGISTRATION_DELAY, there should be one DUA.req from + # per [FED_1_2_1, MED_1_2_1, SED_1_2_1, FED_1_2_2], and 3 DUA.req from ROUTER_1_2 among which + # 2 DUA.req are for its MTD children. + + # Flush all message queues. + self.flush_all() + seq_num = seq_num + 1 + self.nodes[BBR_1].set_backbone_router(seqno=seq_num) + WAIT_TIME = BBR_REREGISTRATION_DELAY + WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + WAIT_TIME = BBR_REREGISTRATION_DELAY + WAIT_REDUNDANCE + self.simulator.go(WAIT_TIME) + for node in [FED_1_2_1, MED_1_2_1, SED_1_2_1, FED_1_2_2]: + self.__check_dua_registration_tmf(node) + + self.__check_dua_registration_tmf(ROUTER_1_2, 3) + + +if __name__ == '__main__': + unittest.main()