| /* |
| * 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/instance.hpp" |
| #include "common/locator-getters.hpp" |
| #include "common/logging.hpp" |
| #include "common/new.hpp" |
| #include "net/dns_headers.hpp" |
| #include "thread/network_data_service.hpp" |
| #include "thread/thread_netif.hpp" |
| |
| namespace ot { |
| namespace Srp { |
| |
| static const char kDefaultDomain[] = "default.service.arpa."; |
| |
| static Dns::UpdateHeader::Response ErrorToDnsResponseCode(otError aError) |
| { |
| Dns::UpdateHeader::Response responseCode; |
| |
| switch (aError) |
| { |
| case OT_ERROR_NONE: |
| responseCode = Dns::UpdateHeader::kResponseSuccess; |
| break; |
| case OT_ERROR_NO_BUFS: |
| responseCode = Dns::UpdateHeader::kResponseServerFailure; |
| break; |
| case OT_ERROR_PARSE: |
| responseCode = Dns::UpdateHeader::kResponseFormatError; |
| break; |
| case OT_ERROR_DUPLICATED: |
| responseCode = Dns::UpdateHeader::kResponseNameExists; |
| break; |
| default: |
| responseCode = Dns::UpdateHeader::kResponseRefused; |
| break; |
| } |
| |
| return responseCode; |
| } |
| |
| Server::Server(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mSocket(aInstance) |
| , mAdvertisingHandler(nullptr) |
| , mAdvertisingHandlerContext(nullptr) |
| , mDomain(nullptr) |
| , mMinLease(kDefaultMinLease) |
| , mMaxLease(kDefaultMaxLease) |
| , mMinKeyLease(kDefaultMinKeyLease) |
| , mMaxKeyLease(kDefaultMaxKeyLease) |
| , mLeaseTimer(aInstance, HandleLeaseTimer) |
| , mOutstandingUpdatesTimer(aInstance, HandleOutstandingUpdatesTimer) |
| , mEnabled(false) |
| { |
| IgnoreError(SetDomain(kDefaultDomain)); |
| } |
| |
| Server::~Server(void) |
| { |
| Instance::HeapFree(mDomain); |
| } |
| |
| void Server::SetServiceHandler(otSrpServerServiceUpdateHandler aServiceHandler, void *aServiceHandlerContext) |
| { |
| mAdvertisingHandler = aServiceHandler; |
| mAdvertisingHandlerContext = aServiceHandlerContext; |
| } |
| |
| bool Server::IsRunning(void) const |
| { |
| return mSocket.IsBound(); |
| } |
| |
| void Server::SetEnabled(bool aEnabled) |
| { |
| VerifyOrExit(mEnabled != aEnabled); |
| |
| mEnabled = aEnabled; |
| |
| if (!mEnabled) |
| { |
| Stop(); |
| } |
| else if (Get<Mle::MleRouter>().IsAttached()) |
| { |
| Start(); |
| } |
| |
| exit: |
| return; |
| } |
| |
| otError Server::SetLeaseRange(uint32_t aMinLease, uint32_t aMaxLease, uint32_t aMinKeyLease, uint32_t aMaxKeyLease) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| // TODO: Support longer LEASE. |
| // We use milliseconds timer for LEASE & KEY-LEASE, this is to avoid overflow. |
| VerifyOrExit(aMaxKeyLease <= TimerMilli::kMaxDelay / 1000, error = OT_ERROR_INVALID_ARGS); |
| VerifyOrExit(aMinLease <= aMaxLease, error = OT_ERROR_INVALID_ARGS); |
| VerifyOrExit(aMinKeyLease <= aMaxKeyLease, error = OT_ERROR_INVALID_ARGS); |
| VerifyOrExit(aMinLease <= aMinKeyLease, error = OT_ERROR_INVALID_ARGS); |
| VerifyOrExit(aMaxLease <= aMaxKeyLease, error = OT_ERROR_INVALID_ARGS); |
| |
| mMinLease = aMinLease; |
| mMaxLease = aMaxLease; |
| mMinKeyLease = aMinKeyLease; |
| mMaxKeyLease = aMaxKeyLease; |
| |
| exit: |
| return error; |
| } |
| |
| uint32_t Server::GrantLease(uint32_t aLease) const |
| { |
| OT_ASSERT(mMinLease <= mMaxLease); |
| |
| return (aLease == 0) ? 0 : OT_MAX(mMinLease, OT_MIN(mMaxLease, aLease)); |
| } |
| |
| uint32_t Server::GrantKeyLease(uint32_t aKeyLease) const |
| { |
| OT_ASSERT(mMinKeyLease <= mMaxKeyLease); |
| |
| return (aKeyLease == 0) ? 0 : OT_MAX(mMinKeyLease, OT_MIN(mMaxKeyLease, aKeyLease)); |
| } |
| |
| const char *Server::GetDomain(void) const |
| { |
| return mDomain; |
| } |
| |
| otError Server::SetDomain(const char *aDomain) |
| { |
| otError error = OT_ERROR_NONE; |
| char * buf = nullptr; |
| size_t appendTrailingDot = 0; |
| size_t length = strlen(aDomain); |
| |
| VerifyOrExit(!mEnabled, error = OT_ERROR_INVALID_STATE); |
| |
| VerifyOrExit(length > 0 && length <= Dns::Name::kMaxLength, error = OT_ERROR_INVALID_ARGS); |
| if (aDomain[length - 1] != '.') |
| { |
| appendTrailingDot = 1; |
| } |
| |
| buf = static_cast<char *>(Instance::HeapCAlloc(1, length + appendTrailingDot + 1)); |
| VerifyOrExit(buf != nullptr, error = OT_ERROR_NO_BUFS); |
| |
| strcpy(buf, aDomain); |
| if (appendTrailingDot) |
| { |
| buf[length] = '.'; |
| buf[length + 1] = '\0'; |
| } |
| Instance::HeapFree(mDomain); |
| mDomain = buf; |
| |
| exit: |
| if (error != OT_ERROR_NONE) |
| { |
| Instance::HeapFree(buf); |
| } |
| return error; |
| } |
| |
| const Server::Host *Server::GetNextHost(const Server::Host *aHost) |
| { |
| return (aHost == nullptr) ? mHosts.GetHead() : aHost->GetNext(); |
| } |
| |
| // This method adds a SRP service host and takes ownership of it. |
| // The caller MUST make sure that there is no existing host with the same hostname. |
| void Server::AddHost(Host *aHost) |
| { |
| OT_ASSERT(mHosts.FindMatching(aHost->GetFullName()) == nullptr); |
| IgnoreError(mHosts.Add(*aHost)); |
| } |
| |
| void Server::RemoveAndFreeHost(Host *aHost) |
| { |
| otLogInfoSrp("[server] fully remove host %s", aHost->GetFullName()); |
| IgnoreError(mHosts.Remove(*aHost)); |
| aHost->Free(); |
| } |
| |
| const Server::Service *Server::FindService(const char *aFullName) const |
| { |
| const Service *service = nullptr; |
| |
| for (const Host *host = mHosts.GetHead(); host != nullptr; host = host->GetNext()) |
| { |
| service = host->FindService(aFullName); |
| if (service != nullptr) |
| { |
| break; |
| } |
| } |
| |
| return service; |
| } |
| |
| bool Server::HasNameConflictsWith(Host &aHost) const |
| { |
| bool hasConflicts = false; |
| const Service *service = nullptr; |
| const Host * existingHost = mHosts.FindMatching(aHost.GetFullName()); |
| |
| if (existingHost != nullptr && *aHost.GetKey() != *existingHost->GetKey()) |
| { |
| ExitNow(hasConflicts = true); |
| } |
| |
| // Check not only services of this host but all hosts. |
| while ((service = aHost.GetNextService(service)) != nullptr) |
| { |
| const Service *existingService = FindService(service->mFullName); |
| if (existingService != nullptr && *service->GetHost().GetKey() != *existingService->GetHost().GetKey()) |
| { |
| ExitNow(hasConflicts = true); |
| } |
| } |
| |
| exit: |
| return hasConflicts; |
| } |
| |
| void Server::HandleAdvertisingResult(const Host *aHost, otError aError) |
| { |
| UpdateMetadata *update = mOutstandingUpdates.FindMatching(aHost); |
| |
| if (update != nullptr) |
| { |
| HandleAdvertisingResult(update, aError); |
| } |
| else |
| { |
| otLogInfoSrp("[server] delayed SRP host update result, the SRP update has been committed"); |
| } |
| } |
| |
| void Server::HandleAdvertisingResult(UpdateMetadata *aUpdate, otError aError) |
| { |
| HandleSrpUpdateResult(aError, aUpdate->GetDnsHeader(), aUpdate->GetHost(), aUpdate->GetMessageInfo()); |
| |
| IgnoreError(mOutstandingUpdates.Remove(*aUpdate)); |
| aUpdate->Free(); |
| |
| if (mOutstandingUpdates.IsEmpty()) |
| { |
| mOutstandingUpdatesTimer.Stop(); |
| } |
| else |
| { |
| mOutstandingUpdatesTimer.StartAt(mOutstandingUpdates.GetTail()->GetExpireTime(), 0); |
| } |
| } |
| |
| void Server::HandleSrpUpdateResult(otError aError, |
| const Dns::UpdateHeader &aDnsHeader, |
| Host & aHost, |
| const Ip6::MessageInfo & aMessageInfo) |
| { |
| Host * existingHost; |
| uint32_t hostLease; |
| uint32_t hostKeyLease; |
| uint32_t grantedLease; |
| uint32_t grantedKeyLease; |
| |
| SuccessOrExit(aError); |
| |
| hostLease = aHost.GetLease(); |
| hostKeyLease = aHost.GetKeyLease(); |
| grantedLease = GrantLease(hostLease); |
| grantedKeyLease = GrantKeyLease(hostKeyLease); |
| |
| aHost.SetLease(grantedLease); |
| aHost.SetKeyLease(grantedKeyLease); |
| |
| existingHost = mHosts.FindMatching(aHost.GetFullName()); |
| |
| if (aHost.GetLease() == 0) |
| { |
| if (aHost.GetKeyLease() == 0) |
| { |
| otLogInfoSrp("[server] remove key of host %s", aHost.GetFullName()); |
| |
| if (existingHost != nullptr) |
| { |
| RemoveAndFreeHost(existingHost); |
| } |
| } |
| else if (existingHost != nullptr) |
| { |
| Service *service = nullptr; |
| |
| existingHost->SetLease(aHost.GetLease()); |
| existingHost->SetKeyLease(aHost.GetKeyLease()); |
| |
| // Clear all resources associated to this host and its services. |
| existingHost->ClearResources(); |
| otLogInfoSrp("[server] remove host '%s' (but retain its name)", existingHost->GetFullName()); |
| while ((service = existingHost->GetNextService(service)) != nullptr) |
| { |
| service->DeleteResourcesButRetainName(); |
| } |
| } |
| |
| aHost.Free(); |
| } |
| else if (existingHost != nullptr) |
| { |
| const Service *service = nullptr; |
| |
| // Merge current updates into existing host. |
| |
| otLogInfoSrp("[server] update host %s", existingHost->GetFullName()); |
| |
| existingHost->CopyResourcesFrom(aHost); |
| while ((service = aHost.GetNextService(service)) != nullptr) |
| { |
| Service *existingService = existingHost->FindService(service->mFullName); |
| |
| if (service->mIsDeleted) |
| { |
| if (existingService != nullptr) |
| { |
| existingService->DeleteResourcesButRetainName(); |
| } |
| } |
| else |
| { |
| Service *newService = existingHost->AddService(service->mFullName); |
| |
| VerifyOrExit(newService != nullptr, aError = OT_ERROR_NO_BUFS); |
| SuccessOrExit(aError = newService->CopyResourcesFrom(*service)); |
| otLogInfoSrp("[server] %s service %s", (existingService != nullptr) ? "update existing" : "add new", |
| service->mFullName); |
| } |
| } |
| |
| aHost.Free(); |
| } |
| else |
| { |
| otLogInfoSrp("[server] add new host %s", aHost.GetFullName()); |
| AddHost(&aHost); |
| } |
| |
| // Re-schedule the lease timer. |
| HandleLeaseTimer(); |
| |
| exit: |
| if (aError == OT_ERROR_NONE && !(grantedLease == hostLease && grantedKeyLease == hostKeyLease)) |
| { |
| SendResponse(aDnsHeader, grantedLease, grantedKeyLease, aMessageInfo); |
| } |
| else |
| { |
| SendResponse(aDnsHeader, ErrorToDnsResponseCode(aError), aMessageInfo); |
| } |
| } |
| |
| void Server::Start(void) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(!IsRunning()); |
| |
| SuccessOrExit(error = mSocket.Open(HandleUdpReceive, this)); |
| SuccessOrExit(error = mSocket.Bind(0)); |
| |
| SuccessOrExit(error = PublishServerData()); |
| |
| otLogInfoSrp("[server] start listening on port %hu", mSocket.GetSockName().mPort); |
| |
| exit: |
| if (error != OT_ERROR_NONE) |
| { |
| otLogCritSrp("[server] failed to start: %s", otThreadErrorToString(error)); |
| // Cleanup any resources we may have allocated. |
| Stop(); |
| } |
| } |
| |
| void Server::Stop(void) |
| { |
| VerifyOrExit(IsRunning()); |
| |
| UnpublishServerData(); |
| |
| while (!mHosts.IsEmpty()) |
| { |
| mHosts.Pop()->Free(); |
| } |
| |
| while (!mOutstandingUpdates.IsEmpty()) |
| { |
| mOutstandingUpdates.Pop()->Free(); |
| } |
| |
| mLeaseTimer.Stop(); |
| mOutstandingUpdatesTimer.Stop(); |
| |
| otLogInfoSrp("[server] stop listening on %hu", mSocket.GetSockName().mPort); |
| IgnoreError(mSocket.Close()); |
| |
| exit: |
| return; |
| } |
| |
| void Server::HandleNotifierEvents(Events aEvents) |
| { |
| VerifyOrExit(mEnabled); |
| VerifyOrExit(aEvents.Contains(kEventThreadRoleChanged)); |
| |
| if (Get<Mle::MleRouter>().IsAttached()) |
| { |
| Start(); |
| } |
| else |
| { |
| Stop(); |
| } |
| |
| exit: |
| return; |
| } |
| |
| otError Server::PublishServerData(void) |
| { |
| NetworkData::Service::SrpServer::ServerData serverData; |
| |
| OT_ASSERT(mSocket.IsBound()); |
| |
| serverData.SetPort(mSocket.GetSockName().GetPort()); |
| |
| return Get<NetworkData::Service::Manager>().Add<NetworkData::Service::SrpServer>(serverData); |
| } |
| |
| void Server::UnpublishServerData(void) |
| { |
| otError error = Get<NetworkData::Service::Manager>().Remove<NetworkData::Service::SrpServer>(); |
| |
| if (error != OT_ERROR_NONE) |
| { |
| otLogWarnSrp("[server] failed to unpublish SRP service: %s", otThreadErrorToString(error)); |
| } |
| } |
| |
| const Server::UpdateMetadata *Server::FindOutstandingUpdate(const Ip6::MessageInfo &aMessageInfo, |
| uint16_t aDnsMessageId) |
| { |
| const UpdateMetadata *ret = nullptr; |
| |
| for (const UpdateMetadata *update = mOutstandingUpdates.GetHead(); update != nullptr; update = update->GetNext()) |
| { |
| if (aDnsMessageId == update->GetDnsHeader().GetMessageId() && |
| aMessageInfo.GetPeerAddr() == update->GetMessageInfo().GetPeerAddr() && |
| aMessageInfo.GetPeerPort() == update->GetMessageInfo().GetPeerPort()) |
| { |
| ExitNow(ret = update); |
| } |
| } |
| |
| exit: |
| return ret; |
| } |
| |
| void Server::HandleDnsUpdate(Message & aMessage, |
| const Ip6::MessageInfo & aMessageInfo, |
| const Dns::UpdateHeader &aDnsHeader, |
| uint16_t aOffset) |
| { |
| otError error = OT_ERROR_NONE; |
| Dns::Zone zone; |
| Host * host = nullptr; |
| |
| otLogInfoSrp("[server] receive DNS update from %s", aMessageInfo.GetPeerAddr().ToString().AsCString()); |
| |
| SuccessOrExit(error = ProcessZoneSection(aMessage, aDnsHeader, aOffset, zone)); |
| |
| if (FindOutstandingUpdate(aMessageInfo, aDnsHeader.GetMessageId()) != nullptr) |
| { |
| otLogInfoSrp("[server] drop duplicated SRP update request: messageId=%hu", aDnsHeader.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 = OT_ERROR_NONE); |
| } |
| |
| // Per 2.3.2 of SRP draft 6, no prerequisites should be included in a SRP update. |
| VerifyOrExit(aDnsHeader.GetPrerequisiteRecordCount() == 0, error = OT_ERROR_FAILED); |
| |
| host = Host::New(); |
| VerifyOrExit(host != nullptr, error = OT_ERROR_NO_BUFS); |
| SuccessOrExit(error = ProcessUpdateSection(*host, aMessage, aDnsHeader, zone, aOffset)); |
| |
| // Parse lease time and validate signature. |
| SuccessOrExit(error = ProcessAdditionalSection(host, aMessage, aDnsHeader, aOffset)); |
| |
| HandleUpdate(aDnsHeader, host, aMessageInfo); |
| |
| exit: |
| if (error != OT_ERROR_NONE) |
| { |
| if (host != nullptr) |
| { |
| host->Free(); |
| } |
| SendResponse(aDnsHeader, ErrorToDnsResponseCode(error), aMessageInfo); |
| } |
| } |
| |
| otError Server::ProcessZoneSection(const Message & aMessage, |
| const Dns::UpdateHeader &aDnsHeader, |
| uint16_t & aOffset, |
| Dns::Zone & aZone) const |
| { |
| otError error = OT_ERROR_NONE; |
| char name[Dns::Name::kMaxLength + 1]; |
| Dns::Zone zone; |
| |
| VerifyOrExit(aDnsHeader.GetZoneRecordCount() == 1, error = OT_ERROR_PARSE); |
| |
| SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name))); |
| // TODO: return `Dns::kResponseNotAuth` for not authorized zone names. |
| VerifyOrExit(strcmp(name, GetDomain()) == 0, error = OT_ERROR_SECURITY); |
| SuccessOrExit(error = aMessage.Read(aOffset, zone)); |
| aOffset += sizeof(zone); |
| |
| VerifyOrExit(zone.GetType() == Dns::ResourceRecord::kTypeSoa, error = OT_ERROR_PARSE); |
| aZone = zone; |
| |
| exit: |
| return error; |
| } |
| |
| otError Server::ProcessUpdateSection(Host & aHost, |
| const Message & aMessage, |
| const Dns::UpdateHeader &aDnsHeader, |
| const Dns::Zone & aZone, |
| uint16_t & aOffset) const |
| { |
| otError error = OT_ERROR_NONE; |
| |
| // 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. |
| error = ProcessServiceDiscoveryInstructions(aHost, aMessage, aDnsHeader, aZone, aOffset); |
| SuccessOrExit(error); |
| |
| // 1. Enumerate over all RRs to build the Host Description Instruction. |
| error = ProcessHostDescriptionInstruction(aHost, aMessage, aDnsHeader, aZone, aOffset); |
| SuccessOrExit(error); |
| |
| // 2. Enumerate over all RRs to build the Service Description Insutructions. |
| error = ProcessServiceDescriptionInstructions(aHost, aMessage, aDnsHeader, aZone, aOffset); |
| SuccessOrExit(error); |
| |
| // 3. Verify that there are no name conflicts. |
| VerifyOrExit(!HasNameConflictsWith(aHost), error = OT_ERROR_DUPLICATED); |
| |
| exit: |
| return error; |
| } |
| |
| otError Server::ProcessHostDescriptionInstruction(Host & aHost, |
| const Message & aMessage, |
| const Dns::UpdateHeader &aDnsHeader, |
| const Dns::Zone & aZone, |
| uint16_t aOffset) const |
| { |
| otError error; |
| |
| OT_ASSERT(aHost.GetFullName() == nullptr); |
| |
| for (uint16_t i = 0; i < aDnsHeader.GetUpdateRecordCount(); ++i) |
| { |
| char name[Dns::Name::kMaxLength + 1]; |
| Dns::ResourceRecord record; |
| |
| SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name))); |
| // TODO: return `Dns::kResponseNotZone` for names not in the zone. |
| VerifyOrExit(Dns::Name::IsSubDomainOf(name, GetDomain()), error = OT_ERROR_SECURITY); |
| SuccessOrExit(error = aMessage.Read(aOffset, record)); |
| |
| if (record.GetClass() == Dns::ResourceRecord::kClassAny) |
| { |
| Service *service; |
| |
| // Delete All RRsets from a name. |
| VerifyOrExit(IsValidDeleteAllRecord(record), error = OT_ERROR_FAILED); |
| |
| service = aHost.FindService(name); |
| if (service == nullptr) |
| { |
| // A "Delete All RRsets from a name" RR can only apply to a Service or Host Description. |
| |
| if (aHost.GetFullName()) |
| { |
| VerifyOrExit(aHost.Matches(name), error = OT_ERROR_FAILED); |
| } |
| else |
| { |
| SuccessOrExit(error = aHost.SetFullName(name)); |
| } |
| aHost.ClearResources(); |
| } |
| |
| aOffset += record.GetSize(); |
| continue; |
| } |
| |
| if (record.GetType() == Dns::ResourceRecord::kTypeAaaa) |
| { |
| Dns::AaaaRecord aaaaRecord; |
| |
| VerifyOrExit(record.GetClass() == aZone.GetClass(), error = OT_ERROR_FAILED); |
| if (aHost.GetFullName() == nullptr) |
| { |
| SuccessOrExit(error = aHost.SetFullName(name)); |
| } |
| else |
| { |
| VerifyOrExit(aHost.Matches(name), error = OT_ERROR_FAILED); |
| } |
| |
| SuccessOrExit(error = aMessage.Read(aOffset, aaaaRecord)); |
| VerifyOrExit(aaaaRecord.IsValid(), error = OT_ERROR_PARSE); |
| |
| // Tolerate OT_ERROR_DROP for AAAA Resources. |
| VerifyOrExit(aHost.AddIp6Address(aaaaRecord.GetAddress()) != OT_ERROR_NO_BUFS, error = OT_ERROR_NO_BUFS); |
| |
| aOffset += aaaaRecord.GetSize(); |
| } |
| else if (record.GetType() == Dns::ResourceRecord::kTypeKey) |
| { |
| // We currently support only ECDSA P-256. |
| Dns::Ecdsa256KeyRecord key; |
| |
| VerifyOrExit(record.GetClass() == aZone.GetClass(), error = OT_ERROR_FAILED); |
| VerifyOrExit(aHost.GetKey() == nullptr, error = OT_ERROR_FAILED); |
| |
| SuccessOrExit(error = aMessage.Read(aOffset, key)); |
| VerifyOrExit(key.IsValid(), error = OT_ERROR_PARSE); |
| |
| aHost.SetKey(key); |
| |
| aOffset += record.GetSize(); |
| } |
| else |
| { |
| aOffset += record.GetSize(); |
| } |
| } |
| |
| // Verify that we have a complete Host Description Instruction. |
| |
| VerifyOrExit(aHost.GetFullName() != nullptr, error = OT_ERROR_FAILED); |
| VerifyOrExit(aHost.GetKey() != nullptr, error = OT_ERROR_FAILED); |
| { |
| uint8_t hostAddressesNum; |
| |
| aHost.GetAddresses(hostAddressesNum); |
| |
| // There MUST be at least one valid address if we have nonzero lease. |
| VerifyOrExit(aHost.GetLease() > 0 || hostAddressesNum > 0, error = OT_ERROR_FAILED); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| otError Server::ProcessServiceDiscoveryInstructions(Host & aHost, |
| const Message & aMessage, |
| const Dns::UpdateHeader &aDnsHeader, |
| const Dns::Zone & aZone, |
| uint16_t aOffset) const |
| { |
| otError error = OT_ERROR_NONE; |
| |
| for (uint16_t i = 0; i < aDnsHeader.GetUpdateRecordCount(); ++i) |
| { |
| char name[Dns::Name::kMaxLength + 1]; |
| Dns::ResourceRecord record; |
| char serviceName[Dns::Name::kMaxLength + 1]; |
| Service * service; |
| |
| SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name))); |
| VerifyOrExit(Dns::Name::IsSubDomainOf(name, GetDomain()), error = OT_ERROR_SECURITY); |
| SuccessOrExit(error = aMessage.Read(aOffset, record)); |
| |
| aOffset += sizeof(record); |
| |
| if (record.GetType() == Dns::ResourceRecord::kTypePtr) |
| { |
| SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, serviceName, sizeof(serviceName))); |
| VerifyOrExit(Dns::Name::IsSubDomainOf(name, GetDomain()), error = OT_ERROR_SECURITY); |
| } |
| else |
| { |
| aOffset += record.GetLength(); |
| continue; |
| } |
| |
| VerifyOrExit(record.GetClass() == Dns::ResourceRecord::kClassNone || record.GetClass() == aZone.GetClass(), |
| error = OT_ERROR_FAILED); |
| |
| // TODO: check if the RR name and the full service name matches. |
| |
| service = aHost.FindService(serviceName); |
| VerifyOrExit(service == nullptr, error = OT_ERROR_FAILED); |
| service = aHost.AddService(serviceName); |
| VerifyOrExit(service != nullptr, error = OT_ERROR_NO_BUFS); |
| |
| // This RR is a "Delete an RR from an RRset" update when the CLASS is NONE. |
| service->mIsDeleted = (record.GetClass() == Dns::ResourceRecord::kClassNone); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| otError Server::ProcessServiceDescriptionInstructions(Host & aHost, |
| const Message & aMessage, |
| const Dns::UpdateHeader &aDnsHeader, |
| const Dns::Zone & aZone, |
| uint16_t & aOffset) const |
| { |
| Service *service; |
| otError error = OT_ERROR_NONE; |
| |
| for (uint16_t i = 0; i < aDnsHeader.GetUpdateRecordCount(); ++i) |
| { |
| char name[Dns::Name::kMaxLength + 1]; |
| Dns::ResourceRecord record; |
| |
| SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name))); |
| VerifyOrExit(Dns::Name::IsSubDomainOf(name, GetDomain()), error = OT_ERROR_SECURITY); |
| SuccessOrExit(error = aMessage.Read(aOffset, record)); |
| |
| if (record.GetClass() == Dns::ResourceRecord::kClassAny) |
| { |
| // Delete All RRsets from a name. |
| VerifyOrExit(IsValidDeleteAllRecord(record), error = OT_ERROR_FAILED); |
| service = aHost.FindService(name); |
| if (service != nullptr) |
| { |
| service->ClearResources(); |
| } |
| |
| aOffset += record.GetSize(); |
| continue; |
| } |
| |
| if (record.GetType() == Dns::ResourceRecord::kTypeSrv) |
| { |
| Dns::SrvRecord srvRecord; |
| char hostName[Dns::Name::kMaxLength + 1]; |
| uint16_t hostNameLength = sizeof(hostName); |
| |
| VerifyOrExit(record.GetClass() == aZone.GetClass(), error = OT_ERROR_FAILED); |
| SuccessOrExit(error = aMessage.Read(aOffset, srvRecord)); |
| aOffset += sizeof(srvRecord); |
| |
| SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, hostName, hostNameLength)); |
| VerifyOrExit(Dns::Name::IsSubDomainOf(name, GetDomain()), error = OT_ERROR_SECURITY); |
| VerifyOrExit(aHost.Matches(hostName), error = OT_ERROR_FAILED); |
| |
| service = aHost.FindService(name); |
| VerifyOrExit(service != nullptr && !service->mIsDeleted, error = OT_ERROR_FAILED); |
| |
| // Make sure that this is the first SRV RR for this service. |
| VerifyOrExit(service->mPort == 0, error = OT_ERROR_FAILED); |
| service->mPriority = srvRecord.GetPriority(); |
| service->mWeight = srvRecord.GetWeight(); |
| service->mPort = srvRecord.GetPort(); |
| } |
| else if (record.GetType() == Dns::ResourceRecord::kTypeTxt) |
| { |
| VerifyOrExit(record.GetClass() == aZone.GetClass(), error = OT_ERROR_FAILED); |
| |
| service = aHost.FindService(name); |
| VerifyOrExit(service != nullptr && !service->mIsDeleted, error = OT_ERROR_FAILED); |
| |
| aOffset += sizeof(record); |
| SuccessOrExit(error = service->SetTxtDataFromMessage(aMessage, aOffset, record.GetLength())); |
| aOffset += record.GetLength(); |
| } |
| else |
| { |
| aOffset += record.GetSize(); |
| } |
| } |
| |
| service = nullptr; |
| while ((service = aHost.GetNextService(service)) != nullptr) |
| { |
| VerifyOrExit(service->mIsDeleted || (service->mTxtData != nullptr && service->mPort != 0), |
| error = OT_ERROR_FAILED); |
| } |
| |
| exit: |
| 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; |
| } |
| |
| otError Server::ProcessAdditionalSection(Host * aHost, |
| const Message & aMessage, |
| const Dns::UpdateHeader &aDnsHeader, |
| uint16_t & aOffset) const |
| { |
| otError error = OT_ERROR_NONE; |
| Dns::OptRecord optRecord; |
| Dns::LeaseOption leaseOption; |
| Dns::SigRecord sigRecord; |
| char name[2]; // The root domain name (".") is expected. |
| uint16_t sigOffset; |
| uint16_t sigRdataOffset; |
| char signerName[Dns::Name::kMaxLength + 1]; |
| uint16_t signatureLength; |
| |
| VerifyOrExit(aDnsHeader.GetAdditionalRecordCount() == 2, error = OT_ERROR_FAILED); |
| |
| // EDNS(0) Update Lease Option. |
| |
| SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name))); |
| SuccessOrExit(error = aMessage.Read(aOffset, optRecord)); |
| SuccessOrExit(error = aMessage.Read(aOffset + sizeof(optRecord), leaseOption)); |
| VerifyOrExit(leaseOption.IsValid(), error = OT_ERROR_FAILED); |
| VerifyOrExit(optRecord.GetSize() == sizeof(optRecord) + sizeof(leaseOption), error = OT_ERROR_PARSE); |
| |
| aOffset += optRecord.GetSize(); |
| |
| aHost->SetLease(leaseOption.GetLeaseInterval()); |
| aHost->SetKeyLease(leaseOption.GetKeyLeaseInterval()); |
| |
| // SIG(0). |
| |
| sigOffset = aOffset; |
| SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name))); |
| SuccessOrExit(error = aMessage.Read(aOffset, sigRecord)); |
| VerifyOrExit(sigRecord.IsValid(), error = OT_ERROR_PARSE); |
| |
| sigRdataOffset = aOffset + sizeof(Dns::ResourceRecord); |
| aOffset += 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, aOffset, signerName, sizeof(signerName))); |
| |
| signatureLength = sigRecord.GetLength() - (aOffset - sigRdataOffset); |
| aOffset += signatureLength; |
| |
| // Verify the signature. Currently supports only ECDSA. |
| |
| VerifyOrExit(sigRecord.GetAlgorithm() == Dns::KeyRecord::kAlgorithmEcdsaP256Sha256, error = OT_ERROR_FAILED); |
| VerifyOrExit(sigRecord.GetTypeCovered() == 0, error = OT_ERROR_FAILED); |
| VerifyOrExit(signatureLength == Crypto::Ecdsa::P256::Signature::kSize, error = OT_ERROR_PARSE); |
| |
| SuccessOrExit(error = VerifySignature(*aHost->GetKey(), aMessage, aDnsHeader, sigOffset, sigRdataOffset, |
| sigRecord.GetLength(), signerName)); |
| |
| exit: |
| return error; |
| } |
| |
| otError Server::VerifySignature(const Dns::Ecdsa256KeyRecord &aKey, |
| const Message & aMessage, |
| Dns::UpdateHeader aDnsHeader, |
| uint16_t aSigOffset, |
| uint16_t aSigRdataOffset, |
| uint16_t aSigRdataLength, |
| const char * aSignerName) const |
| { |
| otError 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 = OT_ERROR_INVALID_ARGS); |
| |
| 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<Ip6::Udp>().NewMessage(0); |
| VerifyOrExit(signerNameMessage != nullptr, error = OT_ERROR_NO_BUFS); |
| 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.GetKey().Verify(hash, signature); |
| |
| exit: |
| FreeMessage(signerNameMessage); |
| return error; |
| } |
| |
| void Server::HandleUpdate(const Dns::UpdateHeader &aDnsHeader, Host *aHost, const Ip6::MessageInfo &aMessageInfo) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| if (aHost->GetLease() == 0) |
| { |
| Host *existingHost = mHosts.FindMatching(aHost->GetFullName()); |
| |
| aHost->ClearResources(); |
| |
| // The client may not include all services it has registered and we should append |
| // those services for current SRP update. |
| if (existingHost != nullptr) |
| { |
| Service *existingService = nullptr; |
| |
| while ((existingService = existingHost->GetNextService(existingService)) != nullptr) |
| { |
| if (!existingService->mIsDeleted) |
| { |
| Service *service = aHost->AddService(existingService->mFullName); |
| VerifyOrExit(service != nullptr, error = OT_ERROR_NO_BUFS); |
| service->mIsDeleted = true; |
| } |
| } |
| } |
| } |
| |
| exit: |
| if (error != OT_ERROR_NONE) |
| { |
| HandleSrpUpdateResult(error, aDnsHeader, *aHost, aMessageInfo); |
| } |
| else if (mAdvertisingHandler != nullptr) |
| { |
| UpdateMetadata *update = UpdateMetadata::New(GetInstance(), aDnsHeader, aHost, aMessageInfo); |
| |
| IgnoreError(mOutstandingUpdates.Add(*update)); |
| mOutstandingUpdatesTimer.StartAt(mOutstandingUpdates.GetTail()->GetExpireTime(), 0); |
| |
| mAdvertisingHandler(aHost, kDefaultEventsHandlerTimeout, mAdvertisingHandlerContext); |
| } |
| else |
| { |
| HandleSrpUpdateResult(OT_ERROR_NONE, aDnsHeader, *aHost, aMessageInfo); |
| } |
| } |
| |
| void Server::SendResponse(const Dns::UpdateHeader & aHeader, |
| Dns::UpdateHeader::Response aResponseCode, |
| const Ip6::MessageInfo & aMessageInfo) |
| { |
| otError error; |
| Message * response = nullptr; |
| Dns::UpdateHeader header; |
| |
| response = mSocket.NewMessage(0); |
| VerifyOrExit(response != nullptr, error = OT_ERROR_NO_BUFS); |
| |
| header.SetMessageId(aHeader.GetMessageId()); |
| header.SetType(Dns::UpdateHeader::kTypeResponse); |
| header.SetQueryType(aHeader.GetQueryType()); |
| header.SetResponseCode(aResponseCode); |
| SuccessOrExit(error = response->Append(header)); |
| |
| SuccessOrExit(error = mSocket.SendTo(*response, aMessageInfo)); |
| |
| if (aResponseCode != Dns::UpdateHeader::kResponseSuccess) |
| { |
| otLogInfoSrp("[server] send fail response: %d", aResponseCode); |
| } |
| else |
| { |
| otLogInfoSrp("[server] send success response"); |
| } |
| |
| exit: |
| if (error != OT_ERROR_NONE) |
| { |
| otLogWarnSrp("[server] failed to send response: %s", otThreadErrorToString(error)); |
| FreeMessage(response); |
| } |
| } |
| |
| void Server::SendResponse(const Dns::UpdateHeader &aHeader, |
| uint32_t aLease, |
| uint32_t aKeyLease, |
| const Ip6::MessageInfo & aMessageInfo) |
| { |
| otError error; |
| Message * response = nullptr; |
| Dns::UpdateHeader header; |
| Dns::OptRecord optRecord; |
| Dns::LeaseOption leaseOption; |
| |
| response = mSocket.NewMessage(0); |
| VerifyOrExit(response != nullptr, error = OT_ERROR_NO_BUFS); |
| |
| 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(); |
| optRecord.SetLength(sizeof(Dns::LeaseOption)); |
| SuccessOrExit(error = response->Append(optRecord)); |
| |
| leaseOption.Init(); |
| leaseOption.SetLeaseInterval(aLease); |
| leaseOption.SetKeyLeaseInterval(aKeyLease); |
| SuccessOrExit(error = response->Append(leaseOption)); |
| |
| SuccessOrExit(error = mSocket.SendTo(*response, aMessageInfo)); |
| |
| otLogInfoSrp("[server] send response with granted lease: %u and key lease: %u", aLease, aKeyLease); |
| |
| exit: |
| if (error != OT_ERROR_NONE) |
| { |
| otLogWarnSrp("[server] failed to send response: %s", otThreadErrorToString(error)); |
| FreeMessage(response); |
| } |
| } |
| |
| void Server::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) |
| { |
| static_cast<Server *>(aContext)->HandleUdpReceive(*static_cast<Message *>(aMessage), |
| *static_cast<const Ip6::MessageInfo *>(aMessageInfo)); |
| } |
| |
| void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) |
| { |
| otError error; |
| Dns::UpdateHeader dnsHeader; |
| uint16_t offset = aMessage.GetOffset(); |
| |
| SuccessOrExit(error = aMessage.Read(offset, dnsHeader)); |
| offset += sizeof(dnsHeader); |
| |
| // Handles only queries. |
| VerifyOrExit(dnsHeader.GetType() == Dns::UpdateHeader::Type::kTypeQuery, error = OT_ERROR_DROP); |
| |
| switch (dnsHeader.GetQueryType()) |
| { |
| case Dns::UpdateHeader::kQueryTypeUpdate: |
| HandleDnsUpdate(aMessage, aMessageInfo, dnsHeader, offset); |
| break; |
| default: |
| error = OT_ERROR_DROP; |
| break; |
| } |
| |
| exit: |
| if (error != OT_ERROR_NONE) |
| { |
| otLogInfoSrp("[server] failed to handle DNS message: %s", otThreadErrorToString(error)); |
| } |
| } |
| |
| void Server::HandleLeaseTimer(Timer &aTimer) |
| { |
| aTimer.Get<Server>().HandleLeaseTimer(); |
| } |
| |
| void Server::HandleLeaseTimer(void) |
| { |
| TimeMilli now = TimerMilli::GetNow(); |
| TimeMilli earliestExpireTime = now.GetDistantFuture(); |
| Host * host = mHosts.GetHead(); |
| |
| while (host != nullptr) |
| { |
| Host *nextHost = host->GetNext(); |
| |
| if (host->GetKeyExpireTime() <= now) |
| { |
| otLogInfoSrp("[server] KEY LEASE of host %s expired", host->GetFullName()); |
| |
| // Removes the whole host and all services if the KEY RR expired. |
| RemoveAndFreeHost(host); |
| } |
| else if (host->IsDeleted()) |
| { |
| // The host has been deleted, but the hostname & service instance names retain. |
| |
| Service *service; |
| |
| earliestExpireTime = OT_MIN(earliestExpireTime, host->GetKeyExpireTime()); |
| |
| // Check if any service instance name expired. |
| service = host->GetNextService(nullptr); |
| while (service != nullptr) |
| { |
| OT_ASSERT(service->mIsDeleted); |
| |
| Service *nextService = service->GetNext(); |
| |
| if (service->GetKeyExpireTime() <= now) |
| { |
| otLogInfoSrp("[server] KEY LEASE of service %s expired", service->mFullName); |
| host->RemoveAndFreeService(service); |
| } |
| else |
| { |
| earliestExpireTime = OT_MIN(earliestExpireTime, service->GetKeyExpireTime()); |
| } |
| |
| service = nextService; |
| } |
| } |
| else if (host->GetExpireTime() <= now) |
| { |
| Service *service = nullptr; |
| |
| otLogInfoSrp("[server] LEASE of host %s expired", host->GetFullName()); |
| |
| // If the host expired, delete all resources of this host and its services. |
| host->DeleteResourcesButRetainName(); |
| while ((service = host->GetNextService(service)) != nullptr) |
| { |
| service->DeleteResourcesButRetainName(); |
| } |
| |
| earliestExpireTime = OT_MIN(earliestExpireTime, host->GetKeyExpireTime()); |
| } |
| else |
| { |
| // The host doesn't expire, check if any service expired or is explicitly removed. |
| |
| OT_ASSERT(!host->IsDeleted()); |
| |
| Service *service = host->GetNextService(nullptr); |
| |
| earliestExpireTime = OT_MIN(earliestExpireTime, host->GetExpireTime()); |
| |
| while (service != nullptr) |
| { |
| Service *nextService = service->GetNext(); |
| |
| if (service->mIsDeleted) |
| { |
| // The service has been deleted but the name retains. |
| earliestExpireTime = OT_MIN(earliestExpireTime, service->GetKeyExpireTime()); |
| } |
| else if (service->GetExpireTime() <= now) |
| { |
| otLogInfoSrp("[server] LEASE of service %s expired", service->mFullName); |
| |
| // The service gets expired, delete it. |
| service->DeleteResourcesButRetainName(); |
| earliestExpireTime = OT_MIN(earliestExpireTime, service->GetKeyExpireTime()); |
| } |
| else |
| { |
| earliestExpireTime = OT_MIN(earliestExpireTime, service->GetExpireTime()); |
| } |
| |
| service = nextService; |
| } |
| } |
| |
| host = nextHost; |
| } |
| |
| if (earliestExpireTime != now.GetDistantFuture()) |
| { |
| if (!mLeaseTimer.IsRunning() || earliestExpireTime <= mLeaseTimer.GetFireTime()) |
| { |
| otLogInfoSrp("[server] lease timer is scheduled for %u seconds", (earliestExpireTime - now) / 1000); |
| mLeaseTimer.StartAt(earliestExpireTime, 0); |
| } |
| } |
| else |
| { |
| otLogInfoSrp("[server] lease timer is stopped"); |
| mLeaseTimer.Stop(); |
| } |
| } |
| |
| void Server::HandleOutstandingUpdatesTimer(Timer &aTimer) |
| { |
| aTimer.Get<Server>().HandleOutstandingUpdatesTimer(); |
| } |
| |
| void Server::HandleOutstandingUpdatesTimer(void) |
| { |
| otLogInfoSrp("[server] outstanding service update timeout"); |
| while (!mOutstandingUpdates.IsEmpty() && mOutstandingUpdates.GetTail()->GetExpireTime() <= TimerMilli::GetNow()) |
| { |
| HandleAdvertisingResult(mOutstandingUpdates.GetTail(), OT_ERROR_RESPONSE_TIMEOUT); |
| } |
| } |
| |
| Server::Service *Server::Service::New(const char *aFullName) |
| { |
| void * buf; |
| Service *service = nullptr; |
| |
| buf = Instance::HeapCAlloc(1, sizeof(Service)); |
| VerifyOrExit(buf != nullptr); |
| |
| service = new (buf) Service(); |
| if (service->SetFullName(aFullName) != OT_ERROR_NONE) |
| { |
| service->Free(); |
| service = nullptr; |
| } |
| |
| exit: |
| return service; |
| } |
| |
| void Server::Service::Free(void) |
| { |
| Instance::HeapFree(mFullName); |
| Instance::HeapFree(mTxtData); |
| Instance::HeapFree(this); |
| } |
| |
| Server::Service::Service(void) |
| : mFullName(nullptr) |
| , mPriority(0) |
| , mWeight(0) |
| , mPort(0) |
| , mTxtLength(0) |
| , mTxtData(nullptr) |
| , mHost(nullptr) |
| , mNext(nullptr) |
| , mTimeLastUpdate(TimerMilli::GetNow()) |
| { |
| } |
| |
| otError Server::Service::SetFullName(const char *aFullName) |
| { |
| OT_ASSERT(aFullName != nullptr); |
| |
| otError error = OT_ERROR_NONE; |
| char * nameCopy = static_cast<char *>(Instance::HeapCAlloc(1, strlen(aFullName) + 1)); |
| |
| VerifyOrExit(nameCopy != nullptr, error = OT_ERROR_NO_BUFS); |
| strcpy(nameCopy, aFullName); |
| |
| Instance::HeapFree(mFullName); |
| mFullName = nameCopy; |
| |
| exit: |
| return error; |
| } |
| |
| otError Server::Service::GetNextTxtEntry(Dns::TxtRecord::TxtIterator &aIterator, Dns::TxtEntry &aTxtEntry) const |
| { |
| return Dns::TxtRecord::GetNextTxtEntry(mTxtData, mTxtLength, aIterator, aTxtEntry); |
| } |
| |
| TimeMilli Server::Service::GetExpireTime(void) const |
| { |
| OT_ASSERT(!mIsDeleted); |
| OT_ASSERT(!GetHost().IsDeleted()); |
| |
| return mTimeLastUpdate + Time::SecToMsec(GetHost().GetLease()); |
| } |
| |
| TimeMilli Server::Service::GetKeyExpireTime(void) const |
| { |
| return mTimeLastUpdate + Time::SecToMsec(GetHost().GetKeyLease()); |
| } |
| |
| otError Server::Service::SetTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength) |
| { |
| otError error = OT_ERROR_NONE; |
| uint8_t *txtData; |
| |
| txtData = static_cast<uint8_t *>(Instance::HeapCAlloc(1, aTxtDataLength)); |
| VerifyOrExit(txtData != nullptr, error = OT_ERROR_NO_BUFS); |
| |
| memcpy(txtData, aTxtData, aTxtDataLength); |
| |
| Instance::HeapFree(mTxtData); |
| mTxtData = txtData; |
| mTxtLength = aTxtDataLength; |
| |
| // If a TXT RR is associated to this service, the service will retain. |
| mIsDeleted = false; |
| |
| exit: |
| return error; |
| } |
| |
| otError Server::Service::SetTxtDataFromMessage(const Message &aMessage, uint16_t aOffset, uint16_t aLength) |
| { |
| otError error = OT_ERROR_NONE; |
| uint8_t *txtData; |
| |
| txtData = static_cast<uint8_t *>(Instance::HeapCAlloc(1, aLength)); |
| VerifyOrExit(txtData != nullptr, error = OT_ERROR_NO_BUFS); |
| VerifyOrExit(aMessage.ReadBytes(aOffset, txtData, aLength) == aLength, error = OT_ERROR_PARSE); |
| VerifyOrExit(Dns::TxtRecord::VerifyTxtData(txtData, aLength), error = OT_ERROR_PARSE); |
| |
| Instance::HeapFree(mTxtData); |
| mTxtData = txtData; |
| mTxtLength = aLength; |
| |
| mIsDeleted = false; |
| |
| exit: |
| if (error != OT_ERROR_NONE) |
| { |
| Instance::HeapFree(txtData); |
| } |
| |
| return error; |
| } |
| |
| void Server::Service::ClearResources(void) |
| { |
| mPort = 0; |
| Instance::HeapFree(mTxtData); |
| mTxtData = nullptr; |
| mTxtLength = 0; |
| } |
| |
| void Server::Service::DeleteResourcesButRetainName(void) |
| { |
| mIsDeleted = true; |
| ClearResources(); |
| otLogInfoSrp("[server] remove service '%s' (but retain its name)", mFullName); |
| } |
| |
| otError Server::Service::CopyResourcesFrom(const Service &aService) |
| { |
| otError error; |
| |
| SuccessOrExit(error = SetTxtData(aService.mTxtData, aService.mTxtLength)); |
| mPriority = aService.mPriority; |
| mWeight = aService.mWeight; |
| mPort = aService.mPort; |
| |
| mIsDeleted = false; |
| mTimeLastUpdate = TimerMilli::GetNow(); |
| |
| exit: |
| return error; |
| } |
| |
| bool Server::Service::Matches(const char *aFullName) const |
| { |
| return (mFullName != nullptr) && (strcmp(mFullName, aFullName) == 0); |
| } |
| |
| Server::Host *Server::Host::New(void) |
| { |
| void *buf; |
| Host *host = nullptr; |
| |
| buf = Instance::HeapCAlloc(1, sizeof(Host)); |
| VerifyOrExit(buf != nullptr); |
| |
| host = new (buf) Host(); |
| |
| exit: |
| return host; |
| } |
| |
| void Server::Host::Free(void) |
| { |
| RemoveAndFreeAllServices(); |
| Instance::HeapFree(mFullName); |
| Instance::HeapFree(this); |
| } |
| |
| Server::Host::Host(void) |
| : mFullName(nullptr) |
| , mAddressesNum(0) |
| , mNext(nullptr) |
| , mLease(0) |
| , mKeyLease(0) |
| , mTimeLastUpdate(TimerMilli::GetNow()) |
| { |
| mKey.Clear(); |
| } |
| |
| otError Server::Host::SetFullName(const char *aFullName) |
| { |
| OT_ASSERT(aFullName != nullptr); |
| |
| otError error = OT_ERROR_NONE; |
| char * nameCopy = static_cast<char *>(Instance::HeapCAlloc(1, strlen(aFullName) + 1)); |
| |
| VerifyOrExit(nameCopy != nullptr, error = OT_ERROR_NO_BUFS); |
| strcpy(nameCopy, aFullName); |
| |
| if (mFullName != nullptr) |
| { |
| Instance::HeapFree(mFullName); |
| } |
| mFullName = nameCopy; |
| |
| exit: |
| return error; |
| } |
| |
| void Server::Host::SetKey(Dns::Ecdsa256KeyRecord &aKey) |
| { |
| OT_ASSERT(aKey.IsValid()); |
| |
| mKey = aKey; |
| } |
| |
| void Server::Host::SetLease(uint32_t aLease) |
| { |
| mLease = aLease; |
| } |
| |
| void Server::Host::SetKeyLease(uint32_t aKeyLease) |
| { |
| mKeyLease = aKeyLease; |
| } |
| |
| TimeMilli Server::Host::GetExpireTime(void) const |
| { |
| OT_ASSERT(!IsDeleted()); |
| |
| return mTimeLastUpdate + Time::SecToMsec(mLease); |
| } |
| |
| TimeMilli Server::Host::GetKeyExpireTime(void) const |
| { |
| return mTimeLastUpdate + Time::SecToMsec(mKeyLease); |
| } |
| |
| // Add a new service entry to the host, do nothing if there is already |
| // such services with the same name. |
| Server::Service *Server::Host::AddService(const char *aFullName) |
| { |
| Service *service = FindService(aFullName); |
| |
| VerifyOrExit(service == nullptr); |
| |
| service = Service::New(aFullName); |
| if (service != nullptr) |
| { |
| IgnoreError(mServices.Add(*service)); |
| service->mHost = this; |
| } |
| |
| exit: |
| return service; |
| } |
| |
| void Server::Host::RemoveAndFreeService(Service *aService) |
| { |
| if (aService != nullptr) |
| { |
| IgnoreError(mServices.Remove(*aService)); |
| aService->Free(); |
| } |
| } |
| |
| void Server::Host::RemoveAndFreeAllServices(void) |
| { |
| while (!mServices.IsEmpty()) |
| { |
| RemoveAndFreeService(mServices.GetHead()); |
| } |
| } |
| |
| void Server::Host::ClearResources(void) |
| { |
| mAddressesNum = 0; |
| } |
| |
| void Server::Host::DeleteResourcesButRetainName(void) |
| { |
| // Mark the host as deleted. |
| mLease = 0; |
| ClearResources(); |
| otLogInfoSrp("[server] remove host '%s' (but retain its name)", mFullName); |
| } |
| |
| void Server::Host::CopyResourcesFrom(const Host &aHost) |
| { |
| memcpy(mAddresses, aHost.mAddresses, aHost.mAddressesNum * sizeof(mAddresses[0])); |
| mAddressesNum = aHost.mAddressesNum; |
| mKey = aHost.mKey; |
| mLease = aHost.mLease; |
| mKeyLease = aHost.mKeyLease; |
| |
| mTimeLastUpdate = TimerMilli::GetNow(); |
| } |
| |
| Server::Service *Server::Host::FindService(const char *aFullName) |
| { |
| return mServices.FindMatching(aFullName); |
| } |
| |
| const Server::Service *Server::Host::FindService(const char *aFullName) const |
| { |
| return const_cast<Host *>(this)->FindService(aFullName); |
| } |
| |
| otError Server::Host::AddIp6Address(const Ip6::Address &aIp6Address) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| 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 = OT_ERROR_DROP); |
| } |
| |
| for (const Ip6::Address &addr : mAddresses) |
| { |
| if (aIp6Address == addr) |
| { |
| // Drop duplicate addresses. |
| ExitNow(error = OT_ERROR_DROP); |
| } |
| } |
| |
| if (mAddressesNum >= kMaxAddressesNum) |
| { |
| otLogWarnSrp("[server] too many addresses for host %s", GetFullName()); |
| ExitNow(error = OT_ERROR_NO_BUFS); |
| } |
| |
| mAddresses[mAddressesNum++] = aIp6Address; |
| |
| exit: |
| return error; |
| } |
| |
| bool Server::Host::Matches(const char *aName) const |
| { |
| return mFullName != nullptr && strcmp(mFullName, aName) == 0; |
| } |
| |
| Server::UpdateMetadata *Server::UpdateMetadata::New(Instance & aInstance, |
| const Dns::UpdateHeader &aHeader, |
| Host * aHost, |
| const Ip6::MessageInfo & aMessageInfo) |
| { |
| void * buf; |
| UpdateMetadata *update = nullptr; |
| |
| buf = aInstance.HeapCAlloc(1, sizeof(UpdateMetadata)); |
| VerifyOrExit(buf != nullptr); |
| |
| update = new (buf) UpdateMetadata(aInstance, aHeader, aHost, aMessageInfo); |
| |
| exit: |
| return update; |
| } |
| |
| void Server::UpdateMetadata::Free(void) |
| { |
| Instance::HeapFree(this); |
| } |
| |
| Server::UpdateMetadata::UpdateMetadata(Instance & aInstance, |
| const Dns::UpdateHeader &aHeader, |
| Host * aHost, |
| const Ip6::MessageInfo & aMessageInfo) |
| : InstanceLocator(aInstance) |
| , mExpireTime(TimerMilli::GetNow() + kDefaultEventsHandlerTimeout) |
| , mDnsHeader(aHeader) |
| , mHost(aHost) |
| , mMessageInfo(aMessageInfo) |
| , mNext(nullptr) |
| { |
| } |
| |
| } // namespace Srp |
| } // namespace ot |
| |
| #endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE |