From 903f67721534d44a0f9dd57c0b8f348963531760 Mon Sep 17 00:00:00 2001 From: Simon Lin Date: Sat, 31 Oct 2020 02:18:05 +0800 Subject: [PATCH] [dua] implement Backbone query and answer (#5664) This commit implements DUA features of Thread 1.2 DUA that involves sending and processing Backbone query and answer: - Extend address query to the Backbone Link - Send BB.qry for Thread address query - Send ADDR_ANS.ntf for BB.ans - DAD process - PBBR to send BB.qry on the Backbone link (3 times if not answered) - PBBR to receive and handle BB.qry and sends BB.ans - PBBR to receive BB.ans and sends ADDR_ERR.ntf if duplicated - PRO_BB.ntf - Send PRO_BB.ntf when DUA registration is updated - Handle PRO_BB.ntf - Add test_dua_dad.py to test the DAD process when it was successful or duplicated - Verify the normal DAD process - Verify Address Query can be extended to the Backbone link - Verify DAD duplicate is handled correctly - Verify PRO_BB.ntf for duplicated DUA is handled correctly --- .github/workflows/simulation-1.2.yml | 2 +- include/openthread/instance.h | 2 +- include/openthread/thread.h | 19 ++ src/cli/cli.cpp | 16 + src/core/api/thread_ftd_api.cpp | 15 + src/core/backbone_router/bbr_manager.cpp | 284 +++++++++++++++- src/core/backbone_router/bbr_manager.hpp | 63 +++- src/core/backbone_router/ndproxy_table.cpp | 80 ++++- src/core/backbone_router/ndproxy_table.hpp | 128 +++++-- src/core/thread/address_resolver.cpp | 23 ++ src/core/thread/address_resolver.hpp | 15 +- src/core/thread/mle_types.hpp | 9 +- src/core/thread/thread_tlvs.hpp | 1 + src/core/thread/uri_paths.hpp | 2 +- .../thread-cert/backbone/bbr_5_11_01.py | 8 +- .../thread-cert/backbone/test_dua_dad.py | 317 ++++++++++++++++++ tests/scripts/thread-cert/config.py | 1 + tests/scripts/thread-cert/node.py | 10 +- .../thread-cert/pktverify/layer_fields.py | 7 +- tests/scripts/thread-cert/pktverify/packet.py | 4 + .../thread-cert/pktverify/packet_filter.py | 25 +- .../thread-cert/pktverify/packet_verifier.py | 218 +++--------- tests/scripts/thread-cert/pktverify/utils.py | 2 +- tests/scripts/thread-cert/run_bbr_tests.py | 2 +- tests/scripts/thread-cert/thread_cert.py | 5 + 25 files changed, 1028 insertions(+), 230 deletions(-) create mode 100644 tests/scripts/thread-cert/backbone/test_dua_dad.py diff --git a/.github/workflows/simulation-1.2.yml b/.github/workflows/simulation-1.2.yml index 29ef2bac6..4003db7d5 100644 --- a/.github/workflows/simulation-1.2.yml +++ b/.github/workflows/simulation-1.2.yml @@ -248,7 +248,7 @@ jobs: THREAD_VERSION: 1.2 INTER_OP: 1 COVERAGE: 1 - MULTIPLY: 3 + MULTIPLY: 1 PYTHONUNBUFFERED: 1 VERBOSE: 1 OTBR_COMMIT: "3cf6ff1126dca96803252514691f40d0554046bf" # Date: Mon Oct 26 15:27:04 2020 +0800 diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 92c6cc747..2da7e3aaf 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 (43) +#define OPENTHREAD_API_VERSION (44) /** * @addtogroup api-instance diff --git a/include/openthread/thread.h b/include/openthread/thread.h index 9d0f9fc6b..389de0150 100644 --- a/include/openthread/thread.h +++ b/include/openthread/thread.h @@ -851,6 +851,25 @@ void otThreadSendAddressNotification(otInstance * aInstance, otIp6Address * aTarget, otIp6InterfaceIdentifier *aMlIid); +/** + * This function sends a Proactive Backbone Notification (PRO_BB.ntf) message on the Backbone link. + * + * This function is only available when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aTarget The target address of the PRO_BB.ntf message. + * @param[in] aMlIid The ML-IID of the PRO_BB.ntf message. + * @param[in] aTimeSinceLastTransaction Time since last transaction (in seconds). + * + * @retval OT_ERROR_NONE Successfully sent PRO_BB.ntf on backbone link. + * @retval OT_ERROR_NO_BUFS If insufficient message buffers available. + * + */ +otError otThreadSendProactiveBackboneNotification(otInstance * aInstance, + otIp6Address * aTarget, + otIp6InterfaceIdentifier *aMlIid, + uint32_t aTimeSinceLastTransaction); + /** * @} * diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 3ae860970..a7278ac21 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -1542,6 +1542,22 @@ otError Interpreter::ProcessFake(uint8_t aArgsLength, char *aArgs[]) SuccessOrExit(error = ParseAsHexString(aArgs[3], mlIid.mFields.m8)); otThreadSendAddressNotification(mInstance, &destination, &target, &mlIid); } +#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + else if (strcmp(aArgs[0], "/b/ba") == 0) + { + otIp6Address target; + otIp6InterfaceIdentifier mlIid; + uint32_t timeSinceLastTransaction; + + VerifyOrExit(aArgsLength == 4, error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit(error = ParseAsIp6Address(aArgs[1], target)); + SuccessOrExit(error = ParseAsHexString(aArgs[2], mlIid.mFields.m8)); + SuccessOrExit(error = ParseAsUint32(aArgs[3], timeSinceLastTransaction)); + + error = otThreadSendProactiveBackboneNotification(mInstance, &target, &mlIid, timeSinceLastTransaction); + } +#endif exit: return error; } diff --git a/src/core/api/thread_ftd_api.cpp b/src/core/api/thread_ftd_api.cpp index 3bde808f1..57424f885 100644 --- a/src/core/api/thread_ftd_api.cpp +++ b/src/core/api/thread_ftd_api.cpp @@ -37,6 +37,7 @@ #include +#include "backbone_router/bbr_manager.hpp" #include "common/instance.hpp" #include "common/locator-getters.hpp" #include "thread/mle_types.hpp" @@ -404,6 +405,20 @@ void otThreadSendAddressNotification(otInstance * aInstance, static_cast(*aMlIid), nullptr, static_cast(*aDestination)); } + +#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE +otError otThreadSendProactiveBackboneNotification(otInstance * aInstance, + otIp6Address * aTarget, + otIp6InterfaceIdentifier *aMlIid, + uint32_t aTimeSinceLastTransaction) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().SendProactiveBackboneNotification( + static_cast(*aTarget), static_cast(*aMlIid), + aTimeSinceLastTransaction); +} +#endif #endif #endif // OPENTHREAD_FTD diff --git a/src/core/backbone_router/bbr_manager.cpp b/src/core/backbone_router/bbr_manager.cpp index 1d8da4b5d..3056d323f 100644 --- a/src/core/backbone_router/bbr_manager.cpp +++ b/src/core/backbone_router/bbr_manager.cpp @@ -53,6 +53,8 @@ Manager::Manager(Instance &aInstance) : InstanceLocator(aInstance) , mMulticastListenerRegistration(UriPath::kMlr, Manager::HandleMulticastListenerRegistration, this) , mDuaRegistration(UriPath::kDuaRegistrationRequest, Manager::HandleDuaRegistration, this) + , mBackboneQuery(UriPath::kBackboneQuery, Manager::HandleBackboneQuery, this) + , mBackboneAnswer(UriPath::kBackboneAnswer, Manager::HandleBackboneAnswer, this) , mNdProxyTable(aInstance) , mMulticastListenersTable(aInstance) , mTimer(aInstance, Manager::HandleTimer, this) @@ -64,6 +66,8 @@ Manager::Manager(Instance &aInstance) , mMlrResponseIsSpecified(false) #endif { + mBackboneTmfAgent.AddResource(mBackboneQuery); + mBackboneTmfAgent.AddResource(mBackboneAnswer); } void Manager::HandleNotifierEvents(Events aEvents) @@ -116,6 +120,7 @@ void Manager::HandleNotifierEvents(Events aEvents) void Manager::HandleTimer(void) { mMulticastListenersTable.Expire(); + mNdProxyTable.HandleTimer(); mTimer.Start(kTimerInterval); } @@ -327,7 +332,7 @@ void Manager::SendBackboneMulticastListenerRegistration(const Ip6::Address *aAdd messageInfo.SetHopLimit(Mle::kDefaultBackboneHoplimit); messageInfo.SetIsHostInterface(true); - SuccessOrExit(error = backboneTmf.SendMessage(*message, messageInfo, nullptr, nullptr)); + SuccessOrExit(error = backboneTmf.SendMessage(*message, messageInfo)); exit: FreeMessageOnError(message, error); @@ -393,9 +398,6 @@ void Manager::HandleDuaRegistration(const Coap::Message &aMessage, const Ip6::Me break; } - // TODO: (DUA) Add DAD process - // TODO: (DUA) Extended Address Query - exit: otLogInfoBbr("Received DUA.req on %s: %s", (isPrimary ? "PBBR" : "SBBR"), otThreadErrorToString(error)); @@ -488,6 +490,280 @@ exit: return forwardToBackbone; } +otError Manager::SendBackboneQuery(const Ip6::Address &aDua, uint16_t aRloc16) +{ + otError error = OT_ERROR_NONE; + Coap::Message * message = nullptr; + Ip6::MessageInfo messageInfo; + + VerifyOrExit(Get().IsPrimary(), error = OT_ERROR_INVALID_STATE); + + VerifyOrExit((message = mBackboneTmfAgent.NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS); + + SuccessOrExit(error = message->InitAsNonConfirmablePost(UriPath::kBackboneQuery)); + SuccessOrExit(error = message->SetPayloadMarker()); + + SuccessOrExit(error = ThreadTlv::AppendTlv(*message, ThreadTlv::kTarget, &aDua, sizeof(aDua))); + + if (aRloc16 != Mac::kShortAddrInvalid) + { + SuccessOrExit(error = ThreadTlv::AppendUint16Tlv(*message, ThreadTlv::kRloc16, aRloc16)); + } + + messageInfo.SetPeerAddr(Get().GetAllDomainBackboneRoutersAddress()); + messageInfo.SetPeerPort(BackboneRouter::kBackboneUdpPort); + + messageInfo.SetHopLimit(Mle::kDefaultBackboneHoplimit); + messageInfo.SetIsHostInterface(true); + + error = mBackboneTmfAgent.SendMessage(*message, messageInfo); + +exit: + otLogInfoBbr("SendBackboneQuery for %s (rloc16=%04x): %s", aDua.ToString().AsCString(), aRloc16, + otThreadErrorToString(error)); + FreeMessageOnError(message, error); + return error; +} + +void Manager::HandleBackboneQuery(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) +{ + static_cast(aContext)->HandleBackboneQuery(*static_cast(aMessage), + *static_cast(aMessageInfo)); +} + +void Manager::HandleBackboneQuery(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + otError error = OT_ERROR_NONE; + Ip6::Address dua; + uint16_t rloc16 = Mac::kShortAddrInvalid; + NdProxyTable::NdProxy *ndProxy; + + VerifyOrExit(aMessageInfo.IsHostInterface(), error = OT_ERROR_DROP); + + VerifyOrExit(Get().IsPrimary(), error = OT_ERROR_INVALID_STATE); + VerifyOrExit(aMessage.IsNonConfirmablePostRequest(), error = OT_ERROR_PARSE); + + SuccessOrExit(error = ThreadTlv::FindTlv(aMessage, ThreadTlv::kTarget, &dua, sizeof(dua))); + + error = ThreadTlv::FindUint16Tlv(aMessage, ThreadTlv::kRloc16, rloc16); + VerifyOrExit(error == OT_ERROR_NONE || error == OT_ERROR_NOT_FOUND); + + otLogInfoBbr("Received BB.qry from %s for %s (rloc16=%04x)", aMessageInfo.GetPeerAddr().ToString().AsCString(), + dua.ToString().AsCString(), rloc16); + + ndProxy = mNdProxyTable.ResolveDua(dua); + VerifyOrExit(ndProxy != nullptr && !ndProxy->GetDadFlag(), error = OT_ERROR_NOT_FOUND); + + error = SendBackboneAnswer(aMessageInfo, dua, rloc16, *ndProxy); + +exit: + otLogInfoBbr("HandleBackboneQuery: %s", otThreadErrorToString(error)); +} + +void Manager::HandleBackboneAnswer(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) +{ + static_cast(aContext)->HandleBackboneAnswer(*static_cast(aMessage), + *static_cast(aMessageInfo)); +} + +void Manager::HandleBackboneAnswer(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + otError error = OT_ERROR_NONE; + bool proactive; + Ip6::Address dua; + Ip6::InterfaceIdentifier meshLocalIid; + uint16_t networkNameOffset, networkNameLength; + uint32_t timeSinceLastTransaction; + uint16_t srcRloc16 = Mac::kShortAddrInvalid; + + VerifyOrExit(aMessageInfo.IsHostInterface(), error = OT_ERROR_DROP); + + VerifyOrExit(Get().IsPrimary(), error = OT_ERROR_INVALID_STATE); + VerifyOrExit(aMessage.IsPostRequest(), error = OT_ERROR_PARSE); + + proactive = !aMessage.IsConfirmable(); + + SuccessOrExit(error = ThreadTlv::FindTlv(aMessage, ThreadTlv::kTarget, &dua, sizeof(dua))); + SuccessOrExit(error = ThreadTlv::FindTlv(aMessage, ThreadTlv::kMeshLocalEid, &meshLocalIid, sizeof(meshLocalIid))); + SuccessOrExit(error = + ThreadTlv::FindUint32Tlv(aMessage, ThreadTlv::kLastTransactionTime, timeSinceLastTransaction)); + + SuccessOrExit( + error = ThreadTlv::FindTlvValueOffset(aMessage, ThreadTlv::kNetworkName, networkNameOffset, networkNameLength)); + + error = ThreadTlv::FindUint16Tlv(aMessage, ThreadTlv::kRloc16, srcRloc16); + VerifyOrExit(error == OT_ERROR_NONE || error == OT_ERROR_NOT_FOUND); + + if (proactive) + { + HandleProactiveBackboneNotification(dua, meshLocalIid, timeSinceLastTransaction); + } + else if (srcRloc16 == Mac::kShortAddrInvalid) + { + HandleDadBackboneAnswer(dua, meshLocalIid); + } + else + { + HandleExtendedBackboneAnswer(dua, meshLocalIid, timeSinceLastTransaction, srcRloc16); + } + + SuccessOrExit(error = mBackboneTmfAgent.SendEmptyAck(aMessage, aMessageInfo)); + +exit: + otLogInfoBbr("HandleBackboneAnswer: %s", otThreadErrorToString(error)); +} + +otError Manager::SendProactiveBackboneNotification(const Ip6::Address & aDua, + const Ip6::InterfaceIdentifier &aMeshLocalIid, + uint32_t aTimeSinceLastTransaction) +{ + return SendBackboneAnswer(Get().GetAllDomainBackboneRoutersAddress(), + BackboneRouter::kBackboneUdpPort, aDua, aMeshLocalIid, aTimeSinceLastTransaction, + Mac::kShortAddrInvalid); +} + +otError Manager::SendBackboneAnswer(const Ip6::MessageInfo & aQueryMessageInfo, + const Ip6::Address & aDua, + uint16_t aSrcRloc16, + const NdProxyTable::NdProxy &aNdProxy) +{ + return SendBackboneAnswer(aQueryMessageInfo.GetPeerAddr(), aQueryMessageInfo.GetPeerPort(), aDua, + aNdProxy.GetMeshLocalIid(), aNdProxy.GetTimeSinceLastTransaction(), aSrcRloc16); +} + +otError Manager::SendBackboneAnswer(const Ip6::Address & aDstAddr, + uint16_t aDstPort, + const Ip6::Address & aDua, + const Ip6::InterfaceIdentifier &aMeshLocalIid, + uint32_t aTimeSinceLastTransaction, + uint16_t aSrcRloc16) +{ + otError error = OT_ERROR_NONE; + Coap::Message * message = nullptr; + Ip6::MessageInfo messageInfo; + bool proactive = aDstAddr.IsMulticast(); + + VerifyOrExit((message = mBackboneTmfAgent.NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS); + + SuccessOrExit(error = message->Init(proactive ? Coap::kTypeNonConfirmable : Coap::kTypeConfirmable, Coap::kCodePost, + UriPath::kBackboneAnswer)); + SuccessOrExit(error = message->SetPayloadMarker()); + + SuccessOrExit(error = ThreadTlv::AppendTlv(*message, ThreadTlv::kTarget, &aDua, sizeof(aDua))); + + SuccessOrExit(error = + ThreadTlv::AppendTlv(*message, ThreadTlv::kMeshLocalEid, &aMeshLocalIid, sizeof(aMeshLocalIid))); + + SuccessOrExit(error = + ThreadTlv::AppendUint32Tlv(*message, ThreadTlv::kLastTransactionTime, aTimeSinceLastTransaction)); + + { + const Mac::NameData nameData = Get().GetNetworkName().GetAsData(); + + SuccessOrExit(error = ThreadTlv::AppendTlv(*message, ThreadTlv::kNetworkName, nameData.GetBuffer(), + nameData.GetLength())); + } + + if (aSrcRloc16 != Mac::kShortAddrInvalid) + { + SuccessOrExit(ThreadTlv::AppendUint16Tlv(*message, ThreadTlv::kRloc16, aSrcRloc16)); + } + + messageInfo.SetPeerAddr(aDstAddr); + messageInfo.SetPeerPort(aDstPort); + + messageInfo.SetHopLimit(Mle::kDefaultBackboneHoplimit); + messageInfo.SetIsHostInterface(true); + + error = mBackboneTmfAgent.SendMessage(*message, messageInfo); + +exit: + otLogInfoBbr("Send %s for %s (rloc16=%04x): %s", proactive ? "PRO_BB.ntf" : "BB.ans", aDua.ToString().AsCString(), + aSrcRloc16, otThreadErrorToString(error)); + + FreeMessageOnError(message, error); + return error; +} + +void Manager::HandleDadBackboneAnswer(const Ip6::Address &aDua, const Ip6::InterfaceIdentifier &aMeshLocalIid) +{ + otError error = OT_ERROR_NONE; + NdProxyTable::NdProxy *ndProxy = mNdProxyTable.ResolveDua(aDua); + bool duplicate = false; + + OT_UNUSED_VARIABLE(error); + + VerifyOrExit(ndProxy != nullptr, error = OT_ERROR_NOT_FOUND); + + duplicate = ndProxy->GetMeshLocalIid() != aMeshLocalIid; + + if (duplicate) + { + Ip6::Address dest; + + dest.SetToRoutingLocator(Get().GetMeshLocalPrefix(), ndProxy->GetRloc16()); + Get().SendAddressError(aDua, aMeshLocalIid, &dest); + } + + ot::BackboneRouter::NdProxyTable::NotifyDadComplete(*ndProxy, duplicate); + +exit: + otLogInfoBbr("HandleDadBackboneAnswer: %s, target=%s, mliid=%s, duplicate=%s", otThreadErrorToString(error), + aDua.ToString().AsCString(), aMeshLocalIid.ToString().AsCString(), duplicate ? "Y" : "N"); +} + +void Manager::HandleExtendedBackboneAnswer(const Ip6::Address & aDua, + const Ip6::InterfaceIdentifier &aMeshLocalIid, + uint32_t aTimeSinceLastTransaction, + uint16_t aSrcRloc16) +{ + Ip6::Address dest; + + dest.SetToRoutingLocator(Get().GetMeshLocalPrefix(), aSrcRloc16); + Get().SendAddressQueryResponse(aDua, aMeshLocalIid, &aTimeSinceLastTransaction, dest); + + otLogInfoBbr("HandleExtendedBackboneAnswer: target=%s, mliid=%s, LTT=%lds, rloc16=%04x", + aDua.ToString().AsCString(), aMeshLocalIid.ToString().AsCString(), aTimeSinceLastTransaction, + aSrcRloc16); +} + +void Manager::HandleProactiveBackboneNotification(const Ip6::Address & aDua, + const Ip6::InterfaceIdentifier &aMeshLocalIid, + uint32_t aTimeSinceLastTransaction) +{ + otError error = OT_ERROR_NONE; + NdProxyTable::NdProxy *ndProxy = mNdProxyTable.ResolveDua(aDua); + + OT_UNUSED_VARIABLE(error); + + VerifyOrExit(ndProxy != nullptr, error = OT_ERROR_NOT_FOUND); + + if (ndProxy->GetMeshLocalIid() == aMeshLocalIid) + { + uint32_t localTimeSinceLastTransaction = ndProxy->GetTimeSinceLastTransaction(); + + if (aTimeSinceLastTransaction <= localTimeSinceLastTransaction) + { + BackboneRouter::NdProxyTable::Erase(*ndProxy); + } + else + { + IgnoreError(SendProactiveBackboneNotification(aDua, ndProxy->GetMeshLocalIid(), + ndProxy->GetTimeSinceLastTransaction())); + } + } + else + { + // Duplicated address detected, send ADDR_ERR.ntf to ff03::2 in the Thread network + BackboneRouter::NdProxyTable::Erase(*ndProxy); + Get().SendAddressError(aDua, aMeshLocalIid, nullptr); + } + +exit: + otLogInfoBbr("HandleProactiveBackboneNotification: %s, target=%s, mliid=%s, LTT=%lds", otThreadErrorToString(error), + aDua.ToString().AsCString(), aMeshLocalIid.ToString().AsCString(), aTimeSinceLastTransaction); +} + } // namespace BackboneRouter } // namespace ot diff --git a/src/core/backbone_router/bbr_manager.hpp b/src/core/backbone_router/bbr_manager.hpp index c7b939f29..62ffa6b6e 100644 --- a/src/core/backbone_router/bbr_manager.hpp +++ b/src/core/backbone_router/bbr_manager.hpp @@ -133,6 +133,35 @@ public: */ BackboneTmfAgent &GetBackboneTmfAgent(void) { return mBackboneTmfAgent; } + /** + * This method sends BB.qry on the Backbone link. + * + * @param[in] aDua The Domain Unicast Address to query. + * @param[in] aRloc16 The short address of the address resolution initiator or `Mac::kShortAddrInvalid` for + * DUA DAD. + * + * @retval OT_ERROR_NONE Successfully sent BB.qry on backbone link. + * @retval OT_ERROR_INVALID_STATE If the Backbone Router is not primary, or not enabled. + * @retval OT_ERROR_NO_BUFS If insufficient message buffers available. + * + */ + otError SendBackboneQuery(const Ip6::Address &aDua, uint16_t aRloc16 = Mac::kShortAddrInvalid); + + /** + * This method send a Proactive Backbone Notification (PRO_BB.ntf) on the Backbone link. + * + * @param[in] aDua The Domain Unicast Address to notify. + * @param[in] aMeshLocalIid The Mesh-Local IID to notify. + * @param[in] aTimeSinceLastTransaction Time since last transaction (in seconds). + * + * @retval OT_ERROR_NONE Successfully sent PRO_BB.ntf on backbone link. + * @retval OT_ERROR_NO_BUFS If insufficient message buffers available. + * + */ + otError SendProactiveBackboneNotification(const Ip6::Address & aDua, + const Ip6::InterfaceIdentifier &aMeshLocalIid, + uint32_t aTimeSinceLastTransaction); + private: enum { @@ -161,11 +190,33 @@ private: 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); + void HandleDuaRegistration(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + static void HandleBackboneQuery(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); + void HandleBackboneQuery(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + static void HandleBackboneAnswer(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); + void HandleBackboneAnswer(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + otError SendBackboneAnswer(const Ip6::MessageInfo & aQueryMessageInfo, + const Ip6::Address & aDua, + uint16_t aSrcRloc16, + const NdProxyTable::NdProxy &aNdProxy); + otError SendBackboneAnswer(const Ip6::Address & aDstAddr, + uint16_t aDstPort, + const Ip6::Address & aDua, + const Ip6::InterfaceIdentifier &aMeshLocalIid, + uint32_t aTimeSinceLastTransaction, + uint16_t aSrcRloc16); + void HandleDadBackboneAnswer(const Ip6::Address &aDua, const Ip6::InterfaceIdentifier &aMeshLocalIid); + void HandleExtendedBackboneAnswer(const Ip6::Address & aDua, + const Ip6::InterfaceIdentifier &aMeshLocalIid, + uint32_t aTimeSinceLastTransaction, + uint16_t aSrcRloc16); + void HandleProactiveBackboneNotification(const Ip6::Address & aDua, + const Ip6::InterfaceIdentifier &aMeshLocalIid, + uint32_t aTimeSinceLastTransaction); + void SendDuaRegistrationResponse(const Coap::Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + const Ip6::Address & aTarget, + ThreadStatusTlv::DuaStatus aStatus); void HandleNotifierEvents(Events aEvents); @@ -174,6 +225,8 @@ private: Coap::Resource mMulticastListenerRegistration; Coap::Resource mDuaRegistration; + Coap::Resource mBackboneQuery; + Coap::Resource mBackboneAnswer; NdProxyTable mNdProxyTable; MulticastListenersTable mMulticastListenersTable; diff --git a/src/core/backbone_router/ndproxy_table.cpp b/src/core/backbone_router/ndproxy_table.cpp index 86216f298..46bab94e1 100644 --- a/src/core/backbone_router/ndproxy_table.cpp +++ b/src/core/backbone_router/ndproxy_table.cpp @@ -49,9 +49,13 @@ void NdProxyTable::NdProxy::Init(const Ip6::InterfaceIdentifier &aAddressIid, { OT_ASSERT(!mValid); + Clear(); + mValid = true; mAddressIid = aAddressIid; mMeshLocalIid = aMeshLocalIid; + mDadFlag = true; + Update(aRloc16, aTimeSinceLastTransaction); } @@ -77,6 +81,9 @@ bool NdProxyTable::MatchesFilter(const NdProxy &aProxy, Filter aFilter) case kFilterValid: rval = aProxy.mValid; break; + case kFilterDadInProcess: + rval = aProxy.mValid && aProxy.mDadFlag; + break; } return rval; @@ -113,9 +120,9 @@ void NdProxyTable::Iterator::Advance(void) } while (mCurrent < OT_ARRAY_END(table.mProxies) && !MatchesFilter(*mCurrent, mFilter)); } -void NdProxyTable::Erase(NdProxy &aProxy) +void NdProxyTable::Erase(NdProxy &aNdProxy) { - aProxy.Clear(); + aNdProxy.mValid = false; } void NdProxyTable::HandleDomainPrefixUpdate(Leader::DomainPrefixState aState) @@ -151,6 +158,7 @@ otError NdProxyTable::Register(const Ip6::InterfaceIdentifier &aAddressIid, VerifyOrExit(proxy->mMeshLocalIid == aMeshLocalIid, error = OT_ERROR_DUPLICATED); proxy->Update(aRloc16, timeSinceLastTransaction); + NotifyDuaRegistrationOnBackboneLink(*proxy); ExitNow(); } @@ -168,6 +176,7 @@ otError NdProxyTable::Register(const Ip6::InterfaceIdentifier &aAddressIid, } proxy->Init(aAddressIid, aMeshLocalIid, aRloc16, timeSinceLastTransaction); + mIsAnyDadInProcess = true; exit: otLogInfoBbr("NdProxyTable::Register %s MLIID %s RLOC16 %04x LTT %u => %s", aAddressIid.ToString().AsCString(), @@ -225,6 +234,73 @@ exit: return found; } +void NdProxyTable::HandleTimer(void) +{ + VerifyOrExit(mIsAnyDadInProcess); + + mIsAnyDadInProcess = false; + + for (NdProxy &proxy : Iterate(kFilterDadInProcess)) + { + if (proxy.IsDadAttamptsComplete()) + { + proxy.mDadFlag = false; + NotifyDuaRegistrationOnBackboneLink(proxy); + } + else + { + mIsAnyDadInProcess = true; + + if (Get().SendBackboneQuery(GetDua(proxy)) == OT_ERROR_NONE) + { + proxy.IncreaseDadAttampts(); + } + } + } + +exit: + return; +} + +void NdProxyTable::NotifyDadComplete(NdProxyTable::NdProxy &aNdProxy, bool aDuplicated) +{ + if (aDuplicated) + { + Erase(aNdProxy); + } + else + { + aNdProxy.mDadAttempts = Mle::kDuaDadRepeats; + } +} + +Ip6::Address NdProxyTable::GetDua(NdProxy &aNdProxy) +{ + Ip6::Address dua; + const Ip6::Prefix *domainPrefix = Get().GetDomainPrefix(); + + OT_ASSERT(domainPrefix != nullptr); + + dua.SetPrefix(*domainPrefix); + dua.SetIid(aNdProxy.mAddressIid); + + return dua; +} + +NdProxyTable::NdProxy *NdProxyTable::ResolveDua(const Ip6::Address &aDua) +{ + return Get().IsDomainUnicast(aDua) ? FindByAddressIid(aDua.GetIid()) : nullptr; +} + +void NdProxyTable::NotifyDuaRegistrationOnBackboneLink(NdProxyTable::NdProxy &aNdProxy) +{ + if (!aNdProxy.mDadFlag) + { + IgnoreError(Get().SendProactiveBackboneNotification( + GetDua(aNdProxy), aNdProxy.GetMeshLocalIid(), aNdProxy.GetTimeSinceLastTransaction())); + } +} + } // namespace BackboneRouter } // namespace ot diff --git a/src/core/backbone_router/ndproxy_table.hpp b/src/core/backbone_router/ndproxy_table.hpp index 4e72d87cc..cbb95e8ba 100644 --- a/src/core/backbone_router/ndproxy_table.hpp +++ b/src/core/backbone_router/ndproxy_table.hpp @@ -44,6 +44,7 @@ #include "common/non_copyable.hpp" #include "common/time.hpp" #include "net/ip6_address.hpp" +#include "thread/mle_types.hpp" namespace ot { @@ -56,6 +57,73 @@ namespace BackboneRouter { class NdProxyTable : public InstanceLocator, private NonCopyable { public: + /** + * This class represents a ND Proxy instance. + * + */ + class NdProxy : private Clearable + { + friend class NdProxyTable; + + public: + /** + * This method gets the Mesh-Local IID of the ND Proxy. + * + * @returns The Mesh-Local IID. + * + */ + const Ip6::InterfaceIdentifier &GetMeshLocalIid(void) const { return mMeshLocalIid; } + + /** + * This method gets the time since last transaction of the ND Proxy. + * + * @returns The time since last transaction in seconds. + * + */ + uint32_t GetTimeSinceLastTransaction(void) const + { + return TimeMilli::MsecToSec(TimerMilli::GetNow() - mLastRegistrationTime); + } + + /** + * This method gets the short address of the device who sends the DUA registration. + * + * @returns The RLOC16 value. + * + */ + uint16_t GetRloc16(void) const { return mRloc16; } + + /** + * This method gets the DAD flag of the ND Proxy. + * + * @returns The DAD flag. + * + */ + bool GetDadFlag(void) const { return mDadFlag; } + + private: + NdProxy(void) { Clear(); } + + void Init(const Ip6::InterfaceIdentifier &aAddressIid, + const Ip6::InterfaceIdentifier &aMeshLocalIid, + uint16_t aRloc16, + uint32_t aTimeSinceLastTransaction); + + void Update(uint16_t aRloc16, uint32_t aTimeSinceLastTransaction); + void IncreaseDadAttampts(void) { mDadAttempts++; } + bool IsDadAttamptsComplete() const { return mDadAttempts == Mle::kDuaDadRepeats; } + + Ip6::InterfaceIdentifier mAddressIid; + Ip6::InterfaceIdentifier mMeshLocalIid; + TimeMilli mLastRegistrationTime; ///< in milliseconds + uint16_t mRloc16; + uint8_t mDadAttempts : 2; + bool mDadFlag : 1; + bool mValid : 1; + + static_assert(Mle::kDuaDadRepeats < 4, "Mle::kDuaDadRepeats does not fit in mDadAttempts field as 2-bit value"); + }; + /** * This constructor initializes the `NdProxyTable` object. * @@ -64,6 +132,7 @@ public: */ explicit NdProxyTable(Instance &aInstance) : InstanceLocator(aInstance) + , mIsAnyDadInProcess(false) { } @@ -104,6 +173,39 @@ public: */ void HandleDomainPrefixUpdate(Leader::DomainPrefixState aState); + /** + * This method notifies ND Proxy table of the timer tick. + * + */ + void HandleTimer(void); + + /** + * This method gets the ND Proxy info for a given Domain Unicast Address. + * + * @param[in] aDua The Domain Unicaste Address. + * + * @returns The `NdProxy` instance matching the specified @p aDua, or nullptr if not found. + * + */ + NdProxy *ResolveDua(const Ip6::Address &aDua); + + /** + * This method notifies DAD completed for a given ND Proxy. + * + * @param[in] aNdProxy The ND Proxy to notify of. + * @param[in] aDuplicated Whether duplicate was detected. + * + */ + static void NotifyDadComplete(NdProxy &aNdProxy, bool aDuplicated); + + /** + * This method removes the ND Proxy. + * + * @param[in] aNdProxy The ND Proxy to remove. + * + */ + static void Erase(NdProxy &aNdProxy); + private: enum { @@ -114,27 +216,7 @@ private: { kFilterInvalid, kFilterValid, - }; - - class NdProxy : private Clearable - { - friend class NdProxyTable; - - private: - NdProxy(void) { Clear(); } - - void Init(const Ip6::InterfaceIdentifier &aAddressIid, - const Ip6::InterfaceIdentifier &aMeshLocalIid, - uint16_t aRloc16, - uint32_t aTimeSinceLastTransaction); - - void Update(uint16_t aRloc16, uint32_t aTimeSinceLastTransaction); - - Ip6::InterfaceIdentifier mAddressIid; - Ip6::InterfaceIdentifier mMeshLocalIid; - TimeMilli mLastRegistrationTime; ///< in milliseconds - uint16_t mRloc16; - bool mValid : 1; + kFilterDadInProcess, }; /** @@ -192,9 +274,11 @@ private: NdProxy * FindByAddressIid(const Ip6::InterfaceIdentifier &aAddressIid); NdProxy * FindByMeshLocalIid(const Ip6::InterfaceIdentifier &aMeshLocalIid); NdProxy * FindInvalid(void); - static void Erase(NdProxy &aProxy); + Ip6::Address GetDua(NdProxy &aNdProxy); + void NotifyDuaRegistrationOnBackboneLink(NdProxy &aNdProxy); NdProxy mProxies[kMaxNdProxyNum]; + bool mIsAnyDadInProcess : 1; }; } // namespace BackboneRouter diff --git a/src/core/thread/address_resolver.cpp b/src/core/thread/address_resolver.cpp index de1e8ab8b..e20441a6d 100644 --- a/src/core/thread/address_resolver.cpp +++ b/src/core/thread/address_resolver.cpp @@ -548,6 +548,18 @@ exit: Get().RegisterReceiver(TimeTicker::kAddressResolver); FreeMessageOnError(message, error); + +#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + if (Get().IsPrimary() && Get().IsDomainUnicast(aEid)) + { + uint16_t selfRloc16 = Get().GetRloc16(); + + otLogInfoArp("Extending ADDR.qry to BB.qry for target=%s, rloc16=%04x(self)", aEid.ToString().AsCString(), + selfRloc16); + IgnoreError(Get().SendBackboneQuery(aEid, selfRloc16)); + } +#endif + return error; } @@ -788,6 +800,17 @@ void AddressResolver::HandleAddressQuery(Coap::Message &aMessage, const Ip6::Mes } } +#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + if (Get().IsPrimary() && Get().IsDomainUnicast(target)) + { + uint16_t srcRloc16 = aMessageInfo.GetPeerAddr().GetIid().GetLocator(); + + otLogInfoArp("Extending ADDR.qry to BB.qry for target=%s, rloc16=%04x", target.ToString().AsCString(), + srcRloc16); + IgnoreError(Get().SendBackboneQuery(target, srcRloc16)); + } +#endif + exit: return; } diff --git a/src/core/thread/address_resolver.hpp b/src/core/thread/address_resolver.hpp index 6e63d5bdb..bccf1bebe 100644 --- a/src/core/thread/address_resolver.hpp +++ b/src/core/thread/address_resolver.hpp @@ -193,6 +193,18 @@ public: const uint32_t * aLastTransactionTimeTlv, const Ip6::Address & aDestination); + /** + * This method sends an Address Error Notification (ADDR_ERR.ntf) message. + * + * @param aTarget The target address of the ADDR_ERR.ntf message. + * @param aMeshLocalIid The ML-IID of the ADDR_ERR.ntf message. + * @param aDestination The destination to send the ADDR_ERR.ntf message. + * + */ + void SendAddressError(const Ip6::Address & aTarget, + const Ip6::InterfaceIdentifier &aMeshLocalIid, + const Ip6::Address * aDestination); + private: enum { @@ -301,9 +313,6 @@ private: void RemoveCacheEntry(CacheEntry &aEntry, CacheEntryList &aList, CacheEntry *aPrevEntry, Reason aReason); otError SendAddressQuery(const Ip6::Address &aEid); - void SendAddressError(const Ip6::Address & aTarget, - const Ip6::InterfaceIdentifier &aMeshLocalIid, - const Ip6::Address * aDestination); static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); diff --git a/src/core/thread/mle_types.hpp b/src/core/thread/mle_types.hpp index 45c4a06fa..dbbffaa9c 100644 --- a/src/core/thread/mle_types.hpp +++ b/src/core/thread/mle_types.hpp @@ -252,7 +252,7 @@ enum #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 /** - * Backbone Router / MLR constants + * Backbone Router / DUA / MLR constants * */ enum @@ -268,8 +268,11 @@ enum KResponseTimeoutDelay = 30, ///< In seconds. kDuaDadPeriod = 100, ///< In seconds. Time period after which the address ///< becomes "Preferred" if no duplicate address error. - kTimeSinceLastTransactionMax = 10 * 86400, ///< In seconds (10 days). - kDefaultBackboneHoplimit = 1, ///< default hoplimit for Thread Backbone Link Protocol messages + kDuaDadRepeats = 3, ///< Maximum number of times the multicast DAD query and wait time DUA_DAD_QUERY_TIMEOUT are + ///< repeated by the BBR, as part of the DAD process. + kDuaRecentTime = 20, ///< Time period (in seconds) during which a DUA registration is considered 'recent' at a BBR. + kTimeSinceLastTransactionMax = 10 * 86400, ///< In seconds (10 days). + kDefaultBackboneHoplimit = 1, ///< default hoplimit for Thread Backbone Link Protocol messages }; static_assert(kMlrTimeoutDefault >= kMlrTimeoutMin && kMlrTimeoutDefault <= kMlrTimeoutMax, diff --git a/src/core/thread/thread_tlvs.hpp b/src/core/thread/thread_tlvs.hpp index c82b15909..99dd7a266 100644 --- a/src/core/thread/thread_tlvs.hpp +++ b/src/core/thread/thread_tlvs.hpp @@ -80,6 +80,7 @@ public: kNDData = 9, ///< ND Data TLV kThreadNetworkData = 10, ///< Thread Network Data TLV kTimeout = 11, ///< Timeout TLV + kNetworkName = 12, ///< Network Name TLV kIPv6Addresses = 14, ///< IPv6 Addresses TLV kCommissionerSessionId = 15, ///< Commissioner Session ID TLV }; diff --git a/src/core/thread/uri_paths.hpp b/src/core/thread/uri_paths.hpp index 4450d7dd7..ef5bb193c 100644 --- a/src/core/thread/uri_paths.hpp +++ b/src/core/thread/uri_paths.hpp @@ -81,7 +81,7 @@ struct UriPath static const char kDuaRegistrationRequest[]; ///< The URI Path for DUA Registration Request ("n/dr"). static const char kDuaRegistrationNotify[]; ///< The URI Path for DUA Registration Notification ("n/dn"). static const char kBackboneQuery[]; ///< The URI Path for Backbone Query ("b/bq"). - static const char kBackboneAnswer[]; ///< The URI Path for Backbone Answer ("b/ba"). + static const char kBackboneAnswer[]; ///< The URI Path for Backbone Answer / Backbone Notification ("b/ba"). static const char kBackboneMlr[]; ///< The URI Path for Backbone Multicast Listener Report ("b/bmr"). }; diff --git a/tests/scripts/thread-cert/backbone/bbr_5_11_01.py b/tests/scripts/thread-cert/backbone/bbr_5_11_01.py index 569d4a4e5..efd5ac0f8 100755 --- a/tests/scripts/thread-cert/backbone/bbr_5_11_01.py +++ b/tests/scripts/thread-cert/backbone/bbr_5_11_01.py @@ -139,6 +139,7 @@ class BBR_5_11_01(thread_cert.TestCase): BR_1_BGUA = pv.vars['BR_1_BGUA'] Host_BGUA = pv.vars['Host_BGUA'] Dg = pv.vars['Dg'] # DUA of Router_2 + ROUTER2 = pv.vars['Router_2'] # Step 3: BR_1: Checks received Network Data and determines that it needs to send its BBR Dataset to the # leader to become primary BBR. @@ -149,12 +150,7 @@ class BBR_5_11_01(thread_cert.TestCase): and thread_nwd.tlv.service.s_data.mlrtimeout is not null """) - # Step 9: BR_1: Responds to the DUA registration. - pkts.filter_wpan_src64(BR_1).filter_coap_ack('/n/dr', - port=MM).must_next().must_verify('thread_nm.tlv.status == 0') - - # TODO: (DUA) Step 10: BR_1: Performs DAD on the backbone link. - pkts.filter_eth_src(BR_1_ETH).filter_coap_request('/b/bq', port=BB).must_not_next() + pv.verify_dua_registration(ROUTER2, Dg, pbbr_eth=BR_1_ETH, pbbr_src64=BR_1) # Verify Host ping BBR pkts.filter_eth_src(Host_ETH).filter_ipv6_src_dst(Host_BGUA, BR_1_BGUA).filter_ping_request().must_next() diff --git a/tests/scripts/thread-cert/backbone/test_dua_dad.py b/tests/scripts/thread-cert/backbone/test_dua_dad.py new file mode 100644 index 000000000..2a4fcc14f --- /dev/null +++ b/tests/scripts/thread-cert/backbone/test_dua_dad.py @@ -0,0 +1,317 @@ +#!/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. +# +# This test verifies that PBBR sets DUA routes correctly. +# +import ipaddress +import re +import unittest + +import config +import thread_cert +from pktverify.packet_verifier import PacketVerifier + +# Use two channels +CH1 = 11 +CH2 = 22 + +PBBR = 1 +SBBR = 2 +ROUTER1 = 3 +HOST = 4 +PBBR2 = 5 +ROUTER2 = 6 + +REREG_DELAY = 5 # Seconds +MLR_TIMEOUT = 300 # Seconds +WAIT_REDUNDANCE = 3 + +DUPLICATE_DUA = 'fd00:7d03:7d03:7d03:11:2233:4455:6677' +assert (re.match(config.DOMAIN_PREFIX_REGEX_PATTERN, DUPLICATE_DUA)) + +DUPLICATE_IID = ''.join('%02x' % c for c in ipaddress.IPv6Address(DUPLICATE_DUA).packed[-8:]) + +print('DUPLICATE_DUA: ', DUPLICATE_DUA) +print('DUPLICATE_IID: ', DUPLICATE_IID) + + +class TestDuaDad(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + + # Topology: + # ------(eth)---------------------- + # | | | | + # PBBR----SBBR HOST PBBR2 + # \ CH1 / | CH2 + # ROUTER1 ROUTER2 + # + # PBBR2 is in the secondary channel + # + TOPOLOGY = { + PBBR: { + 'name': 'PBBR', + 'allowlist': [SBBR, ROUTER1], + 'is_otbr': True, + 'version': '1.2', + 'router_selection_jitter': 1, + 'channel': CH1, + 'prefer_router_id': 0x1, # Use prefer_router_id to avoid Router ID conflicts in the two channels. + }, + SBBR: { + 'name': 'SBBR', + 'allowlist': [PBBR, ROUTER1], + 'is_otbr': True, + 'version': '1.2', + 'router_selection_jitter': 1, + 'channel': CH1, + 'prefer_router_id': 0x2, + }, + ROUTER1: { + 'name': 'ROUTER1', + 'allowlist': [PBBR, SBBR], + 'version': '1.2', + 'router_selection_jitter': 1, + 'channel': CH1, + 'prefer_router_id': 0x3, + }, + HOST: { + 'name': 'HOST', + 'is_host': True + }, + PBBR2: { + 'name': 'PBBR2', + 'is_otbr': True, + 'version': '1.2', + 'router_selection_jitter': 1, + 'channel': CH2, + 'prefer_router_id': 0x4, + }, + ROUTER2: { + 'name': 'ROUTER2', + 'version': '1.2', + 'router_selection_jitter': 1, + 'channel': CH2, + 'prefer_router_id': 0x5, + }, + } + + def _bootstrap(self): + # Bring up HOST + self.nodes[HOST].start() + + # Bring up PBBR + self.nodes[PBBR].start() + self.simulator.go(5) + self.assertEqual('leader', self.nodes[PBBR].get_state()) + self.wait_node_state(PBBR, 'leader', 5) + + self.nodes[PBBR].set_backbone_router(reg_delay=REREG_DELAY, mlr_timeout=MLR_TIMEOUT) + self.nodes[PBBR].enable_backbone_router() + self.nodes[PBBR].set_domain_prefix(config.DOMAIN_PREFIX, 'prosD') + self.simulator.go(5) + self.assertTrue(self.nodes[PBBR].is_primary_backbone_router) + self.assertIsNotNone(self.nodes[PBBR].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + # Bring up SBBR + self.nodes[SBBR].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[SBBR].get_state()) + + self.nodes[SBBR].set_backbone_router(reg_delay=REREG_DELAY, mlr_timeout=MLR_TIMEOUT) + self.nodes[SBBR].enable_backbone_router() + self.simulator.go(5) + self.assertFalse(self.nodes[SBBR].is_primary_backbone_router) + self.assertIsNotNone(self.nodes[SBBR].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + # Bring up ROUTER1 + self.nodes[ROUTER1].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[ROUTER1].get_state()) + self.simulator.go(config.DUA_DAD_DELAY + WAIT_REDUNDANCE) + self.assertIsNotNone(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + # Bring up PBBR2 + self.nodes[PBBR2].start() + self.simulator.go(5) + self.assertEqual('leader', self.nodes[PBBR2].get_state()) + self.wait_node_state(PBBR2, 'leader', 5) + + self.nodes[PBBR2].set_backbone_router(reg_delay=REREG_DELAY, mlr_timeout=MLR_TIMEOUT) + self.nodes[PBBR2].enable_backbone_router() + self.nodes[PBBR2].set_domain_prefix(config.DOMAIN_PREFIX, 'prosD') + self.simulator.go(config.DUA_DAD_DELAY) + self.assertTrue(self.nodes[PBBR2].is_primary_backbone_router) + self.assertIsNotNone(self.nodes[PBBR2].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + # Bring up ROUTER2 + self.nodes[ROUTER2].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[ROUTER2].get_state()) + self.simulator.go(config.DUA_DAD_DELAY + WAIT_REDUNDANCE) + self.assertIsNotNone(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + def test(self): + self._bootstrap() + + self.collect_ipaddrs() + self.collect_rloc16s() + self.collect_rlocs() + + self._test_extend_backbone_query() + + self._test_dad_duplicate() + + self._send_fake_pro_bb_ntf() + + def _test_extend_backbone_query(self): + self.nodes[ROUTER2].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + def _test_dad_duplicate(self): + self.nodes[ROUTER2].set_dua_iid(DUPLICATE_IID) + self.simulator.go(config.DUA_DAD_DELAY + WAIT_REDUNDANCE) + self.nodes[ROUTER1].set_dua_iid(DUPLICATE_IID) + self.simulator.go(config.DUA_DAD_DELAY + WAIT_REDUNDANCE) + + # now Router1 should have generated new DUA + self.assertNotEqual(DUPLICATE_DUA, self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.DUA)) + + def _send_fake_pro_bb_ntf(self): + router1_dua = self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.DUA) + self.nodes[PBBR2].send_proactive_backbone_notification(router1_dua, DUPLICATE_IID, 0) + self.simulator.go(config.DUA_DAD_DELAY + WAIT_REDUNDANCE) + + def verify(self, pv: PacketVerifier): + pkts = pv.pkts + pv.add_common_vars() + pv.summary.show() + + PBBR = pv.vars['PBBR'] + ROUTER1 = pv.vars['ROUTER1'] + ROUTER1_DUA = pv.vars['ROUTER1_DUA'] + ROUTER2 = pv.vars['ROUTER2'] + ROUTER2_DUA = pv.vars['ROUTER2_DUA'] + PBBR_ETH = pv.vars['PBBR_ETH'] + SBBR_ETH = pv.vars['SBBR_ETH'] + PBBR2 = pv.vars['PBBR2'] + PBBR2_ETH = pv.vars['PBBR2_ETH'] + + MM = pv.vars['MM_PORT'] + BB = pv.vars['BB_PORT'] + + # Verify the whole DAD process for ROUTER1 + pv.verify_dua_registration(ROUTER1, ROUTER1_DUA, pbbr_eth=PBBR_ETH, pbbr_src64=PBBR, sbbr_eth=SBBR_ETH) + + # Verify the whole DAD process for ROUTER2 + pv.verify_dua_registration(ROUTER2, ROUTER2_DUA, pbbr_eth=PBBR2_ETH, pbbr_src64=PBBR2) + + ############################################################################################################### + # Now we verify Extending ADDR.qry to BB.qry works for the Ping Request from Router2 to Router1 + ############################################################################################################### + + # Router2 should send ADDR.qry in the Thread network for Router1's DUA + pkts.filter_wpan_src64(ROUTER2).filter_coap_request('/a/aq', port=MM).filter( + 'thread_address.tlv.target_eid == {ROUTER1_DUA}', ROUTER1_DUA=ROUTER1_DUA).must_next() + # PBBR2 should extend ADDR.qry to BB.qry + pkts.filter_backbone_query(ROUTER1_DUA, eth_src=PBBR2_ETH, port=BB).must_next() + # SBBR should not answer with BB.ans + pkts.filter_backbone_query(ROUTER1_DUA, eth_src=SBBR_ETH, port=BB).must_not_next() + # PBBR1 should answer with BB.ans + pkts.filter_backbone_answer(ROUTER1_DUA, eth_src=PBBR_ETH, port=BB).must_next() + # PBBR2 should send ADDR.ntf to Router2 + pkts.filter_wpan_src64(PBBR2).filter_coap_request('/a/an', port=MM).filter( + 'thread_address.tlv.target_eid == {ROUTER1_DUA}', ROUTER1_DUA=ROUTER1_DUA).must_next() + + # Now, Router2 should send the Ping Request to PBBR2 + pkts.filter_wpan_src64(ROUTER2).filter_ping_request().filter_ipv6_dst(ROUTER1_DUA).must_next() + + ############################################################################################################### + # Now we start to verify that DAD duplicate is handled correctly + ############################################################################################################### + + # PBBR should send /b/bq for DUPLICATE_DUA + pkts.filter_backbone_query(DUPLICATE_DUA, eth_src=PBBR_ETH, port=BB).must_next() + + # PBBR2 should send /b/ba for DUPLICATE_DUA + ba = pkts.filter_backbone_answer(DUPLICATE_DUA, eth_src=PBBR2_ETH, port=BB).must_next() + ba.must_verify(""" + set(thread_bl.tlv.type) == {tlvs} + and thread_bl.tlv.last_transaction_time > 0 + and thread_meshcop.tlv.net_name == {NET_NAME} + """, + tlvs={0, 3, 6, 12}, + **pv.vars) + + # PBBR should NOT send /b/bq for DUPLICATE_DUA anymore + pkts.filter_backbone_query(DUPLICATE_DUA, eth_src=PBBR_ETH, port=BB).must_not_next() + + # PBBR should send ADDR_ERR.ntf to Router1 + ROUTER1_RLOC = pv.vars['ROUTER1_RLOC'] + pkts.filter_wpan_src64(PBBR).filter_ipv6_dst(ROUTER1_RLOC).filter_coap_request('/a/ae', port=MM).must_next() \ + .must_verify(""" + thread_address.tlv.target_eid == {DUPLICATE_DUA} + and thread_address.tlv.ml_eid == {ml_eid} + """, DUPLICATE_DUA=DUPLICATE_DUA, ml_eid=ba.thread_bl.tlv.ml_eid) + + # Router1 should generate new DUA and register again + # Router1 should generate and register a new DUA + with pkts.save_index(): + new_dr = pkts.filter_wpan_src64(ROUTER1).filter_coap_request('/n/dr', port=MM).must_next().must_verify( + """ + thread_nm.tlv.target_eid != {DUPLICATE_DUA} + """, + DUPLICATE_DUA=DUPLICATE_DUA) + router1_dua2 = new_dr.thread_nm.tlv.target_eid + + # Verify the DAD process for the new DUA + pv.verify_dua_registration(ROUTER1, router1_dua2, pbbr_eth=PBBR_ETH, pbbr_src64=PBBR, sbbr_eth=SBBR_ETH) + + ############################################################################################################### + # Now we start to verify that PRO_BB.ntf is handled correctly + ############################################################################################################### + + # Scripted: PBBR2 should send PRO_BB.ntf for Router1's DUA + pkts.filter_eth_src(PBBR2_ETH).filter_backbone_answer(router1_dua2, + port=BB, + confirmable=False, + mliid=DUPLICATE_IID).must_next() + + # PBBR should broadcaset /a/ae to the Thread network + pkts.filter_wpan_src64(PBBR).filter_coap_request('/a/ae', port=MM, + confirmable=False).filter_RLARMA().must_next() + + # Router1 should generate and register a new DUA + with pkts.save_index(): + new_dr = pkts.filter_wpan_src64(ROUTER1).filter_coap_request('/n/dr', port=MM).must_next() + router1_dua3 = new_dr.thread_nm.tlv.target_eid + + # Verify the DAD process for the new DUA + pv.verify_dua_registration(ROUTER1, router1_dua3, pbbr_eth=PBBR_ETH, pbbr_src64=PBBR, sbbr_eth=SBBR_ETH) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index 3f90b8ddd..34b0ae389 100644 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -128,6 +128,7 @@ ADDRESS_QUERY_INITIAL_RETRY_DELAY = 15 DEFAULT_CHILD_TIMEOUT = 6 VIRTUAL_TIME = int(os.getenv('VIRTUAL_TIME', 0)) PARENT_AGGREGATIOIN_DELAY = 5 +DUA_DAD_DELAY = 5 LEADER_NOTIFY_SED_BY_CHILD_UPDATE_REQUEST = True diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 51df79cd8..f817d2682 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -748,7 +748,10 @@ class NodeImpl: self.send_command(cmd) self._expect('Done') - def set_dua_iid(self, iid): + def set_dua_iid(self, iid: str): + assert len(iid) == 16 + int(iid, 16) + cmd = 'dua iid {}'.format(iid) self.send_command(cmd) self._expect('Done') @@ -1941,6 +1944,11 @@ class NodeImpl: self.send_command(cmd) self._expect("Done") + def send_proactive_backbone_notification(self, target: str, mliid: str, ltt: int): + cmd = f'fake /b/ba {target} {mliid} {ltt}' + self.send_command(cmd) + self._expect("Done") + class Node(NodeImpl, OtCli): pass diff --git a/tests/scripts/thread-cert/pktverify/layer_fields.py b/tests/scripts/thread-cert/pktverify/layer_fields.py index 1264884ca..c7003fd8a 100644 --- a/tests/scripts/thread-cert/pktverify/layer_fields.py +++ b/tests/scripts/thread-cert/pktverify/layer_fields.py @@ -26,8 +26,9 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # +import datetime import sys -import time, datetime +import time from typing import Any, Union from pyshark.packet.fields import LayerFieldsContainer, LayerField @@ -512,6 +513,8 @@ _LAYER_FIELDS = { 'thread_address.tlv.router_mask_id_seq': _auto, 'thread_address.tlv.router_mask_assigned': _bytes, 'thread_address.tlv.rloc16': _hex, + 'thread_address.tlv.target_eid': _ipv6_addr, + 'thread_address.tlv.ml_eid': _ext_addr, # thread bl 'thread_bl.tlv.type': _list(_auto), @@ -571,7 +574,7 @@ _LAYER_FIELDS = { 'thread_nwd.tlv.server_16': _auto, 'thread_nwd.tlv.border_router_16': _list(_auto), 'thread_nwd.tlv.sub_tlvs': _list(_str), - #TODO: support thread_nwd.tlv.prefix.length and thread_nwd.tlv.prefix.domain_id + # TODO: support thread_nwd.tlv.prefix.length and thread_nwd.tlv.prefix.domain_id 'thread_nwd.tlv.prefix': _list(_ipv6_addr), 'thread_nwd.tlv.border_router.pref': _auto, 'thread_nwd.tlv.border_router.flag.s': _list(_auto), diff --git a/tests/scripts/thread-cert/pktverify/packet.py b/tests/scripts/thread-cert/pktverify/packet.py index 6ed839646..d4f340fee 100644 --- a/tests/scripts/thread-cert/pktverify/packet.py +++ b/tests/scripts/thread-cert/pktverify/packet.py @@ -131,10 +131,14 @@ class Packet(object): self.debug_fields() raise errors.VerifyFailed(self) + return self + def must_not_verify(self, func: Union[str, Callable], **vars): if self.verify(func, **vars): raise errors.VerifyFailed(self) + return self + @property def sniff_timestamp(self) -> float: return float(self._packet.sniff_timestamp) diff --git a/tests/scripts/thread-cert/pktverify/packet_filter.py b/tests/scripts/thread-cert/pktverify/packet_filter.py index 927773be1..f17e35668 100644 --- a/tests/scripts/thread-cert/pktverify/packet_filter.py +++ b/tests/scripts/thread-cert/pktverify/packet_filter.py @@ -360,7 +360,7 @@ class PacketFilter(object): """ return self.filter(attrgetter('coap'), **kwargs) - def filter_coap_request(self, uri_path, port=None, **kwargs): + def filter_coap_request(self, uri_path, port=None, confirmable=None, **kwargs): """ Create a new PacketFilter to filter COAP Request packets. @@ -373,7 +373,8 @@ class PacketFilter(object): assert port is None or isinstance(port, int), port return self.filter( lambda p: (p.coap.is_post and p.coap.opt.uri_path_recon == uri_path and - (port is None or p.udp.dstport == port)), **kwargs) + (confirmable is None or p.coap.type == + (0 if confirmable else 1)) and (port is None or p.udp.dstport == port)), **kwargs) def filter_coap_ack(self, uri_path, port=None, **kwargs): """ @@ -390,6 +391,26 @@ class PacketFilter(object): lambda p: (p.coap.is_ack and p.coap.opt.uri_path_recon == uri_path and (port is None or p.udp.dstport == port)), **kwargs) + def filter_backbone_answer(self, + target: str, + *, + eth_src: Optional[EthAddr] = None, + port: int = None, + confirmable: bool = None, + mliid=None): + filter_eth = self.filter_eth_src(eth_src) if eth_src else self.filter_eth() + f = filter_eth.filter_coap_request('/b/ba', port=port, + confirmable=confirmable).filter('thread_bl.tlv.target_eid == {target}', + target=target) + if mliid is not None: + f = f.filter('thread_bl.tlv.ml_eid == {mliid}', mliid=mliid) + + return f + + def filter_backbone_query(self, target: str, *, eth_src: EthAddr, port: int = None) -> 'PacketFilter': + return self.filter_eth_src(eth_src).filter_coap_request('/b/bq', port=port, confirmable=False).filter( + 'thread_bl.tlv.target_eid == {target}', target=target) + def filter_wpan(self, **kwargs): """ Create a new PacketFilter for filter WPAN packets. diff --git a/tests/scripts/thread-cert/pktverify/packet_verifier.py b/tests/scripts/thread-cert/pktverify/packet_verifier.py index a09288a58..5dd8fcd38 100644 --- a/tests/scripts/thread-cert/pktverify/packet_verifier.py +++ b/tests/scripts/thread-cert/pktverify/packet_verifier.py @@ -30,7 +30,7 @@ import logging from typing import Tuple from pktverify import consts -from pktverify.consts import DUA_RECENT_TIME, MLE_CHILD_ID_REQUEST, MLE_ADVERTISEMENT, MLE_CHILD_ID_RESPONSE +from pktverify.consts import MLE_CHILD_ID_REQUEST, MLE_ADVERTISEMENT, MLE_CHILD_ID_RESPONSE from pktverify.pcap_reader import PcapReader from pktverify.summary import Summary from pktverify.test_info import TestInfo @@ -161,180 +161,6 @@ class PacketVerifier(object): logging.info("add extra var: %s = %s", k, v) self._vars[k] = v - def verify_dua_registration(self, - td: str, - bbr: str, - pkts=None, - dua_deadline=None, - DAD=False, - sbbr=None) -> VerifyResult: - """ - Run the packet verification for the while DAD registration, including optional steps - This is commonly used in many test cases. - - :param pkts: The packet filter to verify, or self.pkts if None - :param td: TB's name. - :param bbr: BBR's name. - """ - assert self.is_thread_device(td) - assert self.is_thread_device(bbr) and self.is_backbone_device(bbr), bbr - - if pkts is None: - pkts = self.pkts - - logging.info("verifying DUA registration from %s to %s ...", td, bbr) - result = VerifyResult() - - TD = self.vars[td] - BBR = self.vars[bbr] - BBR_ETH = self.vars[bbr + '_ETH'] - if sbbr: - SBBR_ETH = self.vars[sbbr + '_ETH'] - # BBR_DUA = self.vars[bbr + '_DUA'] - p = pkts.filter_wpan_src64(TD) \ - .filter_coap_request("/n/dr", port=self.MM_PORT) \ - .must_next() - - result.record_last("/n/dr", pkts) - - if dua_deadline is not None: - p.must_verify(lambda p: p.sniff_timestamp <= dua_deadline) - - idx_after_n_dr = pkts.index - - p.must_verify(lambda p: p.coap.tlv.target_eid and p.coap.tlv.ml_eid) - DUA = p.coap.tlv.target_eid - MLEID = p.coap.tlv.ml_eid - self.add_vars(**{td + "_DUA": DUA, td + "_MLEID": MLEID}) - logging.info(f"DUA={DUA}, MLEID={MLEID}") - - if DAD: - # DAD (DUA_DATA_REPEAT+1) TIMES - before_dad_index = pkts.index - after_dad_index = before_dad_index - with pkts.save_index(): - for i in range(consts.DUA_DAD_REPEATS + 1): - # Step 3: PBBR - Performs DAD on the backbone link - Multicasts a BB.qry CoAP request - filter = pkts.filter_eth_src(BBR_ETH) \ - .filter_LLABMA() \ - .filter_coap_request("/b/bq") \ - .filter(lambda p: p.coap.tlv.target_eid == DUA) - p = filter.must_next() - after_dad_index = self.max_index(after_dad_index, pkts.index) - - with pkts.save_index(): - # try to find the next multicast - # start_index = pkts.index - if filter.next(): - pe = pkts.last() - time_gap = pe.sniff_timestamp - p.sniff_timestamp - # PBBR Waits for DUA_DAD_QUERY_TIMEOUT: Verify that DUA_DAD_QUERY_TIMEOUT time passes - assert time_gap >= consts.DUA_DAD_QUERY_TIMEOUT - 0.01, time_gap - - # SBBR: Does not respond: SBBR does not respond to the BB.qry message. - if sbbr is not None: - dad_pkts_range = pkts.range(before_dad_index, after_dad_index, cascade=False) - dad_pkts_range.filter_eth_src(SBBR_ETH) \ - .filter_LLABMA() \ - .filter_coap_ack("/b/bq") \ - .must_not_next() - - # BBR updates the corresponding entry in its DUA device table. - # No pass criteria - - # Step 7: BBR informs other BBRs on the network of the DUA registration. - # FIXME: Test plan requires that last_transaction_time <= 3, - # however real OT implementation can have last_transaction_time == 4 - expected_last_transaction_time = 0 if not DAD else 4 - pkts.filter_eth_src(BBR_ETH) \ - .filter_LLABMA() \ - .filter_coap_request("/b/ba") \ - .filter("coap.tlv.target_eid == {DUA}", DUA=DUA) \ - .must_next() \ - .must_verify(""" - coap.tlv.ml_eid == {MLEID} - and coap.tlv.last_transaction_time <= {expected_last_transaction_time} - and coap.tlv.net_name == {NET_NAME} - """, MLEID=MLEID, NET_NAME=self.NET_NAME, - expected_last_transaction_time=expected_last_transaction_time) - - idx1 = pkts.index - - # SBBR receives PRO_BB.ntf and optionally updates the corresponding entry - # in its Backup DUA Devices Table. No pass criteria. - - # BBR announces itself as the new ND proxy for the roaming device - pkts.seek_back(0.2, eth=True) \ - .filter_eth_src(BBR_ETH) \ - .filter_LLANMA() \ - .filter_icmpv6_nd_na(DUA) \ - .must_next() \ - .must_verify(""" - icmpv6.nd.na.flag.s == 0 - and icmpv6.nd.na.flag.o == 1 - and icmpv6.nd.na.flag.r == 1 - and icmpv6.opt.target_linkaddr == {BBR_ETH} - """, BBR_ETH=BBR_ETH) - - idx2 = pkts.index - # BBR responds to the DUA registration - pkts.index = idx_after_n_dr # reset index to just after /n/dr request - pkts.filter_wpan_src64(BBR) \ - .filter_coap_ack("/n/dr") \ - .filter("coap.tlv.target_eid == {DUA}", DUA=DUA) \ - .must_next() \ - .must_verify(""" - coap.tlv.target_eid == {DUA} - and coap.tlv.status == 0 - """, DUA=DUA) - - pkts.index = self.max_index(idx1, idx2, pkts.index) - # BBR optionally repeats the unsolicited neighbor advertisement. - # Optional 1 or 2 times - with pkts.save_index(): - filter = pkts.filter_eth_src(BBR_ETH) \ - .filter_LLANMA() \ - .filter_icmpv6_nd_na(DUA) - - for i in range(2): - if not filter.next(): - break - - filter.last().must_verify(""" - icmpv6.nd.na.flag.s == 0 - and icmpv6.nd.na.flag.o == 1 - and icmpv6.nd.na.flag.r == 1 - and icmpv6.opt.target_linkaddr == {BBR_ETH} - """, - BBR_ETH=BBR_ETH) - - # BBR Optionally repeats the DUA registration notification - # Optional - with pkts.save_index(): - filter = pkts.filter_eth_src(BBR_ETH) \ - .filter_LLABMA() \ - .filter_coap_request("/b/ba") \ - .filter("coap.tlv.target_eid == {DUA}", DUA=DUA) - - if filter.next(): - p = filter.last() - p.must_verify(""" - coap.tlv.ml_eid == {MLEID} - and coap.tlv.net_name == {NET_NAME} - and 3 < coap.tlv.last_transaction_time < DUA_RECENT_TIME - """, - DUA_RECENT_TIME=DUA_RECENT_TIME, - MLEID=MLEID, - NET_NAME=self.NET_NAME) - - if sbbr is not None: - # SBBR: Does not respond to the ND Neighbor Solicitation message. - SBBR_ETH = self.vars[sbbr + '_ETH'] - pkts_in_range = pkts.range(idx_after_n_dr, pkts.index, cascade=False) - pkts_in_range.filter_eth_src(SBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(DUA).must_not_next() - - return result - def verify_attached(self, name: str, pkts=None) -> VerifyResult: """ Verify that the device attaches to the Thread network. @@ -459,3 +285,45 @@ class PacketVerifier(object): eth_idx = max(eth_idx, ei) return wpan_idx, eth_idx + + def verify_dua_registration(self, src64, dua, *, pbbr_eth, sbbr_eth=None, pbbr_src64=None): + pv, pkts = self, self.pkts + MM = pv.vars['MM_PORT'] + BB = pv.vars['BB_PORT'] + + # Router1 should send /n/dr for DUA registration + dr = pkts.filter_wpan_src64(src64).filter_coap_request('/n/dr', port=MM).filter( + 'thread_nm.tlv.target_eid == {ROUTER1_DUA}', ROUTER1_DUA=dua).must_next() + + # SBBR should not send /b/bq for Router1's DUA + if sbbr_eth is not None: + pkts.filter_backbone_query(dua, eth_src=sbbr_eth, port=BB).must_not_next() + + # PBBR should respond to /n/dr + if pbbr_src64 is not None: + pkts.filter_wpan_src64(pbbr_src64).filter_coap_ack( + '/n/dr', port=MM).must_next().must_verify('thread_nm.tlv.status == 0') + + # PBBR should send /b/bq for Router1's DUA (1st time) + bq1 = pkts.filter_backbone_query(dua, eth_src=pbbr_eth, port=BB).must_next() + bq1_index = pkts.index + + assert bq1.sniff_timestamp - dr.sniff_timestamp <= 1.0, bq1.sniff_timestamp - dr.sniff_timestamp + + # PBBR should send /b/bq for Router1's DUA (2nd time) + bq2 = pkts.filter_backbone_query(dua, eth_src=pbbr_eth, port=BB).must_next() + + assert 0.9 < bq2.sniff_timestamp - bq1.sniff_timestamp < 1.1, bq2.sniff_timestamp - bq1.sniff_timestamp + + # PBBR should send /b/bq for Router1's DUA (3rd time) + bq3 = pkts.filter_backbone_query(dua, eth_src=pbbr_eth, port=BB).must_next() + + assert 0.9 < bq3.sniff_timestamp - bq2.sniff_timestamp < 1.1, bq3.sniff_timestamp - bq2.sniff_timestamp + + # PBBR should send PRO_BB.ntf for Router's DUA when DAD completed + pkts.filter_eth_src(pbbr_eth).filter_backbone_answer(dua, port=BB, confirmable=False).must_next().show() + + # PBBR should not recv /b/ba response from other BBRs during this period + pkts.range(bq1_index, pkts.index, + cascade=False).filter('eth.src != {PBBR_ETH}', + PBBR_ETH=pbbr_eth).filter_backbone_answer(dua, port=BB).must_not_next() diff --git a/tests/scripts/thread-cert/pktverify/utils.py b/tests/scripts/thread-cert/pktverify/utils.py index 4df2cda6c..ea78ca51c 100644 --- a/tests/scripts/thread-cert/pktverify/utils.py +++ b/tests/scripts/thread-cert/pktverify/utils.py @@ -28,7 +28,6 @@ # import logging import os -import subprocess import sys from typing import Callable, Union @@ -71,6 +70,7 @@ def make_filter_func(func: Union[str, Callable], **vars) -> Callable: 'EthAddr': EthAddr, 'thread_nm': p.thread_nm, 'thread_nwd': p.thread_nwd, + 'thread_address': p.thread_address, 'null': nullField, }) else: diff --git a/tests/scripts/thread-cert/run_bbr_tests.py b/tests/scripts/thread-cert/run_bbr_tests.py index 784175627..6abc68dab 100644 --- a/tests/scripts/thread-cert/run_bbr_tests.py +++ b/tests/scripts/thread-cert/run_bbr_tests.py @@ -36,7 +36,7 @@ from collections import Counter import config -MULTIPLE_JOBS = 20 +MULTIPLE_JOBS = 10 logging.basicConfig(level=logging.DEBUG, format='File "%(pathname)s", line %(lineno)d, in %(funcName)s\n' diff --git a/tests/scripts/thread-cert/thread_cert.py b/tests/scripts/thread-cert/thread_cert.py index b3a16b70a..44882c9c0 100644 --- a/tests/scripts/thread-cert/thread_cert.py +++ b/tests/scripts/thread-cert/thread_cert.py @@ -176,6 +176,8 @@ class TestCase(NcpSupportMixin, unittest.TestCase): self.nodes[i].set_router_downgrade_threshold(params['router_downgrade_threshold']) if 'router_eligible' in params: self.nodes[i].set_router_eligible(params['router_eligible']) + if 'prefer_router_id' in params: + self.nodes[i].prefer_router_id(params['prefer_router_id']) if 'timeout' in params: self.nodes[i].set_timeout(params['timeout']) @@ -327,6 +329,9 @@ class TestCase(NcpSupportMixin, unittest.TestCase): test_info['rlocs'] = {} for i, node in self.nodes.items(): + if node.is_host: + continue + test_info['rlocs'][i] = node.get_rloc() def collect_leader_aloc(self, node):