mirror of
https://github.com/espressif/openthread.git
synced 2026-08-15 15:17:46 +00:00
[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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
#include <openthread/thread_ftd.h>
|
||||
|
||||
#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<Ip6::InterfaceIdentifier &>(*aMlIid), nullptr,
|
||||
static_cast<Ip6::Address &>(*aDestination));
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
|
||||
otError otThreadSendProactiveBackboneNotification(otInstance * aInstance,
|
||||
otIp6Address * aTarget,
|
||||
otIp6InterfaceIdentifier *aMlIid,
|
||||
uint32_t aTimeSinceLastTransaction)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.Get<BackboneRouter::Manager>().SendProactiveBackboneNotification(
|
||||
static_cast<Ip6::Address &>(*aTarget), static_cast<Ip6::InterfaceIdentifier &>(*aMlIid),
|
||||
aTimeSinceLastTransaction);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // OPENTHREAD_FTD
|
||||
|
||||
@@ -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<BackboneRouter::Local>().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<BackboneRouter::Local>().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<Manager *>(aContext)->HandleBackboneQuery(*static_cast<const Coap::Message *>(aMessage),
|
||||
*static_cast<const Ip6::MessageInfo *>(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<Local>().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<Manager *>(aContext)->HandleBackboneAnswer(*static_cast<const Coap::Message *>(aMessage),
|
||||
*static_cast<const Ip6::MessageInfo *>(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<Local>().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<BackboneRouter::Local>().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<Mac::Mac>().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<Mle::MleRouter>().GetMeshLocalPrefix(), ndProxy->GetRloc16());
|
||||
Get<AddressResolver>().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<Mle::MleRouter>().GetMeshLocalPrefix(), aSrcRloc16);
|
||||
Get<AddressResolver>().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<AddressResolver>().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
|
||||
|
||||
@@ -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<Manager *>(aContext)->HandleDuaRegistration(*static_cast<const Coap::Message *>(aMessage),
|
||||
*static_cast<const Ip6::MessageInfo *>(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;
|
||||
|
||||
@@ -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<BackboneRouter::Manager>().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<BackboneRouter::Leader>().GetDomainPrefix();
|
||||
|
||||
OT_ASSERT(domainPrefix != nullptr);
|
||||
|
||||
dua.SetPrefix(*domainPrefix);
|
||||
dua.SetIid(aNdProxy.mAddressIid);
|
||||
|
||||
return dua;
|
||||
}
|
||||
|
||||
NdProxyTable::NdProxy *NdProxyTable::ResolveDua(const Ip6::Address &aDua)
|
||||
{
|
||||
return Get<Leader>().IsDomainUnicast(aDua) ? FindByAddressIid(aDua.GetIid()) : nullptr;
|
||||
}
|
||||
|
||||
void NdProxyTable::NotifyDuaRegistrationOnBackboneLink(NdProxyTable::NdProxy &aNdProxy)
|
||||
{
|
||||
if (!aNdProxy.mDadFlag)
|
||||
{
|
||||
IgnoreError(Get<BackboneRouter::Manager>().SendProactiveBackboneNotification(
|
||||
GetDua(aNdProxy), aNdProxy.GetMeshLocalIid(), aNdProxy.GetTimeSinceLastTransaction()));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace BackboneRouter
|
||||
|
||||
} // namespace ot
|
||||
|
||||
@@ -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<NdProxy>
|
||||
{
|
||||
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<NdProxy>
|
||||
{
|
||||
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
|
||||
|
||||
@@ -548,6 +548,18 @@ exit:
|
||||
|
||||
Get<TimeTicker>().RegisterReceiver(TimeTicker::kAddressResolver);
|
||||
FreeMessageOnError(message, error);
|
||||
|
||||
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
|
||||
if (Get<BackboneRouter::Local>().IsPrimary() && Get<BackboneRouter::Leader>().IsDomainUnicast(aEid))
|
||||
{
|
||||
uint16_t selfRloc16 = Get<Mle::MleRouter>().GetRloc16();
|
||||
|
||||
otLogInfoArp("Extending ADDR.qry to BB.qry for target=%s, rloc16=%04x(self)", aEid.ToString().AsCString(),
|
||||
selfRloc16);
|
||||
IgnoreError(Get<BackboneRouter::Manager>().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<BackboneRouter::Local>().IsPrimary() && Get<BackboneRouter::Leader>().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<BackboneRouter::Manager>().SendBackboneQuery(target, srcRloc16));
|
||||
}
|
||||
#endif
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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").
|
||||
};
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user