diff --git a/src/core/net/dns_headers.cpp b/src/core/net/dns_headers.cpp index f469ff542..1a104465b 100644 --- a/src/core/net/dns_headers.cpp +++ b/src/core/net/dns_headers.cpp @@ -539,6 +539,37 @@ bool Name::LabelIterator::CompareLabel(const LabelIterator &aOtherIterator) cons mLabelLength); } +bool Name::IsSubDomainOf(const char *aName, const char *aDomain) +{ + bool match = false; + uint16_t nameLength = StringLength(aName, kMaxLength); + uint16_t domainLength = StringLength(aDomain, kMaxLength); + + if (nameLength > 0 && aName[nameLength - 1] == kLabelSeperatorChar) + { + --nameLength; + } + + if (domainLength > 0 && aDomain[domainLength - 1] == kLabelSeperatorChar) + { + --domainLength; + } + + VerifyOrExit(nameLength >= domainLength); + aName += nameLength - domainLength; + + if (nameLength > domainLength) + { + VerifyOrExit(aName[-1] == kLabelSeperatorChar); + } + VerifyOrExit(memcmp(aName, aDomain, domainLength) == 0); + + match = true; + +exit: + return match; +} + otError ResourceRecord::ParseRecords(const Message &aMessage, uint16_t &aOffset, uint16_t aNumRecords) { otError error = OT_ERROR_NONE; diff --git a/src/core/net/dns_headers.hpp b/src/core/net/dns_headers.hpp index 43d8832c5..b1447eab1 100644 --- a/src/core/net/dns_headers.hpp +++ b/src/core/net/dns_headers.hpp @@ -890,6 +890,19 @@ public: */ static otError CompareName(const Message &aMessage, uint16_t &aOffset, const Name &aName); + /** + * This static method tests if a DNS name is a sub-domain of a given domain. + * + * Both @p aName and @p aDomain can end without dot ('.'). + * + * @param[in] aName The dot-separated name. + * @param[in] aDomain The dot-separated domain. + * + * @returns TRUE if the name is a sub-domain of @p aDomain, FALSE if is not. + * + */ + static bool IsSubDomainOf(const char *aName, const char *aDomain); + private: enum : char { diff --git a/src/core/net/srp_server.cpp b/src/core/net/srp_server.cpp index b1d231aa2..5bfe944de 100644 --- a/src/core/net/srp_server.cpp +++ b/src/core/net/srp_server.cpp @@ -223,11 +223,11 @@ void Server::RemoveAndFreeHost(Host *aHost) aHost->Free(); } -Server::Service *Server::FindService(const char *aFullName) +const Server::Service *Server::FindService(const char *aFullName) const { - Service *service = nullptr; + const Service *service = nullptr; - for (Host *host = mHosts.GetHead(); host != nullptr; host = host->GetNext()) + for (const Host *host = mHosts.GetHead(); host != nullptr; host = host->GetNext()) { service = host->FindService(aFullName); if (service != nullptr) @@ -239,11 +239,11 @@ Server::Service *Server::FindService(const char *aFullName) return service; } -bool Server::HasNameConflictsWith(Host &aHost) +bool Server::HasNameConflictsWith(Host &aHost) const { bool hasConflicts = false; const Service *service = nullptr; - Host * existingHost = mHosts.FindMatching(aHost.GetFullName()); + const Host * existingHost = mHosts.FindMatching(aHost.GetFullName()); if (existingHost != nullptr && *aHost.GetKey() != *existingHost->GetKey()) { @@ -253,7 +253,7 @@ bool Server::HasNameConflictsWith(Host &aHost) // Check not only services of this host but all hosts. while ((service = aHost.GetNextService(service)) != nullptr) { - Service *existingService = FindService(service->mFullName); + const Service *existingService = FindService(service->mFullName); if (existingService != nullptr && *service->GetHost().GetKey() != *existingService->GetHost().GetKey()) { ExitNow(hasConflicts = true); @@ -569,14 +569,17 @@ exit: otError Server::ProcessZoneSection(const Message & aMessage, const Dns::UpdateHeader &aDnsHeader, uint16_t & aOffset, - Dns::Zone & aZone) + 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::ParseName(aMessage, aOffset)); + 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); @@ -591,7 +594,7 @@ otError Server::ProcessUpdateSection(Host & aHost, const Message & aMessage, const Dns::UpdateHeader &aDnsHeader, const Dns::Zone & aZone, - uint16_t & aOffset) + uint16_t & aOffset) const { otError error = OT_ERROR_NONE; @@ -623,7 +626,7 @@ otError Server::ProcessHostDescriptionInstruction(Host & aHost const Message & aMessage, const Dns::UpdateHeader &aDnsHeader, const Dns::Zone & aZone, - uint16_t aOffset) + uint16_t aOffset) const { otError error; @@ -635,6 +638,8 @@ otError Server::ProcessHostDescriptionInstruction(Host & aHost 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) @@ -728,7 +733,7 @@ otError Server::ProcessServiceDiscoveryInstructions(Host & aHo const Message & aMessage, const Dns::UpdateHeader &aDnsHeader, const Dns::Zone & aZone, - uint16_t aOffset) + uint16_t aOffset) const { otError error = OT_ERROR_NONE; @@ -740,6 +745,7 @@ otError Server::ProcessServiceDiscoveryInstructions(Host & aHo 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); @@ -747,6 +753,7 @@ otError Server::ProcessServiceDiscoveryInstructions(Host & aHo 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 { @@ -776,7 +783,7 @@ otError Server::ProcessServiceDescriptionInstructions(Host & a const Message & aMessage, const Dns::UpdateHeader &aDnsHeader, const Dns::Zone & aZone, - uint16_t & aOffset) + uint16_t & aOffset) const { Service *service; otError error = OT_ERROR_NONE; @@ -787,6 +794,7 @@ otError Server::ProcessServiceDescriptionInstructions(Host & a 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) @@ -814,6 +822,7 @@ otError Server::ProcessServiceDescriptionInstructions(Host & a 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); @@ -862,7 +871,7 @@ bool Server::IsValidDeleteAllRecord(const Dns::ResourceRecord &aRecord) otError Server::ProcessAdditionalSection(Host * aHost, const Message & aMessage, const Dns::UpdateHeader &aDnsHeader, - uint16_t & aOffset) + uint16_t & aOffset) const { otError error = OT_ERROR_NONE; Dns::OptRecord optRecord; @@ -927,7 +936,7 @@ otError Server::VerifySignature(const Dns::Ecdsa256KeyRecord &aKey, uint16_t aSigOffset, uint16_t aSigRdataOffset, uint16_t aSigRdataLength, - const char * aSignerName) + const char * aSignerName) const { otError error; uint16_t offset = aMessage.GetOffset(); @@ -1571,6 +1580,11 @@ 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(this)->FindService(aFullName); +} + otError Server::Host::AddIp6Address(const Ip6::Address &aIp6Address) { otError error = OT_ERROR_NONE; diff --git a/src/core/net/srp_server.hpp b/src/core/net/srp_server.hpp index cc76cb53b..be5aa105a 100644 --- a/src/core/net/srp_server.hpp +++ b/src/core/net/srp_server.hpp @@ -361,7 +361,8 @@ public: void DeleteResourcesButRetainName(void); void CopyResourcesFrom(const Host &aHost); Service *FindService(const char *aFullName); - otError AddIp6Address(const Ip6::Address &aIp6Address); + const Service *FindService(const char *aFullName) const; + otError AddIp6Address(const Ip6::Address &aIp6Address); char * mFullName; Ip6::Address mAddresses[kMaxAddressesNum]; @@ -552,45 +553,45 @@ private: const Message & aMessage, const Dns::UpdateHeader &aDnsHeader, const Dns::Zone & aZone, - uint16_t & aOffset); + uint16_t & aOffset) const; otError ProcessAdditionalSection(Host * aHost, const Message & aMessage, const Dns::UpdateHeader &aDnsHeader, - uint16_t & aOffset); + uint16_t & aOffset) const; otError VerifySignature(const Dns::Ecdsa256KeyRecord &aKey, const Message & aMessage, Dns::UpdateHeader aDnsHeader, uint16_t aSigOffset, uint16_t aSigRdataOffset, uint16_t aSigRdataLength, - const char * aSignerName); + const char * aSignerName) const; + otError ProcessZoneSection(const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + uint16_t & aOffset, + Dns::Zone & aZone) const; + otError ProcessHostDescriptionInstruction(Host & aHost, + const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + const Dns::Zone & aZone, + uint16_t aOffset) const; + otError ProcessServiceDiscoveryInstructions(Host & aHost, + const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + const Dns::Zone & aZone, + uint16_t aOffset) const; + otError ProcessServiceDescriptionInstructions(Host & aHost, + const Message & aMessage, + const Dns::UpdateHeader &aDnsHeader, + const Dns::Zone & aZone, + uint16_t & aOffset) const; - static otError ProcessZoneSection(const Message & aMessage, - const Dns::UpdateHeader &aDnsHeader, - uint16_t & aOffset, - Dns::Zone & aZone); - static otError ProcessHostDescriptionInstruction(Host & aHost, - const Message & aMessage, - const Dns::UpdateHeader &aDnsHeader, - const Dns::Zone & aZone, - uint16_t aOffset); - static otError ProcessServiceDiscoveryInstructions(Host & aHost, - const Message & aMessage, - const Dns::UpdateHeader &aDnsHeader, - const Dns::Zone & aZone, - uint16_t aOffset); - static otError ProcessServiceDescriptionInstructions(Host & aHost, - const Message & aMessage, - const Dns::UpdateHeader &aDnsHeader, - const Dns::Zone & aZone, - uint16_t & aOffset); static bool IsValidDeleteAllRecord(const Dns::ResourceRecord &aRecord); + const Service *FindService(const char *aFullName) const; void HandleUpdate(const Dns::UpdateHeader &aDnsHeader, Host *aHost, const Ip6::MessageInfo &aMessageInfo); void AddHost(Host *aHost); void RemoveAndFreeHost(Host *aHost); - Service * FindService(const char *aFullName); - bool HasNameConflictsWith(Host &aHost); + bool HasNameConflictsWith(Host &aHost) const; void SendResponse(const Dns::UpdateHeader & aHeader, Dns::UpdateHeader::Response aResponseCode, const Ip6::MessageInfo & aMessageInfo); diff --git a/tests/unit/test_dns.cpp b/tests/unit/test_dns.cpp index f4fcf2815..2fac6fb46 100644 --- a/tests/unit/test_dns.cpp +++ b/tests/unit/test_dns.cpp @@ -145,6 +145,43 @@ void TestDnsName(void) message->SetOffset(0); + printf("----------------------------------------------------------------\n"); + printf("Verify domain name match:\n"); + + { + const char *subDomain; + const char *domain; + + subDomain = "my-service._ipps._tcp.local."; + domain = "local."; + VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed"); + + subDomain = "my-service._ipps._tcp.local"; + domain = "local."; + VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed"); + + subDomain = "my-service._ipps._tcp.local."; + domain = "local"; + VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed"); + + subDomain = "my-service._ipps._tcp.local"; + domain = "local"; + VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed"); + + subDomain = "my-service._ipps._tcp.default.service.arpa."; + domain = "default.service.arpa."; + VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed"); + + subDomain = "my-service._ipps._tcp.default.service.arpa."; + domain = "service.arpa."; + VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed"); + + // Verify it doesn't match a portion of a label. + subDomain = "my-service._ipps._tcp.default.service.arpa."; + domain = "vice.arpa."; + VerifyOrQuit(!Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() succeed"); + } + printf("----------------------------------------------------------------\n"); printf("Append names, check encoded bytes, parse name and read labels:\n");