[dns-client] add support for arbitrary DNS record type query (#11359)

This commit introduces a new feature in `Dns::Client` to support
sending DNS queries for arbitrary record types. Callers are notified
of received response via a callback. New methods are provided to
parse and read all records in the response. Public APIs and related
CLI commands for this new feature are also added.

The `OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE` build
configuration is added to control this feature. This allows projects
that do not require arbitrary DNS query functionality to disable it,
avoiding its associated firmware code size overhead.

Importantly, if a retrieved record type is PTR, CNAME, DNAME, NS, or
SRV, the record data in the received response contains a DNS name
which may use DNS name compression. For these specific record types,
the record data is first decompressed such that it contains the
uncompressed DNS name. For all other record types, the record data is
read and provided as it appears in the received response message.
This commit is contained in:
Abtin Keshavarzian
2025-04-11 16:53:32 -07:00
committed by GitHub
parent 1382f5f33e
commit 98b88dc8f7
10 changed files with 692 additions and 60 deletions
+133
View File
@@ -646,6 +646,139 @@ otError otDnsServiceResponseGetHostAddress(const otDnsServiceResponse *aResponse
otIp6Address *aAddress,
uint32_t *aTtl);
/**
* An opaque representation of a response to a query for an arbitrary record type.
*
* Pointers to instance of this type are provided from callback `otDnsRecordCallback`.
*/
typedef struct otDnsRecordResponse otDnsRecordResponse;
/**
* Pointer is called when a DNS response is received for a DNS query to an arbitrary record type.
*
* Within this callback the user can use `otDnsRecordResponseGet{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 the stack.
* - OT_ERROR_RESPONSE_TIMEOUT No DNS response has been received within timeout.
*
*/
typedef void (*otDnsRecordCallback)(otError aError, const otDnsRecordResponse *aResponse, void *aContext);
/**
* Represents a section in a DNS query/response message.
*/
typedef enum
{
OT_DNS_SECTION_ANSWER, ///< Answer section.
OT_DNS_SECTION_AUTHORITY, ///< Authority section.
OT_DNS_SECTION_ADDITIONAL, ///< Additional section.
} otDnsRecordSection;
/**
* Represents info for a record in an `otDnsRecordResponse`.
*
* This struct is used as input to `otDnsRecordResponseGetRecordInfo()`.
*/
typedef struct otDnsRecordInfo
{
char *mNameBuffer; ///< Buffer to output the name (MUST NOT be NULL).
uint16_t mNameBufferSize; ///< Size of `mNameBuffer`.
uint16_t mRecordType; ///< The record type.
uint16_t mRecordLength; ///< The record data length (in bytes).
uint32_t mTtl; ///< Record TTL (in seconds).
uint8_t *mDataBuffer; ///< Buffer to output the data (Can be NULL if data not needed).
uint16_t mDataBufferSize; ///< On input, size of `mDataBuffer`. On output number of bytes written.
otDnsRecordSection mSection; ///< Indicates the section of the record.
} otDnsRecordInfo;
/**
* Sends a DNS query for a given record type and name.
*
* Requires `OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE`.
*
* The @p aConfig can be NULL. In this case the default config (from `otDnsClientGetDefaultConfig()`) will be used as
* the config for this query. In a non-NULL @p aConfig, some of the fields can be left unspecified (value zero). The
* unspecified fields are then replaced by the values from the default config.
*
* @param[in] aInstance A pointer to an OpenThread instance.
* @param[in] aRecordType The resource record type to query.
* @param[in] aFirstLabel The first label of the name to be queried (can be NULL if not needed).
* @param[in] aNextLabels The next labels of the name to be queried (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 used when @p aCallback is invoked.
* @param[in] aConfig A pointer to the config to use for this query.
*
* @retval OT_ERROR_NONE Query sent successfully. @p aCallback will be invoked to report the outcome.
* @retval OT_ERROR_NO_BUFS Insufficient buffer to prepare and send query.
* @retval OT_ERROR_INVALID_STATE Cannot send query since Thread interface is not up.
*/
otError otDnsClientQueryRecord(otInstance *aInstance,
uint16_t aRecordType,
const char *aFirstLabel,
const char *aNextLabels,
otDnsRecordCallback aCallback,
void *aContext,
const otDnsQueryConfig *aConfig);
/**
* Gets the query name associated with a record query DNS response.
*
* MUST only be used from `otDnsRecordCallback`.
*
* @param[in] aResponse A pointer to the response.
* @param[out] aNameBuffer A buffer to char array to output the full query name (MUST NOT be NULL).
* @param[in] aNameBufferSize The size of @p aNameBuffer.
*
* @retval OT_ERROR_NONE The full query name was read successfully.
* @retval OT_ERROR_NO_BUFS The name does not fit in @p aNameBuffer.
*/
otError otDnsRecordResponseGetQueryName(const otDnsRecordResponse *aResponse,
char *aNameBuffer,
uint16_t aNameBufferSize);
/**
* Reads the records from a DNS query response.
*
* MUST only be used from `otDnsRecordCallback`.
*
* The response may include multiple records. @p aIndex can be used to iterate through the list. Index zero gets the
* first record and so on. When we reach the end of the list, `OT_ERROR_NOT_FOUND` is returned.
*
* Upon successful retrieval (`OT_ERROR_NONE`):
* - `mRecordLength` is set to the actual length of the record's data.
* - The data is copied into `mDataBuffer` (if not `NULL`) up to its capacity specified by `mDataBufferSize`.
* - `mDataBufferSize` is then updated to reflect the number of bytes actually written into `mDataBuffer`.
*
* If the retrieved record type is PTR (12), CNAME (5), DNAME (39), NS (2), or SRV (33), the record data in the
* received response contains a DNS name which may use DNS name compression. For these specific record types, the
* record data is first decompressed such that it contains the full uncompressed DNS name. This decompressed data is
* then provided in `mDataBuffer`, and `mRecordDataLength` will indicate the length of this decompressed data. For all
* other record types, the record data is read and provided as it appears in the received response message.
*
* @param[in] aResponse A pointer to the response.
* @param[in] aIndex The record index to retrieve.
* @param[out] aRecordInfo A pointer to a `otDnsRecordInfo` to populate with read record info.
*
* @retval OT_ERROR_NONE The record was read successfully.
* @retval OT_ERROR_NOT_FOUND No record in @p aResponse at @p aIndex.
* @retval OT_ERROR_PARSE Could not parse the records in the @p aResponse.
* @retval OT_ERROR_NO_BUFS The record name does not fit in the provided `mNameBufferSize` in @p aRecordInfo, or
* failed to allocate buffer to decompress a compressed DNS name (PTR, SRV, CNAME).
*/
otError otDnsRecordResponseGetRecordInfo(const otDnsRecordResponse *aResponse,
uint16_t aIndex,
otDnsRecordInfo *aRecordInfo);
/**
* @}
*/
+1 -1
View File
@@ -52,7 +52,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (493)
#define OPENTHREAD_API_VERSION (494)
/**
* @addtogroup api-instance
+22
View File
@@ -1561,6 +1561,28 @@ Service instance label is provided first, followed by the service name (note tha
The parameters after `service-name` are optional. Any unspecified (or zero) value for these optional parameters is replaced by the value from the current default config (`dns config`).
### dns query \<record-type\> \<first-label\> \<next-labels\> \[DNS server IP\] \[DNS server port\] \[response timeout (ms)\] \[max tx attempts\] \[recursion desired (boolean)\]
Requires `OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE` and `OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE`.
Sends a DNS query for a given record type and DNS name. DNS name is provided as a first label, followed by the next labels which are dot '.' separated. Note that the first label can itself contain the dot '.' character.
The `record-type` is a numerical value corresponding to the DNS RRType values.
The parameters after `next-labels` are optional. Any unspecified (or zero) value for these optional parameters is replaced by the value from the current default config (`dns config`).
If record type is `PTR` (12), `CNAME` (5), `DNAME` (39), `NS` (2), or `SRV` (33), the record data in the received response contains a DNS name which may use DNS name compression. For these specific record types, the record data is first decompressed such that it contains the full uncompressed DNS name. This decompressed data is then provided in the output. For all other record types, the record data is read and provided as it appears in the received response message.
```bash
> dns query 25 myhost default.service.arpa.
DNS query response for myhost.default.service.arpa.
0)
RecordType:25, RecordLength: 32, TTL:7108, Section:answer
Name:myhost.default.service.arpa.
RecordData:[001900010000e02d00440201030d4983605c0406803deb2d672cc42224773977]
Done
```
### dns server upstream \[enable|disable\]
Enable/Disable the upstream DNS feature. If no argument is provided, it prints whether the upstream DNS feature is enabled.
+124
View File
@@ -410,6 +410,55 @@ exit:
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
/**
* @cli dns query
* @cparam dns query @ca{record-type} @ca{first-label} @ca{next-labels} <!--
* --> [@ca{DNS-server-IP}] [@ca{DNS-server-port}] <!--
* --> [@ca{response-timeout-ms}] [@ca{max-tx-attempts}] <!--
* --> [@ca{recursion-desired-boolean}]
* @code
* dns query 25 myhost default.service.arpa.
* DNS query response for myhost.default.service.arpa.
* 0)
* RecordType:25, RecordLength: 32, TTL:7108, Section:answer
* Name:myhost.default.service.arpa.
* RecordData:[001900010000e02d00440201030d4983605c0406803deb2d672cc42224773977]
* Done
* @endcode
* @par
* Send a DNS query for a given record type and DNS name.
* DNS name is provided as a first label, followed by the next labels
* which are dot '.' separated. Note that the first label can itself
* contain the dot '.' character.
* @par
* The parameters after `next-labels` are optional. Any unspecified (or zero)
* value for these optional parameters is replaced by the value from the
* current default config (`dns config`).
* @par
* `OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE` is required.
*/
template <> otError Dns::Process<Cmd("query")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otDnsQueryConfig queryConfig;
otDnsQueryConfig *config = &queryConfig;
uint16_t recordType;
SuccessOrExit(error = aArgs[0].ParseAsUint16(recordType));
VerifyOrExit(!aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = GetDnsConfig(aArgs + 3, config));
SuccessOrExit(error = otDnsClientQueryRecord(GetInstancePtr(), recordType, aArgs[1].GetCString(),
aArgs[2].GetCString(), &HandleDnsRecordResponse, this, config));
error = OT_ERROR_PENDING;
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
//----------------------------------------------------------------------------------------------------------------------
void Dns::OutputResult(otError aError) { Interpreter::GetInterpreter().OutputResult(aError); }
@@ -650,6 +699,78 @@ void Dns::HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *a
}
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
void Dns::HandleDnsRecordResponse(otError aError, const otDnsRecordResponse *aResponse, void *aContext)
{
static_cast<Dns *>(aContext)->HandleDnsRecordResponse(aError, aResponse);
}
void Dns::HandleDnsRecordResponse(otError aError, const otDnsRecordResponse *aResponse)
{
char name[OT_DNS_MAX_NAME_SIZE];
uint8_t data[kMaxRrDataSize];
otDnsRecordInfo recordInfo;
IgnoreError(otDnsRecordResponseGetQueryName(aResponse, name, sizeof(name)));
OutputLine("DNS query response for %s ", name);
SuccessOrExit(aError);
for (uint16_t index = 0;; index++)
{
ClearAllBytes(recordInfo);
recordInfo.mNameBuffer = name;
recordInfo.mNameBufferSize = sizeof(name);
recordInfo.mDataBuffer = data;
recordInfo.mDataBufferSize = sizeof(name);
aError = otDnsRecordResponseGetRecordInfo(aResponse, index, &recordInfo);
if (aError == OT_ERROR_NOT_FOUND)
{
aError = OT_ERROR_NONE;
ExitNow();
}
SuccessOrExit(aError);
OutputLine("%u)", index);
OutputLine(kIndentSize, "RecordType:%u, RecordLength:%u, TTL:%lu, Section:%s", recordInfo.mRecordType,
recordInfo.mRecordLength, ToUlong(recordInfo.mTtl), RecordSectionToString(recordInfo.mSection));
OutputLine(kIndentSize, "Name:%s", recordInfo.mNameBuffer);
OutputFormat(kIndentSize, "RecordData:[");
OutputBytes(recordInfo.mDataBuffer, recordInfo.mDataBufferSize);
if (recordInfo.mDataBufferSize != recordInfo.mRecordLength)
{
OutputFormat("...");
}
OutputLine("]");
}
exit:
OutputResult(aError);
}
const char *Dns::RecordSectionToString(otDnsRecordSection aSection)
{
const char *const kSectionString[] = {
"answer", // (0) OT_DNS_SECTION_ANSWER
"authority", // (1) OT_DNS_SECTION_AUTHORITY
"additional", // (2) OT_DNS_SECTION_ADDITIONAL
};
static_assert(0 == OT_DNS_SECTION_ANSWER, "OT_DNS_SECTION_ANSWER value is incorrect");
static_assert(1 == OT_DNS_SECTION_AUTHORITY, "OT_DNS_SECTION_AUTHORITY value is incorrect");
static_assert(2 == OT_DNS_SECTION_ADDITIONAL, "OT_DNS_SECTION_ADDITIONALATA value is incorrect");
return Stringify(aSection, kSectionString);
}
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
@@ -716,6 +837,9 @@ otError Dns::Process(Arg aArgs[])
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
CmdEntry("config"),
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
CmdEntry("query"),
#endif
CmdEntry("resolve"),
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
CmdEntry("resolve4"),
+12 -2
View File
@@ -92,6 +92,9 @@ public:
private:
static constexpr uint8_t kIndentSize = 4;
static constexpr uint16_t kMaxTxtDataSize = OPENTHREAD_CONFIG_CLI_TXT_RECORD_MAX_SIZE;
static constexpr uint8_t kMaxRrDataSize = 128;
static const char *const kServiceModeStrings[];
using Command = CommandEntry<Dns>;
@@ -104,9 +107,8 @@ private:
otError ParseDnsServiceMode(const Arg &aArg, otDnsServiceMode &aMode) const;
static void HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext);
void HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse);
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
static const char *const kServiceModeStrings[];
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
typedef otError (&ResolveServiceFn)(otInstance *,
const char *,
const char *,
@@ -121,7 +123,15 @@ private:
static void HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse, void *aContext);
void HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse);
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
static void HandleDnsRecordResponse(otError aError, const otDnsRecordResponse *aResponse, void *aContext);
void HandleDnsRecordResponse(otError aError, const otDnsRecordResponse *aResponse);
static const char *RecordSectionToString(otDnsRecordSection aSection);
#endif
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
};
} // namespace Cli
+34
View File
@@ -253,4 +253,38 @@ otError otDnsServiceResponseGetHostAddress(const otDnsServiceResponse *aResponse
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
otError otDnsClientQueryRecord(otInstance *aInstance,
uint16_t aRecordType,
const char *aFirstLabel,
const char *aNextLabels,
otDnsRecordCallback aCallback,
void *aContext,
const otDnsQueryConfig *aConfig)
{
AssertPointerIsNotNull(aNextLabels);
return AsCoreType(aInstance).Get<Dns::Client>().QueryRecord(aRecordType, aFirstLabel, aNextLabels, aCallback,
aContext, AsCoreTypePtr(aConfig));
}
otError otDnsRecordResponseGetQueryName(const otDnsRecordResponse *aResponse,
char *aNameBuffer,
uint16_t aNameBufferSize)
{
AssertPointerIsNotNull(aNameBuffer);
return AsCoreType(aResponse).GetQueryName(aNameBuffer, aNameBufferSize);
}
otError otDnsRecordResponseGetRecordInfo(const otDnsRecordResponse *aResponse,
uint16_t aIndex,
otDnsRecordInfo *aRecordInfo)
{
return AsCoreType(aResponse).GetRecordInfo(aIndex, AsCoreType(aRecordInfo));
}
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
+9
View File
@@ -84,6 +84,15 @@
#define OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE 1
#endif
/**
* @def OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
*
* Define to 1 to enable support for a query of arbitrary DNS record type.
*/
#ifndef OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
#define OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE 1
#endif
/**
* @def OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE
*
+203 -42
View File
@@ -374,6 +374,100 @@ exit:
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
Error Client::Response::ReadRecordInfo(uint16_t aIndex, RecordInfo &aRecordInfo) const
{
Error error;
uint16_t offset;
ResourceRecord record;
Message *decompressedData = nullptr;
if (aIndex < mAnswerRecordCount)
{
aRecordInfo.mSection = MapEnum(RecordInfo::kSectionAnswer);
}
else if (aIndex < mAnswerRecordCount + mAuthorityRecordCount)
{
aRecordInfo.mSection = MapEnum(RecordInfo::kSectionAuthority);
}
else if (aIndex < mAnswerRecordCount + mAuthorityRecordCount + mAdditionalRecordCount)
{
aRecordInfo.mSection = MapEnum(RecordInfo::kSectionAdditional);
}
else
{
ExitNow(error = kErrorNotFound);
}
offset = mAnswerOffset;
SuccessOrExit(error = ResourceRecord::ParseRecords(*mMessage, offset, aIndex));
SuccessOrExit(error = Name::ReadName(*mMessage, offset, aRecordInfo.mNameBuffer, aRecordInfo.mNameBufferSize));
SuccessOrExit(error = mMessage->Read(offset, record));
VerifyOrExit(offset + record.GetSize() <= mMessage->GetLength(), error = kErrorParse);
offset += sizeof(record);
aRecordInfo.mRecordType = record.GetType();
aRecordInfo.mTtl = record.GetTtl();
// We may need to translate the record data for PTR, CNAME, DNAME, NS
// and SRV record since the data format contains a DNS name which
// may use compression.
switch (record.GetType())
{
case ResourceRecord::kTypePtr:
case ResourceRecord::kTypeCname:
case ResourceRecord::kTypeDname:
case ResourceRecord::kTypeNs:
case ResourceRecord::kTypeSrv:
decompressedData = mMessage->Get<MessagePool>().Allocate(Message::kTypeOther);
VerifyOrExit(decompressedData != nullptr, error = kErrorNoBufs);
if (record.GetType() == ResourceRecord::kTypeSrv)
{
uint16_t srvMinLength = sizeof(SrvRecord) - sizeof(ResourceRecord);
VerifyOrExit(record.GetLength() > srvMinLength, error = kErrorParse);
SuccessOrExit(error = decompressedData->AppendBytesFromMessage(*mMessage, offset, srvMinLength));
offset += srvMinLength;
}
SuccessOrExit(error = Name(*mMessage, offset).AppendTo(*decompressedData));
break;
default:
break;
}
aRecordInfo.mRecordLength = (decompressedData != nullptr) ? decompressedData->GetLength() : record.GetLength();
if (aRecordInfo.mDataBuffer == nullptr)
{
aRecordInfo.mDataBufferSize = 0;
ExitNow();
}
aRecordInfo.mDataBufferSize = Min(aRecordInfo.mRecordLength, aRecordInfo.mDataBufferSize);
if (decompressedData != nullptr)
{
decompressedData->ReadBytes(0, aRecordInfo.mDataBuffer, aRecordInfo.mDataBufferSize);
}
else
{
mMessage->ReadBytes(offset, aRecordInfo.mDataBuffer, aRecordInfo.mDataBufferSize);
}
exit:
FreeMessage(decompressedData);
return error;
}
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
void Client::Response::PopulateFrom(const Message &aMessage)
{
// Populate `Response` with info from `aMessage`.
@@ -394,11 +488,13 @@ void Client::Response::PopulateFrom(const Message &aMessage)
mAnswerOffset = offset;
IgnoreError(ResourceRecord::ParseRecords(aMessage, offset, header.GetAnswerCount()));
mAuthorityOffset = offset;
IgnoreError(ResourceRecord::ParseRecords(aMessage, offset, header.GetAuthorityRecordCount()));
mAdditionalOffset = offset;
IgnoreError(ResourceRecord::ParseRecords(aMessage, offset, header.GetAdditionalRecordCount()));
mAnswerRecordCount = header.GetAnswerCount();
mAuthorityRecordCount = header.GetAuthorityRecordCount();
mAdditionalRecordCount = header.GetAdditionalRecordCount();
}
@@ -691,42 +787,6 @@ Error Client::ServiceResponse::GetHostAddress(const char *aHostName,
//---------------------------------------------------------------------------------------------------------------------
// Client
const uint16_t Client::kIp6AddressQueryRecordTypes[] = {ResourceRecord::kTypeAaaa};
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
const uint16_t Client::kIp4AddressQueryRecordTypes[] = {ResourceRecord::kTypeA};
#endif
#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[] = {
/* kIp6AddressQuery -> */ GetArrayLength(kIp6AddressQueryRecordTypes), // AAAA record
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
/* kIp4AddressQuery -> */ GetArrayLength(kIp4AddressQueryRecordTypes), // A record
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
/* kBrowseQuery -> */ GetArrayLength(kBrowseQueryRecordTypes), // PTR record
/* kServiceQuerySrvTxt -> */ GetArrayLength(kServiceQueryRecordTypes), // SRV and TXT records
/* kServiceQuerySrv -> */ 1, // SRV record only
/* kServiceQueryTxt -> */ 1, // TXT record only
#endif
};
const uint16_t *const Client::kQuestionRecordTypes[] = {
/* kIp6AddressQuery -> */ kIp6AddressQueryRecordTypes,
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
/* kIp4AddressQuery -> */ kIp4AddressQueryRecordTypes,
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
/* kBrowseQuery -> */ kBrowseQueryRecordTypes,
/* kServiceQuerySrvTxt -> */ kServiceQueryRecordTypes,
/* kServiceQuerySrv -> */ &kServiceQueryRecordTypes[0],
/* kServiceQueryTxt -> */ &kServiceQueryRecordTypes[1],
#endif
};
Client::Client(Instance &aInstance)
: InstanceLocator(aInstance)
, mSocket(aInstance, *this)
@@ -972,6 +1032,28 @@ exit:
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
Error Client::QueryRecord(uint16_t aRecordType,
const char *aFirstLabel,
const char *aNextLabels,
RecordCallback aCallback,
void *aContext,
const QueryConfig *aConfig)
{
QueryInfo info;
info.Clear();
info.mQueryType = kRecordQuery;
info.mRecordType = aRecordType;
info.mConfig.SetFrom(aConfig, mDefaultConfig);
info.mCallback.mRecordCallback = aCallback;
info.mCallbackContext = aContext;
return StartQuery(info, aFirstLabel, aNextLabels);
}
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
Error Client::StartQuery(QueryInfo &aInfo, const char *aLabel, const char *aName, QueryType aSecondType)
{
// The `aLabel` can be `nullptr` and then `aName` provides the
@@ -1117,7 +1199,14 @@ Error Client::SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer)
header.SetRecursionDesiredFlag();
}
header.SetQuestionCount(kQuestionCount[aInfo.mQueryType]);
header.SetQuestionCount(1);
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
if (aInfo.mQueryType == kServiceQuerySrvTxt)
{
header.SetQuestionCount(2);
}
#endif
message = mSocket.NewMessage();
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
@@ -1126,11 +1215,16 @@ Error Client::SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer)
// Prepare the question section.
for (uint8_t num = 0; num < kQuestionCount[aInfo.mQueryType]; num++)
SuccessOrExit(error = AppendNameFromQuery(aQuery, *message));
SuccessOrExit(error = message->Append(Question(DetermineQuestionRecordType(aInfo))));
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
if (aInfo.mQueryType == kServiceQuerySrvTxt)
{
SuccessOrExit(error = AppendNameFromQuery(aQuery, *message));
SuccessOrExit(error = message->Append(Question(kQuestionRecordTypes[aInfo.mQueryType][num])));
SuccessOrExit(error = message->Append(Question(ResourceRecord::kTypeTxt)));
}
#endif
length = message->GetLength() - message->GetOffset();
@@ -1204,6 +1298,53 @@ exit:
return error;
}
uint16_t Client::DetermineQuestionRecordType(const QueryInfo &aInfo) const
{
// Determine the first record type to include in Question
// section based on the `mQueryType`.
uint8_t recordType = 0;
switch (aInfo.mQueryType)
{
case kIp6AddressQuery:
recordType = ResourceRecord::kTypeAaaa;
break;
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
case kIp4AddressQuery:
recordType = ResourceRecord::kTypeA;
break;
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
case kBrowseQuery:
recordType = ResourceRecord::kTypePtr;
break;
case kServiceQuerySrv:
case kServiceQuerySrvTxt:
recordType = ResourceRecord::kTypeSrv;
break;
case kServiceQueryTxt:
recordType = ResourceRecord::kTypeTxt;
break;
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
case kRecordQuery:
recordType = aInfo.mRecordType;
break;
#endif
case kNoQuery:
break;
}
return recordType;
}
Error Client::AppendNameFromQuery(const Query &aQuery, Message &aMessage)
{
// The name is encoded and included after the `Info` in `aQuery`
@@ -1259,6 +1400,14 @@ void Client::FinalizeQuery(Response &aResponse, Error aError)
callback.mServiceCallback(aError, &aResponse, context);
}
break;
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
case kRecordQuery:
if (callback.mRecordCallback != nullptr)
{
callback.mRecordCallback(aError, &aResponse, context);
}
break;
#endif
case kNoQuery:
break;
@@ -1359,6 +1508,7 @@ Error Client::ParseResponse(const Message &aResponseMessage, Query *&aQuery, Err
{
Error error = kErrorNone;
uint16_t offset = aResponseMessage.GetOffset();
uint16_t questionCount;
Header header;
QueryInfo info;
Name queryName;
@@ -1379,9 +1529,18 @@ Error Client::ParseResponse(const Message &aResponseMessage, Query *&aQuery, Err
// Check the Question Section
if (header.GetQuestionCount() == kQuestionCount[info.mQueryType])
questionCount = 1;
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
if (info.mQueryType == kServiceQuerySrvTxt)
{
for (uint8_t num = 0; num < kQuestionCount[info.mQueryType]; num++)
questionCount = 2;
}
#endif
if (header.GetQuestionCount() == questionCount)
{
for (uint16_t num = 0; num < questionCount; num++)
{
SuccessOrExit(error = Name::CompareName(aResponseMessage, offset, queryName));
offset += sizeof(Question);
@@ -1403,10 +1562,12 @@ Error Client::ParseResponse(const Message &aResponseMessage, Query *&aQuery, Err
aResponseError = Header::ResponseCodeToError(header.GetResponseCode());
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
if ((aResponseError == kErrorNone) && (info.mQueryType == kServiceQuerySrvTxt))
{
RecordServerAsCapableOfMultiQuestions(info.mConfig.GetServerSockAddr().GetAddress());
}
#endif
exit:
return error;
@@ -1808,7 +1969,7 @@ Error Client::ReadFromLinkBuffer(const otLinkedBuffer *&aLinkedBuffer,
// `aOffset` and `aLinkedBuffer` are updated.
// - `kErrorNotFound` is not enough bytes available to read
// from `aLinkedBuffer`.
// - `kErrorNotBufs` if cannot grow `aMessage` to append bytes.
// - `kErrorNoBufs` if cannot grow `aMessage` to append bytes.
Error error = kErrorNone;
+152 -15
View File
@@ -90,6 +90,17 @@ struct otDnsServiceResponse
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
/**
* Represents an opaque (and empty) type for a response to arbitrary record DNS query.
*/
struct otDnsRecordResponse
{
};
#endif
namespace ot {
namespace Srp {
@@ -266,6 +277,25 @@ public:
typedef otDnsServiceInfo ServiceInfo;
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
/**
* Provides info for a DNS record in a query response.
*/
class RecordInfo : public otDnsRecordInfo
{
public:
/**
* Represents a section in a DNS response.
*/
enum RecordSection : uint8_t
{
kSectionAnswer = OT_DNS_SECTION_ANSWER, ///< Answer section.
kSectionAuthority = OT_DNS_SECTION_AUTHORITY, ///< Authority section.
kSectionAdditional = OT_DNS_SECTION_ADDITIONAL, ///< Additional section.
};
};
#endif
/**
* Represents a DNS query response.
*/
@@ -273,6 +303,9 @@ public:
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
public otDnsBrowseResponse,
public otDnsServiceResponse,
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
public otDnsRecordResponse,
#endif
public Clearable<Response>
{
@@ -303,6 +336,9 @@ public:
void InitServiceInfo(ServiceInfo &aServiceInfo) const;
Error ReadServiceInfo(Section aSection, const Name &aName, ServiceInfo &aServiceInfo) const;
Error ReadTxtRecord(Section aSection, const Name &aName, ServiceInfo &aServiceInfo) const;
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
Error ReadRecordInfo(uint16_t aIndex, RecordInfo &aRecordInfo) const;
#endif
void PopulateFrom(const Message &aMessage);
@@ -312,6 +348,8 @@ public:
Response *mNext; // The next response when we have related queries.
uint16_t mAnswerOffset; // Answer section offset in `mMessage`.
uint16_t mAnswerRecordCount; // Number of records in answer section.
uint16_t mAuthorityOffset; // Authority section offset in `mMessage`.
uint16_t mAuthorityRecordCount; // Number of records in authority section.
uint16_t mAdditionalOffset; // Additional data section offset in `mMessage`.
uint16_t mAdditionalRecordCount; // Number of records in additional data section.
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
@@ -560,6 +598,75 @@ public:
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
/**
* Represents the function pointer callback which is called when a DNS response for a DNS query response
* is received.
*/
typedef otDnsRecordCallback RecordCallback;
/**
* Represents a record query response.
*/
class RecordResponse : public Response
{
friend class Client;
public:
/**
* Gets the query name associated with a record DNS query.
*
* MUST only be used from `RecordCallback`.
*
* @param[out] aNameBuffer A buffer to char array to output the name.
* @param[in] aNameBufferSize The size of @p aNameBuffer.
*
* @retval kErrorNone The host name was read successfully.
* @retval kErrorNoBufs The name does not fit in @p aNameBuffer.
*/
Error GetQueryName(char *aNameBuffer, uint16_t aNameBufferSize) const
{
return GetName(aNameBuffer, aNameBufferSize);
}
/**
* Gets the record data from a DNS query response.
*
* MUST only be used from `RecordCallback`.
*
* The response may include multiple records. @p aIndex can be used to iterate through the list. Index zero gets
* the first record and so on. When we reach the end of the list, `kErrorNotFound` is returned.
*
* Upon successful retrieval (`kErrorNone`):
* - `mRecordLength` is set to the actual length of the record's data.
* - The data is copied into `mDataBuffer` (if not `NULL`) up to its capacity specified by `mDataBufferSize`.
* - `mDataBufferSize` is then updated to reflect the number of bytes actually written into `mDataBuffer`.
*
* If the retrieved record type is PTR (12), CNAME (5), DNAME (39), NS (2), or SRV (33), the record data in the
* received response contains a DNS name which may use DNS name compression. For these specific record types,
* the record data is first decompressed such that it contains the full uncompressed DNS name. This
* decompressed data is then provided in `mDataBuffer`, and `mRecordDataLength` will indicate the length of
* this decompressed data. For all other record types, the record data is read and provided as it appears in
* the received response message.
*
* @param[in] aIndex The record index to retrieve.
* @param[out] aRecordInfo A reference to a `RecordInfo` struct to populate from the read record.
*
* @retval kErrorNone The record data was read successfully.
* @retval kErrorNotFound No record in @p aResponse at @p aIndex.
* @retval kErrorParse Could not parse the records.
* @retval kErrorNoBufs The name does not fit in the provided `mNameBufferSize` in @p aRecordInfo, or
* failed to allocate buffer to decompress a DNS name (for PTR, SRV, CNAME).
*/
Error GetRecordInfo(uint16_t aIndex, RecordInfo &aRecordInfo) const
{
return ReadRecordInfo(aIndex, aRecordInfo);
}
};
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
/**
* Initializes the object.
*
@@ -724,6 +831,33 @@ public:
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
/**
* Sends a DNS query for a given record type and name.
*
* The @p aConfig can be `nullptr`. In this case the default config (from `GetDefaultConfig()`) will be used as
* the config for this query. In a non-NULL @p aConfig, some of the fields can be left unspecified (value zero).
* The unspecified fields are then replaced by the values from the default config.
*
* @param[in] aRecordType The resource record type (RRType) to query.
* @param[in] aFirstLabel The first label of the name to be queried (can be NULL if not needed).
* @param[in] aNextLabels The next labels of the name to be queried (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 used with @p aCallback.
* @param[in] aConfig A pointer to the config to use for this query (or NULL for default config).
*
* @retval kErrorNone Query sent successfully. @p aCallback will be invoked to report the outcome.
* @retval kErrorNoBufs Insufficient buffer to prepare and send query.
* @retval kErrorInvalidState Cannot send query since Thread interface is not up.
*/
Error QueryRecord(uint16_t aRecordType,
const char *aFirstLabel,
const char *aNextLabels,
RecordCallback aCallback,
void *aContext,
const QueryConfig *aConfig = nullptr);
#endif
private:
static constexpr uint16_t kMaxCnameAliasNameChanges = 40;
static constexpr uint8_t kLimitedQueryServersArraySize = 3;
@@ -739,6 +873,9 @@ private:
kServiceQuerySrvTxt, // Service instance resolution both SRV and TXT records.
kServiceQuerySrv, // Service instance resolution SRV record only.
kServiceQueryTxt, // Service instance resolution TXT record only.
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
kRecordQuery,
#endif
kNoQuery,
};
@@ -759,6 +896,9 @@ private:
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
BrowseCallback mBrowseCallback;
ServiceCallback mServiceCallback;
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
RecordCallback mRecordCallback;
#endif
};
@@ -776,9 +916,12 @@ private:
QueryConfig mConfig;
uint8_t mTransmissionCount;
bool mShouldResolveHostAddr;
Query *mMainQuery;
Query *mNextQuery;
Message *mSavedResponse;
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
uint16_t mRecordType; // Used only when `mQueryType == kRecordQuery`
#endif
Query *mMainQuery;
Query *mNextQuery;
Message *mSavedResponse;
// Followed by the name (service, host, instance) encoded as a `Dns::Name`.
};
@@ -790,6 +933,7 @@ private:
void UpdateQuery(Query &aQuery, const QueryInfo &aInfo) { aQuery.Write(0, aInfo); }
Query &FindMainQuery(Query &aQuery);
Error SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer);
uint16_t DetermineQuestionRecordType(const QueryInfo &aInfo) const;
void FinalizeQuery(Query &aQuery, Error aError);
void FinalizeQuery(Response &Response, Error aError);
static void GetQueryTypeAndCallback(const Query &aQuery, QueryType &aType, Callback &aCallback, void *&aContext);
@@ -849,18 +993,6 @@ private:
void PrepareTcpMessage(Message &aMessage);
#endif // OPENTHREAD_CONFIG_DNS_CLIENT_OVER_TCP_ENABLE
static const uint8_t kQuestionCount[];
static const uint16_t *const kQuestionRecordTypes[];
static const uint16_t kIp6AddressQueryRecordTypes[];
#if OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE
static const uint16_t kIp4AddressQueryRecordTypes[];
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
static const uint16_t kBrowseQueryRecordTypes[];
static const uint16_t kServiceQueryRecordTypes[];
#endif
static constexpr uint16_t kUdpQueryMaxSize = 512;
using RetryTimer = TimerMilliIn<Client, &Client::HandleTimer>;
@@ -896,6 +1028,11 @@ DefineCoreType(otDnsBrowseResponse, Dns::Client::BrowseResponse);
DefineCoreType(otDnsServiceResponse, Dns::Client::ServiceResponse);
DefineCoreType(otDnsServiceInfo, Dns::Client::ServiceInfo);
#endif
#if OPENTHREAD_CONFIG_DNS_CLIENT_ARBITRARY_RECORD_QUERY_ENABLE
DefineCoreType(otDnsRecordResponse, Dns::Client::RecordResponse);
DefineCoreType(otDnsRecordInfo, Dns::Client::RecordInfo);
DefineMapEnum(otDnsRecordSection, Dns::Client::RecordInfo::RecordSection);
#endif
} // namespace ot
+2
View File
@@ -1267,6 +1267,7 @@ public:
// Resource Record Types.
static constexpr uint16_t kTypeZero = 0; ///< Zero as special indicator for the SIG RR (SIG(0) from RFC 2931).
static constexpr uint16_t kTypeA = 1; ///< Address record (IPv4).
static constexpr uint16_t kTypeNs = 2; ///< NS record (an authoritative name server).
static constexpr uint16_t kTypeSoa = 6; ///< Start of (zone of) authority.
static constexpr uint16_t kTypeCname = 5; ///< CNAME record.
static constexpr uint16_t kTypePtr = 12; ///< PTR record.
@@ -1275,6 +1276,7 @@ public:
static constexpr uint16_t kTypeKey = 25; ///< KEY record.
static constexpr uint16_t kTypeAaaa = 28; ///< IPv6 address record.
static constexpr uint16_t kTypeSrv = 33; ///< SRV locator record.
static constexpr uint16_t kTypeDname = 39; ///< DNAME record.
static constexpr uint16_t kTypeOpt = 41; ///< Option record.
static constexpr uint16_t kTypeNsec = 47; ///< NSEC record.
static constexpr uint16_t kTypeAny = 255; ///< ANY record.