blob: c67fa3a00536c05cb5bf0818e4d64991a5280d54 [file]
/*
* Copyright (c) 2021, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements the DNS-SD server.
*/
#include "dnssd_server.hpp"
#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
#include "instance/instance.hpp"
namespace ot {
namespace Dns {
namespace ServiceDiscovery {
RegisterLogModule("DnssdServer");
const char Server::kDefaultDomainName[] = "default.service.arpa.";
const char Server::kSubLabel[] = "_sub";
const char Server::kMdnsDomainName[] = "local.";
#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
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)
#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE
, mDiscoveryProxy(aInstance)
#endif
#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
, mEnableUpstreamQuery(false)
#endif
, mTimer(aInstance)
, mTestMode(kTestModeDisabled)
{
mCounters.Clear();
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE || OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE
ClearAllBytes(mSoaServerName);
#endif
}
Error Server::Start(void)
{
Error error = kErrorNone;
VerifyOrExit(!IsRunning());
SuccessOrExit(error = mSocket.Open(kBindUnspecifiedNetif ? Ip6::kNetifUnspecified : Ip6::kNetifThreadInternal));
SuccessOrExit(error = mSocket.Bind(kPort));
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
Get<Srp::Server>().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
mDiscoveryProxy.UpdateState();
#endif
exit:
if (error != kErrorNone)
{
IgnoreError(mSocket.Close());
}
return error;
}
void Server::Stop(void)
{
for (ProxyQuery &query : mProxyQueries)
{
Finalize(query, Header::kResponseServerFailure);
}
#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE
mDiscoveryProxy.Stop();
#endif
#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
for (UpstreamQueryTransaction &txn : mUpstreamQueryTransactions)
{
if (txn.IsValid())
{
ResetUpstreamQueryTransaction(txn, kErrorFailed);
}
}
#endif
mTimer.Stop();
IgnoreError(mSocket.Close());
LogInfo("Stopped");
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
Get<Srp::Server>().HandleDnssdServerStateChange();
#endif
}
void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Request request;
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
// We first let the `Srp::Server` process the received message.
// It returns `kErrorNone` to indicate that it successfully
// processed the message.
VerifyOrExit(Get<Srp::Server>().HandleDnssdServerUdpReceive(aMessage, aMessageInfo) != kErrorNone);
#endif
request.mMessage = &aMessage;
request.mMessageInfo = &aMessageInfo;
SuccessOrExit(aMessage.Read(aMessage.GetOffset(), request.mHeader));
VerifyOrExit(request.mHeader.GetType() == Header::kTypeQuery);
LogInfo("Received query from %s", aMessageInfo.GetPeerAddr().ToString().AsCString());
ProcessQuery(request);
exit:
return;
}
void Server::ProcessQuery(Request &aRequest)
{
ResponseCode rcode = Header::kResponseSuccess;
bool shouldRespond = true;
Response response(GetInstance());
#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
if (ShouldForwardToUpstream(aRequest))
{
if (ResolveByUpstream(aRequest) == kErrorNone)
{
ExitNow();
}
rcode = Header::kResponseServerFailure;
// Continue to allocate and prepare the response message
// to send the `kResponseServerFailure` response code.
}
#endif
SuccessOrExit(response.AllocateAndInitFrom(aRequest));
#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
// Forwarding the query to the upstream may have already set the
// response error code.
SuccessOrExit(rcode);
#endif
SuccessOrExit(rcode = aRequest.ParseQuestions(mTestMode, shouldRespond));
SuccessOrExit(rcode = response.AddQuestionsFrom(aRequest));
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
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())
{
case kErrorNone:
mCounters.mResolvedBySrp++;
ExitNow();
case kErrorNotFound:
rcode = Header::kResponseNameError;
break;
default:
rcode = Header::kResponseServerFailure;
ExitNow();
}
#endif
// `ResolveByProxy` may take ownership of `response.mMessage` and
// setting it to `nullptr`. In such a case, the `response.Send()`
// call will effectively do nothing.
ResolveByProxy(response, *aRequest.mMessageInfo);
exit:
if (rcode != Header::kResponseSuccess)
{
response.SetResponseCode(rcode);
}
if (shouldRespond)
{
response.Send(*aRequest.mMessageInfo);
}
}
Server::Response::Response(Instance &aInstance)
: InstanceLocator(aInstance)
{
// `mHeader` constructors already clears it
mOffsets.Clear();
}
Error Server::Response::AllocateAndInitFrom(const Request &aRequest)
{
Error error = kErrorNone;
mMessage.Reset(Get<Server>().mSocket.NewMessage());
VerifyOrExit(!mMessage.IsNull(), error = kErrorNoBufs);
mHeader.SetType(Header::kTypeResponse);
mHeader.SetMessageId(aRequest.mHeader.GetMessageId());
mHeader.SetQueryType(aRequest.mHeader.GetQueryType());
if (aRequest.mHeader.IsRecursionDesiredFlagSet())
{
mHeader.SetRecursionDesiredFlag();
}
// Append the empty header to reserve room for it in the message.
// Header will be updated in the message before sending it.
error = mMessage->Append(mHeader);
exit:
if (error != kErrorNone)
{
mMessage.Free();
}
return error;
}
void Server::Response::Send(const Ip6::MessageInfo &aMessageInfo)
{
ResponseCode rcode = mHeader.GetResponseCode();
VerifyOrExit(!mMessage.IsNull());
if (rcode == Header::kResponseServerFailure)
{
mHeader.SetQuestionCount(0);
mHeader.SetAnswerCount(0);
mHeader.SetAdditionalRecordCount(0);
IgnoreError(mMessage->SetLength(sizeof(Header)));
}
mMessage->Write(0, mHeader);
SuccessOrExit(Get<Server>().mSocket.SendTo(*mMessage, aMessageInfo));
// When `SendTo()` returns success it takes over ownership of
// the given message, so we release ownership of `mMessage`.
mMessage.Release();
LogInfo("Send response, rcode:%u", rcode);
Get<Server>().UpdateResponseCounters(rcode);
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
// determine the `QueryType`.
ResponseCode rcode = Header::kResponseFormatError;
uint16_t offset = sizeof(Header);
uint16_t questionCount = mHeader.GetQuestionCount();
Question question;
aShouldRespond = true;
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);
mQuestions.mFirstRrType = question.GetType();
if (questionCount > 1)
{
VerifyOrExit(!(aTestMode & kTestModeRejectMultiQuestionQuery));
VerifyOrExit(!(aTestMode & kTestModeIgnoreMultiQuestionQuery), aShouldRespond = false);
VerifyOrExit(questionCount == 2);
// Allow SRV and TXT questions for the same service
// instance name in the same query.
SuccessOrExit(Name::CompareName(*mMessage, offset, *mMessage, sizeof(Header)));
SuccessOrExit(mMessage->Read(offset, question));
mQuestions.mSecondRrType = question.GetType();
VerifyOrExit(mQuestions.IsFor(kRrTypeSrv) && mQuestions.IsFor(kRrTypeTxt));
}
rcode = Header::kResponseSuccess;
exit:
return rcode;
}
Server::ResponseCode Server::Response::AddQuestionsFrom(const Request &aRequest)
{
ResponseCode rcode = Header::kResponseServerFailure;
uint16_t offset;
mQuestions = aRequest.mQuestions;
// 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, kQueryNameOffset).AppendTo(*mMessage));
mHeader.SetQuestionCount(aRequest.mHeader.GetQuestionCount());
offset = sizeof(Header);
for (uint16_t questionCount = 0; questionCount < mHeader.GetQuestionCount(); questionCount++)
{
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));
}
// Check the name to include the correct domain name and determine
// the domain name offset (for DNS name compression).
rcode = (ParseQueryName() == kErrorNone) ? Header::kResponseSuccess : Header::kResponseNameError;
exit:
if (rcode == Header::kResponseServerFailure)
{
// If we fail to add questions to the response message
// (no buffer), we clear the question count in the header and
// just include the header in the message.
mHeader.SetQuestionCount(0);
IgnoreError(mMessage->SetLength(sizeof(Header)));
}
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
// offsets, and validates that the query name is for
// `kDefaultDomainName` ("default.service.arpa.").
Error error = kErrorNone;
Name::Buffer name;
uint16_t offset;
offset = sizeof(Header);
SuccessOrExit(error = Name::ReadName(*mMessage, offset, name));
// `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 = kQueryNameOffset;
while (true)
{
Name::LabelBuffer label;
uint8_t labelLength = sizeof(label);
uint16_t comapreOffset;
comapreOffset = offset;
if (Name::CompareName(*mMessage, comapreOffset, kDefaultDomainName) == kErrorNone)
{
mOffsets.mDomainName = offset;
ExitNow();
}
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;
}
void Server::Response::ReadQueryName(Name::Buffer &aName) const { Server::ReadQueryName(*mMessage, aName); }
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
Error Server::Response::AppendPtrRecord(const Srp::Server::Service &aService)
{
uint32_t ttl = TimeMilli::MsecToSec(aService.GetExpireTime() - TimerMilli::GetNow());
return AppendPtrRecord(aService.GetInstanceLabel(), ttl);
}
#endif
Error Server::Response::AppendPtrRecord(const char *aInstanceLabel, uint32_t aTtl)
{
Error error;
uint16_t recordOffset;
PtrRecord ptrRecord;
ptrRecord.Init();
ptrRecord.SetTtl(aTtl);
SuccessOrExit(error = AppendQueryName());
recordOffset = mMessage->GetLength();
SuccessOrExit(error = mMessage->Append(ptrRecord));
mOffsets.mInstanceName = mMessage->GetLength();
SuccessOrExit(error = Name::AppendLabel(aInstanceLabel, *mMessage));
SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mServiceName, *mMessage));
ResourceRecord::UpdateRecordLengthInMessage(*mMessage, recordOffset);
IncResourceRecordCount();
exit:
return error;
}
#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)
{
Error error = kErrorNone;
SrvRecord srvRecord;
uint16_t recordOffset;
Name::Buffer hostLabels;
uint16_t nameOffset;
SuccessOrExit(error = Name::ExtractLabels(aHostName, kDefaultDomainName, hostLabels));
srvRecord.Init();
srvRecord.SetTtl(aTtl);
srvRecord.SetPriority(aPriority);
srvRecord.SetWeight(aWeight);
srvRecord.SetPort(aPort);
nameOffset = mQuestions.IsFor(kRrTypeAny) ? kQueryNameOffset : mOffsets.mInstanceName;
SuccessOrExit(error = Name::AppendPointerLabel(nameOffset, *mMessage));
recordOffset = mMessage->GetLength();
SuccessOrExit(error = mMessage->Append(srvRecord));
mOffsets.mHostName = mMessage->GetLength();
SuccessOrExit(error = Name::AppendMultipleLabels(hostLabels, *mMessage));
SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mDomainName, *mMessage));
ResourceRecord::UpdateRecordLengthInMessage(*mMessage, recordOffset);
IncResourceRecordCount();
exit:
return error;
}
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
Error Server::Response::AppendHostAddresses(const Srp::Server::Service &aService)
{
return AppendHostAddresses(aService.GetHost());
}
Error Server::Response::AppendHostAddresses(const Srp::Server::Host &aHost)
{
const Ip6::Address *addrs;
uint8_t addrsLength;
uint32_t ttl;
addrs = aHost.GetAddresses(addrsLength);
ttl = TimeMilli::MsecToSec(aHost.GetExpireTime() - TimerMilli::GetNow());
return AppendHostAddresses(kIp6AddrType, addrs, addrsLength, ttl);
}
#endif
Error Server::Response::AppendHostAddresses(AddrType aAddrType, const HostInfo &aHostInfo)
{
return AppendHostAddresses(aAddrType, AsCoreTypePtr(aHostInfo.mAddresses), aHostInfo.mAddressNum, aHostInfo.mTtl);
}
Error Server::Response::AppendHostAddresses(const ServiceInstanceInfo &aInstanceInfo)
{
return AppendHostAddresses(kIp6AddrType, AsCoreTypePtr(aInstanceInfo.mAddresses), aInstanceInfo.mAddressNum,
aInstanceInfo.mTtl);
}
Error Server::Response::AppendHostAddresses(AddrType aAddrType,
const Ip6::Address *aAddrs,
uint16_t aAddrsLength,
uint32_t aTtl)
{
Error error = kErrorNone;
for (uint16_t index = 0; index < aAddrsLength; index++)
{
const Ip6::Address &address = aAddrs[index];
switch (aAddrType)
{
case kIp6AddrType:
SuccessOrExit(error = AppendAaaaRecord(address, aTtl));
break;
case kIp4AddrType:
SuccessOrExit(error = AppendARecord(address, aTtl));
break;
}
}
exit:
return error;
}
Error Server::Response::AppendAaaaRecord(const Ip6::Address &aAddress, uint32_t aTtl)
{
Error error = kErrorNone;
AaaaRecord aaaaRecord;
uint16_t nameOffset;
VerifyOrExit(!aAddress.IsIp4Mapped());
aaaaRecord.Init();
aaaaRecord.SetTtl(aTtl);
aaaaRecord.SetAddress(aAddress);
nameOffset = mQuestions.IsFor(kRrTypeAny) ? kQueryNameOffset : mOffsets.mHostName;
SuccessOrExit(error = Name::AppendPointerLabel(nameOffset, *mMessage));
SuccessOrExit(error = mMessage->Append(aaaaRecord));
IncResourceRecordCount();
exit:
return error;
}
Error Server::Response::AppendARecord(const Ip6::Address &aAddress, uint32_t aTtl)
{
Error error = kErrorNone;
ARecord aRecord;
Ip4::Address ip4Address;
uint16_t nameOffset;
SuccessOrExit(ip4Address.ExtractFromIp4MappedIp6Address(aAddress));
aRecord.Init();
aRecord.SetTtl(aTtl);
aRecord.SetAddress(ip4Address);
nameOffset = mQuestions.IsFor(kRrTypeAny) ? kQueryNameOffset : mOffsets.mHostName;
SuccessOrExit(error = Name::AppendPointerLabel(nameOffset, *mMessage));
SuccessOrExit(error = mMessage->Append(aRecord));
IncResourceRecordCount();
exit:
return error;
}
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
Error Server::Response::AppendTxtRecord(const Srp::Server::Service &aService)
{
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;
uint16_t nameOffset;
uint8_t emptyTxt = 0;
if (aTxtLength == 0)
{
aTxtData = &emptyTxt;
aTxtLength = sizeof(emptyTxt);
}
txtRecord.Init();
txtRecord.SetTtl(aTtl);
txtRecord.SetLength(aTxtLength);
nameOffset = mQuestions.IsFor(kRrTypeAny) ? kQueryNameOffset : mOffsets.mInstanceName;
SuccessOrExit(error = Name::AppendPointerLabel(nameOffset, *mMessage));
SuccessOrExit(error = mMessage->Append(txtRecord));
SuccessOrExit(error = mMessage->AppendBytes(aTxtData, aTxtLength));
IncResourceRecordCount();
exit:
return error;
}
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
Error Server::Response::AppendKeyRecord(const Srp::Server::Host &aHost)
{
Ecdsa256KeyRecord keyRecord;
RecordData keyData;
uint32_t ttl;
keyRecord.Init();
keyRecord.SetFlags(KeyRecord::kAuthConfidPermitted, KeyRecord::kOwnerNonZone, KeyRecord::kSignatoryFlagGeneral);
keyRecord.SetProtocol(KeyRecord::kProtocolDnsSec);
keyRecord.SetAlgorithm(KeyRecord::kAlgorithmEcdsaP256Sha256);
keyRecord.SetLength(sizeof(Ecdsa256KeyRecord) - sizeof(ResourceRecord));
keyRecord.SetKey(aHost.GetKey());
keyData.InitFrom(keyRecord);
ttl = TimeMilli::MsecToSec(aHost.GetExpireTime() - TimerMilli::GetNow());
return AppendGenericRecord(Ecdsa256KeyRecord::kType, keyData, ttl);
}
#endif
Error Server::Response::AppendGenericRecord(uint16_t aRrType, const RecordData &aData, uint32_t aTtl)
{
Error error = kErrorNone;
ResourceRecord record;
uint16_t recordOffset;
record.Init(aRrType);
record.SetTtl(aTtl);
SuccessOrExit(error = Name::AppendPointerLabel(kQueryNameOffset, *mMessage));
recordOffset = mMessage->GetLength();
SuccessOrExit(error = mMessage->Append(record));
SuccessOrExit(error = ResourceRecord::AppendTranslatedRecordDataTo(*mMessage, aRrType, aData, kMdnsDomainName,
mOffsets.mDomainName));
ResourceRecord::UpdateRecordLengthInMessage(*mMessage, recordOffset);
IncResourceRecordCount();
exit:
return error;
}
template <typename ServiceType> Error Server::Response::AppendServiceRecords(const ServiceType &aService)
{
static const Section kSections[] = {kAnswerSection, kAdditionalDataSection};
// Append SRV and TXT records along with associated host AAAA addresses
// in the proper sections.
Error error = kErrorNone;
for (Section section : kSections)
{
mSection = section;
if (mSection == kAdditionalDataSection)
{
VerifyOrExit(!mQuestions.IsFor(kRrTypeAny));
VerifyOrExit(!(Get<Server>().mTestMode & kTestModeEmptyAdditionalSection));
}
if (mSection == mQuestions.SectionFor(kRrTypeSrv))
{
SuccessOrExit(error = AppendSrvRecord(aService));
}
if (mSection == mQuestions.SectionFor(kRrTypeTxt))
{
SuccessOrExit(error = AppendTxtRecord(aService));
}
}
error = AppendHostAddresses(aService);
exit:
return error;
}
void Server::Response::IncResourceRecordCount(void)
{
switch (mSection)
{
case kAnswerSection:
mHeader.SetAnswerCount(mHeader.GetAnswerCount() + 1);
break;
case kAuthoritySection:
mHeader.SetAuthorityRecordCount(mHeader.GetAuthorityRecordCount() + 1);
break;
case kAdditionalDataSection:
mHeader.SetAdditionalRecordCount(mHeader.GetAdditionalRecordCount() + 1);
break;
}
}
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
void Server::Response::Log(void) const
{
Name::Buffer name;
bool hasTwoQuestions = (mQuestions.mSecondRrType != 0);
ReadQueryName(name);
LogInfo("%s%s%s query for '%s'", ResourceRecord::TypeToString(mQuestions.mFirstRrType).AsCString(),
hasTwoQuestions ? " and " : "",
hasTwoQuestions ? ResourceRecord::TypeToString(mQuestions.mSecondRrType).AsCString() : "", name);
}
#endif
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
Error Server::Response::ResolveBySrp(void)
{
Error error = kErrorNone;
const Srp::Server::Service *matchedService = nullptr;
mSection = kAnswerSection;
for (const Srp::Server::Host &host : Get<Srp::Server>().GetHosts())
{
if (host.IsDeleted())
{
continue;
}
if (QueryNameMatches(host.GetFullName()))
{
error = ResolveUsingSrpHost(host);
ExitNow();
}
for (const Srp::Server::Service &service : host.GetServices())
{
if (service.IsDeleted())
{
continue;
}
if (QueryNameMatches(service.GetInstanceName()))
{
error = ResolveUsingSrpService(service);
ExitNow();
}
if ((mQuestions.IsFor(kRrTypePtr) || mQuestions.IsFor(kRrTypeAny)) && QueryNameMatchesService(service))
{
SuccessOrExit(error = AppendPtrRecord(service));
matchedService = &service;
}
}
}
VerifyOrExit(matchedService != nullptr, error = kErrorNotFound);
// 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.
if (mQuestions.IsFor(kRrTypePtr) && (mHeader.GetAnswerCount() == 1))
{
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;
}
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:
return matches;
}
#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<Mac::Mac>().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<Server>().mSoaServerName, serverFullName);
if (QueryNameMatches(serverFullName))
{
for (Ip6::Netif::UnicastAddress &unicastAddr : Get<ThreadNetif>().GetUnicastAddresses())
{
if (!unicastAddr.mValid || !unicastAddr.mPreferred || unicastAddr.GetAddress().IsLinkLocalUnicast() ||
Get<Mle::Mle>().IsMeshLocalAddress(unicastAddr.GetAddress()))
{
continue;
}
SuccessOrExit(error = AppendAaaaRecord(unicastAddr.GetAddress(), kServerAaaaTtl));
}
if (IsEmpty())
{
SuccessOrExit(error = AppendAaaaRecord(Get<Mle::Mle>().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<Server>().mSoaServerName, *mMessage));
SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mDomainName, *mMessage));
// RNAME: Use constant "postmaster.<domain>"
SuccessOrExit(error = Name::AppendLabel(kSoaRnameLabel, *mMessage));
SuccessOrExit(error = Name::AppendPointerLabel(mOffsets.mDomainName, *mMessage));
for (uint32_t fieldValue : kFields)
{
SuccessOrExit(error = mMessage->Append<uint32_t>(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<Server>().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
{
bool shouldForward = false;
uint16_t readOffset;
Name::Buffer name;
VerifyOrExit(mEnableUpstreamQuery);
VerifyOrExit(aRequest.mHeader.IsRecursionDesiredFlagSet());
readOffset = sizeof(Header);
for (uint16_t i = 0; i < aRequest.mHeader.GetQuestionCount(); i++)
{
SuccessOrExit(Name::ReadName(*aRequest.mMessage, readOffset, name));
readOffset += sizeof(Question);
VerifyOrExit(!Name::IsSubDomainOf(name, kDefaultDomainName));
for (const char *blockedDomain : kBlockedDomains)
{
VerifyOrExit(!Name::IsSameDomain(name, blockedDomain));
}
}
shouldForward = true;
exit:
return shouldForward;
}
void Server::OnUpstreamQueryDone(UpstreamQueryTransaction &aQueryTransaction, Message *aResponseMessage)
{
Error error = kErrorNone;
VerifyOrExit(aQueryTransaction.IsValid(), error = kErrorInvalidArgs);
if (aResponseMessage != nullptr)
{
error = mSocket.SendTo(*aResponseMessage, aQueryTransaction.GetMessageInfo());
}
else
{
error = kErrorResponseTimeout;
}
ResetUpstreamQueryTransaction(aQueryTransaction, error);
exit:
FreeMessageOnError(aResponseMessage, error);
}
Server::UpstreamQueryTransaction *Server::AllocateUpstreamQueryTransaction(const Ip6::MessageInfo &aMessageInfo)
{
UpstreamQueryTransaction *newTxn = nullptr;
for (UpstreamQueryTransaction &txn : mUpstreamQueryTransactions)
{
if (!txn.IsValid())
{
newTxn = &txn;
break;
}
}
VerifyOrExit(newTxn != nullptr, mCounters.mUpstreamDnsCounters.mFailures++);
newTxn->Init(aMessageInfo);
LogInfo("Upstream query transaction %d initialized.", static_cast<int>(newTxn - mUpstreamQueryTransactions));
mTimer.FireAtIfEarlier(newTxn->GetExpireTime());
exit:
return newTxn;
}
Error Server::ResolveByUpstream(const Request &aRequest)
{
Error error = kErrorNone;
UpstreamQueryTransaction *txn;
txn = AllocateUpstreamQueryTransaction(*aRequest.mMessageInfo);
VerifyOrExit(txn != nullptr, error = kErrorNoBufs);
VerifyOrExit(otPlatDnsIsUpstreamQueryAvailable(&GetInstance()), error = kErrorInvalidState);
otPlatDnsStartUpstreamQuery(&GetInstance(), txn, aRequest.mMessage);
mCounters.mUpstreamDnsCounters.mQueries++;
exit:
LogWarnOnError(error, "forward to upstream");
return error;
}
#endif // OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
void Server::ResolveByProxy(Response &aResponse, const Ip6::MessageInfo &aMessageInfo)
{
ProxyQuery *query;
ProxyQueryInfo info;
#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE
VerifyOrExit(mQuerySubscribe.IsSet() || mDiscoveryProxy.IsRunning());
#else
VerifyOrExit(mQuerySubscribe.IsSet());
#endif
// We try to convert `aResponse.mMessage` to a `ProxyQuery` by
// appending `ProxyQueryInfo` to it.
info.mQuestions = aResponse.mQuestions;
info.mMessageInfo = aMessageInfo;
info.mExpireTime = TimerMilli::GetNow() + kQueryTimeout;
info.mOffsets = aResponse.mOffsets;
#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE
info.mAction = kNoAction;
#endif
if (aResponse.mMessage->Append(info) != kErrorNone)
{
aResponse.SetResponseCode(Header::kResponseServerFailure);
ExitNow();
}
// Take over the ownership of `aResponse.mMessage` and add it as a
// `ProxyQuery` in `mProxyQueries` list.
query = aResponse.mMessage.Release();
query->Write(0, aResponse.mHeader);
mProxyQueries.Enqueue(*query);
mTimer.FireAtIfEarlier(info.mExpireTime);
#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE
if (mQuerySubscribe.IsSet())
#endif
{
Name::Buffer name;
ReadQueryName(*query, name);
mQuerySubscribe.Invoke(name);
}
#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE
else
{
mDiscoveryProxy.Resolve(*query, info);
}
#endif
exit:
return;
}
void Server::ReadQueryName(const Message &aQuery, Name::Buffer &aName)
{
uint16_t offset = sizeof(Header);
IgnoreError(Name::ReadName(aQuery, offset, aName));
}
bool Server::QueryNameMatches(const Message &aQuery, const char *aName)
{
uint16_t offset = sizeof(Header);
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;
IgnoreError(Name::ReadName(aQuery, offset, aName, sizeof(aName)));
}
void Server::ReadQueryInstanceName(const ProxyQuery &aQuery,
const ProxyQueryInfo &aInfo,
Name::LabelBuffer &aInstanceLabel,
Name::Buffer &aServiceType)
{
// Reads the service instance label and service type with domain
// name stripped.
uint16_t offset = aInfo.mOffsets.mInstanceName;
uint8_t labelLength = sizeof(aInstanceLabel);
IgnoreError(Dns::Name::ReadLabel(aQuery, offset, aInstanceLabel, labelLength));
IgnoreError(Dns::Name::ReadName(aQuery, offset, aServiceType));
IgnoreError(StripDomainName(aServiceType));
}
bool Server::QueryInstanceNameMatches(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, const char *aName)
{
uint16_t offset = aInfo.mOffsets.mInstanceName;
return (Name::CompareName(aQuery, offset, aName) == kErrorNone);
}
void Server::ReadQueryHostName(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, Name::Buffer &aName)
{
uint16_t offset = aInfo.mOffsets.mHostName;
IgnoreError(Name::ReadName(aQuery, offset, aName, sizeof(aName)));
}
bool Server::QueryHostNameMatches(const ProxyQuery &aQuery, const ProxyQueryInfo &aInfo, const char *aName)
{
uint16_t offset = aInfo.mOffsets.mHostName;
return (Name::CompareName(aQuery, offset, aName) == kErrorNone);
}
Error Server::StripDomainName(Name::Buffer &aName)
{
// In-place removes the domain name from `aName`.
return Name::StripName(aName, kDefaultDomainName);
}
Error Server::StripDomainName(const char *aFullName, Name::Buffer &aLabels)
{
// Remove the domain name from `aFullName` and copies
// the result into `aLabels`.
return Name::ExtractLabels(aFullName, kDefaultDomainName, aLabels, sizeof(aLabels));
}
void Server::ConstructFullName(const char *aLabels, Name::Buffer &aFullName)
{
// Construct a full name by appending the default domain name
// to `aLabels`.
StringWriter fullName(aFullName, sizeof(aFullName));
fullName.Append("%s.%s", aLabels, kDefaultDomainName);
}
void Server::ConstructFullName(const char *aFirstLabel, const char *aNextLabels, Name::Buffer &aFullName)
{
StringWriter fullName(aFullName, sizeof(aFullName));
fullName.Append("%s.", aFirstLabel);
if (aNextLabels != nullptr)
{
fullName.Append("%s.", aNextLabels);
}
fullName.Append("%s", kDefaultDomainName);
}
void Server::ConstructFullInstanceName(const char *aInstanceLabel, const char *aServiceType, Name::Buffer &aFullName)
{
ConstructFullName(aInstanceLabel, aServiceType, aFullName);
}
void Server::ConstructFullServiceSubTypeName(const char *aServiceType,
const char *aSubTypeLabel,
Name::Buffer &aFullName)
{
StringWriter fullName(aFullName, sizeof(aFullName));
fullName.Append("%s._sub.%s.%s", aSubTypeLabel, aServiceType, kDefaultDomainName);
}
Error Server::Response::ExtractServiceInstanceLabel(const char *aInstanceName, Name::LabelBuffer &aLabel)
{
uint16_t offset;
Name::Buffer serviceName;
offset = mOffsets.mServiceName;
IgnoreError(Name::ReadName(*mMessage, offset, serviceName));
return Name::ExtractLabels(aInstanceName, serviceName, aLabel);
}
void Server::RemoveQueryAndPrepareResponse(ProxyQuery &aQuery, ProxyQueryInfo &aInfo, Response &aResponse)
{
#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE
mDiscoveryProxy.CancelAction(aQuery, aInfo);
#endif
mProxyQueries.Dequeue(aQuery);
aInfo.RemoveFrom(aQuery);
if (mQueryUnsubscribe.IsSet())
{
Name::Buffer name;
ReadQueryName(aQuery, name);
mQueryUnsubscribe.Invoke(name);
}
aResponse.InitFrom(aQuery, aInfo);
}
void Server::Response::InitFrom(ProxyQuery &aQuery, const ProxyQueryInfo &aInfo)
{
mMessage.Reset(&aQuery);
IgnoreError(mMessage->Read(0, mHeader));
mQuestions = aInfo.mQuestions;
mOffsets = aInfo.mOffsets;
}
void Server::Response::Answer(const ServiceInstanceInfo &aInstanceInfo, const Ip6::MessageInfo &aMessageInfo)
{
Error error = kErrorNone;
if (mQuestions.IsFor(kRrTypePtr))
{
Name::LabelBuffer instanceLabel;
SuccessOrExit(error = ExtractServiceInstanceLabel(aInstanceInfo.mFullName, instanceLabel));
mSection = kAnswerSection;
SuccessOrExit(error = AppendPtrRecord(instanceLabel, aInstanceInfo.mTtl));
}
error = AppendServiceRecords(aInstanceInfo);
exit:
if (error != kErrorNone)
{
SetResponseCode(Header::kResponseServerFailure);
}
Send(aMessageInfo);
}
void Server::Response::Answer(const HostInfo &aHostInfo, const Ip6::MessageInfo &aMessageInfo)
{
// Caller already ensures that question is either for AAAA or A record.
AddrType addrType = mQuestions.IsFor(kRrTypeAaaa) ? kIp6AddrType : kIp4AddrType;
mSection = kAnswerSection;
if (AppendHostAddresses(addrType, aHostInfo) != kErrorNone)
{
SetResponseCode(Header::kResponseServerFailure);
}
Send(aMessageInfo);
}
void Server::SetQueryCallbacks(SubscribeCallback aSubscribe, UnsubscribeCallback aUnsubscribe, void *aContext)
{
OT_ASSERT((aSubscribe == nullptr) == (aUnsubscribe == nullptr));
mQuerySubscribe.Set(aSubscribe, aContext);
mQueryUnsubscribe.Set(aUnsubscribe, aContext);
}
void Server::HandleDiscoveredServiceInstance(const char *aServiceFullName, const ServiceInstanceInfo &aInstanceInfo)
{
OT_ASSERT(StringEndsWith(aServiceFullName, Name::kLabelSeparatorChar));
OT_ASSERT(StringEndsWith(aInstanceInfo.mFullName, Name::kLabelSeparatorChar));
OT_ASSERT(StringEndsWith(aInstanceInfo.mHostName, Name::kLabelSeparatorChar));
// It is safe to remove entries from `mProxyQueries` as we iterate
// over it since it is a `MessageQueue`.
for (ProxyQuery &query : mProxyQueries)
{
bool canAnswer = false;
ProxyQueryInfo info;
info.ReadFrom(query);
if (info.mQuestions.IsFor(kRrTypePtr))
{
canAnswer = QueryNameMatches(query, aServiceFullName);
}
else if (info.mQuestions.IsFor(kRrTypeSrv) || info.mQuestions.IsFor(kRrTypeTxt))
{
canAnswer = QueryNameMatches(query, aInstanceInfo.mFullName);
}
if (canAnswer)
{
Response response(GetInstance());
RemoveQueryAndPrepareResponse(query, info, response);
response.Answer(aInstanceInfo, info.mMessageInfo);
}
}
}
void Server::HandleDiscoveredHost(const char *aHostFullName, const HostInfo &aHostInfo)
{
OT_ASSERT(StringEndsWith(aHostFullName, Name::kLabelSeparatorChar));
for (ProxyQuery &query : mProxyQueries)
{
ProxyQueryInfo info;
info.ReadFrom(query);
if (!info.mQuestions.IsFor(kRrTypeAaaa) && !info.mQuestions.IsFor(kRrTypeA))
{
continue;
}
if (QueryNameMatches(query, aHostFullName))
{
Response response(GetInstance());
RemoveQueryAndPrepareResponse(query, info, response);
response.Answer(aHostInfo, info.mMessageInfo);
}
}
}
const otDnssdQuery *Server::GetNextQuery(const otDnssdQuery *aQuery) const
{
const ProxyQuery *query = static_cast<const ProxyQuery *>(aQuery);
return (query == nullptr) ? mProxyQueries.GetHead() : query->GetNext();
}
Server::DnsQueryType Server::GetQueryTypeAndName(const otDnssdQuery *aQuery, Dns::Name::Buffer &aName)
{
const ProxyQuery *query = static_cast<const ProxyQuery *>(aQuery);
ProxyQueryInfo info;
DnsQueryType type;
ReadQueryName(*query, aName);
info.ReadFrom(*query);
type = kDnsQueryResolveHost;
if (info.mQuestions.IsFor(kRrTypePtr))
{
type = kDnsQueryBrowse;
}
else if (info.mQuestions.IsFor(kRrTypeSrv) || info.mQuestions.IsFor(kRrTypeTxt))
{
type = kDnsQueryResolve;
}
return type;
}
void Server::HandleTimer(void)
{
NextFireTime nextExpire;
for (ProxyQuery &query : mProxyQueries)
{
ProxyQueryInfo info;
info.ReadFrom(query);
if (info.mExpireTime <= nextExpire.GetNow())
{
Finalize(query, Header::kResponseSuccess);
}
else
{
nextExpire.UpdateIfEarlier(info.mExpireTime);
}
}
#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
for (UpstreamQueryTransaction &query : mUpstreamQueryTransactions)
{
if (!query.IsValid())
{
continue;
}
if (query.GetExpireTime() <= nextExpire.GetNow())
{
otPlatDnsCancelUpstreamQuery(&GetInstance(), &query);
}
else
{
nextExpire.UpdateIfEarlier(query.GetExpireTime());
}
}
#endif
mTimer.FireAtIfEarlier(nextExpire);
}
void Server::Finalize(ProxyQuery &aQuery, ResponseCode aResponseCode)
{
Response response(GetInstance());
ProxyQueryInfo info;
info.ReadFrom(aQuery);
RemoveQueryAndPrepareResponse(aQuery, info, response);
response.SetResponseCode(aResponseCode);
response.Send(info.mMessageInfo);
}
void Server::UpdateResponseCounters(ResponseCode aResponseCode)
{
switch (aResponseCode)
{
case UpdateHeader::kResponseSuccess:
++mCounters.mSuccessResponse;
break;
case UpdateHeader::kResponseServerFailure:
++mCounters.mServerFailureResponse;
break;
case UpdateHeader::kResponseFormatError:
++mCounters.mFormatErrorResponse;
break;
case UpdateHeader::kResponseNameError:
++mCounters.mNameErrorResponse;
break;
case UpdateHeader::kResponseNotImplemented:
++mCounters.mNotImplementedResponse;
break;
default:
++mCounters.mOtherResponse;
break;
}
}
#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
void Server::UpstreamQueryTransaction::Init(const Ip6::MessageInfo &aMessageInfo)
{
mMessageInfo = aMessageInfo;
mValid = true;
mExpireTime = TimerMilli::GetNow() + kQueryTimeout;
}
void Server::ResetUpstreamQueryTransaction(UpstreamQueryTransaction &aTxn, Error aError)
{
int index = static_cast<int>(&aTxn - mUpstreamQueryTransactions);
// Avoid the warnings when info / warn logging is disabled.
OT_UNUSED_VARIABLE(index);
if (aError == kErrorNone)
{
mCounters.mUpstreamDnsCounters.mResponses++;
LogInfo("Upstream query transaction %d completed.", index);
}
else
{
mCounters.mUpstreamDnsCounters.mFailures++;
LogWarnOnError(aError, "handle upstream query transaction %d", index);
}
aTxn.Reset();
}
#endif
#if OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE
Server::DiscoveryProxy::DiscoveryProxy(Instance &aInstance)
: InstanceLocator(aInstance)
, mIsRunning(false)
{
}
void Server::DiscoveryProxy::UpdateState(void)
{
if (Get<Server>().IsRunning() && Get<Dnssd>().IsReady() && Get<BorderRouter::InfraIf>().IsRunning())
{
Start();
}
else
{
Stop();
}
}
void Server::DiscoveryProxy::Start(void)
{
VerifyOrExit(!mIsRunning);
mIsRunning = true;
LogInfo("Started discovery proxy");
exit:
return;
}
void Server::DiscoveryProxy::Stop(void)
{
VerifyOrExit(mIsRunning);
for (ProxyQuery &query : Get<Server>().mProxyQueries)
{
Get<Server>().Finalize(query, Header::kResponseSuccess);
}
mIsRunning = false;
LogInfo("Stopped discovery proxy");
exit:
return;
}
void Server::DiscoveryProxy::Resolve(ProxyQuery &aQuery, ProxyQueryInfo &aInfo)
{
// Determine which proxy action to start with based on the query's
// question record type(s). Note that the order in which the record
// types are checked is important. Particularly if the query
// contains questions for both SRV and TXT records, we want to
// start with the `kResolvingSrv` action first.
struct ActionEntry
{
uint16_t mRrType;
ProxyAction mAction;
};
static const ActionEntry kActionTable[] = {
{kRrTypePtr, kBrowsing}, // PTR -> Browser
{kRrTypeSrv, kResolvingSrv}, // SRV -> SrvResolver
{kRrTypeTxt, kResolvingTxt}, // TXT -> TxtResolver
{kRrTypeAaaa, kResolvingIp6Address}, // AAAA -> Ip6AddressResolver
{kRrTypeA, kResolvingIp4Address}, // A -> Ip4AddressResolver
// Misc -> RecordQuerier
};
ProxyAction action;
for (const ActionEntry &entry : kActionTable)
{
if (aInfo.mQuestions.IsFor(entry.mRrType))
{
action = entry.mAction;
ExitNow();
}
}
action = kQueryingRecord;
exit:
Perform(action, aQuery, aInfo);
}
void Server::DiscoveryProxy::Perform(ProxyAction aAction, ProxyQuery &aQuery, ProxyQueryInfo &aInfo)
{
bool shouldStart;
Name::Buffer name;
uint16_t querierRrType;
VerifyOrExit(aAction != kNoAction);
// The order of the steps below is crucial. First, we read the
// name associated with the action. Then we check if another
// query has an active browser/resolver for the same name. This
// helps us determine if a new browser/resolver is needed. Then,
// we update the `ProxyQueryInfo` within `aQuery` to reflect the
// `aAction` being performed. Finally, if necessary, we start the
// proper browser/resolver on DNS-SD/mDNS. Placing this last
// ensures correct processing even if a DNS-SD/mDNS callback is
// invoked immediately.
ReadNameFor(aAction, aQuery, aInfo, name);
querierRrType = (aAction == kQueryingRecord) ? aInfo.mQuestions.mFirstRrType : 0;
shouldStart = !HasActive(aAction, name, querierRrType);
aInfo.mAction = aAction;
aInfo.UpdateIn(aQuery);
VerifyOrExit(shouldStart);
UpdateProxy(kStart, aAction, aQuery, aInfo, name);
exit:
return;
}
void Server::DiscoveryProxy::ReadNameFor(ProxyAction aAction,
ProxyQuery &aQuery,
ProxyQueryInfo &aInfo,
Name::Buffer &aName) const
{
// Read the name corresponding to `aAction` from `aQuery`.
switch (aAction)
{
case kNoAction:
break;
case kBrowsing:
case kQueryingRecord:
ReadQueryName(aQuery, aName);
break;
case kResolvingSrv:
case kResolvingTxt:
ReadQueryInstanceName(aQuery, aInfo, aName);
break;
case kResolvingIp6Address:
case kResolvingIp4Address:
ReadQueryHostName(aQuery, aInfo, aName);
break;
}
}
void Server::DiscoveryProxy::CancelAction(ProxyQuery &aQuery, ProxyQueryInfo &aInfo)
{
// Cancel the current action for a given `aQuery`, then
// determine if we need to stop any browser/resolver
// on infrastructure.
ProxyAction action = aInfo.mAction;
Name::Buffer name;
uint16_t querierRrType;
VerifyOrExit(mIsRunning);
VerifyOrExit(action != kNoAction);
// We first update the `aInfo` on `aQuery` before calling
// `HasActive()`. This ensures that the current query is not
// taken into account when we try to determine if any query
// is waiting for same `action` browser/resolver.
ReadNameFor(action, aQuery, aInfo, name);
querierRrType = (action == kQueryingRecord) ? aInfo.mQuestions.mFirstRrType : 0;
aInfo.mAction = kNoAction;
aInfo.UpdateIn(aQuery);
VerifyOrExit(!HasActive(action, name, querierRrType));
UpdateProxy(kStop, action, aQuery, aInfo, name);
exit:
return;
}
void Server::DiscoveryProxy::UpdateProxy(Command aCommand,
ProxyAction aAction,
const ProxyQuery &aQuery,
const ProxyQueryInfo &aInfo,
Name::Buffer &aName)
{
// Start or stop browser/resolver corresponding to `aAction`.
// `aName` may be changed.
switch (aAction)
{
case kNoAction:
break;
case kBrowsing:
StartOrStopBrowser(aCommand, aName);
break;
case kResolvingSrv:
StartOrStopSrvResolver(aCommand, aQuery, aInfo);
break;
case kResolvingTxt:
StartOrStopTxtResolver(aCommand, aQuery, aInfo);
break;
case kResolvingIp6Address:
StartOrStopIp6Resolver(aCommand, aName);
break;
case kResolvingIp4Address:
StartOrStopIp4Resolver(aCommand, aName);
break;
case kQueryingRecord:
StartOrStopRecordQuerier(aCommand, aQuery, aInfo);
break;
}
}
void Server::DiscoveryProxy::StartOrStopBrowser(Command aCommand, Name::Buffer &aServiceName)
{
// Start or stop a service browser for a given service type
// or sub-type.
static const char kFullSubLabel[] = "._sub.";
Dnssd::Browser browser;
char *ptr;
browser.Clear();
IgnoreError(StripDomainName(aServiceName));
// Check if the service name is a sub-type with name
// format: "<sub-label>._sub.<service-labels>.
ptr = AsNonConst(StringFind(aServiceName, kFullSubLabel, kStringCaseInsensitiveMatch));
if (ptr != nullptr)
{
*ptr = kNullChar;
ptr += sizeof(kFullSubLabel) - 1;
browser.mServiceType = ptr;
browser.mSubTypeLabel = aServiceName;
}
else
{
browser.mServiceType = aServiceName;
browser.mSubTypeLabel = nullptr;
}
browser.mInfraIfIndex = Get<BorderRouter::InfraIf>().GetIfIndex();
browser.mCallback = HandleBrowseResult;
switch (aCommand)
{
case kStart:
Get<Dnssd>().StartBrowser(browser);
break;
case kStop:
Get<Dnssd>().StopBrowser(browser);
break;
}
}
void Server::DiscoveryProxy::StartOrStopSrvResolver(Command aCommand,
const ProxyQuery &aQuery,
const ProxyQueryInfo &aInfo)
{
// Start or stop an SRV record resolver for a given query.
Dnssd::SrvResolver resolver;
Name::LabelBuffer instanceLabel;
Name::Buffer serviceType;
ReadQueryInstanceName(aQuery, aInfo, instanceLabel, serviceType);
resolver.Clear();
resolver.mServiceInstance = instanceLabel;
resolver.mServiceType = serviceType;
resolver.mInfraIfIndex = Get<BorderRouter::InfraIf>().GetIfIndex();
resolver.mCallback = HandleSrvResult;
switch (aCommand)
{
case kStart:
Get<Dnssd>().StartSrvResolver(resolver);
break;
case kStop:
Get<Dnssd>().StopSrvResolver(resolver);
break;
}
}
void Server::DiscoveryProxy::StartOrStopTxtResolver(Command aCommand,
const ProxyQuery &aQuery,
const ProxyQueryInfo &aInfo)
{
// Start or stop a TXT record resolver for a given query.
Dnssd::TxtResolver resolver;
Name::LabelBuffer instanceLabel;
Name::Buffer serviceType;
ReadQueryInstanceName(aQuery, aInfo, instanceLabel, serviceType);
resolver.Clear();
resolver.mServiceInstance = instanceLabel;
resolver.mServiceType = serviceType;
resolver.mInfraIfIndex = Get<BorderRouter::InfraIf>().GetIfIndex();
resolver.mCallback = HandleTxtResult;
switch (aCommand)
{
case kStart:
Get<Dnssd>().StartTxtResolver(resolver);
break;
case kStop:
Get<Dnssd>().StopTxtResolver(resolver);
break;
}
}
void Server::DiscoveryProxy::StartOrStopIp6Resolver(Command aCommand, Name::Buffer &aHostName)
{
// Start or stop an IPv6 address resolver for a given host name.
Dnssd::AddressResolver resolver;
IgnoreError(StripDomainName(aHostName));
resolver.mHostName = aHostName;
resolver.mInfraIfIndex = Get<BorderRouter::InfraIf>().GetIfIndex();
resolver.mCallback = HandleIp6AddressResult;
switch (aCommand)
{
case kStart:
Get<Dnssd>().StartIp6AddressResolver(resolver);
break;
case kStop:
Get<Dnssd>().StopIp6AddressResolver(resolver);
break;
}
}
void Server::DiscoveryProxy::StartOrStopIp4Resolver(Command aCommand, Name::Buffer &aHostName)
{
// Start or stop an IPv4 address resolver for a given host name.
Dnssd::AddressResolver resolver;
IgnoreError(StripDomainName(aHostName));
resolver.mHostName = aHostName;
resolver.mInfraIfIndex = Get<BorderRouter::InfraIf>().GetIfIndex();
resolver.mCallback = HandleIp4AddressResult;
switch (aCommand)
{
case kStart:
Get<Dnssd>().StartIp4AddressResolver(resolver);
break;
case kStop:
Get<Dnssd>().StopIp4AddressResolver(resolver);
break;
}
}
void Server::DiscoveryProxy::StartOrStopRecordQuerier(Command aCommand,
const ProxyQuery &aQuery,
const ProxyQueryInfo &aInfo)
{
// Start or stop a record querier.
Dnssd::RecordQuerier querier;
Name::LabelBuffer firstLabel;
Name::Buffer nextLabels;
uint16_t offset = sizeof(Header);
uint8_t labelLength = sizeof(firstLabel);
IgnoreError(Dns::Name::ReadLabel(aQuery, offset, firstLabel, labelLength));
IgnoreError(Dns::Name::ReadName(aQuery, offset, nextLabels));
querier.mFirstLabel = firstLabel;
querier.mNextLabels = (StripDomainName(nextLabels) == kErrorNone) ? nextLabels : nullptr;
querier.mRecordType = aInfo.mQuestions.mFirstRrType;
querier.mInfraIfIndex = Get<BorderRouter::InfraIf>().GetIfIndex();
querier.mCallback = HandleRecordResult;
switch (aCommand)
{
case kStart:
Get<Dnssd>().StartRecordQuerier(querier);
break;
case kStop:
Get<Dnssd>().StopRecordQuerier(querier);
break;
}
}
bool Server::DiscoveryProxy::QueryMatches(const ProxyQuery &aQuery,
const ProxyQueryInfo &aInfo,
ProxyAction aAction,
const Name::Buffer &aName,
uint16_t aQuerierRrType,
RrTypeMatchMode aRrTypeMatchMode) const
{
// Check whether `aQuery` is performing `aAction` and
// 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;
VerifyOrExit(aInfo.mAction == aAction);
if (aAction == kQueryingRecord)
{
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:
case kResolvingTxt:
VerifyOrExit(QueryInstanceNameMatches(aQuery, aInfo, aName));
break;
case kResolvingIp6Address:
case kResolvingIp4Address:
VerifyOrExit(QueryHostNameMatches(aQuery, aInfo, aName));
break;
case kNoAction:
ExitNow();
}
matches = true;
exit:
return matches;
}
bool Server::DiscoveryProxy::HasActive(ProxyAction aAction, const Name::Buffer &aName, uint16_t aQuerierRrType) const
{
// Determine whether or not we have an active browser, resolver, or record
// querier corresponding to `aAction` for `aName`. The `aQuerierRrType`
// is only used when the action is `kQueryingRecord` to indicate the
// `RecordQuerier` record type.
bool has = false;
for (const ProxyQuery &query : Get<Server>().mProxyQueries)
{
ProxyQueryInfo info;
info.ReadFrom(query);
if (QueryMatches(query, info, aAction, aName, aQuerierRrType, kRequireExactMatch))
{
has = true;
break;
}
}
return has;
}
void Server::DiscoveryProxy::HandleBrowseResult(otInstance *aInstance, const otPlatDnssdBrowseResult *aResult)
{
AsCoreType(aInstance).Get<Server>().mDiscoveryProxy.HandleBrowseResult(*aResult);
}
void Server::DiscoveryProxy::HandleBrowseResult(const Dnssd::BrowseResult &aResult)
{
Name::Buffer serviceName;
VerifyOrExit(mIsRunning);
VerifyOrExit(aResult.mTtl != 0);
VerifyOrExit(aResult.mInfraIfIndex == Get<BorderRouter::InfraIf>().GetIfIndex());
if (aResult.mSubTypeLabel != nullptr)
{
ConstructFullServiceSubTypeName(aResult.mServiceType, aResult.mSubTypeLabel, serviceName);
}
else
{
ConstructFullName(aResult.mServiceType, serviceName);
}
HandleResult(kBrowsing, serviceName, &Response::AppendPtrRecord, ProxyResult(aResult));
exit:
return;
}
void Server::DiscoveryProxy::HandleSrvResult(otInstance *aInstance, const otPlatDnssdSrvResult *aResult)
{
AsCoreType(aInstance).Get<Server>().mDiscoveryProxy.HandleSrvResult(*aResult);
}
void Server::DiscoveryProxy::HandleSrvResult(const Dnssd::SrvResult &aResult)
{
Name::Buffer instanceName;
VerifyOrExit(mIsRunning);
VerifyOrExit(aResult.mTtl != 0);
VerifyOrExit(aResult.mInfraIfIndex == Get<BorderRouter::InfraIf>().GetIfIndex());
ConstructFullInstanceName(aResult.mServiceInstance, aResult.mServiceType, instanceName);
HandleResult(kResolvingSrv, instanceName, &Response::AppendSrvRecord, ProxyResult(aResult));
exit:
return;
}
void Server::DiscoveryProxy::HandleTxtResult(otInstance *aInstance, const otPlatDnssdTxtResult *aResult)
{
AsCoreType(aInstance).Get<Server>().mDiscoveryProxy.HandleTxtResult(*aResult);
}
void Server::DiscoveryProxy::HandleTxtResult(const Dnssd::TxtResult &aResult)
{
Name::Buffer instanceName;
VerifyOrExit(mIsRunning);
VerifyOrExit(aResult.mTtl != 0);
VerifyOrExit(aResult.mInfraIfIndex == Get<BorderRouter::InfraIf>().GetIfIndex());
ConstructFullInstanceName(aResult.mServiceInstance, aResult.mServiceType, instanceName);
HandleResult(kResolvingTxt, instanceName, &Response::AppendTxtRecord, ProxyResult(aResult));
exit:
return;
}
void Server::DiscoveryProxy::HandleIp6AddressResult(otInstance *aInstance, const otPlatDnssdAddressResult *aResult)
{
AsCoreType(aInstance).Get<Server>().mDiscoveryProxy.HandleIp6AddressResult(*aResult);
}
void Server::DiscoveryProxy::HandleIp6AddressResult(const Dnssd::AddressResult &aResult)
{
bool hasValidAddress = false;
Name::Buffer fullHostName;
VerifyOrExit(mIsRunning);
VerifyOrExit(aResult.mInfraIfIndex == Get<BorderRouter::InfraIf>().GetIfIndex());
for (uint16_t index = 0; index < aResult.mAddressesLength; index++)
{
const Dnssd::AddressAndTtl &entry = aResult.mAddresses[index];
const Ip6::Address &address = AsCoreType(&entry.mAddress);
if (entry.mTtl == 0)
{
continue;
}
if (IsProxyAddressValid(address))
{
hasValidAddress = true;
break;
}
}
VerifyOrExit(hasValidAddress);
ConstructFullName(aResult.mHostName, fullHostName);
HandleResult(kResolvingIp6Address, fullHostName, &Response::AppendHostIp6Addresses, ProxyResult(aResult));
exit:
return;
}
void Server::DiscoveryProxy::HandleIp4AddressResult(otInstance *aInstance, const otPlatDnssdAddressResult *aResult)
{
AsCoreType(aInstance).Get<Server>().mDiscoveryProxy.HandleIp4AddressResult(*aResult);
}
void Server::DiscoveryProxy::HandleIp4AddressResult(const Dnssd::AddressResult &aResult)
{
bool hasValidAddress = false;
Name::Buffer fullHostName;
VerifyOrExit(mIsRunning);
VerifyOrExit(aResult.mInfraIfIndex == Get<BorderRouter::InfraIf>().GetIfIndex());
for (uint16_t index = 0; index < aResult.mAddressesLength; index++)
{
const Dnssd::AddressAndTtl &entry = aResult.mAddresses[index];
const Ip6::Address &address = AsCoreType(&entry.mAddress);
if (entry.mTtl == 0)
{
continue;
}
if (address.IsIp4Mapped())
{
hasValidAddress = true;
break;
}
}
VerifyOrExit(hasValidAddress);
ConstructFullName(aResult.mHostName, fullHostName);
HandleResult(kResolvingIp4Address, fullHostName, &Response::AppendHostIp4Addresses, ProxyResult(aResult));
exit:
return;
}
void Server::DiscoveryProxy::HandleRecordResult(otInstance *aInstance, const otPlatDnssdRecordResult *aResult)
{
AsCoreType(aInstance).Get<Server>().mDiscoveryProxy.HandleRecordResult(*aResult);
}
void Server::DiscoveryProxy::HandleRecordResult(const Dnssd::RecordResult &aResult)
{
Name::Buffer name;
VerifyOrExit(mIsRunning);
VerifyOrExit(aResult.mTtl != 0);
VerifyOrExit(aResult.mInfraIfIndex == Get<BorderRouter::InfraIf>().GetIfIndex());
ConstructFullName(aResult.mFirstLabel, aResult.mNextLabels, name);
HandleResult(kQueryingRecord, name, &Response::AppendGenericRecord, ProxyResult(aResult));
exit:
return;
}
void Server::DiscoveryProxy::HandleResult(ProxyAction aAction,
const Name::Buffer &aName,
ResponseAppender aAppender,
const ProxyResult &aResult)
{
// Common method that handles result from DNS-SD/mDNS. It
// iterates over all `ProxyQuery` entries and checks if any entry
// is waiting for the result of `aAction` for `aName`. Matching
// queries are updated using the `aAppender` method pointer,
// which appends the corresponding record(s) to the response. We
// then determine the next action to be performed for the
// `ProxyQuery` or if it can be finalized.
ProxyQueryList nextActionQueries;
ProxyQueryInfo info;
ProxyAction nextAction;
uint16_t querierRrType;
querierRrType = (aAction == kQueryingRecord) ? aResult.mRecordResult->mRecordType : 0;
for (ProxyQuery &query : Get<Server>().mProxyQueries)
{
Response response(GetInstance());
bool shouldFinalize;
info.ReadFrom(query);
if (!QueryMatches(query, info, aAction, aName, querierRrType, kPermitAnyOrExactMatch))
{
continue;
}
CancelAction(query, info);
nextAction = kNoAction;
switch (aAction)
{
case kBrowsing:
nextAction = kResolvingSrv;
break;
case kResolvingSrv:
nextAction = (info.mQuestions.IsFor(kRrTypeSrv) && !info.mQuestions.IsFor(kRrTypeTxt))
? kResolvingIp6Address
: kResolvingTxt;
break;
case kResolvingTxt:
nextAction = (info.mQuestions.IsFor(kRrTypeTxt) && !info.mQuestions.IsFor(kRrTypeSrv))
? kNoAction
: kResolvingIp6Address;
break;
case kNoAction:
case kResolvingIp6Address:
case kResolvingIp4Address:
case kQueryingRecord:
break;
}
shouldFinalize = (nextAction == kNoAction);
if ((Get<Server>().mTestMode & kTestModeEmptyAdditionalSection) &&
IsActionForAdditionalSection(nextAction, info.mQuestions))
{
shouldFinalize = true;
}
Get<Server>().mProxyQueries.Dequeue(query);
info.RemoveFrom(query);
response.InitFrom(query, info);
if ((response.*aAppender)(aResult) != kErrorNone)
{
response.SetResponseCode(Header::kResponseServerFailure);
shouldFinalize = true;
}
if (shouldFinalize)
{
response.Send(info.mMessageInfo);
continue;
}
// The `query` is not yet finished and we need to perform
// the `nextAction` for it.
// Reinitialize `response` as a `ProxyQuey` by updating
// and appending `info` to it after the newly appended
// records from `aResult` and saving the `mHeader`.
info.mOffsets = response.mOffsets;
info.mAction = nextAction;
response.mMessage->Write(0, response.mHeader);
if (response.mMessage->Append(info) != kErrorNone)
{
response.SetResponseCode(Header::kResponseServerFailure);
response.Send(info.mMessageInfo);
continue;
}
// Take back ownership of `response.mMessage` as we still
// treat it as a `ProxyQuery`.
response.mMessage.Release();
// We place the `query` in a separate list and add it back to
// the main `mProxyQueries` list after we are done with the
// current iteration. This ensures that other entries in the
// `mProxyQueries` list are not updated or removed due to the
// DNS-SD platform callback being invoked immediately when we
// potentially start a browser or resolver to perform the
// `nextAction` for `query`.
nextActionQueries.Enqueue(query);
}
for (ProxyQuery &query : nextActionQueries)
{
nextActionQueries.Dequeue(query);
info.ReadFrom(query);
nextAction = info.mAction;
info.mAction = kNoAction;
info.UpdateIn(query);
Get<Server>().mProxyQueries.Enqueue(query);
Perform(nextAction, query, info);
}
}
bool Server::DiscoveryProxy::IsActionForAdditionalSection(ProxyAction aAction, const Questions &aQuestions)
{
bool isForAddnlSection = false;
uint16_t rrType = 0;
switch (aAction)
{
case kResolvingSrv:
rrType = kRrTypeSrv;
break;
case kResolvingTxt:
rrType = kRrTypeTxt;
break;
case kResolvingIp6Address:
rrType = kRrTypeAaaa;
break;
case kResolvingIp4Address:
rrType = kRrTypeA;
break;
case kNoAction:
case kBrowsing:
case kQueryingRecord:
ExitNow();
}
isForAddnlSection = aQuestions.SectionFor(rrType) == kAdditionalDataSection;
exit:
return isForAddnlSection;
}
Error Server::Response::AppendPtrRecord(const ProxyResult &aResult)
{
const Dnssd::BrowseResult *browseResult = aResult.mBrowseResult;
mSection = kAnswerSection;
return AppendPtrRecord(browseResult->mServiceInstance, DiscoveryProxy::CapTtl(browseResult->mTtl));
}
Error Server::Response::AppendSrvRecord(const ProxyResult &aResult)
{
const Dnssd::SrvResult *srvResult = aResult.mSrvResult;
Name::Buffer fullHostName;
mSection = mQuestions.SectionFor(kRrTypeSrv);
ConstructFullName(srvResult->mHostName, fullHostName);
return AppendSrvRecord(fullHostName, DiscoveryProxy::CapTtl(srvResult->mTtl), srvResult->mPriority,
srvResult->mWeight, srvResult->mPort);
}
Error Server::Response::AppendTxtRecord(const ProxyResult &aResult)
{
const Dnssd::TxtResult *txtResult = aResult.mTxtResult;
mSection = mQuestions.SectionFor(kRrTypeTxt);
return AppendTxtRecord(txtResult->mTxtData, txtResult->mTxtDataLength, DiscoveryProxy::CapTtl(txtResult->mTtl));
}
Error Server::Response::AppendHostIp6Addresses(const ProxyResult &aResult)
{
Error error = kErrorNone;
const Dnssd::AddressResult *addrResult = aResult.mAddressResult;
mSection = mQuestions.SectionFor(kRrTypeAaaa);
for (uint16_t index = 0; index < addrResult->mAddressesLength; index++)
{
const Dnssd::AddressAndTtl &entry = addrResult->mAddresses[index];
const Ip6::Address &address = AsCoreType(&entry.mAddress);
if (entry.mTtl == 0)
{
continue;
}
if (!IsProxyAddressValid(address))
{
continue;
}
SuccessOrExit(error = AppendAaaaRecord(address, DiscoveryProxy::CapTtl(entry.mTtl)));
}
exit:
return error;
}
Error Server::Response::AppendHostIp4Addresses(const ProxyResult &aResult)
{
Error error = kErrorNone;
const Dnssd::AddressResult *addrResult = aResult.mAddressResult;
mSection = mQuestions.SectionFor(kRrTypeA);
for (uint16_t index = 0; index < addrResult->mAddressesLength; index++)
{
const Dnssd::AddressAndTtl &entry = addrResult->mAddresses[index];
const Ip6::Address &address = AsCoreType(&entry.mAddress);
if (entry.mTtl == 0)
{
continue;
}
SuccessOrExit(error = AppendARecord(address, DiscoveryProxy::CapTtl(entry.mTtl)));
}
exit:
return error;
}
Error Server::Response::AppendGenericRecord(const ProxyResult &aResult)
{
const Dnssd::RecordResult *result = aResult.mRecordResult;
RecordData data;
mSection = kAnswerSection;
data.Init(result->mRecordData, result->mRecordDataLength);
return AppendGenericRecord(result->mRecordType, data, DiscoveryProxy::CapTtl(result->mTtl));
}
bool Server::IsProxyAddressValid(const Ip6::Address &aAddress)
{
return !aAddress.IsLinkLocalUnicast() && !aAddress.IsMulticast() && !aAddress.IsUnspecified() &&
!aAddress.IsLoopback();
}
#endif // OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE
} // namespace ServiceDiscovery
} // namespace Dns
} // namespace ot
#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE && OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_MOCK_PLAT_APIS_ENABLE
bool otPlatDnsIsUpstreamQueryAvailable(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return true;
}
void otPlatDnsStartUpstreamQuery(otInstance *aInstance, otPlatDnsUpstreamQuery *aTxn, const otMessage *aQuery)
{
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aTxn);
OT_UNUSED_VARIABLE(aQuery);
}
void otPlatDnsCancelUpstreamQuery(otInstance *aInstance, otPlatDnsUpstreamQuery *aTxn)
{
otPlatDnsUpstreamQueryDone(aInstance, aTxn, nullptr);
}
#endif
#endif // OPENTHREAD_CONFIG_DNS_SERVER_ENABLE