/* * Copyright (c) 2018, 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 the BorderAgent service. */ #include "border_agent.hpp" #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE #include "coap/coap_message.hpp" #include "common/as_core_type.hpp" #include "common/heap.hpp" #include "common/instance.hpp" #include "common/locator_getters.hpp" #include "common/log.hpp" #include "meshcop/meshcop.hpp" #include "meshcop/meshcop_tlvs.hpp" #include "thread/thread_netif.hpp" #include "thread/thread_tlvs.hpp" #include "thread/uri_paths.hpp" namespace ot { namespace MeshCoP { RegisterLogModule("BorderAgent"); namespace { constexpr uint16_t kBorderAgentUdpPort = OPENTHREAD_CONFIG_BORDER_AGENT_UDP_PORT; ///< UDP port of border agent service. } void BorderAgent::ForwardContext::Init(Instance & aInstance, const Coap::Message &aMessage, bool aPetition, bool aSeparate) { InstanceLocatorInit::Init(aInstance); mMessageId = aMessage.GetMessageId(); mPetition = aPetition; mSeparate = aSeparate; mType = aMessage.GetType(); mTokenLength = aMessage.GetTokenLength(); memcpy(mToken, aMessage.GetToken(), mTokenLength); } Error BorderAgent::ForwardContext::ToHeader(Coap::Message &aMessage, uint8_t aCode) { if ((mType == Coap::kTypeNonConfirmable) || mSeparate) { aMessage.Init(Coap::kTypeNonConfirmable, static_cast(aCode)); } else { aMessage.Init(Coap::kTypeAck, static_cast(aCode)); } if (!mSeparate) { aMessage.SetMessageId(mMessageId); } return aMessage.SetToken(mToken, mTokenLength); } Coap::Message::Code BorderAgent::CoapCodeFromError(Error aError) { Coap::Message::Code code; switch (aError) { case kErrorNone: code = Coap::kCodeChanged; break; case kErrorParse: code = Coap::kCodeBadRequest; break; default: code = Coap::kCodeInternalError; break; } return code; } void BorderAgent::SendErrorMessage(ForwardContext &aForwardContext, Error aError) { Error error = kErrorNone; Coap::CoapSecure &coaps = Get(); Coap::Message * message = nullptr; VerifyOrExit((message = coaps.NewPriorityMessage()) != nullptr, error = kErrorNoBufs); SuccessOrExit(error = aForwardContext.ToHeader(*message, CoapCodeFromError(aError))); SuccessOrExit(error = coaps.SendMessage(*message, coaps.GetMessageInfo())); exit: FreeMessageOnError(message, error); LogError("send error CoAP message", error); } void BorderAgent::SendErrorMessage(const Coap::Message &aRequest, bool aSeparate, Error aError) { Error error = kErrorNone; Coap::CoapSecure &coaps = Get(); Coap::Message * message = nullptr; VerifyOrExit((message = coaps.NewPriorityMessage()) != nullptr, error = kErrorNoBufs); if (aRequest.IsNonConfirmable() || aSeparate) { message->Init(Coap::kTypeNonConfirmable, CoapCodeFromError(aError)); } else { message->Init(Coap::kTypeAck, CoapCodeFromError(aError)); } if (!aSeparate) { message->SetMessageId(aRequest.GetMessageId()); } SuccessOrExit(error = message->SetTokenFromMessage(aRequest)); SuccessOrExit(error = coaps.SendMessage(*message, coaps.GetMessageInfo())); exit: FreeMessageOnError(message, error); LogError("send error CoAP message", error); } void BorderAgent::HandleCoapResponse(void * aContext, otMessage * aMessage, const otMessageInfo *aMessageInfo, Error aResult) { OT_UNUSED_VARIABLE(aMessageInfo); ForwardContext &forwardContext = *static_cast(aContext); forwardContext.Get().HandleCoapResponse(forwardContext, AsCoapMessagePtr(aMessage), aResult); } void BorderAgent::HandleCoapResponse(ForwardContext &aForwardContext, const Coap::Message *aResponse, Error aResult) { Coap::Message *message = nullptr; Error error; SuccessOrExit(error = aResult); VerifyOrExit((message = Get().NewPriorityMessage()) != nullptr, error = kErrorNoBufs); if (aForwardContext.IsPetition() && aResponse->GetCode() == Coap::kCodeChanged) { uint8_t state; SuccessOrExit(error = Tlv::Find(*aResponse, state)); if (state == StateTlv::kAccept) { uint16_t sessionId; SuccessOrExit(error = Tlv::Find(*aResponse, sessionId)); IgnoreError(Get().GetCommissionerAloc(mCommissionerAloc.GetAddress(), sessionId)); Get().AddUnicastAddress(mCommissionerAloc); IgnoreError(Get().AddReceiver(mUdpReceiver)); LogInfo("commissioner accepted: session ID=%d, ALOC=%s", sessionId, mCommissionerAloc.GetAddress().ToString().AsCString()); } } SuccessOrExit(error = aForwardContext.ToHeader(*message, aResponse->GetCode())); if (aResponse->GetLength() > aResponse->GetOffset()) { SuccessOrExit(error = message->SetPayloadMarker()); } SuccessOrExit(error = ForwardToCommissioner(*message, *aResponse)); exit: if (error != kErrorNone) { FreeMessage(message); LogWarn("Commissioner request[%hu] failed: %s", aForwardContext.GetMessageId(), ErrorToString(error)); SendErrorMessage(aForwardContext, error); } Heap::Free(&aForwardContext); } template void BorderAgent::HandleRequest(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) { IgnoreError(static_cast(aContext)->ForwardToLeader( AsCoapMessage(aMessage), AsCoreType(aMessageInfo), (static_cast(aContext)->*aResource).GetUriPath(), false, false)); } template <> void BorderAgent::HandleRequest<&BorderAgent::mCommissionerPetition>(void * aContext, otMessage * aMessage, const otMessageInfo *aMessageInfo) { IgnoreError(static_cast(aContext)->ForwardToLeader(AsCoapMessage(aMessage), AsCoreType(aMessageInfo), UriPath::kLeaderPetition, true, true)); } template <> void BorderAgent::HandleRequest<&BorderAgent::mCommissionerKeepAlive>(void * aContext, otMessage * aMessage, const otMessageInfo *aMessageInfo) { static_cast(aContext)->HandleKeepAlive(AsCoapMessage(aMessage), AsCoreType(aMessageInfo)); } template <> void BorderAgent::HandleRequest<&BorderAgent::mRelayTransmit>(void * aContext, otMessage * aMessage, const otMessageInfo *aMessageInfo) { OT_UNUSED_VARIABLE(aMessageInfo); static_cast(aContext)->HandleRelayTransmit(AsCoapMessage(aMessage)); } template <> void BorderAgent::HandleRequest<&BorderAgent::mRelayReceive>(void * aContext, otMessage * aMessage, const otMessageInfo *aMessageInfo) { OT_UNUSED_VARIABLE(aMessageInfo); static_cast(aContext)->HandleRelayReceive(AsCoapMessage(aMessage)); } template <> void BorderAgent::HandleRequest<&BorderAgent::mProxyTransmit>(void * aContext, otMessage * aMessage, const otMessageInfo *aMessageInfo) { OT_UNUSED_VARIABLE(aMessageInfo); static_cast(aContext)->HandleProxyTransmit(AsCoapMessage(aMessage)); } BorderAgent::BorderAgent(Instance &aInstance) : InstanceLocator(aInstance) , mCommissionerPetition(UriPath::kCommissionerPetition, BorderAgent::HandleRequest<&BorderAgent::mCommissionerPetition>, this) , mCommissionerKeepAlive(UriPath::kCommissionerKeepAlive, BorderAgent::HandleRequest<&BorderAgent::mCommissionerKeepAlive>, this) , mRelayTransmit(UriPath::kRelayTx, BorderAgent::HandleRequest<&BorderAgent::mRelayTransmit>, this) , mRelayReceive(UriPath::kRelayRx, BorderAgent::HandleRequest<&BorderAgent::mRelayReceive>, this) , mCommissionerGet(UriPath::kCommissionerGet, BorderAgent::HandleRequest<&BorderAgent::mCommissionerGet>, this) , mCommissionerSet(UriPath::kCommissionerSet, BorderAgent::HandleRequest<&BorderAgent::mCommissionerSet>, this) , mActiveGet(UriPath::kActiveGet, BorderAgent::HandleRequest<&BorderAgent::mActiveGet>, this) , mActiveSet(UriPath::kActiveSet, BorderAgent::HandleRequest<&BorderAgent::mActiveSet>, this) , mPendingGet(UriPath::kPendingGet, BorderAgent::HandleRequest<&BorderAgent::mPendingGet>, this) , mPendingSet(UriPath::kPendingSet, BorderAgent::HandleRequest<&BorderAgent::mPendingSet>, this) , mProxyTransmit(UriPath::kProxyTx, BorderAgent::HandleRequest<&BorderAgent::mProxyTransmit>, this) , mUdpReceiver(BorderAgent::HandleUdpReceive, this) , mTimer(aInstance, HandleTimeout) , mState(kStateStopped) , mUdpProxyPort(0) { mCommissionerAloc.InitAsThreadOriginRealmLocalScope(); } void BorderAgent::HandleNotifierEvents(Events aEvents) { VerifyOrExit(aEvents.ContainsAny(kEventThreadRoleChanged | kEventCommissionerStateChanged)); #if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD VerifyOrExit(Get().IsDisabled()); #endif if (Get().IsAttached()) { Start(); } else { Stop(); } exit: return; } void BorderAgent::HandleProxyTransmit(const Coap::Message &aMessage) { Message * message = nullptr; Ip6::MessageInfo messageInfo; uint16_t offset; Error error; UdpEncapsulationTlv tlv; SuccessOrExit(error = Tlv::FindTlvOffset(aMessage, Tlv::kUdpEncapsulation, offset)); SuccessOrExit(error = aMessage.Read(offset, tlv)); VerifyOrExit((message = Get().NewMessage(0)) != nullptr, error = kErrorNoBufs); SuccessOrExit(error = message->SetLength(tlv.GetUdpLength())); aMessage.CopyTo(offset + sizeof(tlv), 0, tlv.GetUdpLength(), *message); VerifyOrExit(tlv.GetSourcePort() > 0 && tlv.GetDestinationPort() > 0, error = kErrorDrop); messageInfo.SetSockPort(tlv.GetSourcePort()); messageInfo.SetSockAddr(mCommissionerAloc.GetAddress()); messageInfo.SetPeerPort(tlv.GetDestinationPort()); SuccessOrExit(error = Tlv::Find(aMessage, messageInfo.GetPeerAddr())); SuccessOrExit(error = Get().SendDatagram(*message, messageInfo, Ip6::kProtoUdp)); mUdpProxyPort = tlv.GetSourcePort(); LogInfo("Proxy transmit sent to %s", messageInfo.GetPeerAddr().ToString().AsCString()); exit: FreeMessageOnError(message, error); LogError("send proxy stream", error); } bool BorderAgent::HandleUdpReceive(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { Error error; Coap::Message *message = nullptr; VerifyOrExit(aMessageInfo.GetSockAddr() == mCommissionerAloc.GetAddress(), error = kErrorDestinationAddressFiltered); VerifyOrExit(aMessage.GetLength() > 0, error = kErrorNone); message = Get().NewPriorityNonConfirmablePostMessage(UriPath::kProxyRx); VerifyOrExit(message != nullptr, error = kErrorNoBufs); { UdpEncapsulationTlv tlv; uint16_t offset; uint16_t udpLength = aMessage.GetLength() - aMessage.GetOffset(); tlv.Init(); tlv.SetSourcePort(aMessageInfo.GetPeerPort()); tlv.SetDestinationPort(aMessageInfo.GetSockPort()); tlv.SetUdpLength(udpLength); SuccessOrExit(error = message->Append(tlv)); offset = message->GetLength(); SuccessOrExit(error = message->SetLength(offset + udpLength)); aMessage.CopyTo(aMessage.GetOffset(), offset, udpLength, *message); } SuccessOrExit(error = Tlv::Append(*message, aMessageInfo.GetPeerAddr())); SuccessOrExit(error = Get().SendMessage(*message, Get().GetMessageInfo())); LogInfo("Sent to commissioner on %s", UriPath::kProxyRx); exit: FreeMessageOnError(message, error); LogError("notify commissioner on ProxyRx (c/ur)", error); return error != kErrorDestinationAddressFiltered; } void BorderAgent::HandleRelayReceive(const Coap::Message &aMessage) { Coap::Message *message = nullptr; Error error; VerifyOrExit(aMessage.IsNonConfirmablePostRequest(), error = kErrorDrop); message = Get().NewPriorityNonConfirmablePostMessage(UriPath::kRelayRx); VerifyOrExit(message != nullptr, error = kErrorNoBufs); SuccessOrExit(error = ForwardToCommissioner(*message, aMessage)); LogInfo("Sent to commissioner on %s", UriPath::kRelayRx); exit: FreeMessageOnError(message, error); } Error BorderAgent::ForwardToCommissioner(Coap::Message &aForwardMessage, const Message &aMessage) { Error error = kErrorNone; uint16_t offset = 0; offset = aForwardMessage.GetLength(); SuccessOrExit(error = aForwardMessage.SetLength(offset + aMessage.GetLength() - aMessage.GetOffset())); aMessage.CopyTo(aMessage.GetOffset(), offset, aMessage.GetLength() - aMessage.GetOffset(), aForwardMessage); SuccessOrExit(error = Get().SendMessage(aForwardMessage, Get().GetMessageInfo())); LogInfo("Sent to commissioner"); exit: LogError("send to commissioner", error); return error; } void BorderAgent::HandleKeepAlive(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { Error error; error = ForwardToLeader(aMessage, aMessageInfo, UriPath::kLeaderKeepAlive, false, true); if (error == kErrorNone) { mTimer.Start(kKeepAliveTimeout); } } void BorderAgent::HandleRelayTransmit(const Coap::Message &aMessage) { Error error = kErrorNone; uint16_t joinerRouterRloc; Coap::Message * message = nullptr; Tmf::MessageInfo messageInfo(GetInstance()); uint16_t offset = 0; VerifyOrExit(aMessage.IsNonConfirmablePostRequest()); SuccessOrExit(error = Tlv::Find(aMessage, joinerRouterRloc)); message = Get().NewPriorityNonConfirmablePostMessage(UriPath::kRelayTx); VerifyOrExit(message != nullptr, error = kErrorNoBufs); offset = message->GetLength(); SuccessOrExit(error = message->SetLength(offset + aMessage.GetLength() - aMessage.GetOffset())); aMessage.CopyTo(aMessage.GetOffset(), offset, aMessage.GetLength() - aMessage.GetOffset(), *message); messageInfo.SetSockAddrToRlocPeerAddrTo(joinerRouterRloc); messageInfo.SetSockPortToTmf(); SuccessOrExit(error = Get().SendMessage(*message, messageInfo)); LogInfo("Sent to joiner router request on %s", UriPath::kRelayTx); exit: FreeMessageOnError(message, error); LogError("send to joiner router request RelayTx (c/tx)", error); } Error BorderAgent::ForwardToLeader(const Coap::Message & aMessage, const Ip6::MessageInfo &aMessageInfo, const char * aPath, bool aPetition, bool aSeparate) { Error error = kErrorNone; ForwardContext * forwardContext = nullptr; Tmf::MessageInfo messageInfo(GetInstance()); Coap::Message * message = nullptr; uint16_t offset = 0; if (aSeparate) { SuccessOrExit(error = Get().SendAck(aMessage, aMessageInfo)); } forwardContext = static_cast(Heap::CAlloc(1, sizeof(ForwardContext))); VerifyOrExit(forwardContext != nullptr, error = kErrorNoBufs); forwardContext->Init(GetInstance(), aMessage, aPetition, aSeparate); message = Get().NewPriorityConfirmablePostMessage(aPath); VerifyOrExit(message != nullptr, error = kErrorNoBufs); offset = message->GetLength(); SuccessOrExit(error = message->SetLength(offset + aMessage.GetLength() - aMessage.GetOffset())); aMessage.CopyTo(aMessage.GetOffset(), offset, aMessage.GetLength() - aMessage.GetOffset(), *message); SuccessOrExit(error = messageInfo.SetSockAddrToRlocPeerAddrToLeaderAloc()); messageInfo.SetSockPortToTmf(); SuccessOrExit(error = Get().SendMessage(*message, messageInfo, HandleCoapResponse, forwardContext)); // HandleCoapResponse is responsible to free this forward context. forwardContext = nullptr; LogInfo("Forwarded request to leader on %s", aPath); exit: LogError("forward to leader", error); if (error != kErrorNone) { if (forwardContext != nullptr) { Heap::Free(forwardContext); } FreeMessage(message); SendErrorMessage(aMessage, aSeparate, error); } return error; } void BorderAgent::HandleConnected(bool aConnected, void *aContext) { static_cast(aContext)->HandleConnected(aConnected); } void BorderAgent::HandleConnected(bool aConnected) { if (aConnected) { LogInfo("Commissioner connected"); mState = kStateActive; mTimer.Start(kKeepAliveTimeout); } else { LogInfo("Commissioner disconnected"); IgnoreError(Get().RemoveReceiver(mUdpReceiver)); Get().RemoveUnicastAddress(mCommissionerAloc); mState = kStateStarted; mUdpProxyPort = 0; } } uint16_t BorderAgent::GetUdpPort(void) const { return Get().GetUdpPort(); } void BorderAgent::Start(void) { Error error; Coap::CoapSecure &coaps = Get(); Pskc pskc; VerifyOrExit(mState == kStateStopped, error = kErrorNone); Get().GetPskc(pskc); SuccessOrExit(error = coaps.Start(kBorderAgentUdpPort)); SuccessOrExit(error = coaps.SetPsk(pskc.m8, Pskc::kSize)); pskc.Clear(); coaps.SetConnectedCallback(HandleConnected, this); coaps.AddResource(mActiveGet); coaps.AddResource(mActiveSet); coaps.AddResource(mPendingGet); coaps.AddResource(mPendingSet); coaps.AddResource(mCommissionerPetition); coaps.AddResource(mCommissionerKeepAlive); coaps.AddResource(mCommissionerSet); coaps.AddResource(mCommissionerGet); coaps.AddResource(mProxyTransmit); coaps.AddResource(mRelayTransmit); Get().AddResource(mRelayReceive); mState = kStateStarted; mUdpProxyPort = 0; LogInfo("Border Agent start listening on port %u", GetUdpPort()); exit: if (error != kErrorNone) { LogWarn("failed to start Border Agent on port %d: %s", kBorderAgentUdpPort, ErrorToString(error)); } } void BorderAgent::HandleTimeout(Timer &aTimer) { aTimer.Get().HandleTimeout(); } void BorderAgent::HandleTimeout(void) { if (Get().IsConnected()) { Get().Disconnect(); LogWarn("Reset commissioner session"); } } void BorderAgent::Stop(void) { Coap::CoapSecure &coaps = Get(); VerifyOrExit(mState != kStateStopped); mTimer.Stop(); coaps.RemoveResource(mCommissionerPetition); coaps.RemoveResource(mCommissionerKeepAlive); coaps.RemoveResource(mCommissionerSet); coaps.RemoveResource(mCommissionerGet); coaps.RemoveResource(mActiveGet); coaps.RemoveResource(mActiveSet); coaps.RemoveResource(mPendingGet); coaps.RemoveResource(mPendingSet); coaps.RemoveResource(mProxyTransmit); coaps.RemoveResource(mRelayTransmit); Get().RemoveResource(mRelayReceive); coaps.Stop(); mState = kStateStopped; mUdpProxyPort = 0; LogInfo("Border Agent stopped"); exit: return; } void BorderAgent::ApplyMeshLocalPrefix(void) { VerifyOrExit(mState == kStateActive); if (Get().HasUnicastAddress(mCommissionerAloc)) { Get().RemoveUnicastAddress(mCommissionerAloc); mCommissionerAloc.GetAddress().SetPrefix(Get().GetMeshLocalPrefix()); Get().AddUnicastAddress(mCommissionerAloc); } exit: return; } } // namespace MeshCoP } // namespace ot #endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE