[mlr] add Commissioner API to register Multicast Listeners (#5346)

This commit is contained in:
Simon Lin
2020-08-29 08:52:29 -07:00
committed by GitHub
parent 5e233fb617
commit 7266e1b909
13 changed files with 731 additions and 123 deletions
+53
View File
@@ -653,6 +653,59 @@ typedef bool (*otIp6SlaacPrefixFilter)(otInstance *aInstance, const otIp6Prefix
*/
void otIp6SetSlaacPrefixFilter(otInstance *aInstance, otIp6SlaacPrefixFilter aFilter);
/**
* This function pointer is called with results of `otIp6RegisterMulticastListeners`.
*
* @param[in] aContext A pointer to the user context.
* @param[in] aError OT_ERROR_NONE when successfully sent MLR.req and received MLR.rsp,
* OT_ERROR_RESPONSE_TIMEOUT when failed to receive MLR.rsp,
* OT_ERROR_PARSE when failed to parse MLR.rsp.
* @param[in] aMlrStatus The Multicast Listener Registration status when @p aError is OT_ERROR_NONE.
* @param[in] aFailedAddresses A pointer to the failed Ip6 addresses when @p aError is OT_ERROR_NONE.
* @param[in] aFailedAddressNum The number of failed Ip6 addresses when @p aError is OT_ERROR_NONE.
*
* @sa otIp6RegisterMulticastListeners
*
*/
typedef void (*otIp6RegisterMulticastListenersCallback)(void * aContext,
otError aError,
uint8_t aMlrStatus,
const otIp6Address *aFailedAddresses,
uint8_t aFailedAddressNum);
/**
* This function registers Multicast Listeners to Primary Backbone Router.
*
* Note: only available when both `OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE` and
* `OPENTHREAD_CONFIG_COMMISSIONER_ENABLE` are enabled)
*
* @param[in] aInstance A pointer to an OpenThread instance.
* @param[in] aAddresses A Multicast Address Array to register.
* @param[in] aAddressNum The number of Multicast Address to register (0 if @p aAddresses is NULL).
* @param[in] aTimeout A pointer to the timeout value (in seconds) to be included in MLR.req. A timeout value of 0
* removes the corresponding Multicast Listener. If NULL, MLR.req would have no Timeout Tlv by
* default.
* @param[in] aCallback A pointer to the callback function.
* @param[in] aContext A pointer to the user context.
*
* @retval OT_ERROR_NONE Successfully sent MLR.req. The @p aCallback will be called iff this method
* returns OT_ERROR_NONE.
* @retval OT_ERROR_BUSY If a previous registration was ongoing.
* @retval OT_ERROR_INVALID_ARGS If one or more arguments are invalid.
* @retval OT_ERROR_INVALID_STATE If the device was not in a valid state to send MLR.req (e.g. Commissioner not
* started, Primary Backbone Router not found).
* @retval OT_ERROR_NO_BUFS If insufficient message buffers available.
*
* @sa otIp6RegisterMulticastListenersCallback
*
*/
otError otIp6RegisterMulticastListeners(otInstance * aInstance,
const otIp6Address * aAddresses,
uint8_t aAddressNum,
const uint32_t * aTimeout,
otIp6RegisterMulticastListenersCallback aCallback,
void * aContext);
/**
* @}
*
+27
View File
@@ -60,6 +60,7 @@ Done
- [mac](#mac-retries-direct)
- [macfilter](#macfilter)
- [masterkey](#masterkey)
- [mlr](#mlr-reg-ipaddr--timeout)
- [mode](#mode)
- [neighbor](#neighbor-list)
- [netdata](#netdata-steeringdata-check-eui64discerner)
@@ -1097,6 +1098,32 @@ Set the Thread Master Key value.
Done
```
### mlr reg \<ipaddr\> ... [timeout]
Register Multicast Listeners to Primary Backbone Router, with an optional `timeout` (in seconds).
Omit `timeout` to use the default MLR timeout on the Primary Backbone Router.
Use `timeout = 0` to deregister Multicast Listeners.
NOTE: Only for Thread 1.2 Commissioner FTD device.
```bash
> mlr reg ff04::1
status 0, 0 failed
Done
> mlr reg ff04::1 ff04::2 ff02::1
status 2, 1 failed
ff02:0:0:0:0:0:0:1
Done
> mlr reg ff04::1 ff04::2 1000
status 0, 0 failed
Done
> mlr reg ff04::1 ff04::2 0
status 0, 0 failed
Done
```
### mode
Get the Thread Device Mode value.
+91
View File
@@ -184,6 +184,9 @@ const struct Command Interpreter::sCommands[] = {
{"macfilter", &Interpreter::ProcessMacFilter},
#endif
{"masterkey", &Interpreter::ProcessMasterKey},
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
{"mlr", &Interpreter::ProcessMlr},
#endif
{"mode", &Interpreter::ProcessMode},
#if OPENTHREAD_FTD
{"neighbor", &Interpreter::ProcessNeighbor},
@@ -2070,6 +2073,94 @@ exit:
AppendResult(error);
}
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
void Interpreter::ProcessMlr(uint8_t aArgsLength, char **aArgs)
{
if (aArgsLength == 0)
{
AppendResult(OT_ERROR_INVALID_COMMAND);
}
else if (!strcmp(aArgs[0], "reg"))
{
ProcessMlrReg(aArgsLength - 1, aArgs + 1);
}
else
{
AppendResult(OT_ERROR_INVALID_COMMAND);
}
}
void Interpreter::ProcessMlrReg(uint8_t aArgsLength, char *aArgs[])
{
otError error = OT_ERROR_NONE;
otIp6Address addresses[kIPv6AddressesNumMax];
uint32_t timeout;
uint8_t i;
VerifyOrExit(aArgsLength >= 1, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(aArgsLength <= kIPv6AddressesNumMax + 1, error = OT_ERROR_INVALID_ARGS);
for (i = 0; i < aArgsLength && i < kIPv6AddressesNumMax; i++)
{
if (otIp6AddressFromString(aArgs[i], &addresses[i]) != OT_ERROR_NONE)
{
break;
}
}
VerifyOrExit(i > 0 && (i == aArgsLength || i == aArgsLength - 1), error = OT_ERROR_INVALID_ARGS);
if (i == aArgsLength - 1)
{
// Parse the last argument as a timeout in seconds
unsigned long value;
SuccessOrExit(error = ParseUnsignedLong(aArgs[i], value));
timeout = static_cast<uint32_t>(value);
}
error = otIp6RegisterMulticastListeners(mInstance, addresses, i, i == aArgsLength - 1 ? &timeout : nullptr,
Interpreter::HandleMlrRegResult, this);
exit:
if (error != OT_ERROR_NONE)
{
AppendResult(error);
}
}
void Interpreter::HandleMlrRegResult(void * aContext,
otError aError,
uint8_t aMlrStatus,
const otIp6Address *aFailedAddresses,
uint8_t aFailedAddressNum)
{
static_cast<Interpreter *>(aContext)->HandleMlrRegResult(aError, aMlrStatus, aFailedAddresses, aFailedAddressNum);
}
void Interpreter::HandleMlrRegResult(otError aError,
uint8_t aMlrStatus,
const otIp6Address *aFailedAddresses,
uint8_t aFailedAddressNum)
{
if (aError == OT_ERROR_NONE)
{
OutputFormat("status %d, %d failed\r\n", aMlrStatus, aFailedAddressNum);
for (uint8_t i = 0; i < aFailedAddressNum; i++)
{
OutputIp6Address(aFailedAddresses[i]);
OutputFormat("\r\n");
}
}
AppendResult(aError);
}
#endif // OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
void Interpreter::ProcessMode(uint8_t aArgsLength, char *aArgs[])
{
otError error = OT_ERROR_NONE;
+17
View File
@@ -354,6 +354,23 @@ private:
void ProcessLeaderWeight(uint8_t aArgsLength, char *aArgs[]);
#endif
void ProcessMasterKey(uint8_t aArgsLength, char *aArgs[]);
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
void ProcessMlr(uint8_t aArgsLength, char *aArgs[]);
#if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
void ProcessMlrReg(uint8_t aArgsLength, char *aArgs[]);
static void HandleMlrRegResult(void * aContext,
otError aError,
uint8_t aMlrStatus,
const otIp6Address *aFailedAddresses,
uint8_t aFailedAddressNum);
void HandleMlrRegResult(otError aError,
uint8_t aMlrStatus,
const otIp6Address *aFailedAddresses,
uint8_t aFailedAddressNum);
#endif
#endif
void ProcessMode(uint8_t aArgsLength, char *aArgs[]);
#if OPENTHREAD_FTD
void ProcessNeighbor(uint8_t aArgsLength, char *aArgs[]);
+15
View File
@@ -286,4 +286,19 @@ void otIp6SetSlaacPrefixFilter(otInstance *aInstance, otIp6SlaacPrefixFilter aFi
instance.Get<Utils::Slaac>().SetFilter(aFilter);
}
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
otError otIp6RegisterMulticastListeners(otInstance * aInstance,
const otIp6Address * aAddresses,
uint8_t aAddressNum,
const uint32_t * aTimeout,
otIp6RegisterMulticastListenersCallback aCallback,
void * aContext)
{
Instance &instance = *static_cast<Instance *>(aInstance);
return instance.Get<MlrManager>().RegisterMulticastListeners(aAddresses, aAddressNum, aTimeout, aCallback,
aContext);
}
#endif
#endif // OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE
+85 -32
View File
@@ -33,6 +33,8 @@
#include "bbr_manager.hpp"
#include <limits>
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
#include "common/code_utils.hpp"
@@ -99,13 +101,19 @@ void Manager::HandleMulticastListenerRegistration(const Coap::Message &aMessage,
otError error = OT_ERROR_NONE;
bool isPrimary = Get<BackboneRouter::Local>().IsPrimary();
ThreadStatusTlv::MlrStatus status = ThreadStatusTlv::kMlrSuccess;
uint16_t addressesOffset, addressesLength;
Ip6::Address address;
Ip6::Address failedAddresses[kIPv6AddressesNumMax];
uint8_t failedAddressNum = 0;
TimeMilli expireTime;
BackboneRouterConfig config;
uint16_t addressesOffset, addressesLength;
Ip6::Address address;
Ip6::Address failedAddresses[kIPv6AddressesNumMax];
uint8_t failedAddressNum = 0;
TimeMilli expireTime;
uint32_t timeout;
uint16_t commissionerSessionId;
bool hasCommissionerSessionIdTlv = false;
bool processTimeoutTlv = false;
VerifyOrExit(aMessage.IsConfirmable() && aMessage.GetCode() == OT_COAP_CODE_POST, error = OT_ERROR_PARSE);
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
@@ -119,8 +127,23 @@ void Manager::HandleMulticastListenerRegistration(const Coap::Message &aMessage,
VerifyOrExit(isPrimary, status = ThreadStatusTlv::kMlrBbrNotPrimary);
// TODO: (MLR) handle Commissioner Session TLV
// TODO: (MLR) handle Timeout TLV
// TODO: (MLR) send configured MLR response for Reference Device
if (ThreadTlv::FindUint16Tlv(aMessage, ThreadTlv::kCommissionerSessionId, commissionerSessionId) == OT_ERROR_NONE)
{
const MeshCoP::CommissionerSessionIdTlv *commissionerSessionIdTlv =
static_cast<const MeshCoP::CommissionerSessionIdTlv *>(
Get<NetworkData::Leader>().GetCommissioningDataSubTlv(MeshCoP::Tlv::kCommissionerSessionId));
VerifyOrExit(commissionerSessionIdTlv != nullptr &&
commissionerSessionIdTlv->GetCommissionerSessionId() == commissionerSessionId,
status = ThreadStatusTlv::kMlrGeneralFailure);
hasCommissionerSessionIdTlv = true;
}
processTimeoutTlv = hasCommissionerSessionIdTlv &&
(ThreadTlv::FindUint32Tlv(aMessage, ThreadTlv::kTimeout, timeout) == OT_ERROR_NONE);
VerifyOrExit(ThreadTlv::FindTlvValueOffset(aMessage, IPv6AddressesTlv::kIPv6Addresses, addressesOffset,
addressesLength) == OT_ERROR_NONE,
@@ -129,39 +152,69 @@ void Manager::HandleMulticastListenerRegistration(const Coap::Message &aMessage,
VerifyOrExit(addressesLength / sizeof(Ip6::Address) <= kIPv6AddressesNumMax,
status = ThreadStatusTlv::kMlrGeneralFailure);
IgnoreError(Get<BackboneRouter::Leader>().GetConfig(config));
expireTime = TimerMilli::GetNow() + TimeMilli::SecToMsec(config.mMlrTimeout);
if (!processTimeoutTlv)
{
IgnoreError(Get<BackboneRouter::Leader>().GetConfig(config));
timeout = config.mMlrTimeout;
}
else
{
VerifyOrExit(timeout < std::numeric_limits<uint32_t>::max(), status = ThreadStatusTlv::kMlrNoPersistent);
if (timeout != 0)
{
uint32_t origTimeout = timeout;
timeout = OT_MIN(timeout, static_cast<uint32_t>(Mle::kMlrTimeoutMax));
timeout = OT_MAX(timeout, static_cast<uint32_t>(Mle::kMlrTimeoutMin));
if (timeout != origTimeout)
{
otLogNoteBbr("MLR.req: MLR timeout is normalized from %u to %u", origTimeout, timeout);
}
}
}
expireTime = TimerMilli::GetNow() + TimeMilli::SecToMsec(timeout);
for (uint16_t offset = 0; offset < addressesLength; offset += sizeof(Ip6::Address))
{
bool failed = true;
IgnoreReturnValue(aMessage.Read(addressesOffset + offset, sizeof(Ip6::Address), &address));
switch (mMulticastListenersTable.Add(address, expireTime))
if (timeout == 0)
{
case OT_ERROR_NONE:
failed = false;
break;
case OT_ERROR_INVALID_ARGS:
if (status == ThreadStatusTlv::kMlrSuccess)
{
status = ThreadStatusTlv::kMlrInvalid;
}
break;
case OT_ERROR_NO_BUFS:
if (status == ThreadStatusTlv::kMlrSuccess)
{
status = ThreadStatusTlv::kMlrNoResources;
}
break;
default:
OT_ASSERT(false);
mMulticastListenersTable.Remove(address);
}
if (failed)
else
{
failedAddresses[failedAddressNum++] = address;
bool failed = true;
switch (mMulticastListenersTable.Add(address, expireTime))
{
case OT_ERROR_NONE:
failed = false;
break;
case OT_ERROR_INVALID_ARGS:
if (status == ThreadStatusTlv::kMlrSuccess)
{
status = ThreadStatusTlv::kMlrInvalid;
}
break;
case OT_ERROR_NO_BUFS:
if (status == ThreadStatusTlv::kMlrSuccess)
{
status = ThreadStatusTlv::kMlrNoResources;
}
break;
default:
OT_ASSERT(false);
}
if (failed)
{
failedAddresses[failedAddressNum++] = address;
}
}
}
+12
View File
@@ -80,6 +80,18 @@
#define OT_DEFINE_ALIGNED_VAR(name, size, align_type) \
align_type name[(((size) + (sizeof(align_type) - 1)) / sizeof(align_type))]
/**
* This macro returns the smaller of @p a and @p b.
*
*/
#define OT_MIN(a, b) ((b) < (a) ? (b) : (a))
/**
* This macro returns the greater of @p a and @p b.
*
*/
#define OT_MAX(a, b) ((a) < (b) ? (b) : (a))
/**
* This macro checks for the specified status, which is expected to commonly be successful, and branches to the local
* label 'exit' if the status is unsuccessful.
+5
View File
@@ -189,7 +189,12 @@
* This is compulsory for 1.2 FTD.
*
*/
#ifdef OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
#error \
"Don't define `OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE` manually. It's an alias of `((OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) && OPENTHREAD_FTD)`."
#else
#define OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE \
((OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) && OPENTHREAD_FTD)
#endif
#endif // CONFIG_TMF_H_
+192 -60
View File
@@ -48,9 +48,16 @@ namespace ot {
MlrManager::MlrManager(Instance &aInstance)
: InstanceLocator(aInstance)
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
, mRegisterMulticastListenersCallback(nullptr)
, mRegisterMulticastListenersContext(nullptr)
#endif
, mReregistrationDelay(0)
, mSendDelay(0)
, mMlrPending(false)
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
, mRegisterMulticastListenersPending(false)
#endif
{
}
@@ -218,13 +225,10 @@ void MlrManager::UpdateTimeTickerRegistration(void)
void MlrManager::SendMulticastListenerRegistration(void)
{
otError error = OT_ERROR_NONE;
Mle::MleRouter & mle = Get<Mle::MleRouter>();
Coap::Message * message = nullptr;
Ip6::MessageInfo messageInfo;
IPv6AddressesTlv addressesTlv;
Ip6::Address addresses[kIPv6AddressesNumMax];
uint8_t addressesNum = 0;
otError error;
Mle::MleRouter &mle = Get<Mle::MleRouter>();
Ip6::Address addresses[kIPv6AddressesNumMax];
uint8_t addressesNum = 0;
VerifyOrExit(!mMlrPending, error = OT_ERROR_BUSY);
VerifyOrExit(mle.IsAttached(), error = OT_ERROR_INVALID_STATE);
@@ -280,6 +284,113 @@ void MlrManager::SendMulticastListenerRegistration(void)
#endif
VerifyOrExit(addressesNum > 0, error = OT_ERROR_NOT_FOUND);
SuccessOrExit(
error = SendMulticastListenerRegistrationMessage(
addresses, addressesNum, nullptr, &MlrManager::HandleMulticastListenerRegistrationResponse, this));
mMlrPending = true;
// TODO: not enable fast polls for SSED
if (!Get<Mle::Mle>().IsRxOnWhenIdle())
{
Get<DataPollSender>().SendFastPolls(DataPollSender::kDefaultFastPolls);
}
exit:
if (error != OT_ERROR_NONE)
{
SetMulticastAddressMlrState(kMlrStateRegistering, kMlrStateToRegister);
if (error == OT_ERROR_NO_BUFS)
{
ScheduleSend(1);
}
}
LogMulticastAddresses();
CheckInvariants();
}
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
otError MlrManager::RegisterMulticastListeners(const otIp6Address * aAddresses,
uint8_t aAddressNum,
const uint32_t * aTimeout,
otIp6RegisterMulticastListenersCallback aCallback,
void * aContext)
{
otError error;
VerifyOrExit(aAddresses != nullptr, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(aAddressNum > 0 && aAddressNum <= kIPv6AddressesNumMax, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(aContext == nullptr || aCallback != nullptr, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(Get<MeshCoP::Commissioner>().IsActive(), error = OT_ERROR_INVALID_STATE);
// Only allow one outstanding registration if callback is specified.
VerifyOrExit(!mRegisterMulticastListenersPending, error = OT_ERROR_BUSY);
SuccessOrExit(error = SendMulticastListenerRegistrationMessage(
aAddresses, aAddressNum, aTimeout, &MlrManager::HandleRegisterMulticastListenersResponse, this));
mRegisterMulticastListenersPending = true;
mRegisterMulticastListenersCallback = aCallback;
mRegisterMulticastListenersContext = aContext;
exit:
return error;
}
void MlrManager::HandleRegisterMulticastListenersResponse(void * aContext,
otMessage * aMessage,
const otMessageInfo *aMessageInfo,
otError aResult)
{
static_cast<MlrManager *>(aContext)->HandleRegisterMulticastListenersResponse(
static_cast<Coap::Message *>(aMessage), static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
}
void MlrManager::HandleRegisterMulticastListenersResponse(otMessage * aMessage,
const otMessageInfo *aMessageInfo,
otError aResult)
{
OT_UNUSED_VARIABLE(aMessageInfo);
uint8_t status;
otError error;
Ip6::Address failedAddresses[kIPv6AddressesNumMax];
uint8_t failedAddressNum = 0;
otIp6RegisterMulticastListenersCallback callback = mRegisterMulticastListenersCallback;
void * context = mRegisterMulticastListenersContext;
mRegisterMulticastListenersPending = false;
mRegisterMulticastListenersCallback = nullptr;
mRegisterMulticastListenersContext = nullptr;
error = ParseMulticastListenerRegistrationResponse(aResult, static_cast<Coap::Message *>(aMessage), status,
failedAddresses, failedAddressNum);
if (callback != nullptr)
{
callback(context, error, status, failedAddresses, failedAddressNum);
}
}
#endif // OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
otError MlrManager::SendMulticastListenerRegistrationMessage(const otIp6Address * aAddresses,
uint8_t aAddressNum,
const uint32_t * aTimeout,
Coap::ResponseHandler aResponseHandler,
void * aResponseContext)
{
OT_UNUSED_VARIABLE(aTimeout);
otError error = OT_ERROR_NONE;
Mle::MleRouter & mle = Get<Mle::MleRouter>();
Coap::Message * message = nullptr;
Ip6::MessageInfo messageInfo;
IPv6AddressesTlv addressesTlv;
VerifyOrExit(Get<BackboneRouter::Leader>().HasPrimary(), error = OT_ERROR_INVALID_STATE);
VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
@@ -289,9 +400,24 @@ void MlrManager::SendMulticastListenerRegistration(void)
SuccessOrExit(message->SetPayloadMarker());
addressesTlv.Init();
addressesTlv.SetLength(sizeof(Ip6::Address) * addressesNum);
addressesTlv.SetLength(sizeof(Ip6::Address) * aAddressNum);
SuccessOrExit(error = message->Append(&addressesTlv, sizeof(addressesTlv)));
SuccessOrExit(error = message->Append(&addresses, sizeof(Ip6::Address) * addressesNum));
SuccessOrExit(error = message->Append(aAddresses, sizeof(Ip6::Address) * aAddressNum));
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
if (Get<MeshCoP::Commissioner>().IsActive())
{
SuccessOrExit(error = ThreadTlv::AppendUint16Tlv(*message, ThreadTlv::kCommissionerSessionId,
Get<MeshCoP::Commissioner>().GetSessionId()));
}
if (aTimeout != nullptr)
{
SuccessOrExit(error = Tlv::AppendUint32Tlv(*message, ThreadTlv::kTimeout, *aTimeout));
}
#else
OT_ASSERT(aTimeout == nullptr);
#endif
if (!mle.IsFullThreadDevice() && mle.GetParent().IsThreadVersion1p1())
{
@@ -309,36 +435,26 @@ void MlrManager::SendMulticastListenerRegistration(void)
messageInfo.SetPeerPort(Tmf::kUdpPort);
messageInfo.SetSockAddr(mle.GetMeshLocal16());
SuccessOrExit(error = Get<Tmf::TmfAgent>().SendMessage(
*message, messageInfo, &MlrManager::HandleMulticastListenerRegistrationResponse, this));
mMlrPending = true;
// TODO: not enable fast polls for SSED
if (!Get<Mle::Mle>().IsRxOnWhenIdle())
{
Get<DataPollSender>().SendFastPolls(DataPollSender::kDefaultFastPolls);
}
error = Get<Tmf::TmfAgent>().SendMessage(*message, messageInfo, aResponseHandler, aResponseContext);
exit:
if (error != OT_ERROR_NONE)
otLogInfoMlr("Send MLR.req: %s, addressNum=%d", otThreadErrorToString(error), aAddressNum);
if (error != OT_ERROR_NONE && message != nullptr)
{
if (message != nullptr)
{
message->Free();
}
SetMulticastAddressMlrState(kMlrStateRegistering, kMlrStateToRegister);
if (error == OT_ERROR_NO_BUFS)
{
ScheduleSend(1);
}
message->Free();
}
otLogInfoMlr("Send MLR.req: %s", otThreadErrorToString(error));
LogMulticastAddresses();
CheckInvariants();
return error;
}
void MlrManager::HandleMulticastListenerRegistrationResponse(void * aContext,
otMessage * aMessage,
const otMessageInfo *aMessageInfo,
otError aResult)
{
static_cast<MlrManager *>(aContext)->HandleMulticastListenerRegistrationResponse(
static_cast<Coap::Message *>(aMessage), static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
}
void MlrManager::HandleMulticastListenerRegistrationResponse(Coap::Message * aMessage,
@@ -347,35 +463,12 @@ void MlrManager::HandleMulticastListenerRegistrationResponse(Coap::Message *
{
OT_UNUSED_VARIABLE(aMessageInfo);
uint8_t status = ThreadStatusTlv::MlrStatus::kMlrGeneralFailure;
otError error = OT_ERROR_NONE;
uint16_t addressesOffset, addressesLength;
uint8_t status;
otError error;
Ip6::Address failedAddresses[kIPv6AddressesNumMax];
uint8_t failedAddressNum = 0;
VerifyOrExit(aResult == OT_ERROR_NONE && aMessage != nullptr, error = OT_ERROR_PARSE);
VerifyOrExit(aMessage->GetCode() == OT_COAP_CODE_CHANGED, error = OT_ERROR_PARSE);
SuccessOrExit(error = Tlv::FindUint8Tlv(*aMessage, ThreadTlv::kStatus, status));
if (ThreadTlv::FindTlvValueOffset(*aMessage, IPv6AddressesTlv::kIPv6Addresses, addressesOffset, addressesLength) ==
OT_ERROR_NONE)
{
VerifyOrExit(addressesLength % sizeof(Ip6::Address) == 0, error = OT_ERROR_PARSE);
VerifyOrExit(addressesLength / sizeof(Ip6::Address) <= kIPv6AddressesNumMax, error = OT_ERROR_PARSE);
for (uint16_t offset = 0; offset < addressesLength; offset += sizeof(Ip6::Address))
{
IgnoreReturnValue(
aMessage->Read(addressesOffset + offset, sizeof(Ip6::Address), &failedAddresses[failedAddressNum]));
failedAddressNum++;
}
}
VerifyOrExit(failedAddressNum == 0 || status != ThreadStatusTlv::MlrStatus::kMlrSuccess, error = OT_ERROR_PARSE);
exit:
LogMlrResponse(aResult, error, status, failedAddresses, failedAddressNum);
error = ParseMulticastListenerRegistrationResponse(aResult, aMessage, status, failedAddresses, failedAddressNum);
FinishMulticastListenerRegistration(error == OT_ERROR_NONE && status == ThreadStatusTlv::MlrStatus::kMlrSuccess,
failedAddresses, failedAddressNum);
@@ -403,6 +496,43 @@ exit:
}
}
otError MlrManager::ParseMulticastListenerRegistrationResponse(otError aResult,
Coap::Message *aMessage,
uint8_t & aStatus,
Ip6::Address * aFailedAddresses,
uint8_t & aFailedAddressNum)
{
otError error;
uint16_t addressesOffset, addressesLength;
aStatus = ThreadStatusTlv::MlrStatus::kMlrGeneralFailure;
VerifyOrExit(aResult == OT_ERROR_NONE && aMessage != nullptr, error = OT_ERROR_PARSE);
VerifyOrExit(aMessage->GetCode() == OT_COAP_CODE_CHANGED, error = OT_ERROR_PARSE);
SuccessOrExit(error = Tlv::FindUint8Tlv(*aMessage, ThreadTlv::kStatus, aStatus));
if (ThreadTlv::FindTlvValueOffset(*aMessage, IPv6AddressesTlv::kIPv6Addresses, addressesOffset, addressesLength) ==
OT_ERROR_NONE)
{
VerifyOrExit(addressesLength % sizeof(Ip6::Address) == 0, error = OT_ERROR_PARSE);
VerifyOrExit(addressesLength / sizeof(Ip6::Address) <= kIPv6AddressesNumMax, error = OT_ERROR_PARSE);
for (uint16_t offset = 0; offset < addressesLength; offset += sizeof(Ip6::Address))
{
IgnoreReturnValue(
aMessage->Read(addressesOffset + offset, sizeof(Ip6::Address), &aFailedAddresses[aFailedAddressNum]));
aFailedAddressNum++;
}
}
VerifyOrExit(aFailedAddressNum == 0 || aStatus != ThreadStatusTlv::MlrStatus::kMlrSuccess, error = OT_ERROR_PARSE);
exit:
LogMlrResponse(aResult, error, aStatus, aFailedAddresses, aFailedAddressNum);
return aResult != OT_ERROR_NONE ? aResult : error;
}
void MlrManager::SetMulticastAddressMlrState(MlrState aFromState, MlrState aToState)
{
#if OPENTHREAD_CONFIG_MLR_ENABLE
@@ -485,6 +615,8 @@ void MlrManager::HandleTimeTick(void)
void MlrManager::Reregister(void)
{
otLogInfoMlr("MLR Reregister!");
SetMulticastAddressMlrState(kMlrStateRegistered, kMlrStateToRegister);
CheckInvariants();
+74 -20
View File
@@ -107,23 +107,68 @@ public:
uint16_t aOldMlrRegisteredAddressNum);
#endif
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
/**
* This method registers Multicast Listeners to Primary Backbone Router.
*
* Note: only available when both `OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE` and
* `OPENTHREAD_CONFIG_COMMISSIONER_ENABLE` are enabled)
*
* @param aAddresses A pointer to Ip6 multicast addresses to register.
* @param aAddressNum The number of Ip6 multicast addresses.
* @param aTimeout A pointer to the timeout (in seconds), or nullptr to use the default MLR timeout.
* A timeout of 0 seconds removes the Multicast Listener addresses.
* @param aCallback A callback function.
* @param aContext A user context pointer.
*
* @retval OT_ERROR_NONE Successfully sent MLR.req. The @p aCallback will be called iff this method
* returns OT_ERROR_NONE.
* @retval OT_ERROR_BUSY If a previous registration was ongoing.
* @retval OT_ERROR_INVALID_ARGS If one or more arguments are invalid.
* @retval OT_ERROR_INVALID_STATE If the device was not in a valid state to send MLR.req (e.g. Commissioner not
* started, Primary Backbone Router not found).
* @retval OT_ERROR_NO_BUFS If insufficient message buffers available.
*
*/
otError RegisterMulticastListeners(const otIp6Address * aAddresses,
uint8_t aAddressNum,
const uint32_t * aTimeout,
otIp6RegisterMulticastListenersCallback aCallback,
void * aContext);
#endif
private:
void HandleNotifierEvents(Events aEvents);
void SendMulticastListenerRegistration(void);
void SendMulticastListenerRegistration(void);
otError SendMulticastListenerRegistrationMessage(const otIp6Address * aAddresses,
uint8_t aAddressNum,
const uint32_t * aTimeout,
Coap::ResponseHandler aResponseHandler,
void * aResponseContext);
static void HandleMulticastListenerRegistrationResponse(void * aContext,
otMessage * aMessage,
const otMessageInfo *aMessageInfo,
otError aResult)
{
static_cast<MlrManager *>(aContext)->HandleMulticastListenerRegistrationResponse(
static_cast<Coap::Message *>(aMessage), static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
}
static void HandleMulticastListenerRegistrationResponse(void * aContext,
otMessage * aMessage,
const otMessageInfo *aMessageInfo,
otError aResult);
void HandleMulticastListenerRegistrationResponse(Coap::Message * aMessage,
const Ip6::MessageInfo *aMessageInfo,
otError aResult);
static otError ParseMulticastListenerRegistrationResponse(otError aResult,
Coap::Message *aMessage,
uint8_t & aStatus,
Ip6::Address * aFailedAddresses,
uint8_t & aFailedAddressNum);
void HandleMulticastListenerRegistrationResponse(Coap::Message * aMessage,
const Ip6::MessageInfo *aMessageInfo,
otError aResult);
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
static void HandleRegisterMulticastListenersResponse(void * aContext,
otMessage * aMessage,
const otMessageInfo *aMessageInfo,
otError aResult);
void HandleRegisterMulticastListenersResponse(otMessage * aMessage,
const otMessageInfo *aMessageInfo,
otError aResult);
#endif
#if OPENTHREAD_CONFIG_MLR_ENABLE
void UpdateLocalSubscriptions(void);
@@ -156,17 +201,26 @@ private:
void Reregister(void);
void HandleTimeTick(void);
void LogMulticastAddresses(void);
void CheckInvariants(void) const;
void LogMlrResponse(otError aResult,
otError aError,
uint8_t aStatus,
const Ip6::Address *aFailedAddresses,
uint8_t aFailedAddressNum);
void LogMulticastAddresses(void);
void CheckInvariants(void) const;
static void LogMlrResponse(otError aResult,
otError aError,
uint8_t aStatus,
const Ip6::Address *aFailedAddresses,
uint8_t aFailedAddressNum);
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
otIp6RegisterMulticastListenersCallback mRegisterMulticastListenersCallback;
void * mRegisterMulticastListenersContext;
#endif
uint32_t mReregistrationDelay;
uint16_t mSendDelay;
bool mMlrPending : 1;
bool mMlrPending : 1;
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
bool mRegisterMulticastListenersPending : 1;
#endif
};
} // namespace ot
+13 -11
View File
@@ -69,17 +69,19 @@ public:
*/
enum Type
{
kTarget = 0, ///< Target EID TLV
kExtMacAddress = 1, ///< Extended MAC Address TLV
kRloc16 = 2, ///< RLOC16 TLV
kMeshLocalEid = 3, ///< ML-EID TLV
kStatus = 4, ///< Status TLV
kLastTransactionTime = 6, ///< Time Since Last Transaction TLV
kRouterMask = 7, ///< Router Mask TLV
kNDOption = 8, ///< ND Option TLV
kNDData = 9, ///< ND Data TLV
kThreadNetworkData = 10, ///< Thread Network Data TLV
kIPv6Addresses = 14, ///< IPv6 Addresses TLV
kTarget = 0, ///< Target EID TLV
kExtMacAddress = 1, ///< Extended MAC Address TLV
kRloc16 = 2, ///< RLOC16 TLV
kMeshLocalEid = 3, ///< ML-EID TLV
kStatus = 4, ///< Status TLV
kLastTransactionTime = 6, ///< Time Since Last Transaction TLV
kRouterMask = 7, ///< Router Mask TLV
kNDOption = 8, ///< ND Option TLV
kNDData = 9, ///< ND Data TLV
kThreadNetworkData = 10, ///< Thread Network Data TLV
kTimeout = 11, ///< Timeout TLV
kIPv6Addresses = 14, ///< IPv6 Addresses TLV
kCommissionerSessionId = 15, ///< Commissioner Session ID TLV
};
/**
+49
View File
@@ -281,6 +281,37 @@ class Node:
return results
def _expect_command_output(self, cmd: str):
lines = []
cmd_output_started = False
while True:
self._expect(r"[^\n]+")
line = self.pexpect.match.group(0).decode('utf8').strip()
if line.startswith('> '):
line = line[2:]
if line == '':
continue
if line == cmd:
cmd_output_started = True
continue
if not cmd_output_started:
continue
if line == 'Done':
break
elif line.startswith('Error '):
raise Exception(line)
else:
lines.append(line)
print(f'_expect_command_output({cmd!r}) returns {lines!r}')
return lines
def __init_soc(self, nodeid):
""" Initialize a System-on-a-chip node connected via UART. """
import fdpexpect
@@ -564,6 +595,24 @@ class Node:
self.send_command(cmd)
self._expect('Done')
def register_multicast_listener(self, *ipaddrs: Union[ipaddress.IPv6Address, str], timeout=None):
assert len(ipaddrs) > 0, ipaddrs
cmd = f'mlr reg {" ".join(ipaddrs)}'
if timeout is not None:
cmd += f' {int(timeout)}'
self.send_command(cmd)
self.simulator.go(3)
lines = self._expect_command_output(cmd)
m = re.match(r'status (\d+), (\d+) failed', lines[0])
assert m is not None, lines
status = int(m.group(1))
failed_num = int(m.group(2))
assert failed_num == len(lines) - 1
failed_ips = list(map(ipaddress.IPv6Address, lines[1:]))
print(f"register_multicast_listener {ipaddrs} => status: {status}, failed ips: {failed_ips}")
return status, failed_ips
def set_link_quality(self, addr, lqi):
cmd = 'macfilter rss add-lqi %s %s' % (addr, lqi)
self.send_command(cmd)
@@ -339,6 +339,104 @@ class TestMulticastListenerRegistration(thread_cert.TestCase):
self.simulator.go(WAIT_REDUNDANCE)
self.flush_all()
def testCommissionerRegisterMulticastListeners(self):
self._bootstrap()
# Use ROUTER_1_2 as the Commissioner
commissioiner = self.nodes[ROUTER_1_2]
self.assertRaisesRegex(Exception, "InvalidState", lambda: commissioiner.register_multicast_listener("ff04::1"))
commissioiner.commissioner_start()
self.simulator.go(10)
# Now the Commissioner should be able to register MAs
for ip in ["ff04::1", "ff04::2"]:
status, failed_ips = commissioiner.register_multicast_listener(ip)
self.assertTrue(status == 0 and not failed_ips)
self.__check_multicast_listener(ip, expect_mlr_timeout_range=[290, 300])
# Register existing MA with a new timeout should be able to update the timeout
for ip in ["ff04::1", "ff04::2"]:
status, failed_ips = commissioiner.register_multicast_listener(ip, timeout=1000)
self.assertTrue(status == 0 and not failed_ips)
self.__check_multicast_listener(ip, expect_mlr_timeout_range=[990, 1000])
# Register MAs with given timeouts
for ip, timeout in [("ff05::1", 400), ("ff05::2", 500), ("ff05::3", 600)]:
status, failed_ips = commissioiner.register_multicast_listener(ip, timeout=timeout)
self.assertTrue(status == 0 and not failed_ips)
self.__check_multicast_listener(ip, expect_mlr_timeout_range=[timeout - 10, timeout])
# Register multiple MAs with one call
ips = ["ff05::4", "ff05::5", "ff05::6"]
status, failed_ips = commissioiner.register_multicast_listener(*ips, timeout=700)
self.assertTrue(status == 0 and not failed_ips)
for ip in ips:
self.__check_multicast_listener(ip, expect_mlr_timeout_range=[690, 700])
# Register multiple MAs with one call (without timeout)
ips = ["ff05::7", "ff05::8", "ff05::9"]
status, failed_ips = commissioiner.register_multicast_listener(*ips)
self.assertTrue(status == 0 and not failed_ips)
for ip in ips:
self.__check_multicast_listener(ip, expect_mlr_timeout_range=[290, 300])
# Unregister MAs using timeout=0
for ip in ["ff05::1", "ff05::2", "ff05::3"]:
status, failed_ips = commissioiner.register_multicast_listener(ip, timeout=0)
self.assertTrue(status == 0 and not failed_ips)
self.__check_multicast_listener(ip, expect_not_present=True)
# Unregister multiple MAs
ips = ["ff05::4", "ff05::5", "ff05::6"]
status, failed_ips = commissioiner.register_multicast_listener(*ips, timeout=0)
self.assertTrue(status == 0 and not failed_ips)
self.__check_multicast_listener(ip, expect_not_present=True)
# Remove MAs that are not subscribed should not fail
ips = ["ff06::1", "ff02::1", "2001::1"]
status, failed_ips = commissioiner.register_multicast_listener(*ips, timeout=0)
self.assertTrue(status == 0 and not failed_ips)
self.__check_multicast_listener(ip, expect_not_present=True)
# Register invalid MAs should fail
for ip in ["ff02::1", "ff03::1", "2001::1"]:
status, failed_ips = commissioiner.register_multicast_listener(ip)
self.assertEqual(status, 2)
self.assertEqual(set(failed_ips), {ipaddress.IPv6Address(ip)})
self.__check_multicast_listener(ip, expect_not_present=True)
# Register valid MAs with invalid MAs should succeed partially
ips = ["ff05::1", "ff05::2", "ff02::1", "2001::1"]
status, failed_ips = commissioiner.register_multicast_listener(*ips)
self.assertEqual(status, 2)
self.assertEqual(set(failed_ips), {ipaddress.IPv6Address("ff02::1"), ipaddress.IPv6Address("2001::1")})
self.__check_multicast_listener("ff05::1")
self.__check_multicast_listener("ff05::2")
self.__check_multicast_listener("ff02::1", expect_not_present=True)
self.__check_multicast_listener("2001::1", expect_not_present=True)
# Registering persistent MAs should fail for now
status, failed_ips = commissioiner.register_multicast_listener("ff06::1", timeout=0xffffffff)
self.assertEqual(status, 3)
# "ff06::1" should not be included in failed IPs because all IPs failed
self.assertTrue(not failed_ips == 0)
def __check_multicast_listener(self, *addrs, expect_mlr_timeout_range=None, expect_not_present=False):
addrs = map(ipaddress.IPv6Address, addrs)
listeners = self.nodes[BBR_1].multicast_listener_list()
logging.info("__check_multicast_listener get listeners: %s", listeners)
for addr in addrs:
if not expect_not_present:
self.assertIn(addr, listeners)
if expect_mlr_timeout_range is not None:
self.assertGreaterEqual(listeners[addr], expect_mlr_timeout_range[0])
self.assertLessEqual(listeners[addr], expect_mlr_timeout_range[1])
else:
self.assertNotIn(addr, listeners)
def __test_multicast_listeners_table_api(self):
self.assertTrue(self.nodes[BBR_1].multicast_listener_list() == {})