/* * Copyright (c) 2020, 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 includes implementation for SRP server. */ #include "srp_server.hpp" #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE #include "common/as_core_type.hpp" #include "common/const_cast.hpp" #include "common/locator_getters.hpp" #include "common/log.hpp" #include "common/new.hpp" #include "common/num_utils.hpp" #include "common/random.hpp" #include "common/string.hpp" #include "instance/instance.hpp" #include "net/dns_types.hpp" #include "thread/thread_netif.hpp" namespace ot { namespace Srp { RegisterLogModule("SrpServer"); static const char kDefaultDomain[] = "default.service.arpa."; static const char kServiceSubTypeLabel[] = "._sub."; static Dns::UpdateHeader::Response ErrorToDnsResponseCode(Error aError) { Dns::UpdateHeader::Response responseCode; switch (aError) { case kErrorNone: responseCode = Dns::UpdateHeader::kResponseSuccess; break; case kErrorNoBufs: responseCode = Dns::UpdateHeader::kResponseServerFailure; break; case kErrorParse: responseCode = Dns::UpdateHeader::kResponseFormatError; break; case kErrorDuplicated: responseCode = Dns::UpdateHeader::kResponseNameExists; break; default: responseCode = Dns::UpdateHeader::kResponseRefused; break; } return responseCode; } //--------------------------------------------------------------------------------------------------------------------- // Server Server::Server(Instance &aInstance) : InstanceLocator(aInstance) , mSocket(aInstance) , mLeaseTimer(aInstance) , mOutstandingUpdatesTimer(aInstance) , mCompletedUpdateTask(aInstance) , mServiceUpdateId(Random::NonCrypto::GetUint32()) , mPort(kUninitializedPort) , mState(kStateDisabled) , mAddressMode(kDefaultAddressMode) , mAnycastSequenceNumber(0) , mHasRegisteredAnyService(false) #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE , mAutoEnable(false) #endif { IgnoreError(SetDomain(kDefaultDomain)); } Error Server::SetAddressMode(AddressMode aMode) { Error error = kErrorNone; VerifyOrExit(mState == kStateDisabled, error = kErrorInvalidState); VerifyOrExit(mAddressMode != aMode); LogInfo("Address Mode: %s -> %s", AddressModeToString(mAddressMode), AddressModeToString(aMode)); mAddressMode = aMode; exit: return error; } Error Server::SetAnycastModeSequenceNumber(uint8_t aSequenceNumber) { Error error = kErrorNone; VerifyOrExit(mState == kStateDisabled, error = kErrorInvalidState); mAnycastSequenceNumber = aSequenceNumber; LogInfo("Set Anycast Address Mode Seq Number to %d", aSequenceNumber); exit: return error; } void Server::SetEnabled(bool aEnabled) { #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE mAutoEnable = false; #endif if (aEnabled) { Enable(); } else { Disable(); } } void Server::Enable(void) { VerifyOrExit(mState == kStateDisabled); mState = kStateStopped; // Request publishing of "DNS/SRP Address Service" entry in the // Thread Network Data based of `mAddressMode`. Then wait for // callback `HandleNetDataPublisherEntryChange()` from the // `Publisher` to start the SRP server. switch (mAddressMode) { case kAddressModeUnicast: SelectPort(); Get().PublishDnsSrpServiceUnicast(mPort); break; case kAddressModeAnycast: mPort = kAnycastAddressModePort; Get().PublishDnsSrpServiceAnycast(mAnycastSequenceNumber); break; } exit: return; } void Server::Disable(void) { VerifyOrExit(mState != kStateDisabled); Get().UnpublishDnsSrpService(); Stop(); mState = kStateDisabled; exit: return; } #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE void Server::SetAutoEnableMode(bool aEnabled) { VerifyOrExit(mAutoEnable != aEnabled); mAutoEnable = aEnabled; Get().HandleSrpServerAutoEnableMode(); exit: return; } #endif Server::TtlConfig::TtlConfig(void) { mMinTtl = kDefaultMinTtl; mMaxTtl = kDefaultMaxTtl; } Error Server::SetTtlConfig(const TtlConfig &aTtlConfig) { Error error = kErrorNone; VerifyOrExit(aTtlConfig.IsValid(), error = kErrorInvalidArgs); mTtlConfig = aTtlConfig; exit: return error; } uint32_t Server::TtlConfig::GrantTtl(uint32_t aLease, uint32_t aTtl) const { OT_ASSERT(mMinTtl <= mMaxTtl); return Clamp(Min(aTtl, aLease), mMinTtl, mMaxTtl); } Server::LeaseConfig::LeaseConfig(void) { mMinLease = kDefaultMinLease; mMaxLease = kDefaultMaxLease; mMinKeyLease = kDefaultMinKeyLease; mMaxKeyLease = kDefaultMaxKeyLease; } bool Server::LeaseConfig::IsValid(void) const { bool valid = false; // TODO: Support longer LEASE. // We use milliseconds timer for LEASE & KEY-LEASE, this is to avoid overflow. VerifyOrExit(mMaxKeyLease <= Time::MsecToSec(TimerMilli::kMaxDelay)); VerifyOrExit(mMinLease <= mMaxLease); VerifyOrExit(mMinKeyLease <= mMaxKeyLease); VerifyOrExit(mMinLease <= mMinKeyLease); VerifyOrExit(mMaxLease <= mMaxKeyLease); valid = true; exit: return valid; } uint32_t Server::LeaseConfig::GrantLease(uint32_t aLease) const { OT_ASSERT(mMinLease <= mMaxLease); return (aLease == 0) ? 0 : Clamp(aLease, mMinLease, mMaxLease); } uint32_t Server::LeaseConfig::GrantKeyLease(uint32_t aKeyLease) const { OT_ASSERT(mMinKeyLease <= mMaxKeyLease); return (aKeyLease == 0) ? 0 : Clamp(aKeyLease, mMinKeyLease, mMaxKeyLease); } Error Server::SetLeaseConfig(const LeaseConfig &aLeaseConfig) { Error error = kErrorNone; VerifyOrExit(aLeaseConfig.IsValid(), error = kErrorInvalidArgs); mLeaseConfig = aLeaseConfig; exit: return error; } Error Server::SetDomain(const char *aDomain) { Error error = kErrorNone; uint16_t length; VerifyOrExit(mState == kStateDisabled, error = kErrorInvalidState); length = StringLength(aDomain, Dns::Name::kMaxNameSize); VerifyOrExit((length > 0) && (length < Dns::Name::kMaxNameSize), error = kErrorInvalidArgs); if (aDomain[length - 1] == '.') { error = mDomain.Set(aDomain); } else { // Need to append dot at the end char buf[Dns::Name::kMaxNameSize]; VerifyOrExit(length < Dns::Name::kMaxNameSize - 1, error = kErrorInvalidArgs); memcpy(buf, aDomain, length); buf[length] = '.'; buf[length + 1] = '\0'; error = mDomain.Set(buf); } exit: return error; } const Server::Host *Server::GetNextHost(const Server::Host *aHost) { return (aHost == nullptr) ? mHosts.GetHead() : aHost->GetNext(); } void Server::RemoveHost(Host *aHost, RetainName aRetainName) { VerifyOrExit(aHost != nullptr); aHost->mLease = 0; aHost->ClearResources(); if (aRetainName) { LogInfo("Remove host %s (but retain its name)", aHost->GetFullName()); } else { aHost->mKeyLease = 0; IgnoreError(mHosts.Remove(*aHost)); LogInfo("Fully remove host %s", aHost->GetFullName()); } if (mServiceUpdateHandler.IsSet()) { uint32_t updateId = AllocateServiceUpdateId(); LogInfo("SRP update handler is notified (updatedId = %lu)", ToUlong(updateId)); mServiceUpdateHandler.Invoke(updateId, aHost, static_cast(kDefaultEventsHandlerTimeout)); // We don't wait for the reply from the service update handler, // but always remove the host (and its services) regardless of // host/service update result. Because removing a host should fail // only when there is system failure of the platform mDNS implementation // and in which case the host is not expected to be still registered. } #if OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE else { Get().AdvertiseRemovalOf(*aHost); } #endif if (!aRetainName) { aHost->Free(); } exit: return; } bool Server::HasNameConflictsWith(Host &aHost) const { bool hasConflicts = false; const Host *existingHost = mHosts.FindMatching(aHost.GetFullName()); if ((existingHost != nullptr) && (aHost.mKey != existingHost->mKey)) { LogWarn("Name conflict: host name %s has already been allocated", aHost.GetFullName()); ExitNow(hasConflicts = true); } for (const Service &service : aHost.mServices) { // Check on all hosts for a matching service with the same // instance name and if found, verify that it has the same // key. for (const Host &host : mHosts) { if (host.HasService(service.GetInstanceName()) && (aHost.mKey != host.mKey)) { LogWarn("Name conflict: service name %s has already been allocated", service.GetInstanceName()); ExitNow(hasConflicts = true); } } } exit: return hasConflicts; } void Server::HandleServiceUpdateResult(ServiceUpdateId aId, Error aError) { UpdateMetadata *update = mOutstandingUpdates.RemoveMatching(aId); if (update == nullptr) { LogInfo("Delayed SRP host update result, the SRP update has been committed (updateId = %lu)", ToUlong(aId)); ExitNow(); } update->SetError(aError); LogInfo("Handler result of SRP update (id = %lu) is received: %s", ToUlong(update->GetId()), ErrorToString(aError)); // We add new `update` at the tail of the `mCompletedUpdates` list // so that updates are processed in the order we receive the // `HandleServiceUpdateResult()` callbacks for them. The // completed updates are processed from `mCompletedUpdateTask` // and `ProcessCompletedUpdates()`. mCompletedUpdates.PushAfterTail(*update); mCompletedUpdateTask.Post(); if (mOutstandingUpdates.IsEmpty()) { mOutstandingUpdatesTimer.Stop(); } else { mOutstandingUpdatesTimer.FireAt(mOutstandingUpdates.GetTail()->GetExpireTime()); } exit: return; } void Server::ProcessCompletedUpdates(void) { UpdateMetadata *update; while ((update = mCompletedUpdates.Pop()) != nullptr) { CommitSrpUpdate(*update); } } void Server::CommitSrpUpdate(Error aError, Host &aHost, const MessageMetadata &aMessageMetadata) { CommitSrpUpdate(aError, aHost, aMessageMetadata.mDnsHeader, aMessageMetadata.mMessageInfo, aMessageMetadata.mTtlConfig, aMessageMetadata.mLeaseConfig); } void Server::CommitSrpUpdate(UpdateMetadata &aUpdateMetadata) { CommitSrpUpdate(aUpdateMetadata.GetError(), aUpdateMetadata.GetHost(), aUpdateMetadata.GetDnsHeader(), aUpdateMetadata.IsDirectRxFromClient() ? &aUpdateMetadata.GetMessageInfo() : nullptr, aUpdateMetadata.GetTtlConfig(), aUpdateMetadata.GetLeaseConfig()); aUpdateMetadata.Free(); } void Server::CommitSrpUpdate(Error aError, Host &aHost, const Dns::UpdateHeader &aDnsHeader, const Ip6::MessageInfo *aMessageInfo, const TtlConfig &aTtlConfig, const LeaseConfig &aLeaseConfig) { Host *existingHost = nullptr; uint32_t grantedTtl = 0; uint32_t hostLease = 0; uint32_t hostKeyLease = 0; uint32_t grantedLease = 0; uint32_t grantedKeyLease = 0; bool useShortLease = aHost.ShouldUseShortLeaseOption(); if (aError != kErrorNone || (mState != kStateRunning)) { aHost.Free(); ExitNow(); } hostLease = aHost.GetLease(); hostKeyLease = aHost.GetKeyLease(); grantedLease = aLeaseConfig.GrantLease(hostLease); grantedKeyLease = useShortLease ? grantedLease : aLeaseConfig.GrantKeyLease(hostKeyLease); grantedTtl = aTtlConfig.GrantTtl(grantedLease, aHost.GetTtl()); existingHost = mHosts.RemoveMatching(aHost.GetFullName()); LogInfo("Committing update for %s host %s", (existingHost != nullptr) ? "existing" : "new", aHost.GetFullName()); LogInfo(" Granted lease:%lu, key-lease:%lu, ttl:%lu", ToUlong(grantedLease), ToUlong(grantedKeyLease), ToUlong(grantedTtl)); aHost.SetLease(grantedLease); aHost.SetKeyLease(grantedKeyLease); aHost.SetTtl(grantedTtl); if (grantedKeyLease == 0) { VerifyOrExit(existingHost != nullptr); LogInfo("Fully remove host %s", aHost.GetFullName()); aHost.Free(); ExitNow(); } mHosts.Push(aHost); for (Service &service : aHost.mServices) { service.mLease = grantedLease; service.mKeyLease = grantedKeyLease; service.mTtl = grantedTtl; service.mIsCommitted = true; #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) { Service::Action action = Service::kAddNew; if (service.mIsDeleted) { action = Service::kRemoveButRetainName; } else if ((existingHost != nullptr) && existingHost->HasService(service.GetInstanceName())) { action = Service::kUpdateExisting; } service.Log(action); } #endif } if (existingHost != nullptr) { // Move any existing service that is not included in the new // update into `aHost`. Service *existingService; while ((existingService = existingHost->mServices.Pop()) != nullptr) { if (!aHost.HasService(existingService->GetInstanceName())) { aHost.AddService(*existingService); // If host is deleted we make sure to add any existing // service that is not already included as deleted. // When processing an SRP update message that removes // a host, we construct `aHost` and add any existing // services that we have for the same host name as // deleted. However, due to asynchronous nature // of "update handler" (advertising proxy), by the // time we get the callback to commit `aHost`, there // may be new services added that were not included // when constructing `aHost`. if (aHost.IsDeleted() && !existingService->IsDeleted()) { existingService->mIsDeleted = true; existingService->Log(Service::kRemoveButRetainName); } else { existingService->Log(Service::kKeepUnchanged); } } else { existingService->Free(); } } } #if OPENTHREAD_CONFIG_SRP_SERVER_PORT_SWITCH_ENABLE if (!mHasRegisteredAnyService && (mAddressMode == kAddressModeUnicast)) { Settings::SrpServerInfo info; mHasRegisteredAnyService = true; info.SetPort(GetSocket().mSockName.mPort); IgnoreError(Get().Save(info)); } #endif if (!aHost.IsDeleted()) { mLeaseTimer.FireAtIfEarlier(Min(aHost.GetExpireTime(), aHost.GetKeyExpireTime())); } exit: if (aMessageInfo != nullptr) { if (aError == kErrorNone && !(grantedLease == hostLease && grantedKeyLease == hostKeyLease)) { SendResponse(aDnsHeader, grantedLease, grantedKeyLease, useShortLease, *aMessageInfo); } else { SendResponse(aDnsHeader, ErrorToDnsResponseCode(aError), *aMessageInfo); } } if (existingHost != nullptr) { existingHost->Free(); } } void Server::InitPort(void) { mPort = kUdpPortMin; #if OPENTHREAD_CONFIG_SRP_SERVER_PORT_SWITCH_ENABLE { Settings::SrpServerInfo info; if (Get().Read(info) == kErrorNone) { mPort = info.GetPort(); } } #endif } void Server::SelectPort(void) { if (mPort == kUninitializedPort) { InitPort(); } ++mPort; if (mPort < kUdpPortMin || mPort > kUdpPortMax) { mPort = kUdpPortMin; } LogInfo("Selected port %u", mPort); } void Server::Start(void) { Error error = kErrorNone; VerifyOrExit(mState == kStateStopped); mState = kStateRunning; SuccessOrExit(error = PrepareSocket()); LogInfo("Start listening on port %u", mPort); #if OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE Get().HandleServerStateChange(); #endif exit: // Re-enable server to select a new port. if (error != kErrorNone) { Disable(); Enable(); } } Error Server::PrepareSocket(void) { Error error = kErrorNone; #if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE Ip6::Udp::Socket &dnsSocket = Get().mSocket; if (dnsSocket.GetSockName().GetPort() == mPort) { // If the DNS-SD socket matches our port number, we use the // same socket so we close our own socket (in case it was // open). `GetSocket()` will now return the DNS-SD socket. IgnoreError(mSocket.Close()); ExitNow(); } #endif VerifyOrExit(!mSocket.IsOpen()); SuccessOrExit(error = mSocket.Open(HandleUdpReceive, this)); error = mSocket.Bind(mPort, Ip6::kNetifThread); exit: if (error != kErrorNone) { LogWarnOnError(error, "prepare socket"); IgnoreError(mSocket.Close()); Stop(); } return error; } Ip6::Udp::Socket &Server::GetSocket(void) { Ip6::Udp::Socket *socket = &mSocket; #if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE Ip6::Udp::Socket &dnsSocket = Get().mSocket; if (dnsSocket.GetSockName().GetPort() == mPort) { socket = &dnsSocket; } #endif return *socket; } #if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE void Server::HandleDnssdServerStateChange(void) { // This is called from` Dns::ServiceDiscovery::Server` to notify // that it has started or stopped. We check whether we need to // share the socket. if (mState == kStateRunning) { IgnoreError(PrepareSocket()); } } Error Server::HandleDnssdServerUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { // This is called from` Dns::ServiceDiscovery::Server` when a UDP // message is received on its socket. We check whether we are // sharing socket and if so we process the received message. We // return `kErrorNone` to indicate that message was successfully // processed by `Srp::Server`, otherwise `kErrorDrop` is returned. Error error = kErrorDrop; VerifyOrExit((mState == kStateRunning) && !mSocket.IsOpen()); error = ProcessMessage(aMessage, aMessageInfo); exit: return error; } #endif // OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE void Server::Stop(void) { VerifyOrExit(mState == kStateRunning); mState = kStateStopped; while (!mHosts.IsEmpty()) { RemoveHost(mHosts.GetHead(), kDeleteName); } // TODO: We should cancel any outstanding service updates, but current // OTBR mDNS publisher cannot properly handle it. while (!mOutstandingUpdates.IsEmpty()) { mOutstandingUpdates.Pop()->Free(); } mLeaseTimer.Stop(); mOutstandingUpdatesTimer.Stop(); LogInfo("Stop listening on %u", mPort); IgnoreError(mSocket.Close()); mHasRegisteredAnyService = false; #if OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE Get().HandleServerStateChange(); #endif exit: return; } void Server::HandleNetDataPublisherEvent(NetworkData::Publisher::Event aEvent) { switch (aEvent) { case NetworkData::Publisher::kEventEntryAdded: Start(); break; case NetworkData::Publisher::kEventEntryRemoved: Stop(); break; } } const Server::UpdateMetadata *Server::FindOutstandingUpdate(const MessageMetadata &aMessageMetadata) const { const UpdateMetadata *ret = nullptr; VerifyOrExit(aMessageMetadata.IsDirectRxFromClient()); for (const UpdateMetadata &update : mOutstandingUpdates) { if (aMessageMetadata.mDnsHeader.GetMessageId() == update.GetDnsHeader().GetMessageId() && aMessageMetadata.mMessageInfo->GetPeerAddr() == update.GetMessageInfo().GetPeerAddr() && aMessageMetadata.mMessageInfo->GetPeerPort() == update.GetMessageInfo().GetPeerPort()) { ExitNow(ret = &update); } } exit: return ret; } void Server::ProcessDnsUpdate(Message &aMessage, MessageMetadata &aMetadata) { Error error = kErrorNone; Host *host = nullptr; LogInfo("Received DNS update from %s", aMetadata.IsDirectRxFromClient() ? aMetadata.mMessageInfo->GetPeerAddr().ToString().AsCString() : "an SRPL Partner"); SuccessOrExit(error = ProcessZoneSection(aMessage, aMetadata)); if (FindOutstandingUpdate(aMetadata) != nullptr) { LogInfo("Drop duplicated SRP update request: MessageId=%u", aMetadata.mDnsHeader.GetMessageId()); // Silently drop duplicate requests. // This could rarely happen, because the outstanding SRP update timer should // be shorter than the SRP update retransmission timer. ExitNow(error = kErrorNone); } // Per 2.3.2 of SRP draft 6, no prerequisites should be included in a SRP update. VerifyOrExit(aMetadata.mDnsHeader.GetPrerequisiteRecordCount() == 0, error = kErrorFailed); host = Host::Allocate(GetInstance(), aMetadata.mRxTime); VerifyOrExit(host != nullptr, error = kErrorNoBufs); SuccessOrExit(error = ProcessUpdateSection(*host, aMessage, aMetadata)); // Parse lease time and validate signature. SuccessOrExit(error = ProcessAdditionalSection(host, aMessage, aMetadata)); #if OPENTHREAD_FTD if (aMetadata.IsDirectRxFromClient()) { UpdateAddrResolverCacheTable(*aMetadata.mMessageInfo, *host); } #endif HandleUpdate(*host, aMetadata); exit: if (error != kErrorNone) { if (host != nullptr) { host->Free(); } if (aMetadata.IsDirectRxFromClient()) { SendResponse(aMetadata.mDnsHeader, ErrorToDnsResponseCode(error), *aMetadata.mMessageInfo); } } } Error Server::ProcessZoneSection(const Message &aMessage, MessageMetadata &aMetadata) const { Error error = kErrorNone; Dns::Name::Buffer name; uint16_t offset = aMetadata.mOffset; VerifyOrExit(aMetadata.mDnsHeader.GetZoneRecordCount() == 1, error = kErrorParse); SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, name)); // TODO: return `Dns::kResponseNotAuth` for not authorized zone names. VerifyOrExit(StringMatch(name, GetDomain(), kStringCaseInsensitiveMatch), error = kErrorSecurity); SuccessOrExit(error = aMessage.Read(offset, aMetadata.mDnsZone)); offset += sizeof(Dns::Zone); VerifyOrExit(aMetadata.mDnsZone.GetType() == Dns::ResourceRecord::kTypeSoa, error = kErrorParse); aMetadata.mOffset = offset; exit: LogWarnOnError(error, "process DNS Zone section"); return error; } Error Server::ProcessUpdateSection(Host &aHost, const Message &aMessage, MessageMetadata &aMetadata) const { Error error = kErrorNone; // Process Service Discovery, Host and Service Description Instructions with // 3 times iterations over all DNS update RRs. The order of those processes matters. // 0. Enumerate over all Service Discovery Instructions before processing any other records. // So that we will know whether a name is a hostname or service instance name when processing // a "Delete All RRsets from a name" record. SuccessOrExit(error = ProcessServiceDiscoveryInstructions(aHost, aMessage, aMetadata)); // 1. Enumerate over all RRs to build the Host Description Instruction. SuccessOrExit(error = ProcessHostDescriptionInstruction(aHost, aMessage, aMetadata)); // 2. Enumerate over all RRs to build the Service Description Instructions. SuccessOrExit(error = ProcessServiceDescriptionInstructions(aHost, aMessage, aMetadata)); // 3. Verify that there are no name conflicts. VerifyOrExit(!HasNameConflictsWith(aHost), error = kErrorDuplicated); exit: LogWarnOnError(error, "Process DNS Update section"); return error; } Error Server::ProcessHostDescriptionInstruction(Host &aHost, const Message &aMessage, const MessageMetadata &aMetadata) const { Error error = kErrorNone; uint16_t offset = aMetadata.mOffset; OT_ASSERT(aHost.GetFullName() == nullptr); for (uint16_t numRecords = aMetadata.mDnsHeader.GetUpdateRecordCount(); numRecords > 0; numRecords--) { Dns::Name::Buffer name; Dns::ResourceRecord record; SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, name)); SuccessOrExit(error = aMessage.Read(offset, record)); if (record.GetClass() == Dns::ResourceRecord::kClassAny) { // Delete All RRsets from a name. VerifyOrExit(IsValidDeleteAllRecord(record), error = kErrorFailed); // A "Delete All RRsets from a name" RR can only apply to a Service or Host Description. if (!aHost.HasService(name)) { // If host name is already set to a different name, `SetFullName()` // will return `kErrorFailed`. SuccessOrExit(error = aHost.SetFullName(name)); aHost.ClearResources(); } } else if (record.GetType() == Dns::ResourceRecord::kTypeAaaa) { Dns::AaaaRecord aaaaRecord; VerifyOrExit(record.GetClass() == aMetadata.mDnsZone.GetClass(), error = kErrorFailed); SuccessOrExit(error = aHost.ProcessTtl(record.GetTtl())); SuccessOrExit(error = aHost.SetFullName(name)); SuccessOrExit(error = aMessage.Read(offset, aaaaRecord)); VerifyOrExit(aaaaRecord.IsValid(), error = kErrorParse); // Tolerate kErrorDrop for AAAA Resources. VerifyOrExit(aHost.AddIp6Address(aaaaRecord.GetAddress()) != kErrorNoBufs, error = kErrorNoBufs); } else if (record.GetType() == Dns::ResourceRecord::kTypeKey) { // We currently support only ECDSA P-256. Dns::Ecdsa256KeyRecord keyRecord; VerifyOrExit(record.GetClass() == aMetadata.mDnsZone.GetClass(), error = kErrorFailed); SuccessOrExit(error = aHost.ProcessTtl(record.GetTtl())); SuccessOrExit(error = aMessage.Read(offset, keyRecord)); VerifyOrExit(keyRecord.IsValid(), error = kErrorParse); if (aHost.mParsedKey) { VerifyOrExit(aHost.mKey == keyRecord.GetKey(), error = kErrorSecurity); } else { aHost.mParsedKey = true; aHost.mKey = keyRecord.GetKey(); } } offset += record.GetSize(); } // Verify that we have a complete Host Description Instruction. VerifyOrExit(aHost.GetFullName() != nullptr, error = kErrorFailed); VerifyOrExit(aHost.mParsedKey, error = kErrorFailed); // We check the number of host addresses after processing of the // Lease Option in the Addition Section and determining whether // the host is being removed or registered. exit: LogWarnOnError(error, "process Host Description instructions"); return error; } Error Server::ProcessServiceDiscoveryInstructions(Host &aHost, const Message &aMessage, const MessageMetadata &aMetadata) const { Error error = kErrorNone; uint16_t offset = aMetadata.mOffset; for (uint16_t numRecords = aMetadata.mDnsHeader.GetUpdateRecordCount(); numRecords > 0; numRecords--) { Dns::Name::Buffer serviceName; Dns::Name::LabelBuffer instanceLabel; Dns::Name::Buffer instanceServiceName; String instanceName; Dns::PtrRecord ptrRecord; const char *subServiceName; Service *service; bool isSubType; bool isDelete; SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, serviceName)); VerifyOrExit(Dns::Name::IsSubDomainOf(serviceName, GetDomain()), error = kErrorSecurity); error = Dns::ResourceRecord::ReadRecord(aMessage, offset, ptrRecord); if (error == kErrorNotFound) { // `ReadRecord()` updates `aOffset` to skip over a // non-matching record. error = kErrorNone; continue; } SuccessOrExit(error); SuccessOrExit(error = ptrRecord.ReadPtrName(aMessage, offset, instanceLabel, instanceServiceName)); instanceName.Append("%s.%s", instanceLabel, instanceServiceName); // Class None indicates "Delete an RR from an RRset". isDelete = (ptrRecord.GetClass() == Dns::ResourceRecord::kClassNone); VerifyOrExit(isDelete || ptrRecord.GetClass() == aMetadata.mDnsZone.GetClass(), error = kErrorParse); // Check if the `serviceName` is a sub-type with name // format: "._sub..." subServiceName = StringFind(serviceName, kServiceSubTypeLabel, kStringCaseInsensitiveMatch); isSubType = (subServiceName != nullptr); if (isSubType) { // Skip over the "._sub." label to get to the base // service name. subServiceName += sizeof(kServiceSubTypeLabel) - 1; } // Verify that instance name and service name are related. VerifyOrExit(Dns::Name::IsSubDomainOf(instanceName.AsCString(), isSubType ? subServiceName : serviceName), error = kErrorFailed); // Find a matching existing service or allocate a new one. service = aHost.FindService(instanceName.AsCString()); if (service == nullptr) { service = aHost.AddNewService(instanceName.AsCString(), instanceLabel, aMetadata.mRxTime); VerifyOrExit(service != nullptr, error = kErrorNoBufs); } if (isSubType) { VerifyOrExit(!service->HasSubTypeServiceName(serviceName), error = kErrorFailed); // Ignore a sub-type service delete. if (!isDelete) { Heap::String *newSubTypeLabel = service->mSubTypes.PushBack(); VerifyOrExit(newSubTypeLabel != nullptr, error = kErrorNoBufs); SuccessOrExit(error = newSubTypeLabel->Set(serviceName)); } } else { // Processed PTR record is the base service (not a // sub-type). `mServiceName` is only set when base // service is processed. VerifyOrExit(service->mServiceName.IsNull(), error = kErrorFailed); SuccessOrExit(error = service->mServiceName.Set(serviceName)); service->mIsDeleted = isDelete; } if (!isDelete) { SuccessOrExit(error = aHost.ProcessTtl(ptrRecord.GetTtl())); } } // Verify that for all services, a PTR record was processed for // the base service (`mServiceName` is set), and for a deleted // service, no PTR record was seen adding a sub-type. for (const Service &service : aHost.mServices) { VerifyOrExit(!service.mServiceName.IsNull(), error = kErrorParse); if (service.mIsDeleted) { VerifyOrExit(service.mSubTypes.GetLength() == 0, error = kErrorParse); } } exit: LogWarnOnError(error, "process Service Discovery instructions"); return error; } Error Server::ProcessServiceDescriptionInstructions(Host &aHost, const Message &aMessage, MessageMetadata &aMetadata) const { Error error = kErrorNone; uint16_t offset = aMetadata.mOffset; for (uint16_t numRecords = aMetadata.mDnsHeader.GetUpdateRecordCount(); numRecords > 0; numRecords--) { Dns::Name::Buffer name; Dns::ResourceRecord record; Service *service; SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, name)); SuccessOrExit(error = aMessage.Read(offset, record)); if (record.GetClass() == Dns::ResourceRecord::kClassAny) { // Delete All RRsets from a name. VerifyOrExit(IsValidDeleteAllRecord(record), error = kErrorFailed); service = aHost.FindService(name); if (service != nullptr) { VerifyOrExit(!service->mParsedDeleteAllRrset); service->mParsedDeleteAllRrset = true; } offset += record.GetSize(); continue; } if (record.GetType() == Dns::ResourceRecord::kTypeSrv) { Dns::SrvRecord srvRecord; Dns::Name::Buffer hostName; VerifyOrExit(record.GetClass() == aMetadata.mDnsZone.GetClass(), error = kErrorFailed); SuccessOrExit(error = aHost.ProcessTtl(record.GetTtl())); SuccessOrExit(error = aMessage.Read(offset, srvRecord)); offset += sizeof(srvRecord); SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, hostName)); VerifyOrExit(Dns::Name::IsSubDomainOf(name, GetDomain()), error = kErrorSecurity); VerifyOrExit(aHost.Matches(hostName), error = kErrorFailed); service = aHost.FindService(name); VerifyOrExit(service != nullptr, error = kErrorFailed); VerifyOrExit(!service->mParsedSrv, error = kErrorParse); service->mParsedSrv = true; service->mTtl = srvRecord.GetTtl(); service->mPriority = srvRecord.GetPriority(); service->mWeight = srvRecord.GetWeight(); service->mPort = srvRecord.GetPort(); } else if (record.GetType() == Dns::ResourceRecord::kTypeTxt) { VerifyOrExit(record.GetClass() == aMetadata.mDnsZone.GetClass(), error = kErrorFailed); SuccessOrExit(error = aHost.ProcessTtl(record.GetTtl())); service = aHost.FindService(name); VerifyOrExit(service != nullptr, error = kErrorFailed); service->mParsedTxt = true; offset += sizeof(record); SuccessOrExit(error = service->SetTxtDataFromMessage(aMessage, offset, record.GetLength())); offset += record.GetLength(); } else { offset += record.GetSize(); } } for (const Service &service : aHost.mServices) { VerifyOrExit(service.mParsedDeleteAllRrset, error = kErrorFailed); VerifyOrExit(service.mParsedSrv == service.mParsedTxt, error = kErrorFailed); if (!service.mIsDeleted) { VerifyOrExit(service.mParsedSrv, error = kErrorFailed); } } aMetadata.mOffset = offset; exit: LogWarnOnError(error, "process Service Description instructions"); return error; } bool Server::IsValidDeleteAllRecord(const Dns::ResourceRecord &aRecord) { return aRecord.GetClass() == Dns::ResourceRecord::kClassAny && aRecord.GetType() == Dns::ResourceRecord::kTypeAny && aRecord.GetTtl() == 0 && aRecord.GetLength() == 0; } Error Server::ProcessAdditionalSection(Host *aHost, const Message &aMessage, MessageMetadata &aMetadata) const { Error error = kErrorNone; Dns::OptRecord optRecord; Dns::LeaseOption leaseOption; Dns::SigRecord sigRecord; char name[2]; // The root domain name (".") is expected. uint16_t offset = aMetadata.mOffset; uint16_t sigOffset; uint16_t sigRdataOffset; Dns::Name::Buffer signerName; uint16_t signatureLength; VerifyOrExit(aMetadata.mDnsHeader.GetAdditionalRecordCount() == 2, error = kErrorFailed); // EDNS(0) Update Lease Option. SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, name)); SuccessOrExit(error = aMessage.Read(offset, optRecord)); SuccessOrExit(error = leaseOption.ReadFrom(aMessage, offset + sizeof(optRecord), optRecord.GetLength())); offset += optRecord.GetSize(); aHost->SetLease(leaseOption.GetLeaseInterval()); aHost->SetKeyLease(leaseOption.GetKeyLeaseInterval()); for (Service &service : aHost->mServices) { if (aHost->GetLease() == 0) { service.mIsDeleted = true; } service.mLease = service.mIsDeleted ? 0 : leaseOption.GetLeaseInterval(); service.mKeyLease = leaseOption.GetKeyLeaseInterval(); } // If the client included the short variant of Lease Option, // server must also use the short variant in its response. aHost->SetUseShortLeaseOption(leaseOption.IsShortVariant()); if (aHost->GetLease() > 0) { // There MUST be at least one valid address if we have nonzero lease. VerifyOrExit(aHost->mAddresses.GetLength() > 0, error = kErrorFailed); } // SIG(0). sigOffset = offset; SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, name)); SuccessOrExit(error = aMessage.Read(offset, sigRecord)); VerifyOrExit(sigRecord.IsValid(), error = kErrorParse); sigRdataOffset = offset + sizeof(Dns::ResourceRecord); offset += sizeof(sigRecord); // TODO: Verify that the signature doesn't expire. This is not // implemented because the end device may not be able to get // the synchronized date/time. SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, signerName)); signatureLength = sigRecord.GetLength() - (offset - sigRdataOffset); offset += signatureLength; // Verify the signature. Currently supports only ECDSA. VerifyOrExit(sigRecord.GetAlgorithm() == Dns::KeyRecord::kAlgorithmEcdsaP256Sha256, error = kErrorFailed); VerifyOrExit(sigRecord.GetTypeCovered() == 0, error = kErrorFailed); VerifyOrExit(signatureLength == Crypto::Ecdsa::P256::Signature::kSize, error = kErrorParse); SuccessOrExit(error = VerifySignature(aHost->mKey, aMessage, aMetadata.mDnsHeader, sigOffset, sigRdataOffset, sigRecord.GetLength(), signerName)); aMetadata.mOffset = offset; exit: LogWarnOnError(error, "process DNS Additional section"); return error; } Error Server::VerifySignature(const Host::Key &aKey, const Message &aMessage, Dns::UpdateHeader aDnsHeader, uint16_t aSigOffset, uint16_t aSigRdataOffset, uint16_t aSigRdataLength, const char *aSignerName) const { Error error; uint16_t offset = aMessage.GetOffset(); uint16_t signatureOffset; Crypto::Sha256 sha256; Crypto::Sha256::Hash hash; Crypto::Ecdsa::P256::Signature signature; Message *signerNameMessage = nullptr; VerifyOrExit(aSigRdataLength >= Crypto::Ecdsa::P256::Signature::kSize, error = kErrorInvalidArgs); sha256.Start(); // SIG RDATA less signature. sha256.Update(aMessage, aSigRdataOffset, sizeof(Dns::SigRecord) - sizeof(Dns::ResourceRecord)); // The uncompressed (canonical) form of the signer name should be used for signature // verification. See https://tools.ietf.org/html/rfc2931#section-3.1 for details. signerNameMessage = Get().NewMessage(); VerifyOrExit(signerNameMessage != nullptr, error = kErrorNoBufs); SuccessOrExit(error = Dns::Name::AppendName(aSignerName, *signerNameMessage)); sha256.Update(*signerNameMessage, signerNameMessage->GetOffset(), signerNameMessage->GetLength()); // We need the DNS header before appending the SIG RR. aDnsHeader.SetAdditionalRecordCount(aDnsHeader.GetAdditionalRecordCount() - 1); sha256.Update(aDnsHeader); sha256.Update(aMessage, offset + sizeof(aDnsHeader), aSigOffset - offset - sizeof(aDnsHeader)); sha256.Finish(hash); signatureOffset = aSigRdataOffset + aSigRdataLength - Crypto::Ecdsa::P256::Signature::kSize; SuccessOrExit(error = aMessage.Read(signatureOffset, signature)); error = aKey.Verify(hash, signature); exit: LogWarnOnError(error, "verify message signature"); FreeMessage(signerNameMessage); return error; } void Server::HandleUpdate(Host &aHost, const MessageMetadata &aMetadata) { Error error = kErrorNone; Host *existingHost; // Check whether the SRP update wants to remove `aHost`. VerifyOrExit(aHost.GetLease() == 0); aHost.ClearResources(); existingHost = mHosts.FindMatching(aHost.GetFullName()); VerifyOrExit(existingHost != nullptr); // The client may not include all services it has registered before // when removing a host. We copy and append any missing services to // `aHost` from the `existingHost` and mark them as deleted. for (const Service &existingService : existingHost->mServices) { Service *service; if (existingService.mIsDeleted || aHost.HasService(existingService.GetInstanceName())) { continue; } service = aHost.AddNewService(existingService.GetInstanceName(), existingService.GetInstanceLabel(), aMetadata.mRxTime); VerifyOrExit(service != nullptr, error = kErrorNoBufs); SuccessOrExit(error = service->mServiceName.Set(existingService.GetServiceName())); service->mIsDeleted = true; service->mKeyLease = existingService.mKeyLease; } exit: InformUpdateHandlerOrCommit(error, aHost, aMetadata); } void Server::InformUpdateHandlerOrCommit(Error aError, Host &aHost, const MessageMetadata &aMetadata) { #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) if (aError == kErrorNone) { uint8_t numAddrs; const Ip6::Address *addrs; LogInfo("Processed SRP update info"); LogInfo(" Host:%s", aHost.GetFullName()); LogInfo(" Lease:%lu, key-lease:%lu, ttl:%lu", ToUlong(aHost.GetLease()), ToUlong(aHost.GetKeyLease()), ToUlong(aHost.GetTtl())); addrs = aHost.GetAddresses(numAddrs); if (numAddrs == 0) { LogInfo(" No host address"); } else { LogInfo(" %d host address(es):", numAddrs); for (; numAddrs > 0; addrs++, numAddrs--) { LogInfo(" %s", addrs->ToString().AsCString()); } } for (const Service &service : aHost.mServices) { LogInfo(" %s service '%s'", service.IsDeleted() ? "Deleting" : "Adding", service.GetInstanceName()); for (const Heap::String &subType : service.mSubTypes) { Dns::Name::LabelBuffer label; IgnoreError(Service::ParseSubTypeServiceName(subType.AsCString(), label)); LogInfo(" sub-type: %s", label); } } } else { LogInfo("Error %s processing received SRP update", ErrorToString(aError)); } #endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) if ((aError == kErrorNone) && mServiceUpdateHandler.IsSet()) { UpdateMetadata *update = UpdateMetadata::Allocate(GetInstance(), aHost, aMetadata); if (update != nullptr) { mOutstandingUpdates.Push(*update); mOutstandingUpdatesTimer.FireAtIfEarlier(update->GetExpireTime()); LogInfo("SRP update handler is notified (updatedId = %lu)", ToUlong(update->GetId())); mServiceUpdateHandler.Invoke(update->GetId(), &aHost, static_cast(kDefaultEventsHandlerTimeout)); ExitNow(); } aError = kErrorNoBufs; } #if OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE else if (aError == kErrorNone) { // Ask `AdvertisingProxy` to advertise the new update. // Proxy will report the outcome by calling // `Server::CommitSrpUpdate()` directly. Get().Advertise(aHost, aMetadata); ExitNow(); } #endif CommitSrpUpdate(aError, aHost, aMetadata); exit: return; } void Server::SendResponse(const Dns::UpdateHeader &aHeader, Dns::UpdateHeader::Response aResponseCode, const Ip6::MessageInfo &aMessageInfo) { Error error = kErrorNone; Message *response = nullptr; Dns::UpdateHeader header; VerifyOrExit(mState == kStateRunning, error = kErrorInvalidState); response = GetSocket().NewMessage(); VerifyOrExit(response != nullptr, error = kErrorNoBufs); header.SetMessageId(aHeader.GetMessageId()); header.SetType(Dns::UpdateHeader::kTypeResponse); header.SetQueryType(aHeader.GetQueryType()); header.SetResponseCode(aResponseCode); SuccessOrExit(error = response->Append(header)); SuccessOrExit(error = GetSocket().SendTo(*response, aMessageInfo)); if (aResponseCode != Dns::UpdateHeader::kResponseSuccess) { LogWarn("Send fail response: %d", aResponseCode); } else { LogInfo("Send success response"); } UpdateResponseCounters(aResponseCode); exit: LogWarnOnError(error, "send response"); FreeMessageOnError(response, error); } void Server::SendResponse(const Dns::UpdateHeader &aHeader, uint32_t aLease, uint32_t aKeyLease, bool mUseShortLeaseOption, const Ip6::MessageInfo &aMessageInfo) { Error error; Message *response = nullptr; Dns::UpdateHeader header; Dns::OptRecord optRecord; Dns::LeaseOption leaseOption; uint16_t optionSize; response = GetSocket().NewMessage(); VerifyOrExit(response != nullptr, error = kErrorNoBufs); header.SetMessageId(aHeader.GetMessageId()); header.SetType(Dns::UpdateHeader::kTypeResponse); header.SetQueryType(aHeader.GetQueryType()); header.SetResponseCode(Dns::UpdateHeader::kResponseSuccess); header.SetAdditionalRecordCount(1); SuccessOrExit(error = response->Append(header)); // Append the root domain ("."). SuccessOrExit(error = Dns::Name::AppendTerminator(*response)); optRecord.Init(); optRecord.SetUdpPayloadSize(kUdpPayloadSize); optRecord.SetDnsSecurityFlag(); if (mUseShortLeaseOption) { leaseOption.InitAsShortVariant(aLease); } else { leaseOption.InitAsLongVariant(aLease, aKeyLease); } optionSize = static_cast(leaseOption.GetSize()); optRecord.SetLength(optionSize); SuccessOrExit(error = response->Append(optRecord)); SuccessOrExit(error = response->AppendBytes(&leaseOption, optionSize)); SuccessOrExit(error = GetSocket().SendTo(*response, aMessageInfo)); LogInfo("Send success response with granted lease: %lu and key lease: %lu", ToUlong(aLease), ToUlong(aKeyLease)); UpdateResponseCounters(Dns::UpdateHeader::kResponseSuccess); exit: LogWarnOnError(error, "send response"); FreeMessageOnError(response, error); } void Server::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) { static_cast(aContext)->HandleUdpReceive(AsCoreType(aMessage), AsCoreType(aMessageInfo)); } void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { Error error = ProcessMessage(aMessage, aMessageInfo); LogWarnOnError(error, "handle DNS message"); OT_UNUSED_VARIABLE(error); } Error Server::ProcessMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { return ProcessMessage(aMessage, TimerMilli::GetNow(), mTtlConfig, mLeaseConfig, &aMessageInfo); } Error Server::ProcessMessage(Message &aMessage, TimeMilli aRxTime, const TtlConfig &aTtlConfig, const LeaseConfig &aLeaseConfig, const Ip6::MessageInfo *aMessageInfo) { Error error; MessageMetadata metadata; metadata.mOffset = aMessage.GetOffset(); metadata.mRxTime = aRxTime; metadata.mTtlConfig = aTtlConfig; metadata.mLeaseConfig = aLeaseConfig; metadata.mMessageInfo = aMessageInfo; SuccessOrExit(error = aMessage.Read(metadata.mOffset, metadata.mDnsHeader)); metadata.mOffset += sizeof(Dns::UpdateHeader); VerifyOrExit(metadata.mDnsHeader.GetType() == Dns::UpdateHeader::Type::kTypeQuery, error = kErrorDrop); VerifyOrExit(metadata.mDnsHeader.GetQueryType() == Dns::UpdateHeader::kQueryTypeUpdate, error = kErrorDrop); ProcessDnsUpdate(aMessage, metadata); exit: return error; } void Server::HandleLeaseTimer(void) { TimeMilli now = TimerMilli::GetNow(); TimeMilli earliestExpireTime = now.GetDistantFuture(); Host *nextHost; for (Host *host = mHosts.GetHead(); host != nullptr; host = nextHost) { nextHost = host->GetNext(); if (host->GetKeyExpireTime() <= now) { LogInfo("KEY LEASE of host %s expired", host->GetFullName()); // Removes the whole host and all services if the KEY RR expired. RemoveHost(host, kDeleteName); } else if (host->IsDeleted()) { // The host has been deleted, but the hostname & service instance names retain. Service *next; earliestExpireTime = Min(earliestExpireTime, host->GetKeyExpireTime()); // Check if any service instance name expired. for (Service *service = host->mServices.GetHead(); service != nullptr; service = next) { next = service->GetNext(); OT_ASSERT(service->mIsDeleted); if (service->GetKeyExpireTime() <= now) { service->Log(Service::kKeyLeaseExpired); host->RemoveService(service, kDeleteName, kNotifyServiceHandler); } else { earliestExpireTime = Min(earliestExpireTime, service->GetKeyExpireTime()); } } } else if (host->GetExpireTime() <= now) { LogInfo("LEASE of host %s expired", host->GetFullName()); // If the host expired, delete all resources of this host and its services. for (Service &service : host->mServices) { // Don't need to notify the service handler as `RemoveHost` at below will do. host->RemoveService(&service, kRetainName, kDoNotNotifyServiceHandler); } RemoveHost(host, kRetainName); earliestExpireTime = Min(earliestExpireTime, host->GetKeyExpireTime()); } else { // The host doesn't expire, check if any service expired or is explicitly removed. Service *next; OT_ASSERT(!host->IsDeleted()); earliestExpireTime = Min(earliestExpireTime, host->GetExpireTime()); for (Service *service = host->mServices.GetHead(); service != nullptr; service = next) { next = service->GetNext(); if (service->GetKeyExpireTime() <= now) { service->Log(Service::kKeyLeaseExpired); host->RemoveService(service, kDeleteName, kNotifyServiceHandler); } else if (service->mIsDeleted) { // The service has been deleted but the name retains. earliestExpireTime = Min(earliestExpireTime, service->GetKeyExpireTime()); } else if (service->GetExpireTime() <= now) { service->Log(Service::kLeaseExpired); // The service is expired, delete it. host->RemoveService(service, kRetainName, kNotifyServiceHandler); earliestExpireTime = Min(earliestExpireTime, service->GetKeyExpireTime()); } else { earliestExpireTime = Min(earliestExpireTime, service->GetExpireTime()); } } } } if (earliestExpireTime != now.GetDistantFuture()) { OT_ASSERT(earliestExpireTime >= now); if (!mLeaseTimer.IsRunning() || earliestExpireTime <= mLeaseTimer.GetFireTime()) { LogInfo("Lease timer is scheduled for %lu seconds", ToUlong(Time::MsecToSec(earliestExpireTime - now))); mLeaseTimer.StartAt(earliestExpireTime, 0); } } else { LogInfo("Lease timer is stopped"); mLeaseTimer.Stop(); } } void Server::HandleOutstandingUpdatesTimer(void) { TimeMilli now = TimerMilli::GetNow(); UpdateMetadata *update; while ((update = mOutstandingUpdates.GetTail()) != nullptr) { if (update->GetExpireTime() > now) { mOutstandingUpdatesTimer.FireAtIfEarlier(update->GetExpireTime()); break; } LogInfo("Outstanding service update timeout (updateId = %lu)", ToUlong(update->GetId())); IgnoreError(mOutstandingUpdates.Remove(*update)); update->SetError(kErrorResponseTimeout); CommitSrpUpdate(*update); } } const char *Server::AddressModeToString(AddressMode aMode) { static const char *const kAddressModeStrings[] = { "unicast", // (0) kAddressModeUnicast "anycast", // (1) kAddressModeAnycast }; static_assert(kAddressModeUnicast == 0, "kAddressModeUnicast value is incorrect"); static_assert(kAddressModeAnycast == 1, "kAddressModeAnycast value is incorrect"); return kAddressModeStrings[aMode]; } void Server::UpdateResponseCounters(Dns::UpdateHeader::Response aResponseCode) { switch (aResponseCode) { case Dns::UpdateHeader::kResponseSuccess: ++mResponseCounters.mSuccess; break; case Dns::UpdateHeader::kResponseServerFailure: ++mResponseCounters.mServerFailure; break; case Dns::UpdateHeader::kResponseFormatError: ++mResponseCounters.mFormatError; break; case Dns::UpdateHeader::kResponseNameExists: ++mResponseCounters.mNameExists; break; case Dns::UpdateHeader::kResponseRefused: ++mResponseCounters.mRefused; break; default: ++mResponseCounters.mOther; break; } } #if OPENTHREAD_FTD void Server::UpdateAddrResolverCacheTable(const Ip6::MessageInfo &aMessageInfo, const Host &aHost) { // If message is from a client on mesh, we add all registered // addresses as snooped entries in the address resolver cache // table. We associate the registered addresses with the same // RLOC16 (if any) as the received message's peer IPv6 address. uint16_t rloc16; VerifyOrExit(aHost.GetLease() != 0); VerifyOrExit(aHost.GetTtl() > 0); // If the `LookUp()` call succeeds, the cache entry will be marked // as "cached and in-use". We can mark it as "in-use" early since // the entry will be needed and used soon when sending the SRP // response. This also prevents a snooped cache entry (added for // `GetPeerAddr()` due to rx of the SRP update message) from // being overwritten by `UpdateSnoopedCacheEntry()` calls when // there are limited snoop entries available. rloc16 = Get().LookUp(aMessageInfo.GetPeerAddr()); VerifyOrExit(rloc16 != Mac::kShortAddrInvalid); for (const Ip6::Address &address : aHost.mAddresses) { Get().UpdateSnoopedCacheEntry(address, rloc16, Get().GetRloc16()); } exit: return; } #endif //--------------------------------------------------------------------------------------------------------------------- // Server::Service Error Server::Service::Init(const char *aInstanceName, const char *aInstanceLabel, Host &aHost, TimeMilli aUpdateTime) { Error error; mNext = nullptr; mHost = &aHost; mPriority = 0; mWeight = 0; mTtl = 0; mPort = 0; mLease = 0; mKeyLease = 0; mUpdateTime = aUpdateTime; mIsDeleted = false; mIsCommitted = false; #if OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE mIsRegistered = false; mIsKeyRegistered = false; mIsReplaced = false; mShouldAdvertise = false; mShouldRegisterKey = false; mAdvId = kInvalidRequestId; mKeyAdvId = kInvalidRequestId; #endif mParsedDeleteAllRrset = false; mParsedSrv = false; mParsedTxt = false; SuccessOrExit(error = mInstanceLabel.Set(aInstanceLabel)); error = mInstanceName.Set(aInstanceName); exit: return error; } const char *Server::Service::GetSubTypeServiceNameAt(uint16_t aIndex) const { const Heap::String *subType = mSubTypes.At(aIndex); return (subType == nullptr) ? nullptr : subType->AsCString(); } Error Server::Service::ParseSubTypeServiceName(const char *aSubTypeServiceName, char *aLabel, uint8_t aLabelSize) { Error error = kErrorNone; const char *subPos; uint8_t labelLength; aLabel[0] = kNullChar; subPos = StringFind(aSubTypeServiceName, kServiceSubTypeLabel, kStringCaseInsensitiveMatch); VerifyOrExit(subPos != nullptr, error = kErrorInvalidArgs); if (subPos - aSubTypeServiceName < aLabelSize) { labelLength = static_cast(subPos - aSubTypeServiceName); } else { labelLength = aLabelSize - 1; error = kErrorNoBufs; } memcpy(aLabel, aSubTypeServiceName, labelLength); aLabel[labelLength] = kNullChar; exit: return error; } TimeMilli Server::Service::GetExpireTime(void) const { OT_ASSERT(!mIsDeleted); OT_ASSERT(!GetHost().IsDeleted()); return mUpdateTime + Time::SecToMsec(mLease); } TimeMilli Server::Service::GetKeyExpireTime(void) const { return mUpdateTime + Time::SecToMsec(mKeyLease); } void Server::Service::GetLeaseInfo(LeaseInfo &aLeaseInfo) const { TimeMilli now = TimerMilli::GetNow(); TimeMilli keyExpireTime = GetKeyExpireTime(); aLeaseInfo.mLease = Time::SecToMsec(GetLease()); aLeaseInfo.mKeyLease = Time::SecToMsec(GetKeyLease()); aLeaseInfo.mRemainingKeyLease = (now <= keyExpireTime) ? (keyExpireTime - now) : 0; if (!mIsDeleted) { TimeMilli expireTime = GetExpireTime(); aLeaseInfo.mRemainingLease = (now <= expireTime) ? (expireTime - now) : 0; } else { aLeaseInfo.mRemainingLease = 0; } } bool Server::Service::MatchesInstanceName(const char *aInstanceName) const { return Matches(aInstanceName); } bool Server::Service::MatchesServiceName(const char *aServiceName) const { return StringMatch(mServiceName.AsCString(), aServiceName, kStringCaseInsensitiveMatch); } bool Server::Service::Matches(const char *aInstanceName) const { return StringMatch(mInstanceName.AsCString(), aInstanceName, kStringCaseInsensitiveMatch); } bool Server::Service::HasSubTypeServiceName(const char *aSubTypeServiceName) const { bool has = false; for (const Heap::String &subType : mSubTypes) { if (StringMatch(subType.AsCString(), aSubTypeServiceName, kStringCaseInsensitiveMatch)) { has = true; break; } } return has; } #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) void Server::Service::Log(Action aAction) const { static const char *const kActionStrings[] = { "Add new", // (0) kAddNew "Update existing", // (1) kUpdateExisting "Keep unchanged", // (2) kKeepUnchanged "Remove but retain name of", // (3) kRemoveButRetainName "Fully remove", // (4) kFullyRemove "LEASE expired for", // (5) kLeaseExpired "KEY LEASE expired for", // (6) kKeyLeaseExpired }; static_assert(0 == kAddNew, "kAddNew value is incorrect"); static_assert(1 == kUpdateExisting, "kUpdateExisting value is incorrect"); static_assert(2 == kKeepUnchanged, "kKeepUnchanged value is incorrect"); static_assert(3 == kRemoveButRetainName, "kRemoveButRetainName value is incorrect"); static_assert(4 == kFullyRemove, "kFullyRemove value is incorrect"); static_assert(5 == kLeaseExpired, "kLeaseExpired value is incorrect"); static_assert(6 == kKeyLeaseExpired, "kKeyLeaseExpired value is incorrect"); // We only log if the `Service` is marked as committed. This // ensures that temporary `Service` entries associated with a // newly received SRP update message are not logged (e.g., when // associated `Host` is being freed). if (mIsCommitted) { LogInfo("%s service '%s'", kActionStrings[aAction], GetInstanceName()); for (const Heap::String &subType : mSubTypes) { Dns::Name::LabelBuffer label; IgnoreError(ParseSubTypeServiceName(subType.AsCString(), label)); LogInfo(" sub-type: %s", subType.AsCString()); } } } #else void Server::Service::Log(Action) const {} #endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) Error Server::Service::SetTxtDataFromMessage(const Message &aMessage, uint16_t aOffset, uint16_t aLength) { Error error; SuccessOrExit(error = mTxtData.SetFrom(aMessage, aOffset, aLength)); VerifyOrExit(Dns::TxtRecord::VerifyTxtData(mTxtData.GetBytes(), mTxtData.GetLength(), /* aAllowEmpty */ false), error = kErrorParse); exit: if (error != kErrorNone) { mTxtData.Free(); } return error; } //--------------------------------------------------------------------------------------------------------------------- // Server::Host Server::Host::Host(Instance &aInstance, TimeMilli aUpdateTime) : InstanceLocator(aInstance) , mNext(nullptr) , mTtl(0) , mLease(0) , mKeyLease(0) , mUpdateTime(aUpdateTime) , mParsedKey(false) , mUseShortLeaseOption(false) #if OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE , mIsRegistered(false) , mIsKeyRegistered(false) , mIsReplaced(false) , mShouldAdvertise(false) , mShouldRegisterKey(false) , mAdvId(kInvalidRequestId) , mKeyAdvId(kInvalidRequestId) #endif { } Server::Host::~Host(void) { FreeAllServices(); } Error Server::Host::SetFullName(const char *aFullName) { // `mFullName` becomes immutable after it is set, so if it is // already set, we only accept a `aFullName` that matches the // current name. Error error; if (mFullName.IsNull()) { error = mFullName.Set(aFullName); } else { error = Matches(aFullName) ? kErrorNone : kErrorFailed; } return error; } bool Server::Host::Matches(const char *aFullName) const { return StringMatch(mFullName.AsCString(), aFullName, kStringCaseInsensitiveMatch); } TimeMilli Server::Host::GetExpireTime(void) const { OT_ASSERT(!IsDeleted()); return mUpdateTime + Time::SecToMsec(mLease); } TimeMilli Server::Host::GetKeyExpireTime(void) const { return mUpdateTime + Time::SecToMsec(mKeyLease); } void Server::Host::GetLeaseInfo(LeaseInfo &aLeaseInfo) const { TimeMilli now = TimerMilli::GetNow(); TimeMilli keyExpireTime = GetKeyExpireTime(); aLeaseInfo.mLease = Time::SecToMsec(GetLease()); aLeaseInfo.mKeyLease = Time::SecToMsec(GetKeyLease()); aLeaseInfo.mRemainingKeyLease = (now <= keyExpireTime) ? (keyExpireTime - now) : 0; if (!IsDeleted()) { TimeMilli expireTime = GetExpireTime(); aLeaseInfo.mRemainingLease = (now <= expireTime) ? (expireTime - now) : 0; } else { aLeaseInfo.mRemainingLease = 0; } } Error Server::Host::ProcessTtl(uint32_t aTtl) { // This method processes the TTL value received in a resource record. // // If no TTL value is stored, this method will set the stored value to @p aTtl and return `kErrorNone`. // If a TTL value is stored and @p aTtl equals the stored value, this method returns `kErrorNone`. // Otherwise, this method returns `kErrorRejected`. Error error = kErrorRejected; VerifyOrExit(aTtl && (mTtl == 0 || mTtl == aTtl)); mTtl = aTtl; error = kErrorNone; exit: return error; } const Server::Service *Server::Host::GetNextService(const Service *aPrevService) const { return (aPrevService == nullptr) ? mServices.GetHead() : aPrevService->GetNext(); } Server::Service *Server::Host::AddNewService(const char *aInstanceName, const char *aInstanceLabel, TimeMilli aUpdateTime) { Service *service = Service::AllocateAndInit(aInstanceName, aInstanceLabel, *this, aUpdateTime); VerifyOrExit(service != nullptr); AddService(*service); exit: return service; } void Server::Host::AddService(Service &aService) { aService.mHost = this; mServices.Push(aService); } void Server::Host::RemoveService(Service *aService, RetainName aRetainName, NotifyMode aNotifyServiceHandler) { Server &server = Get(); VerifyOrExit(aService != nullptr); aService->mIsDeleted = true; aService->mLease = 0; if (!aRetainName) { aService->mKeyLease = 0; } aService->Log(aRetainName ? Service::kRemoveButRetainName : Service::kFullyRemove); if (aNotifyServiceHandler && server.mServiceUpdateHandler.IsSet()) { uint32_t updateId = server.AllocateServiceUpdateId(); LogInfo("SRP update handler is notified (updatedId = %lu)", ToUlong(updateId)); server.mServiceUpdateHandler.Invoke(updateId, this, static_cast(kDefaultEventsHandlerTimeout)); // We don't wait for the reply from the service update handler, // but always remove the service regardless of service update result. // Because removing a service should fail only when there is system // failure of the platform mDNS implementation and in which case the // service is not expected to be still registered. } #if OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE else if (aNotifyServiceHandler) { Get().AdvertiseRemovalOf(*aService); } #endif if (!aRetainName) { IgnoreError(mServices.Remove(*aService)); aService->Free(); } exit: return; } void Server::Host::FreeAllServices(void) { while (!mServices.IsEmpty()) { RemoveService(mServices.GetHead(), kDeleteName, kDoNotNotifyServiceHandler); } } void Server::Host::ClearResources(void) { mAddresses.Free(); } Server::Service *Server::Host::FindService(const char *aInstanceName) { return mServices.FindMatching(aInstanceName); } const Server::Service *Server::Host::FindService(const char *aInstanceName) const { return mServices.FindMatching(aInstanceName); } bool Server::Host::HasService(const char *aInstanceName) const { return mServices.ContainsMatching(aInstanceName); } Error Server::Host::AddIp6Address(const Ip6::Address &aIp6Address) { Error error = kErrorNone; if (aIp6Address.IsMulticast() || aIp6Address.IsUnspecified() || aIp6Address.IsLoopback()) { // We don't like those address because they cannot be used // for communication with exterior devices. ExitNow(error = kErrorDrop); } // Drop duplicate addresses. VerifyOrExit(!mAddresses.Contains(aIp6Address), error = kErrorDrop); error = mAddresses.PushBack(aIp6Address); if (error == kErrorNoBufs) { LogWarn("Too many addresses for host %s", GetFullName()); } exit: return error; } //--------------------------------------------------------------------------------------------------------------------- // Server::UpdateMetadata Server::UpdateMetadata::UpdateMetadata(Instance &aInstance, Host &aHost, const MessageMetadata &aMessageMetadata) : InstanceLocator(aInstance) , mNext(nullptr) , mExpireTime(TimerMilli::GetNow() + kDefaultEventsHandlerTimeout) , mDnsHeader(aMessageMetadata.mDnsHeader) , mId(Get().AllocateServiceUpdateId()) , mTtlConfig(aMessageMetadata.mTtlConfig) , mLeaseConfig(aMessageMetadata.mLeaseConfig) , mHost(aHost) , mError(kErrorNone) , mIsDirectRxFromClient(aMessageMetadata.IsDirectRxFromClient()) { if (aMessageMetadata.mMessageInfo != nullptr) { mMessageInfo = *aMessageMetadata.mMessageInfo; } } } // namespace Srp } // namespace ot #endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE