diff --git a/src/core/net/dns_types.cpp b/src/core/net/dns_types.cpp index d34bdd9a5..ec640b294 100644 --- a/src/core/net/dns_types.cpp +++ b/src/core/net/dns_types.cpp @@ -166,11 +166,6 @@ exit: } Error Name::AppendMultipleLabels(const char *aLabels, Message &aMessage) -{ - return AppendMultipleLabels(aLabels, kMaxNameLength, aMessage); -} - -Error Name::AppendMultipleLabels(const char *aLabels, uint8_t aLength, Message &aMessage) { Error error = kErrorNone; uint16_t index = 0; @@ -181,7 +176,7 @@ Error Name::AppendMultipleLabels(const char *aLabels, uint8_t aLength, Message & do { - ch = index < aLength ? aLabels[index] : static_cast(kNullChar); + ch = aLabels[index]; if ((ch == kNullChar) || (ch == kLabelSeparatorChar)) { @@ -633,6 +628,35 @@ exit: return error; } +Error Name::ExtractLabels(const char *aName, const char *aSuffixName, char *aLabels, uint16_t aLabelsSize) +{ + Error error = kErrorParse; + uint16_t nameLength = StringLength(aName, kMaxNameSize); + uint16_t suffixLength = StringLength(aSuffixName, kMaxNameSize); + const char *suffixStart; + + VerifyOrExit(nameLength < kMaxNameSize); + VerifyOrExit(suffixLength < kMaxNameSize); + + VerifyOrExit(nameLength > suffixLength); + + suffixStart = aName + nameLength - suffixLength; + VerifyOrExit(StringMatch(suffixStart, aSuffixName, kStringCaseInsensitiveMatch)); + suffixStart--; + VerifyOrExit(*suffixStart == kLabelSeparatorChar); + + // Determine the labels length to copy + nameLength -= (suffixLength + 1); + VerifyOrExit(nameLength < aLabelsSize, error = kErrorNoBufs); + + memcpy(aLabels, aName, nameLength); + aLabels[nameLength] = kNullChar; + error = kErrorNone; + +exit: + return error; +} + bool Name::IsSubDomainOf(const char *aName, const char *aDomain) { bool match = false; diff --git a/src/core/net/dns_types.hpp b/src/core/net/dns_types.hpp index 4922edf82..6c9caa4cb 100644 --- a/src/core/net/dns_types.hpp +++ b/src/core/net/dns_types.hpp @@ -686,26 +686,6 @@ public: */ static Error AppendLabel(const char *aLabel, Message &aMessage); - /** - * Encodes and appends a single name label of specified length to a message. - * - * The @p aLabel is assumed to contain a single name label of given @p aLength. @p aLabel must not contain - * '\0' characters within the length @p aLength. Unlike `AppendMultipleLabels()` which parses the label string - * and treats it as sequence of multiple (dot-separated) labels, this method always appends @p aLabel as a single - * whole label. This allows the label string to even contain dot '.' character, which, for example, is useful for - * "Service Instance Names" where portion is a user-friendly name and can contain dot characters. - * - * @param[in] aLabel The label string to append. MUST NOT be `nullptr`. - * @param[in] aLength The length of the label to append. - * @param[in] aMessage The message to append to. - * - * @retval kErrorNone Successfully encoded and appended the name label to @p aMessage. - * @retval kErrorInvalidArgs @p aLabel is not valid (e.g., label length is not within valid range). - * @retval kErrorNoBufs Insufficient available buffers to grow the message. - * - */ - static Error AppendLabel(const char *aLabel, uint8_t aLength, Message &aMessage); - /** * Encodes and appends a sequence of name labels to a given message. * @@ -728,33 +708,6 @@ public: */ static Error AppendMultipleLabels(const char *aLabels, Message &aMessage); - /** - * Encodes and appends a sequence of name labels within the specified length to a given message. - * Stops appending labels if @p aLength characters are read or '\0' is found before @p aLength - * characters. - * - * Is useful for appending a number of labels of the name instead of appending all labels. - * - * The @p aLabels must follow "..", i.e., a sequence of labels separated by dot '.' char. - * E.g., "_http._tcp", "_http._tcp." (same as previous one), "host-1.test". - * - * Validates that the @p aLabels is a valid name format, i.e., no empty label, and labels are - * `kMaxLabelLength` (63) characters or less. - * - * @note This method NEVER adds a label terminator (empty label) to the message, even in the case where @p aLabels - * ends with a dot character, e.g., "host-1.test." is treated same as "host-1.test". - * - * @param[in] aLabels A name label string. Can be `nullptr` (then treated as ""). - * @param[in] aLength The max length of the name labels to encode. - * @param[in] aMessage The message to which to append the encoded name. - * - * @retval kErrorNone Successfully encoded and appended the name label(s) to @p aMessage. - * @retval kErrorInvalidArgs Name label @p aLabels is not valid. - * @retval kErrorNoBufs Insufficient available buffers to grow the message. - * - */ - static Error AppendMultipleLabels(const char *aLabels, uint8_t aLength, Message &aMessage); - /** * Appends a name label terminator to a message. * @@ -977,6 +930,25 @@ public: */ static Error CompareName(const Message &aMessage, uint16_t &aOffset, const Name &aName); + /** + * Extracts label(s) from a full name by checking that it contains a given suffix name (e.g., suffix name can be + * a domain name) and removing it. + * + * Both @p aName and @p aSuffixName must be full DNS name and end with ('.'), otherwise the behavior of this method + * is undefined. + * + * @param[in] aName The full name to extract labels from. + * @param[in] aSuffixName The suffix name (e.g. can be domain name). + * @param[out] aLabels Pointer to buffer to copy the extracted labels. + * @param[in] aLabelsSize Size of @p aLabels buffer. + * + * @retval kErrorNone Successfully extracted the labels, @p aLabels is updated. + * @retval kErrorParse @p aName does not contain @p aSuffixName. + * @retval kErrorNoBufs Could not fit the labels in @p aLabelsSize. + * + */ + static Error ExtractLabels(const char *aName, const char *aSuffixName, char *aLabels, uint16_t aLabelsSize); + /** * Tests if a DNS name is a sub-domain of a given domain. * @@ -1056,6 +1028,8 @@ private: { } + static Error AppendLabel(const char *aLabel, uint8_t aLength, Message &aMessage); + const char *mString; // String containing the name or `nullptr` if name is not from string. const Message *mMessage; // Message containing the encoded name, or `nullptr` if `Name` is not from message. uint16_t mOffset; // Offset in `mMessage` to the start of name (used when name is from `mMessage`). diff --git a/src/core/net/dnssd_server.cpp b/src/core/net/dnssd_server.cpp index 13da75caf..5e81902cc 100644 --- a/src/core/net/dnssd_server.cpp +++ b/src/core/net/dnssd_server.cpp @@ -54,11 +54,12 @@ namespace ServiceDiscovery { RegisterLogModule("DnssdServer"); -const char Server::kDnssdProtocolUdp[] = "_udp"; -const char Server::kDnssdProtocolTcp[] = "_tcp"; -const char Server::kDnssdSubTypeLabel[] = "._sub."; -const char Server::kDefaultDomainName[] = "default.service.arpa."; -const char *Server::kBlockedDomains[] = {"ipv4only.arpa."}; +const char Server::kDefaultDomainName[] = "default.service.arpa."; +const char Server::kSubLabel[] = "_sub"; + +#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE +const char *Server::kBlockedDomains[] = {"ipv4only.arpa."}; +#endif Server::Server(Instance &aInstance) : InstanceLocator(aInstance) @@ -85,9 +86,9 @@ Error Server::Start(void) Get().HandleDnssdServerStateChange(); #endif -exit: - LogInfo("started: %s", ErrorToString(error)); + LogInfo("Started"); +exit: if (error != kErrorNone) { IgnoreError(mSocket.Close()); @@ -103,7 +104,7 @@ void Server::Stop(void) { if (query.IsValid()) { - query.Finalize(Header::kResponseServerFailure); + query.Finalize(kErrorFailed); } } @@ -120,7 +121,7 @@ void Server::Stop(void) mTimer.Stop(); IgnoreError(mSocket.Close()); - LogInfo("stopped"); + LogInfo("Stopped"); #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE Get().HandleDnssdServerStateChange(); @@ -150,18 +151,20 @@ void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessag VerifyOrExit(request.mHeader.GetType() == Header::kTypeQuery); + LogInfo("Received query from %s", aMessageInfo.GetPeerAddr().ToString().AsCString()); + ProcessQuery(request); exit: return; } -void Server::ProcessQuery(const Request &aRequest) +void Server::ProcessQuery(Request &aRequest) { - Error error = kErrorNone; - Response response; - bool shouldSendResponse = true; - Header::Response rcode = Header::kResponseSuccess; + Error error = kErrorNone; + Response response; + bool shouldSendResponse = true; + ResponseCode rcode = Header::kResponseSuccess; #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE if (mEnableUpstreamQuery && ShouldForwardToUpstream(aRequest)) @@ -174,7 +177,7 @@ void Server::ProcessQuery(const Request &aRequest) ExitNow(); } - LogWarn("Failed to forward DNS query to upstream: %s", ErrorToString(error)); + LogWarn("Error forwarding to upstream: %s", ErrorToString(error)); error = kErrorNone; rcode = Header::kResponseServerFailure; @@ -184,7 +187,7 @@ void Server::ProcessQuery(const Request &aRequest) } #endif - response.mMessage = mSocket.NewMessage(); + response.mMessage = Get().mSocket.NewMessage(); VerifyOrExit(response.mMessage != nullptr, error = kErrorNoBufs); // Prepare DNS response header @@ -204,29 +207,30 @@ void Server::ProcessQuery(const Request &aRequest) #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE // Forwarding the query to the upstream may have already set the // response error code. - VerifyOrExit(rcode == Header::kResponseSuccess); + SuccessOrExit(rcode); #endif - // Validate the query - VerifyOrExit(aRequest.mHeader.GetQueryType() == Header::kQueryTypeStandard, - rcode = Header::kResponseNotImplemented); - VerifyOrExit(!aRequest.mHeader.IsTruncationFlagSet(), rcode = Header::kResponseFormatError); - VerifyOrExit(aRequest.mHeader.GetQuestionCount() > 0, rcode = Header::kResponseFormatError); + SuccessOrExit(rcode = aRequest.ParseQuestions(mTestMode)); + SuccessOrExit(rcode = response.AddQuestionsFrom(aRequest)); - if (mTestMode & kTestModeSingleQuestionOnly) - { - VerifyOrExit(aRequest.mHeader.GetQuestionCount() == 1, rcode = Header::kResponseFormatError); - } - - SuccessOrExit(response.AddQuestionsFrom(aRequest)); +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) + response.Log(); +#endif #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE - response.ResolveBySrp(); - - if (response.mHeader.GetAnswerCount() != 0) + switch (response.ResolveBySrp()) { + case kErrorNone: mCounters.mResolvedBySrp++; ExitNow(); + + case kErrorNotFound: + rcode = Header::kResponseNameError; + break; + + default: + rcode = Header::kResponseServerFailure; + ExitNow(); } #endif @@ -244,7 +248,7 @@ exit: { if (rcode != Header::kResponseSuccess) { - response.mHeader.SetResponseCode(rcode); + response.SetResponseCode(rcode); } response.Send(*aRequest.mMessageInfo); @@ -255,12 +259,11 @@ exit: void Server::Response::Send(const Ip6::MessageInfo &aMessageInfo) { - Error error; - Header::Response rcode = mHeader.GetResponseCode(); + Error error; + ResponseCode rcode = mHeader.GetResponseCode(); if (rcode == Header::kResponseServerFailure) { - LogWarn("failed to handle DNS query due to server failure"); mHeader.SetQuestionCount(0); mHeader.SetAnswerCount(0); mHeader.SetAdditionalRecordCount(0); @@ -274,122 +277,236 @@ void Server::Response::Send(const Ip6::MessageInfo &aMessageInfo) if (error != kErrorNone) { mMessage->Free(); - LogWarn("failed to send DNS-SD reply: %s", ErrorToString(error)); + LogWarn("Failed to send reply: %s", ErrorToString(error)); } else { - LogInfo("send DNS-SD reply: %s, RCODE=%d", ErrorToString(error), rcode); + LogInfo("Send response, rcode:%u", rcode); } Get().UpdateResponseCounters(rcode); } -Error Server::Response::AddQuestionsFrom(const Request &aRequest) +Server::ResponseCode Server::Request::ParseQuestions(uint8_t aTestMode) { - uint16_t readOffset; - Header::Response rcode = Header::kResponseSuccess; + // Parse header and questions from a `Request` query message and + // determine the `QueryType`. - readOffset = sizeof(Header); + ResponseCode rcode = Header::kResponseFormatError; + uint16_t offset = sizeof(Header); + uint16_t questionCount = mHeader.GetQuestionCount(); + Question question; - for (uint16_t i = 0; i < aRequest.mHeader.GetQuestionCount(); i++) + VerifyOrExit(mHeader.GetQueryType() == Header::kQueryTypeStandard, rcode = Header::kResponseNotImplemented); + VerifyOrExit(!mHeader.IsTruncationFlagSet()); + + VerifyOrExit(questionCount > 0); + + SuccessOrExit(Name::ParseName(*mMessage, offset)); + SuccessOrExit(mMessage->Read(offset, question)); + offset += sizeof(question); + + switch (question.GetType()) { - char name[Name::kMaxNameSize]; - NameComponentsOffsetInfo nameInfo; - Question question; + case ResourceRecord::kTypePtr: + mType = kPtrQuery; + break; + case ResourceRecord::kTypeSrv: + mType = kSrvQuery; + break; + case ResourceRecord::kTypeTxt: + mType = kTxtQuery; + break; + case ResourceRecord::kTypeAaaa: + mType = kAaaaQuery; + break; + default: + ExitNow(rcode = Header::kResponseNotImplemented); + } - VerifyOrExit(Name::ReadName(*aRequest.mMessage, readOffset, name, sizeof(name)) == kErrorNone, - rcode = Header::kResponseFormatError); - VerifyOrExit(aRequest.mMessage->Read(readOffset, question) == kErrorNone, rcode = Header::kResponseFormatError); - readOffset += sizeof(question); + if (questionCount > 1) + { + VerifyOrExit(!(aTestMode & kTestModeSingleQuestionOnly)); + + VerifyOrExit(questionCount == 2); + + SuccessOrExit(Name::CompareName(*mMessage, offset, *mMessage, sizeof(Header))); + SuccessOrExit(mMessage->Read(offset, question)); switch (question.GetType()) { - case ResourceRecord::kTypePtr: case ResourceRecord::kTypeSrv: + VerifyOrExit(mType == kTxtQuery); + break; + case ResourceRecord::kTypeTxt: - case ResourceRecord::kTypeAaaa: + VerifyOrExit(mType == kSrvQuery); break; default: - rcode = Header::kResponseNotImplemented; ExitNow(); } - VerifyOrExit(FindNameComponents(name, kDefaultDomainName, nameInfo) == kErrorNone, - rcode = Header::kResponseNameError); - - switch (question.GetType()) - { - case ResourceRecord::kTypePtr: - VerifyOrExit(nameInfo.IsServiceName(), rcode = Header::kResponseNameError); - break; - case ResourceRecord::kTypeSrv: - VerifyOrExit(nameInfo.IsServiceInstanceName(), rcode = Header::kResponseNameError); - break; - case ResourceRecord::kTypeTxt: - VerifyOrExit(nameInfo.IsServiceInstanceName(), rcode = Header::kResponseNameError); - break; - case ResourceRecord::kTypeAaaa: - VerifyOrExit(nameInfo.IsHostName(), rcode = Header::kResponseNameError); - break; - default: - break; - } - - VerifyOrExit(AppendQuestion(name, question) == kErrorNone, rcode = Header::kResponseServerFailure); + mType = kSrvTxtQuery; } + rcode = Header::kResponseSuccess; + +exit: + return rcode; +} + +Server::ResponseCode Server::Response::AddQuestionsFrom(const Request &aRequest) +{ + ResponseCode rcode = Header::kResponseServerFailure; + uint16_t offset; + + mType = aRequest.mType; + + // Read the name from `aRequest.mMessage` and append it as is to + // the response message. This ensures all name formats, including + // service instance names with dot characters in the instance + // label, are appended correctly. + + SuccessOrExit(Name(*aRequest.mMessage, sizeof(Header)).AppendTo(*mMessage)); + + // Check the name to include the correct domain name and determine + // the domain name offset (for DNS name compression). + + VerifyOrExit(ParseQueryName() == kErrorNone, rcode = Header::kResponseNameError); + mHeader.SetQuestionCount(aRequest.mHeader.GetQuestionCount()); -exit: - mHeader.SetResponseCode(rcode); - return (rcode == Header::kResponseSuccess) ? kErrorNone : kErrorFailed; -} + offset = sizeof(Header); -Error Server::Response::AppendQuestion(const char *aName, const Question &aQuestion) -{ - Error error = kErrorNone; - - switch (aQuestion.GetType()) + for (uint16_t questionCount = 0; questionCount < mHeader.GetQuestionCount(); questionCount++) { - case ResourceRecord::kTypePtr: - SuccessOrExit(error = AppendServiceName(aName)); - break; - case ResourceRecord::kTypeSrv: - case ResourceRecord::kTypeTxt: - SuccessOrExit(error = AppendInstanceName(aName)); - break; - case ResourceRecord::kTypeAaaa: - SuccessOrExit(error = AppendHostName(aName)); - break; - default: - OT_ASSERT(false); + Question question; + + // The names and questions in `aRequest` are validated already + // from `ParseQuestions()`, so we can `IgnoreError()` here. + + IgnoreError(Name::ParseName(*aRequest.mMessage, offset)); + IgnoreError(aRequest.mMessage->Read(offset, question)); + offset += sizeof(question); + + if (questionCount != 0) + { + SuccessOrExit(AppendQueryName()); + } + + SuccessOrExit(mMessage->Append(question)); } - error = mMessage->Append(aQuestion); + rcode = Header::kResponseSuccess; + +exit: + return rcode; +} + +Error Server::Response::ParseQueryName(void) +{ + // Parses and validates the query name and updates + // the name compression offsets. + + Error error = kErrorNone; + DnsName name; + uint16_t offset; + + offset = sizeof(Header); + SuccessOrExit(error = Name::ReadName(*mMessage, offset, name, sizeof(name))); + + switch (mType) + { + case kPtrQuery: + // `mServiceOffset` may be updated as we read labels and if we + // determine that the query name is a sub-type service. + mServiceOffset = sizeof(Header); + break; + + case kSrvQuery: + case kTxtQuery: + case kSrvTxtQuery: + mInstanceOffset = sizeof(Header); + break; + + case kAaaaQuery: + mHostOffset = sizeof(Header); + break; + } + + // 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); + + while (true) + { + DnsLabel label; + uint8_t labelLength = sizeof(label); + uint16_t comapreOffset; + + SuccessOrExit(error = Name::ReadLabel(*mMessage, offset, label, labelLength)); + + if ((mType == kPtrQuery) && StringMatch(label, kSubLabel, kStringCaseInsensitiveMatch)) + { + mServiceOffset = offset; + } + + comapreOffset = offset; + + if (Name::CompareName(*mMessage, comapreOffset, kDefaultDomainName) == kErrorNone) + { + mDomainOffset = offset; + ExitNow(); + } + } + + error = kErrorParse; exit: return error; } -Error Server::Response::AppendPtrRecord(const char *aServiceName, const char *aInstanceName, uint32_t aTtl) +void Server::Response::ReadQueryName(DnsName &aName) const +{ + // Query name is always present immediately after `Header` in the + // question section + + uint16_t offset = sizeof(Header); + + IgnoreError(Name::ReadName(*mMessage, offset, aName, sizeof(aName))); +} + +bool Server::Response::QueryNameMatches(const char *aName) const +{ + uint16_t offset = sizeof(Header); + + return (Name::CompareName(*mMessage, offset, aName) == kErrorNone); +} + +Error Server::Response::AppendQueryName(void) const { return Name::AppendPointerLabel(sizeof(Header), *mMessage); } + +Error Server::Response::AppendPtrRecord(const char *aInstanceLabel, uint32_t aTtl) { Error error; - PtrRecord ptrRecord; uint16_t recordOffset; + PtrRecord ptrRecord; ptrRecord.Init(); ptrRecord.SetTtl(aTtl); - SuccessOrExit(error = AppendServiceName(aServiceName)); + SuccessOrExit(error = AppendQueryName()); recordOffset = mMessage->GetLength(); - SuccessOrExit(error = mMessage->SetLength(recordOffset + sizeof(ptrRecord))); + SuccessOrExit(error = mMessage->Append(ptrRecord)); - SuccessOrExit(error = AppendInstanceName(aInstanceName)); + mInstanceOffset = mMessage->GetLength(); + SuccessOrExit(error = Name::AppendLabel(aInstanceLabel, *mMessage)); + SuccessOrExit(error = Name::AppendPointerLabel(mServiceOffset, *mMessage)); - ptrRecord.SetLength(mMessage->GetLength() - (recordOffset + sizeof(ResourceRecord))); - mMessage->Write(recordOffset, ptrRecord); + UpdateRecordLength(ptrRecord, recordOffset); IncResourceRecordCount(); @@ -397,16 +514,34 @@ exit: return error; } -Error Server::Response::AppendSrvRecord(const char *aInstanceName, - const char *aHostName, +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE +Error Server::Response::AppendSrvRecord(const Srp::Server::Service &aService) +{ + uint32_t ttl = TimeMilli::MsecToSec(aService.GetExpireTime() - TimerMilli::GetNow()); + + return AppendSrvRecord(aService.GetHost().GetFullName(), ttl, aService.GetPriority(), aService.GetWeight(), + aService.GetPort()); +} +#endif + +Error Server::Response::AppendSrvRecord(const ServiceInstanceInfo &aInstanceInfo) +{ + return AppendSrvRecord(aInstanceInfo.mHostName, aInstanceInfo.mTtl, aInstanceInfo.mPriority, aInstanceInfo.mWeight, + aInstanceInfo.mPort); +} + +Error Server::Response::AppendSrvRecord(const char *aHostName, uint32_t aTtl, uint16_t aPriority, uint16_t aWeight, uint16_t aPort) { - SrvRecord srvRecord; Error error = kErrorNone; + SrvRecord srvRecord; uint16_t recordOffset; + DnsName hostLabels; + + SuccessOrExit(error = Name::ExtractLabels(aHostName, kDefaultDomainName, hostLabels, sizeof(hostLabels))); srvRecord.Init(); srvRecord.SetTtl(aTtl); @@ -414,15 +549,16 @@ Error Server::Response::AppendSrvRecord(const char *aInstanceName, srvRecord.SetWeight(aWeight); srvRecord.SetPort(aPort); - SuccessOrExit(error = AppendInstanceName(aInstanceName)); + SuccessOrExit(error = Name::AppendPointerLabel(mInstanceOffset, *mMessage)); recordOffset = mMessage->GetLength(); - SuccessOrExit(error = mMessage->SetLength(recordOffset + sizeof(srvRecord))); + SuccessOrExit(error = mMessage->Append(srvRecord)); - SuccessOrExit(error = AppendHostName(aHostName)); + mHostOffset = mMessage->GetLength(); + SuccessOrExit(error = Name::AppendMultipleLabels(hostLabels, *mMessage)); + SuccessOrExit(error = Name::AppendPointerLabel(mDomainOffset, *mMessage)); - srvRecord.SetLength(mMessage->GetLength() - (recordOffset + sizeof(ResourceRecord))); - mMessage->Write(recordOffset, srvRecord); + UpdateRecordLength(srvRecord, recordOffset); IncResourceRecordCount(); @@ -430,445 +566,273 @@ exit: return error; } -Error Server::Response::AppendAaaaRecord(const char *aHostName, const Ip6::Address &aAddress, uint32_t aTtl) -{ - AaaaRecord aaaaRecord; - Error error; - - aaaaRecord.Init(); - aaaaRecord.SetTtl(aTtl); - aaaaRecord.SetAddress(aAddress); - - SuccessOrExit(error = AppendHostName(aHostName)); - SuccessOrExit(error = mMessage->Append(aaaaRecord)); - - IncResourceRecordCount(); - -exit: - return error; -} - -Error Server::Response::AppendServiceName(const char *aName) -{ - Error error; - uint16_t serviceCompressOffset = mCompressInfo.GetServiceNameOffset(*mMessage, aName); - const char *serviceName; - - // Check whether `aName` is a sub-type service name. - serviceName = StringFind(aName, kDnssdSubTypeLabel, kStringCaseInsensitiveMatch); - - if (serviceName != nullptr) - { - uint8_t subTypeLabelLength = static_cast(serviceName - aName) + sizeof(kDnssdSubTypeLabel) - 1; - - SuccessOrExit(error = Name::AppendMultipleLabels(aName, subTypeLabelLength, *mMessage)); - - // Skip over the "._sub." label to get to the root service name. - serviceName += sizeof(kDnssdSubTypeLabel) - 1; - } - else - { - serviceName = aName; - } - - if (serviceCompressOffset != NameCompressInfo::kUnknownOffset) - { - error = Name::AppendPointerLabel(serviceCompressOffset, *mMessage); - } - else - { - uint16_t domainStart = StringLength(serviceName, Name::kMaxNameSize - 1) - (sizeof(kDefaultDomainName) - 1); - uint16_t domainCompressOffset = mCompressInfo.GetDomainNameOffset(); - - serviceCompressOffset = mMessage->GetLength(); - mCompressInfo.SetServiceNameOffset(serviceCompressOffset); - - if (domainCompressOffset == NameCompressInfo::kUnknownOffset) - { - mCompressInfo.SetDomainNameOffset(serviceCompressOffset + domainStart); - error = Name::AppendName(serviceName, *mMessage); - } - else - { - SuccessOrExit(error = - Name::AppendMultipleLabels(serviceName, static_cast(domainStart), *mMessage)); - error = Name::AppendPointerLabel(domainCompressOffset, *mMessage); - } - } - -exit: - return error; -} - -Error Server::Response::AppendInstanceName(const char *aName) -{ - Error error; - uint16_t instanceCompressOffset = mCompressInfo.GetInstanceNameOffset(*mMessage, aName); - - if (instanceCompressOffset != NameCompressInfo::kUnknownOffset) - { - error = Name::AppendPointerLabel(instanceCompressOffset, *mMessage); - } - else - { - NameComponentsOffsetInfo nameComponentsInfo; - - IgnoreError(FindNameComponents(aName, kDefaultDomainName, nameComponentsInfo)); - OT_ASSERT(nameComponentsInfo.IsServiceInstanceName()); - - mCompressInfo.SetInstanceNameOffset(mMessage->GetLength()); - - // Append the instance name as one label - SuccessOrExit(error = Name::AppendLabel(aName, nameComponentsInfo.mServiceOffset - 1, *mMessage)); - - { - const char *serviceName = aName + nameComponentsInfo.mServiceOffset; - uint16_t serviceCompressOffset = mCompressInfo.GetServiceNameOffset(*mMessage, serviceName); - - if (serviceCompressOffset != NameCompressInfo::kUnknownOffset) - { - error = Name::AppendPointerLabel(serviceCompressOffset, *mMessage); - } - else - { - mCompressInfo.SetServiceNameOffset(mMessage->GetLength()); - error = Name::AppendName(serviceName, *mMessage); - } - } - } - -exit: - return error; -} - -Error Server::Response::AppendTxtRecord(const char *aInstanceName, - const void *aTxtData, - uint16_t aTxtLength, - uint32_t aTtl) -{ - Error error = kErrorNone; - TxtRecord txtRecord; - const uint8_t kEmptyTxt = 0; - - SuccessOrExit(error = AppendInstanceName(aInstanceName)); - - txtRecord.Init(); - txtRecord.SetTtl(aTtl); - txtRecord.SetLength(aTxtLength > 0 ? aTxtLength : sizeof(kEmptyTxt)); - - SuccessOrExit(error = mMessage->Append(txtRecord)); - - if (aTxtLength > 0) - { - SuccessOrExit(error = mMessage->AppendBytes(aTxtData, aTxtLength)); - } - else - { - SuccessOrExit(error = mMessage->Append(kEmptyTxt)); - } - - IncResourceRecordCount(); - -exit: - return error; -} - -Error Server::Response::AppendHostName(const char *aName) -{ - Error error; - uint16_t hostCompressOffset = mCompressInfo.GetHostNameOffset(*mMessage, aName); - - if (hostCompressOffset != NameCompressInfo::kUnknownOffset) - { - error = Name::AppendPointerLabel(hostCompressOffset, *mMessage); - } - else - { - uint16_t domainStart = StringLength(aName, Name::kMaxNameLength) - (sizeof(kDefaultDomainName) - 1); - uint16_t domainCompressOffset = mCompressInfo.GetDomainNameOffset(); - - hostCompressOffset = mMessage->GetLength(); - mCompressInfo.SetHostNameOffset(hostCompressOffset); - - if (domainCompressOffset == NameCompressInfo::kUnknownOffset) - { - mCompressInfo.SetDomainNameOffset(hostCompressOffset + domainStart); - error = Name::AppendName(aName, *mMessage); - } - else - { - SuccessOrExit(error = Name::AppendMultipleLabels(aName, static_cast(domainStart), *mMessage)); - error = Name::AppendPointerLabel(domainCompressOffset, *mMessage); - } - } - -exit: - return error; -} - -void Server::Response::IncResourceRecordCount(void) -{ - if (mAdditional) - { - mHeader.SetAdditionalRecordCount(mHeader.GetAdditionalRecordCount() + 1); - } - else - { - mHeader.SetAnswerCount(mHeader.GetAnswerCount() + 1); - } -} - -Error Server::FindNameComponents(const char *aName, const char *aDomain, NameComponentsOffsetInfo &aInfo) -{ - uint8_t nameLen = static_cast(StringLength(aName, Name::kMaxNameLength)); - uint8_t domainLen = static_cast(StringLength(aDomain, Name::kMaxNameLength)); - Error error = kErrorNone; - uint8_t labelBegin, labelEnd; - - VerifyOrExit(Name::IsSubDomainOf(aName, aDomain), error = kErrorInvalidArgs); - - labelBegin = nameLen - domainLen; - aInfo.mDomainOffset = labelBegin; - - while (true) - { - error = FindPreviousLabel(aName, labelBegin, labelEnd); - - VerifyOrExit(error == kErrorNone, error = (error == kErrorNotFound ? kErrorNone : error)); - - if (labelEnd == labelBegin + kProtocolLabelLength && - (StringStartsWith(&aName[labelBegin], kDnssdProtocolUdp, kStringCaseInsensitiveMatch) || - StringStartsWith(&aName[labelBegin], kDnssdProtocolTcp, kStringCaseInsensitiveMatch))) - { - // label found - aInfo.mProtocolOffset = labelBegin; - break; - } - } - - // Get service label - error = FindPreviousLabel(aName, labelBegin, labelEnd); - VerifyOrExit(error == kErrorNone, error = (error == kErrorNotFound ? kErrorNone : error)); - - aInfo.mServiceOffset = labelBegin; - - // Check for service subtype - error = FindPreviousLabel(aName, labelBegin, labelEnd); - VerifyOrExit(error == kErrorNone, error = (error == kErrorNotFound ? kErrorNone : error)); - - // Note that `kDnssdSubTypeLabel` is "._sub.". Here we get the - // label only so we want to compare it with "_sub". - if ((labelEnd == labelBegin + kSubTypeLabelLength) && - StringStartsWith(&aName[labelBegin], kDnssdSubTypeLabel + 1, kStringCaseInsensitiveMatch)) - { - SuccessOrExit(error = FindPreviousLabel(aName, labelBegin, labelEnd)); - VerifyOrExit(labelBegin == 0, error = kErrorInvalidArgs); - aInfo.mSubTypeOffset = labelBegin; - ExitNow(); - } - - // Treat everything before as label - aInfo.mInstanceOffset = 0; - -exit: - return error; -} - -Error Server::FindPreviousLabel(const char *aName, uint8_t &aStart, uint8_t &aStop) -{ - // This method finds the previous label before the current label (whose start index is @p aStart), and updates @p - // aStart to the start index of the label and @p aStop to the index of the dot just after the label. - // @note The input value of @p aStop does not matter because it is only used to output. - - Error error = kErrorNone; - uint8_t start = aStart; - uint8_t end; - - VerifyOrExit(start > 0, error = kErrorNotFound); - VerifyOrExit(aName[--start] == Name::kLabelSeparatorChar, error = kErrorInvalidArgs); - - end = start; - while (start > 0 && aName[start - 1] != Name::kLabelSeparatorChar) - { - start--; - } - - VerifyOrExit(start < end, error = kErrorInvalidArgs); - - aStart = start; - aStop = end; - -exit: - return error; -} - #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE -void Server::Response::ResolveBySrp(void) +Error Server::Response::AppendHostAddresses(const Srp::Server::Host &aHost) { - uint16_t readOffset = sizeof(Header); - char name[Name::kMaxNameSize]; - Question question; + const Ip6::Address *addrs; + uint8_t addrsLength; + uint32_t ttl; - mAdditional = false; + addrs = aHost.GetAddresses(addrsLength); + ttl = TimeMilli::MsecToSec(aHost.GetExpireTime() - TimerMilli::GetNow()); - for (uint16_t i = 0; i < mHeader.GetQuestionCount(); i++) + return AppendHostAddresses(addrs, addrsLength, ttl); +} +#endif + +Error Server::Response::AppendHostAddresses(const HostInfo &aHostInfo) +{ + return AppendHostAddresses(AsCoreTypePtr(aHostInfo.mAddresses), aHostInfo.mAddressNum, aHostInfo.mTtl); +} + +Error Server::Response::AppendHostAddresses(const ServiceInstanceInfo &aInstanceInfo) +{ + return AppendHostAddresses(AsCoreTypePtr(aInstanceInfo.mAddresses), aInstanceInfo.mAddressNum, aInstanceInfo.mTtl); +} + +Error Server::Response::AppendHostAddresses(const Ip6::Address *aAddrs, uint16_t aAddrsLength, uint32_t aTtl) +{ + Error error = kErrorNone; + + for (uint16_t index = 0; index < aAddrsLength; index++) { - // The names and questions in the request message are validated - // from `AddQuestionsFrom()`, so we `IgnoreError()` here. + AaaaRecord aaaaRecord; - IgnoreError(Name::ReadName(*mMessage, readOffset, name, sizeof(name))); - IgnoreError(mMessage->Read(readOffset, question)); - readOffset += sizeof(question); + aaaaRecord.Init(); + aaaaRecord.SetTtl(aTtl); + aaaaRecord.SetAddress(aAddrs[index]); - ResolveQuestionBySrp(name, question); + SuccessOrExit(error = Name::AppendPointerLabel(mHostOffset, *mMessage)); + SuccessOrExit(error = mMessage->Append(aaaaRecord)); - LogInfo("ANSWER: TRANSACTION=0x%04x, QUESTION=[%s %d %d], RCODE=%d", mHeader.GetMessageId(), name, - question.GetClass(), question.GetType(), mHeader.GetResponseCode()); - - VerifyOrExit(mHeader.GetResponseCode() == Header::kResponseSuccess); - } - - // Answer the questions with additional RRs if required - if (mHeader.GetAnswerCount() > 0) - { - mAdditional = true; - - VerifyOrExit(!(Get().mTestMode & kTestModeEmptyAdditionalSection)); - - readOffset = sizeof(Header); - for (uint16_t i = 0; i < mHeader.GetQuestionCount(); i++) - { - IgnoreError(Name::ReadName(*mMessage, readOffset, name, sizeof(name))); - IgnoreError(mMessage->Read(readOffset, question)); - readOffset += sizeof(question); - - if ((question.GetType() == ResourceRecord::kTypePtr) && (mHeader.GetAnswerCount() > 1)) - { - // 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. - continue; - } - - ResolveQuestionBySrp(name, question); - - LogInfo("ADDITIONAL: TRANSACTION=0x%04x, QUESTION=[%s %d %d], RCODE=%d", mHeader.GetMessageId(), name, - question.GetClass(), question.GetType(), mHeader.GetResponseCode()); - - VerifyOrExit(mHeader.GetResponseCode() == Header::kResponseSuccess); - } + IncResourceRecordCount(); } exit: - return; + return error; } -void Server::Response::ResolveQuestionBySrp(const char *aName, const Question &aQuestion) +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE +Error Server::Response::AppendTxtRecord(const Srp::Server::Service &aService) { - Error error = kErrorNone; - TimeMilli now = TimerMilli::GetNow(); - uint16_t qtype = aQuestion.GetType(); - Header::Response rcode = Header::kResponseNameError; + return AppendTxtRecord(aService.GetTxtData(), aService.GetTxtDataLength(), + TimeMilli::MsecToSec(aService.GetExpireTime() - TimerMilli::GetNow())); +} +#endif + +Error Server::Response::AppendTxtRecord(const ServiceInstanceInfo &aInstanceInfo) +{ + return AppendTxtRecord(aInstanceInfo.mTxtData, aInstanceInfo.mTxtLength, aInstanceInfo.mTtl); +} + +Error Server::Response::AppendTxtRecord(const void *aTxtData, uint16_t aTxtLength, uint32_t aTtl) +{ + Error error = kErrorNone; + TxtRecord txtRecord; + uint8_t emptyTxt = 0; + + if (aTxtLength == 0) + { + aTxtData = &emptyTxt; + aTxtLength = sizeof(emptyTxt); + } + + txtRecord.Init(); + txtRecord.SetTtl(aTtl); + txtRecord.SetLength(aTxtLength); + + SuccessOrExit(error = Name::AppendPointerLabel(mInstanceOffset, *mMessage)); + SuccessOrExit(error = mMessage->Append(txtRecord)); + SuccessOrExit(error = mMessage->AppendBytes(aTxtData, aTxtLength)); + + IncResourceRecordCount(); + +exit: + return error; +} + +void Server::Response::UpdateRecordLength(ResourceRecord &aRecord, uint16_t aOffset) const +{ + // Calculates RR DATA length and updates and re-writes it in the + // response message. This should be called immediately + // after all the fields in the record are written in the message. + // `aOffset` gives the offset in the message to the start of the + // record. + + aRecord.SetLength(mMessage->GetLength() - aOffset - sizeof(Dns::ResourceRecord)); + mMessage->Write(aOffset, aRecord); +} + +void Server::Response::IncResourceRecordCount(void) +{ + switch (mSection) + { + case kAnswerSection: + mHeader.SetAnswerCount(mHeader.GetAnswerCount() + 1); + break; + case kAdditionalDataSection: + mHeader.SetAdditionalRecordCount(mHeader.GetAdditionalRecordCount() + 1); + break; + } +} + +uint8_t Server::GetNameLength(const char *aName) +{ + return static_cast(StringLength(aName, Name::kMaxNameLength)); +} + +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) +void Server::Response::Log(void) const +{ + DnsName name; + + ReadQueryName(name); + LogInfo("%s query for '%s'", QueryTypeToString(mType), name); +} + +const char *Server::Response::QueryTypeToString(QueryType aType) +{ + static const char *const kTypeNames[] = { + "PTR", // (0) kPtrQuery + "SRV", // (1) kSrvQuery + "TXT", // (2) kTxtQuery + "SRV & TXT", // (3) kSrvTxtQuery + "AAAA", // (4) kAaaaQuery + }; + + static_assert(0 == kPtrQuery, "kPtrQuery value is incorrect"); + static_assert(1 == kSrvQuery, "kSrvQuery value is incorrect"); + static_assert(2 == kTxtQuery, "kTxtQuery value is incorrect"); + static_assert(3 == kSrvTxtQuery, "kSrvTxtQuery value is incorrect"); + static_assert(4 == kAaaaQuery, "kAaaaQuery value is incorrect"); + + return kTypeNames[aType]; +} +#endif + +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + +Error Server::Response::ResolveBySrp(void) +{ + static const Section kSections[] = {kAnswerSection, kAdditionalDataSection}; + + Error error = kErrorNotFound; + const Srp::Server::Service *matchedService = nullptr; + bool found = false; + Section srvSection; + Section txtSection; + + mSection = kAnswerSection; for (const Srp::Server::Host &host : Get().GetHosts()) { - bool needAdditionalAaaaRecord = false; - const char *hostName = host.GetFullName(); - if (host.IsDeleted()) { continue; } - // Handle PTR/SRV/TXT query - if (qtype == ResourceRecord::kTypePtr || qtype == ResourceRecord::kTypeSrv || qtype == ResourceRecord::kTypeTxt) + if (mType == kAaaaQuery) { - for (const Srp::Server::Service &service : host.GetServices()) + if (QueryNameMatches(host.GetFullName())) { - uint32_t instanceTtl; - const char *instanceName; - bool serviceNameMatched; - bool instanceNameMatched; - bool ptrQueryMatched; - bool srvQueryMatched; - bool txtQueryMatched; + error = AppendHostAddresses(host); + ExitNow(); + } - if (service.IsDeleted()) + continue; + } + + // `mType` is PTR or SRV/TXT query + + for (const Srp::Server::Service &service : host.GetServices()) + { + if (service.IsDeleted()) + { + continue; + } + + if (mType == kPtrQuery) + { + if (QueryNameMatchesService(service)) { - continue; - } + uint32_t ttl = TimeMilli::MsecToSec(service.GetExpireTime() - TimerMilli::GetNow()); - instanceTtl = TimeMilli::MsecToSec(service.GetExpireTime() - TimerMilli::GetNow()); - instanceName = service.GetInstanceName(); - serviceNameMatched = service.MatchesServiceName(aName) || service.HasSubTypeServiceName(aName); - instanceNameMatched = service.MatchesInstanceName(aName); - ptrQueryMatched = qtype == ResourceRecord::kTypePtr && serviceNameMatched; - srvQueryMatched = qtype == ResourceRecord::kTypeSrv && instanceNameMatched; - txtQueryMatched = qtype == ResourceRecord::kTypeTxt && instanceNameMatched; - - if (ptrQueryMatched || srvQueryMatched) - { - needAdditionalAaaaRecord = true; - } - - if (!mAdditional && ptrQueryMatched) - { - SuccessOrExit(error = AppendPtrRecord(aName, instanceName, instanceTtl)); - rcode = Header::kResponseSuccess; - } - - if ((!mAdditional && srvQueryMatched) || - (mAdditional && ptrQueryMatched && !HasQuestion(instanceName, ResourceRecord::kTypeSrv))) - { - SuccessOrExit(error = AppendSrvRecord(instanceName, hostName, instanceTtl, service.GetPriority(), - service.GetWeight(), service.GetPort())); - rcode = Header::kResponseSuccess; - } - - if ((!mAdditional && txtQueryMatched) || - (mAdditional && ptrQueryMatched && !HasQuestion(instanceName, ResourceRecord::kTypeTxt))) - { - SuccessOrExit(error = AppendTxtRecord(instanceName, service.GetTxtData(), - service.GetTxtDataLength(), instanceTtl)); - rcode = Header::kResponseSuccess; + SuccessOrExit(error = AppendPtrRecord(service.GetInstanceLabel(), ttl)); + matchedService = &service; } } + else if (QueryNameMatches(service.GetInstanceName())) + { + matchedService = &service; + found = true; + break; + } } - // Handle AAAA query - if ((!mAdditional && qtype == ResourceRecord::kTypeAaaa && host.Matches(aName)) || - (mAdditional && needAdditionalAaaaRecord && !HasQuestion(hostName, ResourceRecord::kTypeAaaa))) + if (found) { - uint8_t addrNum; - const Ip6::Address *addrs = host.GetAddresses(addrNum); - uint32_t hostTtl = TimeMilli::MsecToSec(host.GetExpireTime() - now); - - for (uint8_t i = 0; i < addrNum; i++) - { - SuccessOrExit(error = AppendAaaaRecord(hostName, addrs[i], hostTtl)); - } - - rcode = Header::kResponseSuccess; + break; } } + VerifyOrExit(matchedService != nullptr); + + if (mType == kPtrQuery) + { + // 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. + + VerifyOrExit(mHeader.GetAnswerCount() == 1); + } + + srvSection = ((mType == kSrvQuery) || (mType == kSrvTxtQuery)) ? kAnswerSection : kAdditionalDataSection; + txtSection = ((mType == kTxtQuery) || (mType == kSrvTxtQuery)) ? kAnswerSection : kAdditionalDataSection; + + for (Section section : kSections) + { + mSection = section; + + if (mSection == kAdditionalDataSection) + { + VerifyOrExit(!(Get().mTestMode & kTestModeEmptyAdditionalSection)); + } + + if (srvSection == mSection) + { + SuccessOrExit(error = AppendSrvRecord(*matchedService)); + } + + if (txtSection == mSection) + { + SuccessOrExit(error = AppendTxtRecord(*matchedService)); + } + } + + SuccessOrExit(error = AppendHostAddresses(matchedService->GetHost())); + +exit: + return error; +} + +bool Server::Response::QueryNameMatchesService(const Srp::Server::Service &aService) const +{ + // Check if the query name matches the base service name or any + // sub-type service names associated with `aService`. + + bool matches = QueryNameMatches(aService.GetServiceName()); + + VerifyOrExit(!matches); + + for (uint16_t index = 0; index < aService.GetNumberOfSubTypes(); index++) + { + matches = QueryNameMatches(aService.GetSubTypeServiceNameAt(index)); + VerifyOrExit(!matches); + } + exit: - - // If there is an `error` (for example, appending to the message - // fails), we always set the response code in the header to - // `kResponseServerFailure`. Otherwise, we only set the response - // code if the entry is for the answer section, not the - // additional data section. - - if (error != kErrorNone) - { - mHeader.SetResponseCode(Header::kResponseServerFailure); - } - else if (!mAdditional) - { - mHeader.SetResponseCode(rcode); - } + return matches; } #endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE @@ -877,17 +841,14 @@ Error Server::ResolveByQueryCallbacks(Response &aResponse, const Ip6::MessageInf { Error error = kErrorNone; QueryTransaction *query = nullptr; - DnsQueryType queryType; - char name[Name::kMaxNameSize]; + DnsName name; VerifyOrExit(mQuerySubscribe.IsSet(), error = kErrorFailed); - aResponse.GetQueryTypeAndName(queryType, name); - VerifyOrExit(queryType != kDnsQueryNone, error = kErrorNotImplemented); - query = NewQuery(aResponse, aMessageInfo); VerifyOrExit(query != nullptr, error = kErrorNoBufs); + query->ReadQueryName(name); mQuerySubscribe.Invoke(name); exit: @@ -899,7 +860,7 @@ bool Server::ShouldForwardToUpstream(const Request &aRequest) { bool shouldForward = false; uint16_t readOffset; - char name[Name::kMaxNameSize]; + DnsName name; VerifyOrExit(aRequest.mHeader.IsRecursionDesiredFlagSet()); readOffset = sizeof(Header); @@ -1005,21 +966,21 @@ exit: bool Server::QueryTransaction::CanAnswer(const char *aServiceFullName, const ServiceInstanceInfo &aInstanceInfo) const { - char name[Name::kMaxNameSize]; - DnsQueryType sdType; - bool canAnswer = false; + bool canAnswer = false; - GetQueryTypeAndName(sdType, name); - - switch (sdType) + switch (mType) { - case kDnsQueryBrowse: - canAnswer = StringMatch(name, aServiceFullName, kStringCaseInsensitiveMatch); + case kPtrQuery: + canAnswer = QueryNameMatches(aServiceFullName); break; - case kDnsQueryResolve: - canAnswer = StringMatch(name, aInstanceInfo.mFullName, kStringCaseInsensitiveMatch); + + case kSrvQuery: + case kTxtQuery: + case kSrvTxtQuery: + canAnswer = QueryNameMatches(aInstanceInfo.mFullName); break; - default: + + case kAaaaQuery: break; } @@ -1028,84 +989,71 @@ bool Server::QueryTransaction::CanAnswer(const char *aServiceFullName, const Ser bool Server::QueryTransaction::CanAnswer(const char *aHostFullName) const { - char name[Name::kMaxNameSize]; - DnsQueryType sdType; - - GetQueryTypeAndName(sdType, name); - - return (sdType == kDnsQueryResolveHost) && StringMatch(name, aHostFullName, kStringCaseInsensitiveMatch); + return (mType == kAaaaQuery) && QueryNameMatches(aHostFullName); } -void Server::QueryTransaction::Answer(const char *aServiceFullName, const ServiceInstanceInfo &aInstanceInfo) +Error Server::QueryTransaction::ExtractServiceInstanceLabel(const char *aInstanceName, DnsLabel &aLabel) { - Error error = kErrorNone; + uint16_t offset; + DnsName serviceName; - mAdditional = false; + offset = mServiceOffset; + IgnoreError(Name::ReadName(*mMessage, offset, serviceName, sizeof(serviceName))); - if (HasQuestion(aServiceFullName, ResourceRecord::kTypePtr)) + return Name::ExtractLabels(aInstanceName, serviceName, aLabel, sizeof(aLabel)); +} + +void Server::QueryTransaction::Answer(const ServiceInstanceInfo &aInstanceInfo) +{ + static const Section kSections[] = {kAnswerSection, kAdditionalDataSection}; + + Error error = kErrorNone; + Section srvSection = ((mType == kSrvQuery) || (mType == kSrvTxtQuery)) ? kAnswerSection : kAdditionalDataSection; + Section txtSection = ((mType == kTxtQuery) || (mType == kSrvTxtQuery)) ? kAnswerSection : kAdditionalDataSection; + + if (mType == kPtrQuery) { - SuccessOrExit(error = AppendPtrRecord(aServiceFullName, aInstanceInfo.mFullName, aInstanceInfo.mTtl)); + DnsLabel instanceLabel; + + SuccessOrExit(error = ExtractServiceInstanceLabel(aInstanceInfo.mFullName, instanceLabel)); + mSection = kAnswerSection; + SuccessOrExit(error = AppendPtrRecord(instanceLabel, aInstanceInfo.mTtl)); } - for (uint8_t additional = 0; additional <= 1; additional++) + for (Section section : kSections) { - if (additional == 1) + mSection = section; + + if (mSection == kAdditionalDataSection) { - mAdditional = true; VerifyOrExit(!(Get().mTestMode & kTestModeEmptyAdditionalSection)); } - if (HasQuestion(aInstanceInfo.mFullName, ResourceRecord::kTypeSrv) == !additional) + if (srvSection == mSection) { - SuccessOrExit(error = AppendSrvRecord(aInstanceInfo.mFullName, aInstanceInfo.mHostName, aInstanceInfo.mTtl, - aInstanceInfo.mPriority, aInstanceInfo.mWeight, aInstanceInfo.mPort)); + SuccessOrExit(error = AppendSrvRecord(aInstanceInfo)); } - if (HasQuestion(aInstanceInfo.mFullName, ResourceRecord::kTypeTxt) == !additional) + if (txtSection == mSection) { - SuccessOrExit(error = AppendTxtRecord(aInstanceInfo.mFullName, aInstanceInfo.mTxtData, - aInstanceInfo.mTxtLength, aInstanceInfo.mTtl)); - } - - if (HasQuestion(aInstanceInfo.mHostName, ResourceRecord::kTypeAaaa) == !additional) - { - for (uint8_t i = 0; i < aInstanceInfo.mAddressNum; i++) - { - const Ip6::Address &address = AsCoreType(&aInstanceInfo.mAddresses[i]); - - OT_ASSERT(!address.IsUnspecified() && !address.IsLinkLocal() && !address.IsMulticast() && - !address.IsLoopback()); - - SuccessOrExit(error = AppendAaaaRecord(aInstanceInfo.mHostName, address, aInstanceInfo.mTtl)); - } + SuccessOrExit(error = AppendTxtRecord(aInstanceInfo)); } } + error = AppendHostAddresses(aInstanceInfo); + exit: - Finalize(error == kErrorNone ? Header::kResponseSuccess : Header::kResponseServerFailure); + Finalize(error); } -void Server::QueryTransaction::Answer(const char *aHostFullName, const HostInfo &aHostInfo) +void Server::QueryTransaction::Answer(const HostInfo &aHostInfo) { - Error error = kErrorNone; + Error error; - mAdditional = false; + mSection = kAnswerSection; + error = AppendHostAddresses(aHostInfo); - if (HasQuestion(aHostFullName, ResourceRecord::kTypeAaaa)) - { - for (uint8_t i = 0; i < aHostInfo.mAddressNum; i++) - { - const Ip6::Address &address = AsCoreType(&aHostInfo.mAddresses[i]); - - OT_ASSERT(!address.IsUnspecified() && !address.IsMulticast() && !address.IsLinkLocal() && - !address.IsLoopback()); - - SuccessOrExit(error = AppendAaaaRecord(aHostFullName, address, aHostInfo.mTtl)); - } - } - -exit: - Finalize(error == kErrorNone ? Header::kResponseSuccess : Header::kResponseServerFailure); + Finalize(error); } void Server::SetQueryCallbacks(SubscribeCallback aSubscribe, UnsubscribeCallback aUnsubscribe, void *aContext) @@ -1126,7 +1074,7 @@ void Server::HandleDiscoveredServiceInstance(const char *aServiceFullName, const { if (query.IsValid() && query.CanAnswer(aServiceFullName, aInstanceInfo)) { - query.Answer(aServiceFullName, aInstanceInfo); + query.Answer(aInstanceInfo); } } } @@ -1139,7 +1087,7 @@ void Server::HandleDiscoveredHost(const char *aHostFullName, const HostInfo &aHo { if (query.IsValid() && query.CanAnswer(aHostFullName)) { - query.Answer(aHostFullName, aHostInfo); + query.Answer(aHostInfo); } } } @@ -1179,69 +1127,27 @@ Server::DnsQueryType Server::GetQueryTypeAndName(const otDnssdQuery *aQuery, cha return type; } -void Server::Response::GetQueryTypeAndName(DnsQueryType &aType, char (&aName)[Name::kMaxNameSize]) const +void Server::Response::GetQueryTypeAndName(DnsQueryType &aType, DnsName &aName) const { - aType = kDnsQueryNone; + ReadQueryName(aName); - for (uint16_t i = 0, readOffset = sizeof(Header); i < mHeader.GetQuestionCount(); i++) + aType = kDnsQueryBrowse; + + switch (mType) { - Question question; + case kPtrQuery: + break; - IgnoreError(Name::ReadName(*mMessage, readOffset, aName, sizeof(aName))); - IgnoreError(mMessage->Read(readOffset, question)); - readOffset += sizeof(question); + case kSrvQuery: + case kTxtQuery: + case kSrvTxtQuery: + aType = kDnsQueryResolve; + break; - switch (question.GetType()) - { - case ResourceRecord::kTypePtr: - ExitNow(aType = kDnsQueryBrowse); - case ResourceRecord::kTypeSrv: - case ResourceRecord::kTypeTxt: - ExitNow(aType = kDnsQueryResolve); - } + case kAaaaQuery: + aType = kDnsQueryResolveHost; + break; } - - for (uint16_t i = 0, readOffset = sizeof(Header); i < mHeader.GetQuestionCount(); i++) - { - Question question; - - IgnoreError(Name::ReadName(*mMessage, readOffset, aName, sizeof(aName))); - IgnoreError(mMessage->Read(readOffset, question)); - readOffset += sizeof(question); - - switch (question.GetType()) - { - case ResourceRecord::kTypeAaaa: - case ResourceRecord::kTypeA: - ExitNow(aType = kDnsQueryResolveHost); - } - } - -exit: - return; -} - -bool Server::Response::HasQuestion(const char *aName, uint16_t aQuestionType) const -{ - bool found = false; - - for (uint16_t i = 0, readOffset = sizeof(Header); i < mHeader.GetQuestionCount(); i++) - { - Question question; - Error error; - - error = Name::CompareName(*mMessage, readOffset, aName); - IgnoreError(mMessage->Read(readOffset, question)); - readOffset += sizeof(question); - - if ((error == kErrorNone) && (aQuestionType == question.GetType())) - { - ExitNow(found = true); - } - } - -exit: - return found; } void Server::HandleTimer(void) @@ -1258,7 +1164,7 @@ void Server::HandleTimer(void) if (query.mExpireTime <= now) { - query.Finalize(Header::kResponseSuccess); + query.Finalize(kErrorNone); } else { @@ -1291,19 +1197,14 @@ void Server::HandleTimer(void) } } -void Server::QueryTransaction::Finalize(Header::Response aResponseCode) +void Server::QueryTransaction::Finalize(Error aError) { - char name[Name::kMaxNameSize]; - DnsQueryType sdType; - - GetQueryTypeAndName(sdType, name); - - OT_ASSERT(sdType != kDnsQueryNone); - OT_UNUSED_VARIABLE(sdType); + DnsName name; + ReadQueryName(name); Get().mQueryUnsubscribe.InvokeIfSet(name); - mHeader.SetResponseCode(aResponseCode); + mHeader.SetResponseCode((aError == kErrorNone) ? Header::kResponseSuccess : Header::kResponseServerFailure); Send(mMessageInfo); // Set the `mMessage` to null to indicate that @@ -1311,7 +1212,7 @@ void Server::QueryTransaction::Finalize(Header::Response aResponseCode) mMessage = nullptr; } -void Server::UpdateResponseCounters(Header::Response aResponseCode) +void Server::UpdateResponseCounters(ResponseCode aResponseCode) { switch (aResponseCode) { diff --git a/src/core/net/dnssd_server.hpp b/src/core/net/dnssd_server.hpp index f5224ea3c..d442d3cbc 100644 --- a/src/core/net/dnssd_server.hpp +++ b/src/core/net/dnssd_server.hpp @@ -291,160 +291,99 @@ public: void SetTestMode(uint8_t aTestMode) { mTestMode = aTestMode; } private: - class NameCompressInfo : public Clearable - { - public: - static constexpr uint16_t kUnknownOffset = 0; // Unknown offset value (used when offset is not yet set). - - NameCompressInfo(void) { Clear(); } - - uint16_t GetDomainNameOffset(void) const { return mDomainNameOffset; } - - void SetDomainNameOffset(uint16_t aOffset) { mDomainNameOffset = aOffset; } - - uint16_t GetServiceNameOffset(const Message &aMessage, const char *aServiceName) const - { - return MatchCompressedName(aMessage, mServiceNameOffset, aServiceName) - ? mServiceNameOffset - : static_cast(kUnknownOffset); - }; - - void SetServiceNameOffset(uint16_t aOffset) - { - if (mServiceNameOffset == kUnknownOffset) - { - mServiceNameOffset = aOffset; - } - } - - uint16_t GetInstanceNameOffset(const Message &aMessage, const char *aName) const - { - return MatchCompressedName(aMessage, mInstanceNameOffset, aName) ? mInstanceNameOffset - : static_cast(kUnknownOffset); - } - - void SetInstanceNameOffset(uint16_t aOffset) - { - if (mInstanceNameOffset == kUnknownOffset) - { - mInstanceNameOffset = aOffset; - } - } - - uint16_t GetHostNameOffset(const Message &aMessage, const char *aName) const - { - return MatchCompressedName(aMessage, mHostNameOffset, aName) ? mHostNameOffset - : static_cast(kUnknownOffset); - } - - void SetHostNameOffset(uint16_t aOffset) - { - if (mHostNameOffset == kUnknownOffset) - { - mHostNameOffset = aOffset; - } - } - - private: - static bool MatchCompressedName(const Message &aMessage, uint16_t aOffset, const char *aName) - { - return aOffset != kUnknownOffset && Name::CompareName(aMessage, aOffset, aName) == kErrorNone; - } - - uint16_t mDomainNameOffset; // Offset of domain name serialization into the response message. - uint16_t mServiceNameOffset; // Offset of service name serialization into the response message. - uint16_t mInstanceNameOffset; // Offset of instance name serialization into the response message. - uint16_t mHostNameOffset; // Offset of host name serialization into the response message. - }; - static constexpr bool kBindUnspecifiedNetif = OPENTHREAD_CONFIG_DNSSD_SERVER_BIND_UNSPECIFIED_NETIF; static constexpr uint8_t kProtocolLabelLength = 4; static constexpr uint8_t kSubTypeLabelLength = 4; static constexpr uint16_t kMaxConcurrentQueries = 32; static constexpr uint16_t kMaxConcurrentUpstreamQueries = 32; - // This structure represents the splitting information of a full name. - struct NameComponentsOffsetInfo + typedef Header::Response ResponseCode; + typedef char DnsName[Name::kMaxNameSize]; + typedef char DnsLabel[Name::kMaxLabelSize]; + + enum QueryType : uint8_t { - static constexpr uint8_t kNotPresent = 0xff; // Indicates the component is not present. + kPtrQuery, + kSrvQuery, + kTxtQuery, + kSrvTxtQuery, + kAaaaQuery, + }; - explicit NameComponentsOffsetInfo(void) - : mDomainOffset(kNotPresent) - , mProtocolOffset(kNotPresent) - , mServiceOffset(kNotPresent) - , mSubTypeOffset(kNotPresent) - , mInstanceOffset(kNotPresent) - { - } - - bool IsServiceInstanceName(void) const { return mInstanceOffset != kNotPresent; } - - bool IsServiceName(void) const { return mServiceOffset != kNotPresent && mInstanceOffset == kNotPresent; } - - bool IsHostName(void) const { return mProtocolOffset == kNotPresent && mDomainOffset != 0; } - - uint8_t mDomainOffset; // Offset to . - uint8_t mProtocolOffset; // Offset to (i.e. _tcp or _udp) or `kNotPresent` if not service name. - uint8_t mServiceOffset; // Offset to or `kNotPresent` if not service or instance. - uint8_t mSubTypeOffset; // Offset to sub-type label or `kNotPresent` is not a sub-type. - uint8_t mInstanceOffset; // Offset to or `kNotPresent` if the name is not a instance. + enum Section : uint8_t + { + kAnswerSection, + kAdditionalDataSection, }; struct Request { + ResponseCode ParseQuestions(uint8_t aTestMode); + const Message *mMessage; const Ip6::MessageInfo *mMessageInfo; Header mHeader; + QueryType mType; }; - struct Response : public GetProvider + class Response : public GetProvider, public Clearable { - Response(void) - : mMessage(nullptr) - , mAdditional(false) - { - } - - Instance &GetInstance(void) const { return mMessage->GetInstance(); } - - Error AddQuestionsFrom(const Request &aRequest); - Error AppendQuestion(const char *aName, const Question &aQuestion); - Error AppendPtrRecord(const char *aServiceName, const char *aInstanceName, uint32_t aTtl); - Error AppendSrvRecord(const char *aInstanceName, - const char *aHostName, - uint32_t aTtl, - uint16_t aPriority, - uint16_t aWeight, - uint16_t aPort); - Error AppendTxtRecord(const char *aInstanceName, const void *aTxtData, uint16_t aTxtLength, uint32_t aTtl); - Error AppendAaaaRecord(const char *aHostName, const Ip6::Address &aAddress, uint32_t aTtl); - Error AppendServiceName(const char *aName); - Error AppendInstanceName(const char *aName); - Error AppendHostName(const char *aName); - void IncResourceRecordCount(void); - bool HasQuestion(const char *aName, uint16_t aQuestionType) const; - void Send(const Ip6::MessageInfo &aMessageInfo); - void GetQueryTypeAndName(DnsQueryType &aType, char (&aName)[Name::kMaxNameSize]) const; - + public: + Response(void) { Clear(); } + Instance &GetInstance(void) const { return mMessage->GetInstance(); } + void SetResponseCode(ResponseCode aResponseCode) { mHeader.SetResponseCode(aResponseCode); } + ResponseCode AddQuestionsFrom(const Request &aRequest); + Error ParseQueryName(void); + void ReadQueryName(DnsName &aName) const; + bool QueryNameMatches(const char *aName) const; + Error AppendQueryName(void) const; + Error AppendPtrRecord(const char *aInstanceLabel, uint32_t aTtl); + Error AppendSrvRecord(const ServiceInstanceInfo &aInstanceInfo); + Error AppendSrvRecord(const char *aHostName, + uint32_t aTtl, + uint16_t aPriority, + uint16_t aWeight, + uint16_t aPort); + Error AppendTxtRecord(const ServiceInstanceInfo &aInstanceInfo); + Error AppendTxtRecord(const void *aTxtData, uint16_t aTxtLength, uint32_t aTtl); + Error AppendHostAddresses(const HostInfo &aHostInfo); + Error AppendHostAddresses(const ServiceInstanceInfo &aInstanceInfo); + Error AppendHostAddresses(const Ip6::Address *aAddrs, uint16_t aAddrsLength, uint32_t aTtl); + void UpdateRecordLength(ResourceRecord &aRecord, uint16_t aOffset) const; + void IncResourceRecordCount(void); + void Send(const Ip6::MessageInfo &aMessageInfo); + void GetQueryTypeAndName(DnsQueryType &aType, DnsName &aName) const; #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE - void ResolveBySrp(void); - void ResolveQuestionBySrp(const char *aName, const Question &aQuestion); + Error ResolveBySrp(void); + bool QueryNameMatchesService(const Srp::Server::Service &aService) const; + Error AppendSrvRecord(const Srp::Server::Service &aService); + Error AppendTxtRecord(const Srp::Server::Service &aService); + Error AppendHostAddresses(const Srp::Server::Host &aHost); +#endif +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) + void Log(void) const; + static const char *QueryTypeToString(QueryType aType); #endif - Message *mMessage; - Header mHeader; - NameCompressInfo mCompressInfo; - bool mAdditional; // Whether or not appending new records in additional data section. + Message *mMessage; + Header mHeader; + QueryType mType; + Section mSection; + uint16_t mDomainOffset; + uint16_t mServiceOffset; + uint16_t mInstanceOffset; + uint16_t mHostOffset; }; struct QueryTransaction : public Response { - bool IsValid(void) const { return mMessage != nullptr; } - bool CanAnswer(const char *aServiceFullName, const ServiceInstanceInfo &aInstanceInfo) const; - bool CanAnswer(const char *aHostFullName) const; - void Answer(const char *aServiceFullName, const ServiceInstanceInfo &aInstanceInfo); - void Answer(const char *aHostFullName, const HostInfo &aHostInfo); - void Finalize(Header::Response aResponseCode); + bool IsValid(void) const { return mMessage != nullptr; } + Error ExtractServiceInstanceLabel(const char *aInstanceName, DnsLabel &aLabel); + bool CanAnswer(const char *aServiceFullName, const ServiceInstanceInfo &aInstanceInfo) const; + bool CanAnswer(const char *aHostFullName) const; + void Answer(const ServiceInstanceInfo &aInstanceInfo); + void Answer(const HostInfo &aHostInfo); + void Finalize(Error aError); Ip6::MessageInfo mMessageInfo; TimeMilli mExpireTime; @@ -452,12 +391,11 @@ private: static constexpr uint32_t kQueryTimeout = OPENTHREAD_CONFIG_DNSSD_QUERY_TIMEOUT; - bool IsRunning(void) const { return mSocket.IsBound(); } - static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); - void HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); - void ProcessQuery(const Request &aRequest); - static Error FindNameComponents(const char *aName, const char *aDomain, NameComponentsOffsetInfo &aInfo); - static Error FindPreviousLabel(const char *aName, uint8_t &aStart, uint8_t &aStop); + bool IsRunning(void) const { return mSocket.IsBound(); } + static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); + void HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + void ProcessQuery(Request &aRequest); + static uint8_t GetNameLength(const char *aName); #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE static bool ShouldForwardToUpstream(const Request &aRequest); @@ -472,14 +410,15 @@ private: void HandleTimer(void); void ResetTimer(void); - void UpdateResponseCounters(Header::Response aResponseCode); + void UpdateResponseCounters(ResponseCode aResponseCode); using ServerTimer = TimerMilliIn; - static const char kDnssdProtocolUdp[]; - static const char kDnssdProtocolTcp[]; - static const char kDnssdSubTypeLabel[]; static const char kDefaultDomainName[]; + static const char kSubLabel[]; +#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE + static const char *kBlockedDomains[]; +#endif Ip6::Udp::Socket mSocket; @@ -487,7 +426,6 @@ private: Callback mQuerySubscribe; Callback mQueryUnsubscribe; - static const char *kBlockedDomains[]; #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE bool mEnableUpstreamQuery; UpstreamQueryTransaction mUpstreamQueryTransactions[kMaxConcurrentUpstreamQueries]; diff --git a/tests/unit/test_dns.cpp b/tests/unit/test_dns.cpp index 7a87b25ea..2a2597efb 100644 --- a/tests/unit/test_dns.cpp +++ b/tests/unit/test_dns.cpp @@ -68,6 +68,8 @@ void TestDnsName(void) const char *subDomain; const char *domain; const char *domain2; + const char *fullName; + const char *suffixName; static const uint8_t kEncodedName1[] = {7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0}; static const uint8_t kEncodedName2[] = {3, 'f', 'o', 'o', 1, 'a', 2, 'b', 'b', 3, 'e', 'd', 'u', 0}; @@ -247,6 +249,57 @@ void TestDnsName(void) domain2 = ".example.com."; VerifyOrQuit(!Dns::Name::IsSameDomain(domain, domain2)); + printf("----------------------------------------------------------------\n"); + printf("Extracting label(s) and removing domains:\n"); + + fullName = "my-service._ipps._tcp.default.service.arpa."; + suffixName = "default.service.arpa."; + SuccessOrQuit(Dns::Name::ExtractLabels(fullName, suffixName, name, sizeof(name))); + VerifyOrQuit(strcmp(name, "my-service._ipps._tcp") == 0); + + fullName = "my.service._ipps._tcp.default.service.arpa."; + suffixName = "_ipps._tcp.default.service.arpa."; + SuccessOrQuit(Dns::Name::ExtractLabels(fullName, suffixName, name, sizeof(name))); + VerifyOrQuit(strcmp(name, "my.service") == 0); + + fullName = "my-service._ipps._tcp.default.service.arpa."; + suffixName = "DeFault.SerVice.ARPA."; + SuccessOrQuit(Dns::Name::ExtractLabels(fullName, suffixName, name, sizeof(name))); + VerifyOrQuit(strcmp(name, "my-service._ipps._tcp") == 0); + + fullName = "my-service._ipps._tcp.default.service.arpa."; + suffixName = "efault.service.arpa."; + VerifyOrQuit(Dns::Name::ExtractLabels(fullName, suffixName, name, sizeof(name)) == kErrorParse); + + fullName = "my-service._ipps._tcp.default.service.arpa."; + suffixName = "xdefault.service.arpa."; + VerifyOrQuit(Dns::Name::ExtractLabels(fullName, suffixName, name, sizeof(name)) == kErrorParse); + + fullName = "my-service._ipps._tcp.default.service.arpa."; + suffixName = ".default.service.arpa."; + VerifyOrQuit(Dns::Name::ExtractLabels(fullName, suffixName, name, sizeof(name)) == kErrorParse); + + fullName = "my-service._ipps._tcp.default.service.arpa."; + suffixName = "default.service.arp."; + VerifyOrQuit(Dns::Name::ExtractLabels(fullName, suffixName, name, sizeof(name)) == kErrorParse); + + fullName = "default.service.arpa."; + suffixName = "default.service.arpa."; + VerifyOrQuit(Dns::Name::ExtractLabels(fullName, suffixName, name, sizeof(name)) == kErrorParse); + + fullName = "efault.service.arpa."; + suffixName = "default.service.arpa."; + VerifyOrQuit(Dns::Name::ExtractLabels(fullName, suffixName, name, sizeof(name)) == kErrorParse); + + fullName = "my-service._ipps._tcp.default.service.arpa."; + suffixName = "default.service.arpa."; + SuccessOrQuit(Dns::Name::ExtractLabels(fullName, suffixName, name, 22)); + VerifyOrQuit(strcmp(name, "my-service._ipps._tcp") == 0); + + fullName = "my-service._ipps._tcp.default.service.arpa."; + suffixName = "default.service.arpa."; + VerifyOrQuit(Dns::Name::ExtractLabels(fullName, suffixName, name, 21) == kErrorNoBufs); + printf("----------------------------------------------------------------\n"); printf("Append names, check encoded bytes, parse name and read labels:\n"); diff --git a/tests/unit/test_dns_client.cpp b/tests/unit/test_dns_client.cpp index 79b70ef0d..456489ae7 100644 --- a/tests/unit/test_dns_client.cpp +++ b/tests/unit/test_dns_client.cpp @@ -242,9 +242,10 @@ static const char kService1Name[] = "_srv._udp"; static const char kService1FullName[] = "_srv._udp.default.service.arpa."; static const char kInstance1Label[] = "srv-instance"; -static const char kService2Name[] = "_game._udp"; -static const char kService2FullName[] = "_game._udp.default.service.arpa."; -static const char kInstance2Label[] = "last-ninja"; +static const char kService2Name[] = "_game._udp"; +static const char kService2FullName[] = "_game._udp.default.service.arpa."; +static const char kService2SubTypeFullName[] = "_best._sub._game._udp.default.service.arpa."; +static const char kInstance2Label[] = "last-ninja"; void PrepareService1(Srp::Client::Service &aService) { @@ -277,7 +278,7 @@ void PrepareService1(Srp::Client::Service &aService) void PrepareService2(Srp::Client::Service &aService) { - static const char kSub4[] = "_44444444"; + static const char kSub4[] = "_best"; static const char *kSubLabels2[] = {kSub4, nullptr}; memset(&aService, 0, sizeof(aService)); @@ -598,6 +599,15 @@ void TestDnsClient(void) SuccessOrQuit(sBrowseInfo.mError); VerifyOrQuit(sBrowseInfo.mNumInstances == 1); + sBrowseInfo.Reset(); + + Log("Browse(%s)", kService2SubTypeFullName); + SuccessOrQuit(dnsClient->Browse(kService2SubTypeFullName, BrowseCallback, sInstance)); + AdvanceTime(100); + VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); + SuccessOrQuit(sBrowseInfo.mError); + VerifyOrQuit(sBrowseInfo.mNumInstances == 1); + sBrowseInfo.Reset(); Log("Browse() for unknown service"); SuccessOrQuit(dnsClient->Browse("_unknown._udp.default.service.arpa.", BrowseCallback, sInstance));