blob: e5f07c433db02649da46b9d455e3014e26e60f6a [file] [log] [blame]
/*
* Copyright (c) 2020, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements Backbone Router management.
*/
#include "bbr_manager.hpp"
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
#include "common/code_utils.hpp"
#include "common/instance.hpp"
#include "common/locator-getters.hpp"
#include "common/logging.hpp"
#include "common/random.hpp"
#include "thread/mle_types.hpp"
#include "thread/thread_netif.hpp"
#include "thread/thread_tlvs.hpp"
#include "thread/uri_paths.hpp"
namespace ot {
namespace BackboneRouter {
Manager::Manager(Instance &aInstance)
: InstanceLocator(aInstance)
, mMulticastListenerRegistration(UriPath::kMlr, Manager::HandleMulticastListenerRegistration, this)
, mDuaRegistration(UriPath::kDuaRegistrationRequest, Manager::HandleDuaRegistration, this)
, mNdProxyTable(aInstance)
, mMulticastListenersTable(aInstance)
, mTimer(aInstance, Manager::HandleTimer, this)
, mBackboneTmfAgent(aInstance)
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
, mDuaResponseStatus(ThreadStatusTlv::kDuaSuccess)
, mMlrResponseStatus(ThreadStatusTlv::kMlrSuccess)
, mDuaResponseIsSpecified(false)
, mMlrResponseIsSpecified(false)
#endif
{
}
void Manager::HandleNotifierEvents(Events aEvents)
{
otError error;
if (aEvents.Contains(kEventThreadBackboneRouterStateChanged))
{
if (Get<BackboneRouter::Local>().GetState() == OT_BACKBONE_ROUTER_STATE_DISABLED)
{
Get<Tmf::TmfAgent>().RemoveResource(mMulticastListenerRegistration);
Get<Tmf::TmfAgent>().RemoveResource(mDuaRegistration);
mTimer.Stop();
mMulticastListenersTable.Clear();
error = mBackboneTmfAgent.Stop();
if (error != OT_ERROR_NONE)
{
otLogWarnBbr("Stop Backbone TMF agent: %s", otThreadErrorToString(error));
}
else
{
otLogInfoBbr("Stop Backbone TMF agent: %s", otThreadErrorToString(error));
}
}
else
{
Get<Tmf::TmfAgent>().AddResource(mMulticastListenerRegistration);
Get<Tmf::TmfAgent>().AddResource(mDuaRegistration);
if (!mTimer.IsRunning())
{
mTimer.Start(kTimerInterval);
}
error = mBackboneTmfAgent.Start();
if (error != OT_ERROR_NONE)
{
otLogCritBbr("Start Backbone TMF agent: %s", otThreadErrorToString(error));
}
else
{
otLogInfoBbr("Start Backbone TMF agent: %s", otThreadErrorToString(error));
}
}
}
}
void Manager::HandleTimer(void)
{
mMulticastListenersTable.Expire();
mTimer.Start(kTimerInterval);
}
void Manager::HandleMulticastListenerRegistration(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
otError error = OT_ERROR_NONE;
bool isPrimary = Get<BackboneRouter::Local>().IsPrimary();
ThreadStatusTlv::MlrStatus status = ThreadStatusTlv::kMlrSuccess;
BackboneRouterConfig config;
uint16_t addressesOffset, addressesLength;
Ip6::Address address;
Ip6::Address addresses[kIPv6AddressesNumMax];
uint8_t failedAddressNum = 0;
uint8_t successAddressNum = 0;
TimeMilli expireTime;
uint32_t timeout;
uint16_t commissionerSessionId;
bool hasCommissionerSessionIdTlv = false;
bool processTimeoutTlv = false;
VerifyOrExit(aMessage.IsConfirmablePostRequest(), error = OT_ERROR_PARSE);
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
// Required by Test Specification 5.10.22 DUA-TC-26, only for certification purpose
if (mMlrResponseIsSpecified)
{
mMlrResponseIsSpecified = false;
ExitNow(status = mMlrResponseStatus);
}
#endif
VerifyOrExit(isPrimary, status = ThreadStatusTlv::kMlrBbrNotPrimary);
// 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,
error = OT_ERROR_PARSE);
VerifyOrExit(addressesLength % sizeof(Ip6::Address) == 0, status = ThreadStatusTlv::kMlrGeneralFailure);
VerifyOrExit(addressesLength / sizeof(Ip6::Address) <= kIPv6AddressesNumMax,
status = ThreadStatusTlv::kMlrGeneralFailure);
if (!processTimeoutTlv)
{
IgnoreError(Get<BackboneRouter::Leader>().GetConfig(config));
timeout = config.mMlrTimeout;
}
else
{
VerifyOrExit(timeout < UINT32_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))
{
IgnoreError(aMessage.Read(addressesOffset + offset, address));
if (timeout == 0)
{
mMulticastListenersTable.Remove(address);
}
else
{
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)
{
addresses[failedAddressNum++] = address;
}
else
{
// Put successfully registered addresses at the end of `addresses`.
addresses[kIPv6AddressesNumMax - (++successAddressNum)] = address;
}
}
}
exit:
if (error == OT_ERROR_NONE)
{
SendMulticastListenerRegistrationResponse(aMessage, aMessageInfo, status, addresses, failedAddressNum);
}
if (successAddressNum > 0)
{
SendBackboneMulticastListenerRegistration(&addresses[kIPv6AddressesNumMax - successAddressNum],
successAddressNum, timeout);
}
}
void Manager::SendMulticastListenerRegistrationResponse(const Coap::Message & aMessage,
const Ip6::MessageInfo & aMessageInfo,
ThreadStatusTlv::MlrStatus aStatus,
Ip6::Address * aFailedAddresses,
uint8_t aFailedAddressNum)
{
otError error = OT_ERROR_NONE;
Coap::Message *message = nullptr;
VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
SuccessOrExit(message->SetDefaultResponseHeader(aMessage));
SuccessOrExit(message->SetPayloadMarker());
SuccessOrExit(Tlv::AppendUint8Tlv(*message, ThreadTlv::kStatus, aStatus));
if (aFailedAddressNum > 0)
{
IPv6AddressesTlv addressesTlv;
addressesTlv.Init();
addressesTlv.SetLength(sizeof(Ip6::Address) * aFailedAddressNum);
SuccessOrExit(error = message->Append(addressesTlv));
for (uint8_t i = 0; i < aFailedAddressNum; i++)
{
SuccessOrExit(error = message->Append(aFailedAddresses[i]));
}
}
SuccessOrExit(error = Get<Tmf::TmfAgent>().SendMessage(*message, aMessageInfo));
exit:
FreeMessageOnError(message, error);
otLogInfoBbr("Sent MLR.rsp (status=%d): %s", aStatus, otThreadErrorToString(error));
}
void Manager::SendBackboneMulticastListenerRegistration(const Ip6::Address *aAddresses,
uint8_t aAddressNum,
uint32_t aTimeout)
{
otError error = OT_ERROR_NONE;
Coap::Message * message = nullptr;
Ip6::MessageInfo messageInfo;
IPv6AddressesTlv addressesTlv;
BackboneTmfAgent &backboneTmf = Get<BackboneRouter::BackboneTmfAgent>();
OT_ASSERT(aAddressNum >= kIPv6AddressesNumMin && aAddressNum <= kIPv6AddressesNumMax);
VerifyOrExit((message = backboneTmf.NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
SuccessOrExit(error = message->InitAsNonConfirmablePost(UriPath::kBackboneMlr));
SuccessOrExit(error = message->SetPayloadMarker());
addressesTlv.Init();
addressesTlv.SetLength(sizeof(Ip6::Address) * aAddressNum);
SuccessOrExit(error = message->Append(addressesTlv));
SuccessOrExit(error = message->AppendBytes(aAddresses, sizeof(Ip6::Address) * aAddressNum));
SuccessOrExit(error = ThreadTlv::AppendUint32Tlv(*message, ThreadTlv::kTimeout, aTimeout));
messageInfo.SetPeerAddr(Get<BackboneRouter::Local>().GetAllNetworkBackboneRoutersAddress());
messageInfo.SetPeerPort(BackboneRouter::kBackboneUdpPort); // TODO: Provide API for configuring Backbone COAP port.
messageInfo.SetSockAddr(Get<Mle::MleRouter>().GetMeshLocal16());
messageInfo.SetHopLimit(Mle::kDefaultBackboneHoplimit);
messageInfo.SetIsHostInterface(true);
SuccessOrExit(error = backboneTmf.SendMessage(*message, messageInfo, nullptr, nullptr));
exit:
FreeMessageOnError(message, error);
otLogInfoBbr("Sent BMLR.ntf: %s", otThreadErrorToString(error));
}
void Manager::HandleDuaRegistration(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
otError error = OT_ERROR_NONE;
ThreadStatusTlv::DuaStatus status = ThreadStatusTlv::kDuaSuccess;
bool isPrimary = Get<BackboneRouter::Local>().IsPrimary();
uint32_t lastTransactionTime;
bool hasLastTransactionTime;
Ip6::Address target;
Ip6::InterfaceIdentifier meshLocalIid;
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
Coap::Code duaRespCoapCode = Coap::kCodeEmpty;
#endif
VerifyOrExit(aMessageInfo.GetPeerAddr().GetIid().IsRoutingLocator(), error = OT_ERROR_DROP);
VerifyOrExit(aMessage.IsConfirmablePostRequest(), error = OT_ERROR_PARSE);
SuccessOrExit(error = Tlv::FindTlv(aMessage, ThreadTlv::kTarget, &target, sizeof(target)));
SuccessOrExit(error = Tlv::FindTlv(aMessage, ThreadTlv::kMeshLocalEid, &meshLocalIid, sizeof(meshLocalIid)));
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
if (mDuaResponseIsSpecified && (mDuaResponseTargetMlIid.IsUnspecified() || mDuaResponseTargetMlIid == meshLocalIid))
{
mDuaResponseIsSpecified = false;
if (mDuaResponseStatus >= Coap::kCodeResponseMin)
{
duaRespCoapCode = static_cast<Coap::Code>(mDuaResponseStatus);
}
else
{
status = static_cast<ThreadStatusTlv::DuaStatus>(mDuaResponseStatus);
}
ExitNow();
}
#endif
VerifyOrExit(isPrimary, status = ThreadStatusTlv::kDuaNotPrimary);
VerifyOrExit(Get<BackboneRouter::Leader>().IsDomainUnicast(target), status = ThreadStatusTlv::kDuaInvalid);
hasLastTransactionTime =
(Tlv::FindUint32Tlv(aMessage, ThreadTlv::kLastTransactionTime, lastTransactionTime) == OT_ERROR_NONE);
switch (mNdProxyTable.Register(target.GetIid(), meshLocalIid, aMessageInfo.GetPeerAddr().GetIid().GetLocator(),
hasLastTransactionTime ? &lastTransactionTime : nullptr))
{
case OT_ERROR_NONE:
// TODO: update its EID-to-RLOC Map Cache based on the pair {DUA, RLOC16-source} which is gleaned from the
// DUA.req packet according to Thread Spec. 5.23.3.6.2
break;
case OT_ERROR_DUPLICATED:
status = ThreadStatusTlv::kDuaDuplicate;
break;
case OT_ERROR_NO_BUFS:
status = ThreadStatusTlv::kDuaNoResources;
break;
default:
status = ThreadStatusTlv::kDuaGeneralFailure;
break;
}
// TODO: (DUA) Add DAD process
// TODO: (DUA) Extended Address Query
exit:
otLogInfoBbr("Received DUA.req on %s: %s", (isPrimary ? "PBBR" : "SBBR"), otThreadErrorToString(error));
if (error == OT_ERROR_NONE)
{
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
if (duaRespCoapCode != Coap::kCodeEmpty)
{
IgnoreError(Get<Tmf::TmfAgent>().SendEmptyAck(aMessage, aMessageInfo, duaRespCoapCode));
}
else
#endif
{
SendDuaRegistrationResponse(aMessage, aMessageInfo, target, status);
}
}
}
void Manager::SendDuaRegistrationResponse(const Coap::Message & aMessage,
const Ip6::MessageInfo & aMessageInfo,
const Ip6::Address & aTarget,
ThreadStatusTlv::DuaStatus aStatus)
{
otError error = OT_ERROR_NONE;
Coap::Message *message = nullptr;
VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
SuccessOrExit(message->SetDefaultResponseHeader(aMessage));
SuccessOrExit(message->SetPayloadMarker());
SuccessOrExit(Tlv::AppendUint8Tlv(*message, ThreadTlv::kStatus, aStatus));
SuccessOrExit(Tlv::AppendTlv(*message, ThreadTlv::kTarget, &aTarget, sizeof(aTarget)));
SuccessOrExit(error = Get<Tmf::TmfAgent>().SendMessage(*message, aMessageInfo));
exit:
FreeMessageOnError(message, error);
otLogInfoBbr("Sent DUA.rsp for DUA %s, status %d %s", aTarget.ToString().AsCString(), aStatus,
otThreadErrorToString(error));
}
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
void Manager::ConfigNextDuaRegistrationResponse(const Ip6::InterfaceIdentifier *aMlIid, uint8_t aStatus)
{
mDuaResponseIsSpecified = true;
if (aMlIid)
{
mDuaResponseTargetMlIid = *aMlIid;
}
else
{
mDuaResponseTargetMlIid.Clear();
}
mDuaResponseStatus = aStatus;
}
void Manager::ConfigNextMulticastListenerRegistrationResponse(ThreadStatusTlv::MlrStatus aStatus)
{
mMlrResponseIsSpecified = true;
mMlrResponseStatus = aStatus;
}
#endif
NdProxyTable &Manager::GetNdProxyTable(void)
{
return mNdProxyTable;
}
bool Manager::ShouldForwardDuaToBackbone(const Ip6::Address &aAddress)
{
bool forwardToBackbone = false;
Mac::ShortAddress rloc16;
otError error;
VerifyOrExit(Get<Local>().IsPrimary());
VerifyOrExit(Get<Leader>().IsDomainUnicast(aAddress));
VerifyOrExit(!mNdProxyTable.IsRegistered(aAddress.GetIid()));
error = Get<AddressResolver>().Resolve(aAddress, rloc16, /* aAllowAddressQuery */ false);
VerifyOrExit(error != OT_ERROR_NONE || rloc16 == Get<Mle::MleRouter>().GetRloc16());
// TODO: check if the DUA is an address of any Child?
forwardToBackbone = true;
exit:
return forwardToBackbone;
}
} // namespace BackboneRouter
} // namespace ot
#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE