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");