From 7084422a0e769b276088d35ae8f2c50586950145 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 12 Jan 2021 10:55:12 -0800 Subject: [PATCH] [dns-client] service discovery (DNS-SD) support and enhancements (#6116) This commit re-designs the `Dns::Client` module enhancing the address resolution implementation and also adding support for DNS-Based Service Discovery (DNS-SD). With regards to address resolution query the new model relaxes the requirements of the public OT APIs such that caller does not need to persist the query info (e.g., the host name string buffer can be a temporary variable and does not need to persist during the query) and it is all managed by the `Dns::Client` core implementation itself. The new model also supports the case where the response contains multiple IPv6 addresses providing new APIs to allow the user to iterate through the list of addresses and retrieve them one by one. The implementation also handles the case where the DNS query response contains CNAME record mapping the queried host name to a canonical name for which a list of addresses are then provided. The core implementation is also simplified, instead of cloning a query message and saving it for possible retx, the new code saves the query related info from which it can re-construct the query message for retx if/when needed. This commit also adds support for DNS-SD in `Dns::Client`. The config `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` can be used to disable service discovery feature. The implementation supports "service instance enumeration" which is referred to as "browsing" and "service instance resolution". Callbacks are used to notify the user when a response is received. In the callback a pointer to an opaque response object is given to the user which can then be used with the new set of APIs to get more info about the response, such as the list of discovered service instances or more details about a specific service instance (e.g., port number, host name and its address) or iterate through address lists. This provides a flexible and scalable solution to handle larger lists in the response without adding memory overhead in the implementation. --- include/Makefile.am | 2 +- include/openthread/BUILD.gn | 2 +- include/openthread/dns.h | 60 +- include/openthread/dns_client.h | 470 ++++++++++ include/openthread/instance.h | 2 +- src/cli/cli.cpp | 72 +- src/cli/cli.hpp | 17 +- src/core/api/dns_api.cpp | 137 ++- src/core/config/dns_client.h | 10 + src/core/net/dns_client.cpp | 924 ++++++++++++++----- src/core/net/dns_client.hpp | 527 +++++++++-- src/core/net/dns_headers.cpp | 10 +- src/core/net/dns_headers.hpp | 122 ++- src/core/net/dnssd_server.cpp | 14 +- src/core/net/srp_server.cpp | 16 +- src/core/thread/thread_netif.cpp | 2 +- tests/toranj/openthread-core-toranj-config.h | 8 + tests/unit/test_dns.cpp | 17 +- 18 files changed, 1892 insertions(+), 520 deletions(-) create mode 100644 include/openthread/dns_client.h diff --git a/include/Makefile.am b/include/Makefile.am index b4cca2284..e46a6028e 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -51,7 +51,7 @@ openthread_headers = \ openthread/dataset_ftd.h \ openthread/dataset_updater.h \ openthread/diag.h \ - openthread/dns.h \ + openthread/dns_client.h \ openthread/entropy.h \ openthread/error.h \ openthread/heap.h \ diff --git a/include/openthread/BUILD.gn b/include/openthread/BUILD.gn index 15457a0a2..9b74202dd 100644 --- a/include/openthread/BUILD.gn +++ b/include/openthread/BUILD.gn @@ -72,7 +72,7 @@ source_set("openthread") { "dataset_ftd.h", "dataset_updater.h", "diag.h", - "dns.h", + "dns_client.h", "entropy.h", "error.h", "heap.h", diff --git a/include/openthread/dns.h b/include/openthread/dns.h index f4d44ce1f..48059f284 100644 --- a/include/openthread/dns.h +++ b/include/openthread/dns.h @@ -29,14 +29,13 @@ /** * @file * @brief - * This file defines the top-level dns functions for the OpenThread library. + * This file defines the top-level DNS functions for the OpenThread library. */ #ifndef OPENTHREAD_DNS_H_ #define OPENTHREAD_DNS_H_ -#include -#include +#include #ifdef __cplusplus extern "C" { @@ -52,10 +51,9 @@ extern "C" { * */ -#define OT_DNS_MAX_HOSTNAME_LENGTH 62 ///< Maximum allowed hostname length (maximum label size - 1 for compression). +#define OT_DNS_MAX_NAME_SIZE 255 ///< Maximum name string size (includes null char at the end of string). -#define OT_DNS_DEFAULT_SERVER_IP "2001:4860:4860::8888" ///< Defines default DNS Server address - Google DNS. -#define OT_DNS_DEFAULT_SERVER_PORT 53 ///< Defines default DNS Server port. +#define OT_DNS_MAX_LABEL_SIZE 64 ///< Maximum label string size (include null char at the end of string) /** * Initializer for otDnsTxtIterator. @@ -96,56 +94,6 @@ typedef struct otDnsTxtEntry uint8_t mKeyLength; ///< Number of bytes in `mKey` buffer. MUST be set even if `mKey` is a null-terminated string. } otDnsTxtEntry; -/** - * This structure implements DNS Query parameters. - * - */ -typedef struct otDnsQuery -{ - const char * mHostname; ///< Identifies hostname to be found. It shall not change during resolving. - const otMessageInfo *mMessageInfo; ///< A reference to the message info related with DNS Server. - bool mNoRecursion; ///< If cleared, it directs name server to pursue the query recursively. -} otDnsQuery; - -/** - * This function pointer is called when a DNS response is received. - * - * @param[in] aContext A pointer to application-specific context. - * @param[in] aHostname Identifies hostname related with DNS response. - * @param[in] aAddress A pointer to the IPv6 address received in DNS response. May be null. - * @param[in] aTtl Specifies the maximum time in seconds that the resource record may be cached. - * @param[in] aResult A result of the DNS transaction. - * - * @retval OT_ERROR_NONE A response was received successfully and IPv6 address is provided - * in @p aAddress. - * @retval OT_ERROR_ABORT A DNS transaction was aborted by stack. - * @retval OT_ERROR_RESPONSE_TIMEOUT No DNS response has been received within timeout. - * @retval OT_ERROR_NOT_FOUND A response was received but no IPv6 address has been found. - * @retval OT_ERROR_FAILED A response was received but status code is different than success. - * - */ -typedef void (*otDnsResponseHandler)(void * aContext, - const char * aHostname, - const otIp6Address *aAddress, - uint32_t aTtl, - otError aResult); - -/** - * This function sends a DNS query for AAAA (IPv6) record. - * - * This function is available only if feature `OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE` is enabled. - * - * @param[in] aInstance A pointer to an OpenThread instance. - * @param[in] aQuery A pointer to specify DNS query parameters. - * @param[in] aHandler A function pointer that shall be called on response reception or time-out. - * @param[in] aContext A pointer to arbitrary context information. - * - */ -otError otDnsClientQuery(otInstance * aInstance, - const otDnsQuery * aQuery, - otDnsResponseHandler aHandler, - void * aContext); - /** * @} * diff --git a/include/openthread/dns_client.h b/include/openthread/dns_client.h new file mode 100644 index 000000000..bab6ef22f --- /dev/null +++ b/include/openthread/dns_client.h @@ -0,0 +1,470 @@ +/* + * Copyright (c) 2017-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 top-level DNS functions for the OpenThread library. + */ + +#ifndef OPENTHREAD_DNS_CLIENT_H_ +#define OPENTHREAD_DNS_CLIENT_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup api-dns + * + * @brief + * This module includes functions that control DNS communication. + * + * The functions in this module are available only if feature `OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE` is enabled. + * + * @{ + * + */ + +#define OT_DNS_DEFAULT_SERVER_PORT 53 ///< The default DNS Server port. + +#define OT_DNS_DEFAULT_SERVER_IP "2001:4860:4860::8888" ///< Defines default DNS Server address - Google DNS. + +/** + * This type is an opaque representation of a response to an address resolution DNS query. + * + * Pointers to instance of this type are provided from callback `otDnsAddressCallback`. + * + */ +typedef struct otDnsAddressResponse otDnsAddressResponse; + +/** + * This function pointer is called when a DNS response is received for an address resolution query. + * + * Within this callback the user can use `otDnsAddressResponseGet{Item}()` functions along with the @p aResponse + * pointer to get more info about the response. + * + * The @p aResponse pointer can only be used within this callback and after returning from this function it will not + * stay valid, so the user MUST NOT retain the @p aResponse pointer for later use. + * + * @param[in] aError The result of the DNS transaction. + * @param[in] aResponse A pointer to the response (it is always non-NULL). + * @param[in] aContext A pointer to application-specific context. + * + * The @p aError can have the following: + * + * - OT_ERROR_NONE A response was received successfully. + * - OT_ERROR_ABORT A DNS transaction was aborted by stack. + * - OT_ERROR_RESPONSE_TIMEOUT No DNS response has been received within timeout. + * + * If the server rejects the address resolution request the error code from server is mapped as follow: + * + * (0) NOERROR Success (no error condition) -> OT_ERROR_NONE + * (1) FORMERR Server unable to interpret due to format error -> OT_ERROR_PARSE + * (2) SERVFAIL Server encountered an internal failure -> OT_ERROR_FAILED + * (3) NXDOMAIN Name that ought to exist, does not exist -> OT_ERROR_NOT_FOUND + * (4) NOTIMP Server does not support the query type (OpCode) -> OT_ERROR_NOT_IMPLEMENTED + * (5) REFUSED Server refused for policy/security reasons -> OT_ERROR_SECURITY + * (6) YXDOMAIN Some name that ought not to exist, does exist -> OT_ERROR_DUPLICATED + * (7) YXRRSET Some RRset that ought not to exist, does exist -> OT_ERROR_DUPLICATED + * (8) NXRRSET Some RRset that ought to exist, does not exist -> OT_ERROR_NOT_FOUND + * (9) NOTAUTH Service is not authoritative for zone -> OT_ERROR_SECURITY + * (10) NOTZONE A name is not in the zone -> OT_ERROR_PARSE + * (20) BADNAME Bad name -> OT_ERROR_PARSE + * (21) BADALG Bad algorithm -> OT_ERROR_SECURITY + * (22) BADTRUN Bad truncation -> OT_ERROR_PARSE + * Other response codes -> OT_ERROR_FAILED + * + */ +typedef void (*otDnsAddressCallback)(otError aError, const otDnsAddressResponse *aResponse, void *aContext); + +/** + * This function sends an address resolution DNS query for AAAA (IPv6) record(s) for a given host name. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aServerSockAddr A pointer to the server socket address. + * @param[in] aHostName The host name for which to query the address (MUST NOT be NULL). + * @param[in] aNoRecursion Indicates whether name server can resolve the query recursively or not. + * @param[in] aCallback A function pointer that shall be called on response reception or time-out. + * @param[in] aContext A pointer to arbitrary context information. + * + * @retval OT_ERROR_NONE Query sent successfully. @p aCallback will be invoked to report the status. + * @retval OT_ERROR_NO_BUFS Insufficient buffer to prepare and send query. + * + */ +otError otDnsClientResolveAddress(otInstance * aInstance, + const otSockAddr * aServerSockAddr, + const char * aHostName, + bool aNoRecursion, + otDnsAddressCallback aCallback, + void * aContext); + +/** + * This function gets the full host name associated with an address resolution DNS response. + * + * This function MUST only be used from `otDnsAddressCallback`. + * + * @param[in] aResponse A pointer to the response. + * @param[out] aNameBuffer A buffer to char array to output the full host name (MUST NOT be NULL). + * @param[in] aNameBufferSize The size of @p aNameBuffer. + * + * @retval OT_ERROR_NONE The full host name was read successfully. + * @retval OT_ERROR_NO_BUFS The name does not fit in @p aNameBuffer. + * + */ +otError otDnsAddressResponseGetHostName(const otDnsAddressResponse *aResponse, + char * aNameBuffer, + uint16_t aNameBufferSize); + +/** + * This function gets an IPv6 address associated with an address resolution DNS response. + * + * This function MUST only be used from `otDnsAddressCallback`. + * + * The response may include multiple IPv6 address records. @p aIndex can be used to iterate through the list of + * addresses. Index zero gets the first address and so on. When we reach end of the list, `OT_ERROR_NOT_FOUND` is + * returned. + * + * @param[in] aResponse A pointer to the response. + * @param[in] aIndex The address record index to retrieve. + * @param[out] aAddress A pointer to a IPv6 address to output the address (MUST NOT be NULL). + * @param[out] aTtl A pointer to an `uint32_t` to output TTL for the address. It can be NULL if caller does not + * want to get the TTL. + * + * @retval OT_ERROR_NONE The address was read successfully. + * @retval OT_ERROR_NOT_FOUND No address record in @p aResponse at @p aIndex. + * @retval OT_ERROR_PARSE Could not parse the records in the @p aResponse. + * + */ +otError otDnsAddressResponseGetAddress(const otDnsAddressResponse *aResponse, + uint16_t aIndex, + otIp6Address * aAddress, + uint32_t * aTtl); + +/** + * This type is an opaque representation of a response to a browse (service instance enumeration) DNS query. + * + * Pointers to instance of this type are provided from callback `otDnsBrowseCallback`. + * + */ +typedef struct otDnsBrowseResponse otDnsBrowseResponse; + +/** + * This function pointer is called when a DNS response is received for a browse (service instance enumeration) query. + * + * Within this callback the user can use `otDnsBrowseResponseGet{Item}()` functions along with the @p aResponse + * pointer to get more info about the response. + * + * The @p aResponse pointer can only be used within this callback and after returning from this function it will not + * stay valid, so the user MUST NOT retain the @p aResponse pointer for later use. + * + * @param[in] aError The result of the DNS transaction. + * @param[in] aResponse A pointer to the response (it is always non-NULL). + * @param[in] aContext A pointer to application-specific context. + * + * For the full list of possible values for @p aError, please see `otDnsAddressCallback()`. + * + */ +typedef void (*otDnsBrowseCallback)(otError aError, const otDnsBrowseResponse *aResponse, void *aContext); + +/** + * This structure provides info for a DNS service instance. + * + */ +typedef struct otDnsServiceInfo +{ + uint32_t mTtl; ///< Service record TTL (in seconds). + uint16_t mPort; ///< Service port number. + uint16_t mPriority; ///< Service priority. + uint16_t mWeight; ///< Service weight. + char * mHostNameBuffer; ///< Buffer to output the service host name (can be NULL if not needed). + uint16_t mHostNameBufferSize; ///< Size of `mHostNameBuffer`. + otIp6Address mHostAddress; ///< The host IPv6 address. Set to all zero if not available. + uint32_t mHostAddressTtl; ///< The host address TTL. + uint8_t * mTxtData; ///< Buffer to output TXT data (can be NULL if not needed). + uint16_t mTxtDataSize; ///< On input, size of `mTxtData` buffer. On output `mTxtData` length. + uint32_t mTxtDataTtl; ///< The TXT data TTL. +} otDnsServiceInfo; + +/** + * This function sends a DNS browse (service instance enumeration) query for a given service name. + * + * This function is available when `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aServerSockAddr A pointer to the server socket address. + * @param[in] aServiceName The service name to query for (MUST NOT be NULL). + * @param[in] aCallback A function pointer that shall be called on response reception or time-out. + * @param[in] aContext A pointer to arbitrary context information. + * + * @retval OT_ERROR_NONE Query sent successfully. @p aCallback will be invoked to report the status. + * @retval OT_ERROR_NO_BUFS Insufficient buffer to prepare and send query. + * + */ +otError otDnsClientBrowse(otInstance * aInstance, + const otSockAddr * aServerSockAddr, + const char * aServiceName, + otDnsBrowseCallback aCallback, + void * aContext); + +/** + * This function gets the service name associated with a DNS browse (service instance enumeration) response. + * + * This function MUST only be used from `otDnsBrowseCallback`. + * + * @param[in] aResponse A pointer to the response. + * @param[out] aNameBuffer A buffer to char array to output the service name (MUST NOT be NULL). + * @param[in] aNameBufferSize The size of @p aNameBuffer. + * + * @retval OT_ERROR_NONE The service name was read successfully. + * @retval OT_ERROR_NO_BUFS The name does not fit in @p aNameBuffer. + * + */ +otError otDnsBrowseResponseGetServiceName(const otDnsBrowseResponse *aResponse, + char * aNameBuffer, + uint16_t aNameBufferSize); + +/** + * This function gets a service instance associated with a DNS browse (service instance enumeration) response. + * + * This function MUST only be used from `otDnsBrowseCallback`. + * + * The response may include multiple service instance records. @p aIndex can be used to iterate through the list. Index + * zero gives the the first record. When we reach end of the list, `OT_ERROR_NOT_FOUND` is returned. + * + * Note that this function gets the service instance label and not the full service instance name which is of the form + * `..`. + * + * @param[in] aResponse A pointer to the response. + * @param[in] aIndex The service instance record index to retrieve. + * @param[out] aLabelBuffer A buffer to char array to output the service instance label (MUST NOT be NULL). + * @param[in] aLabelBufferSize The size of @p aLabelBuffer. + * + * @retval OT_ERROR_NONE The service instance was read successfully. + * @retval OT_ERROR_NO_BUFS The name does not fit in @p aNameBuffer. + * @retval OT_ERROR_NOT_FOUND No service instance record in @p aResponse at @p aIndex. + * @retval OT_ERROR_PARSE Could not parse the records in the @p aResponse. + * + */ +otError otDnsBrowseResponseGetServiceInstance(const otDnsBrowseResponse *aResponse, + uint16_t aIndex, + char * aLabelBuffer, + uint8_t aLabelBufferSize); + +/** + * This function gets info for a service instance from a DNS browse (service instance enumeration) response. + * + * This function MUST only be used from `otDnsBrowseCallback`. + * + * A browse DNS response should include the SRV, TXT, and AAAA records for the service instances that are enumerated + * (note that it is a SHOULD and not a MUST requirement). This function tries to retrieve this info for a given service + * instance when available. + * + * If no matching SRV record is found in @p aResponse, `OT_ERROR_NOT_FOUND` is returned. + * If a matching SRV record is found in @p aResponse, @p aServiceInfo is updated and `OT_ERROR_NONE` is returned. + * If no matching TXT record is found in @p aResponse, `mTxtDataSize` in @p aServiceInfo is set to zero. + * If no matching AAAA record is found in @p aResponse, `mHostAddress is set to all zero or unspecified address. + * If there are multiple AAAA records for the host name in @p aResponse, `mHostAddress` is set to the first one. The + * other addresses can be retrieved using `otDnsBrowseResponseGetHostAddress()`. + * + * @param[in] aResponse A pointer to the response. + * @param[in] aInstanceLabel The service instance label (MUST NOT be NULL). + * @param[out] aServiceInfo A `ServiceInfo` to output the service instance information (MUST NOT be NULL). + * + * @retval OT_ERROR_NONE The service instance info was read. @p aServiceInfo is updated. + * @retval OT_ERROR_NOT_FOUND Could not find a matching SRV record for @p aInstanceLabel. + * @retval OT_ERROR_NO_BUFS The host name and/or TXT data could not fit in the given buffers. + * @retval OT_ERROR_PARSE Could not parse the records in the @p aResponse. + * + */ +otError otDnsBrowseResponseGetServiceInfo(const otDnsBrowseResponse *aResponse, + const char * aInstanceLabel, + otDnsServiceInfo * aServiceInfo); + +/** + * This function gets the host IPv6 address from a DNS browse (service instance enumeration) response. + * + * This function MUST only be used from `otDnsBrowseCallback`. + * + * The response can include zero or more IPv6 address records. @p aIndex can be used to iterate through the list of + * addresses. Index zero gets the first address and so on. When we reach end of the list, `OT_ERROR_NOT_FOUND` is + * returned. + * + * @param[in] aResponse A pointer to the response. + * @param[in] aHostName The host name to get the address (MUST NOT be NULL). + * @param[in] aIndex The address record index to retrieve. + * @param[out] aAddress A pointer to a IPv6 address to output the address (MUST NOT be NULL). + * @param[out] aTtl A pointer to an `uint32_t` to output TTL for the address. It can be NULL if caller does + * not want to get the TTL. + * + * @retval OT_ERROR_NONE The address was read successfully. + * @retval OT_ERROR_NOT_FOUND No address record for @p aHostname in @p aResponse at @p aIndex. + * @retval OT_ERROR_PARSE Could not parse the records in the @p aResponse. + * + */ +otError otDnsBrowseResponseGetHostAddress(const otDnsBrowseResponse *aResponse, + const char * aHostName, + uint16_t aIndex, + otIp6Address * aAddress, + uint32_t * aTtl); + +/** + * This type is an opaque representation of a response to a service instance resolution DNS query. + * + * Pointers to instance of this type are provided from callback `otDnsAddressCallback`. + * + */ +typedef struct otDnsServiceResponse otDnsServiceResponse; + +/** + * This function pointer is called when a DNS response is received for a service instance resolution query. + * + * Within this callback the user can use `otDnsServiceResponseGet{Item}()` functions along with the @p aResponse + * pointer to get more info about the response. + * + * The @p aResponse pointer can only be used within this callback and after returning from this function it will not + * stay valid, so the user MUST NOT retain the @p aResponse pointer for later use. + * + * @param[in] aError The result of the DNS transaction. + * @param[in] aResponse A pointer to the response (it is always non-NULL). + * @param[in] aContext A pointer to application-specific context. + * + * For the full list of possible values for @p aError, please see `otDnsAddressCallback()`. + * + */ +typedef void (*otDnsServiceCallback)(otError aError, const otDnsServiceResponse *aResponse, void *aContext); + +/** + * This function sends a DNS service instance resolution query for a given service instance. + * + * This function is available when `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aServerSockAddr A pointer to the server socket address. + * @param[in] aInstanceLabel The service instance label. + * @param[in] aServiceName The service name (together with @p aInstanceLabel form full instance name). + * @param[in] aCallback A function pointer that shall be called on response reception or time-out. + * @param[in] aContext A pointer to arbitrary context information. + * + * @retval OT_ERROR_NONE Query sent successfully. @p aCallback will be invoked to report the status. + * @retval OT_ERROR_NO_BUFS Insufficient buffer to prepare and send query. + * + */ +otError otDnsClientResolveService(otInstance * aInstance, + const otSockAddr * aServerSockAddr, + const char * aInstanceLabel, + const char * aServiceName, + otDnsServiceCallback aCallback, + void * aContext); + +/** + * This function gets the service instance name associated with a DNS service instance resolution response. + * + * This function MUST only be used from `otDnsServiceCallback`. + * + * @param[in] aResponse A pointer to the response. + * @param[out] aLabelBuffer A buffer to char array to output the service instance label (MUST NOT be NULL). + * @param[in] aLabelBufferSize The size of @p aLabelBuffer. + * @param[out] aNameBuffer A buffer to char array to output the rest of service name (can be NULL if user is + * not interested in getting the name. + * @param[in] aNameBufferSize The size of @p aNameBuffer. + * + * @retval OT_ERROR_NONE The service name was read successfully. + * @retval OT_ERROR_NO_BUFS Either the label or name does not fit in the given buffers. + * + */ +otError otDnsServiceResponseGetServiceName(const otDnsServiceResponse *aResponse, + char * aLabelBuffer, + uint8_t aLabelBufferSize, + char * aNameBuffer, + uint16_t aNameBufferSize); + +/** + * This function gets info for a service instance from a DNS service instance resolution response. + * + * This function MUST only be used from `otDnsServiceCallback`. + * + * If no matching SRV record is found in @p aResponse, `OT_ERROR_NOT_FOUND` is returned. + * If a matching SRV record is found in @p aResponse, @p aServiceInfo is updated and `OT_ERROR_NONE` is returned. + * If no matching TXT record is found in @p aResponse, `mTxtDataSize` in @p aServiceInfo is set to zero. + * If no matching AAAA record is found in @p aResponse, `mHostAddress is set to all zero or unspecified address. + * If there are multiple AAAA records for the host name in @p aResponse, `mHostAddress` is set to the first one. The + * other addresses can be retrieved using `otDnsServiceResponseGetHostAddress()`. + * + * @param[in] aResponse A pointer to the response. + * @param[out] aServiceInfo A `ServiceInfo` to output the service instance information (MUST NOT be NULL). + * + * @retval OT_ERROR_NONE The service instance info was read. @p aServiceInfo is updated. + * @retval OT_ERROR_NOT_FOUND Could not find a matching SRV record in @p aResponse. + * @retval OT_ERROR_NO_BUFS The host name and/or TXT data could not fit in the given buffers. + * @retval OT_ERROR_PARSE Could not parse the records in the @p aResponse. + * + */ +otError otDnsServiceResponseGetServiceInfo(const otDnsServiceResponse *aResponse, otDnsServiceInfo *aServiceInfo); + +/** + * This function gets the host IPv6 address from a DNS service instance resolution response. + * + * This function MUST only be used from `otDnsServiceCallback`. + * + * The response can include zero or more IPv6 address records. @p aIndex can be used to iterate through the list of + * addresses. Index zero gets the first address and so on. When we reach end of the list, `OT_ERROR_NOT_FOUND` is + * returned. + * + * @param[in] aResponse A pointer to the response. + * @param[in] aHostName The host name to get the address (MUST NOT be NULL). + * @param[in] aIndex The address record index to retrieve. + * @param[out] aAddress A pointer to a IPv6 address to output the address (MUST NOT be NULL). + * @param[out] aTtl A pointer to an `uint32_t` to output TTL for the address. It can be NULL if caller does + * not want to get the TTL. + * + * @retval OT_ERROR_NONE The address was read successfully. + * @retval OT_ERROR_NOT_FOUND No address record for @p aHostname in @p aResponse at @p aIndex. + * @retval OT_ERROR_PARSE Could not parse the records in the @p aResponse. + * + */ +otError otDnsServiceResponseGetHostAddress(const otDnsServiceResponse *aResponse, + const char * aHostName, + uint16_t aIndex, + otIp6Address * aAddress, + uint32_t * aTtl); + +/** + * @} + * + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // OPENTHREAD_DNS_CLIENT_H_ diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 122f60985..d7143ee25 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 (69) +#define OPENTHREAD_API_VERSION (70) /** * @addtogroup api-instance diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 21d876999..6be6466a8 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -121,9 +121,6 @@ Interpreter::Interpreter(Instance *aInstance) , mPingAllowZeroHopLimit(false) , mPingIdentifier(0) , mPingTimer(*aInstance, Interpreter::HandlePingTimer) -#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE - , mResolvingInProgress(false) -#endif #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE , mSntpQueryingInProgress(false) #endif @@ -159,10 +156,6 @@ Interpreter::Interpreter(Instance *aInstance) mIcmpHandler.mReceiveCallback = Interpreter::HandleIcmpReceive; mIcmpHandler.mContext = this; IgnoreError(otIcmp6RegisterHandler(mInstance, &mIcmpHandler)); - -#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE - memset(mResolvingHostname, 0, sizeof(mResolvingHostname)); -#endif } void Interpreter::OutputResult(otError aError) @@ -1344,45 +1337,34 @@ exit: #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE otError Interpreter::ProcessDns(uint8_t aArgsLength, char *aArgs[]) { - otError error = OT_ERROR_NONE; - uint16_t port = OT_DNS_DEFAULT_SERVER_PORT; - Ip6::MessageInfo messageInfo; - otDnsQuery query; + otError error = OT_ERROR_NONE; + otSockAddr serverSockAddr; + + serverSockAddr.mPort = OT_DNS_DEFAULT_SERVER_PORT; VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS); if (strcmp(aArgs[0], "resolve") == 0) { - VerifyOrExit(!mResolvingInProgress, error = OT_ERROR_BUSY); VerifyOrExit(aArgsLength > 1, error = OT_ERROR_INVALID_ARGS); - VerifyOrExit(strlen(aArgs[1]) < OT_DNS_MAX_HOSTNAME_LENGTH, error = OT_ERROR_INVALID_ARGS); - - strcpy(mResolvingHostname, aArgs[1]); if (aArgsLength > 2) { - SuccessOrExit(error = ParseAsIp6Address(aArgs[2], messageInfo.GetPeerAddr())); + SuccessOrExit(error = ParseAsIp6Address(aArgs[2], serverSockAddr.mAddress)); } else { // Use IPv6 address of default DNS server. - SuccessOrExit(error = messageInfo.GetPeerAddr().FromString(OT_DNS_DEFAULT_SERVER_IP)); + SuccessOrExit(error = otIp6AddressFromString(OT_DNS_DEFAULT_SERVER_IP, &serverSockAddr.mAddress)); } if (aArgsLength > 3) { - SuccessOrExit(error = ParseAsUint16(aArgs[3], port)); + SuccessOrExit(error = ParseAsUint16(aArgs[3], serverSockAddr.mPort)); } - messageInfo.SetPeerPort(port); - - query.mHostname = mResolvingHostname; - query.mMessageInfo = static_cast(&messageInfo); - query.mNoRecursion = false; - - SuccessOrExit(error = otDnsClientQuery(mInstance, &query, &Interpreter::HandleDnsResponse, this)); - - mResolvingInProgress = true; + SuccessOrExit(error = otDnsClientResolveAddress(mInstance, &serverSockAddr, aArgs[1], /* aNoRecursion */ false, + &Interpreter::HandleDnsResponse, this)); } else { @@ -1395,32 +1377,36 @@ exit: return error; } -void Interpreter::HandleDnsResponse(void * aContext, - const char * aHostname, - const otIp6Address *aAddress, - uint32_t aTtl, - otError aResult) +void Interpreter::HandleDnsResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext) { - static_cast(aContext)->HandleDnsResponse(aHostname, static_cast(aAddress), - aTtl, aResult); + static_cast(aContext)->HandleDnsResponse(aError, aResponse); } -void Interpreter::HandleDnsResponse(const char *aHostname, const Ip6::Address *aAddress, uint32_t aTtl, otError aResult) +void Interpreter::HandleDnsResponse(otError aError, const otDnsAddressResponse *aResponse) { - OutputFormat("DNS response for %s - ", aHostname); + char hostName[OT_DNS_MAX_NAME_SIZE]; + otIp6Address address; + uint32_t ttl; - if (aResult == OT_ERROR_NONE) + IgnoreError(otDnsAddressResponseGetHostName(aResponse, hostName, sizeof(hostName))); + + OutputFormat("DNS response for %s - ", hostName); + + if (aError == OT_ERROR_NONE) { - if (aAddress != nullptr) + uint16_t index = 0; + + while (otDnsAddressResponseGetAddress(aResponse, index, &address, &ttl) == OT_ERROR_NONE) { - OutputIp6Address(*aAddress); + OutputIp6Address(address); + OutputFormat(" TTL: %u ", ttl); + index++; } - OutputLine(" TTL: %d", aTtl); + + OutputLine(""); } - OutputResult(aResult); - - mResolvingInProgress = false; + OutputResult(aError); } #endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 386d98ea0..a5ad143b0 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -41,7 +41,7 @@ #include #include -#include +#include #include #include #include @@ -554,11 +554,8 @@ private: #endif #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE - static void HandleDnsResponse(void * aContext, - const char * aHostname, - const otIp6Address *aAddress, - uint32_t aTtl, - otError aResult); + static void HandleDnsResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext); + void HandleDnsResponse(otError aError, const otDnsAddressResponse *aResponse); #endif #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE @@ -570,9 +567,6 @@ private: void HandleActiveScanResult(otActiveScanResult *aResult); void HandleEnergyScanResult(otEnergyScanResult *aResult); void HandleLinkPcapReceive(const otRadioFrame *aFrame, bool aIsTx); -#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE - void HandleDnsResponse(const char *aHostname, const Ip6::Address *aAddress, uint32_t aTtl, otError aResult); -#endif #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE void HandleSntpResponse(uint64_t aTime, otError aResult); #endif @@ -796,11 +790,6 @@ private: otIp6Address mPingDestAddress; TimerMilli mPingTimer; otIcmp6Handler mIcmpHandler; -#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE - bool mResolvingInProgress; - char mResolvingHostname[OT_DNS_MAX_HOSTNAME_LENGTH]; -#endif - #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE bool mSntpQueryingInProgress; #endif diff --git a/src/core/api/dns_api.cpp b/src/core/api/dns_api.cpp index b8bc4fbc7..c25fa1d60 100644 --- a/src/core/api/dns_api.cpp +++ b/src/core/api/dns_api.cpp @@ -33,7 +33,7 @@ #include "openthread-core-config.h" -#include +#include #include "common/instance.hpp" #include "common/locator-getters.hpp" @@ -42,10 +42,139 @@ using namespace ot; #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE -otError otDnsClientQuery(otInstance *aInstance, const otDnsQuery *aQuery, otDnsResponseHandler aHandler, void *aContext) + +otError otDnsClientResolveAddress(otInstance * aInstance, + const otSockAddr * aServerSockAddr, + const char * aHostName, + bool aNoRecursion, + otDnsAddressCallback aCallback, + void * aContext) { Instance &instance = *static_cast(aInstance); - return instance.Get().Query(*static_cast(aQuery), aHandler, aContext); + return instance.Get().ResolveAddress(*static_cast(aServerSockAddr), aHostName, + aNoRecursion, aCallback, aContext); } -#endif + +otError otDnsAddressResponseGetHostName(const otDnsAddressResponse *aResponse, + char * aNameBuffer, + uint16_t aNameBufferSize) +{ + const Dns::Client::AddressResponse &response = *static_cast(aResponse); + + return response.GetHostName(aNameBuffer, aNameBufferSize); +} + +otError otDnsAddressResponseGetAddress(const otDnsAddressResponse *aResponse, + uint16_t aIndex, + otIp6Address * aAddress, + uint32_t * aTtl) +{ + const Dns::Client::AddressResponse &response = *static_cast(aResponse); + uint32_t ttl; + + return response.GetAddress(aIndex, *static_cast(aAddress), (aTtl != nullptr) ? *aTtl : ttl); +} + +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + +otError otDnsClientBrowse(otInstance * aInstance, + const otSockAddr * aServerSockAddr, + const char * aServiceName, + otDnsBrowseCallback aCallback, + void * aContext) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().Browse(*static_cast(aServerSockAddr), aServiceName, + aCallback, aContext); +} + +otError otDnsBrowseResponseGetServiceName(const otDnsBrowseResponse *aResponse, + char * aNameBuffer, + uint16_t aNameBufferSize) +{ + const Dns::Client::BrowseResponse &response = *static_cast(aResponse); + + return response.GetServiceName(aNameBuffer, aNameBufferSize); +} + +otError otDnsBrowseResponseGetServiceInstance(const otDnsBrowseResponse *aResponse, + uint16_t aIndex, + char * aLabelBuffer, + uint8_t aLabelBufferSize) +{ + const Dns::Client::BrowseResponse &response = *static_cast(aResponse); + + return response.GetServiceInstance(aIndex, aLabelBuffer, aLabelBufferSize); +} + +otError otDnsBrowseResponseGetServiceInfo(const otDnsBrowseResponse *aResponse, + const char * aInstanceLabel, + otDnsServiceInfo * aServiceInfo) +{ + const Dns::Client::BrowseResponse &response = *static_cast(aResponse); + + return response.GetServiceInfo(aInstanceLabel, *static_cast(aServiceInfo)); +} + +otError otDnsBrowseResponseGetHostAddress(const otDnsBrowseResponse *aResponse, + const char * aHostName, + uint16_t aIndex, + otIp6Address * aAddress, + uint32_t * aTtl) +{ + const Dns::Client::BrowseResponse &response = *static_cast(aResponse); + uint32_t ttl; + + return response.GetHostAddress(aHostName, aIndex, *static_cast(aAddress), + aTtl != nullptr ? *aTtl : ttl); +} + +otError otDnsClientResolveService(otInstance * aInstance, + const otSockAddr * aServerSockAddr, + const char * aInstanceLabel, + const char * aServiceName, + otDnsServiceCallback aCallback, + void * aContext) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().ResolveService(*static_cast(aServerSockAddr), + aInstanceLabel, aServiceName, aCallback, aContext); +} + +otError otDnsServiceResponseGetServiceName(const otDnsServiceResponse *aResponse, + char * aLabelBuffer, + uint8_t aLabelBufferSize, + char * aNameBuffer, + uint16_t aNameBufferSize) +{ + const Dns::Client::ServiceResponse &response = *static_cast(aResponse); + + return response.GetServiceName(aLabelBuffer, aLabelBufferSize, aNameBuffer, aNameBufferSize); +} + +otError otDnsServiceResponseGetServiceInfo(const otDnsServiceResponse *aResponse, otDnsServiceInfo *aServiceInfo) +{ + const Dns::Client::ServiceResponse &response = *static_cast(aResponse); + + return response.GetServiceInfo(*static_cast(aServiceInfo)); +} + +otError otDnsServiceResponseGetHostAddress(const otDnsServiceResponse *aResponse, + const char * aHostName, + uint16_t aIndex, + otIp6Address * aAddress, + uint32_t * aTtl) +{ + const Dns::Client::ServiceResponse &response = *static_cast(aResponse); + uint32_t ttl; + + return response.GetHostAddress(aHostName, aIndex, *static_cast(aAddress), + (aTtl != nullptr) ? *aTtl : ttl); +} + +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE diff --git a/src/core/config/dns_client.h b/src/core/config/dns_client.h index 914bc84ca..55a7ed8a7 100644 --- a/src/core/config/dns_client.h +++ b/src/core/config/dns_client.h @@ -65,4 +65,14 @@ #define OPENTHREAD_CONFIG_DNS_MAX_RETRANSMIT 2 #endif +/** + * @def OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + * + * Define to 1 to enable DNS based Service Discovery (DNS-SD) client. + * + */ +#ifndef OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE +#define OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE 1 +#endif + #endif // CONFIG_DNS_CLIENT_H_ diff --git a/src/core/net/dns_client.cpp b/src/core/net/dns_client.cpp index 9adc9ec32..0b8a3bf43 100644 --- a/src/core/net/dns_client.cpp +++ b/src/core/net/dns_client.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, The OpenThread Authors. + * Copyright (c) 2017-2021, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,15 +43,355 @@ * This file implements the DNS client. */ -using ot::Encoding::BigEndian::HostSwap16; - namespace ot { namespace Dns { -Client::Client(Instance &aInstance) - : mSocket(aInstance) - , mRetransmissionTimer(aInstance, Client::HandleRetransmissionTimer) +//--------------------------------------------------------------------------------------------------------------------- +// Client::Response + +void Client::Response::SelectSection(Section aSection, uint16_t &aOffset, uint16_t &aNumRecord) const { + switch (aSection) + { + case kAnswerSection: + aOffset = mAnswerOffset; + aNumRecord = mAnswerRecordCount; + break; + case kAdditionalDataSection: + default: + aOffset = mAdditionalOffset; + aNumRecord = mAdditionalRecordCount; + break; + } +} + +otError Client::Response::GetName(char *aNameBuffer, uint16_t aNameBufferSize) const +{ + uint16_t offset = kNameOffsetInQuery; + + return Name::ReadName(*mQuery, offset, aNameBuffer, aNameBufferSize); +} + +otError Client::Response::FindHostAddress(Section aSection, + const Name & aHostName, + uint16_t aIndex, + Ip6::Address &aAddress, + uint32_t & aTtl) const +{ + otError error; + uint16_t offset; + uint16_t numRecords; + Name name = aHostName; + CnameRecord cnameRecord; + AaaaRecord aaaaRecord; + + VerifyOrExit(mMessage != nullptr, error = OT_ERROR_NOT_FOUND); + + // If the response includes a CNAME record mapping the query host + // name to a canonical name, we then search for AAAA records + // matching the canonical name. + + SelectSection(aSection, offset, numRecords); + error = ResourceRecord::FindRecord(*mMessage, offset, numRecords, /* aIndex */ 0, aHostName, cnameRecord); + + if (error == OT_ERROR_NONE) + { + name.SetFromMessage(*mMessage, offset); + SuccessOrExit(error = Name::ParseName(*mMessage, offset)); + } + else + { + VerifyOrExit(error == OT_ERROR_NOT_FOUND); + } + + SelectSection(aSection, offset, numRecords); + SuccessOrExit(error = ResourceRecord::FindRecord(*mMessage, offset, numRecords, aIndex, name, aaaaRecord)); + aAddress = aaaaRecord.GetAddress(); + aTtl = aaaaRecord.GetTtl(); + +exit: + return error; +} + +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + +otError Client::Response::FindServiceInfo(Section aSection, const Name &aName, ServiceInfo &aServiceInfo) const +{ + // This method searches for SRV and TXT records in the given + // section matching the record name against `aName`, and updates + // the `aServiceInfo` accordingly. It also searches for AAAA + // record for host name associated with the service (from SRV + // record). The search for AAAA record is always performed in + // Additional Data section (independent of the value given in + // `aSection`). + + otError error; + uint16_t offset; + uint16_t numRecords; + Name hostName; + SrvRecord srvRecord; + TxtRecord txtRecord; + + VerifyOrExit(mMessage != nullptr, error = OT_ERROR_NOT_FOUND); + + // Search for a matching SRV record + SelectSection(aSection, offset, numRecords); + SuccessOrExit(error = ResourceRecord::FindRecord(*mMessage, offset, numRecords, /* aIndex */ 0, aName, srvRecord)); + + aServiceInfo.mTtl = srvRecord.GetTtl(); + aServiceInfo.mPort = srvRecord.GetPort(); + aServiceInfo.mPriority = srvRecord.GetPriority(); + aServiceInfo.mWeight = srvRecord.GetWeight(); + + hostName.SetFromMessage(*mMessage, offset); + + if (aServiceInfo.mHostNameBuffer != nullptr) + { + SuccessOrExit(error = srvRecord.ReadTargetHostName(*mMessage, offset, aServiceInfo.mHostNameBuffer, + aServiceInfo.mHostNameBufferSize)); + } + else + { + SuccessOrExit(error = Name::ParseName(*mMessage, offset)); + } + + // Search in additional section for AAAA record for the host name. + + error = FindHostAddress(kAdditionalDataSection, hostName, /* aIndex */ 0, + static_cast(aServiceInfo.mHostAddress), aServiceInfo.mHostAddressTtl); + + if (error == OT_ERROR_NOT_FOUND) + { + static_cast(aServiceInfo.mHostAddress).Clear(); + aServiceInfo.mHostAddressTtl = 0; + } + else + { + SuccessOrExit(error); + } + + // A null `mTxtData` indicates that caller does not want to retrieve TXT data. + VerifyOrExit(aServiceInfo.mTxtData != nullptr); + + // Search for a matching TXT record. If not found, indicate this by + // setting `aServiceInfo.mTxtDataSize` to zero. + + SelectSection(aSection, offset, numRecords); + error = ResourceRecord::FindRecord(*mMessage, offset, numRecords, /* aIndex */ 0, aName, txtRecord); + + switch (error) + { + case OT_ERROR_NONE: + SuccessOrExit(error = + txtRecord.ReadTxtData(*mMessage, offset, aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize)); + aServiceInfo.mTxtDataTtl = txtRecord.GetTtl(); + break; + + case OT_ERROR_NOT_FOUND: + aServiceInfo.mTxtDataSize = 0; + aServiceInfo.mTxtDataTtl = 0; + break; + + default: + ExitNow(); + } + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + +//--------------------------------------------------------------------------------------------------------------------- +// Client::AddressResponse + +otError Client::AddressResponse::GetAddress(uint16_t aIndex, Ip6::Address &aAddress, uint32_t &aTtl) const +{ + return FindHostAddress(kAnswerSection, Name(*mQuery, kNameOffsetInQuery), aIndex, aAddress, aTtl); +} + +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + +//--------------------------------------------------------------------------------------------------------------------- +// Client::BrowseResponse + +otError Client::BrowseResponse::GetServiceInstance(uint16_t aIndex, char *aLabelBuffer, uint8_t aLabelBufferSize) const +{ + otError error; + uint16_t offset; + uint16_t numRecords; + Name serviceName(*mQuery, kNameOffsetInQuery); + PtrRecord ptrRecord; + + VerifyOrExit(mMessage != nullptr, error = OT_ERROR_NOT_FOUND); + + SelectSection(kAnswerSection, offset, numRecords); + SuccessOrExit(error = ResourceRecord::FindRecord(*mMessage, offset, numRecords, aIndex, serviceName, ptrRecord)); + error = ptrRecord.ReadPtrName(*mMessage, offset, aLabelBuffer, aLabelBufferSize, nullptr, 0); + +exit: + return error; +} + +otError Client::BrowseResponse::GetServiceInfo(const char *aInstanceLabel, ServiceInfo &aServiceInfo) const +{ + otError error; + Name instanceName; + + // Find a matching PTR record for the service instance label. + // Then search and read SRV, TXT and AAAA records in Additional Data section + // matching the same name to populate `aServiceInfo`. + + SuccessOrExit(error = FindPtrRecord(aInstanceLabel, instanceName)); + error = FindServiceInfo(kAdditionalDataSection, instanceName, aServiceInfo); + +exit: + return error; +} + +otError Client::BrowseResponse::GetHostAddress(const char * aHostName, + uint16_t aIndex, + Ip6::Address &aAddress, + uint32_t & aTtl) const +{ + return FindHostAddress(kAdditionalDataSection, Name(aHostName), aIndex, aAddress, aTtl); +} + +otError Client::BrowseResponse::FindPtrRecord(const char *aInstanceLabel, Name &aInstanceName) const +{ + // This method searches within the Answer Section for a PTR record + // matching a given instance label @aInstanceLabel. If found, the + // `aName` is updated to return the name in the message. + + otError error; + uint16_t offset; + Name serviceName(*mQuery, kNameOffsetInQuery); + uint16_t numRecords; + uint16_t labelOffset; + PtrRecord ptrRecord; + + VerifyOrExit(mMessage != nullptr, error = OT_ERROR_NOT_FOUND); + + SelectSection(kAnswerSection, offset, numRecords); + + for (; numRecords > 0; numRecords--) + { + SuccessOrExit(error = Name::CompareName(*mMessage, offset, serviceName)); + + error = ResourceRecord::ReadRecord(*mMessage, offset, ptrRecord); + + if (error == OT_ERROR_NOT_FOUND) + { + // `ReadRecord()` updates `offset` to skip over a + // non-matching record. + continue; + } + + SuccessOrExit(error); + + // It is a PTR record. Check the first label to match the + // instance label and the rest of the name to match the service + // name from `mQuery`. + + labelOffset = offset; + error = Name::CompareLabel(*mMessage, labelOffset, aInstanceLabel); + + if (error == OT_ERROR_NONE) + { + error = Name::CompareName(*mMessage, labelOffset, serviceName); + + if (error == OT_ERROR_NONE) + { + aInstanceName.SetFromMessage(*mMessage, offset); + ExitNow(); + } + } + + VerifyOrExit(error == OT_ERROR_NOT_FOUND); + + // Update offset to skip over the PTR record. + offset += static_cast(ptrRecord.GetSize()) - sizeof(ptrRecord); + } + + error = OT_ERROR_NOT_FOUND; + +exit: + return error; +} + +//--------------------------------------------------------------------------------------------------------------------- +// Client::ServiceResponse + +otError Client::ServiceResponse::GetServiceName(char * aLabelBuffer, + uint8_t aLabelBufferSize, + char * aNameBuffer, + uint16_t aNameBufferSize) const +{ + otError error; + uint16_t offset = kNameOffsetInQuery; + + SuccessOrExit(error = Name::ReadLabel(*mQuery, offset, aLabelBuffer, aLabelBufferSize)); + + VerifyOrExit(aNameBuffer != nullptr); + SuccessOrExit(error = Name::ReadName(*mQuery, offset, aNameBuffer, aNameBufferSize)); + +exit: + return error; +} + +otError Client::ServiceResponse::GetServiceInfo(ServiceInfo &aServiceInfo) const +{ + // Search and read SRV, TXT records in Answer Section + // matching name from query. + + return FindServiceInfo(kAnswerSection, Name(*mQuery, kNameOffsetInQuery), aServiceInfo); +} + +otError Client::ServiceResponse::GetHostAddress(const char * aHostName, + uint16_t aIndex, + Ip6::Address &aAddress, + uint32_t & aTtl) const +{ + return FindHostAddress(kAdditionalDataSection, Name(aHostName), aIndex, aAddress, aTtl); +} + +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + +//--------------------------------------------------------------------------------------------------------------------- +// Client + +const uint16_t Client::kAddressQueryRecordTypes[] = {ResourceRecord::kTypeAaaa}; +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE +const uint16_t Client::kBrowseQueryRecordTypes[] = {ResourceRecord::kTypePtr}; +const uint16_t Client::kServiceQueryRecordTypes[] = {ResourceRecord::kTypeSrv, ResourceRecord::kTypeTxt}; +#endif + +const uint8_t Client::kQuestionCount[] = { + /* (0) kAddressQuery -> */ OT_ARRAY_LENGTH(kAddressQueryRecordTypes), // AAAA records +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + /* (1) kBrowseQuery -> */ OT_ARRAY_LENGTH(kBrowseQueryRecordTypes), // PTR records + /* (2) kServiceQuery -> */ OT_ARRAY_LENGTH(kServiceQueryRecordTypes), // SRV and TXT records +#endif +}; + +const uint16_t *Client::kQuestionRecordTypes[] = { + /* (0) kAddressQuery -> */ kAddressQueryRecordTypes, +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + /* (1) kBrowseQuery -> */ kBrowseQueryRecordTypes, + /* (2) kServiceQuery -> */ kServiceQueryRecordTypes, +#endif +}; + +Client::Client(Instance &aInstance) + : InstanceLocator(aInstance) + , mSocket(aInstance) + , mTimer(aInstance, Client::HandleTimer) +{ + static_assert(kAddressQuery == 0, "kAddressQuery value is not correct"); +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + static_assert(kBrowseQuery == 1, "kBrowseQuery value is not correct"); + static_assert(kServiceQuery == 2, "kServiceQuery value is not correct"); +#endif } otError Client::Start(void) @@ -65,337 +405,417 @@ exit: return error; } -otError Client::Stop(void) +void Client::Stop(void) { - Message * message; - QueryMetadata queryMetadata; + Query *query; - // Remove all pending queries. - while ((message = mPendingQueries.GetHead()) != nullptr) + while ((query = mQueries.GetHead()) != nullptr) { - queryMetadata.ReadFrom(*message); - FinalizeDnsTransaction(*message, queryMetadata, nullptr, 0, OT_ERROR_ABORT); + FinalizeQuery(*query, OT_ERROR_ABORT); } - return mSocket.Close(); + IgnoreError(mSocket.Close()); } -otError Client::Query(const QueryInfo &aQuery, ResponseHandler aHandler, void *aContext) +otError Client::ResolveAddress(const Ip6::SockAddr &aServerSockAddr, + const char * aHostName, + bool aNoRecursion, + AddressCallback aCallback, + void * aContext) { - otError error; - QueryMetadata queryMetadata; - Message * message = nullptr; - Message * messageCopy = nullptr; - Header header; - Question question(ResourceRecord::kTypeAaaa); + QueryInfo info; - VerifyOrExit(aQuery.IsValid(), error = OT_ERROR_INVALID_ARGS); + info.Clear(); + info.mQueryType = kAddressQuery; + info.mNoRecursion = aNoRecursion; + info.mCallback.mAddressCallback = aCallback; - do + return StartQuery(info, aServerSockAddr, nullptr, aHostName, aContext); +} + +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + +otError Client::Browse(const Ip6::SockAddr &aServerSockAddr, + const char * aServiceName, + BrowseCallback aCallback, + void * aContext) +{ + QueryInfo info; + + info.Clear(); + info.mQueryType = kBrowseQuery; + info.mCallback.mBrowseCallback = aCallback; + + return StartQuery(info, aServerSockAddr, nullptr, aServiceName, aContext); +} + +otError Client::ResolveService(const Ip6::SockAddr &aServerSockAddr, + const char * aInstanceLabel, + const char * aServiceName, + ServiceCallback aCallback, + void * aContext) +{ + QueryInfo info; + + info.Clear(); + info.mQueryType = kServiceQuery; + info.mCallback.mServiceCallback = aCallback; + + return StartQuery(info, aServerSockAddr, aInstanceLabel, aServiceName, aContext); +} + +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + +otError Client::StartQuery(QueryInfo & aInfo, + const Ip6::SockAddr &aServerSockAddr, + const char * aLabel, + const char * aName, + void * aContext) +{ + // This method assumes that `mQueryType` and `mCallback` to be + // already set by caller on `aInfo`. The `aLabel` can be `nullptr` + // and then `aName` provides the full name, otherwise the name is + // appended as `{aLabel}.{aName}`. + + otError error; + Query * query; + + VerifyOrExit(mSocket.IsBound(), error = OT_ERROR_INVALID_STATE); + + aInfo.mServerSockAddr = aServerSockAddr; + aInfo.mCallbackContext = aContext; + + SuccessOrExit(error = AllocateQuery(aInfo, aLabel, aName, query)); + mQueries.Enqueue(*query); + + SendQuery(*query); + +exit: + return error; +} + +otError Client::AllocateQuery(const QueryInfo &aInfo, const char *aLabel, const char *aName, Query *&aQuery) +{ + otError error = OT_ERROR_NONE; + + aQuery = Get().New(Message::kTypeOther, /* aReserveHeader */ 0); + VerifyOrExit(aQuery != nullptr, error = OT_ERROR_NO_BUFS); + + SuccessOrExit(error = aQuery->Append(aInfo)); + + if (aLabel != nullptr) { - SuccessOrExit(error = header.SetRandomMessageId()); - } while (FindQueryById(header.GetMessageId()) != nullptr); + SuccessOrExit(error = Name::AppendLabel(aLabel, *aQuery)); + } + + SuccessOrExit(error = Name::AppendName(aName, *aQuery)); + +exit: + FreeAndNullMessageOnError(aQuery, error); + return error; +} + +void Client::FreeQuery(Query &aQuery) +{ + mQueries.Dequeue(aQuery); + aQuery.Free(); +} + +void Client::SendQuery(Query &aQuery) +{ + QueryInfo info; + + info.ReadFrom(aQuery); + + SendQuery(aQuery, info, /* aUpdateTimer */ true); +} + +void Client::SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer) +{ + // This method prepares and sends a query message represented by + // `aQuery` and `aInfo`. This method updates `aInfo` (e.g., sets + // the new `mRetransmissionTime`) and updates it in `aQuery` as + // well. `aUpdateTimer` indicates whether the timer should be + // updated when query is sent or not (used in the case where timer + // is handled by caller). + + otError error = OT_ERROR_NONE; + Message * message = nullptr; + Header header; + Ip6::MessageInfo messageInfo; + + aInfo.mRetransmissionTime = TimerMilli::GetNow() + kResponseTimeout; + + if (aInfo.mMessageId == 0) + { + do + { + SuccessOrExit(error = header.SetRandomMessageId()); + } while ((header.GetMessageId() == 0) || (FindQueryById(header.GetMessageId()) != nullptr)); + + aInfo.mMessageId = header.GetMessageId(); + } + else + { + header.SetMessageId(aInfo.mMessageId); + } header.SetType(Header::kTypeQuery); header.SetQueryType(Header::kQueryTypeStandard); - if (!aQuery.IsNoRecursion()) + if (!aInfo.mNoRecursion) { header.SetRecursionDesiredFlag(); } - header.SetQuestionCount(1); + header.SetQuestionCount(kQuestionCount[aInfo.mQueryType]); - VerifyOrExit((message = NewMessage(header)) != nullptr, error = OT_ERROR_NO_BUFS); + message = mSocket.NewMessage(0); + VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS); - SuccessOrExit(error = Name::AppendName(aQuery.GetHostname(), *message)); - SuccessOrExit(error = question.AppendTo(*message)); + SuccessOrExit(error = message->Append(header)); - queryMetadata.mHostname = aQuery.GetHostname(); - queryMetadata.mResponseHandler = aHandler; - queryMetadata.mResponseContext = aContext; - queryMetadata.mTransmissionTime = TimerMilli::GetNow() + kResponseTimeout; - queryMetadata.mSourceAddress = aQuery.GetMessageInfo().GetSockAddr(); - queryMetadata.mDestinationAddress = aQuery.GetMessageInfo().GetPeerAddr(); - queryMetadata.mDestinationPort = aQuery.GetMessageInfo().GetPeerPort(); - queryMetadata.mRetransmissionCount = 0; + // Prepare the question section. - VerifyOrExit((messageCopy = CopyAndEnqueueMessage(*message, queryMetadata)) != nullptr, error = OT_ERROR_NO_BUFS); - SuccessOrExit(error = SendMessage(*message, aQuery.GetMessageInfo())); - -exit: - - if (error != OT_ERROR_NONE) + for (uint8_t num = 0; num < kQuestionCount[aInfo.mQueryType]; num++) { - FreeMessage(message); - - if (messageCopy) - { - DequeueMessage(*messageCopy); - } + SuccessOrExit(error = AppendNameFromQuery(aQuery, *message)); + SuccessOrExit(error = message->Append(Question(kQuestionRecordTypes[aInfo.mQueryType][num]))); } - return error; -} + messageInfo.SetPeerAddr(aInfo.mServerSockAddr.GetAddress()); + messageInfo.SetPeerPort(aInfo.mServerSockAddr.GetPort()); -Message *Client::NewMessage(const Header &aHeader) -{ - Message *message = mSocket.NewMessage(sizeof(aHeader)); - - VerifyOrExit(message != nullptr); - IgnoreError(message->Prepend(aHeader)); - message->SetOffset(0); + SuccessOrExit(error = mSocket.SendTo(*message, messageInfo)); exit: - return message; -} + FreeMessageOnError(message, error); -Message *Client::CopyAndEnqueueMessage(const Message &aMessage, const QueryMetadata &aQueryMetadata) -{ - otError error = OT_ERROR_NONE; - Message *messageCopy = aMessage.Clone(); + UpdateQuery(aQuery, aInfo); - VerifyOrExit(messageCopy != nullptr, error = OT_ERROR_NO_BUFS); - - SuccessOrExit(error = aQueryMetadata.AppendTo(*messageCopy)); - mPendingQueries.Enqueue(*messageCopy); - - mRetransmissionTimer.FireAtIfEarlier(aQueryMetadata.mTransmissionTime); - -exit: - FreeAndNullMessageOnError(messageCopy, error); - return messageCopy; -} - -void Client::DequeueMessage(Message &aMessage) -{ - mPendingQueries.Dequeue(aMessage); - - if (mPendingQueries.GetHead() == nullptr) + if (aUpdateTimer) { - mRetransmissionTimer.Stop(); - } - - aMessage.Free(); -} - -otError Client::SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) -{ - return mSocket.SendTo(aMessage, aMessageInfo); -} - -void Client::SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo) -{ - otError error; - Message *messageCopy = aMessage.Clone(aMessage.GetLength() - sizeof(QueryMetadata)); - - VerifyOrExit(messageCopy != nullptr, error = OT_ERROR_NO_BUFS); - - error = SendMessage(*messageCopy, aMessageInfo); - -exit: - - if (error != OT_ERROR_NONE) - { - FreeMessage(messageCopy); - otLogWarnDns("Failed to send DNS request: %s", otThreadErrorToString(error)); + mTimer.FireAtIfEarlier(aInfo.mRetransmissionTime); } } -otError Client::CompareQuestions(Message &aMessageResponse, Message &aMessageQuery, uint16_t &aOffset) +otError Client::AppendNameFromQuery(const Query &aQuery, Message &aMessage) { otError error = OT_ERROR_NONE; - uint8_t bufQuery[kBufSize]; - uint8_t bufResponse[kBufSize]; - uint16_t read = 0; + uint16_t offset; + uint16_t length; - // Compare question section of the query with the response. - uint16_t length = aMessageQuery.GetLength() - aMessageQuery.GetOffset() - sizeof(Header) - sizeof(QueryMetadata); - uint16_t offset = aMessageQuery.GetOffset() + sizeof(Header); + // The name is encoded and included after the `Info` in `aQuery`. We + // first calculate the encoded length of the name, then grow the + // message, and finally copy the encoded name bytes from `aQuery` + // into `aMessage`. - while (length > 0) - { - VerifyOrExit((read = aMessageQuery.ReadBytes(offset, bufQuery, - length < sizeof(bufQuery) ? length : sizeof(bufQuery))) > 0, - error = OT_ERROR_PARSE); - SuccessOrExit(error = aMessageResponse.Read(aOffset, bufResponse, read)); + length = aQuery.GetLength() - kNameOffsetInQuery; - VerifyOrExit(memcmp(bufResponse, bufQuery, read) == 0, error = OT_ERROR_NOT_FOUND); + offset = aMessage.GetLength(); + SuccessOrExit(error = aMessage.SetLength(offset + length)); - aOffset += read; - offset += read; - length -= read; - } + aQuery.CopyTo(/* aSourceOffset */ kNameOffsetInQuery, /* aDestOffset */ offset, length, aMessage); exit: return error; } -Message *Client::FindQueryById(uint16_t aMessageId) +void Client::FinalizeQuery(Query &aQuery, otError aError) { - uint16_t messageId; - Message *message; + Response response; + QueryInfo info; - for (message = mPendingQueries.GetHead(); message != nullptr; message = message->GetNext()) + response.mQuery = &aQuery; + info.ReadFrom(aQuery); + + FinalizeQuery(response, info.mQueryType, aError); +} + +void Client::FinalizeQuery(Response &aResponse, QueryType aType, otError aError) +{ + Callback callback; + void * context; + + GetCallback(*aResponse.mQuery, callback, context); + + switch (aType) { - // Partially read DNS header to obtain message ID only. - if (message->Read(message->GetOffset(), messageId) != OT_ERROR_NONE) + case kAddressQuery: + if (callback.mAddressCallback != nullptr) { - OT_ASSERT(false); + callback.mAddressCallback(aError, &aResponse, context); } + break; - if (HostSwap16(messageId) == aMessageId) +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + case kBrowseQuery: + if (callback.mBrowseCallback != nullptr) + { + callback.mBrowseCallback(aError, &aResponse, context); + } + break; + + case kServiceQuery: + if (callback.mServiceCallback != nullptr) + { + callback.mServiceCallback(aError, &aResponse, context); + } + break; +#endif + } + + FreeQuery(*aResponse.mQuery); +} + +void Client::GetCallback(const Query &aQuery, Callback &aCallback, void *&aContext) +{ + QueryInfo info; + + info.ReadFrom(aQuery); + + aCallback = info.mCallback; + aContext = info.mCallbackContext; +} + +Client::Query *Client::FindQueryById(uint16_t aMessageId) +{ + Query * query; + QueryInfo info; + + for (query = mQueries.GetHead(); query != nullptr; query = query->GetNext()) + { + info.ReadFrom(*query); + + if (info.mMessageId == aMessageId) { break; } } - return message; + return query; } -void Client::FinalizeDnsTransaction(Message & aQuery, - const QueryMetadata &aQueryMetadata, - const Ip6::Address * aAddress, - uint32_t aTtl, - otError aResult) +void Client::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMsgInfo) { - DequeueMessage(aQuery); + OT_UNUSED_VARIABLE(aMsgInfo); - if (aQueryMetadata.mResponseHandler != nullptr) + static_cast(aContext)->ProcessResponse(*static_cast(aMessage)); +} + +void Client::ProcessResponse(const Message &aMessage) +{ + Response response; + QueryType type; + otError responseError; + + response.mMessage = &aMessage; + + SuccessOrExit(ParseResponse(response, type, responseError)); + FinalizeQuery(response, type, responseError); + +exit: + return; +} + +otError Client::ParseResponse(Response &aResponse, QueryType &aType, otError &aResponseError) +{ + otError error = OT_ERROR_NONE; + const Message &message = *aResponse.mMessage; + uint16_t offset = message.GetOffset(); + Header header; + QueryInfo info; + + SuccessOrExit(error = message.Read(offset, header)); + offset += sizeof(Header); + + VerifyOrExit((header.GetType() == Header::kTypeResponse) && (header.GetQueryType() == Header::kQueryTypeStandard) && + !header.IsTruncationFlagSet(), + error = OT_ERROR_DROP); + + aResponse.mQuery = FindQueryById(header.GetMessageId()); + VerifyOrExit(aResponse.mQuery != nullptr, error = OT_ERROR_NOT_FOUND); + + info.ReadFrom(*aResponse.mQuery); + aType = info.mQueryType; + + // Check the Question Section + + VerifyOrExit(header.GetQuestionCount() == kQuestionCount[aType], error = OT_ERROR_PARSE); + + for (uint8_t num = 0; num < kQuestionCount[aType]; num++) { - aQueryMetadata.mResponseHandler(aQueryMetadata.mResponseContext, aQueryMetadata.mHostname, aAddress, aTtl, - aResult); + // The name is encoded after `Info` struct in `query`. + SuccessOrExit(error = Name::CompareName(message, offset, *aResponse.mQuery, kNameOffsetInQuery)); + offset += sizeof(Question); } -} -void Client::HandleRetransmissionTimer(Timer &aTimer) -{ - aTimer.Get().HandleRetransmissionTimer(); -} + // Check the answer, authority and additional record sections -void Client::HandleRetransmissionTimer(void) -{ - TimeMilli now = TimerMilli::GetNow(); - TimeMilli nextTime = now.GetDistantFuture(); - QueryMetadata queryMetadata; - Message * message; - Message * nextMessage; - Ip6::MessageInfo messageInfo; + aResponse.mAnswerOffset = offset; + SuccessOrExit(error = ResourceRecord::ParseRecords(message, offset, header.GetAnswerCount())); + SuccessOrExit(error = ResourceRecord::ParseRecords(message, offset, header.GetAuthorityRecordCount())); + aResponse.mAdditionalOffset = offset; + SuccessOrExit(error = ResourceRecord::ParseRecords(message, offset, header.GetAdditionalRecordCount())); - for (message = mPendingQueries.GetHead(); message != nullptr; message = nextMessage) + aResponse.mAnswerRecordCount = header.GetAnswerCount(); + aResponse.mAdditionalRecordCount = header.GetAdditionalRecordCount(); + + // Check the response code from server + + aResponseError = Header::ResponseCodeToError(header.GetResponseCode()); + +exit: + if (error != OT_ERROR_NONE) { - nextMessage = message->GetNext(); + otLogInfoDns("Failed to parse response %s", otThreadErrorToString(error)); + } - queryMetadata.ReadFrom(*message); + return error; +} - if (now >= queryMetadata.mTransmissionTime) +void Client::HandleTimer(Timer &aTimer) +{ + aTimer.Get().HandleTimer(); +} + +void Client::HandleTimer(void) +{ + TimeMilli now = TimerMilli::GetNow(); + TimeMilli nextTime = now.GetDistantFuture(); + Query * nextQuery; + QueryInfo info; + + for (Query *query = mQueries.GetHead(); query != nullptr; query = nextQuery) + { + nextQuery = query->GetNext(); + + info.ReadFrom(*query); + + if (now >= info.mRetransmissionTime) { - if (queryMetadata.mRetransmissionCount >= kMaxRetransmit) + if (info.mRetransmissionCount >= kMaxRetransmit) { - FinalizeDnsTransaction(*message, queryMetadata, nullptr, 0, OT_ERROR_RESPONSE_TIMEOUT); - + FinalizeQuery(*query, OT_ERROR_RESPONSE_TIMEOUT); continue; } - // Increment retransmission counter and timer. - queryMetadata.mRetransmissionCount++; - queryMetadata.mTransmissionTime = now + kResponseTimeout; - queryMetadata.UpdateIn(*message); - - // Retransmit - messageInfo.SetPeerAddr(queryMetadata.mDestinationAddress); - messageInfo.SetPeerPort(queryMetadata.mDestinationPort); - messageInfo.SetSockAddr(queryMetadata.mSourceAddress); - - SendCopy(*message, messageInfo); + info.mRetransmissionCount++; + SendQuery(*query, info, /* aUpdateTimer */ false); } - if (nextTime > queryMetadata.mTransmissionTime) + if (nextTime > info.mRetransmissionTime) { - nextTime = queryMetadata.mTransmissionTime; + nextTime = info.mRetransmissionTime; } } if (nextTime < now.GetDistantFuture()) { - mRetransmissionTimer.FireAt(nextTime); + mTimer.FireAt(nextTime); } } -void Client::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) -{ - static_cast(aContext)->HandleUdpReceive(*static_cast(aMessage), - *static_cast(aMessageInfo)); -} - -void Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) -{ - // RFC1035 7.3. Resolver cannot rely that a response will come from the same address - // which it sent the corresponding query to. - OT_UNUSED_VARIABLE(aMessageInfo); - - otError error = OT_ERROR_NOT_FOUND; - Header responseHeader; - QueryMetadata queryMetadata; - AaaaRecord record; - Message * message = nullptr; - uint16_t offset = aMessage.GetOffset(); - - SuccessOrExit(aMessage.Read(offset, responseHeader)); - VerifyOrExit(responseHeader.GetType() == Header::kTypeResponse && responseHeader.GetQuestionCount() == 1 && - !responseHeader.IsTruncationFlagSet()); - offset += sizeof(responseHeader); - - VerifyOrExit((message = FindQueryById(responseHeader.GetMessageId())) != nullptr); - queryMetadata.ReadFrom(*message); - - VerifyOrExit(responseHeader.GetResponseCode() == Header::kResponseSuccess, error = OT_ERROR_FAILED); - - // Parse and check the question section. - SuccessOrExit(error = CompareQuestions(aMessage, *message, offset)); - - // Parse and check the answer section. - for (uint32_t index = 0; index < responseHeader.GetAnswerCount(); index++) - { - uint32_t newOffset; - - SuccessOrExit(error = Name::ParseName(aMessage, offset)); - - SuccessOrExit(error = aMessage.Read(offset, record)); - - if (record.Matches(ResourceRecord::kTypeAaaa)) - { - // Return the first found IPv6 address. - FinalizeDnsTransaction(*message, queryMetadata, &record.GetAddress(), record.GetTtl(), OT_ERROR_NONE); - ExitNow(error = OT_ERROR_NONE); - } - - newOffset = offset + record.GetSize(); - VerifyOrExit(newOffset <= aMessage.GetLength(), error = OT_ERROR_PARSE); - offset = static_cast(newOffset); - } - -exit: - - if (message != nullptr && error != OT_ERROR_NONE) - { - FinalizeDnsTransaction(*message, queryMetadata, nullptr, 0, error); - } -} - -void Client::QueryMetadata::ReadFrom(const Message &aMessage) -{ - uint16_t length = aMessage.GetLength(); - - OT_ASSERT(length >= sizeof(*this)); - IgnoreError(aMessage.Read(length - sizeof(*this), *this)); -} - -void Client::QueryMetadata::UpdateIn(Message &aMessage) const -{ - aMessage.Write(aMessage.GetLength() - sizeof(*this), *this); -} - } // namespace Dns } // namespace ot diff --git a/src/core/net/dns_client.hpp b/src/core/net/dns_client.hpp index 656f5c4f9..94359ab32 100644 --- a/src/core/net/dns_client.hpp +++ b/src/core/net/dns_client.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, The OpenThread Authors. + * Copyright (c) 2017-2021, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,8 +31,9 @@ #include "openthread-core-config.h" -#include +#include +#include "common/clearable.hpp" #include "common/message.hpp" #include "common/non_copyable.hpp" #include "common/timer.hpp" @@ -45,6 +46,34 @@ * This file includes definitions for the DNS client. */ +/** + * This struct represents an opaque (and empty) type for a response to an address resolution DNS query. + * + */ +struct otDnsAddressResponse +{ +}; + +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + +/** + * This struct represents an opaque (and empty) type for a response to browse (service instance enumeration) DNS query. + * + */ +struct otDnsBrowseResponse +{ +}; + +/** + * This struct represents an opaque (and empty) type for a response to service inst resolution DNS query. + * + */ +struct otDnsServiceResponse +{ +}; + +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + namespace ot { namespace Dns { @@ -52,57 +81,309 @@ namespace Dns { * This class implements DNS client. * */ -class Client : private NonCopyable +class Client : public InstanceLocator, private NonCopyable { + typedef Message Query; // `Message` is used to save `Query` related info. + public: +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE /** - * This type represents a DNS Query info/parameters. + * This structure provides info for a DNS service instance. * */ - class QueryInfo : public otDnsQuery + typedef otDnsServiceInfo ServiceInfo; +#endif + + /** + * This class represents a DNS query response. + * + */ + class Response : public Clearable, + public otDnsAddressResponse +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + , + public otDnsBrowseResponse, + public otDnsServiceResponse +#endif + { - public: - /** - * This method indicates whether the `QueryInfo` object is valid or not. - * - * @returns TRUE if the `QueryInfo` is valid, FALSE otherwise. - * - */ - bool IsValid(void) const { return (mHostname != nullptr) && (mMessageInfo != nullptr); } + friend class Client; - /** - * This method gets the host name in a DNS query. - * - * @return The host name. - * - */ - const char *GetHostname(void) const { return mHostname; } - - /** - * This method gets the `MessageInfo` related to DNS Server. - * - * @returns The `MessageInfo` of DNS Server. - * - */ - const Ip6::MessageInfo &GetMessageInfo(void) const + protected: + enum Section : uint8_t { - return *static_cast(mMessageInfo); - } + kAnswerSection, + kAdditionalDataSection, + }; - /** - * This method indicates whether or not the name server can pursue the query recursively. - * - * @returns TRUE if no recursion is allowed, FALSE otherwise. - * - */ - bool IsNoRecursion(void) const { return mNoRecursion; } + Response(void) { Clear(); } + + otError GetName(char *aNameBuffer, uint16_t aNameBufferSize) const; + void SelectSection(Section aSection, uint16_t &aOffset, uint16_t &aNumRecord) const; + otError FindHostAddress(Section aSection, + const Name & aHostName, + uint16_t aIndex, + Ip6::Address &aAddress, + uint32_t & aTtl) const; + +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + otError FindServiceInfo(Section aSection, const Name &aName, ServiceInfo &aServiceInfo) const; +#endif + + Query * mQuery; // The associated query. + const Message *mMessage; // The response message. + uint16_t mAnswerOffset; // Answer section offset in `mMessage`. + uint16_t mAnswerRecordCount; // Number of records in answer section. + uint16_t mAdditionalOffset; // Additional data section offset in `mMessage`. + uint16_t mAdditionalRecordCount; // Number of records in additional data section. }; /** - * This type represents the function pointer type which is called when a DNS response is received. + * This type represents the function pointer callback which is called when a DNS response for an address resolution + * query is received. * */ - typedef otDnsResponseHandler ResponseHandler; + typedef otDnsAddressCallback AddressCallback; + + /** + * This type represents an address resolution query DNS response. + * + */ + class AddressResponse : public Response + { + friend class Client; + + public: + /** + * This method gets the host name associated with an address resolution DNS response. + * + * This method MUST only be used from `AddressCallback`. + * + * @param[out] aNameBuffer A buffer to char array to output the host name. + * @param[in] aNameBufferSize The size of @p aNameBuffer. + * + * @retval OT_ERROR_NONE The host name was read successfully. + * @retval OT_ERROR_NO_BUFS The name does not fit in @p aNameBuffer. + * + */ + otError GetHostName(char *aNameBuffer, uint16_t aNameBufferSize) const + { + return GetName(aNameBuffer, aNameBufferSize); + } + + /** + * This method gets the IPv6 address associated with an address resolution DNS response. + * + * This method MUST only be used from `AddressCallback`. + * + * The response may include multiple IPv6 address records. @p aIndex can be used to iterate through the list of + * addresses. Index zero gets the the first address and so on. When we reach end of the list, this method + * returns `OT_ERROR_NOT_FOUND`. + * + * @param[in] aIndex The address record index to retrieve. + * @param[out] aAddress A reference to an IPv6 address to output the address. + * @param[out] aTtl A reference to a `uint32_t` to output TTL for the address. + * + * @retval OT_ERROR_NONE The address was read successfully. + * @retval OT_ERROR_NOT_FOUND No address record at @p aIndex. + * @retval OT_ERROR_PARSE Could not parse the records. + * + */ + otError GetAddress(uint16_t aIndex, Ip6::Address &aAddress, uint32_t &aTtl) const; + }; + +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + + /** + * This type represents the function pointer callback which is called when a response for a browse (service + * instance enumeration) DNS query is received. + * + */ + typedef otDnsBrowseCallback BrowseCallback; + + /** + * This type represents a browse (service instance enumeration) DNS response. + * + */ + class BrowseResponse : public Response + { + friend class Client; + + public: + /** + * This method gets the service name associated with a DNS browse response. + * + * This method MUST only be used from `BrowseCallback`. + * + * @param[out] aNameBuffer A buffer to char array to output the host name. + * @param[in] aNameBufferSize The size of @p aNameBuffer. + * + * @retval OT_ERROR_NONE The host name was read successfully. + * @retval OT_ERROR_NO_BUFS The name does not fit in @p aNameBuffer. + * + */ + otError GetServiceName(char *aNameBuffer, uint16_t aNameBufferSize) const + { + return GetName(aNameBuffer, aNameBufferSize); + } + + /** + * This method gets a service instance associated with a DNS browse (service instance enumeration) response. + * + * This method MUST only be used from `BrowseCallback`. + * + * A response may include multiple service instance records. @p aIndex can be used to iterate through the list. + * Index zero gives the the first record. When we reach end of the list, `OT_ERROR_NOT_FOUND` is returned. + * + * Note that this method gets the service instance label and not the full service instance name which is of the + * form `..`. + * + * @param[in] aResponse A pointer to a response. + * @param[in] aIndex The service instance record index to retrieve. + * @param[out] aLabelBuffer A char array to output the service instance label (MUST NOT be NULL). + * @param[in] aLabelBufferSize The size of @p aLabelBuffer. + * + * @retval OT_ERROR_NONE The service instance was read successfully. + * @retval OT_ERROR_NO_BUFS The name does not fit in @p aNameBuffer. + * @retval OT_ERROR_NOT_FOUND No service instance record at @p aIndex. + * @retval OT_ERROR_PARSE Could not parse the records. + * + */ + otError GetServiceInstance(uint16_t aIndex, char *aLabelBuffer, uint8_t aLabelBufferSize) const; + + /** + * This method gets info for a service instance from a DNS browse (service instance enumeration) response. + * + * This method MUST only be used from `BrowseCallback`. + * + * A browse DNS response should include the SRV, TXT, and AAAA records for the service instances that are + * enumerated (note that it is a SHOULD and not a MUST requirement). This method tries to retrieve this info + * for a given service instance. + * + * If no matching SRV record is found, `OT_ERROR_NOT_FOUND` is returned. + * If a matching SRV record is found, @p aServiceInfo is updated returning `OT_ERROR_NONE`. + * If no matching TXT record is found, `mTxtDataSize` in @p aServiceInfo is set to zero. + * If no matching AAAA record is found, `mHostAddress is set to all zero or unspecified address. + * If there are multiple AAAA records for the host name `mHostAddress` is set to the first one. + * The other addresses can be retrieved using `GetHostAddress()` method. + * + * @param[in] aInstanceLabel The service instance label (MUST NOT be `nullptr`). + * @param[out] aServiceInfo A `ServiceInfo` to output the service instance information. + * + * @retval OT_ERROR_NONE The service instance info was read. @p aServiceInfo is updated. + * @retval OT_ERROR_NOT_FOUND Could not find a matching SRV record for @p aInstanceLabel. + * @retval OT_ERROR_NO_BUFS The host name and/or the TXT data could not fit in given buffers. + * @retval OT_ERROR_PARSE Could not parse the records. + * + */ + otError GetServiceInfo(const char *aInstanceLabel, ServiceInfo &aServiceInfo) const; + + /** + * This method gets the host IPv6 address from a DNS browse (service instance enumeration) response. + * + * This method MUST only be used from `BrowseCallback`. + * + * The response can include zero or more IPv6 address records. @p aIndex can be used to iterate through the + * list of addresses. Index zero gets the first address and so on. When we reach end of the list, this method + * returns `OT_ERROR_NOT_FOUND`. + * + * @param[in] aHostName The host name to get the address (MUST NOT be `nullptr`). + * @param[in] aIndex The address record index to retrieve. + * @param[out] aAddress A reference to an IPv6 address to output the address. + * @param[out] aTtl A reference to a `uint32_t` to output TTL for the address. + * + * @retval OT_ERROR_NONE The address was read successfully. + * @retval OT_ERROR_NOT_FOUND No address record for @p aHostname at @p aIndex. + * @retval OT_ERROR_PARSE Could not parse the records. + * + */ + otError GetHostAddress(const char *aHostName, uint16_t aIndex, Ip6::Address &aAddress, uint32_t &aTtl) const; + + private: + otError FindPtrRecord(const char *aInstanceLabel, Name &aInstanceName) const; + }; + + /** + * This type represents the function pointer callback which is called when a response for a service instance + * resolution DNS query is received. + * + */ + typedef otDnsServiceCallback ServiceCallback; + + /** + * This type represents a service instance resolution DNS response. + * + */ + class ServiceResponse : public Response + { + friend class Client; + + public: + /** + * This method gets the service instance name associated with a DNS service instance resolution response. + * + * This method MUST only be used from `ServiceCallback`. + * + * @param[out] aLabelBuffer A buffer to char array to output the service instance label (MUST NOT be NULL). + * @param[in] aLabelBufferSize The size of @p aLabelBuffer. + * @param[out] aNameBuffer A buffer to char array to output the rest of service name (can be NULL if user + * is not interested in getting the name). + * @param[in] aNameBufferSize The size of @p aNameBuffer. + * + * @retval OT_ERROR_NONE The service instance name was read successfully. + * @retval OT_ERROR_NO_BUFS Either the label or name does not fit in the given buffers. + * + */ + otError GetServiceName(char * aLabelBuffer, + uint8_t aLabelBufferSize, + char * aNameBuffer, + uint16_t aNameBufferSize) const; + + /** + * This method gets info for a service instance from a DNS service instance resolution response. + * + * This method MUST only be used from `ServiceCallback`. + * + * If no matching SRV record is found, `OT_ERROR_NOT_FOUND` is returned. + * If a matching SRV record is found, @p aServiceInfo is updated and `OT_ERROR_NONE` is returned. + * If no matching TXT record is found, `mTxtDataSize` in @p aServiceInfo is set to zero. + * If no matching AAAA record is found, `mHostAddress is set to all zero or unspecified address. + * If there are multiple AAAA records for the host name, `mHostAddress` is set to the first one. + * The other addresses can be retrieved using `GetHostAddress()` method. + * + * @param[out] aServiceInfo A `ServiceInfo` to output the service instance information + * + * @retval OT_ERROR_NONE The service instance info was read. @p aServiceInfo is updated. + * @retval OT_ERROR_NOT_FOUND Could not find a matching SRV record. + * @retval OT_ERROR_NO_BUFS The host name and/or TXT data could not fit in the given buffers. + * @retval OT_ERROR_PARSE Could not parse the records in the @p aResponse. + * + */ + otError GetServiceInfo(ServiceInfo &aServiceInfo) const; + + /** + * This method gets the host IPv6 address from a DNS service instance resolution response. + * + * This method MUST only be used from `ServiceCallback`. + * + * The response can include zero or more IPv6 address records. @p aIndex can be used to iterate through the + * list of addresses. Index zero gets the first address and so on. When we reach end of the list, this method + * returns `OT_ERROR_NOT_FOUND`. + * + * @param[in] aHostName The host name to get the address (MUST NOT be `nullptr`). + * @param[in] aIndex The address record index to retrieve. + * @param[out] aAddress A reference to an IPv6 address to output the address. + * @param[out] aTtl A reference to a `uint32_t` to output TTL for the address. + * + * @retval OT_ERROR_NONE The address was read successfully. + * @retval OT_ERROR_NOT_FOUND No address record for @p aHostname at @p aIndex. + * @retval OT_ERROR_PARSE Could not parse the records. + * + */ + otError GetHostAddress(const char *aHostName, uint16_t aIndex, Ip6::Address &aAddress, uint32_t &aTtl) const; + }; + +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE /** * This constructor initializes the object. @@ -124,84 +405,150 @@ public: /** * This method stops the DNS client. * - * @retval OT_ERROR_NONE Successfully stopped the DNS client. - * */ - otError Stop(void); + void Stop(void); /** - * This method sends a DNS query. + * This method sends an address resolution DNS query for AAAA (IPv6) record for a given host name. * - * @param[in] aQuery A pointer to specify DNS query parameters. - * @param[in] aHandler A function pointer that shall be called on response reception or time-out. - * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aServerSockAddr The server socket address. + * @param[in] aHostName The host name for which to query the address (MUST NOT be `nullptr`). + * @param[in] aNoRecursion Indicates whether name server can resolve the query recursively or not. + * @param[in] aCallback A callback function pointer to report the result of query. + * @param[in] aContext A pointer to arbitrary context information passed to @p aCallback. * - * @retval OT_ERROR_NONE Successfully sent DNS query. - * @retval OT_ERROR_NO_BUFS Failed to allocate retransmission data. - * @retval OT_ERROR_INVALID_ARGS Invalid arguments supplied. + * @retval OT_ERROR_NONE Successfully sent DNS query. + * @retval OT_ERROR_NO_BUFS Failed to allocate retransmission data. + * @retval OT_ERROR_INVALID_ARGS The host name is not valid format. + * @retval OT_ERROR_INVALID_STATE Cannot send query since Thread interface is not up. * */ - otError Query(const QueryInfo &aQuery, ResponseHandler aHandler, void *aContext); + otError ResolveAddress(const Ip6::SockAddr &aServerSockAddr, + const char * aHostName, + bool aNoRecursion, + AddressCallback aCallback, + void * aContext); + +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + + /** + * This method sends a browse (service instance enumeration) DNS query for a given service name. + * + * @param[in] aServerSockAddr The server socket address. + * @param[in] aServiceName The service name to query for (MUST NOT be `nullptr`). + * @param[in] aCallback The callback to report the response or errors (such as time-out). + * @param[in] aContext A pointer to arbitrary context information. + * + * @retval OT_ERROR_NONE Query sent successfully. @p aCallback will be invoked to report the status. + * @retval OT_ERROR_NO_BUFS Insufficient buffer to prepare and send query. + * + */ + otError Browse(const Ip6::SockAddr &aServerSockAddr, + const char * aServiceName, + BrowseCallback aCallback, + void * aContext); + + /** + * This function sends a DNS service instance resolution query for a given service instance. + * + * @param[in] aServerSockAddr The server socket address. + * @param[in] aInstanceLabel The service instance label. + * @param[in] aServiceName The service name (together with @p aInstanceLabel form full instance name). + * @param[in] aCallback A function pointer that shall be called on response reception or time-out. + * @param[in] aContext A pointer to arbitrary context information. + * + * @retval OT_ERROR_NONE Query sent successfully. @p aCallback will be invoked to report the status. + * @retval OT_ERROR_NO_BUFS Insufficient buffer to prepare and send query. + * + */ + otError ResolveService(const Ip6::SockAddr &aServerSockAddr, + const char * aInstanceLabel, + const char * aServiceName, + otDnsServiceCallback aCallback, + void * aContext); + +#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE private: - /** - * Retransmission parameters. - * - */ enum { - kResponseTimeout = OPENTHREAD_CONFIG_DNS_RESPONSE_TIMEOUT, + kResponseTimeout = OPENTHREAD_CONFIG_DNS_RESPONSE_TIMEOUT, // in msec kMaxRetransmit = OPENTHREAD_CONFIG_DNS_MAX_RETRANSMIT, }; - enum + enum QueryType : uint8_t { - kBufSize = 16 + kAddressQuery, // Address resolution. +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + kBrowseQuery, // Browse (service instance enumeration). + kServiceQuery, // Service instance resolution. +#endif }; - struct QueryMetadata + union Callback { - otError AppendTo(Message &aMessage) const { return aMessage.Append(*this); } - void ReadFrom(const Message &aMessage); - void UpdateIn(Message &aMessage) const; - - const char * mHostname; - ResponseHandler mResponseHandler; - void * mResponseContext; - TimeMilli mTransmissionTime; - Ip6::Address mSourceAddress; - Ip6::Address mDestinationAddress; - uint16_t mDestinationPort; - uint8_t mRetransmissionCount; + AddressCallback mAddressCallback; +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + BrowseCallback mBrowseCallback; + ServiceCallback mServiceCallback; +#endif }; - Message *NewMessage(const Header &aHeader); - Message *CopyAndEnqueueMessage(const Message &aMessage, const QueryMetadata &aQueryMetadata); - void DequeueMessage(Message &aMessage); - otError SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); - void SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + typedef MessageQueue QueryList; // List of queries. - otError GenerateUniqueRandomId(uint16_t &aRandomId); + struct QueryInfo : public Clearable // Query related Info + { + void ReadFrom(const Query &aQuery) { IgnoreError(aQuery.Read(0, *this)); } - otError CompareQuestions(Message &aMessageResponse, Message &aMessageQuery, uint16_t &aOffset); + QueryType mQueryType; + uint16_t mMessageId; + Ip6::SockAddr mServerSockAddr; + Callback mCallback; + void * mCallbackContext; + TimeMilli mRetransmissionTime; + uint8_t mRetransmissionCount; + bool mNoRecursion; + // Followed by the name (service, host, instance) encoded as a `Dns::Name`. + }; - Message *FindQueryById(uint16_t aMessageId); - void FinalizeDnsTransaction(Message & aQuery, - const QueryMetadata &aQueryMetadata, - const Ip6::Address * aAddress, - uint32_t aTtl, - otError aResult); + enum : uint16_t + { + kNameOffsetInQuery = sizeof(QueryInfo), + }; - static void HandleRetransmissionTimer(Timer &aTimer); - void HandleRetransmissionTimer(void); + otError StartQuery(QueryInfo & aInfo, + const Ip6::SockAddr &aServerSockAddr, + const char * aLabel, + const char * aName, + void * aContext); + otError AllocateQuery(const QueryInfo &aInfo, const char *aLabel, const char *aName, Query *&aQuery); + void FreeQuery(Query &aQuery); + void UpdateQuery(Query &aQuery, const QueryInfo &aInfo) { aQuery.Write(0, aInfo); } + void SendQuery(Query &aQuery); + void SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer); + void FinalizeQuery(Query &aQuery, otError aError); + void FinalizeQuery(Response &Response, QueryType aType, otError aError); + static void GetCallback(const Query &aQuery, Callback &aCallback, void *&aContext); + otError AppendNameFromQuery(const Query &aQuery, Message &aMessage); + Query * FindQueryById(uint16_t aMessageId); + static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMsgInfo); + void ProcessResponse(const Message &aMessage); + otError ParseResponse(Response &aResponse, QueryType &aType, otError &aResponseError); + static void HandleTimer(Timer &aTimer); + void HandleTimer(void); - static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); - void HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + static const uint8_t kQuestionCount[]; + static const uint16_t *kQuestionRecordTypes[]; + + static const uint16_t kAddressQueryRecordTypes[]; +#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE + static const uint16_t kBrowseQueryRecordTypes[]; + static const uint16_t kServiceQueryRecordTypes[]; +#endif Ip6::Udp::Socket mSocket; - - MessageQueue mPendingQueries; - TimerMilli mRetransmissionTimer; + QueryList mQueries; + TimerMilli mTimer; }; } // namespace Dns diff --git a/src/core/net/dns_headers.cpp b/src/core/net/dns_headers.cpp index 2f8b59aa2..836c1a412 100644 --- a/src/core/net/dns_headers.cpp +++ b/src/core/net/dns_headers.cpp @@ -102,7 +102,7 @@ otError Header::ResponseCodeToError(Response aResponse) otError Name::AppendLabel(const char *aLabel, Message &aMessage) { - return AppendLabel(aLabel, static_cast(StringLength(aLabel, kMaxLabelLength + 1)), aMessage); + return AppendLabel(aLabel, static_cast(StringLength(aLabel, kMaxLabelSize)), aMessage); } otError Name::AppendLabel(const char *aLabel, uint8_t aLength, Message &aMessage) @@ -120,7 +120,7 @@ exit: otError Name::AppendMultipleLabels(const char *aLabels, Message &aMessage) { - return AppendMultipleLabels(aLabels, kMaxLength, aMessage); + return AppendMultipleLabels(aLabels, kMaxNameLength, aMessage); } otError Name::AppendMultipleLabels(const char *aLabels, uint8_t aLength, Message &aMessage) @@ -270,7 +270,7 @@ otError Name::ReadName(const Message &aMessage, uint16_t &aOffset, char *aNameBu // here since `iterator.ReadLabel()` would verify it. } - labelLength = static_cast(OT_MIN(kMaxLabelLength + 1, aNameBufferSize)); + labelLength = static_cast(OT_MIN(static_cast(kMaxLabelSize), aNameBufferSize)); SuccessOrExit(error = iterator.ReadLabel(aNameBuffer, labelLength, /* aAllowDotCharInLabel */ false)); aNameBuffer += labelLength; aNameBufferSize -= labelLength; @@ -548,8 +548,8 @@ bool Name::LabelIterator::CompareLabel(const LabelIterator &aOtherIterator) cons bool Name::IsSubDomainOf(const char *aName, const char *aDomain) { bool match = false; - uint16_t nameLength = StringLength(aName, kMaxLength); - uint16_t domainLength = StringLength(aDomain, kMaxLength); + uint16_t nameLength = StringLength(aName, kMaxNameLength); + uint16_t domainLength = StringLength(aDomain, kMaxNameLength); if (nameLength > 0 && aName[nameLength - 1] == kLabelSeperatorChar) { diff --git a/src/core/net/dns_headers.hpp b/src/core/net/dns_headers.hpp index 8f97eca8c..15d24fa47 100644 --- a/src/core/net/dns_headers.hpp +++ b/src/core/net/dns_headers.hpp @@ -37,6 +37,7 @@ #include "openthread-core-config.h" #include +#include #include "common/clearable.hpp" #include "common/encoding.hpp" @@ -490,9 +491,29 @@ class Name : public Clearable public: enum : uint8_t { - kMaxLabelLength = 63, ///< Max number of characters in a label. - kMaxLength = 254, ///< Max number of characters in a name. - kMaxEncodedLength = 255, ///< Max length of an encoded name. + /** + * Max size (number of chars) in a name string array (includes null char at the end of string). + * + */ + kMaxNameSize = OT_DNS_MAX_NAME_SIZE, + + /** + * Maximum length in a name string (does not include null char at the end of string). + * + */ + kMaxNameLength = kMaxNameSize - 1, + + /** + * Max size (number of chars) in a label string array (includes null char at the end of the string). + * + */ + kMaxLabelSize = OT_DNS_MAX_LABEL_SIZE, + + /** + * Maximum length in a label string (does not include null char at the end of string). + * + */ + kMaxLabelLength = kMaxLabelSize - 1, }; enum : char @@ -972,6 +993,8 @@ private: kLabelTypeMask = 0xc0, // 0b1100_0000 (first two bits) kTextLabelType = 0x00, // Text label type (00) kPointerLabelType = 0xc0, // Pointer label type - compressed name (11) + + kMaxEncodedLength = 255, ///< Max length of an encoded name. }; enum : uint16_t @@ -1085,17 +1108,18 @@ public: */ enum : uint16_t { - kTypeZero = 0, ///< Zero is used as a special indicator for the SIG RR (SIG(0) from RFC 2931). - kTypeA = 1, ///< Address record (IPv4). - kTypeSoa = 6, ///< Start of (zone of) authority. - kTypePtr = 12, ///< PTR record. - kTypeTxt = 16, ///< TXT record. - kTypeSig = 24, ///< SIG record. - kTypeKey = 25, ///< KEY record. - kTypeAaaa = 28, ///< IPv6 address record. - kTypeSrv = 33, ///< SRV locator record. - kTypeOpt = 41, ///< Option record. - kTypeAny = 255, ///< ANY record. + kTypeZero = 0, ///< Zero is used as a special indicator for the SIG RR (SIG(0) from RFC 2931). + kTypeA = 1, ///< Address record (IPv4). + kTypeSoa = 6, ///< Start of (zone of) authority. + kTypeCname = 5, ///< CNAME record. + kTypePtr = 12, ///< PTR record. + kTypeTxt = 16, ///< TXT record. + kTypeSig = 24, ///< SIG record. + kTypeKey = 25, ///< KEY record. + kTypeAaaa = 28, ///< IPv6 address record. + kTypeSrv = 33, ///< SRV locator record. + kTypeOpt = 41, ///< Option record. + kTypeAny = 255, ///< ANY record. }; /** @@ -1373,6 +1397,60 @@ private: } OT_TOOL_PACKED_END; +/** + * This class implements Resource Record body format of CNAME type. + * + */ +OT_TOOL_PACKED_BEGIN +class CnameRecord : public ResourceRecord +{ +public: + enum : uint16_t + { + kType = kTypeCname, ///< The CNAME record type. + }; + + /** + * This method initializes the CNAME Resource Record by setting its type and class. + * + * Other record fields (TTL, length) remain unchanged/uninitialized. + * + * @param[in] aClass The class of the resource record (default is `kClassInternet`). + * + */ + void Init(uint16_t aClass = kClassInternet) { ResourceRecord::Init(kTypeCname, aClass); } + + /** + * This method parses and reads the CNAME alias name from a message. + * + * This method also verifies that the CNAME record is well-formed (e.g., the record data length `GetLength()` + * matches the CNAME encoded name). + * + * @param[in] aMessage The message to read from. `aMessage.GetOffset()` MUST point to the start of + * DNS header. + * @param[inout] aOffset On input, the offset in @p aMessage to start of CNAME name field. + * On exit when successfully read, @p aOffset is updated to point to the byte + * after the entire PTR record (skipping over the record). + * @param[out] aNameBuffer A pointer to a char array to output the read name as a null-terminated C string + * (MUST NOT be nullptr). + * @param[in] aNameBufferSize The size of @p aNameBuffer. + * + * @retval OT_ERROR_NONE The CNAME name was read successfully. @p aOffset and @p aNameBuffer are updated. + * @retval OT_ERROR_PARSE The CNAME record in @p aMessage could not be parsed (invalid format). + * @retval OT_ERROR_NO_BUFS Name could not fit in @p aNameBufferSize chars. + * + */ + otError ReadCanonicalName(const Message &aMessage, + uint16_t & aOffset, + char * aNameBuffer, + uint16_t aNameBufferSize) const + { + return ResourceRecord::ReadName(aMessage, aOffset, /* aStartOffset */ aOffset - sizeof(CnameRecord), + aNameBuffer, aNameBufferSize, /* aSkipRecord */ true); + } + +} OT_TOOL_PACKED_END; + /** * This class implements Resource Record body format of PTR type. * @@ -2424,10 +2502,10 @@ class Question { public: /** - * Default constructor for Question. + * Default constructor for Question * */ - Question() = default; + Question(void) = default; /** * Constructor for Question. @@ -2471,21 +2549,9 @@ public: */ void SetClass(uint16_t aClass) { mClass = HostSwap16(aClass); } - /** - * This method appends the question data to the message. - * - * @param[in] aMessage A reference to the message. - * - * @retval OT_ERROR_NONE Successfully appended the question data. - * @retval OT_ERROR_NO_BUFS Insufficient available buffers to grow the message. - * - */ - otError AppendTo(Message &aMessage) const { return aMessage.Append(*this); } - private: uint16_t mType; // The type of the data in question section. uint16_t mClass; // The class of the data in question section. - } OT_TOOL_PACKED_END; /** diff --git a/src/core/net/dnssd_server.cpp b/src/core/net/dnssd_server.cpp index 651b75968..bf94f3ed5 100644 --- a/src/core/net/dnssd_server.cpp +++ b/src/core/net/dnssd_server.cpp @@ -116,7 +116,7 @@ void Server::ProcessQuery(Message &aMessage, Message &aResponse, const Header &a uint16_t readOffset, nameSerializeOffset; Question question; uint16_t qtype; - char name[Dns::Name::kMaxLength + 1]; + char name[Dns::Name::kMaxNameSize]; otError error = OT_ERROR_NONE; NameCompressInfo compressInfo; @@ -376,8 +376,8 @@ otError Server::AppendInstanceName(Message &aMessage, const char *aName, NameCom } else { - uint8_t serviceStart = static_cast(StringLength(aName, Name::kMaxLength) - - StringLength(aCompressInfo.GetServiceName(), Name::kMaxLength)); + uint8_t serviceStart = static_cast(StringLength(aName, Name::kMaxNameLength) - + StringLength(aCompressInfo.GetServiceName(), Name::kMaxNameLength)); aCompressInfo.SetInstanceNameOffset(aMessage.GetLength(), aName); @@ -400,8 +400,8 @@ otError Server::AppendHostName(Message &aMessage, const char *aName, NameCompres } else { - uint8_t domainStart = static_cast(StringLength(aName, Name::kMaxLength) - - StringLength(aCompressInfo.GetDomainName(), Name::kMaxLength)); + uint8_t domainStart = static_cast(StringLength(aName, Name::kMaxNameLength) - + StringLength(aCompressInfo.GetDomainName(), Name::kMaxNameLength)); aCompressInfo.SetHostNameOffset(aMessage.GetLength(), aName); @@ -427,8 +427,8 @@ void Server::IncResourceRecordCount(Header &aHeader, bool aAdditional) otError Server::FindNameComponents(const char *aName, const char *aDomain, NameComponentsOffsetInfo &aInfo) { - uint8_t nameLen = static_cast(StringLength(aName, Name::kMaxLength)); - uint8_t domainLen = static_cast(StringLength(aDomain, Name::kMaxLength)); + uint8_t nameLen = static_cast(StringLength(aName, Name::kMaxNameLength)); + uint8_t domainLen = static_cast(StringLength(aDomain, Name::kMaxNameLength)); otError error = OT_ERROR_NONE; uint8_t labelBegin, labelEnd; diff --git a/src/core/net/srp_server.cpp b/src/core/net/srp_server.cpp index 2345ed514..d19c33388 100644 --- a/src/core/net/srp_server.cpp +++ b/src/core/net/srp_server.cpp @@ -175,7 +175,7 @@ otError Server::SetDomain(const char *aDomain) VerifyOrExit(!mEnabled, error = OT_ERROR_INVALID_STATE); - VerifyOrExit(length > 0 && length <= Dns::Name::kMaxLength, error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(length > 0 && length < Dns::Name::kMaxNameSize, error = OT_ERROR_INVALID_ARGS); if (aDomain[length - 1] != '.') { appendTrailingDot = 1; @@ -556,7 +556,7 @@ otError Server::ProcessZoneSection(const Message & aMessage, Dns::Zone & aZone) const { otError error = OT_ERROR_NONE; - char name[Dns::Name::kMaxLength + 1]; + char name[Dns::Name::kMaxNameSize]; Dns::Zone zone; VerifyOrExit(aDnsHeader.GetZoneRecordCount() == 1, error = OT_ERROR_PARSE); @@ -618,7 +618,7 @@ otError Server::ProcessHostDescriptionInstruction(Host & aHost for (uint16_t i = 0; i < aDnsHeader.GetUpdateRecordCount(); ++i) { - char name[Dns::Name::kMaxLength + 1]; + char name[Dns::Name::kMaxNameSize]; Dns::ResourceRecord record; SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name))); @@ -723,9 +723,9 @@ otError Server::ProcessServiceDiscoveryInstructions(Host & aHo for (uint16_t i = 0; i < aDnsHeader.GetUpdateRecordCount(); ++i) { - char name[Dns::Name::kMaxLength + 1]; + char name[Dns::Name::kMaxNameSize]; Dns::ResourceRecord record; - char serviceName[Dns::Name::kMaxLength + 1]; + char serviceName[Dns::Name::kMaxNameSize]; Service * service; SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name))); @@ -774,7 +774,7 @@ otError Server::ProcessServiceDescriptionInstructions(Host & a for (uint16_t i = 0; i < aDnsHeader.GetUpdateRecordCount(); ++i) { - char name[Dns::Name::kMaxLength + 1]; + char name[Dns::Name::kMaxNameSize]; Dns::ResourceRecord record; SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name))); @@ -798,7 +798,7 @@ otError Server::ProcessServiceDescriptionInstructions(Host & a if (record.GetType() == Dns::ResourceRecord::kTypeSrv) { Dns::SrvRecord srvRecord; - char hostName[Dns::Name::kMaxLength + 1]; + char hostName[Dns::Name::kMaxNameSize]; uint16_t hostNameLength = sizeof(hostName); VerifyOrExit(record.GetClass() == aZone.GetClass(), error = OT_ERROR_FAILED); @@ -864,7 +864,7 @@ otError Server::ProcessAdditionalSection(Host * aHost, char name[2]; // The root domain name (".") is expected. uint16_t sigOffset; uint16_t sigRdataOffset; - char signerName[Dns::Name::kMaxLength + 1]; + char signerName[Dns::Name::kMaxNameSize]; uint16_t signatureLength; VerifyOrExit(aDnsHeader.GetAdditionalRecordCount() == 2, error = OT_ERROR_FAILED); diff --git a/src/core/thread/thread_netif.cpp b/src/core/thread/thread_netif.cpp index 579409445..876de402a 100644 --- a/src/core/thread/thread_netif.cpp +++ b/src/core/thread/thread_netif.cpp @@ -182,7 +182,7 @@ void ThreadNetif::Down(void) VerifyOrExit(mIsUp); #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE - IgnoreError(Get().Stop()); + Get().Stop(); #endif #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE IgnoreError(Get().Stop()); diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h index 474452879..6e4c0d1c5 100644 --- a/tests/toranj/openthread-core-toranj-config.h +++ b/tests/toranj/openthread-core-toranj-config.h @@ -478,6 +478,14 @@ */ #define OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE 1 +/** + * @def OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + * + * Define to 1 to enable SRP Server support. + * + */ +#define OPENTHREAD_CONFIG_SRP_SERVER_ENABLE 1 + /** * @def OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_CHANGE_ENABLE * diff --git a/tests/unit/test_dns.cpp b/tests/unit/test_dns.cpp index b15018ce6..68df937c5 100644 --- a/tests/unit/test_dns.cpp +++ b/tests/unit/test_dns.cpp @@ -42,9 +42,8 @@ void TestDnsName(void) { enum { - kMaxSize = 300, - kLabelSize = 64, - kNameSize = 256, + kMaxSize = 300, + kMaxNameLength = Dns::Name::kMaxNameSize - 1, }; struct TestName @@ -62,9 +61,9 @@ void TestDnsName(void) uint8_t buffer[kMaxSize]; uint16_t len; uint16_t offset; - char label[kLabelSize]; + char label[Dns::Name::kMaxLabelSize]; uint8_t labelLength; - char name[kNameSize]; + char name[Dns::Name::kMaxNameSize]; static const uint8_t kEncodedName1[] = {7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0}; static const uint8_t kEncodedName2[] = {3, 'f', 'o', 'o', 1, 'a', 2, 'b', 'b', 3, 'e', 'd', 'u', 0}; @@ -297,11 +296,11 @@ void TestDnsName(void) { if (maxLengthName[strlen(maxLengthName) - 1] == '.') { - VerifyOrQuit(strlen(maxLengthName) == Dns::Name::kMaxLength, "invalid max length string"); + VerifyOrQuit(strlen(maxLengthName) == kMaxNameLength, "invalid max length string"); } else { - VerifyOrQuit(strlen(maxLengthName) == Dns::Name::kMaxLength - 1, "invalid max length string"); + VerifyOrQuit(strlen(maxLengthName) == kMaxNameLength - 1, "invalid max length string"); } IgnoreError(message->SetLength(0)); @@ -742,8 +741,8 @@ void TestHeaderAndResourceRecords(void) Dns::ResourceRecord record; Ip6::Address hostAddress; - char label[Dns::Name::kMaxLabelLength + 1]; - char name[Dns::Name::kMaxLength]; + char label[Dns::Name::kMaxLabelSize]; + char name[Dns::Name::kMaxNameSize]; uint8_t buffer[kMaxSize]; printf("================================================================\n");