From 33cc75ed3b7510ab93b2d563eafb5661a43f22d4 Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Tue, 31 May 2022 22:01:27 -0700 Subject: [PATCH] [srp-server] implement TTL processing (#7738) --- include/openthread/instance.h | 2 +- include/openthread/srp_server.h | 44 +++++++ src/cli/cli_srp_server.cpp | 25 ++++ src/cli/cli_srp_server.hpp | 2 + src/core/api/srp_server_api.cpp | 15 +++ src/core/net/srp_server.cpp | 79 ++++++++++- src/core/net/srp_server.hpp | 77 ++++++++++- tests/scripts/thread-cert/Makefile.am | 2 + tests/scripts/thread-cert/node.py | 9 +- tests/scripts/thread-cert/test_srp_ttl.py | 151 ++++++++++++++++++++++ tests/toranj/cli/cli.py | 5 +- tools/otci/otci/otci.py | 2 +- 12 files changed, 399 insertions(+), 14 deletions(-) create mode 100755 tests/scripts/thread-cert/test_srp_ttl.py diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 769b59e21..877e3267a 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (214) +#define OPENTHREAD_API_VERSION (215) /** * @addtogroup api-instance diff --git a/include/openthread/srp_server.h b/include/openthread/srp_server.h index c9fd220e8..fba71749b 100644 --- a/include/openthread/srp_server.h +++ b/include/openthread/srp_server.h @@ -155,6 +155,16 @@ typedef enum otSrpServerAddressMode OT_SRP_SERVER_ADDRESS_MODE_ANYCAST = 1, ///< Anycast address mode. } otSrpServerAddressMode; +/** + * This structure includes SRP server TTL configurations. + * + */ +typedef struct otSrpServerTtlConfig +{ + uint32_t mMinTtl; ///< The minimum TTL in seconds. + uint32_t mMaxTtl; ///< The maximum TTL in seconds. +} otSrpServerTtlConfig; + /** * This structure includes SRP server LEASE and KEY-LEASE configurations. * @@ -298,6 +308,30 @@ otError otSrpServerSetAnycastModeSequenceNumber(otInstance *aInstance, uint8_t a */ void otSrpServerSetEnabled(otInstance *aInstance, bool aEnabled); +/** + * This function returns SRP server TTL configuration. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[out] aTtlConfig A pointer to an `otSrpServerTtlConfig` instance. + * + */ +void otSrpServerGetTtlConfig(otInstance *aInstance, otSrpServerTtlConfig *aTtlConfig); + +/** + * This function sets SRP server TTL configuration. + * + * The granted TTL will always be no greater than the max lease interval configured via `otSrpServerSetLeaseConfig()`, + * regardless of the minimum and maximum TTL configuration. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aTtlConfig A pointer to an `otSrpServerTtlConfig` instance. + * + * @retval OT_ERROR_NONE Successfully set the TTL configuration. + * @retval OT_ERROR_INVALID_ARGS The TTL configuration is not valid. + * + */ +otError otSrpServerSetTtlConfig(otInstance *aInstance, const otSrpServerTtlConfig *aTtlConfig); + /** * This function returns SRP server LEASE and KEY-LEASE configurations. * @@ -617,6 +651,16 @@ uint16_t otSrpServerServiceGetWeight(const otSrpServerService *aService); */ uint16_t otSrpServerServiceGetPriority(const otSrpServerService *aService); +/** + * This function returns the TTL of the service instance. + * + * @param[in] aService A pointer to the SRP service. + * + * @returns The TTL of the service instance.. + * + */ +uint32_t otSrpServerServiceGetTtl(const otSrpServerService *aService); + /** * This function returns the TXT record data of the service instance. * diff --git a/src/cli/cli_srp_server.cpp b/src/cli/cli_srp_server.cpp index 74930419b..e13b8eca6 100644 --- a/src/cli/cli_srp_server.cpp +++ b/src/cli/cli_srp_server.cpp @@ -149,6 +149,30 @@ otError SrpServer::ProcessDisable(Arg aArgs[]) return OT_ERROR_NONE; } +otError SrpServer::ProcessTtl(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + otSrpServerTtlConfig ttlConfig; + + if (aArgs[0].IsEmpty()) + { + otSrpServerGetTtlConfig(GetInstancePtr(), &ttlConfig); + OutputLine("min ttl: %u", ttlConfig.mMinTtl); + OutputLine("max ttl: %u", ttlConfig.mMaxTtl); + } + else + { + SuccessOrExit(error = aArgs[0].ParseAsUint32(ttlConfig.mMinTtl)); + SuccessOrExit(error = aArgs[1].ParseAsUint32(ttlConfig.mMaxTtl)); + VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + error = otSrpServerSetTtlConfig(GetInstancePtr(), &ttlConfig); + } + +exit: + return error; +} + otError SrpServer::ProcessLease(Arg aArgs[]) { otError error = OT_ERROR_NONE; @@ -289,6 +313,7 @@ otError SrpServer::ProcessService(Arg aArgs[]) OutputLine(kIndentSize, "port: %hu", otSrpServerServiceGetPort(service)); OutputLine(kIndentSize, "priority: %hu", otSrpServerServiceGetPriority(service)); OutputLine(kIndentSize, "weight: %hu", otSrpServerServiceGetWeight(service)); + OutputLine(kIndentSize, "ttl: %hu", otSrpServerServiceGetTtl(service)); txtData = otSrpServerServiceGetTxtData(service, &txtDataLength); OutputFormat(kIndentSize, "TXT: "); diff --git a/src/cli/cli_srp_server.hpp b/src/cli/cli_srp_server.hpp index 03e5293df..363f59118 100644 --- a/src/cli/cli_srp_server.hpp +++ b/src/cli/cli_srp_server.hpp @@ -90,6 +90,7 @@ private: otError ProcessHost(Arg aArgs[]); otError ProcessService(Arg aArgs[]); otError ProcessSeqNum(Arg aArgs[]); + otError ProcessTtl(Arg aArgs[]); otError ProcessHelp(Arg aArgs[]); void OutputHostAddresses(const otSrpServerHost *aHost); @@ -100,6 +101,7 @@ private: {"help", &SrpServer::ProcessHelp}, {"host", &SrpServer::ProcessHost}, {"lease", &SrpServer::ProcessLease}, {"seqnum", &SrpServer::ProcessSeqNum}, {"service", &SrpServer::ProcessService}, {"state", &SrpServer::ProcessState}, + {"ttl", &SrpServer::ProcessTtl}, }; static_assert(BinarySearch::IsSorted(sCommands), "Command Table is not sorted"); diff --git a/src/core/api/srp_server_api.cpp b/src/core/api/srp_server_api.cpp index 950f25e3b..f9d4367e4 100644 --- a/src/core/api/srp_server_api.cpp +++ b/src/core/api/srp_server_api.cpp @@ -87,6 +87,16 @@ void otSrpServerSetEnabled(otInstance *aInstance, bool aEnabled) AsCoreType(aInstance).Get().SetEnabled(aEnabled); } +void otSrpServerGetTtlConfig(otInstance *aInstance, otSrpServerTtlConfig *aTtlConfig) +{ + AsCoreType(aInstance).Get().GetTtlConfig(AsCoreType(aTtlConfig)); +} + +otError otSrpServerSetTtlConfig(otInstance *aInstance, const otSrpServerTtlConfig *aTtlConfig) +{ + return AsCoreType(aInstance).Get().SetTtlConfig(AsCoreType(aTtlConfig)); +} + void otSrpServerGetLeaseConfig(otInstance *aInstance, otSrpServerLeaseConfig *aLeaseConfig) { AsCoreType(aInstance).Get().GetLeaseConfig(AsCoreType(aLeaseConfig)); @@ -204,6 +214,11 @@ uint16_t otSrpServerServiceGetPriority(const otSrpServerService *aService) return AsCoreType(aService).GetPriority(); } +uint32_t otSrpServerServiceGetTtl(const otSrpServerService *aService) +{ + return AsCoreType(aService).GetTtl(); +} + const uint8_t *otSrpServerServiceGetTxtData(const otSrpServerService *aService, uint16_t *aDataLength) { *aDataLength = AsCoreType(aService).GetTxtDataLength(); diff --git a/src/core/net/srp_server.cpp b/src/core/net/srp_server.cpp index ca012ab61..dd18a4070 100644 --- a/src/core/net/srp_server.cpp +++ b/src/core/net/srp_server.cpp @@ -168,6 +168,30 @@ exit: return; } +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 OT_MAX(mMinTtl, OT_MIN(OT_MIN(mMaxTtl, aLease), aTtl)); +} + Server::LeaseConfig::LeaseConfig(void) { mMinLease = kDefaultMinLease; @@ -266,7 +290,6 @@ void Server::AddHost(Host &aHost) OT_ASSERT(mHosts.FindMatching(aHost.GetFullName()) == nullptr); IgnoreError(mHosts.Add(aHost)); } - void Server::RemoveHost(Host *aHost, RetainName aRetainName, NotifyMode aNotifyServiceHandler) { VerifyOrExit(aHost != nullptr); @@ -374,20 +397,21 @@ void Server::HandleServiceUpdateResult(UpdateMetadata *aUpdate, Error aError) void Server::CommitSrpUpdate(Error aError, Host &aHost, const MessageMetadata &aMessageMetadata) { CommitSrpUpdate(aError, aHost, aMessageMetadata.mDnsHeader, aMessageMetadata.mMessageInfo, - aMessageMetadata.mLeaseConfig); + aMessageMetadata.mTtlConfig, aMessageMetadata.mLeaseConfig); } void Server::CommitSrpUpdate(Error aError, UpdateMetadata &aUpdateMetadata) { CommitSrpUpdate(aError, aUpdateMetadata.GetHost(), aUpdateMetadata.GetDnsHeader(), aUpdateMetadata.IsDirectRxFromClient() ? &aUpdateMetadata.GetMessageInfo() : nullptr, - aUpdateMetadata.GetLeaseConfig()); + aUpdateMetadata.GetTtlConfig(), aUpdateMetadata.GetLeaseConfig()); } void Server::CommitSrpUpdate(Error aError, Host & aHost, const Dns::UpdateHeader &aDnsHeader, const Ip6::MessageInfo * aMessageInfo, + const TtlConfig & aTtlConfig, const LeaseConfig & aLeaseConfig) { Host * existingHost; @@ -395,6 +419,7 @@ void Server::CommitSrpUpdate(Error aError, uint32_t hostKeyLease; uint32_t grantedLease; uint32_t grantedKeyLease; + uint32_t grantedTtl; bool shouldFreeHost = true; SuccessOrExit(aError); @@ -403,14 +428,17 @@ void Server::CommitSrpUpdate(Error aError, hostKeyLease = aHost.GetKeyLease(); grantedLease = aLeaseConfig.GrantLease(hostLease); grantedKeyLease = aLeaseConfig.GrantKeyLease(hostKeyLease); + grantedTtl = aTtlConfig.GrantTtl(grantedLease, aHost.GetTtl()); aHost.SetLease(grantedLease); aHost.SetKeyLease(grantedKeyLease); + aHost.SetTtl(grantedTtl); for (Service &service : aHost.mServices) { service.mDescription->mLease = grantedLease; service.mDescription->mKeyLease = grantedKeyLease; + service.mDescription->mTtl = grantedTtl; } existingHost = mHosts.FindMatching(aHost.GetFullName()); @@ -802,6 +830,8 @@ Error Server::ProcessHostDescriptionInstruction(Host & aHost, 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)); @@ -816,6 +846,9 @@ Error Server::ProcessHostDescriptionInstruction(Host & aHost, 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); @@ -908,6 +941,11 @@ Error Server::ProcessServiceDiscoveryInstructions(Host & aHost, // This RR is a "Delete an RR from an RRset" update when the CLASS is NONE. service->mIsDeleted = (ptrRecord.GetClass() == Dns::ResourceRecord::kClassNone); + + if (!service->mIsDeleted) + { + SuccessOrExit(error = aHost.ProcessTtl(ptrRecord.GetTtl())); + } } exit: @@ -959,6 +997,9 @@ Error Server::ProcessServiceDescriptionInstructions(Host & aHost, uint16_t hostNameLength = sizeof(hostName); VerifyOrExit(record.GetClass() == aMetadata.mDnsZone.GetClass(), error = kErrorFailed); + + SuccessOrExit(error = aHost.ProcessTtl(record.GetTtl())); + SuccessOrExit(error = aMessage.Read(offset, srvRecord)); offset += sizeof(srvRecord); @@ -971,6 +1012,7 @@ Error Server::ProcessServiceDescriptionInstructions(Host & aHost, // Make sure that this is the first SRV RR for this service description VerifyOrExit(desc->mPort == 0, error = kErrorFailed); + desc->mTtl = srvRecord.GetTtl(); desc->mPriority = srvRecord.GetPriority(); desc->mWeight = srvRecord.GetWeight(); desc->mPort = srvRecord.GetPort(); @@ -980,6 +1022,8 @@ Error Server::ProcessServiceDescriptionInstructions(Host & aHost, { VerifyOrExit(record.GetClass() == aMetadata.mDnsZone.GetClass(), error = kErrorFailed); + SuccessOrExit(error = aHost.ProcessTtl(record.GetTtl())); + desc = aHost.FindServiceDescription(name); VerifyOrExit(desc != nullptr, error = kErrorFailed); @@ -1324,11 +1368,12 @@ void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessag Error Server::ProcessMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { - return ProcessMessage(aMessage, TimerMilli::GetNow(), mLeaseConfig, &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) { @@ -1337,6 +1382,7 @@ Error Server::ProcessMessage(Message & aMessage, metadata.mOffset = aMessage.GetOffset(); metadata.mRxTime = aRxTime; + metadata.mTtlConfig = aTtlConfig; metadata.mLeaseConfig = aLeaseConfig; metadata.mMessageInfo = aMessageInfo; @@ -1680,6 +1726,7 @@ Error Server::Service::Description::Init(const char *aInstanceName, Host &aHost) mHost = &aHost; mPriority = 0; mWeight = 0; + mTtl = 0; mPort = 0; mLease = 0; mKeyLease = 0; @@ -1708,6 +1755,7 @@ void Server::Service::Description::TakeResourcesFrom(Description &aDescription) mWeight = aDescription.mWeight; mPort = aDescription.mPort; + mTtl = aDescription.mTtl; mLease = aDescription.mLease; mKeyLease = aDescription.mKeyLease; mUpdateTime = TimerMilli::GetNow(); @@ -1736,6 +1784,7 @@ exit: Server::Host::Host(Instance &aInstance, TimeMilli aUpdateTime) : InstanceLocator(aInstance) , mNext(nullptr) + , mTtl(0) , mLease(0) , mKeyLease(0) , mUpdateTime(aUpdateTime) @@ -1804,6 +1853,26 @@ void Server::Host::GetLeaseInfo(LeaseInfo &aLeaseInfo) const aLeaseInfo.mRemainingKeyLease = (now <= keyExpireTime) ? (keyExpireTime - now) : 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 wil 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::FindNextService(const Service *aPrevService, Service::Flags aFlags, const char * aServiceName, @@ -1915,6 +1984,7 @@ Error Server::Host::MergeServicesAndResourcesFrom(Host &aHost) mAddresses.TakeFrom(static_cast &&>(aHost.mAddresses)); mKeyRecord = aHost.mKeyRecord; + mTtl = aHost.mTtl; mLease = aHost.mLease; mKeyLease = aHost.mKeyLease; mUpdateTime = TimerMilli::GetNow(); @@ -2028,6 +2098,7 @@ Server::UpdateMetadata::UpdateMetadata(Instance &aInstance, Host &aHost, const M , mExpireTime(TimerMilli::GetNow() + kDefaultEventsHandlerTimeout) , mDnsHeader(aMessageMetadata.mDnsHeader) , mId(Get().AllocateId()) + , mTtlConfig(aMessageMetadata.mTtlConfig) , mLeaseConfig(aMessageMetadata.mLeaseConfig) , mHost(aHost) , mIsDirectRxFromClient(aMessageMetadata.IsDirectRxFromClient()) diff --git a/src/core/net/srp_server.hpp b/src/core/net/srp_server.hpp index f31bd21b1..6fcc1d818 100644 --- a/src/core/net/srp_server.hpp +++ b/src/core/net/srp_server.hpp @@ -259,6 +259,14 @@ public: */ Error GetServiceSubTypeLabel(char *aLabel, uint8_t aMaxSize) const; + /** + * This method returns the TTL of the service instance. + * + * @returns The TTL of the service instance. + * + */ + uint32_t GetTtl(void) const { return mDescription->mTtl; } + /** * This method returns the port of the service instance. * @@ -390,6 +398,7 @@ public: uint16_t mPriority; uint16_t mWeight; uint16_t mPort; + uint32_t mTtl; // The TTL in seconds. uint32_t mLease; // The LEASE time in seconds. uint32_t mKeyLease; // The KEY-LEASE time in seconds. TimeMilli mUpdateTime; @@ -467,6 +476,14 @@ public: return mAddresses.AsCArray(); } + /** + * This method returns the TTL of the host. + * + * @returns The TTL of the host. + * + */ + uint32_t GetTtl(void) const { return mTtl; } + /** * This method returns the LEASE time of the host. * @@ -555,10 +572,13 @@ public: Host(Instance &aInstance, TimeMilli aUpdateTime); ~Host(void); - Error SetFullName(const char *aFullName); - void SetKeyRecord(Dns::Ecdsa256KeyRecord &aKeyRecord); - void SetLease(uint32_t aLease) { mLease = aLease; } - void SetKeyLease(uint32_t aKeyLease) { mKeyLease = aKeyLease; } + Error SetFullName(const char *aFullName); + void SetKeyRecord(Dns::Ecdsa256KeyRecord &aKeyRecord); + void SetTtl(uint32_t aTtl) { mTtl = aTtl; } + void SetLease(uint32_t aLease) { mLease = aLease; } + void SetKeyLease(uint32_t aKeyLease) { mKeyLease = aKeyLease; } + Error ProcessTtl(uint32_t aTtl); + LinkedList &GetServices(void) { return mServices; } Service * AddNewService(const char *aServiceName, const char *aInstanceName, @@ -582,12 +602,33 @@ public: // TODO(wgtdkp): there is no necessary to save the entire resource // record, saving only the ECDSA-256 public key should be enough. Dns::Ecdsa256KeyRecord mKeyRecord; + uint32_t mTtl; // The TTL in seconds. uint32_t mLease; // The LEASE time in seconds. uint32_t mKeyLease; // The KEY-LEASE time in seconds. TimeMilli mUpdateTime; LinkedList mServices; }; + /** + * This class handles TTL configuration. + * + */ + class TtlConfig : public otSrpServerTtlConfig + { + friend class Server; + + public: + /** + * This constructor initializes to default TTL configuration. + * + */ + TtlConfig(void); + + private: + bool IsValid(void) const { return mMinTtl <= mMaxTtl; } + uint32_t GrantTtl(uint32_t aLease, uint32_t aTtl) const; + }; + /** * This class handles LEASE and KEY-LEASE configurations. * @@ -760,6 +801,25 @@ public: */ void SetEnabled(bool aEnabled); + /** + * This method returns the TTL configuration. + * + * @param[out] aTtlConfig A reference to the `TtlConfig` instance. + * + */ + void GetTtlConfig(TtlConfig &aTtlConfig) const { aTtlConfig = mTtlConfig; } + + /** + * This method sets the TTL configuration. + * + * @param[in] aTtlConfig A reference to the `TtlConfig` instance. + * + * @retval kErrorNone Successfully set the TTL configuration + * @retval kErrorInvalidArgs The TTL range is not valid. + * + */ + Error SetTtlConfig(const TtlConfig &aTtlConfig); + /** * This method returns the LEASE and KEY-LEASE configurations. * @@ -814,6 +874,8 @@ public: private: static constexpr uint16_t kUdpPayloadSize = Ip6::kMaxDatagramLength - sizeof(Ip6::Udp::Header); + static constexpr uint32_t kDefaultMinTtl = 60u; // 1 min (in seconds). + static constexpr uint32_t kDefaultMaxTtl = 3600u * 2; // 2 hours (in seconds). static constexpr uint32_t kDefaultMinLease = 60u * 30; // 30 min (in seconds). static constexpr uint32_t kDefaultMaxLease = 3600u * 2; // 2 hours (in seconds). static constexpr uint32_t kDefaultMinKeyLease = 3600u * 24; // 1 day (in seconds). @@ -836,6 +898,7 @@ private: Dns::Zone mDnsZone; uint16_t mOffset; TimeMilli mRxTime; + TtlConfig mTtlConfig; LeaseConfig mLeaseConfig; const Ip6::MessageInfo *mMessageInfo; // Set to `nullptr` when from SRPL. }; @@ -853,6 +916,7 @@ private: TimeMilli GetExpireTime(void) const { return mExpireTime; } const Dns::UpdateHeader &GetDnsHeader(void) const { return mDnsHeader; } ServiceUpdateId GetId(void) const { return mId; } + const TtlConfig & GetTtlConfig(void) const { return mTtlConfig; } const LeaseConfig & GetLeaseConfig(void) const { return mLeaseConfig; } Host & GetHost(void) { return mHost; } const Ip6::MessageInfo & GetMessageInfo(void) const { return mMessageInfo; } @@ -866,6 +930,7 @@ private: TimeMilli mExpireTime; Dns::UpdateHeader mDnsHeader; ServiceUpdateId mId; // The ID of this service update transaction. + TtlConfig mTtlConfig; // TTL config to use when processing the message. LeaseConfig mLeaseConfig; // Lease config to use when processing the message. Host & mHost; // The `UpdateMetadata` has no ownership of this host. Ip6::MessageInfo mMessageInfo; // Valid when `mIsDirectRxFromClient` is true. @@ -894,10 +959,12 @@ private: Host & aHost, const Dns::UpdateHeader &aDnsHeader, const Ip6::MessageInfo * aMessageInfo, + const TtlConfig & aTtlConfig, const LeaseConfig & aLeaseConfig); Error ProcessMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); Error ProcessMessage(Message & aMessage, TimeMilli aRxTime, + const TtlConfig & aTtlConfig, const LeaseConfig & aLeaseConfig, const Ip6::MessageInfo *aMessageInfo); void ProcessDnsUpdate(Message &aMessage, MessageMetadata &aMetadata); @@ -951,6 +1018,7 @@ private: Heap::String mDomain; + TtlConfig mTtlConfig; LeaseConfig mLeaseConfig; LinkedList mHosts; @@ -971,6 +1039,7 @@ private: } // namespace Srp +DefineCoreType(otSrpServerTtlConfig, Srp::Server::TtlConfig); DefineCoreType(otSrpServerLeaseConfig, Srp::Server::LeaseConfig); DefineCoreType(otSrpServerHost, Srp::Server::Host); DefineCoreType(otSrpServerService, Srp::Server::Service); diff --git a/tests/scripts/thread-cert/Makefile.am b/tests/scripts/thread-cert/Makefile.am index 4b115e902..326869093 100644 --- a/tests/scripts/thread-cert/Makefile.am +++ b/tests/scripts/thread-cert/Makefile.am @@ -196,6 +196,7 @@ EXTRA_DIST = \ test_srp_server_anycast_mode.py \ test_srp_server_reboot_port.py \ test_srp_sub_type.py \ + test_srp_ttl.py \ test_zero_len_external_route.py \ thread_cert.py \ tlvs_parsing.py \ @@ -270,6 +271,7 @@ check_SCRIPTS = \ test_srp_server_anycast_mode.py \ test_srp_server_reboot_port.py \ test_srp_sub_type.py \ + test_srp_ttl.py \ test_zero_len_external_route.py \ Cert_5_1_01_RouterAttach.py \ Cert_5_1_02_ChildAddressTimeout.py \ diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index add478fa5..f2cf62b98 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -929,6 +929,10 @@ class NodeImpl: self.send_command(f'srp server lease {min_lease} {max_lease} {min_key_lease} {max_key_lease}') self._expect_done() + def srp_server_set_ttl_range(self, min_ttl, max_ttl): + self.send_command(f'srp server ttl {min_ttl} {max_ttl}') + self._expect_done() + def srp_server_get_hosts(self): """Returns the host list on the SRP server as a list of property dictionary. @@ -989,6 +993,7 @@ class NodeImpl: 'port': '12345', 'priority': '0', 'weight': '0', + 'ttl': '7200', 'TXT': ['abc=010203'], 'host_fullname': 'my-host.default.service.arpa.', 'host': 'my-host', @@ -1015,8 +1020,8 @@ class NodeImpl: service_list.append(service) continue - # 'subtypes', port', 'priority', 'weight' - for i in range(0, 4): + # 'subtypes', port', 'priority', 'weight', 'ttl' + for i in range(0, 5): key_value = lines.pop(0).strip().split(':') service[key_value[0].strip()] = key_value[1].strip() diff --git a/tests/scripts/thread-cert/test_srp_ttl.py b/tests/scripts/thread-cert/test_srp_ttl.py new file mode 100755 index 000000000..5ea55ef5c --- /dev/null +++ b/tests/scripts/thread-cert/test_srp_ttl.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2022, 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. +# + +import ipaddress +import unittest + +import command +import config +import thread_cert + +# Test description: +# This test verifies the SRP server and client properly handle SRP host +# and service instance TTLs. +# +# Topology: +# LEADER (SRP server) +# | +# | +# ROUTER (SRP client) +# + +SERVER = 1 +CLIENT = 2 +KEY_LEASE = 240 # Seconds + + +class SrpTtl(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + SUPPORT_NCP = False + + TOPOLOGY = { + SERVER: { + 'name': 'SRP_SERVER', + 'mode': 'rdn', + }, + CLIENT: { + 'name': 'SRP_CLIENT', + 'mode': 'rdn', + }, + } + + def test(self): + server = self.nodes[SERVER] + client = self.nodes[CLIENT] + + # + # Start the server and client devices. + # + + server.srp_server_set_enabled(True) + server.srp_server_set_lease_range(120, 240, KEY_LEASE, KEY_LEASE) + server.start() + self.simulator.go(5) + self.assertEqual(server.get_state(), 'leader') + self.simulator.go(5) + + client.srp_server_set_enabled(False) + client.start() + self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.assertEqual(client.get_state(), 'router') + + client.srp_client_set_host_name('my-host') + client.srp_client_set_host_address('2001::1') + client.srp_client_start(server.get_addrs()[0], client.get_srp_server_port()) + client.srp_client_add_service('my-service', '_ipps._tcp', 12345) + self.simulator.go(2) + + # + # CLIENT_TTL < TTL_MIN < LEASE_MAX ==> TTL_MIN + # + + client.srp_client_set_ttl(100) + server.srp_server_set_ttl_range(120, 240) + server.srp_server_set_lease_range(120, 240, KEY_LEASE, KEY_LEASE) + self.simulator.go(KEY_LEASE) + self.check_ttl(120) + + # + # TTL_MIN < CLIENT_TTL < TTL_MAX < LEASE_MAX ==> CLIENT_TTL + # + + client.srp_client_set_ttl(100) + server.srp_server_set_ttl_range(60, 120) + server.srp_server_set_lease_range(120, 240, KEY_LEASE, KEY_LEASE) + self.simulator.go(KEY_LEASE) + self.check_ttl(100) + + # + # TTL_MAX < LEASE_MAX < CLIENT_TTL ==> TTL_MAX + # + + client.srp_client_set_ttl(240) + server.srp_server_set_ttl_range(60, 120) + server.srp_server_set_lease_range(120, 240, KEY_LEASE, KEY_LEASE) + self.simulator.go(KEY_LEASE) + self.check_ttl(120) + + # + # LEASE_MAX < TTL_MAX < CLIENT_TTL ==> LEASE_MAX + # + + client.srp_client_set_ttl(240) + server.srp_server_set_ttl_range(60, 120) + server.srp_server_set_lease_range(30, 60, KEY_LEASE, KEY_LEASE) + self.simulator.go(KEY_LEASE) + self.check_ttl(60) + + def check_ttl(self, ttl): + """Check that we have properly registered host and service instance. + """ + + server = self.nodes[SERVER] + + server_services = server.srp_server_get_services() + print(server_services) + self.assertEqual(len(server_services), 1) + server_service = server_services[0] + + # Verify that the server accepted the SRP registration and stored + # the same service resources. + self.assertEqual(int(server_service['ttl']), ttl) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index 184271b09..5e24bedec 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -458,6 +458,7 @@ class Node(object): 'port': '12345', 'priority': '0', 'weight': '0', + 'ttl': '7200', 'TXT': ['abc=010203'], 'host_fullname': 'my-host.default.service.arpa.', 'host': 'my-host', @@ -478,8 +479,8 @@ class Node(object): if service['deleted'] == 'true': service_list.append(service) continue - # 'subtypes', port', 'priority', 'weight' - for i in range(0, 4): + # 'subtypes', port', 'priority', 'weight', 'ttl' + for i in range(0, 5): key_value = outputs.pop(0).strip().split(':') service[key_value[0].strip()] = key_value[1].strip() txt_entries = outputs.pop(0).strip().split('[')[1].strip(' ]').split(',') diff --git a/tools/otci/otci/otci.py b/tools/otci/otci/otci.py index 710400e0b..da6ef6135 100644 --- a/tools/otci/otci/otci.py +++ b/tools/otci/otci/otci.py @@ -976,7 +976,7 @@ class OTCI(object): info['addresses'] = list(map(Ip6Addr, v.split(', '))) elif k == 'subtypes': info[k] = list() if v == '(null)' else list(v.split(',')) - elif k in ('port', 'weight', 'priority'): + elif k in ('port', 'weight', 'priority', 'ttl'): info[k] = int(v) elif k in ('host',): info[k] = v