diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 15e4b7a23..1e08a2ad1 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 (337) +#define OPENTHREAD_API_VERSION (338) /** * @addtogroup api-instance diff --git a/include/openthread/thread_ftd.h b/include/openthread/thread_ftd.h index 3776e1d7d..24154e45d 100644 --- a/include/openthread/thread_ftd.h +++ b/include/openthread/thread_ftd.h @@ -104,6 +104,7 @@ typedef struct otCacheEntryInfo otShortAddress mRloc16; ///< RLOC16 otCacheEntryState mState; ///< Entry state bool mCanEvict : 1; ///< Indicates whether the entry can be evicted. + bool mRampDown : 1; ///< Whether in ramp-down mode while in `OT_CACHE_ENTRY_STATE_RETRY_QUERY`. bool mValidLastTrans : 1; ///< Indicates whether last transaction time and ML-EID are valid. uint32_t mLastTransTime; ///< Last transaction time (applicable in cached state). otIp6Address mMeshLocalEid; ///< Mesh Local EID (applicable if entry in cached state). diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 6c0d6bab5..2e7316ef3 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -2580,7 +2580,7 @@ void Interpreter::OutputEidCacheEntry(const otCacheEntryInfo &aEntry) if (aEntry.mState == OT_CACHE_ENTRY_STATE_RETRY_QUERY) { - OutputFormat(" retryDelay=%u", aEntry.mRetryDelay); + OutputFormat(" retryDelay=%u rampDown=%d", aEntry.mRetryDelay, aEntry.mRampDown); } OutputNewLine(); diff --git a/src/core/thread/address_resolver.cpp b/src/core/thread/address_resolver.cpp index 3fac74606..49a4ea840 100644 --- a/src/core/thread/address_resolver.cpp +++ b/src/core/thread/address_resolver.cpp @@ -152,7 +152,8 @@ Error AddressResolver::GetNextCacheEntry(EntryInfo &aInfo, Iterator &aIterator) } else { - aInfo.mState = MapEnum(EntryInfo::kStateRetryQuery); + aInfo.mState = MapEnum(EntryInfo::kStateRetryQuery); + aInfo.mRampDown = entry->IsInRampDown(); } aInfo.mCanEvict = entry->CanEvict(); @@ -542,10 +543,10 @@ Error AddressResolver::Resolve(const Ip6::Address &aEid, Mac::ShortAddress &aRlo if (list == &mQueryRetryList) { // Allow an entry in query-retry mode to resend an Address - // Query again only if the timeout (retry delay interval) is - // expired. + // Query again only if it is in ramp down mode, i.e., the + // retry delay timeout is expired. - VerifyOrExit(entry->IsTimeoutZero(), error = kErrorDrop); + VerifyOrExit(entry->IsInRampDown(), error = kErrorDrop); mQueryRetryList.PopAfter(prev); } @@ -939,6 +940,33 @@ void AddressResolver::HandleTimeTick(void) continueRxingTicks = true; entry.DecrementTimeout(); + + if (entry.IsTimeoutZero()) + { + if (!entry.IsInRampDown()) + { + entry.SetRampDown(true); + entry.SetTimeout(kAddressQueryMaxRetryDelay); + + LogInfo("Starting ramp down of %s retry-delay:%u", entry.GetTarget().ToString().AsCString(), + entry.GetTimeout()); + } + else + { + uint16_t retryDelay = entry.GetRetryDelay(); + + retryDelay >>= 1; + retryDelay = Max(retryDelay, kAddressQueryInitialRetryDelay); + + if (retryDelay != entry.GetRetryDelay()) + { + entry.SetRetryDelay(retryDelay); + entry.SetTimeout(kAddressQueryMaxRetryDelay); + + LogInfo("Ramping down %s retry-delay:%u", entry.GetTarget().ToString().AsCString(), retryDelay); + } + } + } } { @@ -963,6 +991,7 @@ void AddressResolver::HandleTimeTick(void) entry->SetRetryDelay(retryDelay); entry->SetCanEvict(true); + entry->SetRampDown(false); // Move the entry from `mQueryList` to `mQueryRetryList` mQueryList.PopAfter(prev); diff --git a/src/core/thread/address_resolver.hpp b/src/core/thread/address_resolver.hpp index 171fa1447..c01d3cc1a 100644 --- a/src/core/thread/address_resolver.hpp +++ b/src/core/thread/address_resolver.hpp @@ -285,6 +285,9 @@ private: bool CanEvict(void) const { return mInfo.mOther.mCanEvict; } void SetCanEvict(bool aCanEvict) { mInfo.mOther.mCanEvict = aCanEvict; } + bool IsInRampDown(void) const { return mInfo.mOther.mRampDown; } + void SetRampDown(bool aRampDown) { mInfo.mOther.mRampDown = aRampDown; } + bool Matches(const Ip6::Address &aEid) const { return GetTarget() == aEid; } private: @@ -308,6 +311,7 @@ private: uint16_t mTimeout; uint16_t mRetryDelay; bool mCanEvict; + bool mRampDown; } mOther; } mInfo; diff --git a/tests/toranj/cli/test-014-address-resolver.py b/tests/toranj/cli/test-014-address-resolver.py index 4203129fa..f3d20cc07 100755 --- a/tests/toranj/cli/test-014-address-resolver.py +++ b/tests/toranj/cli/test-014-address-resolver.py @@ -201,17 +201,18 @@ verify_within(check_cache_entry_switch_to_retry_state, 20) # Now wait for all entries to reach zero timeout. -def check_cache_entry_in_retry_state_to_get_to_zero_timeout(): +def check_cache_entry_in_retry_state_to_enter_rampdown(): cache_table = r1.get_eidcache() for entry in cache_table: fields = entry.strip().split(' ') verify(fields[2] == 'retry') verify(fields[3] == 'canEvict=1') verify(fields[4].startswith('timeout=')) - verify(int(fields[4].split('=')[1]) == 0) + verify(fields[5].startswith('retryDelay=')) + verify(fields[6] == 'rampDown=1') -verify_within(check_cache_entry_in_retry_state_to_get_to_zero_timeout, 20) +verify_within(check_cache_entry_in_retry_state_to_enter_rampdown, 20) # Now send again to the same addresses. @@ -231,6 +232,37 @@ def check_cache_entry_switch_to_query_state(): verify_within(check_cache_entry_switch_to_query_state, 20) + +def check_cache_entry_switch_to_retry_state_with_double_retry_delay(): + cache_table = r1.get_eidcache() + for entry in cache_table: + fields = entry.strip().split(' ') + verify(fields[2] == 'retry') + verify(fields[3] == 'canEvict=1') + verify(fields[4].startswith('timeout=')) + verify(fields[5].startswith('retryDelay=')) + verify(int(fields[5].split('=')[1]) == 2 * initial_retry_delay) + + +verify_within(check_cache_entry_switch_to_retry_state_with_double_retry_delay, 40) + +verify_within(check_cache_entry_in_retry_state_to_enter_rampdown, 40) + + +def check_cache_entry_ramp_down_to_initial_retry_delay(): + cache_table = r1.get_eidcache() + for entry in cache_table: + fields = entry.strip().split(' ') + verify(fields[2] == 'retry') + verify(fields[3] == 'canEvict=1') + verify(fields[4].startswith('timeout=')) + verify(fields[5].startswith('retryDelay=')) + verify(int(fields[5].split('=')[1]) == initial_retry_delay) + verify(fields[6] == 'rampDown=1') + + +verify_within(check_cache_entry_ramp_down_to_initial_retry_delay, 60) + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Verify snoop optimization behavior.