diff --git a/src/core/net/dnssd_server.cpp b/src/core/net/dnssd_server.cpp index 2a44178d1..8520fd958 100644 --- a/src/core/net/dnssd_server.cpp +++ b/src/core/net/dnssd_server.cpp @@ -51,6 +51,10 @@ const char Server::kMdnsDomainName[] = "local."; const char *Server::kBlockedDomains[] = {"ipv4only.arpa."}; #endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE || OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE +const char Server::kSoaRnameLabel[] = "postmaster"; +#endif + Server::Server(Instance &aInstance) : InstanceLocator(aInstance) , mSocket(aInstance, *this) @@ -64,6 +68,10 @@ Server::Server(Instance &aInstance) , mTestMode(kTestModeDisabled) { mCounters.Clear(); + +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE || OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + ClearAllBytes(mSoaServerName); +#endif } Error Server::Start(void) @@ -79,6 +87,10 @@ Error Server::Start(void) Get().HandleDnssdServerStateChange(); #endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE || OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + ConstructSoaServerName(); +#endif + LogInfo("Started"); #if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE @@ -187,6 +199,13 @@ void Server::ProcessQuery(Request &aRequest) response.Log(); #endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE || OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + // Try to resolve SOA/NS queries. If successfully + // resolved, the response will not be empty. + VerifyOrExit(response.ResolveSoaOrNsQuery() == kErrorNone, rcode = Header::kResponseServerFailure); + VerifyOrExit(response.IsEmpty()); +#endif + #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE switch (response.ResolveBySrp()) { @@ -408,6 +427,12 @@ exit: return rcode; } +bool Server::Response::IsEmpty(void) const +{ + return (mHeader.GetAnswerCount() == 0) && (mHeader.GetAuthorityRecordCount() == 0) && + (mHeader.GetAdditionalRecordCount() == 0); +} + Error Server::Response::ParseQueryName(void) { // Parses the query name, determines name compression @@ -439,14 +464,6 @@ Error Server::Response::ParseQueryName(void) uint8_t labelLength = sizeof(label); uint16_t comapreOffset; - SuccessOrExit(error = Name::ReadLabel(*mMessage, offset, label, labelLength)); - - if ((mQuestions.IsFor(kRrTypePtr) || mQuestions.IsFor(kRrTypeAny)) && - StringMatch(label, kSubLabel, kStringCaseInsensitiveMatch)) - { - mOffsets.mServiceName = offset; - } - comapreOffset = offset; if (Name::CompareName(*mMessage, comapreOffset, kDefaultDomainName) == kErrorNone) @@ -454,9 +471,15 @@ Error Server::Response::ParseQueryName(void) mOffsets.mDomainName = offset; ExitNow(); } - } - error = kErrorParse; + SuccessOrExit(error = Name::ReadLabel(*mMessage, offset, label, labelLength)); + + if ((mQuestions.IsFor(kRrTypePtr) || mQuestions.IsFor(kRrTypeAny)) && + StringMatch(label, kSubLabel, kStringCaseInsensitiveMatch)) + { + mOffsets.mServiceName = offset; + } + } exit: return error; @@ -466,6 +489,11 @@ void Server::Response::ReadQueryName(Name::Buffer &aName) const { Server::ReadQu bool Server::Response::QueryNameMatches(const char *aName) const { return Server::QueryNameMatches(*mMessage, aName); } +bool Server::Response::QueryNameIsForDomain(const char *aDomainName) const +{ + return Server::QueryNameIsForDomain(*mMessage, aDomainName); +} + Error Server::Response::AppendQueryName(void) { return Name::AppendPointerLabel(kQueryNameOffset, *mMessage); } #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE @@ -786,6 +814,9 @@ void Server::Response::IncResourceRecordCount(void) case kAnswerSection: mHeader.SetAnswerCount(mHeader.GetAnswerCount() + 1); break; + case kAuthoritySection: + mHeader.SetAuthorityRecordCount(mHeader.GetAuthorityRecordCount() + 1); + break; case kAdditionalDataSection: mHeader.SetAdditionalRecordCount(mHeader.GetAdditionalRecordCount() + 1); break; @@ -929,6 +960,149 @@ exit: #endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE || OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + +void Server::ConstructSoaServerName(void) +{ + if (mSoaServerName[0] == kNullChar) + { + StringWriter writer(mSoaServerName, sizeof(mSoaServerName)); + + writer.Append("otDNS%s", Get().GetExtAddress().ToString().AsCString()); + } +} + +Error Server::Response::ResolveSoaOrNsQuery(void) +{ + Error error = kErrorNone; + + VerifyOrExit(QueryNameIsForDomain(kDefaultDomainName)); + + mSection = kAnswerSection; + + // Handle AAAA (or ANY) query for the server host name used for + // SOA/NS answer. + + if (mQuestions.IsFor(kRrTypeAaaa) || mQuestions.IsFor(kRrTypeAny)) + { + Name::Buffer serverFullName; + + ConstructFullName(Get().mSoaServerName, serverFullName); + + if (QueryNameMatches(serverFullName)) + { + for (Ip6::Netif::UnicastAddress &unicastAddr : Get().GetUnicastAddresses()) + { + if (!unicastAddr.mValid || !unicastAddr.mPreferred || unicastAddr.GetAddress().IsLinkLocalUnicast() || + Get().IsMeshLocalAddress(unicastAddr.GetAddress())) + { + continue; + } + + SuccessOrExit(error = AppendAaaaRecord(unicastAddr.GetAddress(), kServerAaaaTtl)); + } + + if (IsEmpty()) + { + SuccessOrExit(error = AppendAaaaRecord(Get().GetMeshLocalEid(), kServerAaaaTtl)); + } + + ExitNow(); + } + } + + // Handle SOA or NS (or ANY) queries. The SOA or NS query should + // be for the domain name (`default.service.arpa`). If it is for a + // name under the default domain, e.g. `host1.default.service.arpa`, + // we provide the SOA record in Authority section in the response. + + if (!QueryNameMatches(kDefaultDomainName)) + { + VerifyOrExit(mQuestions.IsFor(kRrTypeSoa) || mQuestions.IsFor(kRrTypeNs)); + mSection = kAuthoritySection; + ExitNow(error = AppendSoaRecord()); + } + + if (mQuestions.IsFor(kRrTypeSoa) || mQuestions.IsFor(kRrTypeAny)) + { + SuccessOrExit(error = AppendSoaRecord()); + } + + if (mQuestions.IsFor(kRrTypeNs) || mQuestions.IsFor(kRrTypeAny)) + { + SuccessOrExit(error = AppendNsRecord()); + } + +exit: + return error; +} + +Error Server::Response::AppendSoaRecord(void) +{ + const uint32_t kFields[] = { + kSoaSerial, kSoaRefresh, kSoaRetry, kSoaExpire, kSoaMinimum, + }; + + Error error = kErrorNone; + ResourceRecord record; + uint16_t offset; + + record.Init(kRrTypeSoa); + record.SetTtl(kSoaTtl); + + SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mDomainName, *mMessage)); + + offset = mMessage->GetLength(); + SuccessOrExit(error = mMessage->Append(record)); + + // MNAME + SuccessOrExit(error = Name::AppendLabel(Get().mSoaServerName, *mMessage)); + SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mDomainName, *mMessage)); + + // RNAME: Use constant "postmaster." + SuccessOrExit(error = Name::AppendLabel(kSoaRnameLabel, *mMessage)); + SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mDomainName, *mMessage)); + + for (uint32_t fieldValue : kFields) + { + SuccessOrExit(error = mMessage->Append(BigEndian::HostSwap32(fieldValue))); + } + + ResourceRecord::UpdateRecordLengthInMessage(*mMessage, offset); + + IncResourceRecordCount(); + +exit: + return error; +} + +Error Server::Response::AppendNsRecord(void) +{ + Error error = kErrorNone; + ResourceRecord record; + uint16_t offset; + + record.Init(kRrTypeNs); + record.SetTtl(kNsTtl); + + SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mDomainName, *mMessage)); + + offset = mMessage->GetLength(); + SuccessOrExit(error = mMessage->Append(record)); + + SuccessOrExit(error = Name::AppendLabel(Get().mSoaServerName, *mMessage)); + SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mDomainName, *mMessage)); + + ResourceRecord::UpdateRecordLengthInMessage(*mMessage, offset); + + IncResourceRecordCount(); + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE || OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE bool Server::ShouldForwardToUpstream(const Request &aRequest) const { @@ -1096,6 +1270,14 @@ bool Server::QueryNameMatches(const Message &aQuery, const char *aName) return (Name::CompareName(aQuery, offset, aName) == kErrorNone); } +bool Server::QueryNameIsForDomain(const Message &aQuery, const char *aDomainName) +{ + Name::Buffer name; + + ReadQueryName(aQuery, name); + return Name::IsSubDomainOf(name, aDomainName); +} + void Server::ReadQueryInstanceName(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, Name::Buffer &aName) { uint16_t offset = aInfo.mOffsets.mInstanceName; diff --git a/src/core/net/dnssd_server.hpp b/src/core/net/dnssd_server.hpp index 27e46e0d7..c6539cab2 100644 --- a/src/core/net/dnssd_server.hpp +++ b/src/core/net/dnssd_server.hpp @@ -312,6 +312,7 @@ private: static constexpr uint16_t kMaxConcurrentUpstreamQueries = 32; static constexpr uint16_t kRrTypeA = ResourceRecord::kTypeA; + static constexpr uint16_t kRrTypeNs = ResourceRecord::kTypeNs; static constexpr uint16_t kRrTypeSoa = ResourceRecord::kTypeSoa; static constexpr uint16_t kRrTypeCname = ResourceRecord::kTypeCname; static constexpr uint16_t kRrTypePtr = ResourceRecord::kTypePtr; @@ -321,6 +322,16 @@ private: static constexpr uint16_t kRrTypeSrv = ResourceRecord::kTypeSrv; static constexpr uint16_t kRrTypeAny = ResourceRecord::kTypeAny; + // Recommended values for SOA record (RFC 8766 section 6.1). + static constexpr uint32_t kSoaSerial = 0; + static constexpr uint32_t kSoaRefresh = 7200; + static constexpr uint32_t kSoaRetry = 3600; + static constexpr uint32_t kSoaExpire = 86400; + static constexpr uint32_t kSoaMinimum = 10; + static constexpr uint32_t kSoaTtl = 7200; + static constexpr uint32_t kNsTtl = 7200; + static constexpr uint32_t kServerAaaaTtl = 3600; + typedef Header::Response ResponseCode; typedef Message ProxyQuery; @@ -329,6 +340,7 @@ private: enum Section : uint8_t { kAnswerSection, + kAuthoritySection, kAdditionalDataSection, }; @@ -412,9 +424,11 @@ private: Error AllocateAndInitFrom(const Request &aRequest); void InitFrom(ProxyQuery &aQuery, const ProxyQueryInfo &aInfo); void SetResponseCode(ResponseCode aResponseCode) { mHeader.SetResponseCode(aResponseCode); } + bool IsEmpty(void) const; Error ParseQueryName(void); void ReadQueryName(Name::Buffer &aName) const; bool QueryNameMatches(const char *aName) const; + bool QueryNameIsForDomain(const char *aDomainName) const; Error AppendQueryName(void); Error AppendPtrRecord(const char *aInstanceLabel, uint32_t aTtl); Error AppendSrvRecord(const ServiceInstanceInfo &aInstanceInfo); @@ -455,6 +469,11 @@ private: Error AppendHostIp6Addresses(const ProxyResult &aResult); Error AppendHostIp4Addresses(const ProxyResult &aResult); Error AppendGenericRecord(const ProxyResult &aResult); +#endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE || OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + Error ResolveSoaOrNsQuery(void); + Error AppendSoaRecord(void); + Error AppendNsRecord(void); #endif template Error AppendServiceRecords(const ServiceType &aService); @@ -564,6 +583,7 @@ private: static void ReadQueryName(const Message &aQuery, Name::Buffer &aName); static bool QueryNameMatches(const Message &aQuery, const char *aName); + static bool QueryNameIsForDomain(const Message &aQuery, const char *aDomainName); static void ReadQueryInstanceName(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, Name::Buffer &aName); static void ReadQueryInstanceName(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, @@ -596,6 +616,10 @@ private: Error ResolveByUpstream(const Request &aRequest); #endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE || OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + void ConstructSoaServerName(void); +#endif + void HandleTimer(void); void ResetTimer(void); @@ -610,6 +634,9 @@ private: #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE static const char *kBlockedDomains[]; #endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE || OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + static const char kSoaRnameLabel[]; +#endif ServerSocket mSocket; @@ -621,6 +648,10 @@ private: DiscoveryProxy mDiscoveryProxy; #endif +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE || OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE + Name::LabelBuffer mSoaServerName; +#endif + #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE bool mEnableUpstreamQuery; UpstreamQueryTransaction mUpstreamQueryTransactions[kMaxConcurrentUpstreamQueries]; diff --git a/tests/unit/test_dns_client.cpp b/tests/unit/test_dns_client.cpp index 28a7eb5b3..545db9d9e 100644 --- a/tests/unit/test_dns_client.cpp +++ b/tests/unit/test_dns_client.cpp @@ -551,7 +551,7 @@ struct QueryRecordInfo { struct Record : public Dns::Client::RecordInfo { - static constexpr uint16_t kMaxRecordDataSize = 200; + static constexpr uint16_t kMaxRecordDataSize = 500; void Init(void) { @@ -670,6 +670,94 @@ void ValidatePtrRecordData(const QueryRecordInfo::Record &aRecord, const char *a data->Free(); } +void ValidateSoaRecordData(const QueryRecordInfo::Record &aRecord, const char *aServerName) +{ + static constexpr uint32_t kSoaSerial = 0; + static constexpr uint32_t kSoaRefresh = 7200; + static constexpr uint32_t kSoaRetry = 3600; + static constexpr uint32_t kSoaExpire = 86400; + static constexpr uint32_t kSoaMinimum = 10; + + uint16_t offset; + Message *message; + Dns::Name::Buffer name; + + VerifyOrQuit(StringMatch(aRecord.mNameBuffer, "default.service.arpa.")); + VerifyOrQuit(aRecord.mRecordType == Dns::ResourceRecord::kTypeSoa); + VerifyOrQuit(aRecord.mTtl == 7200); + + message = sInstance->Get().Allocate(Message::kTypeOther); + VerifyOrQuit(message != nullptr); + + VerifyOrQuit(aRecord.mRecordLength == aRecord.mDataBufferSize); + SuccessOrQuit(message->AppendBytes(aRecord.mDataBuffer, aRecord.mDataBufferSize)); + + // Validate the SOA record data. + + offset = 0; + + // MNAME field: + SuccessOrQuit(Dns::Name::ReadName(*message, offset, name)); + VerifyOrQuit(StringMatch(name, aServerName, kStringCaseInsensitiveMatch)); + + // RNAME: must be "postmaster.default.service.arpa." + SuccessOrQuit(Dns::Name::ReadName(*message, offset, name)); + SuccessOrQuit(Dns::Name::StripName(name, "default.service.arpa.")); + VerifyOrQuit(StringMatch(name, "postmaster")); + + // SERIAL + VerifyOrQuit(message->Compare(offset, BigEndian::HostSwap32(kSoaSerial))); + offset += sizeof(uint32_t); + + // REFRESH + VerifyOrQuit(message->Compare(offset, BigEndian::HostSwap32(kSoaRefresh))); + offset += sizeof(uint32_t); + + // RETRY + VerifyOrQuit(message->Compare(offset, BigEndian::HostSwap32(kSoaRetry))); + offset += sizeof(uint32_t); + + // EXPIRE + VerifyOrQuit(message->Compare(offset, BigEndian::HostSwap32(kSoaExpire))); + offset += sizeof(uint32_t); + + // MINIMUM + VerifyOrQuit(message->Compare(offset, BigEndian::HostSwap32(kSoaMinimum))); + offset += sizeof(uint32_t); + + VerifyOrQuit(offset == message->GetLength()); + + message->Free(); +} + +void ValidateNsRecordData(const QueryRecordInfo::Record &aRecord, const char *aServerName) +{ + uint16_t offset; + Message *message; + Dns::Name::Buffer name; + + VerifyOrQuit(StringMatch(aRecord.mNameBuffer, "default.service.arpa.")); + VerifyOrQuit(aRecord.mRecordType == Dns::ResourceRecord::kTypeNs); + VerifyOrQuit(aRecord.mTtl == 7200); + + message = sInstance->Get().Allocate(Message::kTypeOther); + VerifyOrQuit(message != nullptr); + + VerifyOrQuit(aRecord.mRecordLength == aRecord.mDataBufferSize); + SuccessOrQuit(message->AppendBytes(aRecord.mDataBuffer, aRecord.mDataBufferSize)); + + // Validate the NS data + + offset = 0; + + SuccessOrQuit(Dns::Name::ReadName(*message, offset, name)); + VerifyOrQuit(StringMatch(name, aServerName, kStringCaseInsensitiveMatch)); + + VerifyOrQuit(offset == message->GetLength()); + + message->Free(); +} + //---------------------------------------------------------------------------------------------------------------------- void TestDnsClient(void) @@ -1734,6 +1822,195 @@ void TestDnssdServerProxyCallback(void) Log("End of TestDnssdServerProxyCallback"); } +void TestDnssdSoaNsResponse(void) +{ + Srp::Server *srpServer; + Srp::Client *srpClient; + Dns::Client *dnsClient; + Dns::Name::Buffer serverName; + StringWriter writer(serverName, sizeof(serverName)); + + Log("--------------------------------------------------------------------------------------------"); + Log("TestDnssdSoaNsResponse"); + + InitTest(); + + srpServer = &sInstance->Get(); + srpClient = &sInstance->Get(); + dnsClient = &sInstance->Get(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP server. + + SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast)); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled); + + srpServer->SetEnabled(true); + VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled); + + AdvanceTime(10000); + VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start SRP client. + + srpClient->EnableAutoStartMode(nullptr, nullptr); + VerifyOrQuit(srpClient->IsAutoStartModeEnabled()); + + AdvanceTime(2000); + VerifyOrQuit(srpClient->IsRunning()); + + writer.Append("otDNS%s.default.service.arpa.", sInstance->Get().GetExtAddress().ToString().AsCString()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("SOA Query"); + + for (uint8_t iter = 0; iter < 2; iter++) + { + sQueryRecordInfo.Reset(); + + // First iteration: Query for `default.service.arpa.` directly, and + // validate that we see the SOA record in the Answer section. + // Second iteration: Query for `myhost.default.service.arpa.`, and + // validate that we see the SOA record in the Authority section. + + if (iter == 0) + { + Log("QueryRecord(%s) for SOA RR", "default.service.arpa."); + SuccessOrQuit(dnsClient->QueryRecord(Dns::ResourceRecord::kTypeSoa, "default", "service.arpa.", + RecordCallback, sInstance)); + } + else + { + Log("QueryRecord(%s) for SOA RR", "myhost.default.service.arpa."); + SuccessOrQuit(dnsClient->QueryRecord(Dns::ResourceRecord::kTypeSoa, "myhost", "default.service.arpa.", + RecordCallback, sInstance)); + } + + AdvanceTime(100); + VerifyOrQuit(sQueryRecordInfo.mCallbackCount == 1); + SuccessOrQuit(sQueryRecordInfo.mError); + VerifyOrQuit(sQueryRecordInfo.mNumRecords == 1); + + if (iter == 0) + { + VerifyOrQuit(StringMatch(sQueryRecordInfo.mQueryName, "default.service.arpa.")); + VerifyOrQuit(MapEnum(sQueryRecordInfo.mRecords[0].mSection) == Dns::Client::RecordInfo::kSectionAnswer); + } + else + { + VerifyOrQuit(StringMatch(sQueryRecordInfo.mQueryName, "myhost.default.service.arpa.")); + VerifyOrQuit(MapEnum(sQueryRecordInfo.mRecords[0].mSection) == Dns::Client::RecordInfo::kSectionAuthority); + } + + ValidateSoaRecordData(sQueryRecordInfo.mRecords[0], serverName); + + AdvanceTime(1000); + } + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + Log("NS Query"); + + for (uint8_t iter = 0; iter < 2; iter++) + { + sQueryRecordInfo.Reset(); + + // First iteration: Query for `default.service.arpa.` directly and + // validate that we see the NS response in the Answer section. Second + // iteration: Query for `myhost.default.service.arpa.` and validate + // that we see the SOA record instead in the Authority section in + // the response. + + if (iter == 0) + { + Log("QueryRecord(%s) for NS RR", "default.service.arpa."); + SuccessOrQuit(dnsClient->QueryRecord(Dns::ResourceRecord::kTypeNs, "default", "service.arpa.", + RecordCallback, sInstance)); + } + else + { + Log("QueryRecord(%s) for NS RR", "myhost.default.service.arpa."); + SuccessOrQuit(dnsClient->QueryRecord(Dns::ResourceRecord::kTypeNs, "myhost", "default.service.arpa.", + RecordCallback, sInstance)); + } + + AdvanceTime(100); + VerifyOrQuit(sQueryRecordInfo.mCallbackCount == 1); + SuccessOrQuit(sQueryRecordInfo.mError); + VerifyOrQuit(sQueryRecordInfo.mNumRecords == 1); + + if (iter == 0) + { + VerifyOrQuit(StringMatch(sQueryRecordInfo.mQueryName, "default.service.arpa.")); + VerifyOrQuit(MapEnum(sQueryRecordInfo.mRecords[0].mSection) == Dns::Client::RecordInfo::kSectionAnswer); + ValidateNsRecordData(sQueryRecordInfo.mRecords[0], serverName); + } + else + { + VerifyOrQuit(StringMatch(sQueryRecordInfo.mQueryName, "myhost.default.service.arpa.")); + VerifyOrQuit(MapEnum(sQueryRecordInfo.mRecords[0].mSection) == Dns::Client::RecordInfo::kSectionAuthority); + ValidateSoaRecordData(sQueryRecordInfo.mRecords[0], serverName); + } + + AdvanceTime(1000); + } + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + + sQueryRecordInfo.Reset(); + + Log("QueryRecord(%s) for ANY RR", "default.service.arpa."); + SuccessOrQuit( + dnsClient->QueryRecord(Dns::ResourceRecord::kTypeAny, "default", "service.arpa.", RecordCallback, sInstance)); + + AdvanceTime(100); + VerifyOrQuit(sQueryRecordInfo.mCallbackCount == 1); + SuccessOrQuit(sQueryRecordInfo.mError); + VerifyOrQuit(sQueryRecordInfo.mNumRecords == 2); + + VerifyOrQuit(StringMatch(sQueryRecordInfo.mQueryName, "default.service.arpa.")); + + for (uint8_t index = 0; index < 2; index++) + { + QueryRecordInfo::Record &record = sQueryRecordInfo.mRecords[index]; + + VerifyOrQuit(MapEnum(record.mSection) == Dns::Client::RecordInfo::kSectionAnswer); + + switch (record.mRecordType) + { + case Dns::ResourceRecord::kTypeSoa: + ValidateSoaRecordData(record, serverName); + break; + case Dns::ResourceRecord::kTypeNs: + ValidateNsRecordData(record, serverName); + break; + default: + VerifyOrQuit(false); + break; + } + + AdvanceTime(1000); + } + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + + sAddressInfo.Reset(); + Log("ResolveAddress(%s)", serverName); + SuccessOrQuit(dnsClient->ResolveAddress(serverName, AddressCallback, sInstance)); + AdvanceTime(100); + VerifyOrQuit(sAddressInfo.mCallbackCount >= 1); + SuccessOrQuit(sAddressInfo.mError); + VerifyOrQuit(sAddressInfo.mHostAddresses[0] == sInstance->Get().GetMeshLocalEid()); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Finalize OT instance and validate all heap allocations are freed. + + Log("Finalizing OT instance"); + FinalizeTest(); + + Log("End of TestDnssdSoaNsResponse"); +} + #endif // ENABLE_DNS_TEST int main(void) @@ -1741,6 +2018,7 @@ int main(void) #if ENABLE_DNS_TEST TestDnsClient(); TestDnssdServerProxyCallback(); + TestDnssdSoaNsResponse(); printf("All tests passed\n"); #else printf("DNS_CLIENT or DSNSSD_SERVER feature is not enabled\n");