diff --git a/include/openthread/dns_client.h b/include/openthread/dns_client.h index 17817c147..6074adbf8 100644 --- a/include/openthread/dns_client.h +++ b/include/openthread/dns_client.h @@ -323,7 +323,8 @@ typedef struct otDnsServiceInfo 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. + uint16_t mTxtDataSize; ///< On input, size of `mTxtData` buffer. On output number bytes written. + bool mTxtDataTruncated; ///< Indicates if TXT data could not fit in `mTxtDataSize` and was truncated. uint32_t mTxtDataTtl; ///< The TXT data TTL. } otDnsServiceInfo; @@ -408,6 +409,7 @@ otError otDnsBrowseResponseGetServiceInstance(const otDnsBrowseResponse *aRespon * - 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 TXT data length is greater than `mTxtDataSize`, it is read partially and `mTxtDataTruncated` is set to true. * - 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()`. @@ -537,6 +539,7 @@ otError otDnsServiceResponseGetServiceName(const otDnsServiceResponse *aResponse * - 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 TXT data length is greater than `mTxtDataSize`, it is read partially and `mTxtDataTruncated` is set to true. * - 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()`. diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 9a61d372b..14972aa84 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 (247) +#define OPENTHREAD_API_VERSION (248) /** * @addtogroup api-instance diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 49760dc4f..a12d865e0 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -3023,7 +3023,18 @@ void Interpreter::OutputDnsServiceInfo(uint8_t aIndentSize, const otDnsServiceIn OutputIp6Address(aServiceInfo.mHostAddress); OutputLine(" TTL:%u", aServiceInfo.mHostAddressTtl); OutputFormat(aIndentSize, "TXT:"); - OutputDnsTxtData(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize); + + if (!aServiceInfo.mTxtDataTruncated) + { + OutputDnsTxtData(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize); + } + else + { + OutputFormat("["); + OutputBytes(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize); + OutputFormat("...]"); + } + OutputLine(" TTL:%u", aServiceInfo.mTxtDataTtl); } @@ -3036,7 +3047,7 @@ void Interpreter::HandleDnsBrowseResponse(otError aError, const otDnsBrowseRespo { char name[OT_DNS_MAX_NAME_SIZE]; char label[OT_DNS_MAX_LABEL_SIZE]; - uint8_t txtBuffer[255]; + uint8_t txtBuffer[kMaxTxtDataSize]; otDnsServiceInfo serviceInfo; IgnoreError(otDnsBrowseResponseGetServiceName(aResponse, name, sizeof(name))); @@ -3078,7 +3089,7 @@ void Interpreter::HandleDnsServiceResponse(otError aError, const otDnsServiceRes { char name[OT_DNS_MAX_NAME_SIZE]; char label[OT_DNS_MAX_LABEL_SIZE]; - uint8_t txtBuffer[255]; + uint8_t txtBuffer[kMaxTxtDataSize]; otDnsServiceInfo serviceInfo; IgnoreError(otDnsServiceResponseGetServiceName(aResponse, label, sizeof(label), name, sizeof(name))); diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 8c51cd0e9..2fa32f656 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -271,6 +271,8 @@ private: static constexpr uint32_t kNetworkDiagnosticTimeoutMsecs = 5000; static constexpr uint32_t kLocateTimeoutMsecs = 2500; + static constexpr uint16_t kMaxTxtDataSize = OPENTHREAD_CONFIG_CLI_TXT_RECORD_MAX_SIZE; + using Command = CommandEntry; template using GetHandler = ValueType (&)(otInstance *); diff --git a/src/cli/cli_config.h b/src/cli/cli_config.h index 36aedc644..aea25066f 100644 --- a/src/cli/cli_config.h +++ b/src/cli/cli_config.h @@ -126,4 +126,17 @@ #define OPENTHREAD_CONFIG_CLI_PROMPT_ENABLE 1 #endif +/** + * @def OPENTHREAD_CONFIG_CLI_TXT_RECORD_MAX_SIZE + * + * Specifies the max TXT record data length to use when performing DNS queries. + * + * If the service TXT record data length is greater than the specified value, it will be read partially (up to the given + * size) and output as a sequence of raw hex bytes `[{hex-bytes}...]` + * + */ +#ifndef OPENTHREAD_CONFIG_CLI_TXT_RECORD_MAX_SIZE +#define OPENTHREAD_CONFIG_CLI_TXT_RECORD_MAX_SIZE 512 +#endif + #endif // CONFIG_CLI_H_ diff --git a/src/core/net/dns_client.cpp b/src/core/net/dns_client.cpp index d60764438..72fae6ad6 100644 --- a/src/core/net/dns_client.cpp +++ b/src/core/net/dns_client.cpp @@ -269,11 +269,10 @@ Error Client::Response::FindServiceInfo(Section aSection, const Name &aName, Ser { AsCoreType(&aServiceInfo.mHostAddress).Clear(); aServiceInfo.mHostAddressTtl = 0; + error = kErrorNone; } - else - { - SuccessOrExit(error); - } + + SuccessOrExit(error); // A null `mTxtData` indicates that caller does not want to retrieve TXT data. VerifyOrExit(aServiceInfo.mTxtData != nullptr); @@ -282,19 +281,30 @@ Error Client::Response::FindServiceInfo(Section aSection, const Name &aName, Ser // setting `aServiceInfo.mTxtDataSize` to zero. SelectSection(aSection, offset, numRecords); + + aServiceInfo.mTxtDataTruncated = false; + error = ResourceRecord::FindRecord(*mMessage, offset, numRecords, /* aIndex */ 0, aName, txtRecord); switch (error) { case kErrorNone: - SuccessOrExit(error = - txtRecord.ReadTxtData(*mMessage, offset, aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize)); + error = txtRecord.ReadTxtData(*mMessage, offset, aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize); + + if (error == kErrorNoBufs) + { + error = kErrorNone; + aServiceInfo.mTxtDataTruncated = true; + } + + SuccessOrExit(error); aServiceInfo.mTxtDataTtl = txtRecord.GetTtl(); break; case kErrorNotFound: aServiceInfo.mTxtDataSize = 0; aServiceInfo.mTxtDataTtl = 0; + error = kErrorNone; break; default: diff --git a/src/core/net/dns_types.cpp b/src/core/net/dns_types.cpp index e0f64a18e..eff5f73eb 100644 --- a/src/core/net/dns_types.cpp +++ b/src/core/net/dns_types.cpp @@ -1136,12 +1136,13 @@ Error TxtRecord::ReadTxtData(const Message &aMessage, { Error error = kErrorNone; - VerifyOrExit(GetLength() <= aTxtBufferSize, error = kErrorNoBufs); - SuccessOrExit(error = aMessage.Read(aOffset, aTxtBuffer, GetLength())); - VerifyOrExit(VerifyTxtData(aTxtBuffer, GetLength(), /* aAllowEmpty */ true), error = kErrorParse); - aTxtBufferSize = GetLength(); + SuccessOrExit(error = aMessage.Read(aOffset, aTxtBuffer, Min(GetLength(), aTxtBufferSize))); aOffset += GetLength(); + VerifyOrExit(GetLength() <= aTxtBufferSize, error = kErrorNoBufs); + aTxtBufferSize = GetLength(); + VerifyOrExit(VerifyTxtData(aTxtBuffer, aTxtBufferSize, /* aAllowEmpty */ true), error = kErrorParse); + exit: return error; } diff --git a/src/core/net/dns_types.hpp b/src/core/net/dns_types.hpp index 2fa3f1a7b..0b90a29ef 100644 --- a/src/core/net/dns_types.hpp +++ b/src/core/net/dns_types.hpp @@ -1692,7 +1692,8 @@ public: /** * This method parses and reads the TXT record data from a message. * - * This method also checks if the TXT data is well-formed by calling `VerifyTxtData()`. + * This method also checks if the TXT data is well-formed by calling `VerifyTxtData()` when it is successfully + * read. * * @param[in] aMessage The message to read from. * @param[in,out] aOffset On input, the offset in @p aMessage to start of TXT record data. @@ -1705,7 +1706,9 @@ public: * @retval kErrorNone The TXT data was read successfully. @p aOffset, @p aTxtBuffer and @p aTxtBufferSize * are updated. * @retval kErrorParse The TXT record in @p aMessage could not be parsed (invalid format). - * @retval kErrorNoBufs TXT data could not fit in @p aTxtBufferSize bytes. + * @retval kErrorNoBufs TXT data could not fit in @p aTxtBufferSize bytes. TXT data is still partially read + * into @p aTxtBuffer up to its size and @p aOffset is updated to skip over the full + * TXT record. * */ Error ReadTxtData(const Message &aMessage, uint16_t &aOffset, uint8_t *aTxtBuffer, uint16_t &aTxtBufferSize) const; diff --git a/tests/unit/test_dns.cpp b/tests/unit/test_dns.cpp index 074cb9ca3..89cdbe8ae 100644 --- a/tests/unit/test_dns.cpp +++ b/tests/unit/test_dns.cpp @@ -1021,6 +1021,8 @@ void TestHeaderAndResourceRecords(void) for (const char *instanceName : kInstanceNames) { + uint16_t savedOffset; + // SRV record SuccessOrQuit(Dns::Name::CompareName(*message, offset, instanceName)); SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, srvRecord)); @@ -1037,12 +1039,21 @@ void TestHeaderAndResourceRecords(void) SuccessOrQuit(Dns::Name::CompareName(*message, offset, instanceName)); SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, txtRecord)); VerifyOrQuit(txtRecord.GetTtl() == kTxtTtl); - len = sizeof(buffer); + savedOffset = offset; + len = sizeof(buffer); SuccessOrQuit(txtRecord.ReadTxtData(*message, offset, buffer, len)); VerifyOrQuit(len == sizeof(kTxtData)); VerifyOrQuit(memcmp(buffer, kTxtData, len) == 0); printf(" \"%s\" TXT %u %d \"%s\"\n", instanceName, txtRecord.GetTtl(), txtRecord.GetLength(), reinterpret_cast(buffer)); + + // Partial read of TXT data + len = sizeof(kTxtData) - 1; + memset(buffer, 0, sizeof(buffer)); + VerifyOrQuit(txtRecord.ReadTxtData(*message, savedOffset, buffer, len) == kErrorNoBufs); + VerifyOrQuit(len == sizeof(kTxtData) - 1); + VerifyOrQuit(memcmp(buffer, kTxtData, len) == 0); + VerifyOrQuit(savedOffset == offset); } SuccessOrQuit(Dns::Name::CompareName(*message, offset, kHostName));