diff --git a/.github/workflows/simulation-1.1.yml b/.github/workflows/simulation-1.1.yml index 24c679368..4ca46895f 100644 --- a/.github/workflows/simulation-1.1.yml +++ b/.github/workflows/simulation-1.1.yml @@ -63,7 +63,7 @@ jobs: export ASAN_OPTIONS=symbolize=1 export DISTCHECK_CONFIGURE_FLAGS= CPPFLAGS=-DOPENTHREAD_SIMULATION_VIRTUAL_TIME=1 ./bootstrap - make -f examples/Makefile-simulation distcheck + VERBOSE=1 make -f examples/Makefile-simulation distcheck cli-ftd-otns: runs-on: ubuntu-20.04 diff --git a/Android.mk b/Android.mk index b93e880a6..b70ee66f2 100644 --- a/Android.mk +++ b/Android.mk @@ -172,6 +172,7 @@ LOCAL_SRC_FILES := \ src/core/api/dataset_updater_api.cpp \ src/core/api/diags_api.cpp \ src/core/api/dns_api.cpp \ + src/core/api/dns_server_api.cpp \ src/core/api/entropy_api.cpp \ src/core/api/error_api.cpp \ src/core/api/heap_api.cpp \ diff --git a/doc/ot_api_doc.h b/doc/ot_api_doc.h index c0c5a72e1..a9cc1e062 100644 --- a/doc/ot_api_doc.h +++ b/doc/ot_api_doc.h @@ -54,6 +54,7 @@ * @{ * * @defgroup api-dns DNSv6 + * @defgroup api-dnssd-server DNS-SD Server * @defgroup api-icmp6 ICMPv6 * @defgroup api-ip6 IPv6 * @defgroup api-srp SRP diff --git a/include/Makefile.am b/include/Makefile.am index a590ce02a..207187b1f 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -54,6 +54,7 @@ openthread_headers = \ openthread/diag.h \ openthread/dns.h \ openthread/dns_client.h \ + openthread/dnssd_server.h \ openthread/entropy.h \ openthread/error.h \ openthread/heap.h \ diff --git a/include/openthread/BUILD.gn b/include/openthread/BUILD.gn index 954e6dfe4..213c0600e 100644 --- a/include/openthread/BUILD.gn +++ b/include/openthread/BUILD.gn @@ -75,6 +75,7 @@ source_set("openthread") { "diag.h", "dns.h", "dns_client.h", + "dnssd_server.h", "entropy.h", "error.h", "heap.h", diff --git a/include/openthread/dnssd_server.h b/include/openthread/dnssd_server.h new file mode 100644 index 000000000..2f439178b --- /dev/null +++ b/include/openthread/dnssd_server.h @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2021, 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 + * @brief + * This file defines the DNS-SD server APIs. + */ + +#ifndef OPENTHREAD_DNSSD_SERVER_H_ +#define OPENTHREAD_DNSSD_SERVER_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup api-dnssd-server + * + * @brief + * This module includes APIs for DNS-SD server. + * + * @{ + * + */ + +/** + * This function is called when a DNS-SD query subscribes one of: + * 1. a service name. + * 2. a service instance name. + * 3. a host name. + * + * The DNS-SD query implementation is responsible for identifying what @p aFullName is. + * If @p aFullName is a service name or service instance name, the DNS-SD query implementation should discover + * corresponding service instance information and notify the DNS-SD server using + * `otDnssdQueryHandleDiscoveredServiceInstance`. + * If @p aFullName is a host name, the DNS-SD query implementation should + * discover the host information and notify the DNS-SD server using `otDnssdQueryHandleDiscoveredHost`. + * + * @note There can be multiple subscription to the same name. DNS-SD query implementation should record the number of + * active subscriptions and stop notifying when there is no active subscription for @p aFullName. + * + * @param[in] aContext A pointer to the application-specific context. + * @param[in] aFullName The null-terminated full service name (e.g. "_ipps._tcp.default.service.arpa."), + * or full service instance name (e.g. "OpenThread._ipps._tcp.default.service.arpa."), + * or full host name (e.g. "ot-host.default.service.arpa."). + * + * @sa otDnssdQueryHandleDiscoveredServiceInstance + * @sa otDnssdQueryHandleDiscoveredHost + * + */ +typedef void (*otDnssdQuerySubscribeCallback)(void *aContext, const char *aFullName); + +/** + * This function is called when a DNS-SD query unsubscribes one of: + * 1. a service name. + * 2. a service instance name. + * 3. a host name. + * + * The DNS-SD query implementation is responsible for identifying what @p aFullName is. + * + * @note There can be multiple subscription to the same name. DNS-SD query implementation should record the number of + * active subscriptions and stop notifying when there is no active subscription for @p aFullName. + * + * @param[in] aContext A pointer to the application-specific context. + * @param[in] aFullName The null-terminated full service name (e.g. "_ipps._tcp.default.service.arpa."), or + * full service instance name (e.g. "OpenThread._ipps._tcp.default.service.arpa."). + * + */ +typedef void (*otDnssdQueryUnsubscribeCallback)(void *aContext, const char *aFullName); + +/** + * This structure represents information of a discovered service instance for a DNS-SD query. + * + */ +typedef struct otDnssdServiceInstanceInfo +{ + const char * mFullName; ///< Full instance name (e.g. "OpenThread._ipps._tcp.default.service.arpa."). + const char * mHostName; ///< Host name (e.g. "ot-host.default.service.arpa."). + uint8_t mAddressNum; ///< Number of host IPv6 addresses. + const otIp6Address *mAddresses; ///< Host IPv6 addresses. + uint16_t mPort; ///< Service port. + uint16_t mPriority; ///< Service priority. + uint16_t mWeight; ///< Service weight. + uint16_t mTxtLength; ///< Service TXT RDATA length. + const uint8_t * mTxtData; ///< Service TXT RDATA. + uint32_t mTtl; ///< Service TTL (in seconds). +} otDnssdServiceInstanceInfo; + +/** + * This structure represents information of a discovered host for a DNS-SD query. + * + */ +typedef struct otDnssdHostInfo +{ + uint8_t mAddressNum; ///< Number of host IPv6 addresses. + const otIp6Address *mAddresses; ///< Host IPv6 addresses. + uint32_t mTtl; ///< Service TTL (in seconds). +} otDnssdHostInfo; + +/** + * This function sets DNS-SD server query callbacks. + * + * The DNS-SD server calls @p aSubscribe to subscribe to a service or service instance to resolve a DNS-SD query and @p + * aUnsubscribe to unsubscribe when the query is resolved or timeout. + * + * @note @p aSubscribe and @p aUnsubscribe must be both set or unset. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aSubscribe A pointer to the callback function to subscribe a service or service instance. + * @param[in] aUnsubscribe A pointer to the callback function to unsubscribe a service or service instance. + * @param[in] aContext A pointer to the application-specific context. + * + */ +void otDnssdQuerySetCallbacks(otInstance * aInstance, + otDnssdQuerySubscribeCallback aSubscribe, + otDnssdQueryUnsubscribeCallback aUnsubscribe, + void * aContext); + +/** + * This function notifies a discovered service instance. + * + * The external query resolver (e.g. Discovery Proxy) should call this function to notify OpenThread core of the + * subscribed services or service instances. + * + * @note @p aInstanceInfo must not contain unspecified or link-local or loop-back or multicast IP addresses. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aServiceFullName The null-terminated full service name. + * @param[in] aInstanceInfo A pointer to the discovered service instance information. + * + */ +void otDnssdQueryHandleDiscoveredServiceInstance(otInstance * aInstance, + const char * aServiceFullName, + otDnssdServiceInstanceInfo *aInstanceInfo); +/** + * This function notifies a discovered host. + * + * The external query resolver (e.g. Discovery Proxy) should call this function to notify OpenThread core of the + * subscribed hosts. + * + * @note @p aHostInfo must not contain unspecified or link-local or loop-back or multicast IP addresses. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aHostFullName The null-terminated full host name. + * @param[in] aHostInfo A pointer to the discovered service instance information. + * + */ +void otDnssdQueryHandleDiscoveredHost(otInstance *aInstance, const char *aHostFullName, otDnssdHostInfo *aHostInfo); + +/** + * @} + * + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // OPENTHREAD_DNSSD_SERVER_H_ diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 957dc3255..9356902b8 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (96) +#define OPENTHREAD_API_VERSION (97) /** * @addtogroup api-instance diff --git a/script/test b/script/test index af73c54c6..357b13829 100755 --- a/script/test +++ b/script/test @@ -263,7 +263,7 @@ do_build_otbr_docker() echo "Building OTBR Docker ..." local otdir local otbrdir - local otbr_options="-DOT_DNSSD_SERVER=ON -DOT_DNS_CLIENT=ON -DOT_SRP_CLIENT=ON -DOT_SLAAC=ON -DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON -DOTBR_REST=OFF -DOTBR_WEB=OFF -DOTBR_DUA_ROUTING=ON" + local otbr_options="-DOTBR_DNSSD_DISCOVERY_PROXY=ON -DOT_DNS_CLIENT=ON -DOT_SRP_CLIENT=ON -DOT_SLAAC=ON -DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON -DOTBR_REST=OFF -DOTBR_WEB=OFF -DOTBR_DUA_ROUTING=ON" local otbr_docker_image=${OTBR_DOCKER_IMAGE:-otbr-ot12-backbone-ci} otbrdir=$(mktemp -d -t otbr_XXXXXX) diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 89c470a53..ee2bbd5cc 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -1538,10 +1538,9 @@ void Interpreter::HandleDnsAddressResponse(otError aError, const otDnsAddressRes OutputFormat(" TTL:%u ", ttl); index++; } - - OutputLine(""); } + OutputLine(""); OutputResult(aError); } diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 71561da5d..0e0ca6563 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -307,6 +307,7 @@ openthread_core_files = [ "api/dataset_updater_api.cpp", "api/diags_api.cpp", "api/dns_api.cpp", + "api/dns_server_api.cpp", "api/entropy_api.cpp", "api/error_api.cpp", "api/heap_api.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 2e9ef874e..308f50de4 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -47,6 +47,7 @@ set(COMMON_SOURCES api/dataset_updater_api.cpp api/diags_api.cpp api/dns_api.cpp + api/dns_server_api.cpp api/entropy_api.cpp api/error_api.cpp api/heap_api.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 88ae2e395..f3dc07456 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -124,6 +124,7 @@ SOURCES_COMMON = \ api/dataset_updater_api.cpp \ api/diags_api.cpp \ api/dns_api.cpp \ + api/dns_server_api.cpp \ api/entropy_api.cpp \ api/error_api.cpp \ api/heap_api.cpp \ diff --git a/src/core/api/dns_server_api.cpp b/src/core/api/dns_server_api.cpp new file mode 100644 index 000000000..a0f793c52 --- /dev/null +++ b/src/core/api/dns_server_api.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021, 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 DNS-SD Server APIs. + */ + +#include "openthread-core-config.h" + +#include "common/instance.hpp" +#include "net/dns_types.hpp" +#include "net/dnssd_server.hpp" + +using namespace ot; + +#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE + +void otDnssdQuerySetCallbacks(otInstance * aInstance, + otDnssdQuerySubscribeCallback aSubscribe, + otDnssdQueryUnsubscribeCallback aUnsubscribe, + void * aContext) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().SetQueryCallbacks(aSubscribe, aUnsubscribe, aContext); +} + +void otDnssdQueryHandleDiscoveredServiceInstance(otInstance * aInstance, + const char * aServiceFullName, + otDnssdServiceInstanceInfo *aInstanceInfo) +{ + Instance &instance = *static_cast(aInstance); + + OT_ASSERT(aServiceFullName != nullptr); + OT_ASSERT(aInstanceInfo != nullptr); + + instance.Get().HandleDiscoveredServiceInstance(aServiceFullName, *aInstanceInfo); +} + +void otDnssdQueryHandleDiscoveredHost(otInstance *aInstance, const char *aHostFullName, otDnssdHostInfo *aHostInfo) +{ + Instance &instance = *static_cast(aInstance); + + OT_ASSERT(aHostFullName != nullptr); + OT_ASSERT(aHostInfo != nullptr); + + instance.Get().HandleDiscoveredHost(aHostFullName, *aHostInfo); +} + +#endif // OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE diff --git a/src/core/common/string.cpp b/src/core/common/string.cpp index 150016bac..b787ee5d7 100644 --- a/src/core/common/string.cpp +++ b/src/core/common/string.cpp @@ -65,6 +65,13 @@ const char *StringFind(const char *aString, char aChar) return ret; } +bool StringEndsWith(const char *aString, char aChar) +{ + size_t len = strlen(aString); + + return len > 0 && aString[len - 1] == aChar; +} + Error StringBase::Write(char *aBuffer, uint16_t aSize, uint16_t &aLength, const char *aFormat, va_list aArgs) { Error error = kErrorNone; diff --git a/src/core/common/string.hpp b/src/core/common/string.hpp index 6dd2a0991..48f626643 100644 --- a/src/core/common/string.hpp +++ b/src/core/common/string.hpp @@ -78,6 +78,18 @@ uint16_t StringLength(const char *aString, uint16_t aMaxLength); */ const char *StringFind(const char *aString, char aChar); +/** + * This function checks whether a null-terminated string ends with a given character. + * + * @param[in] aString A pointer to the string. + * @param[in] aChar A char to check. + * + * @retval TRUE If @p aString ends with character @p aChar. + * @retval FALSE If @p aString does not end with character @p aChar. + * + */ +bool StringEndsWith(const char *aString, char aChar); + /** * This class defines the base class for `String`. * diff --git a/src/core/config/dnssd_server.h b/src/core/config/dnssd_server.h index 1d8c21561..b26786e86 100644 --- a/src/core/config/dnssd_server.h +++ b/src/core/config/dnssd_server.h @@ -55,4 +55,14 @@ #define OPENTHREAD_CONFIG_DNSSD_SERVER_PORT 53 #endif +/** + * @def OPENTHREAD_CONFIG_DNSSD_QUERY_TIMEOUT + * + * Specifies the default wait time that DNS-SD Server waits for a query response (e.g. from Discovery Proxy). + * + */ +#ifndef OPENTHREAD_CONFIG_DNSSD_QUERY_TIMEOUT +#define OPENTHREAD_CONFIG_DNSSD_QUERY_TIMEOUT 6000 +#endif + #endif // CONFIG_DNSSD_SERVER_H_ diff --git a/src/core/net/dnssd_server.cpp b/src/core/net/dnssd_server.cpp index 15aa10063..2c4c42d68 100644 --- a/src/core/net/dnssd_server.cpp +++ b/src/core/net/dnssd_server.cpp @@ -56,6 +56,10 @@ const char Server::kDefaultDomainName[] = "default.service.arpa."; Server::Server(Instance &aInstance) : InstanceLocator(aInstance) , mSocket(aInstance) + , mQueryCallbackContext(nullptr) + , mQuerySubscribe(nullptr) + , mQueryUnsubscribe(nullptr) + , mTimer(aInstance, Server::HandleTimer) { } @@ -70,11 +74,26 @@ Error Server::Start(void) exit: otLogInfoDns("[server] started: %s", ErrorToString(error)); + + if (error != kErrorNone) + { + IgnoreError(mSocket.Close()); + } return error; } void Server::Stop(void) { + // Abort all query transactions + for (QueryTransaction &query : mQueryTransactions) + { + if (query.IsValid()) + { + FinalizeQuery(query, Header::kResponseServerFailure); + } + } + mTimer.Stop(); + IgnoreError(mSocket.Close()); otLogInfoDns("[server] stopped"); } @@ -87,40 +106,32 @@ void Server::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessa void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { - Error error = kErrorNone; - Header requestHeader; - Message *responseMessage = nullptr; + Error error = kErrorNone; + Header requestHeader; SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), requestHeader)); VerifyOrExit(requestHeader.GetType() == Header::kTypeQuery, error = kErrorDrop); + ProcessQuery(requestHeader, aMessage, aMessageInfo); +exit: + return; +} + +void Server::ProcessQuery(const Header &aRequestHeader, Message &aRequestMessage, const Ip6::MessageInfo &aMessageInfo) +{ + Error error = kErrorNone; + Message * responseMessage = nullptr; + Header responseHeader; + NameCompressInfo compressInfo(kDefaultDomainName); + Header::Response response = Header::kResponseSuccess; + bool resolveByQueryCallbacks = false; + responseMessage = mSocket.NewMessage(0); VerifyOrExit(responseMessage != nullptr, error = kErrorNoBufs); // Allocate space for DNS header SuccessOrExit(error = responseMessage->SetLength(sizeof(Header))); - // ProcessQuery is assumed to always prepare the response DNS message and header properly even in the case of system - // failures (e.g. no more buffers). - ProcessQuery(aMessage, *responseMessage, requestHeader); - - error = mSocket.SendTo(*responseMessage, aMessageInfo); - -exit: - FreeMessageOnError(responseMessage, error); -} - -void Server::ProcessQuery(Message &aMessage, Message &aResponse, const Header &aRequestHeader) -{ - Header responseHeader; - uint16_t readOffset; - Question question; - char name[Dns::Name::kMaxNameSize]; - NameCompressInfo compressInfo(kDefaultDomainName); - Header::Response response = Header::Response::kResponseSuccess; - Error error = kErrorNone; - uint8_t resolveAdditional = kResolveAdditionalAll; - // Setup initial DNS response header responseHeader.Clear(); responseHeader.SetType(Header::kTypeResponse); @@ -132,6 +143,75 @@ void Server::ProcessQuery(Message &aMessage, Message &aResponse, const Header &a VerifyOrExit(!aRequestHeader.IsTruncationFlagSet(), response = Header::kResponseFormatError); VerifyOrExit(aRequestHeader.GetQuestionCount() > 0, response = Header::kResponseFormatError); + response = AddQuestions(aRequestHeader, aRequestMessage, responseHeader, *responseMessage, compressInfo); + VerifyOrExit(response == Header::kResponseSuccess); + +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + // Answer the questions + response = ResolveBySrp(responseHeader, *responseMessage, compressInfo); +#endif + + // Resolve the question using query callbacks if SRP server failed to resolve the questions. + if (responseHeader.GetAnswerCount() == 0 && + kErrorNone == ResolveByQueryCallbacks(responseHeader, *responseMessage, compressInfo, aMessageInfo)) + { + resolveByQueryCallbacks = true; + } + +exit: + if (error == kErrorNone && !resolveByQueryCallbacks) + { + SendResponse(responseHeader, response, *responseMessage, aMessageInfo, mSocket); + } + + FreeMessageOnError(responseMessage, error); +} + +void Server::SendResponse(Header aHeader, + Header::Response aResponseCode, + Message & aMessage, + const Ip6::MessageInfo &aMessageInfo, + Ip6::Udp::Socket & aSocket) +{ + Error error; + + if (aResponseCode == Header::kResponseServerFailure) + { + otLogWarnDns("[server] failed to handle DNS query due to server failure"); + aHeader.SetQuestionCount(0); + aHeader.SetAnswerCount(0); + aHeader.SetAdditionalRecordCount(0); + IgnoreError(aMessage.SetLength(sizeof(Header))); + } + + aHeader.SetResponseCode(aResponseCode); + aMessage.Write(0, aHeader); + + error = aSocket.SendTo(aMessage, aMessageInfo); + + FreeMessageOnError(&aMessage, error); + + if (error != kErrorNone) + { + otLogWarnDns("[server] failed to send DNS-SD reply: %s", otThreadErrorToString(error)); + } + else + { + otLogInfoDns("[server] send DNS-SD reply: %s, RCODE=%d", otThreadErrorToString(error), aResponseCode); + } +} + +Header::Response Server::AddQuestions(const Header & aRequestHeader, + const Message & aRequestMessage, + Header & aResponseHeader, + Message & aResponseMessage, + NameCompressInfo &aCompressInfo) +{ + Question question; + uint16_t readOffset; + Header::Response response = Header::kResponseSuccess; + char name[Name::kMaxNameSize]; + readOffset = sizeof(Header); // Check and append the questions @@ -139,9 +219,9 @@ void Server::ProcessQuery(Message &aMessage, Message &aResponse, const Header &a { NameComponentsOffsetInfo nameComponentsOffsetInfo; - VerifyOrExit(kErrorNone == Dns::Name::ReadName(aMessage, readOffset, name, sizeof(name)), + VerifyOrExit(kErrorNone == Name::ReadName(aRequestMessage, readOffset, name, sizeof(name)), response = Header::kResponseFormatError); - VerifyOrExit(kErrorNone == aMessage.Read(readOffset, question), response = Header::kResponseFormatError); + VerifyOrExit(kErrorNone == aRequestMessage.Read(readOffset, question), response = Header::kResponseFormatError); readOffset += sizeof(question); uint16_t qtype = question.GetType(); @@ -150,7 +230,7 @@ void Server::ProcessQuery(Message &aMessage, Message &aResponse, const Header &a qtype == ResourceRecord::kTypeTxt || qtype == ResourceRecord::kTypeAaaa, response = Header::kResponseNotImplemented); - VerifyOrExit(kErrorNone == FindNameComponents(name, compressInfo.GetDomainName(), nameComponentsOffsetInfo), + VerifyOrExit(kErrorNone == FindNameComponents(name, aCompressInfo.GetDomainName(), nameComponentsOffsetInfo), response = Header::kResponseNameError); switch (question.GetType()) @@ -160,94 +240,24 @@ void Server::ProcessQuery(Message &aMessage, Message &aResponse, const Header &a break; case ResourceRecord::kTypeSrv: VerifyOrExit(nameComponentsOffsetInfo.IsServiceInstanceName(), response = Header::kResponseNameError); - resolveAdditional &= ~kResolveAdditionalSrv; break; case ResourceRecord::kTypeTxt: VerifyOrExit(nameComponentsOffsetInfo.IsServiceInstanceName(), response = Header::kResponseNameError); - resolveAdditional &= ~kResolveAdditionalTxt; break; case ResourceRecord::kTypeAaaa: VerifyOrExit(nameComponentsOffsetInfo.IsHostName(), response = Header::kResponseNameError); - resolveAdditional &= ~kResolveAdditionalAaaa; break; default: ExitNow(response = Header::kResponseNotImplemented); } - SuccessOrExit(error = AppendQuestion(name, question, aResponse, compressInfo)); - } - - responseHeader.SetQuestionCount(aRequestHeader.GetQuestionCount()); - - // Answer the questions - readOffset = sizeof(Header); - for (uint16_t i = 0; i < aRequestHeader.GetQuestionCount(); i++) - { - uint8_t resolveKind = kResolveAnswer; - - IgnoreError(Dns::Name::ReadName(aMessage, readOffset, name, sizeof(name))); - IgnoreError(aMessage.Read(readOffset, question)); - readOffset += sizeof(question); - - response = ResolveQuestion(name, question, responseHeader, aResponse, resolveKind, compressInfo); - - otLogInfoDns("[server] ANSWER: TRANSACTION=0x%04x, QUESTION=[%s %d %d], RCODE=%d", - aRequestHeader.GetMessageId(), name, question.GetClass(), question.GetType(), response); - } - - // Answer the questions with additional RRs if required - VerifyOrExit(resolveAdditional != kResolveNone); - - readOffset = sizeof(Header); - for (uint16_t i = 0; i < aRequestHeader.GetQuestionCount(); i++) - { - IgnoreError(Dns::Name::ReadName(aMessage, readOffset, name, sizeof(name))); - IgnoreError(aMessage.Read(readOffset, question)); - readOffset += sizeof(question); - - VerifyOrExit(Header::kResponseServerFailure != - ResolveQuestion(name, question, responseHeader, aResponse, resolveAdditional, compressInfo), + VerifyOrExit(AppendQuestion(name, question, aResponseMessage, aCompressInfo) == kErrorNone, response = Header::kResponseServerFailure); - - otLogInfoDns("[server] ADDITIONAL: TRANSACTION=0x%04x, QUESTION=[%s %d %d], RCODE=%d", - aRequestHeader.GetMessageId(), name, question.GetClass(), question.GetType(), response); } + aResponseHeader.SetQuestionCount(aRequestHeader.GetQuestionCount()); + exit: - response = (error == kErrorNone) ? response : Header::Response::kResponseServerFailure; - - if (response == Header::Response::kResponseServerFailure) - { - otLogWarnDns("[server] failed to handle DNS query due to server failure"); - responseHeader.SetQuestionCount(0); - responseHeader.SetAnswerCount(0); - responseHeader.SetAdditionalRecordCount(0); - IgnoreError(aResponse.SetLength(sizeof(Header))); - } - - responseHeader.SetResponseCode(response); - aResponse.Write(0, responseHeader); -} - -Header::Response Server::ResolveQuestion(const char * aName, - const Question & aQuestion, - Header & aResponseHeader, - Message & aResponseMessage, - uint8_t aResolveKind, - NameCompressInfo &aCompressInfo) -{ - OT_UNUSED_VARIABLE(aName); - OT_UNUSED_VARIABLE(aQuestion); - OT_UNUSED_VARIABLE(aResponseHeader); - OT_UNUSED_VARIABLE(aResponseMessage); - OT_UNUSED_VARIABLE(aCompressInfo); - - Header::Response response = Header::kResponseNameError; - -#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE - response = ResolveQuestionBySrp(aName, aQuestion, aResponseHeader, aResponseMessage, aResolveKind, aCompressInfo); -#endif - return response; } @@ -363,11 +373,11 @@ exit: Error Server::AppendServiceName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo) { Error error; - uint16_t serviceCompressOffset = aCompressInfo.GetServiceNameOffset(aName); + uint16_t serviceCompressOffset = aCompressInfo.GetServiceNameOffset(aMessage, aName); if (serviceCompressOffset != NameCompressInfo::kUnknownOffset) { - error = Dns::Name::AppendPointerLabel(serviceCompressOffset, aMessage); + error = Name::AppendPointerLabel(serviceCompressOffset, aMessage); } else { @@ -376,17 +386,17 @@ Error Server::AppendServiceName(Message &aMessage, const char *aName, NameCompre uint16_t domainCompressOffset = aCompressInfo.GetDomainNameOffset(); serviceCompressOffset = aMessage.GetLength(); - aCompressInfo.SetServiceNameOffset(serviceCompressOffset, aName); + aCompressInfo.SetServiceNameOffset(serviceCompressOffset); if (domainCompressOffset == NameCompressInfo::kUnknownOffset) { aCompressInfo.SetDomainNameOffset(serviceCompressOffset + domainStart); - error = Dns::Name::AppendName(aName, aMessage); + error = Name::AppendName(aName, aMessage); } else { - SuccessOrExit(error = Dns::Name::AppendMultipleLabels(aName, domainStart, aMessage)); - error = Dns::Name::AppendPointerLabel(domainCompressOffset, aMessage); + SuccessOrExit(error = Name::AppendMultipleLabels(aName, domainStart, aMessage)); + error = Name::AppendPointerLabel(domainCompressOffset, aMessage); } } @@ -398,11 +408,11 @@ Error Server::AppendInstanceName(Message &aMessage, const char *aName, NameCompr { Error error; - uint16_t instanceCompressOffset = aCompressInfo.GetInstanceNameOffset(aName); + uint16_t instanceCompressOffset = aCompressInfo.GetInstanceNameOffset(aMessage, aName); if (instanceCompressOffset != NameCompressInfo::kUnknownOffset) { - error = Dns::Name::AppendPointerLabel(instanceCompressOffset, aMessage); + error = Name::AppendPointerLabel(instanceCompressOffset, aMessage); } else { @@ -411,27 +421,49 @@ Error Server::AppendInstanceName(Message &aMessage, const char *aName, NameCompr IgnoreError(FindNameComponents(aName, aCompressInfo.GetDomainName(), nameComponentsInfo)); OT_ASSERT(nameComponentsInfo.IsServiceInstanceName()); - aCompressInfo.SetInstanceNameOffset(aMessage.GetLength(), aName); + aCompressInfo.SetInstanceNameOffset(aMessage.GetLength()); // Append the instance name as one label - SuccessOrExit(error = Dns::Name::AppendLabel(aName, nameComponentsInfo.mServiceOffset - 1, aMessage)); + SuccessOrExit(error = Name::AppendLabel(aName, nameComponentsInfo.mServiceOffset - 1, aMessage)); { const char *serviceName = aName + nameComponentsInfo.mServiceOffset; - uint16_t serviceCompressOffset = aCompressInfo.GetServiceNameOffset(serviceName); + uint16_t serviceCompressOffset = aCompressInfo.GetServiceNameOffset(aMessage, serviceName); if (serviceCompressOffset != NameCompressInfo::kUnknownOffset) { - error = Dns::Name::AppendPointerLabel(serviceCompressOffset, aMessage); + error = Name::AppendPointerLabel(serviceCompressOffset, aMessage); } else { - aCompressInfo.SetServiceNameOffset(aMessage.GetLength(), serviceName); - error = Dns::Name::AppendName(serviceName, aMessage); + aCompressInfo.SetServiceNameOffset(aMessage.GetLength()); + error = Name::AppendName(serviceName, aMessage); } } } +exit: + return error; +} +Error Server::AppendTxtRecord(Message & aMessage, + const char * aInstanceName, + const void * aTxtData, + uint16_t aTxtLength, + uint32_t aTtl, + NameCompressInfo &aCompressInfo) +{ + Error error = kErrorNone; + TxtRecord txtRecord; + + SuccessOrExit(error = AppendInstanceName(aMessage, aInstanceName, aCompressInfo)); + + txtRecord.Init(); + txtRecord.SetTtl(aTtl); + txtRecord.SetLength(aTxtLength); + + SuccessOrExit(error = aMessage.Append(txtRecord)); + error = aMessage.AppendBytes(aTxtData, aTxtLength); + exit: return error; } @@ -439,11 +471,11 @@ exit: Error Server::AppendHostName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo) { Error error; - uint16_t hostCompressOffset = aCompressInfo.GetHostNameOffset(aName); + uint16_t hostCompressOffset = aCompressInfo.GetHostNameOffset(aMessage, aName); if (hostCompressOffset != NameCompressInfo::kUnknownOffset) { - error = Dns::Name::AppendPointerLabel(hostCompressOffset, aMessage); + error = Name::AppendPointerLabel(hostCompressOffset, aMessage); } else { @@ -452,17 +484,17 @@ Error Server::AppendHostName(Message &aMessage, const char *aName, NameCompressI uint16_t domainCompressOffset = aCompressInfo.GetDomainNameOffset(); hostCompressOffset = aMessage.GetLength(); - aCompressInfo.SetHostNameOffset(hostCompressOffset, aName); + aCompressInfo.SetHostNameOffset(hostCompressOffset); if (domainCompressOffset == NameCompressInfo::kUnknownOffset) { aCompressInfo.SetDomainNameOffset(hostCompressOffset + domainStart); - error = Dns::Name::AppendName(aName, aMessage); + error = Name::AppendName(aName, aMessage); } else { - SuccessOrExit(error = Dns::Name::AppendMultipleLabels(aName, domainStart, aMessage)); - error = Dns::Name::AppendPointerLabel(domainCompressOffset, aMessage); + SuccessOrExit(error = Name::AppendMultipleLabels(aName, domainStart, aMessage)); + error = Name::AppendPointerLabel(domainCompressOffset, aMessage); } } @@ -489,7 +521,7 @@ Error Server::FindNameComponents(const char *aName, const char *aDomain, NameCom Error error = kErrorNone; uint8_t labelBegin, labelEnd; - VerifyOrExit(Dns::Name::IsSubDomainOf(aName, aDomain), error = kErrorInvalidArgs); + VerifyOrExit(Name::IsSubDomainOf(aName, aDomain), error = kErrorInvalidArgs); labelBegin = nameLen - domainLen; aInfo.mDomainOffset = labelBegin; @@ -555,12 +587,57 @@ exit: } #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE +Header::Response Server::ResolveBySrp(Header & aResponseHeader, + Message & aResponseMessage, + Server::NameCompressInfo &aCompressInfo) +{ + Question question; + uint16_t readOffset = sizeof(Header); + Header::Response response = Header::kResponseSuccess; + char name[Name::kMaxNameSize]; + + for (uint16_t i = 0; i < aResponseHeader.GetQuestionCount(); i++) + { + IgnoreError(Name::ReadName(aResponseMessage, readOffset, name, sizeof(name))); + IgnoreError(aResponseMessage.Read(readOffset, question)); + readOffset += sizeof(question); + + response = ResolveQuestionBySrp(name, question, aResponseHeader, aResponseMessage, aCompressInfo, + /* aAdditional */ false); + + otLogInfoDns("[server] ANSWER: TRANSACTION=0x%04x, QUESTION=[%s %d %d], RCODE=%d", + aResponseHeader.GetMessageId(), name, question.GetClass(), question.GetType(), response); + } + + // Answer the questions with additional RRs if required + if (aResponseHeader.GetAnswerCount() > 0) + { + readOffset = sizeof(Header); + for (uint16_t i = 0; i < aResponseHeader.GetQuestionCount(); i++) + { + IgnoreError(Name::ReadName(aResponseMessage, readOffset, name, sizeof(name))); + IgnoreError(aResponseMessage.Read(readOffset, question)); + readOffset += sizeof(question); + + VerifyOrExit(Header::kResponseServerFailure != ResolveQuestionBySrp(name, question, aResponseHeader, + aResponseMessage, aCompressInfo, + /* aAdditional */ true), + response = Header::kResponseServerFailure); + + otLogInfoDns("[server] ADDITIONAL: TRANSACTION=0x%04x, QUESTION=[%s %d %d], RCODE=%d", + aResponseHeader.GetMessageId(), name, question.GetClass(), question.GetType(), response); + } + } +exit: + return response; +} + Header::Response Server::ResolveQuestionBySrp(const char * aName, const Question & aQuestion, Header & aResponseHeader, Message & aResponseMessage, - uint8_t aResolveKind, - NameCompressInfo &aCompressInfo) + NameCompressInfo &aCompressInfo, + bool aAdditional) { Error error = kErrorNone; const Srp::Server::Host *host = nullptr; @@ -593,38 +670,41 @@ Header::Response Server::ResolveQuestionBySrp(const char * aName, needAdditionalAaaaRecord = true; } - if (aResolveKind == kResolveAnswer && ptrQueryMatched) + if (!aAdditional && ptrQueryMatched) { SuccessOrExit( error = AppendPtrRecord(aResponseMessage, aName, instanceName, instanceTtl, aCompressInfo)); - IncResourceRecordCount(aResponseHeader, aResolveKind != kResolveAnswer); - response = Header::Response::kResponseSuccess; + IncResourceRecordCount(aResponseHeader, aAdditional); + response = Header::kResponseSuccess; } - if ((aResolveKind == kResolveAnswer && srvQueryMatched) || - ((aResolveKind & kResolveAdditionalSrv) && ptrQueryMatched)) + if ((!aAdditional && srvQueryMatched) || + (aAdditional && ptrQueryMatched && + !HasQuestion(aResponseHeader, aResponseMessage, instanceName, ResourceRecord::kTypeSrv))) { SuccessOrExit(error = AppendSrvRecord(aResponseMessage, instanceName, hostName, instanceTtl, service->GetPriority(), service->GetWeight(), service->GetPort(), aCompressInfo)); - IncResourceRecordCount(aResponseHeader, aResolveKind != kResolveAnswer); - response = Header::Response::kResponseSuccess; + IncResourceRecordCount(aResponseHeader, aAdditional); + response = Header::kResponseSuccess; } - if ((aResolveKind == kResolveAnswer && txtQueryMatched) || - ((aResolveKind & kResolveAdditionalTxt) && ptrQueryMatched)) + if ((!aAdditional && txtQueryMatched) || + (aAdditional && ptrQueryMatched && + !HasQuestion(aResponseHeader, aResponseMessage, instanceName, ResourceRecord::kTypeTxt))) { - SuccessOrExit( - error = AppendTxtRecord(aResponseMessage, instanceName, *service, instanceTtl, aCompressInfo)); - IncResourceRecordCount(aResponseHeader, aResolveKind != kResolveAnswer); - response = Header::Response::kResponseSuccess; + SuccessOrExit(error = AppendTxtRecord(aResponseMessage, instanceName, service->GetTxtData(), + service->GetTxtDataLength(), instanceTtl, aCompressInfo)); + IncResourceRecordCount(aResponseHeader, aAdditional); + response = Header::kResponseSuccess; } } } // Handle AAAA query - if ((aResolveKind == kResolveAnswer && qtype == ResourceRecord::kTypeAaaa && host->Matches(aName)) || - ((aResolveKind & kResolveAdditionalAaaa) && needAdditionalAaaaRecord)) + if ((!aAdditional && qtype == ResourceRecord::kTypeAaaa && host->Matches(aName)) || + (aAdditional && needAdditionalAaaaRecord && + !HasQuestion(aResponseHeader, aResponseMessage, hostName, ResourceRecord::kTypeAaaa))) { uint8_t addrNum; const Ip6::Address *addrs = host->GetAddresses(addrNum); @@ -633,15 +713,15 @@ Header::Response Server::ResolveQuestionBySrp(const char * aName, for (uint8_t i = 0; i < addrNum; i++) { SuccessOrExit(error = AppendAaaaRecord(aResponseMessage, hostName, addrs[i], hostTtl, aCompressInfo)); - IncResourceRecordCount(aResponseHeader, aResolveKind != kResolveAnswer); + IncResourceRecordCount(aResponseHeader, aAdditional); } - response = Header::Response::kResponseSuccess; + response = Header::kResponseSuccess; } } exit: - return error == kErrorNone ? response : Header::Response::kResponseServerFailure; + return error == kErrorNone ? response : Header::kResponseServerFailure; } const Srp::Server::Host *Server::GetNextSrpHost(const Srp::Server::Host *aHost) @@ -668,34 +748,386 @@ const Srp::Server::Service *Server::GetNextSrpService(const Srp::Server::Host & return service; } +#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE -Error Server::AppendTxtRecord(Message & aMessage, - const char * aInstanceName, - const Srp::Server::Service &aService, - uint32_t aTtl, - NameCompressInfo & aCompressInfo) +Error Server::ResolveByQueryCallbacks(Header & aResponseHeader, + Message & aResponseMessage, + NameCompressInfo & aCompressInfo, + const Ip6::MessageInfo &aMessageInfo) { - Error error; - uint16_t recordOffset; - TxtRecord txtRecord; + QueryTransaction *query = nullptr; + DnsQueryType queryType; + char name[Name::kMaxNameSize]; - SuccessOrExit(error = AppendInstanceName(aMessage, aInstanceName, aCompressInfo)); + Error error = kErrorNone; - recordOffset = aMessage.GetLength(); - SuccessOrExit(error = aMessage.SetLength(recordOffset + sizeof(txtRecord))); + VerifyOrExit(mQuerySubscribe != nullptr, error = kErrorFailed); - SuccessOrExit(error = aMessage.AppendBytes(aService.GetTxtData(), aService.GetTxtDataLength())); + queryType = GetQueryType(aResponseHeader, aResponseMessage, name); + VerifyOrExit(queryType != kDnsQueryNone, error = kErrorNotImplemented); - txtRecord.Init(); - txtRecord.SetTtl(aTtl); - txtRecord.SetLength(aMessage.GetLength() - (recordOffset + sizeof(ResourceRecord))); + query = NewQuery(aResponseHeader, aResponseMessage, aCompressInfo, aMessageInfo); + VerifyOrExit(query != nullptr, error = kErrorNoBufs); - aMessage.Write(recordOffset, txtRecord); + mQuerySubscribe(mQueryCallbackContext, name); exit: return error; } -#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + +Server::QueryTransaction *Server::NewQuery(const Header & aResponseHeader, + Message & aResponseMessage, + const NameCompressInfo &aCompressInfo, + const Ip6::MessageInfo &aMessageInfo) +{ + QueryTransaction *newQuery = nullptr; + + for (QueryTransaction &query : mQueryTransactions) + { + if (query.IsValid()) + { + continue; + } + + query.Init(aResponseHeader, aResponseMessage, aCompressInfo, aMessageInfo); + ExitNow(newQuery = &query); + } + +exit: + + if (newQuery != nullptr) + { + ResetTimer(); + } + return newQuery; +} + +bool Server::CanAnswerQuery(const QueryTransaction & aQuery, + const char * aServiceFullName, + const otDnssdServiceInstanceInfo &aInstanceInfo) +{ + char name[Name::kMaxNameSize]; + DnsQueryType sdType; + bool canAnswer = false; + + sdType = GetQueryType(aQuery.GetResponseHeader(), aQuery.GetResponseMessage(), name); + + switch (sdType) + { + case kDnsQueryBrowse: + canAnswer = (strcmp(name, aServiceFullName) == 0); + break; + case kDnsQueryResolve: + canAnswer = (strcmp(name, aInstanceInfo.mFullName) == 0); + break; + default: + break; + } + + return canAnswer; +} + +bool Server::CanAnswerQuery(const Server::QueryTransaction &aQuery, const char *aHostFullName) +{ + char name[Name::kMaxNameSize]; + DnsQueryType sdType; + + sdType = GetQueryType(aQuery.GetResponseHeader(), aQuery.GetResponseMessage(), name); + return (sdType == kDnsQueryResolveHost) && (strcmp(name, aHostFullName) == 0); +} + +void Server::AnswerQuery(QueryTransaction & aQuery, + const char * aServiceFullName, + const otDnssdServiceInstanceInfo &aInstanceInfo) +{ + Header & responseHeader = aQuery.GetResponseHeader(); + Message & responseMessage = aQuery.GetResponseMessage(); + Error error = kErrorNone; + NameCompressInfo &compressInfo = aQuery.GetNameCompressInfo(); + + if (HasQuestion(aQuery.GetResponseHeader(), aQuery.GetResponseMessage(), aServiceFullName, + ResourceRecord::kTypePtr)) + { + SuccessOrExit(error = AppendPtrRecord(responseMessage, aServiceFullName, aInstanceInfo.mFullName, + aInstanceInfo.mTtl, compressInfo)); + IncResourceRecordCount(responseHeader, false); + } + + for (uint8_t additional = 0; additional <= 1; additional++) + { + if (HasQuestion(aQuery.GetResponseHeader(), aQuery.GetResponseMessage(), aInstanceInfo.mFullName, + ResourceRecord::kTypeSrv) == !additional) + { + SuccessOrExit(error = AppendSrvRecord(responseMessage, aInstanceInfo.mFullName, aInstanceInfo.mHostName, + aInstanceInfo.mTtl, aInstanceInfo.mPriority, aInstanceInfo.mWeight, + aInstanceInfo.mPort, compressInfo)); + IncResourceRecordCount(responseHeader, additional); + } + + if (HasQuestion(aQuery.GetResponseHeader(), aQuery.GetResponseMessage(), aInstanceInfo.mFullName, + ResourceRecord::kTypeTxt) == !additional) + { + SuccessOrExit(error = AppendTxtRecord(responseMessage, aInstanceInfo.mFullName, aInstanceInfo.mTxtData, + aInstanceInfo.mTxtLength, aInstanceInfo.mTtl, compressInfo)); + IncResourceRecordCount(responseHeader, additional); + } + + if (HasQuestion(aQuery.GetResponseHeader(), aQuery.GetResponseMessage(), aInstanceInfo.mHostName, + ResourceRecord::kTypeAaaa) == !additional) + { + for (uint8_t i = 0; i < aInstanceInfo.mAddressNum; i++) + { + const Ip6::Address &address = static_cast(aInstanceInfo.mAddresses[i]); + + OT_ASSERT(!address.IsUnspecified() && !address.IsLinkLocal() && !address.IsMulticast() && + !address.IsLoopback()); + + SuccessOrExit(error = AppendAaaaRecord(responseMessage, aInstanceInfo.mHostName, address, + aInstanceInfo.mTtl, compressInfo)); + IncResourceRecordCount(responseHeader, additional); + } + } + } + +exit: + FinalizeQuery(aQuery, error == kErrorNone ? Header::kResponseSuccess : Header::kResponseServerFailure); + ResetTimer(); +} + +void Server::AnswerQuery(QueryTransaction &aQuery, const char *aHostFullName, const otDnssdHostInfo &aHostInfo) +{ + Header & responseHeader = aQuery.GetResponseHeader(); + Message & responseMessage = aQuery.GetResponseMessage(); + Error error = kErrorNone; + NameCompressInfo &compressInfo = aQuery.GetNameCompressInfo(); + + if (HasQuestion(aQuery.GetResponseHeader(), aQuery.GetResponseMessage(), aHostFullName, ResourceRecord::kTypeAaaa)) + { + for (uint8_t i = 0; i < aHostInfo.mAddressNum; i++) + { + const Ip6::Address &address = static_cast(aHostInfo.mAddresses[i]); + + OT_ASSERT(!address.IsUnspecified() && !address.IsMulticast() && !address.IsLinkLocal() && + !address.IsLoopback()); + + SuccessOrExit(error = + AppendAaaaRecord(responseMessage, aHostFullName, address, aHostInfo.mTtl, compressInfo)); + IncResourceRecordCount(responseHeader, /* aAdditional */ false); + } + } + +exit: + FinalizeQuery(aQuery, error == kErrorNone ? Header::kResponseSuccess : Header::kResponseServerFailure); + ResetTimer(); +} + +void Server::SetQueryCallbacks(otDnssdQuerySubscribeCallback aSubscribe, + otDnssdQueryUnsubscribeCallback aUnsubscribe, + void * aContext) +{ + OT_ASSERT((aSubscribe == nullptr) == (aUnsubscribe == nullptr)); + + mQuerySubscribe = aSubscribe; + mQueryUnsubscribe = aUnsubscribe; + mQueryCallbackContext = aContext; +} + +void Server::HandleDiscoveredServiceInstance(const char * aServiceFullName, + const otDnssdServiceInstanceInfo &aInstanceInfo) +{ + OT_ASSERT(StringEndsWith(aServiceFullName, Name::kLabelSeperatorChar)); + OT_ASSERT(StringEndsWith(aInstanceInfo.mFullName, Name::kLabelSeperatorChar)); + OT_ASSERT(StringEndsWith(aInstanceInfo.mHostName, Name::kLabelSeperatorChar)); + + for (QueryTransaction &query : mQueryTransactions) + { + if (query.IsValid() && CanAnswerQuery(query, aServiceFullName, aInstanceInfo)) + { + AnswerQuery(query, aServiceFullName, aInstanceInfo); + } + } +} + +void Server::HandleDiscoveredHost(const char *aHostFullName, const otDnssdHostInfo &aHostInfo) +{ + OT_ASSERT(StringEndsWith(aHostFullName, Name::kLabelSeperatorChar)); + + for (QueryTransaction &query : mQueryTransactions) + { + if (query.IsValid() && CanAnswerQuery(query, aHostFullName)) + { + AnswerQuery(query, aHostFullName, aHostInfo); + } + } +} + +Server::DnsQueryType Server::GetQueryType(const Header & aHeader, + const Message &aMessage, + char (&aName)[Name::kMaxNameSize]) +{ + DnsQueryType sdType = kDnsQueryNone; + + for (uint16_t i = 0, readOffset = sizeof(Header); i < aHeader.GetQuestionCount(); i++) + { + Question question; + + IgnoreError(Name::ReadName(aMessage, readOffset, aName, sizeof(aName))); + IgnoreError(aMessage.Read(readOffset, question)); + readOffset += sizeof(question); + + switch (question.GetType()) + { + case ResourceRecord::kTypePtr: + ExitNow(sdType = kDnsQueryBrowse); + case ResourceRecord::kTypeSrv: + case ResourceRecord::kTypeTxt: + ExitNow(sdType = kDnsQueryResolve); + } + } + + for (uint16_t i = 0, readOffset = sizeof(Header); i < aHeader.GetQuestionCount(); i++) + { + Question question; + + IgnoreError(Name::ReadName(aMessage, readOffset, aName, sizeof(aName))); + IgnoreError(aMessage.Read(readOffset, question)); + readOffset += sizeof(question); + + switch (question.GetType()) + { + case ResourceRecord::kTypeAaaa: + case ResourceRecord::kTypeA: + ExitNow(sdType = kDnsQueryResolveHost); + } + } + +exit: + return sdType; +} + +bool Server::HasQuestion(const Header &aHeader, const Message &aMessage, const char *aName, uint16_t aQuestionType) +{ + bool found = false; + + for (uint16_t i = 0, readOffset = sizeof(Header); i < aHeader.GetQuestionCount(); i++) + { + Question question; + Error error; + + error = Name::CompareName(aMessage, readOffset, aName); + IgnoreError(aMessage.Read(readOffset, question)); + readOffset += sizeof(question); + + if (error == kErrorNone && aQuestionType == question.GetType()) + { + ExitNow(found = true); + } + } + +exit: + return found; +} + +void Server::HandleTimer(Timer &aTimer) +{ + aTimer.Get().HandleTimer(); +} + +void Server::HandleTimer(void) +{ + TimeMilli now = TimerMilli::GetNow(); + + for (QueryTransaction &query : mQueryTransactions) + { + TimeMilli expire; + + if (!query.IsValid()) + { + continue; + } + + expire = query.GetStartTime() + kQueryTimeout; + if (expire <= now) + { + FinalizeQuery(query, Header::kResponseSuccess); + } + } + + ResetTimer(); +} + +void Server::ResetTimer(void) +{ + TimeMilli now = TimerMilli::GetNow(); + TimeMilli nextExpire = now.GetDistantFuture(); + + for (QueryTransaction &query : mQueryTransactions) + { + TimeMilli expire; + + if (!query.IsValid()) + { + continue; + } + + expire = query.GetStartTime() + kQueryTimeout; + if (expire <= now) + { + nextExpire = now; + } + else if (expire < nextExpire) + { + nextExpire = expire; + } + } + + if (nextExpire < now.GetDistantFuture()) + { + mTimer.FireAt(nextExpire); + } + else + { + mTimer.Stop(); + } +} + +void Server::FinalizeQuery(QueryTransaction &aQuery, Header::Response aResponseCode) +{ + char name[Name::kMaxNameSize]; + DnsQueryType sdType; + + OT_ASSERT(mQueryUnsubscribe != nullptr); + + sdType = GetQueryType(aQuery.GetResponseHeader(), aQuery.GetResponseMessage(), name); + + OT_ASSERT(sdType != kDnsQueryNone); + OT_UNUSED_VARIABLE(sdType); + + mQueryUnsubscribe(mQueryCallbackContext, name); + aQuery.Finalize(aResponseCode, mSocket); +} + +void Server::QueryTransaction::Init(const Header & aResponseHeader, + Message & aResponseMessage, + const NameCompressInfo &aCompressInfo, + const Ip6::MessageInfo &aMessageInfo) +{ + OT_ASSERT(mResponseMessage == nullptr); + + mResponseHeader = aResponseHeader; + mResponseMessage = &aResponseMessage; + mCompressInfo = aCompressInfo; + mMessageInfo = aMessageInfo; + mStartTime = TimerMilli::GetNow(); +} + +void Server::QueryTransaction::Finalize(Header::Response aResponseMessage, Ip6::Udp::Socket &aSocket) +{ + OT_ASSERT(mResponseMessage != nullptr); + + SendResponse(mResponseHeader, aResponseMessage, *mResponseMessage, mMessageInfo, aSocket); + mResponseMessage = nullptr; +} } // namespace ServiceDiscovery } // namespace Dns diff --git a/src/core/net/dnssd_server.hpp b/src/core/net/dnssd_server.hpp index af04ba2be..5330485b9 100644 --- a/src/core/net/dnssd_server.hpp +++ b/src/core/net/dnssd_server.hpp @@ -33,7 +33,7 @@ #if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE -#include +#include #include "common/message.hpp" #include "common/non_copyable.hpp" @@ -82,21 +82,42 @@ public: */ void Stop(void); + /** + * This method sets DNS-SD query callbacks. + * + * @param[in] aSubscribe A pointer to the callback function to subscribe a service or service instance. + * @param[in] aUnsubscribe A pointer to the callback function to unsubscribe a service or service instance. + * @param[in] aContext A pointer to the application-specific context. + * + */ + void SetQueryCallbacks(otDnssdQuerySubscribeCallback aSubscribe, + otDnssdQueryUnsubscribeCallback aUnsubscribe, + void * aContext); + + /** + * This method notifies a discovered service instance. + * + * @param[in] aServiceFullName The null-terminated full service name. + * @param[in] aInstanceInfo A pointer to the discovered service instance information. + * + */ + void HandleDiscoveredServiceInstance(const char *aServiceFullName, const otDnssdServiceInstanceInfo &aInstanceInfo); + + /** + * This method notifies a discovered host. + * + * @param[in] aHostFullName The null-terminated full host name. + * @param[in] aHostInfo A pointer to the discovered host information. + * + */ + void HandleDiscoveredHost(const char *aHostFullName, const otDnssdHostInfo &aHostInfo); + private: enum { - kPort = OPENTHREAD_CONFIG_DNSSD_SERVER_PORT, - kProtocolLabelLength = 4, - }; - - enum : uint8_t - { - kResolveNone = 0, - kResolveAnswer = 1u << 0, - kResolveAdditionalSrv = 1u << 1, - kResolveAdditionalTxt = 1u << 2, - kResolveAdditionalAaaa = 1u << 3, - kResolveAdditionalAll = kResolveAdditionalSrv | kResolveAdditionalTxt | kResolveAdditionalAaaa, + kPort = OPENTHREAD_CONFIG_DNSSD_SERVER_PORT, + kProtocolLabelLength = 4, + kMaxConcurrentQueries = 32, }; class NameCompressInfo : public Clearable @@ -107,11 +128,10 @@ private: kUnknownOffset = 0, // Unknown offset value (used when offset is not yet set). }; + explicit NameCompressInfo(void) = default; + explicit NameCompressInfo(const char *aDomainName) : mDomainName(aDomainName) - , mServiceName(nullptr) - , mInstanceName(nullptr) - , mHostName(nullptr) , mDomainNameOffset(kUnknownOffset) , mServiceNameOffset(kUnknownOffset) , mInstanceNameOffset(kUnknownOffset) @@ -125,80 +145,60 @@ private: const char *GetDomainName(void) const { return mDomainName; } - uint16_t GetServiceNameOffset(const char *aServiceName) const + uint16_t GetServiceNameOffset(const Message &aMessage, const char *aServiceName) const { - uint16_t offset = mServiceNameOffset; - - if (offset != kUnknownOffset && strcmp(aServiceName, mServiceName) != 0) - { - offset = kUnknownOffset; - } - - return offset; + return MatchCompressedName(aMessage, mServiceNameOffset, aServiceName) + ? mServiceNameOffset + : static_cast(kUnknownOffset); }; - void SetServiceNameOffset(uint16_t aOffset, const char *aName) + void SetServiceNameOffset(uint16_t aOffset) { - if (mServiceName == nullptr) + if (mServiceNameOffset == kUnknownOffset) { - mServiceName = aName; mServiceNameOffset = aOffset; } } - const char *GetServiceName() const { return mServiceName; } - - uint16_t GetInstanceNameOffset(const char *aName) const + uint16_t GetInstanceNameOffset(const Message &aMessage, const char *aName) const { - uint16_t offset = mInstanceNameOffset; - - if (offset != kUnknownOffset && strcmp(aName, mInstanceName) != 0) - { - offset = kUnknownOffset; - } - - return offset; + return MatchCompressedName(aMessage, mInstanceNameOffset, aName) ? mInstanceNameOffset + : static_cast(kUnknownOffset); } - void SetInstanceNameOffset(uint16_t aOffset, const char *aName) + void SetInstanceNameOffset(uint16_t aOffset) { - if (mInstanceName == nullptr) + if (mInstanceNameOffset == kUnknownOffset) { - mInstanceName = aName; mInstanceNameOffset = aOffset; } } - uint16_t GetHostNameOffset(const char *aName) const + uint16_t GetHostNameOffset(const Message &aMessage, const char *aName) const { - uint16_t offset = mHostNameOffset; - - if (offset != kUnknownOffset && strcmp(aName, mHostName) != 0) - { - offset = kUnknownOffset; - } - - return offset; + return MatchCompressedName(aMessage, mHostNameOffset, aName) ? mHostNameOffset + : static_cast(kUnknownOffset); } - void SetHostNameOffset(uint16_t aOffset, const char *aName) + void SetHostNameOffset(uint16_t aOffset) { - if (mHostName == nullptr) + if (mHostNameOffset == kUnknownOffset) { - mHostName = aName; mHostNameOffset = aOffset; } } private: - const char *const mDomainName; // The serialized domain name. - const char * mServiceName; // The serialized service name (only support one service name). - const char * mInstanceName; // The serialized instance name or nullptr (only support one instance name). - const char * mHostName; // The serialized host name or nullptr (only support one host name). - uint16_t mDomainNameOffset; // Offset of domain name serialization into the response message. - uint16_t mServiceNameOffset; // Offset of service name serialization into the response message. - uint16_t mInstanceNameOffset; // Offset of instance name serialization into the response message. - uint16_t mHostNameOffset; // Offset of host name serialization into the response message. + static bool MatchCompressedName(const Message &aMessage, uint16_t aOffset, const char *aName) + { + return aOffset != kUnknownOffset && Name::CompareName(aMessage, aOffset, aName) == kErrorNone; + } + + const char *mDomainName; // The serialized domain name. + uint16_t mDomainNameOffset; // Offset of domain name serialization into the response message. + uint16_t mServiceNameOffset; // Offset of service name serialization into the response message. + uint16_t mInstanceNameOffset; // Offset of instance name serialization into the response message. + uint16_t mHostNameOffset; // Offset of host name serialization into the response message. }; // This structure represents the splitting information of a full name. @@ -232,67 +232,146 @@ private: // instance. }; - bool IsRunning(void) const { return mSocket.IsBound(); } - static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); - void HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); - void ProcessQuery(Message &aMessage, Message &aResponse, const Header &aRequestHeader); - Header::Response ResolveQuestion(const char * aName, - const Question & aQuestion, - Header & aResponseHeader, - Message & aResponseMessage, - uint8_t aResolveKind, - NameCompressInfo &aCompressInfo); - static Error AppendQuestion(const char * aName, - const Question & aQuestion, - Message & aMessage, - NameCompressInfo &aCompressInfo); - static Error AppendPtrRecord(Message & aMessage, - const char * aServiceName, - const char * aInstanceName, - uint32_t aTtl, - NameCompressInfo &aCompressInfo); - static Error AppendSrvRecord(Message & aMessage, - const char * aInstanceName, - const char * aHostName, - uint32_t aTtl, - uint16_t aPriority, - uint16_t aWeight, - uint16_t aPort, - NameCompressInfo &aCompressInfo); - static Error AppendAaaaRecord(Message & aMessage, - const char * aHostName, - const Ip6::Address &aAddress, - uint32_t aTtl, - NameCompressInfo & aCompressInfo); - static Error AppendServiceName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo); - static Error AppendInstanceName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo); - static Error AppendHostName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo); - static void IncResourceRecordCount(Header &aHeader, bool aAdditional); - static Error FindNameComponents(const char *aName, const char *aDomain, NameComponentsOffsetInfo &aInfo); - static Error FindPreviousLabel(const char *aName, uint8_t &aStart, uint8_t &aStop); + enum : uint32_t + { + kQueryTimeout = OPENTHREAD_CONFIG_DNSSD_QUERY_TIMEOUT, + }; + + class QueryTransaction + { + public: + explicit QueryTransaction(void) + : mResponseMessage(nullptr) + { + } + + void Init(const Header & aResponseHeader, + Message & aResponseMessage, + const NameCompressInfo &aCompressInfo, + const Ip6::MessageInfo &aMessageInfo); + bool IsValid(void) const { return mResponseMessage != nullptr; } + const Ip6::MessageInfo &GetMessageInfo(void) const { return mMessageInfo; } + Header & GetResponseHeader(void) { return mResponseHeader; } + const Header & GetResponseHeader(void) const { return mResponseHeader; } + Message & GetResponseMessage(void) { return *mResponseMessage; } + const Message & GetResponseMessage(void) const { return *mResponseMessage; } + TimeMilli GetStartTime(void) const { return mStartTime; } + NameCompressInfo & GetNameCompressInfo(void) { return mCompressInfo; }; + void Finalize(Header::Response aResponseMessage, Ip6::Udp::Socket &aSocket); + + private: + Header mResponseHeader; + Message * mResponseMessage; + NameCompressInfo mCompressInfo; + Ip6::MessageInfo mMessageInfo; + TimeMilli mStartTime; + }; + + enum DnsQueryType + { + kDnsQueryNone, + kDnsQueryBrowse, + kDnsQueryResolve, + kDnsQueryResolveHost, + }; + + bool IsRunning(void) const { return mSocket.IsBound(); } + static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); + void HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + void ProcessQuery(const Header &aRequestHeader, Message &aRequestMessage, const Ip6::MessageInfo &aMessageInfo); + static Header::Response AddQuestions(const Header & aRequestHeader, + const Message & aRequestMessage, + Header & aResponseHeader, + Message & aResponseMessage, + NameCompressInfo &aCompressInfo); + static Error AppendQuestion(const char * aName, + const Question & aQuestion, + Message & aMessage, + NameCompressInfo &aCompressInfo); + static Error AppendPtrRecord(Message & aMessage, + const char * aServiceName, + const char * aInstanceName, + uint32_t aTtl, + NameCompressInfo &aCompressInfo); + static Error AppendSrvRecord(Message & aMessage, + const char * aInstanceName, + const char * aHostName, + uint32_t aTtl, + uint16_t aPriority, + uint16_t aWeight, + uint16_t aPort, + NameCompressInfo &aCompressInfo); + static Error AppendTxtRecord(Message & aMessage, + const char * aInstanceName, + const void * aTxtData, + uint16_t aTxtLength, + uint32_t aTtl, + NameCompressInfo &aCompressInfo); + static Error AppendAaaaRecord(Message & aMessage, + const char * aHostName, + const Ip6::Address &aAddress, + uint32_t aTtl, + NameCompressInfo & aCompressInfo); + static Error AppendServiceName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo); + static Error AppendInstanceName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo); + static Error AppendHostName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo); + static void IncResourceRecordCount(Header &aHeader, bool aAdditional); + static Error FindNameComponents(const char *aName, const char *aDomain, NameComponentsOffsetInfo &aInfo); + static Error FindPreviousLabel(const char *aName, uint8_t &aStart, uint8_t &aStop); + static void SendResponse(Header aHeader, + Header::Response aResponseCode, + Message & aMessage, + const Ip6::MessageInfo &aMessageInfo, + Ip6::Udp::Socket & aSocket); #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + Header::Response ResolveBySrp(Header & aResponseHeader, + Message & aResponseMessage, + Server::NameCompressInfo &aCompressInfo); Header::Response ResolveQuestionBySrp(const char * aName, const Question & aQuestion, Header & aResponseHeader, Message & aResponseMessage, - uint8_t aResolveKind, - NameCompressInfo &aCompressInfo); + NameCompressInfo &aCompressInfo, + bool aAdditional); const Srp::Server::Host * GetNextSrpHost(const Srp::Server::Host *aHost); static const Srp::Server::Service *GetNextSrpService(const Srp::Server::Host & aHost, const Srp::Server::Service *aService); - static Error AppendTxtRecord(Message & aMessage, - const char * aInstanceName, - const Srp::Server::Service &aService, - uint32_t aTtl, - NameCompressInfo & aCompressInfo); #endif + Error ResolveByQueryCallbacks(Header & aResponseHeader, + Message & aResponseMessage, + NameCompressInfo & aCompressInfo, + const Ip6::MessageInfo &aMessageInfo); + QueryTransaction *NewQuery(const Header & aResponseHeader, + Message & aResponseMessage, + const NameCompressInfo &aCompressInfo, + const Ip6::MessageInfo &aMessageInfo); + static bool CanAnswerQuery(const QueryTransaction & aQuery, + const char * aServiceFullName, + const otDnssdServiceInstanceInfo &aInstanceInfo); + void AnswerQuery(QueryTransaction & aQuery, + const char * aServiceFullName, + const otDnssdServiceInstanceInfo &aInstanceInfo); + static bool CanAnswerQuery(const Server::QueryTransaction &aQuery, const char *aHostFullName); + void AnswerQuery(QueryTransaction &aQuery, const char *aHostFullName, const otDnssdHostInfo &aHostInfo); + void FinalizeQuery(QueryTransaction &aQuery, Header::Response aResponseCode); + static DnsQueryType GetQueryType(const Header &aHeader, const Message &aMessage, char (&aName)[Name::kMaxNameSize]); + static bool HasQuestion(const Header &aHeader, const Message &aMessage, const char *aName, uint16_t aQuestionType); + static void HandleTimer(Timer &aTimer); + void HandleTimer(void); + void ResetTimer(void); + static const char kDnssdProtocolUdp[4]; static const char kDnssdProtocolTcp[4]; static const char kDefaultDomainName[]; + Ip6::Udp::Socket mSocket; - Ip6::Udp::Socket mSocket; + QueryTransaction mQueryTransactions[kMaxConcurrentQueries]; + void * mQueryCallbackContext; + otDnssdQuerySubscribeCallback mQuerySubscribe; + otDnssdQueryUnsubscribeCallback mQueryUnsubscribe; + TimerMilli mTimer; }; } // namespace ServiceDiscovery diff --git a/tests/scripts/thread-cert/border_router/test_dnssd_server.py b/tests/scripts/thread-cert/border_router/test_dnssd_server.py index 80282f03e..3ea314887 100644 --- a/tests/scripts/thread-cert/border_router/test_dnssd_server.py +++ b/tests/scripts/thread-cert/border_router/test_dnssd_server.py @@ -109,6 +109,8 @@ class TestDnssdServerOnBr(thread_cert.TestCase): self.simulator.go(10) + server_addr = self.nodes[SERVER].get_ip6_address(config.ADDRESS_TYPE.OMR)[0] + # Router1 can ping to/from the Host on infra link. self.assertTrue(self.nodes[BR1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0], backbone=True)) @@ -129,7 +131,7 @@ class TestDnssdServerOnBr(thread_cert.TestCase): ins2_full_name = f'ins2.{SERVICE_FULL_NAME}' host1_full_name = f'host1.{DOMAIN}' host2_full_name = f'host2.{DOMAIN}' - server_addr = self.nodes[SERVER].get_ip6_address(config.ADDRESS_TYPE.OMR)[0] + EMPTY_TXT = {} # check if PTR query works dig_result = self.nodes[DIGGER].dns_dig(server_addr, SERVICE_FULL_NAME, 'PTR') @@ -141,11 +143,11 @@ class TestDnssdServerOnBr(thread_cert.TestCase): (SERVICE_FULL_NAME, 'IN', 'PTR', f'ins2.{SERVICE_FULL_NAME}')], 'ADDITIONAL': [ (ins1_full_name, 'IN', 'SRV', 1, 1, 11111, host1_full_name), - (ins1_full_name, 'IN', 'TXT', '""'), + (ins1_full_name, 'IN', 'TXT', EMPTY_TXT), (host1_full_name, 'IN', 'AAAA', client1_addrs[0]), (host1_full_name, 'IN', 'AAAA', client1_addrs[1]), (ins2_full_name, 'IN', 'SRV', 2, 2, 22222, host2_full_name), - (ins2_full_name, 'IN', 'TXT', '""'), + (ins2_full_name, 'IN', 'TXT', EMPTY_TXT), (host2_full_name, 'IN', 'AAAA', client2_addrs[0]), (host2_full_name, 'IN', 'AAAA', client2_addrs[1]), ], @@ -178,13 +180,13 @@ class TestDnssdServerOnBr(thread_cert.TestCase): dig_result = self.nodes[DIGGER].dns_dig(server_addr, ins1_full_name, 'TXT') self._assert_dig_result_matches(dig_result, { 'QUESTION': [(ins1_full_name, 'IN', 'TXT')], - 'ANSWER': [(ins1_full_name, 'IN', 'TXT', '""'),], + 'ANSWER': [(ins1_full_name, 'IN', 'TXT', EMPTY_TXT),], }) dig_result = self.nodes[DIGGER].dns_dig(server_addr, ins2_full_name, 'TXT') self._assert_dig_result_matches(dig_result, { 'QUESTION': [(ins2_full_name, 'IN', 'TXT')], - 'ANSWER': [(ins2_full_name, 'IN', 'TXT', '""'),], + 'ANSWER': [(ins2_full_name, 'IN', 'TXT', EMPTY_TXT),], }) # check if AAAA query works @@ -221,6 +223,63 @@ class TestDnssdServerOnBr(thread_cert.TestCase): 'status': 'NXDOMAIN', }) + # verify Discovery Proxy works for _meshcop._udp + self._verify_discovery_proxy_meshcop(server_addr) + + def _verify_discovery_proxy_meshcop(self, server_addr): + dp_service_name = '_meshcop._udp.default.service.arpa.' + network_name = self.nodes[SERVER].get_network_name() + dp_instance_name = f'{network_name}._meshcop._udp.default.service.arpa.' + dp_hostname = lambda x: x.endswith('.default.service.arpa.') + + dig_result = self.nodes[DIGGER].dns_dig(server_addr, dp_service_name, 'PTR') + self._assert_dig_result_matches( + dig_result, { + 'QUESTION': [(dp_service_name, 'IN', 'PTR'),], + 'ANSWER': [(dp_service_name, 'IN', 'PTR', dp_instance_name),], + 'ADDITIONAL': [ + (dp_instance_name, 'IN', 'SRV', 0, 0, config.BORDER_AGENT_UDP_PORT, dp_hostname), + (dp_instance_name, 'IN', 'TXT', lambda txt: (isinstance(txt, dict) and txt.get( + 'nn') == network_name and 'xp' in txt and 'tv' in txt and 'dd' in txt)), + ], + }) + + # Find the actual host name and IPv6 address + dp_ip6_address = None + for rr in dig_result['ADDITIONAL']: + if rr[3] == 'SRV': + dp_hostname = rr[7] + elif rr[3] == 'AAAA': + dp_ip6_address = rr[4] + + assert isinstance(dp_hostname, str), dig_result + + dig_result = self.nodes[DIGGER].dns_dig(server_addr, dp_instance_name, 'SRV') + self._assert_dig_result_matches( + dig_result, { + 'QUESTION': [(dp_instance_name, 'IN', 'SRV'),], + 'ANSWER': [(dp_instance_name, 'IN', 'SRV', 0, 0, config.BORDER_AGENT_UDP_PORT, dp_hostname),], + 'ADDITIONAL': [(dp_instance_name, 'IN', 'TXT', lambda txt: (isinstance(txt, dict) and txt.get( + 'nn') == network_name and 'xp' in txt and 'tv' in txt and 'dd' in txt)),], + }) + + dig_result = self.nodes[DIGGER].dns_dig(server_addr, dp_instance_name, 'TXT') + self._assert_dig_result_matches( + dig_result, { + 'QUESTION': [(dp_instance_name, 'IN', 'TXT'),], + 'ANSWER': [(dp_instance_name, 'IN', 'TXT', lambda txt: (isinstance(txt, dict) and txt.get( + 'nn') == network_name and 'xp' in txt and 'tv' in txt and 'dd' in txt)),], + 'ADDITIONAL': [(dp_instance_name, 'IN', 'SRV', 0, 0, config.BORDER_AGENT_UDP_PORT, dp_hostname),], + }) + + if dp_ip6_address is not None: + dig_result = self.nodes[DIGGER].dns_dig(server_addr, dp_hostname, 'AAAA') + + self._assert_dig_result_matches(dig_result, { + 'QUESTION': [(dp_hostname, 'IN', 'AAAA'),], + 'ANSWER': [(dp_hostname, 'IN', 'AAAA', dp_ip6_address),], + }) + def _config_srp_client_services(self, client, instancename, hostname, port, priority, weight, addrs): self.nodes[client].netdata_show() srp_server_port = self.nodes[client].get_srp_server_port() @@ -234,7 +293,11 @@ class TestDnssdServerOnBr(thread_cert.TestCase): self.assertEqual(self.nodes[client].srp_client_get_host_state(), 'Registered') def _assert_have_question(self, dig_result, question): - self.assertIn(question, dig_result['QUESTION'], (question, dig_result)) + for dig_question in dig_result['QUESTION']: + if self._match_record(dig_question, question): + return + + self.fail((dig_result, question)) def _assert_have_answer(self, dig_result, record, additional=False): for dig_answer in dig_result['ANSWER' if not additional else 'ADDITIONAL']: @@ -250,11 +313,22 @@ class TestDnssdServerOnBr(thread_cert.TestCase): if record[2] == 'AAAA': record[3] = ipaddress.IPv6Address(record[3]) - if dig_answer == record: + if self._match_record(dig_answer, record): return + print('not match: ', dig_answer, record, + list(a == b or (callable(b) and b(a)) for a, b in zip(dig_answer, record))) + self.fail((record, dig_result)) + def _match_record(self, record, match): + assert not any(callable(elem) for elem in record), record + + if record == match: + return True + + return all(a == b or (callable(b) and b(a)) for a, b in zip(record, match)) + def _assert_dig_result_matches(self, dig_result, expected_result): self.assertEqual(dig_result['opcode'], expected_result.get('opcode', 'QUERY'), dig_result) self.assertEqual(dig_result['status'], expected_result.get('status', 'NOERROR'), dig_result) @@ -272,7 +346,7 @@ class TestDnssdServerOnBr(thread_cert.TestCase): self._assert_have_answer(dig_result, record, additional=False) if 'ADDITIONAL' in expected_result: - self.assertEqual(len(dig_result['ADDITIONAL']), len(expected_result['ADDITIONAL']), dig_result) + self.assertGreaterEqual(len(dig_result['ADDITIONAL']), len(expected_result['ADDITIONAL']), dig_result) for record in expected_result['ADDITIONAL']: self._assert_have_answer(dig_result, record, additional=True) diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index 3b50c1598..718f97d71 100644 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -144,6 +144,8 @@ LEADER_NOTIFY_SED_BY_CHILD_UPDATE_REQUEST = True THREAD_VERSION_1_1 = 2 THREAD_VERSION_1_2 = 3 +BORDER_AGENT_UDP_PORT = 49191 + def create_default_network_data_prefix_sub_tlvs_factories(): return { diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index f60b4da4c..012ddbb56 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -293,11 +293,28 @@ class OtbrDocker: record[1] = int(record[1]) if record[3] == 'SRV': record[4], record[5], record[6] = map(int, [record[4], record[5], record[6]]) + elif record[3] == 'TXT': + record[4:] = [self.__parse_dns_dig_txt(record[4:])] dig_result[section].append(tuple(record)) return dig_result + def __parse_dns_dig_txt(self, txt_entries): + # Example txt_entries: + # ['"nn=OpenThread"', '"xp=\\000\\013\\184\\000\\000\\000\\000\\000"', '"tv=1.2.0"', '"dd=\\022n\\010\\000\\000\\000\\000\\001"'] + txt = {} + for entry in txt_entries: + assert entry.startswith('"') and entry.endswith('"') + entry = entry[1:-1] + if entry == "": + continue + + k, v = entry.split('=') + txt[k] = v + + return txt + def _setup_sysctl(self): self.bash(f'sysctl net.ipv6.conf.{self.ETH_DEV}.accept_ra=2') self.bash(f'sysctl net.ipv6.conf.{self.ETH_DEV}.accept_ra_rt_info_max_plen=64')