From b3c4a7e58331e5720d3ce0c2baddaaa43b43fcbe Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 28 Apr 2025 12:55:47 -0700 Subject: [PATCH] [dnssd] support ANY record type queries (#11447) This commit updates the DNS-SD `Server` implementation to support queries for the `ANY` record type. This is supported whether a query is resolved using the SRP server or the OpenThread native Discovery Proxy. When a query is resolved using the SRP server database, all known records that match the query name and type are included in the response (e.g., AAAA and KEY records for a hostname; SRV, TXT and KEY records for a service instance name; and PTR records for service type or sub-type query names). Note that unlike mDNS, where an `ANY` query is expected to elicit all known matching records, in the case of a unicast DNS query for `ANY`, the response is only required to contain at least one matching record, not necessarily all of them. This will be the behavior when the Discovery Proxy is used to resolve a unicast DNS `ANY` query (i.e., once the first answer is received from the Discovery Proxy (mDNS), a response is prepared and sent to the client). The unit tests `test_dns_client` and `test_dnssd_discovery_proxy` are updated to validate the new `ANY` query behavior. --- src/core/net/dnssd_server.cpp | 197 +++++++++++++--------- src/core/net/dnssd_server.hpp | 18 +- tests/unit/test_dns_client.cpp | 115 ++++++++++++- tests/unit/test_dnssd_discovery_proxy.cpp | 169 ++++++++++++++++++- 4 files changed, 412 insertions(+), 87 deletions(-) diff --git a/src/core/net/dnssd_server.cpp b/src/core/net/dnssd_server.cpp index 3cdb1d617..0623eaff0 100644 --- a/src/core/net/dnssd_server.cpp +++ b/src/core/net/dnssd_server.cpp @@ -290,6 +290,21 @@ exit: return; } +bool Server::Questions::IsFor(uint16_t aRrType) const +{ + // Check if any of questions is for `aRrType`. + + return (mFirstRrType == aRrType) || (mSecondRrType == aRrType); +} + +Server::Section Server::Questions::SectionFor(uint16_t aRrType) const +{ + // Determine section to append `aRrType` record based on the + // query questions. + + return (IsFor(aRrType) || IsFor(kRrTypeAny)) ? kAnswerSection : kAdditionalDataSection; +} + Server::ResponseCode Server::Request::ParseQuestions(uint8_t aTestMode, bool &aShouldRespond) { // Parse header and questions from a `Request` query message and @@ -349,7 +364,7 @@ Server::ResponseCode Server::Response::AddQuestionsFrom(const Request &aRequest) // service instance names with dot characters in the instance // label, are appended correctly. - SuccessOrExit(Name(*aRequest.mMessage, sizeof(Header)).AppendTo(*mMessage)); + SuccessOrExit(Name(*aRequest.mMessage, kQueryNameOffset).AppendTo(*mMessage)); // Check the name to include the correct domain name and determine // the domain name offset (for DNS name compression). @@ -398,26 +413,17 @@ Error Server::Response::ParseQueryName(void) offset = sizeof(Header); SuccessOrExit(error = Name::ReadName(*mMessage, offset, name)); - if (mQuestions.IsFor(kRrTypePtr)) - { - // `mOffsets.mServiceName` may be updated as we read labels and if we - // determine that the query name is a sub-type service. - mOffsets.mServiceName = sizeof(Header); - } - else if (mQuestions.IsFor(kRrTypeSrv) || mQuestions.IsFor(kRrTypeTxt)) - { - mOffsets.mInstanceName = sizeof(Header); - } - else - { - mOffsets.mHostName = sizeof(Header); - } + // `mOffsets.mServiceName` may be updated as we read labels and if we + // determine that the query name is a sub-type service. + mOffsets.mServiceName = kQueryNameOffset; + mOffsets.mInstanceName = kQueryNameOffset; + mOffsets.mHostName = kQueryNameOffset; // Read the query name labels one by one to check if the name is // service sub-type and also check that it is sub-domain of the // default domain name and determine its offset - offset = sizeof(Header); + offset = kQueryNameOffset; while (true) { @@ -427,7 +433,8 @@ Error Server::Response::ParseQueryName(void) SuccessOrExit(error = Name::ReadLabel(*mMessage, offset, label, labelLength)); - if (mQuestions.IsFor(kRrTypePtr) && StringMatch(label, kSubLabel, kStringCaseInsensitiveMatch)) + if ((mQuestions.IsFor(kRrTypePtr) || mQuestions.IsFor(kRrTypeAny)) && + StringMatch(label, kSubLabel, kStringCaseInsensitiveMatch)) { mOffsets.mServiceName = offset; } @@ -451,7 +458,7 @@ void Server::Response::ReadQueryName(Name::Buffer &aName) const { Server::ReadQu bool Server::Response::QueryNameMatches(const char *aName) const { return Server::QueryNameMatches(*mMessage, aName); } -Error Server::Response::AppendQueryName(void) { return Name::AppendPointerLabel(sizeof(Header), *mMessage); } +Error Server::Response::AppendQueryName(void) { return Name::AppendPointerLabel(kQueryNameOffset, *mMessage); } #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE Error Server::Response::AppendPtrRecord(const Srp::Server::Service &aService) @@ -514,6 +521,7 @@ Error Server::Response::AppendSrvRecord(const char *aHostName, SrvRecord srvRecord; uint16_t recordOffset; Name::Buffer hostLabels; + uint16_t nameOffset; SuccessOrExit(error = Name::ExtractLabels(aHostName, kDefaultDomainName, hostLabels)); @@ -523,7 +531,8 @@ Error Server::Response::AppendSrvRecord(const char *aHostName, srvRecord.SetWeight(aWeight); srvRecord.SetPort(aPort); - SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mInstanceName, *mMessage)); + nameOffset = mQuestions.IsFor(kRrTypeAny) ? kQueryNameOffset : mOffsets.mInstanceName; + SuccessOrExit(error = Name::AppendPointerLabel(nameOffset, *mMessage)); recordOffset = mMessage->GetLength(); SuccessOrExit(error = mMessage->Append(srvRecord)); @@ -601,6 +610,7 @@ Error Server::Response::AppendAaaaRecord(const Ip6::Address &aAddress, uint32_t { Error error = kErrorNone; AaaaRecord aaaaRecord; + uint16_t nameOffset; VerifyOrExit(!aAddress.IsIp4Mapped()); @@ -608,7 +618,8 @@ Error Server::Response::AppendAaaaRecord(const Ip6::Address &aAddress, uint32_t aaaaRecord.SetTtl(aTtl); aaaaRecord.SetAddress(aAddress); - SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mHostName, *mMessage)); + nameOffset = mQuestions.IsFor(kRrTypeAny) ? kQueryNameOffset : mOffsets.mHostName; + SuccessOrExit(error = Name::AppendPointerLabel(nameOffset, *mMessage)); SuccessOrExit(error = mMessage->Append(aaaaRecord)); IncResourceRecordCount(); @@ -621,6 +632,7 @@ Error Server::Response::AppendARecord(const Ip6::Address &aAddress, uint32_t aTt Error error = kErrorNone; ARecord aRecord; Ip4::Address ip4Address; + uint16_t nameOffset; SuccessOrExit(ip4Address.ExtractFromIp4MappedIp6Address(aAddress)); @@ -628,7 +640,8 @@ Error Server::Response::AppendARecord(const Ip6::Address &aAddress, uint32_t aTt aRecord.SetTtl(aTtl); aRecord.SetAddress(ip4Address); - SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mHostName, *mMessage)); + nameOffset = mQuestions.IsFor(kRrTypeAny) ? kQueryNameOffset : mOffsets.mHostName; + SuccessOrExit(error = Name::AppendPointerLabel(nameOffset, *mMessage)); SuccessOrExit(error = mMessage->Append(aRecord)); IncResourceRecordCount(); @@ -653,6 +666,7 @@ Error Server::Response::AppendTxtRecord(const void *aTxtData, uint16_t aTxtLengt { Error error = kErrorNone; TxtRecord txtRecord; + uint16_t nameOffset; uint8_t emptyTxt = 0; if (aTxtLength == 0) @@ -665,7 +679,8 @@ Error Server::Response::AppendTxtRecord(const void *aTxtData, uint16_t aTxtLengt txtRecord.SetTtl(aTtl); txtRecord.SetLength(aTxtLength); - SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mInstanceName, *mMessage)); + nameOffset = mQuestions.IsFor(kRrTypeAny) ? kQueryNameOffset : mOffsets.mInstanceName; + SuccessOrExit(error = Name::AppendPointerLabel(nameOffset, *mMessage)); SuccessOrExit(error = mMessage->Append(txtRecord)); SuccessOrExit(error = mMessage->AppendBytes(aTxtData, aTxtLength)); @@ -705,7 +720,7 @@ Error Server::Response::AppendGenericRecord(uint16_t aRrType, const RecordData & record.Init(aRrType); record.SetTtl(aTtl); - SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mHostName, *mMessage)); + SuccessOrExit(error = Name::AppendPointerLabel(kQueryNameOffset, *mMessage)); recordOffset = mMessage->GetLength(); SuccessOrExit(error = mMessage->Append(record)); @@ -735,6 +750,7 @@ template Error Server::Response::AppendServiceRecords(con if (mSection == kAdditionalDataSection) { + VerifyOrExit(!mQuestions.IsFor(kRrTypeAny)); VerifyOrExit(!(Get().mTestMode & kTestModeEmptyAdditionalSection)); } @@ -788,7 +804,6 @@ Error Server::Response::ResolveBySrp(void) { Error error = kErrorNone; const Srp::Server::Service *matchedService = nullptr; - bool found = false; mSection = kAnswerSection; @@ -801,18 +816,7 @@ Error Server::Response::ResolveBySrp(void) if (QueryNameMatches(host.GetFullName())) { - if (mQuestions.IsFor(kRrTypeAaaa) || mQuestions.IsFor(kRrTypeA)) - { - mSection = mQuestions.SectionFor(kRrTypeAaaa); - SuccessOrExit(error = AppendHostAddresses(host)); - } - - if (mQuestions.IsFor(kRrTypeKey)) - { - mSection = kAnswerSection; - SuccessOrExit(error = AppendKeyRecord(host)); - } - + error = ResolveUsingSrpHost(host); ExitNow(); } @@ -823,52 +827,74 @@ Error Server::Response::ResolveBySrp(void) continue; } - if (mQuestions.IsFor(kRrTypePtr)) + if (QueryNameMatches(service.GetInstanceName())) { - if (QueryNameMatchesService(service)) - { - SuccessOrExit(error = AppendPtrRecord(service)); - matchedService = &service; - } + error = ResolveUsingSrpService(service); + ExitNow(); } - else if (QueryNameMatches(service.GetInstanceName())) - { - matchedService = &service; - found = true; - break; - } - } - if (found) - { - break; + if ((mQuestions.IsFor(kRrTypePtr) || mQuestions.IsFor(kRrTypeAny)) && QueryNameMatchesService(service)) + { + SuccessOrExit(error = AppendPtrRecord(service)); + matchedService = &service; + } } } VerifyOrExit(matchedService != nullptr, error = kErrorNotFound); - if (mQuestions.IsFor(kRrTypePtr)) - { - // Skip adding additional records, when answering a - // PTR query with more than one answer. This is the - // recommended behavior to keep the size of the - // response small. + // We append SRV/TXT/AAAA records in additional section for a PTR + // query when there is only a single matched service. This is the + // recommended behavior to keep the size of the response small. - VerifyOrExit(mHeader.GetAnswerCount() == 1); - } - else + if (mQuestions.IsFor(kRrTypePtr) && (mHeader.GetAnswerCount() == 1)) { - if (mQuestions.IsFor(kRrTypeKey)) - { - mSection = kAnswerSection; - error = AppendKeyRecord(matchedService->GetHost()); - ExitNow(); - } - - VerifyOrExit(mQuestions.IsFor(kRrTypeSrv) || mQuestions.IsFor(kRrTypeTxt)); + error = AppendServiceRecords(*matchedService); } - error = AppendServiceRecords(*matchedService); +exit: + return error; +} + +Error Server::Response::ResolveUsingSrpHost(const Srp::Server::Host &aHost) +{ + // The query name is already checked to match the `aHost` name. + + Error error = kErrorNone; + + if (mQuestions.IsFor(kRrTypeAaaa) || mQuestions.IsFor(kRrTypeA) || mQuestions.IsFor(kRrTypeAny)) + { + mSection = mQuestions.SectionFor(kRrTypeAaaa); + SuccessOrExit(error = AppendHostAddresses(aHost)); + } + + if (mQuestions.IsFor(kRrTypeKey) || mQuestions.IsFor(kRrTypeAny)) + { + mSection = kAnswerSection; + SuccessOrExit(error = AppendKeyRecord(aHost)); + } + +exit: + return error; +} + +Error Server::Response::ResolveUsingSrpService(const Srp::Server::Service &aService) +{ + // The query name is already checked to match the + // `aService` instance name. + + Error error = kErrorNone; + + if (mQuestions.IsFor(kRrTypeKey) || mQuestions.IsFor(kRrTypeAny)) + { + mSection = kAnswerSection; + SuccessOrExit(error = AppendKeyRecord(aService.GetHost())); + } + + if (mQuestions.IsFor(kRrTypeSrv) || mQuestions.IsFor(kRrTypeTxt) || mQuestions.IsFor(kRrTypeAny)) + { + SuccessOrExit(error = AppendServiceRecords(aService)); + } exit: return error; @@ -1573,6 +1599,7 @@ void Server::DiscoveryProxy::ReadNameFor(ProxyAction aAction, case kNoAction: break; case kBrowsing: + case kQueryingRecord: ReadQueryName(aQuery, aName); break; case kResolvingSrv: @@ -1581,7 +1608,6 @@ void Server::DiscoveryProxy::ReadNameFor(ProxyAction aAction, break; case kResolvingIp6Address: case kResolvingIp4Address: - case kQueryingRecord: ReadQueryHostName(aQuery, aInfo, aName); break; } @@ -1819,7 +1845,7 @@ void Server::DiscoveryProxy::StartOrStopRecordQuerier(Command aCom Dnssd::RecordQuerier querier; Name::LabelBuffer firstLabel; Name::Buffer nextLabels; - uint16_t offset = aInfo.mOffsets.mHostName; + uint16_t offset = sizeof(Header); uint8_t labelLength = sizeof(firstLabel); IgnoreError(Dns::Name::ReadLabel(aQuery, offset, firstLabel, labelLength)); @@ -1847,12 +1873,14 @@ bool Server::DiscoveryProxy::QueryMatches(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, ProxyAction aAction, const Name::Buffer &aName, - uint16_t aQuerierRrType) const + uint16_t aQuerierRrType, + RrTypeMatchMode aRrTypeMatchMode) const { // Check whether `aQuery` is performing `aAction` and - // its name matches `aName`. The `aQuerierRrType` is only - // used when the action is `kQueryingRecord` to indicate - // which record is being queried. + // its name matches `aName`. The `aQuerierRrType` and + // `aRrTypeMatchMode` are only used when the action is + // `kQueryingRecord` to indicate queried record type and + // how to determine a match. bool matches = false; @@ -1860,12 +1888,22 @@ bool Server::DiscoveryProxy::QueryMatches(const ProxyQuery &aQuery, if (aAction == kQueryingRecord) { - VerifyOrExit(aInfo.mQuestions.IsFor(aQuerierRrType)); + switch (aRrTypeMatchMode) + { + case kRequireExactMatch: + VerifyOrExit(aInfo.mQuestions.IsFor(aQuerierRrType)); + break; + + case kPermitAnyOrExactMatch: + VerifyOrExit(aInfo.mQuestions.IsFor(aQuerierRrType) || aInfo.mQuestions.IsFor(kRrTypeAny)); + break; + } } switch (aAction) { case kBrowsing: + case kQueryingRecord: VerifyOrExit(QueryNameMatches(aQuery, aName)); break; case kResolvingSrv: @@ -1874,7 +1912,6 @@ bool Server::DiscoveryProxy::QueryMatches(const ProxyQuery &aQuery, break; case kResolvingIp6Address: case kResolvingIp4Address: - case kQueryingRecord: VerifyOrExit(QueryHostNameMatches(aQuery, aInfo, aName)); break; case kNoAction: @@ -1902,7 +1939,7 @@ bool Server::DiscoveryProxy::HasActive(ProxyAction aAction, const Name::Buffer & info.ReadFrom(query); - if (QueryMatches(query, info, aAction, aName, aQuerierRrType)) + if (QueryMatches(query, info, aAction, aName, aQuerierRrType, kRequireExactMatch)) { has = true; break; @@ -2105,7 +2142,7 @@ void Server::DiscoveryProxy::HandleResult(ProxyAction aAction, info.ReadFrom(query); - if (!QueryMatches(query, info, aAction, aName, querierRrType)) + if (!QueryMatches(query, info, aAction, aName, querierRrType, kPermitAnyOrExactMatch)) { continue; } diff --git a/src/core/net/dnssd_server.hpp b/src/core/net/dnssd_server.hpp index 5fb2b26e5..27e46e0d7 100644 --- a/src/core/net/dnssd_server.hpp +++ b/src/core/net/dnssd_server.hpp @@ -319,6 +319,7 @@ private: static constexpr uint16_t kRrTypeKey = ResourceRecord::kTypeKey; static constexpr uint16_t kRrTypeAaaa = ResourceRecord::kTypeAaaa; static constexpr uint16_t kRrTypeSrv = ResourceRecord::kTypeSrv; + static constexpr uint16_t kRrTypeAny = ResourceRecord::kTypeAny; typedef Header::Response ResponseCode; @@ -356,8 +357,8 @@ private: { Questions(void) { mFirstRrType = 0, mSecondRrType = 0; } - bool IsFor(uint16_t aRrType) const { return (mFirstRrType == aRrType) || (mSecondRrType == aRrType); } - Section SectionFor(uint16_t aRrType) const { return IsFor(aRrType) ? kAnswerSection : kAdditionalDataSection; } + bool IsFor(uint16_t aRrType) const; + Section SectionFor(uint16_t aRrType) const; uint16_t mFirstRrType; uint16_t mSecondRrType; @@ -403,6 +404,8 @@ private: class Response : public InstanceLocator, private NonCopyable { public: + static constexpr uint16_t kQueryNameOffset = sizeof(Header); + explicit Response(Instance &aInstance); ResponseCode AddQuestionsFrom(const Request &aRequest); @@ -435,6 +438,8 @@ private: Error ExtractServiceInstanceLabel(const char *aInstanceName, Name::LabelBuffer &aLabel); #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE Error ResolveBySrp(void); + Error ResolveUsingSrpHost(const Srp::Server::Host &aHost); + Error ResolveUsingSrpService(const Srp::Server::Service &aService); bool QueryNameMatchesService(const Srp::Server::Service &aService) const; Error AppendPtrRecord(const Srp::Server::Service &aService); Error AppendSrvRecord(const Srp::Server::Service &aService); @@ -497,6 +502,12 @@ private: kStop, }; + enum RrTypeMatchMode : uint8_t + { + kRequireExactMatch, // Require record type to match exactly. + kPermitAnyOrExactMatch, // Permit ANY record type in addition to exact match. + }; + typedef Error (Response::*ResponseAppender)(const ProxyResult &aResult); void Perform(ProxyAction aAction, ProxyQuery &aQuery, ProxyQueryInfo &aInfo); @@ -506,7 +517,8 @@ private: const ProxyQueryInfo &aInfo, ProxyAction aAction, const Name::Buffer &aName, - uint16_t aQuerierRrType) const; + uint16_t aQuerierRrType, + RrTypeMatchMode aRrTypeMatchMode) const; void UpdateProxy(Command aCommand, ProxyAction aAction, const ProxyQuery &aQuery, diff --git a/tests/unit/test_dns_client.cpp b/tests/unit/test_dns_client.cpp index 62118adc4..63e790a48 100644 --- a/tests/unit/test_dns_client.cpp +++ b/tests/unit/test_dns_client.cpp @@ -823,7 +823,7 @@ void TestDnsClient(void) VerifyOrQuit(sAddressInfo.mError != kErrorNone); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate DNS Client `QueryRecord()` for host name + // Validate DNS Client `QueryRecord()` for host name and KEY record sQueryRecordInfo.Reset(); Log("QueryRecord(%s) for KEY RR", kHostFullName); @@ -850,6 +850,43 @@ void TestDnsClient(void) SuccessOrQuit(sQueryRecordInfo.mError); VerifyOrQuit(sQueryRecordInfo.mNumRecords == 0); + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Validate DNS Client `QueryRecord()` for host name and ANY record + + sQueryRecordInfo.Reset(); + Log("QueryRecord(%s) for ANY RR", kHostFullName); + SuccessOrQuit(dnsClient->QueryRecord(Dns::ResourceRecord::kTypeAny, kHostName, "default.service.arpa.", + RecordCallback, sInstance)); + AdvanceTime(100); + VerifyOrQuit(sQueryRecordInfo.mCallbackCount == 1); + SuccessOrQuit(sQueryRecordInfo.mError); + VerifyOrQuit(sQueryRecordInfo.mNumRecords == 3); + + for (uint8_t index = 0; index < 3; index++) + { + const QueryRecordInfo::Record &record = sQueryRecordInfo.mRecords[index]; + + VerifyOrQuit(StringMatch(record.mNameBuffer, kHostFullName)); + VerifyOrQuit(MapEnum(record.mSection) == Dns::Client::RecordInfo::kSectionAnswer); + VerifyOrQuit(record.mTtl > 0); + + if (record.mRecordType == Dns::ResourceRecord::kTypeKey) + { + VerifyOrQuit(record.mRecordLength == sizeof(Dns::Ecdsa256KeyRecord)); + VerifyOrQuit(record.mDataBufferSize == sizeof(Dns::Ecdsa256KeyRecord)); + } + else if (record.mRecordType == Dns::ResourceRecord::kTypeAaaa) + { + VerifyOrQuit(record.mRecordLength == sizeof(Ip6::Address)); + VerifyOrQuit(addresses.Contains(*reinterpret_cast(record.mDataBuffer))); + } + else + { + // Unexpected record type. + VerifyOrQuit(false); + } + } + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate DNS Client `QueryRecord()` for service instance name and KEY record @@ -925,6 +962,41 @@ void TestDnsClient(void) } } + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Validate DNS Client `QueryRecord()` for service instance name and ANY record + + sQueryRecordInfo.Reset(); + Log("QueryRecord(%s) for ANY record", kInstance1FullName); + SuccessOrQuit(dnsClient->QueryRecord(Dns::ResourceRecord::kTypeAny, kInstance1Label, kService1FullName, + RecordCallback, sInstance)); + AdvanceTime(100); + + VerifyOrQuit(sQueryRecordInfo.mCallbackCount == 1); + SuccessOrQuit(sQueryRecordInfo.mError); + VerifyOrQuit(sQueryRecordInfo.mNumRecords == 3); + + for (uint8_t index = 1; index < 3; index++) + { + const QueryRecordInfo::Record &record = sQueryRecordInfo.mRecords[index]; + + VerifyOrQuit(StringMatch(record.mNameBuffer, kInstance1FullName)); + VerifyOrQuit(record.mRecordLength > 0); + VerifyOrQuit(record.mTtl > 0); + VerifyOrQuit(record.mDataBufferSize == record.mRecordLength); + VerifyOrQuit(MapEnum(record.mSection) == Dns::Client::RecordInfo::kSectionAnswer); + + switch (record.mRecordType) + { + case Dns::ResourceRecord::kTypeKey: + case Dns::ResourceRecord::kTypeTxt: + case Dns::ResourceRecord::kTypeSrv: + break; + default: + VerifyOrQuit(false); + break; + } + } + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate DNS Client `QueryRecord()` for PTR record @@ -976,6 +1048,47 @@ void TestDnsClient(void) } } + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Validate DNS Client `QueryRecord()` for service name and ANY record + + sQueryRecordInfo.Reset(); + Log("QueryRecord(%s) for ANY record", kInstance1FullName); + SuccessOrQuit(dnsClient->QueryRecord(Dns::ResourceRecord::kTypeAny, "_srv", "_udp.default.service.arpa.", + RecordCallback, sInstance)); + AdvanceTime(100); + + VerifyOrQuit(sQueryRecordInfo.mCallbackCount == 1); + SuccessOrQuit(sQueryRecordInfo.mError); + VerifyOrQuit(sQueryRecordInfo.mNumRecords == 1); + + VerifyOrQuit(StringMatch(sQueryRecordInfo.mRecords[0].mNameBuffer, kService1FullName)); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mRecordType == Dns::ResourceRecord::kTypePtr); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mRecordLength > 0); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mTtl > 0); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mDataBufferSize == sQueryRecordInfo.mRecords[0].mRecordLength); + VerifyOrQuit(MapEnum(sQueryRecordInfo.mRecords[0].mSection) == Dns::Client::RecordInfo::kSectionAnswer); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Validate DNS Client `QueryRecord()` for sub-type service name and ANY record + + sQueryRecordInfo.Reset(); + Log("QueryRecord(%s) for ANY record", kService2SubTypeFullName); + + SuccessOrQuit(dnsClient->QueryRecord(Dns::ResourceRecord::kTypeAny, "_best", + "_sub._game._udp.default.service.arpa.", RecordCallback, sInstance)); + AdvanceTime(100); + + VerifyOrQuit(sQueryRecordInfo.mCallbackCount == 1); + SuccessOrQuit(sQueryRecordInfo.mError); + VerifyOrQuit(sQueryRecordInfo.mNumRecords == 1); + + VerifyOrQuit(StringMatch(sQueryRecordInfo.mRecords[0].mNameBuffer, kService2SubTypeFullName)); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mRecordType == Dns::ResourceRecord::kTypePtr); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mRecordLength > 0); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mTtl > 0); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mDataBufferSize == sQueryRecordInfo.mRecords[0].mRecordLength); + VerifyOrQuit(MapEnum(sQueryRecordInfo.mRecords[0].mSection) == Dns::Client::RecordInfo::kSectionAnswer); + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate DNS Client `Browse()` diff --git a/tests/unit/test_dnssd_discovery_proxy.cpp b/tests/unit/test_dnssd_discovery_proxy.cpp index 3d5a97418..8daf7dab8 100644 --- a/tests/unit/test_dnssd_discovery_proxy.cpp +++ b/tests/unit/test_dnssd_discovery_proxy.cpp @@ -687,7 +687,8 @@ struct RecordQuerierInfo : public Clearable mCallCount++; CopyString(mFirstLabel, aQuerier->mFirstLabel); CopyString(mNextLabels, aQuerier->mNextLabels); - mCallback = aQuerier->mCallback; + mRecordType = aQuerier->mRecordType; + mCallback = aQuerier->mCallback; } uint16_t mCallCount; @@ -1621,7 +1622,7 @@ void TestProxyBasic(void) ResetPlatDnssdApiInfo(); sQueryRecordInfo.Reset(); - Log("QueryRecord()"); + Log("QueryRecord() for KEY and hostname"); SuccessOrQuit(dnsClient->QueryRecord(Dns::ResourceRecord::kTypeKey, "shield", "default.service.arpa.", RecordCallback, sInstance)); AdvanceTime(10); @@ -1642,6 +1643,7 @@ void TestProxyBasic(void) VerifyOrQuit(sStopRecordQuerierInfo.mCallCount == 0); VerifyOrQuit(sStartRecordQuerierInfo.NameMatches("shield", nullptr)); + VerifyOrQuit(sStartRecordQuerierInfo.mRecordType == Dns::ResourceRecord::kTypeKey); VerifyOrQuit(sQueryRecordInfo.mCallbackCount == 0); @@ -1676,6 +1678,7 @@ void TestProxyBasic(void) VerifyOrQuit(sStopRecordQuerierInfo.mCallCount == 1); VerifyOrQuit(sStopRecordQuerierInfo.NameMatches("shield", nullptr)); + VerifyOrQuit(sStopRecordQuerierInfo.mRecordType == Dns::ResourceRecord::kTypeKey); VerifyOrQuit(sStopRecordQuerierInfo.mCallback == sStartRecordQuerierInfo.mCallback); // Check that response is sent to client and validate it @@ -1699,7 +1702,7 @@ void TestProxyBasic(void) ResetPlatDnssdApiInfo(); sQueryRecordInfo.Reset(); - Log("QueryRecord()"); + Log("QueryRecord() for KEY and service instance name"); SuccessOrQuit(dnsClient->QueryRecord(Dns::ResourceRecord::kTypeKey, "iron.man", "_avenger._udp.default.service.arpa.", RecordCallback, sInstance)); AdvanceTime(10); @@ -1720,6 +1723,7 @@ void TestProxyBasic(void) VerifyOrQuit(sStopRecordQuerierInfo.mCallCount == 0); VerifyOrQuit(sStartRecordQuerierInfo.NameMatches("iron.man", "_avenger._udp")); + VerifyOrQuit(sStartRecordQuerierInfo.mRecordType == Dns::ResourceRecord::kTypeKey); VerifyOrQuit(sQueryRecordInfo.mCallbackCount == 0); @@ -1755,6 +1759,7 @@ void TestProxyBasic(void) VerifyOrQuit(sStopRecordQuerierInfo.NameMatches("iron.man", "_avenger._udp")); VerifyOrQuit(sStopRecordQuerierInfo.mCallback == sStartRecordQuerierInfo.mCallback); + VerifyOrQuit(sStopRecordQuerierInfo.mRecordType == Dns::ResourceRecord::kTypeKey); // Check that response is sent to client and validate it @@ -1851,6 +1856,164 @@ void TestProxyBasic(void) VerifyOrQuit(!memcmp(sQueryRecordInfo.mRecords[0].mDataBuffer, kTranslatedCnameData, sizeof(kTranslatedCnameData))); VerifyOrQuit(MapEnum(sQueryRecordInfo.mRecords[0].mSection) == Dns::Client::RecordInfo::kSectionAnswer); + Log("--------------------------------------------------------------------------------------------"); + + ResetPlatDnssdApiInfo(); + sQueryRecordInfo.Reset(); + + Log("QueryRecord() for ANY and host name"); + SuccessOrQuit(dnsClient->QueryRecord(Dns::ResourceRecord::kTypeAny, "shield", "default.service.arpa.", + RecordCallback, sInstance)); + AdvanceTime(10); + + // Check that a record querier is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp4AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp4AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartRecordQuerierInfo.mCallCount == 1); + VerifyOrQuit(sStopRecordQuerierInfo.mCallCount == 0); + + VerifyOrQuit(sStartRecordQuerierInfo.NameMatches("shield", nullptr)); + VerifyOrQuit(sStartRecordQuerierInfo.mRecordType == Dns::ResourceRecord::kTypeAny); + + VerifyOrQuit(sQueryRecordInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Record Querier callback"); + + recordResult.mFirstLabel = "shield"; + recordResult.mNextLabels = nullptr; + recordResult.mRecordType = Dns::ResourceRecord::kTypeKey; + recordResult.mRecordData = kKeyData; + recordResult.mRecordDataLength = sizeof(kKeyData); + recordResult.mTtl = kTtl; + recordResult.mInfraIfIndex = kInfraIfIndex; + + InvokeRecordQuerierCallback(sStartRecordQuerierInfo.mCallback, recordResult); + + AdvanceTime(10); + + // Check that the record querier is stopped + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp4AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp4AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartRecordQuerierInfo.mCallCount == 1); + VerifyOrQuit(sStopRecordQuerierInfo.mCallCount == 1); + + VerifyOrQuit(sStopRecordQuerierInfo.NameMatches("shield", nullptr)); + VerifyOrQuit(sStopRecordQuerierInfo.mCallback == sStartRecordQuerierInfo.mCallback); + + // Check that response is sent to client and validate it + + VerifyOrQuit(sQueryRecordInfo.mCallbackCount == 1); + SuccessOrQuit(sQueryRecordInfo.mError); + + VerifyOrQuit(!strcmp(sQueryRecordInfo.mQueryName, "shield.default.service.arpa.")); + VerifyOrQuit(sQueryRecordInfo.mNumRecords == 1); + + VerifyOrQuit(!strcmp(sQueryRecordInfo.mRecords[0].mNameBuffer, "shield.default.service.arpa.")); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mRecordType == Dns::ResourceRecord::kTypeKey); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mRecordLength == sizeof(kKeyData)); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mTtl == kTtl); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mDataBufferSize == sizeof(kKeyData)); + VerifyOrQuit(!memcmp(sQueryRecordInfo.mRecords[0].mDataBuffer, kKeyData, sizeof(kKeyData))); + VerifyOrQuit(MapEnum(sQueryRecordInfo.mRecords[0].mSection) == Dns::Client::RecordInfo::kSectionAnswer); + + Log("--------------------------------------------------------------------------------------------"); + + ResetPlatDnssdApiInfo(); + sQueryRecordInfo.Reset(); + + Log("QueryRecord() for ANY and service instance name"); + SuccessOrQuit(dnsClient->QueryRecord(Dns::ResourceRecord::kTypeAny, "iron.man", + "_avenger._udp.default.service.arpa.", RecordCallback, sInstance)); + AdvanceTime(10); + + // Check that a record querier is started + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp4AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp4AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartRecordQuerierInfo.mCallCount == 1); + VerifyOrQuit(sStopRecordQuerierInfo.mCallCount == 0); + + VerifyOrQuit(sStartRecordQuerierInfo.NameMatches("iron.man", "_avenger._udp")); + VerifyOrQuit(sStartRecordQuerierInfo.mRecordType == Dns::ResourceRecord::kTypeAny); + + VerifyOrQuit(sQueryRecordInfo.mCallbackCount == 0); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("Invoke Record Querier callback"); + + recordResult.mFirstLabel = "iron.man"; + recordResult.mNextLabels = "_avenger._udp"; + recordResult.mRecordType = Dns::ResourceRecord::kTypeKey; + recordResult.mRecordData = kKeyData; + recordResult.mRecordDataLength = sizeof(kKeyData); + recordResult.mTtl = kTtl; + recordResult.mInfraIfIndex = kInfraIfIndex; + + InvokeRecordQuerierCallback(sStartRecordQuerierInfo.mCallback, recordResult); + + AdvanceTime(10); + + // Check that the record querier is stopped + + VerifyOrQuit(sStartBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStopBrowserInfo.mCallCount == 0); + VerifyOrQuit(sStartSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopSrvResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopTxtResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp6AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartIp4AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStopIp4AddrResolverInfo.mCallCount == 0); + VerifyOrQuit(sStartRecordQuerierInfo.mCallCount == 1); + VerifyOrQuit(sStopRecordQuerierInfo.mCallCount == 1); + + VerifyOrQuit(sStopRecordQuerierInfo.NameMatches("iron.man", "_avenger._udp")); + VerifyOrQuit(sStopRecordQuerierInfo.mCallback == sStartRecordQuerierInfo.mCallback); + + // Check that response is sent to client and validate it + + VerifyOrQuit(sQueryRecordInfo.mCallbackCount == 1); + SuccessOrQuit(sQueryRecordInfo.mError); + + VerifyOrQuit(!strcmp(sQueryRecordInfo.mQueryName, "iron.man._avenger._udp.default.service.arpa.")); + VerifyOrQuit(sQueryRecordInfo.mNumRecords == 1); + + VerifyOrQuit(!strcmp(sQueryRecordInfo.mRecords[0].mNameBuffer, "iron.man._avenger._udp.default.service.arpa.")); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mRecordType == Dns::ResourceRecord::kTypeKey); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mRecordLength == sizeof(kKeyData)); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mTtl == kTtl); + VerifyOrQuit(sQueryRecordInfo.mRecords[0].mDataBufferSize == sizeof(kKeyData)); + VerifyOrQuit(!memcmp(sQueryRecordInfo.mRecords[0].mDataBuffer, kKeyData, sizeof(kKeyData))); + VerifyOrQuit(MapEnum(sQueryRecordInfo.mRecords[0].mSection) == Dns::Client::RecordInfo::kSectionAnswer); + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); Log("Stop DNS-SD server");