blob: b6acf18c4c1c74000029d99e0fb04f584c52aba6 [file]
/*
* Copyright (c) 2017, 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
* The file implements the Thread border agent.
*/
#define OTBR_LOG_TAG "BA"
#include "border_agent/border_agent.hpp"
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <iomanip>
#include <random>
#include <sstream>
#include <openthread/border_agent.h>
#include <openthread/border_routing.h>
#include <openthread/random_noncrypto.h>
#include <openthread/thread_ftd.h>
#include <openthread/platform/settings.h>
#include <openthread/platform/toolchain.h>
#include "agent/uris.hpp"
#include "ncp/ncp_openthread.hpp"
#if OTBR_ENABLE_BACKBONE_ROUTER
#include "backbone_router/backbone_agent.hpp"
#endif
#include "common/byteswap.hpp"
#include "common/code_utils.hpp"
#include "common/logging.hpp"
#include "common/tlv.hpp"
#include "common/types.hpp"
#include "utils/hex.hpp"
namespace otbr {
static const char kBorderAgentServiceType[] = "_meshcop._udp"; ///< Border agent service type of mDNS
static constexpr int kBorderAgentServiceDummyPort = 49152;
/**
* Locators
*
*/
enum
{
kAloc16Leader = 0xfc00, ///< leader anycast locator.
kInvalidLocator = 0xffff, ///< invalid locator.
};
enum : uint8_t
{
kConnectionModeDisabled = 0,
kConnectionModePskc = 1,
kConnectionModePskd = 2,
kConnectionModeVendor = 3,
kConnectionModeX509 = 4,
};
enum : uint8_t
{
kThreadIfStatusNotInitialized = 0,
kThreadIfStatusInitialized = 1,
kThreadIfStatusActive = 2,
};
enum : uint8_t
{
kAvailabilityInfrequent = 0,
kAvailabilityHigh = 1,
};
struct StateBitmap
{
uint32_t mConnectionMode : 3;
uint32_t mThreadIfStatus : 2;
uint32_t mAvailability : 2;
uint32_t mBbrIsActive : 1;
uint32_t mBbrIsPrimary : 1;
StateBitmap(void)
: mConnectionMode(0)
, mThreadIfStatus(0)
, mAvailability(0)
, mBbrIsActive(0)
, mBbrIsPrimary(0)
{
}
uint32_t ToUint32(void) const
{
uint32_t bitmap = 0;
bitmap |= mConnectionMode << 0;
bitmap |= mThreadIfStatus << 3;
bitmap |= mAvailability << 5;
bitmap |= mBbrIsActive << 7;
bitmap |= mBbrIsPrimary << 8;
return bitmap;
}
};
BorderAgent::BorderAgent(otbr::Ncp::ControllerOpenThread &aNcp, Mdns::Publisher &aPublisher)
: mNcp(aNcp)
, mPublisher(aPublisher)
, mIsEnabled(false)
, mVendorName(OTBR_VENDOR_NAME)
, mProductName(OTBR_PRODUCT_NAME)
, mBaseServiceInstanceName(OTBR_MESHCOP_SERVICE_INSTANCE_NAME)
{
mNcp.AddThreadStateChangedCallback([this](otChangedFlags aFlags) { HandleThreadStateChanged(aFlags); });
}
otbrError BorderAgent::SetMeshCopServiceValues(const std::string &aServiceInstanceName,
const std::string &aProductName,
const std::string &aVendorName,
const std::vector<uint8_t> &aVendorOui)
{
otbrError error = OTBR_ERROR_NONE;
VerifyOrExit(aProductName.size() <= kMaxProductNameLength, error = OTBR_ERROR_INVALID_ARGS);
VerifyOrExit(aVendorName.size() <= kMaxVendorNameLength, error = OTBR_ERROR_INVALID_ARGS);
VerifyOrExit(aVendorOui.empty() || aVendorOui.size() == kVendorOuiLength, error = OTBR_ERROR_INVALID_ARGS);
mProductName = aProductName;
mVendorName = aVendorName;
mVendorOui = aVendorOui;
mBaseServiceInstanceName = aServiceInstanceName;
exit:
return error;
}
void BorderAgent::SetEnabled(bool aIsEnabled)
{
VerifyOrExit(IsEnabled() != aIsEnabled);
mIsEnabled = aIsEnabled;
if (mIsEnabled)
{
Start();
}
else
{
Stop();
}
exit:
return;
}
void BorderAgent::Start(void)
{
otbrLogInfo("Start Thread Border Agent");
#if OTBR_ENABLE_DBUS_SERVER
mNcp.GetThreadHelper()->SetUpdateMeshCopTxtHandler([this](std::map<std::string, std::vector<uint8_t>> aUpdate) {
HandleUpdateVendorMeshCoPTxtEntries(std::move(aUpdate));
});
mNcp.RegisterResetHandler([this]() {
mNcp.GetThreadHelper()->SetUpdateMeshCopTxtHandler([this](std::map<std::string, std::vector<uint8_t>> aUpdate) {
HandleUpdateVendorMeshCoPTxtEntries(std::move(aUpdate));
});
});
#endif
mServiceInstanceName = GetServiceInstanceNameWithExtAddr(mBaseServiceInstanceName);
UpdateMeshCopService();
}
void BorderAgent::Stop(void)
{
otbrLogInfo("Stop Thread Border Agent");
UnpublishMeshCopService();
}
void BorderAgent::HandleMdnsState(Mdns::Publisher::State aState)
{
VerifyOrExit(IsEnabled());
switch (aState)
{
case Mdns::Publisher::State::kReady:
UpdateMeshCopService();
break;
default:
otbrLogWarning("mDNS publisher not available!");
break;
}
exit:
return;
}
static uint64_t ConvertTimestampToUint64(const otTimestamp &aTimestamp)
{
// 64 bits Timestamp fields layout
//-----48 bits------//-----15 bits-----//-------1 bit-------//
// Seconds // Ticks // Authoritative //
return (aTimestamp.mSeconds << 16) | static_cast<uint64_t>(aTimestamp.mTicks << 1) |
static_cast<uint64_t>(aTimestamp.mAuthoritative);
}
#if OTBR_ENABLE_BORDER_ROUTING
void AppendOmrTxtEntry(otInstance &aInstance, Mdns::Publisher::TxtList &aTxtList)
{
otIp6Prefix omrPrefix;
otRoutePreference preference;
if (OT_ERROR_NONE == otBorderRoutingGetFavoredOmrPrefix(&aInstance, &omrPrefix, &preference))
{
std::vector<uint8_t> omrData;
omrData.reserve(1 + OT_IP6_PREFIX_SIZE);
omrData.push_back(omrPrefix.mLength);
std::copy(omrPrefix.mPrefix.mFields.m8, omrPrefix.mPrefix.mFields.m8 + (omrPrefix.mLength + 7) / 8,
std::back_inserter(omrData));
aTxtList.emplace_back("omr", omrData.data(), omrData.size());
}
}
#endif
StateBitmap GetStateBitmap(otInstance &aInstance)
{
StateBitmap state;
state.mConnectionMode = kConnectionModePskc;
state.mAvailability = kAvailabilityHigh;
switch (otThreadGetDeviceRole(&aInstance))
{
case OT_DEVICE_ROLE_DISABLED:
state.mThreadIfStatus = kThreadIfStatusNotInitialized;
break;
case OT_DEVICE_ROLE_DETACHED:
state.mThreadIfStatus = kThreadIfStatusInitialized;
break;
default:
state.mThreadIfStatus = kThreadIfStatusActive;
}
#if OTBR_ENABLE_BACKBONE_ROUTER
state.mBbrIsActive = state.mThreadIfStatus == kThreadIfStatusActive &&
otBackboneRouterGetState(&aInstance) != OT_BACKBONE_ROUTER_STATE_DISABLED;
state.mBbrIsPrimary = state.mThreadIfStatus == kThreadIfStatusActive &&
otBackboneRouterGetState(&aInstance) == OT_BACKBONE_ROUTER_STATE_PRIMARY;
#endif
return state;
}
#if OTBR_ENABLE_BACKBONE_ROUTER
void AppendBbrTxtEntries(otInstance &aInstance, StateBitmap aState, Mdns::Publisher::TxtList &aTxtList)
{
if (aState.mBbrIsActive)
{
otBackboneRouterConfig bbrConfig;
uint16_t bbrPort = htobe16(BackboneRouter::BackboneAgent::kBackboneUdpPort);
otBackboneRouterGetConfig(&aInstance, &bbrConfig);
aTxtList.emplace_back("sq", &bbrConfig.mSequenceNumber, sizeof(bbrConfig.mSequenceNumber));
aTxtList.emplace_back("bb", reinterpret_cast<const uint8_t *>(&bbrPort), sizeof(bbrPort));
}
aTxtList.emplace_back("dn", otThreadGetDomainName(&aInstance));
}
#endif
void AppendActiveTimestampTxtEntry(otInstance &aInstance, Mdns::Publisher::TxtList &aTxtList)
{
otError error;
otOperationalDataset activeDataset;
if ((error = otDatasetGetActive(&aInstance, &activeDataset)) != OT_ERROR_NONE)
{
otbrLogWarning("Failed to get active dataset: %s", otThreadErrorToString(error));
}
else
{
uint64_t activeTimestampValue = ConvertTimestampToUint64(activeDataset.mActiveTimestamp);
activeTimestampValue = htobe64(activeTimestampValue);
aTxtList.emplace_back("at", reinterpret_cast<uint8_t *>(&activeTimestampValue), sizeof(activeTimestampValue));
}
}
#if OTBR_ENABLE_DBUS_SERVER
void AppendVendorTxtEntries(const std::map<std::string, std::vector<uint8_t>> &aVendorEntries,
Mdns::Publisher::TxtList &aTxtList)
{
for (const auto &entry : aVendorEntries)
{
const std::string &key = entry.first;
const std::vector<uint8_t> &value = entry.second;
bool found = false;
for (auto &addedEntry : aTxtList)
{
if (addedEntry.mKey == key)
{
addedEntry.mValue = value;
addedEntry.mIsBooleanAttribute = false;
found = true;
break;
}
}
if (!found)
{
aTxtList.emplace_back(key.c_str(), value.data(), value.size());
}
}
}
#endif
void BorderAgent::PublishMeshCopService(void)
{
StateBitmap state;
uint32_t stateUint32;
otInstance *instance = mNcp.GetInstance();
const otExtendedPanId *extPanId = otThreadGetExtendedPanId(instance);
const otExtAddress *extAddr = otLinkGetExtendedAddress(instance);
const char *networkName = otThreadGetNetworkName(instance);
Mdns::Publisher::TxtList txtList{{"rv", "1"}};
Mdns::Publisher::TxtData txtData;
int port;
otbrError error;
OTBR_UNUSED_VARIABLE(error);
otbrLogInfo("Publish meshcop service %s.%s.local.", mServiceInstanceName.c_str(), kBorderAgentServiceType);
#if OTBR_ENABLE_PUBLISH_MESHCOP_BA_ID
{
otError error;
otBorderAgentId id;
error = otBorderAgentGetId(instance, &id);
if (error == OT_ERROR_NONE)
{
txtList.emplace_back("id", id.mId, sizeof(id));
}
else
{
otbrLogWarning("Failed to retrieve Border Agent ID: %s", otThreadErrorToString(error));
}
}
#endif
if (!mVendorOui.empty())
{
txtList.emplace_back("vo", mVendorOui.data(), mVendorOui.size());
}
if (!mVendorName.empty())
{
txtList.emplace_back("vn", mVendorName.c_str());
}
if (!mProductName.empty())
{
txtList.emplace_back("mn", mProductName.c_str());
}
txtList.emplace_back("nn", networkName);
txtList.emplace_back("xp", extPanId->m8, sizeof(extPanId->m8));
txtList.emplace_back("tv", mNcp.GetThreadVersion());
// "xa" stands for Extended MAC Address (64-bit) of the Thread Interface of the Border Agent.
txtList.emplace_back("xa", extAddr->m8, sizeof(extAddr->m8));
state = GetStateBitmap(*instance);
stateUint32 = htobe32(state.ToUint32());
txtList.emplace_back("sb", reinterpret_cast<uint8_t *>(&stateUint32), sizeof(stateUint32));
if (state.mThreadIfStatus == kThreadIfStatusActive)
{
uint32_t partitionId;
AppendActiveTimestampTxtEntry(*instance, txtList);
partitionId = otThreadGetPartitionId(instance);
txtList.emplace_back("pt", reinterpret_cast<uint8_t *>(&partitionId), sizeof(partitionId));
}
#if OTBR_ENABLE_BACKBONE_ROUTER
AppendBbrTxtEntries(*instance, state, txtList);
#endif
#if OTBR_ENABLE_BORDER_ROUTING
AppendOmrTxtEntry(*instance, txtList);
#endif
#if OTBR_ENABLE_DBUS_SERVER
AppendVendorTxtEntries(mMeshCopTxtUpdate, txtList);
#endif
if (otBorderAgentGetState(instance) != OT_BORDER_AGENT_STATE_STOPPED)
{
port = otBorderAgentGetUdpPort(instance);
}
else
{
// When thread interface is not active, the border agent is not started, thus it's not listening to any port and
// not handling requests. In such situation, we use a dummy port number for publishing the MeshCoP service to
// advertise the status of the border router. One can learn the thread interface status from `sb` entry so it
// doesn't have to send requests to the dummy port when border agent is not running.
port = kBorderAgentServiceDummyPort;
}
error = Mdns::Publisher::EncodeTxtData(txtList, txtData);
assert(error == OTBR_ERROR_NONE);
mPublisher.PublishService(/* aHostName */ "", mServiceInstanceName, kBorderAgentServiceType,
Mdns::Publisher::SubTypeList{}, port, txtData, [this](otbrError aError) {
if (aError == OTBR_ERROR_ABORTED)
{
// OTBR_ERROR_ABORTED is thrown when an ongoing service registration is
// cancelled. This can happen when the meshcop service is being updated
// frequently. To avoid false alarms, it should not be logged like a real error.
otbrLogInfo("Cancelled previous publishing meshcop service %s.%s.local",
mServiceInstanceName.c_str(), kBorderAgentServiceType);
}
else
{
otbrLogResult(aError, "Result of publish meshcop service %s.%s.local",
mServiceInstanceName.c_str(), kBorderAgentServiceType);
}
if (aError == OTBR_ERROR_DUPLICATED)
{
// Try to unpublish current service in case we are trying to register
// multiple new services simultaneously when the original service name
// is conflicted.
UnpublishMeshCopService();
mServiceInstanceName = GetAlternativeServiceInstanceName();
PublishMeshCopService();
}
});
}
void BorderAgent::UnpublishMeshCopService(void)
{
otbrLogInfo("Unpublish meshcop service %s.%s.local", mServiceInstanceName.c_str(), kBorderAgentServiceType);
mPublisher.UnpublishService(mServiceInstanceName, kBorderAgentServiceType, [this](otbrError aError) {
otbrLogResult(aError, "Result of unpublish meshcop service %s.%s.local", mServiceInstanceName.c_str(),
kBorderAgentServiceType);
});
}
void BorderAgent::UpdateMeshCopService(void)
{
VerifyOrExit(IsEnabled());
VerifyOrExit(mPublisher.IsStarted());
PublishMeshCopService();
exit:
return;
}
#if OTBR_ENABLE_DBUS_SERVER
void BorderAgent::HandleUpdateVendorMeshCoPTxtEntries(std::map<std::string, std::vector<uint8_t>> aUpdate)
{
mMeshCopTxtUpdate = std::move(aUpdate);
UpdateMeshCopService();
}
#endif
void BorderAgent::HandleThreadStateChanged(otChangedFlags aFlags)
{
VerifyOrExit(IsEnabled());
if (aFlags & OT_CHANGED_THREAD_ROLE)
{
otbrLogInfo("Thread is %s", (IsThreadStarted() ? "up" : "down"));
}
if (aFlags & (OT_CHANGED_THREAD_ROLE | OT_CHANGED_THREAD_EXT_PANID | OT_CHANGED_THREAD_NETWORK_NAME |
OT_CHANGED_THREAD_BACKBONE_ROUTER_STATE | OT_CHANGED_THREAD_NETDATA))
{
UpdateMeshCopService();
}
exit:
return;
}
bool BorderAgent::IsThreadStarted(void) const
{
otDeviceRole role = otThreadGetDeviceRole(mNcp.GetInstance());
return role == OT_DEVICE_ROLE_CHILD || role == OT_DEVICE_ROLE_ROUTER || role == OT_DEVICE_ROLE_LEADER;
}
std::string BorderAgent::GetServiceInstanceNameWithExtAddr(const std::string &aServiceInstanceName) const
{
const otExtAddress *extAddress = otLinkGetExtendedAddress(mNcp.GetInstance());
std::stringstream ss;
ss << aServiceInstanceName << " #";
ss << std::uppercase << std::hex << std::setfill('0');
ss << std::setw(2) << static_cast<int>(extAddress->m8[6]);
ss << std::setw(2) << static_cast<int>(extAddress->m8[7]);
return ss.str();
}
std::string BorderAgent::GetAlternativeServiceInstanceName() const
{
std::random_device r;
std::default_random_engine engine(r());
std::uniform_int_distribution<uint16_t> uniform_dist(1, 0xFFFF);
uint16_t rand = uniform_dist(engine);
std::stringstream ss;
ss << GetServiceInstanceNameWithExtAddr(mBaseServiceInstanceName) << " (" << rand << ")";
return ss.str();
}
} // namespace otbr