From d87be657c6beb35eeced27df1871e835ddae57f7 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 2 Jul 2024 08:52:28 -0700 Subject: [PATCH] [srp-client] enhance selection of TX jitter based on trigger reason (#10357) This commit improves the `Srp::Client` mechanism for applying random jitter delays before sending update messages to the server. It now supports different jitter ranges based on specific triggers. Since trigger events are often network-wide, potentially causing simultaneous SRP re-registration from many nodes, longer jitter intervals are used to distribute the resulting SRP update transmissions and avoid congestion. The following triggers are covered: - Server switch: Client switching to a new server while already connected to a discovered server. This occurs when a new server entry appears in Network Data which is preferred over the current selection. - Server restart: Client was previously connected to a server that disappeared from Network Data. Later, the same or a new server is discovered in Network Data. - SLAAC address add or remove: This is generally triggered by updates to SLAAC prefixes in Network Data (e.g., OMR prefix changes). - First registration after attach: - If the device is attaching to an established Thread mesh (e.g., after a reboot or initial pairing), the Network Data it receives should already include a server entry, leading to a quick server selection after attach. If server selection occurs within a short window, a shorter TX jitter is used, allowing the device to register quickly and become discoverable. - If server discovery takes longer, a longer TX jitter is used. This situation can indicate a server/BR starting up or a network-wide restart of many nodes (e.g., due to a power outage). This commit introduces `TxJitter` class to manage the requested TX jitter based on a trigger reason. It tracks the time of the event that triggered a longer jitter request. If the update message is sent immediately after the trigger event, the requested maximum jitter is applied. However, if the update message is sent later, the maximum jitter is adjusted proportionally to the time elapsed since the trigger event. If the elapsed time exceeds the requested maximum jitter interval, the default short jitter is applied to avoid unnecessary registration delay. --- .../config/openthread-core-config-check.h | 9 + src/core/config/srp_client.h | 32 --- src/core/net/netif.cpp | 4 + src/core/net/srp_client.cpp | 255 ++++++++++++++++-- src/core/net/srp_client.hpp | 83 +++++- .../test_dnssd_name_with_special_chars.py | 2 +- .../thread-cert/test_srp_auto_host_address.py | 14 +- .../test_srp_client_remove_host.py | 2 +- .../test_srp_client_save_server_info.py | 2 +- tests/scripts/thread-cert/test_srp_lease.py | 4 +- .../test_srp_many_services_mtu_check.py | 2 +- .../test_srp_register_services_diff_lease.py | 2 +- .../test_srp_server_anycast_mode.py | 2 +- .../test_srp_server_reboot_port.py | 4 +- .../scripts/thread-cert/test_srp_sub_type.py | 2 +- tests/scripts/thread-cert/test_srp_ttl.py | 1 + .../toranj/cli/test-400-srp-client-server.py | 3 +- .../cli/test-501-multi-br-failure-recovery.py | 1 + ...st-502-multi-br-leader-failure-recovery.py | 1 + tests/toranj/openthread-core-toranj-config.h | 2 + tests/unit/test_srp_adv_proxy.cpp | 2 +- tests/unit/test_srp_server.cpp | 2 +- 22 files changed, 345 insertions(+), 86 deletions(-) diff --git a/src/core/config/openthread-core-config-check.h b/src/core/config/openthread-core-config-check.h index 1f3a7c59c..53a9544df 100644 --- a/src/core/config/openthread-core-config-check.h +++ b/src/core/config/openthread-core-config-check.h @@ -678,4 +678,13 @@ "OPENTHREAD_LIB_SPINEL_RX_FRAME_BUFFER_SIZE. Pass the macro to source code under"\ "src/lib/spinel." #endif + +#ifdef OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MIN_DELAY +#error "OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MIN_DELAY was removed." +#endif + +#ifdef OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MAX_DELAY +#error "OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MAX_DELAY was removed." +#endif + #endif // OPENTHREAD_CORE_CONFIG_CHECK_H_ diff --git a/src/core/config/srp_client.h b/src/core/config/srp_client.h index 34c9abfb6..c5eaca64b 100644 --- a/src/core/config/srp_client.h +++ b/src/core/config/srp_client.h @@ -225,38 +225,6 @@ #define OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_DENOMINATOR 2 #endif -/** - * @def OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MIN_DELAY - * - * Specifies the minimum value (in msec) for the short random delay wait time before sending an update message. - * - * The random delay is chosen uniformly from the min up to max value `OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MAX_DELAY`. - * - * When there is a change (e.g., a new service is added/removed) that requires an update, the SRP client will wait for - * a short delay before preparing and sending an SRP update message to server. This allows user to provide more change - * that are then all sent in same update message. The delay is only applied on the first change that triggers an - * update message transmission. Subsequent changes (API calls) while waiting for the tx to start will not reset the - * delay timer. - * - */ -#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MIN_DELAY -#define OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MIN_DELAY 10 -#endif - -/** - * @def OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MIN_DELAY - * - * Specifies the maximum value (in msec) for the short random delay wait time before sending an update message. - * - * The random delay is chosen uniformly from the min `OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MIN_DELAY` up to max value. - * - * See `OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MIN_DELAY` for more details. - * - */ -#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MAX_DELAY -#define OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MAX_DELAY 700 -#endif - /** * @def OPENTHREAD_CONFIG_SRP_CLIENT_MIN_RETRY_WAIT_INTERVAL * diff --git a/src/core/net/netif.cpp b/src/core/net/netif.cpp index 0701436c1..999507662 100644 --- a/src/core/net/netif.cpp +++ b/src/core/net/netif.cpp @@ -415,6 +415,10 @@ void Netif::SignalUnicastAddressChange(AddressEvent aEvent, const UnicastAddress Get().Signal(event); +#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE + Get().HandleUnicastAddressEvent(aEvent, aAddress); +#endif + #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE Get().RecordAddressEvent(aEvent, aAddress); #endif diff --git a/src/core/net/srp_client.cpp b/src/core/net/srp_client.cpp index ebc28ce51..33b65ff0d 100644 --- a/src/core/net/srp_client.cpp +++ b/src/core/net/srp_client.cpp @@ -175,6 +175,96 @@ bool Client::Service::Matches(const Service &aOther) const return (strcmp(GetName(), aOther.GetName()) == 0) && (strcmp(GetInstanceName(), aOther.GetInstanceName()) == 0); } +//--------------------------------------------------------------------- +// Client::TxJitter + +const uint32_t Client::TxJitter::kMaxJitters[] = { + Client::kMaxTxJitterOnDeviceReboot, // (0) kOnDeviceReboot + Client::kMaxTxJitterOnServerStart, // (1) kOnServerStart + Client::kMaxTxJitterOnServerRestart, // (2) kOnServerRestart + Client::kMaxTxJitterOnServerSwitch, // (3) kOnServerSwitch + Client::kMaxTxJitterOnSlaacAddrAdd, // (4) kOnSlaacAddrAdd + Client::kMaxTxJitterOnSlaacAddrRemove, // (5) kOnSlaacAddrRemove +}; + +void Client::TxJitter::Request(Reason aReason) +{ + static_assert(0 == kOnDeviceReboot, "kOnDeviceReboot value is incorrect"); + static_assert(1 == kOnServerStart, "kOnServerStart value is incorrect"); + static_assert(2 == kOnServerRestart, "kOnServerRestart value is incorrect"); + static_assert(3 == kOnServerSwitch, "kOnServerSwitch value is incorrect"); + static_assert(4 == kOnSlaacAddrAdd, "kOnSlaacAddrAdd value is incorrect"); + static_assert(5 == kOnSlaacAddrRemove, "kOnSlaacAddrRemove value is incorrect"); + + uint32_t maxJitter = kMaxJitters[aReason]; + + LogInfo("Requesting max tx jitter %lu (%s)", ToUlong(maxJitter), ReasonToString(aReason)); + + if (mRequestedMax != 0) + { + // If we have a previous request, adjust the `mRequestedMax` + // based on the time elapsed since that request was made. + + uint32_t duration = TimerMilli::GetNow() - mRequestTime; + + mRequestedMax = (mRequestedMax > duration) ? mRequestedMax - duration : 0; + } + + mRequestedMax = Max(mRequestedMax, maxJitter); + mRequestTime = TimerMilli::GetNow(); +} + +uint32_t Client::TxJitter::DetermineDelay(void) +{ + uint32_t delay; + uint32_t maxJitter = kMaxTxJitterDefault; + + if (mRequestedMax != 0) + { + uint32_t duration = TimerMilli::GetNow() - mRequestTime; + + if (duration >= mRequestedMax) + { + LogInfo("Requested max tx jitter %lu already expired", ToUlong(mRequestedMax)); + } + else + { + maxJitter = Max(mRequestedMax - duration, kMaxTxJitterDefault); + LogInfo("Applying remaining max jitter %lu", ToUlong(maxJitter)); + } + + mRequestedMax = 0; + } + + delay = Random::NonCrypto::GetUint32InRange(kMinTxJitter, maxJitter); + LogInfo("Use random tx jitter %lu from [%lu, %lu]", ToUlong(delay), ToUlong(kMinTxJitter), ToUlong(maxJitter)); + + return delay; +} + +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) +const char *Client::TxJitter::ReasonToString(Reason aReason) +{ + static const char *const kReasonStrings[] = { + "OnDeviceReboot", // (0) kOnDeviceReboot + "OnServerStart", // (1) kOnServerStart + "OnServerRestart", // (2) kOnServerRestart + "OnServerSwitch", // (3) kOnServerSwitch + "OnSlaacAddrAdd", // (4) kOnSlaacAddrAdd + "OnSlaacAddrRemove", // (5) kOnSlaacAddrRemove + }; + + static_assert(0 == kOnDeviceReboot, "kOnDeviceReboot value is incorrect"); + static_assert(1 == kOnServerStart, "kOnServerStart value is incorrect"); + static_assert(2 == kOnServerRestart, "kOnServerRestart value is incorrect"); + static_assert(3 == kOnServerSwitch, "kOnServerSwitch value is incorrect"); + static_assert(4 == kOnSlaacAddrAdd, "kOnSlaacAddrAdd value is incorrect"); + static_assert(5 == kOnSlaacAddrRemove, "kOnSlaacAddrRemove value is incorrect"); + + return kReasonStrings[aReason]; +} +#endif + //--------------------------------------------------------------------- // Client::AutoStart @@ -183,7 +273,7 @@ bool Client::Service::Matches(const Service &aOther) const Client::AutoStart::AutoStart(void) { Clear(); - mState = kDefaultMode ? kSelectedNone : kDisabled; + mState = kDefaultMode ? kFirstTimeSelecting : kDisabled; } bool Client::AutoStart::HasSelectedServer(void) const @@ -193,7 +283,8 @@ bool Client::AutoStart::HasSelectedServer(void) const switch (mState) { case kDisabled: - case kSelectedNone: + case kFirstTimeSelecting: + case kReselecting: break; case kSelectedUnicastPreferred: @@ -224,18 +315,20 @@ void Client::AutoStart::InvokeCallback(const Ip6::SockAddr *aServerSockAddr) con const char *Client::AutoStart::StateToString(State aState) { static const char *const kStateStrings[] = { - "Disabled", // (0) kDisabled - "Idle", // (1) kSelectedNone - "Unicast-prf", // (2) kSelectedUnicastPreferred - "Anycast", // (3) kSelectedAnycast - "Unicast", // (4) kSelectedUnicast + "Disabled", // (0) kDisabled + "1stTimeSelect", // (1) kFirstTimeSelecting + "Reselect", // (2) kReselecting + "Unicast-prf", // (3) kSelectedUnicastPreferred + "Anycast", // (4) kSelectedAnycast + "Unicast", // (5) kSelectedUnicast }; static_assert(0 == kDisabled, "kDisabled value is incorrect"); - static_assert(1 == kSelectedNone, "kSelectedNone value is incorrect"); - static_assert(2 == kSelectedUnicastPreferred, "kSelectedUnicastPreferred value is incorrect"); - static_assert(3 == kSelectedAnycast, "kSelectedAnycast value is incorrect"); - static_assert(4 == kSelectedUnicast, "kSelectedUnicast value is incorrect"); + static_assert(1 == kFirstTimeSelecting, "kFirstTimeSelecting value is incorrect"); + static_assert(2 == kReselecting, "kReselecting value is incorrect"); + static_assert(3 == kSelectedUnicastPreferred, "kSelectedUnicastPreferred value is incorrect"); + static_assert(4 == kSelectedAnycast, "kSelectedAnycast value is incorrect"); + static_assert(5 == kSelectedUnicast, "kSelectedUnicast value is incorrect"); return kStateStrings[aState]; } @@ -270,6 +363,9 @@ Client::Client(Instance &aInstance) , mSocket(aInstance, *this) , mDomainName(kDefaultDomainName) , mTimer(aInstance) +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + , mGuardTimer(aInstance) +#endif { mHostInfo.Init(); @@ -444,6 +540,10 @@ void Client::HandleRoleChanged(void) { if (Get().IsAttached()) { +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + ApplyAutoStartGuardOnAttach(); +#endif + VerifyOrExit(GetState() == kStatePaused); Resume(); } @@ -524,6 +624,36 @@ exit: return error; } +void Client::HandleUnicastAddressEvent(Ip6::Netif::AddressEvent aEvent, const Ip6::Netif::UnicastAddress &aAddress) +{ + // This callback from `Netif` signals an impending addition or + // removal of a unicast address, occurring before `Notifier` + // events. If `AutoAddress` is enabled, we check whether the + // address origin is SLAAC (e.g., an OMR address) and request a + // longer `TxJitter`. This helps randomize the next SRP + // update transmission time when triggered by an OMR prefix + // change. + + VerifyOrExit(IsRunning()); + VerifyOrExit(mHostInfo.IsAutoAddressEnabled()); + + VerifyOrExit(aAddress.GetOrigin() == Ip6::Netif::kOriginSlaac); + +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + // The `mGuardTimer`, started by `ApplyAutoStartGuardOnAttach()`, + // tracks a guard interval after the attach event. If an + // address change occurs within this short window, we do not + // apply a longer TX jitter, as this likely indicates a device + // reboot. + VerifyOrExit(!mGuardTimer.IsRunning()); +#endif + + mTxJitter.Request((aEvent == Ip6::Netif::kAddressAdded) ? TxJitter::kOnSlaacAddrAdd : TxJitter::kOnSlaacAddrRemove); + +exit: + return; +} + bool Client::ShouldUpdateHostAutoAddresses(void) const { bool shouldUpdate = false; @@ -736,7 +866,7 @@ void Client::SetState(State aState) break; case kStateToUpdate: - mTimer.Start(Random::NonCrypto::GetUint32InRange(kUpdateTxMinDelay, kUpdateTxMaxDelay)); + mTimer.Start(mTxJitter.DetermineDelay()); break; case kStateUpdating: @@ -778,7 +908,8 @@ bool Client::ChangeHostAndServiceStates(const ItemState *aNewStates, ServiceStat switch (mAutoStart.GetState()) { case AutoStart::kDisabled: - case AutoStart::kSelectedNone: + case AutoStart::kFirstTimeSelecting: + case AutoStart::kReselecting: break; case AutoStart::kSelectedUnicastPreferred: @@ -2056,19 +2187,46 @@ void Client::EnableAutoStartMode(AutoStartCallback aCallback, void *aContext) VerifyOrExit(mAutoStart.GetState() == AutoStart::kDisabled); - mAutoStart.SetState(AutoStart::kSelectedNone); + mAutoStart.SetState(AutoStart::kFirstTimeSelecting); + ApplyAutoStartGuardOnAttach(); + ProcessAutoStart(); exit: return; } +void Client::ApplyAutoStartGuardOnAttach(void) +{ + VerifyOrExit(Get().IsAttached()); + VerifyOrExit(!IsRunning()); + VerifyOrExit(mAutoStart.GetState() == AutoStart::kFirstTimeSelecting); + + // The `mGuardTimer` tracks a guard interval after the attach + // event while `AutoStart` has yet to select a server for the + // first time. + // + // This is used by `ProcessAutoStart()` to apply different TX + // jitter values. If server selection occurs within this short + // window, a shorter TX jitter is used. This typically represents + // the device rebooting or being paired. + // + // The guard time is also checked when handling SLAAC address change + // events, to decide whether or not to request longer TX jitter. + + mGuardTimer.Start(kGuardTimeAfterAttachToUseShorterTxJitter); + +exit: + return; +} + void Client::ProcessAutoStart(void) { Ip6::SockAddr serverSockAddr; DnsSrpAnycast::Info anycastInfo; DnsSrpUnicast::Info unicastInfo; - bool shouldRestart = false; + AutoStart::State oldAutoStartState = mAutoStart.GetState(); + bool shouldRestart = false; // If auto start mode is enabled, we check the Network Data entries // to discover and select the preferred SRP server to register with. @@ -2083,7 +2241,7 @@ void Client::ProcessAutoStart(void) if (IsRunning()) { - VerifyOrExit(mAutoStart.GetState() != AutoStart::kSelectedNone); + VerifyOrExit(mAutoStart.HasSelectedServer()); } // There are three types of entries in Network Data: @@ -2131,15 +2289,69 @@ void Client::ProcessAutoStart(void) Stop(kRequesterAuto, kResetRetryInterval); } - if (!serverSockAddr.GetAddress().IsUnspecified()) + if (serverSockAddr.GetAddress().IsUnspecified()) { - IgnoreError(Start(serverSockAddr, kRequesterAuto)); + if (mAutoStart.HasSelectedServer()) + { + mAutoStart.SetState(AutoStart::kReselecting); + } + + ExitNow(); } - else + + // Before calling `Start()`, determine the trigger reason for + // starting the client with the newly discovered server based on + // `AutoStart` state transitions. This reason is then used to + // select the appropriate TX jitter interval (randomizing the + // initial SRP update transmission to the new server). + + switch (oldAutoStartState) { - mAutoStart.SetState(AutoStart::kSelectedNone); + case AutoStart::kDisabled: + break; + + case AutoStart::kFirstTimeSelecting: + + // If the device is attaching to an established Thread mesh + // (e.g., after a reboot or pairing), the Network Data it + // receives should already include a server entry, leading to + // a quick server selection after attachment. The `mGuardTimer`, + // started by `ApplyAutoStartGuardOnAttach()`, tracks a guard + // interval after the attach event. If server selection + // occurs within this short window, a shorter TX jitter is + // used (`TxJitter::kOnDeviceReboot`), allowing the device to + // register quickly and become discoverable. + // + // If server discovery takes longer, a longer TX jitter + // is used (`TxJitter::kOnServerStart`). This situation + // can indicate a server/BR starting up or a network-wide + // restart of many nodes (e.g., due to a power outage). + + if (mGuardTimer.IsRunning()) + { + mTxJitter.Request(TxJitter::kOnDeviceReboot); + } + else + { + mTxJitter.Request(TxJitter::kOnServerStart); + } + + break; + + case AutoStart::kReselecting: + // Server is restarted (or possibly a new server started). + mTxJitter.Request(TxJitter::kOnServerRestart); + break; + + case AutoStart::kSelectedUnicastPreferred: + case AutoStart::kSelectedAnycast: + case AutoStart::kSelectedUnicast: + mTxJitter.Request(TxJitter::kOnServerSwitch); + break; } + IgnoreError(Start(serverSockAddr, kRequesterAuto)); + exit: return; } @@ -2230,7 +2442,8 @@ void Client::SelectNextServer(bool aDisallowSwitchOnRegisteredHost) case AutoStart::kSelectedAnycast: case AutoStart::kDisabled: - case AutoStart::kSelectedNone: + case AutoStart::kFirstTimeSelecting: + case AutoStart::kReselecting: ExitNow(); } diff --git a/src/core/net/srp_client.hpp b/src/core/net/srp_client.hpp index f45b95364..077a5a2cb 100644 --- a/src/core/net/srp_client.hpp +++ b/src/core/net/srp_client.hpp @@ -49,6 +49,7 @@ #include "crypto/ecdsa.hpp" #include "net/dns_types.hpp" #include "net/ip6.hpp" +#include "net/netif.hpp" #include "net/udp6.hpp" #include "thread/network_data_service.hpp" @@ -71,6 +72,7 @@ namespace Srp { class Client : public InstanceLocator, private NonCopyable { friend class ot::Notifier; + friend class ot::Ip6::Netif; using DnsSrpUnicast = NetworkData::Service::DnsSrpUnicast; using DnsSrpAnycast = NetworkData::Service::DnsSrpAnycast; @@ -853,13 +855,28 @@ private: OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_DENOMINATOR; // ------------------------------- - // When there is a change (e.g., a new service is added/removed) - // that requires an update, the SRP client will wait for a short - // delay as specified by `kUpdateTxDelay` before sending an SRP - // update to server. This allows the user to provide more change - // that are then all sent in same update message. - static constexpr uint32_t kUpdateTxMinDelay = OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MIN_DELAY; // in msec. - static constexpr uint32_t kUpdateTxMaxDelay = OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_MAX_DELAY; // in msec. + // TX jitter constants + // + // When changes trigger a new SRP update message transmission a random + // jitter delay is applied before sending the update message to server. + // This can occur due to changes in client services or host info, + // or `AutoStart` selecting a server for the first time or switching + // to a new server thus requiring re-registration. + // + // The constants below specify jitter ranges applied based on + // different trigger reasons. All values are in milliseconds. + // Also see `TxJitter` class. + + static constexpr uint32_t kMinTxJitter = 10; + static constexpr uint32_t kMaxTxJitterDefault = 500; + static constexpr uint32_t kMaxTxJitterOnDeviceReboot = 700; + static constexpr uint32_t kMaxTxJitterOnServerStart = 10 * Time::kOneSecondInMsec; + static constexpr uint32_t kMaxTxJitterOnServerRestart = 10 * Time::kOneSecondInMsec; + static constexpr uint32_t kMaxTxJitterOnServerSwitch = 10 * Time::kOneSecondInMsec; + static constexpr uint32_t kMaxTxJitterOnSlaacAddrAdd = 10 * Time::kOneSecondInMsec; + static constexpr uint32_t kMaxTxJitterOnSlaacAddrRemove = 10 * Time::kOneSecondInMsec; + + static constexpr uint32_t kGuardTimeAfterAttachToUseShorterTxJitter = 1000; // ------------------------------- // Retry related constants @@ -945,16 +962,47 @@ private: kForServicesAppendedInMessage, }; + class TxJitter : public Clearable + { + // Manages the random TX jitter to use when sending SRP update + // messages. + + public: + enum Reason + { + kOnDeviceReboot, + kOnServerStart, + kOnServerRestart, + kOnServerSwitch, + kOnSlaacAddrAdd, + kOnSlaacAddrRemove, + }; + + TxJitter(void) { Clear(); } + void Request(Reason aReason); + uint32_t DetermineDelay(void); + + private: + static const uint32_t kMaxJitters[]; +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) + static const char *ReasonToString(Reason aReason); +#endif + + uint32_t mRequestedMax; + TimeMilli mRequestTime; + }; + #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE class AutoStart : public Clearable { public: enum State : uint8_t{ - kDisabled, // AutoStart is disabled. - kSelectedNone, // AutoStart is enabled but not yet selected any servers. - kSelectedUnicastPreferred, // AutoStart selected a preferred unicast entry (address in service data). - kSelectedAnycast, // AutoStart selected an anycast entry with `mAnycastSeqNum`. - kSelectedUnicast, // AutoStart selected a unicast entry (address in server data). + kDisabled, // Disabled. + kFirstTimeSelecting, // Trying to select a server for the first time since AutoStart was enabled. + kReselecting, // Trying to select a server again (previously selected server was removed). + kSelectedUnicastPreferred, // Has selected a preferred unicast entry (address in service data). + kSelectedAnycast, // Has selected an anycast entry with `mAnycastSeqNum`. + kSelectedUnicast, // Has selected a unicast entry (address in server data). }; AutoStart(void); @@ -1012,6 +1060,7 @@ private: void Pause(void); void HandleNotifierEvents(Events aEvents); void HandleRoleChanged(void); + void HandleUnicastAddressEvent(Ip6::Netif::AddressEvent aEvent, const Ip6::Netif::UnicastAddress &aAddress); bool ShouldUpdateHostAutoAddresses(void) const; bool ShouldHostAutoAddressRegister(const Ip6::Netif::UnicastAddress &aUnicastAddress) const; Error UpdateHostInfoStateOnAddressChange(void); @@ -1056,8 +1105,10 @@ private: bool ShouldRenewEarly(const Service &aService) const; void HandleTimer(void); #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + void ApplyAutoStartGuardOnAttach(void); void ProcessAutoStart(void); Error SelectUnicastEntry(DnsSrpUnicast::Origin aOrigin, DnsSrpUnicast::Info &aInfo) const; + void HandleGuardTimer(void) {} #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE void SelectNextServer(bool aDisallowSwitchOnRegisteredHost); #endif @@ -1077,6 +1128,10 @@ private: using DelayTimer = TimerMilliIn; using ClientSocket = Ip6::Udp::SocketIn; +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + using GuardTimer = TimerMilliIn; +#endif + State mState; uint8_t mTxFailureRetryCount : 4; bool mShouldRemoveKeyLease : 1; @@ -1097,6 +1152,7 @@ private: uint32_t mKeyLease; uint32_t mDefaultLease; uint32_t mDefaultKeyLease; + TxJitter mTxJitter; ClientSocket mSocket; @@ -1106,7 +1162,8 @@ private: LinkedList mServices; DelayTimer mTimer; #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE - AutoStart mAutoStart; + GuardTimer mGuardTimer; + AutoStart mAutoStart; #endif }; diff --git a/tests/scripts/thread-cert/test_dnssd_name_with_special_chars.py b/tests/scripts/thread-cert/test_dnssd_name_with_special_chars.py index 6adf538bc..62c13e740 100755 --- a/tests/scripts/thread-cert/test_dnssd_name_with_special_chars.py +++ b/tests/scripts/thread-cert/test_dnssd_name_with_special_chars.py @@ -84,7 +84,7 @@ class TestDnssdNameWithSpecialChars(thread_cert.TestCase): server.srp_server_set_enabled(True) client.srp_client_enable_auto_start_mode() - self.simulator.go(5) + self.simulator.go(15) # Register a single service with the instance name containing special chars client.srp_client_set_host_name('host1') diff --git a/tests/scripts/thread-cert/test_srp_auto_host_address.py b/tests/scripts/thread-cert/test_srp_auto_host_address.py index 1612a30cf..2f86db58f 100755 --- a/tests/scripts/thread-cert/test_srp_auto_host_address.py +++ b/tests/scripts/thread-cert/test_srp_auto_host_address.py @@ -74,6 +74,7 @@ class SrpAutoHostAddress(thread_cert.TestCase): client.start() self.simulator.go(15) self.assertEqual(client.get_state(), 'leader') + client.srp_client_stop() server.start() self.simulator.go(5) @@ -88,8 +89,9 @@ class SrpAutoHostAddress(thread_cert.TestCase): #------------------------------------------------------------------- # Check auto start mode on SRP client + client.srp_client_enable_auto_start_mode() self.assertEqual(client.srp_client_get_auto_start_mode(), 'Enabled') - self.simulator.go(2) + self.simulator.go(15) self.assertEqual(client.srp_client_get_state(), 'Enabled') @@ -135,7 +137,7 @@ class SrpAutoHostAddress(thread_cert.TestCase): client.add_prefix('fd00:abba:cafe:bee::/64', 'paos') client.register_netdata() - self.simulator.go(5) + self.simulator.go(15) slaac_addr = [addr.strip() for addr in client.get_addrs() if addr.strip().startswith('fd00:abba:cafe:bee:')] self.assertEqual(len(slaac_addr), 1) @@ -147,7 +149,7 @@ class SrpAutoHostAddress(thread_cert.TestCase): client.add_prefix('fd00:9:8:7::/64', 'paos') client.register_netdata() - self.simulator.go(5) + self.simulator.go(15) slaac_addr = [addr.strip() for addr in client.get_addrs() if addr.strip().startswith('fd00:9:8:7:')] self.assertEqual(len(slaac_addr), 1) @@ -160,7 +162,7 @@ class SrpAutoHostAddress(thread_cert.TestCase): client.add_prefix('fd00:a:b:c::/64', 'aos') client.register_netdata() - self.simulator.go(5) + self.simulator.go(15) slaac_addr = [addr.strip() for addr in client.get_addrs() if addr.strip().startswith('fd00:a:b:c:')] self.assertEqual(len(slaac_addr), 1) @@ -173,7 +175,7 @@ class SrpAutoHostAddress(thread_cert.TestCase): client.remove_prefix('fd00:abba:cafe:bee::/64') client.register_netdata() - self.simulator.go(5) + self.simulator.go(15) self.check_registered_addresses(client, server) @@ -184,7 +186,7 @@ class SrpAutoHostAddress(thread_cert.TestCase): client.remove_prefix('fd00:9:8:7::/64') client.register_netdata() - self.simulator.go(5) + self.simulator.go(15) self.check_registered_addresses(client, server) diff --git a/tests/scripts/thread-cert/test_srp_client_remove_host.py b/tests/scripts/thread-cert/test_srp_client_remove_host.py index 298014518..681893914 100755 --- a/tests/scripts/thread-cert/test_srp_client_remove_host.py +++ b/tests/scripts/thread-cert/test_srp_client_remove_host.py @@ -81,7 +81,7 @@ class SrpRemoveHost(thread_cert.TestCase): server.srp_server_set_enabled(True) client.srp_client_enable_auto_start_mode() - self.simulator.go(5) + self.simulator.go(15) #------------------------------------------------------------------------------------- # Register a single service and verify that it worked. diff --git a/tests/scripts/thread-cert/test_srp_client_save_server_info.py b/tests/scripts/thread-cert/test_srp_client_save_server_info.py index 5018aad08..ca657897d 100755 --- a/tests/scripts/thread-cert/test_srp_client_save_server_info.py +++ b/tests/scripts/thread-cert/test_srp_client_save_server_info.py @@ -48,7 +48,7 @@ SERVER1 = 2 SERVER2 = 3 SERVER3 = 4 -WAIT_TIME = 5 +WAIT_TIME = 16 MAX_ITER = 5 diff --git a/tests/scripts/thread-cert/test_srp_lease.py b/tests/scripts/thread-cert/test_srp_lease.py index b259c21a5..863c91a38 100755 --- a/tests/scripts/thread-cert/test_srp_lease.py +++ b/tests/scripts/thread-cert/test_srp_lease.py @@ -114,7 +114,7 @@ class SrpRegisterSingleService(thread_cert.TestCase): # Start the client again, the same service should be successfully registered. client.srp_client_enable_auto_start_mode() - self.simulator.go(2) + self.simulator.go(15) self.check_host_and_service(server, client) @@ -132,7 +132,7 @@ class SrpRegisterSingleService(thread_cert.TestCase): # Start the client again, the same service should be successfully registered. client.srp_client_enable_auto_start_mode() - self.simulator.go(2) + self.simulator.go(15) self.check_host_and_service(server, client) diff --git a/tests/scripts/thread-cert/test_srp_many_services_mtu_check.py b/tests/scripts/thread-cert/test_srp_many_services_mtu_check.py index 65dfd2e99..37221c5f4 100755 --- a/tests/scripts/thread-cert/test_srp_many_services_mtu_check.py +++ b/tests/scripts/thread-cert/test_srp_many_services_mtu_check.py @@ -84,7 +84,7 @@ class SrpManyServicesMtuCheck(thread_cert.TestCase): server.srp_server_set_enabled(True) client.srp_client_enable_auto_start_mode() - self.simulator.go(5) + self.simulator.go(15) # Register 8 services with long name, 6 sub-types and long txt record. # The 8 services won't be fit in a single MTU (1280 bytes) UDP message diff --git a/tests/scripts/thread-cert/test_srp_register_services_diff_lease.py b/tests/scripts/thread-cert/test_srp_register_services_diff_lease.py index 6247527d8..3399dd310 100755 --- a/tests/scripts/thread-cert/test_srp_register_services_diff_lease.py +++ b/tests/scripts/thread-cert/test_srp_register_services_diff_lease.py @@ -84,7 +84,7 @@ class SrpRegisterServicesDiffLease(thread_cert.TestCase): server.srp_server_set_enabled(True) client.srp_client_enable_auto_start_mode() - self.simulator.go(5) + self.simulator.go(15) client.srp_client_set_host_name('host') client.srp_client_enable_auto_host_address() diff --git a/tests/scripts/thread-cert/test_srp_server_anycast_mode.py b/tests/scripts/thread-cert/test_srp_server_anycast_mode.py index 5086a7baa..7bae2e72b 100755 --- a/tests/scripts/thread-cert/test_srp_server_anycast_mode.py +++ b/tests/scripts/thread-cert/test_srp_server_anycast_mode.py @@ -147,7 +147,7 @@ class TestSrpServerAnycastMode(thread_cert.TestCase): # server and uses the proper address and port number. client.srp_client_enable_auto_start_mode() - self.simulator.go(5) + self.simulator.go(15) if addr_mode == 'anycast': server_alocs = server.get_ip6_address(config.ADDRESS_TYPE.ALOC) diff --git a/tests/scripts/thread-cert/test_srp_server_reboot_port.py b/tests/scripts/thread-cert/test_srp_server_reboot_port.py index b38bcfe9e..d7f0a4c34 100755 --- a/tests/scripts/thread-cert/test_srp_server_reboot_port.py +++ b/tests/scripts/thread-cert/test_srp_server_reboot_port.py @@ -109,7 +109,7 @@ class SrpServerRebootPort(thread_cert.TestCase): client.srp_client_set_host_name('my-host') client.srp_client_set_host_address('2001::1') client.srp_client_add_service('my-service', '_ipps._tcp', 12345, 0, 0, ['abc', 'def=', 'xyz=XYZ']) - self.simulator.go(5) + self.simulator.go(16) self.check_host_and_service(server, client, '2001::1') ports = [server.get_srp_server_port()] @@ -129,7 +129,7 @@ class SrpServerRebootPort(thread_cert.TestCase): # re-registered. # server.srp_server_set_enabled(True) - self.simulator.go(5) + self.simulator.go(16) self.assertEqual(client.srp_client_get_state(), 'Enabled') self.assertEqual(client.srp_client_get_server_address(), server.get_mleid()) self.assertNotEqual(old_port, server.get_srp_server_port()) diff --git a/tests/scripts/thread-cert/test_srp_sub_type.py b/tests/scripts/thread-cert/test_srp_sub_type.py index e9b6fdc4a..bbd8efa17 100755 --- a/tests/scripts/thread-cert/test_srp_sub_type.py +++ b/tests/scripts/thread-cert/test_srp_sub_type.py @@ -80,7 +80,7 @@ class SrpSubType(thread_cert.TestCase): server.srp_server_set_enabled(True) client.srp_client_enable_auto_start_mode() - self.simulator.go(5) + self.simulator.go(15) # Register a single service with 3 subtypes and verify that it worked. diff --git a/tests/scripts/thread-cert/test_srp_ttl.py b/tests/scripts/thread-cert/test_srp_ttl.py index 9c3a149db..70a60721e 100755 --- a/tests/scripts/thread-cert/test_srp_ttl.py +++ b/tests/scripts/thread-cert/test_srp_ttl.py @@ -85,6 +85,7 @@ class SrpTtl(thread_cert.TestCase): self.simulator.go(config.ROUTER_STARTUP_DELAY) self.assertEqual(client.get_state(), 'router') + self.simulator.go(15) self.assertEqual(client.srp_client_get_auto_start_mode(), 'Enabled') client.srp_client_set_host_name('my-host') diff --git a/tests/toranj/cli/test-400-srp-client-server.py b/tests/toranj/cli/test-400-srp-client-server.py index 0e3edb2bc..2a3cd32ab 100755 --- a/tests/toranj/cli/test-400-srp-client-server.py +++ b/tests/toranj/cli/test-400-srp-client-server.py @@ -61,11 +61,12 @@ verify(client.get_state() == 'router') verify(server.srp_server_get_state() == 'disabled') verify(server.srp_server_get_addr_mode() == 'unicast') verify(client.srp_client_get_state() == 'Disabled') -verify(client.srp_client_get_auto_start_mode() == 'Enabled') # Start server and client and register single service server.srp_server_enable() +client.srp_client_enable_auto_start_mode() +verify(client.srp_client_get_auto_start_mode() == 'Enabled') client.srp_client_set_host_name('host') client.srp_client_set_host_address('fd00::cafe') client.srp_client_add_service('ins', '_test._udp', 777, 2, 1) diff --git a/tests/toranj/cli/test-501-multi-br-failure-recovery.py b/tests/toranj/cli/test-501-multi-br-failure-recovery.py index ecf41b999..338c955c5 100755 --- a/tests/toranj/cli/test-501-multi-br-failure-recovery.py +++ b/tests/toranj/cli/test-501-multi-br-failure-recovery.py @@ -147,6 +147,7 @@ verify(br2.srp_server_get_state() == 'disabled') # Register SRP services on all nodes for node in nodes_non_br: + node.srp_client_enable_auto_start_mode() verify(node.srp_client_get_auto_start_mode() == 'Enabled') node.srp_client_set_host_name('host' + str(node.index)) node.srp_client_enable_auto_host_address() diff --git a/tests/toranj/cli/test-502-multi-br-leader-failure-recovery.py b/tests/toranj/cli/test-502-multi-br-leader-failure-recovery.py index 71d2ebc1b..6b6e9dfa9 100755 --- a/tests/toranj/cli/test-502-multi-br-leader-failure-recovery.py +++ b/tests/toranj/cli/test-502-multi-br-leader-failure-recovery.py @@ -147,6 +147,7 @@ verify(br2.srp_server_get_state() == 'disabled') # Register SRP services on all nodes for node in nodes_non_br: + node.srp_client_enable_auto_start_mode() verify(node.srp_client_get_auto_start_mode() == 'Enabled') node.srp_client_set_host_name('host' + str(node.index)) node.srp_client_enable_auto_host_address() diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h index 33686ca7a..9f6b66a8d 100644 --- a/tests/toranj/openthread-core-toranj-config.h +++ b/tests/toranj/openthread-core-toranj-config.h @@ -175,6 +175,8 @@ #define OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE 1 +#define OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE 0 + #define OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE 1 #define OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE 1 diff --git a/tests/unit/test_srp_adv_proxy.cpp b/tests/unit/test_srp_adv_proxy.cpp index 08b4a9470..dec83df65 100644 --- a/tests/unit/test_srp_adv_proxy.cpp +++ b/tests/unit/test_srp_adv_proxy.cpp @@ -940,7 +940,7 @@ void TestSrpAdvProxy(void) sProcessedClientCallback = false; - AdvanceTime(5 * 1000); + AdvanceTime(15 * 1000); // This time we should only see new host registration // since that's the only thing that changes diff --git a/tests/unit/test_srp_server.cpp b/tests/unit/test_srp_server.cpp index f0218e004..2f655f2bf 100644 --- a/tests/unit/test_srp_server.cpp +++ b/tests/unit/test_srp_server.cpp @@ -892,7 +892,7 @@ void TestUpdateLeaseShortVariant(void) srpClient->EnableAutoStartMode(nullptr, nullptr); VerifyOrQuit(srpClient->IsAutoStartModeEnabled()); - AdvanceTime(2000); + AdvanceTime(15 * 1000); VerifyOrQuit(srpClient->IsRunning()); SuccessOrQuit(srpClient->SetHostName(kHostName));