diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba8f05d9363..219d87b6071 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,8 +34,10 @@ add_subdirectory(common) if(OTBR_FEATURE_FLAGS) add_subdirectory(proto) endif() +add_subdirectory(dso) add_subdirectory(ncp) add_subdirectory(sdp_proxy) +add_subdirectory(srpl_dnssd) add_subdirectory(trel_dnssd) if(OTBR_DBUS) diff --git a/src/agent/CMakeLists.txt b/src/agent/CMakeLists.txt index c16a5e5e820..a323ffd84c7 100644 --- a/src/agent/CMakeLists.txt +++ b/src/agent/CMakeLists.txt @@ -45,6 +45,7 @@ target_link_libraries(otbr-agent PRIVATE openthread-ftd openthread-spinel-rcp openthread-hdlc + otbr-dso otbr-sdp-proxy otbr-ncp otbr-common diff --git a/src/agent/application.cpp b/src/agent/application.cpp index ad834303d0b..774abe06f18 100644 --- a/src/agent/application.cpp +++ b/src/agent/application.cpp @@ -78,6 +78,9 @@ Application::Application(const std::string &aInterfaceName, #if OTBR_ENABLE_VENDOR_SERVER , mVendorServer(mNcp) #endif +#if OTBR_ENABLE_DNS_DSO + , mDsoAgent(mNcp) +#endif { OTBR_UNUSED_VARIABLE(aRestListenAddress); } diff --git a/src/agent/application.hpp b/src/agent/application.hpp index 5c9e2163557..6bd75e244f0 100644 --- a/src/agent/application.hpp +++ b/src/agent/application.hpp @@ -58,6 +58,7 @@ #if OTBR_ENABLE_VENDOR_SERVER #include "agent/vendor.hpp" #endif +#include "dso/dso_transport.hpp" #include "utils/infra_link_selector.hpp" namespace otbr { @@ -144,6 +145,9 @@ class Application : private NonCopyable #if OTBR_ENABLE_VENDOR_SERVER vendor::VendorServer mVendorServer; #endif +#if OTBR_ENABLE_DNS_DSO + Dso::DsoAgent mDsoAgent; +#endif static std::atomic_bool sShouldTerminate; }; diff --git a/src/border_agent/CMakeLists.txt b/src/border_agent/CMakeLists.txt index c58acdbc46f..cb2f38e9fe5 100644 --- a/src/border_agent/CMakeLists.txt +++ b/src/border_agent/CMakeLists.txt @@ -34,6 +34,7 @@ add_library(otbr-border-agent target_link_libraries(otbr-border-agent PRIVATE $<$:otbr-mdns> $<$:otbr-backbone-router> + otbr-srpl-dnssd otbr-trel-dnssd otbr-common ) diff --git a/src/border_agent/border_agent.cpp b/src/border_agent/border_agent.cpp index 382b5597cb5..5c402f8ce37 100644 --- a/src/border_agent/border_agent.cpp +++ b/src/border_agent/border_agent.cpp @@ -149,6 +149,9 @@ BorderAgent::BorderAgent(otbr::Ncp::ControllerOpenThread &aNcp) #if OTBR_ENABLE_TREL , mTrelDnssd(aNcp, *mPublisher) #endif +#if OTBR_ENABLE_SRP_REPLICATION + , mSrplDnssd(aNcp, *mPublisher) +#endif { } diff --git a/src/border_agent/border_agent.hpp b/src/border_agent/border_agent.hpp index 896281e6db5..c76f3ad6c4a 100644 --- a/src/border_agent/border_agent.hpp +++ b/src/border_agent/border_agent.hpp @@ -49,6 +49,7 @@ #include "ncp/ncp_openthread.hpp" #include "sdp_proxy/advertising_proxy.hpp" #include "sdp_proxy/discovery_proxy.hpp" +#include "srpl_dnssd/srpl_dnssd.hpp" #include "trel_dnssd/trel_dnssd.hpp" #ifndef OTBR_VENDOR_NAME @@ -144,6 +145,9 @@ class BorderAgent : private NonCopyable #if OTBR_ENABLE_TREL TrelDnssd::TrelDnssd mTrelDnssd; #endif +#if OTBR_ENABLE_SRP_REPLICATION + SrplDnssd::SrplDnssd mSrplDnssd; +#endif std::string mServiceInstanceName; }; diff --git a/src/dso/CMakeLists.txt b/src/dso/CMakeLists.txt new file mode 100644 index 00000000000..1fd6117cb51 --- /dev/null +++ b/src/dso/CMakeLists.txt @@ -0,0 +1,39 @@ +# +# Copyright (c) 2023, 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. +# + +add_library(otbr-dso + dso_transport.cpp + dso_transport.hpp + ) + +target_link_libraries(otbr-dso + PUBLIC + mbedtls + PRIVATE + otbr-common +) diff --git a/src/dso/dso_transport.cpp b/src/dso/dso_transport.cpp new file mode 100644 index 00000000000..821bfdd101b --- /dev/null +++ b/src/dso/dso_transport.cpp @@ -0,0 +1,552 @@ +/* + * Copyright (c) 2023, 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. + */ + +#if OTBR_ENABLE_DNS_DSO + +#define OTBR_LOG_TAG "DSO" + +#include "dso_transport.hpp" + +#include +#include +#include +#include +#include + +#include "mbedtls/net_sockets.h" +#include "openthread/openthread-system.h" +#include "openthread/platform/dso_transport.h" + +static otbr::Dso::DsoAgent *sDsoAgent = nullptr; + +extern "C" void otPlatDsoEnableListening(otInstance *aInstance, bool aEnabled) +{ + sDsoAgent->SetEnabled(aInstance, aEnabled); +} + +extern "C" void otPlatDsoConnect(otPlatDsoConnection *aConnection, const otSockAddr *aPeerSockAddr) +{ + sDsoAgent->FindOrCreateConnection(aConnection)->Connect(aPeerSockAddr); +} + +extern "C" void otPlatDsoSend(otPlatDsoConnection *aConnection, otMessage *aMessage) +{ + auto conn = sDsoAgent->FindConnection(aConnection); + + VerifyOrExit(conn != nullptr); + conn->Send(aMessage); + +exit: + otMessageFree(aMessage); +} + +extern "C" void otPlatDsoDisconnect(otPlatDsoConnection *aConnection, otPlatDsoDisconnectMode aMode) +{ + auto conn = sDsoAgent->FindConnection(aConnection); + + VerifyOrExit(conn != nullptr); + conn->Disconnect(aMode); + + sDsoAgent->RemoveConnection(aConnection); + +exit: + return; +} + +namespace otbr { +namespace Dso { + +DsoAgent::DsoAgent(Ncp::ControllerOpenThread &aNcp) + : mNcp(aNcp) +{ + signal(SIGPIPE, SIG_IGN); + mbedtls_net_init(&mListeningCtx); + sDsoAgent = this; +} + +DsoAgent::DsoConnection *DsoAgent::FindConnection(otPlatDsoConnection *aConnection) +{ + DsoConnection *ret = nullptr; + auto it = mMap.find(aConnection); + + if (it != mMap.end()) + { + ret = it->second.get(); + } + + return ret; +} + +DsoAgent::DsoConnection *DsoAgent::FindOrCreateConnection(otPlatDsoConnection *aConnection) +{ + auto &ret = mMap[aConnection]; + + if (!ret) + { + ret = MakeUnique(aConnection); + } + + return ret.get(); +} + +DsoAgent::DsoConnection *DsoAgent::FindOrCreateConnection(otPlatDsoConnection *aConnection, mbedtls_net_context aCtx) +{ + auto &ret = mMap[aConnection]; + + if (!ret) + { + ret = MakeUnique(aConnection, aCtx); + } + + return ret.get(); +} + +void DsoAgent::ProcessConnections(const MainloopContext &aMainloop) +{ + std::vector connections; + + connections.reserve(mMap.size()); + for (auto &conn : mMap) + { + connections.push_back(conn.second.get()); + } + for (const auto &conn : connections) + { + switch (conn->GetState()) + { + case DsoConnection::State::kDisabled: + RemoveConnection(conn->GetOtPlatDsoConnection()); + break; + case DsoConnection::State::kConnecting: + if (FD_ISSET(conn->GetFd(), &aMainloop.mWriteFdSet)) + { + conn->UpdateStateBySocketState(); + } + break; + case DsoConnection::State::kConnected: + if (FD_ISSET(conn->GetFd(), &aMainloop.mReadFdSet)) + { + conn->HandleReceive(); + } + break; + default: + break; + } + } +} + +void DsoAgent::Update(MainloopContext &aMainloop) +{ + if (mListeningEnabled) + { + FD_SET(mListeningCtx.fd, &aMainloop.mReadFdSet); + aMainloop.mMaxFd = std::max(aMainloop.mMaxFd, mListeningCtx.fd); + } + + for (const auto &pair : mMap) + { + switch (pair.second->GetState()) + { + case DsoConnection::State::kDisabled: + break; + case DsoConnection::State::kConnecting: + FD_SET(pair.second->GetFd(), &aMainloop.mWriteFdSet); + aMainloop.mMaxFd = std::max(aMainloop.mMaxFd, pair.second->GetFd()); + break; + case DsoConnection::State::kConnected: + FD_SET(pair.second->GetFd(), &aMainloop.mReadFdSet); + aMainloop.mMaxFd = std::max(aMainloop.mMaxFd, pair.second->GetFd()); + break; + default: + break; + } + } +} + +void DsoAgent::Process(const MainloopContext &aMainloop) +{ + if (FD_ISSET(mListeningCtx.fd, &aMainloop.mReadFdSet)) + { + HandleIncomingConnections(); + } + ProcessConnections(aMainloop); +} + +void DsoAgent::HandleIncomingConnections() +{ + mbedtls_net_context incomingCtx; + uint8_t address[sizeof(sockaddr_in6)]; + size_t len; + int ret = 0; + + VerifyOrExit(mListeningEnabled); + + while (!(ret = mbedtls_net_accept(&mListeningCtx, &incomingCtx, &address, sizeof(address), &len))) + { + HandleIncomingConnection(incomingCtx, address, len); + } + + if (ret != MBEDTLS_ERR_SSL_WANT_READ) + { + otbrLogWarning("Failed to accept incoming connection: %d", ret); + } + +exit: + + return; +} + +void DsoAgent::Enable(otInstance *aInstance) +{ + OTBR_UNUSED_VARIABLE(aInstance); + + assert(aInstance == mNcp.GetInstance()); + + constexpr int kOne = 1; + sockaddr_in6 sockAddr; + + VerifyOrExit(!mListeningEnabled); + + mListeningCtx.fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + + VerifyOrExit(!setsockopt(mListeningCtx.fd, SOL_SOCKET, SO_BINDTODEVICE, otSysGetInfraNetifName(), + strlen(otSysGetInfraNetifName()))); + VerifyOrExit(!setsockopt(mListeningCtx.fd, SOL_SOCKET, SO_REUSEADDR, (const uint8_t *)&kOne, sizeof(kOne))); + VerifyOrExit(!setsockopt(mListeningCtx.fd, SOL_SOCKET, SO_REUSEPORT, (const uint8_t *)&kOne, sizeof(kOne))); + + sockAddr.sin6_family = AF_INET6; + sockAddr.sin6_addr = in6addr_any; + sockAddr.sin6_port = htons(kListeningPort); + VerifyOrExit(!bind(mListeningCtx.fd, (struct sockaddr *)&sockAddr, sizeof(sockAddr))); + VerifyOrExit(!mbedtls_net_set_nonblock(&mListeningCtx), otbrLogWarning("Failed to set non-blocking: %d", errno)); + VerifyOrExit(!listen(mListeningCtx.fd, kMaxQueuedConnections)); + + mListeningEnabled = true; + + otbrLogInfo("DSO socket starts listening"); + +exit: + return; +} + +void DsoAgent::Disable(otInstance *aInstance) +{ + OTBR_UNUSED_VARIABLE(aInstance); + + assert(aInstance == mNcp.GetInstance()); + + VerifyOrExit(mListeningEnabled); + + mbedtls_net_close(&mListeningCtx); + mbedtls_net_free(&mListeningCtx); + mMap.clear(); + mListeningEnabled = false; + +exit: + return; +} + +void DsoAgent::SetEnabled(otInstance *aInstance, bool aEnabled) +{ + if (aEnabled) + { + Enable(aInstance); + } + else + { + Disable(aInstance); + } +} + +void DsoAgent::RemoveConnection(otPlatDsoConnection *aConnection) +{ + mMap.erase(aConnection); +} + +void DsoAgent::DsoConnection::Connect(const otSockAddr *aPeerSockAddr) +{ + int ret; + char addrBuf[OT_IP6_ADDRESS_STRING_SIZE]; + std::string portString; + struct sockaddr_in6 sockAddrIn6; + + VerifyOrExit(mState == State::kDisabled); + + mbedtls_net_init(&mCtx); + mPeerSockAddr = *aPeerSockAddr; + portString = std::to_string(aPeerSockAddr->mPort); + otIp6AddressToString(&aPeerSockAddr->mAddress, addrBuf, sizeof(addrBuf)); + + otbrLogInfo("Connecting to %s:%s", addrBuf, portString.c_str()); + + mCtx.fd = socket(AF_INET6, SOCK_STREAM, 0); + VerifyOrExit(mCtx.fd > 0, otbrLogWarning("Failed to create a socket: %d", errno)); + VerifyOrExit(!mbedtls_net_set_nonblock(&mCtx), otbrLogWarning("Failed to set non-blocking: %d", errno)); + + memset(&sockAddrIn6, 0, sizeof(sockAddrIn6)); + memcpy(sockAddrIn6.sin6_addr.s6_addr, aPeerSockAddr->mAddress.mFields.m8, sizeof(sockAddrIn6.sin6_addr.s6_addr)); + sockAddrIn6.sin6_family = AF_INET6; + sockAddrIn6.sin6_port = htons(aPeerSockAddr->mPort); + ret = connect(mCtx.fd, reinterpret_cast(&sockAddrIn6), sizeof(sockAddrIn6)); + + if (!ret) + { + otbrLogInfo("Connected %s:%s", addrBuf, portString.c_str()); + mState = State::kConnected; + otPlatDsoHandleConnected(mConnection); + } + else if (errno == EAGAIN || errno == EINPROGRESS) + { + mState = State::kConnecting; + } + +exit: + if (mState == State::kDisabled) + { + otPlatDsoHandleDisconnected(mConnection, OT_PLAT_DSO_DISCONNECT_MODE_FORCIBLY_ABORT); + } +} + +void DsoAgent::DsoConnection::Disconnect(otPlatDsoDisconnectMode aMode) +{ + struct linger l; + + VerifyOrExit(mState != State::kDisabled); + + switch (aMode) + { + case OT_PLAT_DSO_DISCONNECT_MODE_FORCIBLY_ABORT: + l.l_onoff = 1; + l.l_linger = 0; + setsockopt(mCtx.fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); + break; + case OT_PLAT_DSO_DISCONNECT_MODE_GRACEFULLY_CLOSE: + break; + default: + otbrLogWarning("Unknown disconnection mode: %d", aMode); + break; + } + + mbedtls_net_close(&mCtx); + mbedtls_net_free(&mCtx); + mState = State::kDisabled; + +exit: + return; +} + +void DsoAgent::DsoConnection::Send(otMessage *aMessage) +{ + uint16_t length = otMessageGetLength(aMessage); + std::vector buf(length + sizeof(uint16_t)); + uint16_t lengthInBigEndian = htons(length); + int writeLen; + + VerifyOrExit(mState == State::kConnected); + + otbrLogDebug("Sending a message with length %" PRIu16, length); + + memcpy(buf.data(), &lengthInBigEndian, sizeof(lengthInBigEndian)); + VerifyOrExit(length == otMessageRead(aMessage, 0, buf.data() + sizeof(lengthInBigEndian), length), + otbrLogWarning("Failed to read message data")); + writeLen = mbedtls_net_send(&mCtx, buf.data(), buf.size()); + if (writeLen < length) + { + otbrLogWarning("Failed to send DSO message: %d", writeLen); + } + HandleMbedTlsError(writeLen); + + // TODO: May need to keep sending until all the data is sent +exit: + return; +} + +void DsoAgent::DsoConnection::HandleReceive(void) +{ + int readLen; + uint8_t buf[kRxBufferSize]; + + VerifyOrExit(mState == State::kConnected); + + while (true) + { + if (mNeedBytes) + { + readLen = mbedtls_net_recv(&mCtx, buf, std::min(sizeof(buf), mNeedBytes)); + + if (readLen <= 0) + { + HandleMbedTlsError(readLen); + VerifyOrExit(readLen != 0); + VerifyOrExit(readLen != MBEDTLS_ERR_SSL_WANT_READ && readLen != MBEDTLS_ERR_SSL_WANT_WRITE); + otbrLogWarning("Failed to receive message: %d", readLen); + ExitNow(); + } + SuccessOrExit(otMessageAppend(mPendingMessage, buf, readLen)); + mNeedBytes -= readLen; + + if (!mNeedBytes) + { + otPlatDsoHandleReceive(mConnection, mPendingMessage); + mPendingMessage = nullptr; + } + } + else + { + assert(mLengthBuffer.size() < sizeof(uint16_t)); + assert(mPendingMessage == nullptr); + + readLen = mbedtls_net_recv(&mCtx, buf, sizeof(uint16_t) - mLengthBuffer.size()); + + if (readLen <= 0) + { + HandleMbedTlsError(readLen); + VerifyOrExit(readLen != 0); + VerifyOrExit(readLen != MBEDTLS_ERR_SSL_WANT_READ && readLen != MBEDTLS_ERR_SSL_WANT_WRITE); + otbrLogWarning("Failed to receive message: %d", readLen); + ExitNow(); + } + for (int i = 0; i < readLen; ++i) + { + mLengthBuffer.push_back(buf[i]); + } + if (mLengthBuffer.size() == sizeof(uint16_t)) + { + mNeedBytes = mLengthBuffer[0] << 8 | mLengthBuffer[1]; + mPendingMessage = otIp6NewMessage(otPlatDsoGetInstance(mConnection), nullptr); + mLengthBuffer.clear(); + VerifyOrExit(mPendingMessage != nullptr); + } + } + } + +exit: + return; +} + +void DsoAgent::DsoConnection::HandleMbedTlsError(int aError) +{ + VerifyOrExit(aError < 0); + + switch (aError) + { + case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: + otPlatDsoHandleDisconnected(mConnection, OT_PLAT_DSO_DISCONNECT_MODE_GRACEFULLY_CLOSE); + mState = State::kDisabled; + break; + case MBEDTLS_ERR_NET_CONN_RESET: + otPlatDsoHandleDisconnected(mConnection, OT_PLAT_DSO_DISCONNECT_MODE_FORCIBLY_ABORT); + mState = State::kDisabled; + break; + case MBEDTLS_ERR_SSL_WANT_READ: + case MBEDTLS_ERR_SSL_WANT_WRITE: + break; + default: + otPlatDsoHandleDisconnected(mConnection, OT_PLAT_DSO_DISCONNECT_MODE_FORCIBLY_ABORT); + mState = State::kDisabled; + break; + } + +exit: + return; +} + +void DsoAgent::DsoConnection::UpdateStateBySocketState(void) +{ + int optVal; + socklen_t optLen; + + otbrLogDebug("Updating state"); + + if (!getsockopt(GetFd(), SOL_SOCKET, SO_ERROR, &optVal, &optLen)) + { + if (!optVal) + { + mState = State::kConnected; + otPlatDsoHandleConnected(mConnection); + otbrLogDebug("Connected"); + } + else + { + mState = State::kDisabled; + otPlatDsoHandleDisconnected(mConnection, OT_PLAT_DSO_DISCONNECT_MODE_FORCIBLY_ABORT); + otbrLogDebug("Disconnected"); + } + } + else + { + otbrLogWarning("Failed to query socket status: %d", errno); + } +} + +void DsoAgent::HandleIncomingConnection(mbedtls_net_context aCtx, uint8_t *aAddress, size_t aAddressLength) +{ + otSockAddr sockAddr; + otPlatDsoConnection *conn; + in6_addr *addrIn6 = nullptr; + bool successful = false; + + VerifyOrExit(!mbedtls_net_set_nonblock(&aCtx), otbrLogWarning("Failed to set the socket as non-blocking")); + + // TODO: support IPv4 + if (aAddressLength == OT_IP6_ADDRESS_SIZE) + { + Ip6Address address; + + addrIn6 = reinterpret_cast(aAddress); + memcpy(&sockAddr.mAddress.mFields.m8, addrIn6, aAddressLength); + sockAddr.mPort = 0; // Mbed TLS doesn't provide the client's port number. + + address.CopyFrom(*addrIn6); + otbrLogInfo("Receiving connection from %s", address.ToString().c_str()); + } + else + { + otbrLogInfo("Unsupported address length: %zu", aAddressLength); + ExitNow(); + } + + conn = otPlatDsoAccept(mNcp.GetInstance(), &sockAddr); + + VerifyOrExit(conn != nullptr, otbrLogInfo("Failed to accept connection")); + + FindOrCreateConnection(conn, aCtx); + otPlatDsoHandleConnected(conn); + successful = true; + +exit: + if (!successful) + { + mbedtls_net_close(&aCtx); + } +} + +} // namespace Dso +} // namespace otbr + +#endif diff --git a/src/dso/dso_transport.hpp b/src/dso/dso_transport.hpp new file mode 100644 index 00000000000..472e5dc1958 --- /dev/null +++ b/src/dso/dso_transport.hpp @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2023, 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 includes definitions for DSO agent. + */ + +#ifndef OTBR_AGENT_DSO_TRANSPORT_HPP_ +#define OTBR_AGENT_DSO_TRANSPORT_HPP_ + +#if OTBR_ENABLE_DNS_DSO + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/code_utils.hpp" +#include "common/mainloop.hpp" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/debug.h" +#include "mbedtls/entropy.h" +#include "mbedtls/error.h" +#include "mbedtls/net_sockets.h" +#include "mbedtls/ssl.h" +#include "ncp/ncp_openthread.hpp" +#include "openthread/logging.h" +#include "openthread/message.h" +#include "openthread/platform/dso_transport.h" + +namespace otbr { +namespace Dso { + +// TODO: Support DNS-over-TLS +/** + * @addtogroup border-router-dso + * + * @brief + * This module includes definitions for DSO agent. + * + * @{ + */ + +class DsoAgent : public MainloopProcessor +{ +private: + class DsoConnection; + +public: + /** + * This constructor initializes the DsoAgent instance. + * + */ + explicit DsoAgent(Ncp::ControllerOpenThread &aNcp); + + /** + * This method enables listening for DsoAgent. + * + * @param[in] aInstance The OT instance. + * + */ + void Enable(otInstance *aInstance); + + /** + * This method disables listening for DsoAgent. + * + * @param[in] aInstance The OT instance. + * + */ + void Disable(otInstance *aInstance); + + /** + * This method enables/disables listening for DsoAgent. + * + * @param[in] aInstance The OT instance. + * @param[in] aEnabled A boolean indicating whether to enable listening. + * + */ + void SetEnabled(otInstance *aInstance, bool aEnabled); + + /** + * This method finds the DsoConnection corresponding to the given otPlatDsoConnection. + * + * @param[in] aConnection A pointer to the otPlatDsoConnection. + * + * @returns A pointer to the matching DsoConnection object. + * + */ + DsoConnection *FindConnection(otPlatDsoConnection *aConnection); + + /** + * This method finds the DsoConnection corresponding to the given otPlatDsoConnection. If the DsoConnection doesn't + * exist, it creates a new one. + * + * @param[in] aConnection A pointer to the otPlatDsoConnection. + * + * @returns A pointer to the matching DsoConnection object. + * + */ + DsoConnection *FindOrCreateConnection(otPlatDsoConnection *aConnection); + + /** + * This method finds the DsoConnection corresponding to the given otPlatDsoConnection. If the DsoConnection doesn't + * exist, it creates a new one. + * + * @param[in] aConnection A pointer to the otPlatDsoConnection. + * @param[in] aCtx A mbedtls_net_context object representing the platform connection. This is used for + * creating the DsoConnection. + * + * @returns A pointer to the matching DsoConnection object. + */ + DsoConnection *FindOrCreateConnection(otPlatDsoConnection *aConnection, mbedtls_net_context aCtx); + + /** + * This method removes and destroys the DsoConnection corresponding to the given otPlatDsoConnection. + * + * @param aConnection A pointer to the otPlatDsoConnection. + * + */ + void RemoveConnection(otPlatDsoConnection *aConnection); + + /** + * This method processes rx/tx for all DSO connections. + * + * @param aMainloop A const reference to MainloopContext. + * + */ + void ProcessConnections(const MainloopContext &aMainloop); + + /** + * This method processes the incoming DSO connections. + * + */ + void HandleIncomingConnections(void); + +private: + class DsoConnection : NonCopyable + { + public: + enum class State + { + kDisabled, + kConnecting, + kConnected + }; + + explicit DsoConnection(otPlatDsoConnection *aConnection) + : mConnection(aConnection) + , mCtx() + , mState(State::kDisabled) + { + } + + DsoConnection(otPlatDsoConnection *aConnection, mbedtls_net_context aCtx) + : mConnection(aConnection) + , mCtx(aCtx) + , mState(State::kConnected) + { + } + + ~DsoConnection(void) + { + Disconnect(OT_PLAT_DSO_DISCONNECT_MODE_FORCIBLY_ABORT); + mbedtls_net_free(&mCtx); + } + + State GetState(void) const { return mState; } + + int GetFd(void) const { return mCtx.fd; } + + otPlatDsoConnection *GetOtPlatDsoConnection(void) const { return mConnection; } + + void Connect(const otSockAddr *aPeerSockAddr); + + void Disconnect(otPlatDsoDisconnectMode aMode); + + void Send(otMessage *aMessage); + + void HandleReceive(void); + + void HandleMbedTlsError(int aError); // Returns true if the connection is still alive, false otherwise. + + void UpdateStateBySocketState(void); // Update the state according to socket's state. + + private: + static constexpr size_t kRxBufferSize = 512; + + otPlatDsoConnection *mConnection; + otSockAddr mPeerSockAddr{}; + otMessage *mPendingMessage = nullptr; + size_t mNeedBytes = 0; + std::vector mLengthBuffer; + mbedtls_net_context mCtx; + State mState; + }; + + static constexpr uint16_t kListeningPort = 853; + static constexpr int kMaxQueuedConnections = 10; + + void Update(MainloopContext &aMainloop) override; + void Process(const MainloopContext &aMainloop) override; + void HandleIncomingConnection(mbedtls_net_context aCtx, uint8_t *aAddress, size_t aAddressLength); + + Ncp::ControllerOpenThread &mNcp; + bool mListeningEnabled = false; + mbedtls_net_context mListeningCtx; + + std::map> mMap; +}; + +/** + * @} + */ + +} // namespace Dso +} // namespace otbr + +#endif // OTBR_ENABLE_DNS_DSO + +#endif // OTBR_AGENT_DSO_TRANSPORT_HPP_ diff --git a/src/mdns/mdns.cpp b/src/mdns/mdns.cpp index 9646d5513c8..bd18073fab8 100644 --- a/src/mdns/mdns.cpp +++ b/src/mdns/mdns.cpp @@ -576,6 +576,18 @@ void Publisher::UpdateHostResolutionEmaLatency(const std::string &aHostName, otb } } +const Publisher::ServiceRegistration *Publisher::FindServiceRegistrationByType(const std::string &aType) const +{ + for (const auto &serviceReg : mServiceRegistrations) + { + if (serviceReg.second->mType == aType) + { + return serviceReg.second.get(); + } + } + return nullptr; +} + } // namespace Mdns } // namespace otbr diff --git a/src/mdns/mdns.hpp b/src/mdns/mdns.hpp index a06578a3870..b6134d007e4 100644 --- a/src/mdns/mdns.hpp +++ b/src/mdns/mdns.hpp @@ -563,6 +563,17 @@ class Publisher : private NonCopyable std::map mHostResolutionBeginTime; otbr::MdnsTelemetryInfo mTelemetryInfo{}; + +public: + /** + * Find the service registration of the given type. + * + * @param aType The service type. + * @returns A pointer to the service registration of the given type. If no service registration is found, + * returns a nullptr. + * + */ + const ServiceRegistration *FindServiceRegistrationByType(const std::string &aType) const; }; /** diff --git a/src/srpl_dnssd/CMakeLists.txt b/src/srpl_dnssd/CMakeLists.txt new file mode 100644 index 00000000000..cc8b0a4a98d --- /dev/null +++ b/src/srpl_dnssd/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright (c) 2023, 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. +# + +add_library(otbr-srpl-dnssd + srpl_dnssd.cpp + srpl_dnssd.hpp + ) + +target_link_libraries(otbr-srpl-dnssd PRIVATE + $<$:otbr-mdns> + otbr-common + ) diff --git a/src/srpl_dnssd/srpl_dnssd.cpp b/src/srpl_dnssd/srpl_dnssd.cpp new file mode 100644 index 00000000000..ef143ded02f --- /dev/null +++ b/src/srpl_dnssd/srpl_dnssd.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2023, 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 includes implementation of SRPL DNS-SD over mDNS. + */ + +#if OTBR_ENABLE_SRP_REPLICATION + +#define OTBR_LOG_TAG "SrplDns" + +#include "srpl_dnssd/srpl_dnssd.hpp" + +#include "openthread/platform/srp_replication.h" + +#include "mdns/mdns.hpp" +#include "openthread/ip6.h" +#include "utils/string_utils.hpp" + +static otbr::SrplDnssd::SrplDnssd *sSrplDnssd = nullptr; + +extern "C" void otPlatSrplDnssdBrowse(otInstance *aInstance, bool aEnable) +{ + OTBR_UNUSED_VARIABLE(aInstance); + + if (aEnable) + { + sSrplDnssd->StartBrowse(); + } + else + { + sSrplDnssd->StopBrowse(); + } +} + +extern "C" void otPlatSrplRegisterDnssdService(otInstance *aInstance, const uint8_t *aTxtData, uint16_t aTxtLength) +{ + OTBR_UNUSED_VARIABLE(aInstance); + + sSrplDnssd->RegisterService(aTxtData, aTxtLength); +} + +extern "C" void otPlatSrplUnregisterDnssdService(otInstance *aInstance) +{ + OTBR_UNUSED_VARIABLE(aInstance); + + sSrplDnssd->UnregisterService(); +} + +namespace otbr { + +namespace SrplDnssd { + +SrplDnssd::SrplDnssd(Ncp::ControllerOpenThread &aNcp, Mdns::Publisher &aPublisher) + : mNcp(aNcp) + , mPublisher(aPublisher) +{ + sSrplDnssd = this; +} + +void SrplDnssd::StartBrowse(void) +{ + VerifyOrExit(!IsBrowsing()); + + otbrLogDebug("Start browsing SRPL services ..."); + + mSubscriberId = mPublisher.AddSubscriptionCallbacks( + [this](const std::string &aType, const DiscoveredInstanceInfo &aInstanceInfo) { + OnServiceInstanceResolved(aType, aInstanceInfo); + }, + nullptr); + mPublisher.SubscribeService(kServiceType, /* aInstanceName */ ""); + +exit: + return; +} + +void SrplDnssd::StopBrowse(void) +{ + VerifyOrExit(IsBrowsing()); + + otbrLogDebug("Stop browsing SRPL services."); + + mPublisher.UnsubscribeService(kServiceType, /* aInstanceName */ ""); + mPublisher.RemoveSubscriptionCallbacks(mSubscriberId); + mSubscriberId = 0; + +exit: + return; +} + +// TODO: handle the case when mDNS publisher is not ready +void SrplDnssd::RegisterService(const uint8_t *aTxtData, uint8_t aTxtLength) +{ + otbr::Mdns::Publisher::TxtList txtList; + + SuccessOrExit(otbr::Mdns::Publisher::DecodeTxtData(txtList, aTxtData, aTxtLength)); + + otbrLogInfo("Publishing SRPL service"); + mPublisher.PublishService(/* aHostName */ "", /* aName */ "", kServiceType, {}, kPort, txtList, + [this](otbrError aError) { + otbrLogResult(aError, "Result of publishing SRPL service"); + if (aError == OTBR_ERROR_NONE) + { + auto serviceRegistration = mPublisher.FindServiceRegistrationByType(kServiceType); + if (serviceRegistration) + { + mServiceInstanceName = serviceRegistration->mName; + otbrLogInfo("SRPL service instance name is %s", mServiceInstanceName.c_str()); + } + } + }); + +exit: + return; +} + +void SrplDnssd::UnregisterService() +{ + otbrLogInfo("Unpublishing SRPL service: %s", mServiceInstanceName.c_str()); + mPublisher.UnpublishService(mServiceInstanceName, kServiceType, [this](otbrError aError) { + if (aError == OTBR_ERROR_NONE) + { + mServiceInstanceName.clear(); + } + }); +} + +void SrplDnssd::OnServiceInstanceResolved(const std::string &aType, const DiscoveredInstanceInfo &aInstanceInfo) +{ + otPlatSrplPartnerInfo partnerInfo; + + VerifyOrExit(IsBrowsing()); + VerifyOrExit(StringUtils::EqualCaseInsensitive(aType, kServiceType)); + VerifyOrExit(!StringUtils::EqualCaseInsensitive(aInstanceInfo.mName, mServiceInstanceName)); + + // TODO: Also need to check by addresses to mark as 'me'. + + partnerInfo.mRemoved = aInstanceInfo.mRemoved; + otbrLogInfo("Discovered SRPL peer: %s", aInstanceInfo.mName.c_str()); + + if (!partnerInfo.mRemoved) + { + VerifyOrExit(!aInstanceInfo.mAddresses.empty()); + // TODO: choose the address with the largest scope + // Currently the mDNS publisher only returns 1 address in every callback, probably we should let BR wait for + // some time to collect all discovered addresses and decide which address to use. + SuccessOrExit(otIp6AddressFromString(aInstanceInfo.mAddresses.front().ToString().c_str(), + &partnerInfo.mSockAddr.mAddress)); + + partnerInfo.mTxtData = aInstanceInfo.mTxtData.data(); + partnerInfo.mTxtLength = aInstanceInfo.mTxtData.size(); + partnerInfo.mSockAddr.mPort = aInstanceInfo.mPort; + } + otPlatSrplHandleDnssdBrowseResult(mNcp.GetInstance(), &partnerInfo); + +exit: + return; +} + +} // namespace SrplDnssd +} // namespace otbr + +#endif diff --git a/src/srpl_dnssd/srpl_dnssd.hpp b/src/srpl_dnssd/srpl_dnssd.hpp new file mode 100644 index 00000000000..c810397b1ca --- /dev/null +++ b/src/srpl_dnssd/srpl_dnssd.hpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2023, 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 includes definitions for SRPL DNS-SD over mDNS. + */ + +#ifndef OTBR_AGENT_SRPL_DNSSD_HPP_ +#define OTBR_AGENT_SRPL_DNSSD_HPP_ + +#if OTBR_ENABLE_SRP_REPLICATION + +#include + +#include "common/types.hpp" +#include "mdns/mdns.hpp" +#include "ncp/ncp_openthread.hpp" + +namespace otbr { + +namespace SrplDnssd { + +/** + * + * @addtogroup border-router-srpl-dnssd + * + * @brief + * This module includes definition for SRPL DNS-SD over mDNS. + * + * @{ + */ + +class SrplDnssd +{ +public: + /** + * This constructor initializes the SrplDnssd instance. + * + * @param aNcp + * @param aPublisher + */ + explicit SrplDnssd(Ncp::ControllerOpenThread &aNcp, Mdns::Publisher &aPublisher); + + /** + * This method starts browsing for SRPL peers. + * + */ + void StartBrowse(void); + + /** + * This method stops browsing for SRPL peers. + * + */ + void StopBrowse(void); + + /** + * This method registers the SRPL service to DNS-SD. + * + * @param[in] aTxtData The TXT data of SRPL service. + * @param[in] aTxtLength The TXT length of SRPL service. + */ + void RegisterService(const uint8_t *aTxtData, uint8_t aTxtLength); + + /** + * This method removes the SRPL service from DNS-SD. + * + */ + void UnregisterService(void); + +private: + static constexpr const char *kServiceType = "_srpl-tls._tcp"; + static constexpr uint16_t kPort = 853; + + using DiscoveredInstanceInfo = otbr::Mdns::Publisher::DiscoveredInstanceInfo; + + bool IsBrowsing(void) const { return mSubscriberId != 0; } + + void OnServiceInstanceResolved(const std::string &aType, const DiscoveredInstanceInfo &aInstanceInfo); + + Ncp::ControllerOpenThread &mNcp; + Mdns::Publisher &mPublisher; + std::string mServiceInstanceName; + uint64_t mSubscriberId = 0; +}; + +/** + * @} + */ + +} // namespace SrplDnssd + +} // namespace otbr + +#endif // OTBR_ENABLE_SRP_REPLICATION + +#endif // OTBR_AGENT_SRPL_DNSSD_HPP_ diff --git a/third_party/openthread/CMakeLists.txt b/third_party/openthread/CMakeLists.txt index 6f474e7ef7e..0bcc52c4e74 100644 --- a/third_party/openthread/CMakeLists.txt +++ b/third_party/openthread/CMakeLists.txt @@ -89,6 +89,13 @@ endif() set(OT_NAT64_TRANSLATOR ${OTBR_NAT64} CACHE BOOL "enable NAT64 translator" FORCE) set(OT_NAT64_BORDER_ROUTING ${OTBR_NAT64} CACHE BOOL "enable NAT64 in border routing manager" FORCE) +set(OT_DNS_DSO ${OTBR_DNS_DSO} CACHE BOOL "enable DSO support" FORCE) + +if (OTBR_SRP_REPLICATION) + set(OT_SRP_REPLICATION ON CACHE BOOL "enable SRP replication" FORCE) + set(OT_DNS_DSO ON CACHE BOOL "enable DSO support" FORCE) +endif() + if (NOT OT_POSIX_SETTINGS_PATH) set(OT_POSIX_SETTINGS_PATH "\"/var/lib/thread\"" CACHE STRING "set the directory to store Thread data" FORCE) endif()