diff --git a/src/core/config/tmf.h b/src/core/config/tmf.h index 37f891b55..e1f54c915 100644 --- a/src/core/config/tmf.h +++ b/src/core/config/tmf.h @@ -45,6 +45,31 @@ #define OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_ENTRIES 10 #endif +/** + * @def OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_MAX_SNOOP_ENTRIES + * + * The maximum number of EID-to-RLOC cache entries that can be used for "snoop optimization" where an entry is created + * by inspecting a received message. + * + */ +#ifndef OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_MAX_SNOOP_ENTRIES +#define OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_MAX_SNOOP_ENTRIES 2 +#endif + +/** + * @def OPENTHREAD_CONFIG_TMF_SNOOP_CACHE_ENTRY_TIMEOUT + * + * The timeout value (in seconds) blocking eviction of an address cache entry created through snoop optimization (i.e., + * inspection of a received message). After the timeout expires the entry can be reclaimed again. This timeout allows + * a longer response delay for a received message giving more chance that a snooped entry will be used (avoiding + * sending Address Query when a response message is sent to same destination from which the message was received + * earlier). + * + */ +#ifndef OPENTHREAD_CONFIG_TMF_SNOOP_CACHE_ENTRY_TIMEOUT +#define OPENTHREAD_CONFIG_TMF_SNOOP_CACHE_ENTRY_TIMEOUT 3 +#endif + /** * @def OPENTHREAD_CONFIG_TMF_ADDRESS_QUERY_TIMEOUT * diff --git a/src/core/thread/address_resolver.cpp b/src/core/thread/address_resolver.cpp index 73c4313a4..215dd520e 100644 --- a/src/core/thread/address_resolver.cpp +++ b/src/core/thread/address_resolver.cpp @@ -57,10 +57,19 @@ AddressResolver::AddressResolver(Instance &aInstance) , mAddressError(OT_URI_PATH_ADDRESS_ERROR, &AddressResolver::HandleAddressError, this) , mAddressQuery(OT_URI_PATH_ADDRESS_QUERY, &AddressResolver::HandleAddressQuery, this) , mAddressNotification(OT_URI_PATH_ADDRESS_NOTIFY, &AddressResolver::HandleAddressNotification, this) + , mCachedList() + , mSnoopedList() + , mQueryList() + , mQueryRetryList() + , mUnusedList() , mIcmpHandler(&AddressResolver::HandleIcmpReceive, this) , mTimer(aInstance, &AddressResolver::HandleTimer, this) { - Init(); + for (CacheEntry *entry = &mCacheEntries[0]; entry < OT_ARRAY_END(mCacheEntries); entry++) + { + entry->Init(GetInstance()); + mUnusedList.Push(*entry); + } Get().AddResource(mAddressError); Get().AddResource(mAddressQuery); @@ -69,41 +78,51 @@ AddressResolver::AddressResolver(Instance &aInstance) Get().RegisterHandler(mIcmpHandler); } -void AddressResolver::Init(void) -{ - memset(&mCache, 0, sizeof(mCache)); - - for (uint8_t i = 0; i < kCacheEntries; i++) - { - mCache[i].mAge = i; - } -} - void AddressResolver::Clear(void) { - for (uint8_t i = 0; i < kCacheEntries; i++) + CacheEntryList *lists[] = {&mCachedList, &mSnoopedList, &mQueryList, &mQueryRetryList}; + + for (uint8_t index = 0; index < OT_ARRAY_LENGTH(lists); index++) { - if (mCache[i].mState != Cache::kStateQuery) + CacheEntryList *list = lists[index]; + CacheEntry * entry; + + while ((entry = list->Pop()) != NULL) { - continue; + if (list == &mQueryList) + { + Get().HandleResolved(entry->GetTarget(), OT_ERROR_DROP); + } + + mUnusedList.Push(*entry); } - - Get().HandleResolved(mCache[i].mTarget, OT_ERROR_DROP); } - - Init(); } otError AddressResolver::GetEntry(uint8_t aIndex, otEidCacheEntry &aEntry) const { - otError error = OT_ERROR_NONE; + otError error = OT_ERROR_NONE; + uint8_t age = aIndex; + const CacheEntryList *lists[] = {&mCachedList, &mSnoopedList}; + const CacheEntry * entry; - VerifyOrExit(aIndex < kCacheEntries, error = OT_ERROR_INVALID_ARGS); + for (uint8_t index = 0; index < OT_ARRAY_LENGTH(lists); index++) + { + for (entry = lists[index]->GetHead(); (aIndex != 0) && (entry != NULL); aIndex--, entry = entry->GetNext()) + ; - aEntry.mTarget = mCache[aIndex].mTarget; - aEntry.mRloc16 = mCache[aIndex].mRloc16; - aEntry.mAge = mCache[aIndex].mAge; - aEntry.mValid = mCache[aIndex].mState == Cache::kStateCached; + if (entry != NULL) + { + break; + } + } + + VerifyOrExit(entry != NULL, error = OT_ERROR_NOT_FOUND); + + aEntry.mTarget = entry->GetTarget(); + aEntry.mRloc16 = entry->GetRloc16(); + aEntry.mAge = age; + aEntry.mValid = true; exit: return error; @@ -111,241 +130,259 @@ exit: void AddressResolver::Remove(uint8_t aRouterId) { - for (int i = 0; i < kCacheEntries; i++) - { - if (Mle::Mle::RouterIdFromRloc16(mCache[i].mRloc16) == aRouterId) - { - InvalidateCacheEntry(mCache[i], kReasonRemovingRouterId); - } - } + Remove(Mle::Mle::Rloc16FromRouterId(aRouterId), /* aMatchRouterId */ true); } void AddressResolver::Remove(uint16_t aRloc16) { - for (int i = 0; i < kCacheEntries; i++) + Remove(aRloc16, /* aMatchRouterId */ false); +} + +void AddressResolver::Remove(Mac::ShortAddress aRloc16, bool aMatchRouterId) +{ + CacheEntryList *lists[] = {&mCachedList, &mSnoopedList}; + + for (uint8_t index = 0; index < OT_ARRAY_LENGTH(lists); index++) { - if (mCache[i].mRloc16 == aRloc16) + CacheEntryList *list = lists[index]; + CacheEntry * prev; + CacheEntry * entry; + CacheEntry * next; + + for (prev = NULL, entry = list->GetHead(); entry != NULL; prev = entry, entry = next) { - InvalidateCacheEntry(mCache[i], kReasonRemovingRloc16); + // We keep track of the next entry in the list before + // checking the entry's RLOC16 or Router Id since the + // entry may be removed from the list if it matches. + + next = entry->GetNext(); + + if ((aMatchRouterId && Mle::Mle::RouterIdMatch(entry->GetRloc16(), aRloc16)) || + (!aMatchRouterId && (entry->GetRloc16() == aRloc16))) + { + RemoveCacheEntry(*entry, *list, prev, aMatchRouterId ? kReasonRemovingRouterId : kReasonRemovingRloc16); + mUnusedList.Push(*entry); + } } } } +AddressResolver::CacheEntry *AddressResolver::FindCacheEntryInList(CacheEntryList & aList, + const Ip6::Address &aEid, + CacheEntry *& aPrevEntry) +{ + CacheEntry *entry; + CacheEntry *prev; + + for (prev = NULL, entry = aList.GetHead(); entry != NULL; prev = entry, entry = entry->GetNext()) + { + if (entry->GetTarget() == aEid) + { + aPrevEntry = prev; + break; + } + } + + return entry; +} + +AddressResolver::CacheEntry *AddressResolver::FindCacheEntry(const Ip6::Address &aEid, + CacheEntryList *& aList, + CacheEntry *& aPrevEntry) +{ + CacheEntry * entry = NULL; + CacheEntryList *lists[] = {&mCachedList, &mSnoopedList, &mQueryList, &mQueryRetryList}; + + for (uint8_t index = 0; index < OT_ARRAY_LENGTH(lists); index++) + { + aList = lists[index]; + entry = FindCacheEntryInList(*aList, aEid, aPrevEntry); + VerifyOrExit(entry == NULL); + } + +exit: + return entry; +} + void AddressResolver::Remove(const Ip6::Address &aEid) { - for (int i = 0; i < kCacheEntries; i++) - { - if (mCache[i].mState == Cache::kStateInvalid || mCache[i].mTarget != aEid) - { - continue; - } - - InvalidateCacheEntry(mCache[i], kReasonRemovingEid); - break; - } + Remove(aEid, kReasonRemovingEid); } -AddressResolver::Cache *AddressResolver::NewCacheEntry(void) +void AddressResolver::Remove(const Ip6::Address &aEid, Reason aReason) { - Cache *rval = NULL; + CacheEntry * entry; + CacheEntry * prev; + CacheEntryList *list; - for (int i = 0; i < kCacheEntries; i++) - { - if (mCache[i].mState == Cache::kStateQuery && mCache[i].mFailures == 0) - { - continue; - } + entry = FindCacheEntry(aEid, list, prev); + VerifyOrExit(entry != NULL); - if (rval == NULL || rval->mAge < mCache[i].mAge) - { - rval = &mCache[i]; - } - } - - if (rval != NULL) - { - InvalidateCacheEntry(*rval, kReasonEvictingForNewEntry); - } - - return rval; -} - -void AddressResolver::MoveCacheEntryAt(Cache &aEntry, uint8_t aAge) -{ - VerifyOrExit(aEntry.mAge != aAge); - - if (aEntry.mAge > aAge) - { - for (int i = 0; i < kCacheEntries; i++) - { - if (mCache[i].mAge >= aAge && mCache[i].mAge < aEntry.mAge) - { - mCache[i].mAge++; - } - } - } - else - { - for (int i = 0; i < kCacheEntries; i++) - { - if (mCache[i].mAge > aEntry.mAge && mCache[i].mAge <= aAge) - { - mCache[i].mAge--; - } - } - } - - aEntry.mAge = aAge; + RemoveCacheEntry(*entry, *list, prev, aReason); + mUnusedList.Push(*entry); exit: return; } -void AddressResolver::MoveCacheEntryBehindCached(Cache &aEntry) +AddressResolver::CacheEntry *AddressResolver::NewCacheEntry(bool aSnoopedEntry) { - bool found = false; - uint8_t lastInCachedState = 0; + CacheEntry * newEntry = NULL; + CacheEntry * prevEntry = NULL; + CacheEntryList *lists[] = {&mSnoopedList, &mQueryRetryList, &mQueryList, &mCachedList}; - for (int i = 0; i < kCacheEntries; i++) + // The following order is used when trying to allocate a new cache + // entry: First the unused list is checked, followed by the list + // of snooped entries, then query-retry list (entries in delay + // retry timeout wait due to a prior query failing to get a + // response), then the query list (entries actively querying and + // waiting for address notification response), and finally the + // cached (in-use) list. Within each list the oldest entry is + // reclaimed first (the list's tail). We also make sure the entry + // can be evicted (e.g., first time query entries can not be + // evicted till timeout). + + newEntry = mUnusedList.Pop(); + VerifyOrExit(newEntry == NULL); + + for (uint8_t index = 0; index < OT_ARRAY_LENGTH(lists); index++) { - if (mCache[i].mState != Cache::kStateCached || mCache[i].mAge == aEntry.mAge) + CacheEntryList *list = lists[index]; + CacheEntry * prev; + CacheEntry * entry; + uint16_t numNonEvictable = 0; + + for (prev = NULL, entry = list->GetHead(); entry != NULL; prev = entry, entry = entry->GetNext()) { - continue; + if ((list != &mCachedList) && !entry->CanEvict()) + { + numNonEvictable++; + continue; + } + + newEntry = entry; + prevEntry = prev; } - if (mCache[i].mAge >= lastInCachedState) + if (newEntry != NULL) { - found = true; - lastInCachedState = mCache[i].mAge; + RemoveCacheEntry(*newEntry, *list, prevEntry, kReasonEvictingForNewEntry); + ExitNow(); + } + + if (aSnoopedEntry && (list == &mSnoopedList)) + { + // Check if the new entry is being requested for "snoop + // optimization" (i.e., inspection of a received message). + // When a new snooped entry is added, we do not allow it + // to be evicted for a short timeout. This allows some + // delay for a response message to use the entry (if entry + // is used it will be moved to the cached list). If a + // snooped entry is not used after the timeout, we allow + // it to be evicted. To ensure snooped entries do not + // overwrite other cached entries, we limit the number of + // snooped entries that are in timeout mode and cannot be + // evicted by `kMaxNonEvictableSnoopedEntries`. + + VerifyOrExit(numNonEvictable < kMaxNonEvictableSnoopedEntries); } } - if (!found) - { - MoveCacheEntryAt(aEntry, 0); - } - else - { - uint8_t newAge = lastInCachedState + 1; - - if (newAge == kCacheEntries) - { - newAge = kCacheEntries - 1; - } - - MoveCacheEntryAt(aEntry, newAge); - } +exit: + return newEntry; } -const char *AddressResolver::InvalidationReasonToString(InvalidationReason aReason) -{ - const char *str = ""; - - switch (aReason) - { - case kReasonRemovingRouterId: - str = "removing router id"; - break; - - case kReasonRemovingRloc16: - str = "removing rloc16"; - break; - - case kReasonReceivedIcmpDstUnreachNoRoute: - str = "received icmp no route"; - break; - - case kReasonEvictingForNewEntry: - str = "evicting for new entry"; - break; - - case kReasonRemovingEid: - str = "removing eid"; - break; - } - - return str; -} - -void AddressResolver::InvalidateCacheEntry(Cache &aEntry, InvalidationReason aReason) +void AddressResolver::RemoveCacheEntry(CacheEntry & aEntry, + CacheEntryList &aList, + CacheEntry * aPrevEntry, + Reason aReason) { OT_UNUSED_VARIABLE(aReason); - switch (aEntry.mState) + aList.PopAfter(aPrevEntry); + + if (&aList == &mQueryList) { - case Cache::kStateCached: - otLogNoteArp("Cache entry removed: %s, 0x%04x - %s", aEntry.mTarget.ToString().AsCString(), aEntry.mRloc16, - InvalidationReasonToString(aReason)); - break; - - case Cache::kStateQuery: - otLogNoteArp("Cache entry (query mode) removed: %s, timeout:%d, retry:%d - %s", - aEntry.mTarget.ToString().AsCString(), aEntry.mTimeout, aEntry.mRetryTimeout, - InvalidationReasonToString(aReason)); - - Get().HandleResolved(aEntry.mTarget, OT_ERROR_DROP); - break; - - default: - break; + Get().HandleResolved(aEntry.GetTarget(), OT_ERROR_DROP); } - aEntry.mState = Cache::kStateInvalid; - MoveCacheEntryAtBack(aEntry); + otLogNoteArp("Cache entry removed: %s, 0x%04x (%s) - %s", aEntry.GetTarget().ToString().AsCString(), + aEntry.GetRloc16(), ListToString(aList), ReasonToString(aReason)); } otError AddressResolver::UpdateCacheEntry(const Ip6::Address &aEid, Mac::ShortAddress aRloc16) { - otError error = OT_ERROR_NOT_FOUND; + otError error = OT_ERROR_NONE; + CacheEntryList *list; + CacheEntry * entry; + CacheEntry * prev; - for (int i = 0; i < kCacheEntries; i++) + entry = FindCacheEntry(aEid, list, prev); + VerifyOrExit(entry != NULL, error = OT_ERROR_NOT_FOUND); + + if ((list == &mCachedList) || (list == &mSnoopedList)) { - if (mCache[i].mState == Cache::kStateInvalid || mCache[i].mTarget != aEid) - { - continue; - } + VerifyOrExit(entry->GetRloc16() != aRloc16); + entry->SetRloc16(aRloc16); + } + else + { + // Entry is in `mQueryList` or `mQueryRetryList`. Remove it + // from its current list, update it, and then add it to the + // `mCachedList`. - if (mCache[i].mRloc16 != aRloc16) - { - // not updating the age here is intentional because this cache entry is not actually being used - mCache[i].mRloc16 = aRloc16; + list->PopAfter(prev); - if (mCache[i].mState != Cache::kStateCached) - { - mCache[i].mLastTransactionTime = static_cast(kLastTransactionTimeInvalid); - mCache[i].mRetryTimeout = 0; - mCache[i].mTimeout = 0; - mCache[i].mFailures = 0; - mCache[i].mState = Cache::kStateCached; - MoveCacheEntryAtFront(mCache[i]); + entry->SetRloc16(aRloc16); + entry->MarkLastTransactionTimeAsInvalid(); + mCachedList.Push(*entry); - Get().HandleResolved(aEid, OT_ERROR_NONE); - } - - otLogNoteArp("Cache entry updated (snoop): %s, 0x%04x", aEid.ToString().AsCString(), aRloc16); - } - - error = OT_ERROR_NONE; + Get().HandleResolved(aEid, OT_ERROR_NONE); } + otLogNoteArp("Cache entry updated (snoop): %s, 0x%04x", aEid.ToString().AsCString(), aRloc16); + +exit: return error; } -otError AddressResolver::AddCacheEntry(const Ip6::Address &aEid, Mac::ShortAddress aRloc16) +otError AddressResolver::AddSnoopedCacheEntry(const Ip6::Address &aEid, Mac::ShortAddress aRloc16) { - otError error = OT_ERROR_NONE; - Cache * entry = NewCacheEntry(); + otError error = OT_ERROR_NONE; + uint16_t numNonEvictable = 0; + CacheEntry *entry; + entry = NewCacheEntry(/* aSnoopedEntry */ true); VerifyOrExit(entry != NULL, error = OT_ERROR_NO_BUFS); - entry->mTarget = aEid; - entry->mLastTransactionTime = static_cast(kLastTransactionTimeInvalid); - entry->mRloc16 = aRloc16; - entry->mRetryTimeout = 0; - entry->mTimeout = 0; - entry->mFailures = 0; - entry->mState = Cache::kStateCached; - MoveCacheEntryBehindCached(*entry); + for (CacheEntry *snooped = mSnoopedList.GetHead(); snooped != NULL; snooped = snooped->GetNext()) + { + if (!snooped->CanEvict()) + { + numNonEvictable++; + } + } + + entry->SetTarget(aEid); + entry->SetRloc16(aRloc16); + + if (numNonEvictable < kMaxNonEvictableSnoopedEntries) + { + entry->SetCanEvict(false); + entry->SetTimeout(kSnoopBlockEvictionTimeout); + + if (!mTimer.IsRunning()) + { + mTimer.Start(kStateUpdatePeriod); + } + } + else + { + entry->SetCanEvict(true); + entry->SetTimeout(0); + } + + mSnoopedList.Push(*entry); otLogNoteArp("Cache entry added (snoop): %s, 0x%04x", aEid.ToString().AsCString(), aRloc16); @@ -355,84 +392,101 @@ exit: void AddressResolver::RestartAddressQueries(void) { - for (int i = 0; i < kCacheEntries; i++) + CacheEntry *tail; + + // We move all entries from `mQueryRetryList` at the tail of + // `mQueryList` and then (re)send Address Query for all entries in + // the updated `mQueryList`. + + tail = mQueryList.GetTail(); + + if (tail == NULL) { - Cache &entry = mCache[i]; + mQueryList.SetHead(mQueryRetryList.GetHead()); + } + else + { + tail->SetNext(mQueryRetryList.GetHead()); + } - if (entry.mState != Cache::kStateQuery) - { - continue; - } + mQueryRetryList.Clear(); - SendAddressQuery(entry.mTarget); + for (CacheEntry *entry = mQueryList.GetHead(); entry != NULL; entry = entry->GetNext()) + { + SendAddressQuery(entry->GetTarget()); - entry.mTimeout = kAddressQueryTimeout; - entry.mFailures = 0; - entry.mRetryTimeout = kAddressQueryInitialRetryDelay; + entry->SetTimeout(kAddressQueryTimeout); + entry->SetRetryDelay(kAddressQueryInitialRetryDelay); + entry->SetCanEvict(false); } } otError AddressResolver::Resolve(const Ip6::Address &aEid, uint16_t &aRloc16) { - otError error = OT_ERROR_NONE; - Cache * entry = NULL; + otError error = OT_ERROR_NONE; + CacheEntry * entry; + CacheEntry * prev; + CacheEntryList *list; - for (int i = 0; i < kCacheEntries; i++) - { - if (mCache[i].mState != Cache::kStateInvalid) - { - if (mCache[i].mTarget == aEid) - { - entry = &mCache[i]; - break; - } - } - } + entry = FindCacheEntry(aEid, list, prev); if (entry == NULL) { - entry = NewCacheEntry(); + // If the entry is not present in any of the lists, try to + // allocate a new entry and perform address query. We do not + // allow first-time address query entries to be evicted till + // timeout. + + entry = NewCacheEntry(/* aSnoopedEntry */ false); + VerifyOrExit(entry != NULL, error = OT_ERROR_NO_BUFS); + + entry->SetTarget(aEid); + entry->SetRloc16(Mac::kShortAddrInvalid); + entry->SetRetryDelay(kAddressQueryInitialRetryDelay); + entry->SetCanEvict(false); + list = NULL; } - VerifyOrExit(entry != NULL, error = OT_ERROR_NO_BUFS); - - switch (entry->mState) + if ((list == &mCachedList) || (list == &mSnoopedList)) { - case Cache::kStateInvalid: - SuccessOrExit(error = SendAddressQuery(aEid)); - entry->mTarget = aEid; - entry->mRloc16 = Mac::kShortAddrInvalid; - entry->mTimeout = kAddressQueryTimeout; - entry->mFailures = 0; - entry->mRetryTimeout = kAddressQueryInitialRetryDelay; - entry->mState = Cache::kStateQuery; - error = OT_ERROR_ADDRESS_QUERY; - break; + // Remove the entry from its current list and push it at the + // head of cached list. - case Cache::kStateQuery: - if (entry->mTimeout > 0) + list->PopAfter(prev); + + if (list == &mSnoopedList) { - error = OT_ERROR_ADDRESS_QUERY; - } - else if (entry->mTimeout == 0 && entry->mRetryTimeout == 0) - { - SuccessOrExit(error = SendAddressQuery(aEid)); - entry->mTimeout = kAddressQueryTimeout; - error = OT_ERROR_ADDRESS_QUERY; - } - else - { - error = OT_ERROR_DROP; + entry->MarkLastTransactionTimeAsInvalid(); } - break; - - case Cache::kStateCached: - aRloc16 = entry->mRloc16; - MoveCacheEntryAtFront(*entry); - break; + mCachedList.Push(*entry); + aRloc16 = entry->GetRloc16(); + ExitNow(); } + if (list == &mQueryList) + { + ExitNow(error = OT_ERROR_ADDRESS_QUERY); + } + + 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. + + VerifyOrExit(entry->IsTimeoutZero(), error = OT_ERROR_DROP); + mQueryRetryList.PopAfter(prev); + } + + entry->SetTimeout(kAddressQueryTimeout); + + error = SendAddressQuery(aEid); + VerifyOrExit(error == OT_ERROR_NONE, mUnusedList.Push(*entry)); + + mQueryList.Push(*entry); + error = OT_ERROR_ADDRESS_QUERY; + exit: return error; } @@ -483,10 +537,13 @@ void AddressResolver::HandleAddressNotification(void *aContext, otMessage *aMess void AddressResolver::HandleAddressNotification(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { - Ip6::Address target; - uint8_t meshLocalIid[Ip6::Address::kInterfaceIdentifierSize]; - uint16_t rloc16; - uint32_t lastTransactionTime; + Ip6::Address target; + uint8_t meshLocalIid[Ip6::Address::kInterfaceIdentifierSize]; + uint16_t rloc16; + uint32_t lastTransactionTime; + CacheEntryList *list; + CacheEntry * entry; + CacheEntry * prev; VerifyOrExit(aMessage.IsConfirmable() && aMessage.GetCode() == OT_COAP_CODE_POST); @@ -508,58 +565,41 @@ void AddressResolver::HandleAddressNotification(Coap::Message &aMessage, const I otLogInfoArp("Received address notification from 0x%04x for %s to 0x%04x", aMessageInfo.GetPeerAddr().GetLocator(), target.ToString().AsCString(), rloc16); - for (int i = 0; i < kCacheEntries; i++) + entry = FindCacheEntry(target, list, prev); + VerifyOrExit(entry != NULL); + + if (list == &mCachedList) { - if (mCache[i].mTarget != target) + if (entry->IsLastTransactionTimeValid()) { - continue; - } + // Receiving multiple Address Notification for an EID from + // different mesh-local IIDs indicates address is in use + // by more than one device. Try to resolve the duplicate + // address by sending an Address Error message. - switch (mCache[i].mState) - { - case Cache::kStateInvalid: - break; + VerifyOrExit(entry->HasMeshLocalIid(meshLocalIid), SendAddressError(target, meshLocalIid, NULL)); - case Cache::kStateCached: - if (mCache[i].mLastTransactionTime != kLastTransactionTimeInvalid) - { - if (memcmp(mCache[i].mMeshLocalIid, meshLocalIid, sizeof(mCache[i].mMeshLocalIid)) != 0) - { - SendAddressError(target, meshLocalIid, NULL); - ExitNow(); - } - - if (lastTransactionTime >= mCache[i].mLastTransactionTime) - { - ExitNow(); - } - } - - // fall through - - case Cache::kStateQuery: - memcpy(mCache[i].mMeshLocalIid, meshLocalIid, sizeof(mCache[i].mMeshLocalIid)); - mCache[i].mRloc16 = rloc16; - mCache[i].mRetryTimeout = 0; - mCache[i].mLastTransactionTime = lastTransactionTime; - mCache[i].mTimeout = 0; - mCache[i].mFailures = 0; - mCache[i].mState = Cache::kStateCached; - MoveCacheEntryAtFront(mCache[i]); - - otLogNoteArp("Cache entry updated (notification): %s, 0x%04x, lastTrans:%d", target.ToString().AsCString(), - rloc16, lastTransactionTime); - - if (Get().SendEmptyAck(aMessage, aMessageInfo) == OT_ERROR_NONE) - { - otLogInfoArp("Sending address notification acknowledgment"); - } - - Get().HandleResolved(target, OT_ERROR_NONE); - break; + VerifyOrExit(lastTransactionTime < entry->GetLastTransactionTime()); } } + entry->SetRloc16(rloc16); + entry->SetMeshLocalIid(meshLocalIid); + entry->SetLastTransactionTime(lastTransactionTime); + + list->PopAfter(prev); + mCachedList.Push(*entry); + + otLogNoteArp("Cache entry updated (notification): %s, 0x%04x, lastTrans:%d", target.ToString().AsCString(), rloc16, + lastTransactionTime); + + if (Get().SendEmptyAck(aMessage, aMessageInfo) == OT_ERROR_NONE) + { + otLogInfoArp("Sending address notification acknowledgment"); + } + + Get().HandleResolved(target, OT_ERROR_NONE); + exit: return; } @@ -786,44 +826,71 @@ void AddressResolver::HandleTimer(Timer &aTimer) void AddressResolver::HandleTimer(void) { - bool continueTimer = false; + bool continueTimer = false; + CacheEntry *prev; + CacheEntry *entry; + CacheEntry *next; - for (int i = 0; i < kCacheEntries; i++) + for (entry = mSnoopedList.GetHead(); entry != NULL; entry = entry->GetNext()) { - if (mCache[i].mState != Cache::kStateQuery) + if (entry->IsTimeoutZero()) { continue; } continueTimer = true; + entry->DecrementTimeout(); - if (mCache[i].mTimeout > 0) + if (entry->IsTimeoutZero()) { - mCache[i].mTimeout--; - - if (mCache[i].mTimeout == 0) - { - mCache[i].mRetryTimeout = - static_cast(kAddressQueryInitialRetryDelay * (1 << mCache[i].mFailures)); - - if (mCache[i].mRetryTimeout < kAddressQueryMaxRetryDelay) - { - mCache[i].mFailures++; - } - else - { - mCache[i].mRetryTimeout = kAddressQueryMaxRetryDelay; - } - - otLogInfoArp("Timed out waiting for address notification for %s, retry: %d", - mCache[i].mTarget.ToString().AsCString(), mCache[i].mRetryTimeout); - - Get().HandleResolved(mCache[i].mTarget, OT_ERROR_DROP); - } + entry->SetCanEvict(true); } - else if (mCache[i].mRetryTimeout > 0) + } + + for (entry = mQueryRetryList.GetHead(); entry != NULL; entry = entry->GetNext()) + { + if (entry->IsTimeoutZero()) { - mCache[i].mRetryTimeout--; + continue; + } + + continueTimer = true; + entry->DecrementTimeout(); + } + + for (prev = NULL, entry = mQueryList.GetHead(); entry != NULL; entry = next) + { + next = entry->GetNext(); + + OT_ASSERT(!entry->IsTimeoutZero()); + + continueTimer = true; + entry->DecrementTimeout(); + + if (entry->IsTimeoutZero()) + { + uint16_t retryDelay = entry->GetRetryDelay(); + + entry->SetTimeout(retryDelay); + + retryDelay <<= 1; + + if (retryDelay > kAddressQueryMaxRetryDelay) + { + retryDelay = kAddressQueryMaxRetryDelay; + } + + entry->SetRetryDelay(retryDelay); + entry->SetCanEvict(true); + + // Move the entry from `mQueryList` to `mQueryRetryList` + mQueryList.PopAfter(prev); + mQueryRetryList.Push(*entry); + + otLogInfoArp("Timed out waiting for address notification for %s, retry: %d", + entry->GetTarget().ToString().AsCString(), entry->GetTimeout()); + + Get().HandleResolved(entry->GetTarget(), OT_ERROR_DROP); } } @@ -857,19 +924,98 @@ void AddressResolver::HandleIcmpReceive(Message & aMessage, VerifyOrExit(aIcmpHeader.GetCode() == Ip6::IcmpHeader::kCodeDstUnreachNoRoute); VerifyOrExit(aMessage.Read(aMessage.GetOffset(), sizeof(ip6Header), &ip6Header) == sizeof(ip6Header)); - for (int i = 0; i < kCacheEntries; i++) - { - if (mCache[i].mState != Cache::kStateInvalid && mCache[i].mTarget == ip6Header.GetDestination()) - { - InvalidateCacheEntry(mCache[i], kReasonReceivedIcmpDstUnreachNoRoute); - break; - } - } + Remove(ip6Header.GetDestination(), kReasonReceivedIcmpDstUnreachNoRoute); exit: return; } +// LCOV_EXCL_START + +const char *AddressResolver::ReasonToString(Reason aReason) +{ + const char *str = ""; + + switch (aReason) + { + case kReasonRemovingRouterId: + str = "removing router id"; + break; + + case kReasonRemovingRloc16: + str = "removing rloc16"; + break; + + case kReasonReceivedIcmpDstUnreachNoRoute: + str = "received icmp no route"; + break; + + case kReasonEvictingForNewEntry: + str = "evicting for new entry"; + break; + + case kReasonRemovingEid: + str = "removing eid"; + break; + } + + return str; +} + +const char *AddressResolver::ListToString(const CacheEntryList &aList) const +{ + const char *str; + + VerifyOrExit(&aList != &mCachedList, str = "cached"); + VerifyOrExit(&aList != &mSnoopedList, str = "snooped"); + VerifyOrExit(&aList != &mQueryList, str = "query"); + VerifyOrExit(&aList != &mQueryRetryList, str = "query-retry"); + str = "unused"; + +exit: + return str; +} + +// LCOV_EXCL_STOP + +//--------------------------------------------------------------------------------------------------------------------- +// AddressResolver::CacheEntry + +void AddressResolver::CacheEntry::Init(Instance &aInstance) +{ + InstanceLocatorInit::Init(aInstance); + mNextIndex = kNoNextIndex; +} + +AddressResolver::CacheEntry *AddressResolver::CacheEntry::GetNext(void) +{ + return (mNextIndex == kNoNextIndex) ? NULL : &Get().mCacheEntries[mNextIndex]; +} + +const AddressResolver::CacheEntry *AddressResolver::CacheEntry::GetNext(void) const +{ + return (mNextIndex == kNoNextIndex) ? NULL : &Get().mCacheEntries[mNextIndex]; +} + +void AddressResolver::CacheEntry::SetNext(CacheEntry *aEntry) +{ + VerifyOrExit(aEntry != NULL, mNextIndex = kNoNextIndex); + mNextIndex = static_cast(aEntry - Get().mCacheEntries); + +exit: + return; +} + +bool AddressResolver::CacheEntry::HasMeshLocalIid(const uint8_t *aIid) const +{ + return memcmp(mInfo.mCached.mMeshLocalIid, aIid, Ip6::Address::kInterfaceIdentifierSize) == 0; +} + +void AddressResolver::CacheEntry::SetMeshLocalIid(const uint8_t *aIid) +{ + memcpy(mInfo.mCached.mMeshLocalIid, aIid, Ip6::Address::kInterfaceIdentifierSize); +} + } // namespace ot #endif // OPENTHREAD_FTD diff --git a/src/core/thread/address_resolver.hpp b/src/core/thread/address_resolver.hpp index 7325f5d96..76e43f230 100644 --- a/src/core/thread/address_resolver.hpp +++ b/src/core/thread/address_resolver.hpp @@ -37,6 +37,7 @@ #include "openthread-core-config.h" #include "coap/coap.hpp" +#include "common/linked_list.hpp" #include "common/locator.hpp" #include "common/timer.hpp" #include "mac/mac.hpp" @@ -92,7 +93,7 @@ public: * @param[in] aRloc16 The RLOC16 address. * */ - void Remove(uint16_t aRloc16); + void Remove(Mac::ShortAddress aRloc16); /** * This method removes all EID-to-RLOC cache entries associated with a Router ID. @@ -123,16 +124,19 @@ public: otError UpdateCacheEntry(const Ip6::Address &aEid, Mac::ShortAddress aRloc16); /** - * This method adds one cache entry for the EID. + * This method adds a snooped cache entry for a given EID. + * + * The method is intended to add an entry for snoop optimization (inspection of a received message to create a + * cache entry mapping an EID to a RLOC). * * @param[in] aEid A reference to the EID. * @param[in] aRloc16 The RLOC16 corresponding to @p aEid. * - * @retval OT_ERROR_NONE Successfully adds one cache entry. + * @retval OT_ERROR_NONE Successfully adds the cache entry. * @retval OT_ERROR_NO_BUFS Insufficient buffer space available to add one cache entry. * */ - otError AddCacheEntry(const Ip6::Address &aEid, Mac::ShortAddress aRloc16); + otError AddSnoopedCacheEntry(const Ip6::Address &aEid, Mac::ShortAddress aRloc16); /** * This method returns the RLOC16 for a given EID, or initiates an Address Query if the mapping is not known. @@ -159,47 +163,81 @@ public: private: enum { - kCacheEntries = OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_ENTRIES, - kStateUpdatePeriod = 1000u, ///< State update period in milliseconds. - }; - - /** - * Thread Protocol Parameters and Constants - * - */ - enum - { + kCacheEntries = OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_ENTRIES, + kMaxNonEvictableSnoopedEntries = OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_MAX_SNOOP_ENTRIES, kAddressQueryTimeout = OPENTHREAD_CONFIG_TMF_ADDRESS_QUERY_TIMEOUT, // in seconds kAddressQueryInitialRetryDelay = OPENTHREAD_CONFIG_TMF_ADDRESS_QUERY_INITIAL_RETRY_DELAY, // in seconds kAddressQueryMaxRetryDelay = OPENTHREAD_CONFIG_TMF_ADDRESS_QUERY_MAX_RETRY_DELAY, // in seconds + kSnoopBlockEvictionTimeout = OPENTHREAD_CONFIG_TMF_SNOOP_CACHE_ENTRY_TIMEOUT, // in seconds + kStateUpdatePeriod = 1000u, // in milliseconds }; - enum + class CacheEntry : public InstanceLocatorInit { - kLastTransactionTimeInvalid = 0xffffffff, ///< Used when entry is populated using forwarded data message. - }; + public: + void Init(Instance &aInstance); - struct Cache - { - enum State + CacheEntry * GetNext(void); + const CacheEntry *GetNext(void) const; + void SetNext(CacheEntry *aEntry); + + const Ip6::Address &GetTarget(void) const { return mTarget; } + void SetTarget(const Ip6::Address &aTarget) { mTarget = aTarget; } + + Mac::ShortAddress GetRloc16(void) const { return mRloc16; } + void SetRloc16(Mac::ShortAddress aRloc16) { mRloc16 = aRloc16; } + + const uint8_t *GetMeshLocalIid(void) const { return mInfo.mCached.mMeshLocalIid; } + bool HasMeshLocalIid(const uint8_t *aIid) const; + void SetMeshLocalIid(const uint8_t *aIid); + + uint32_t GetLastTransactionTime(void) const { return mInfo.mCached.mLastTransactionTime; } + void SetLastTransactionTime(uint32_t aTime) { mInfo.mCached.mLastTransactionTime = aTime; } + bool IsLastTransactionTimeValid(void) const { return GetLastTransactionTime() != kInvalidLastTransTime; } + void MarkLastTransactionTimeAsInvalid(void) { SetLastTransactionTime(kInvalidLastTransTime); } + + void DecrementTimeout(void) { mInfo.mOther.mTimeout--; } + bool IsTimeoutZero(void) const { return mInfo.mOther.mTimeout == 0; } + uint16_t GetTimeout(void) const { return mInfo.mOther.mTimeout; } + void SetTimeout(uint16_t aTimeout) { mInfo.mOther.mTimeout = aTimeout; } + + uint16_t GetRetryDelay(void) const { return mInfo.mOther.mRetryDelay; } + void SetRetryDelay(uint16_t aDelay) { mInfo.mOther.mRetryDelay = aDelay; } + + bool CanEvict(void) const { return mInfo.mOther.mCanEvict; } + void SetCanEvict(bool aCanEvict) { mInfo.mOther.mCanEvict = aCanEvict; } + + private: + enum { - kStateInvalid, - kStateQuery, - kStateCached, + kNoNextIndex = 0xffff, // mNextIndex value when at end of list. + kInvalidLastTransTime = 0xffffffff, // Value indicating mLastTransactionTime is invalid. }; Ip6::Address mTarget; - uint8_t mMeshLocalIid[Ip6::Address::kInterfaceIdentifierSize]; - uint32_t mLastTransactionTime; Mac::ShortAddress mRloc16; - uint16_t mRetryTimeout; - uint8_t mTimeout; - uint8_t mFailures; - uint8_t mAge; - State mState; + uint16_t mNextIndex; + union + { + struct + { + uint32_t mLastTransactionTime; + uint8_t mMeshLocalIid[Ip6::Address::kInterfaceIdentifierSize]; + } mCached; + + struct + { + uint16_t mTimeout; + uint16_t mRetryDelay; + bool mCanEvict; + } mOther; + + } mInfo; }; - enum InvalidationReason + typedef LinkedList CacheEntryList; + + enum Reason { kReasonRemovingRouterId, kReasonRemovingRloc16, @@ -208,17 +246,12 @@ private: kReasonRemovingEid, }; - static const char *InvalidationReasonToString(InvalidationReason aReason); - - void Init(void); - Cache *NewCacheEntry(void); - - void MoveCacheEntryAt(Cache &aEntry, uint8_t aAge); - void MoveCacheEntryAtFront(Cache &aEntry) { MoveCacheEntryAt(aEntry, 0); } - void MoveCacheEntryAtBack(Cache &aEntry) { MoveCacheEntryAt(aEntry, kCacheEntries - 1); } - void MoveCacheEntryBehindCached(Cache &aEntry); - - void InvalidateCacheEntry(Cache &aEntry, InvalidationReason aReason); + void Remove(Mac::ShortAddress aRloc16, bool aMatchRouterId); + void Remove(const Ip6::Address &aEid, Reason aReason); + CacheEntry *FindCacheEntryInList(CacheEntryList &aList, const Ip6::Address &aEid, CacheEntry *&aPrevEntry); + CacheEntry *FindCacheEntry(const Ip6::Address &aEid, CacheEntryList *&aList, CacheEntry *&aPrevEntry); + CacheEntry *NewCacheEntry(bool aSnoopedEntry); + void RemoveCacheEntry(CacheEntry &aEntry, CacheEntryList &aList, CacheEntry *aPrevEntry, Reason aReason); otError SendAddressQuery(const Ip6::Address &aEid); otError SendAddressError(const Ip6::Address &aTarget, @@ -249,10 +282,20 @@ private: static void HandleTimer(Timer &aTimer); void HandleTimer(void); - Coap::Resource mAddressError; - Coap::Resource mAddressQuery; - Coap::Resource mAddressNotification; - Cache mCache[kCacheEntries]; + const char * ListToString(const CacheEntryList &aList) const; + static const char *ReasonToString(Reason aReason); + + Coap::Resource mAddressError; + Coap::Resource mAddressQuery; + Coap::Resource mAddressNotification; + + CacheEntry mCacheEntries[kCacheEntries]; + CacheEntryList mCachedList; + CacheEntryList mSnoopedList; + CacheEntryList mQueryList; + CacheEntryList mQueryRetryList; + CacheEntryList mUnusedList; + Ip6::IcmpHandler mIcmpHandler; TimerMilli mTimer; }; diff --git a/src/core/thread/mesh_forwarder_ftd.cpp b/src/core/thread/mesh_forwarder_ftd.cpp index c9ef5672a..60f86bb9f 100644 --- a/src/core/thread/mesh_forwarder_ftd.cpp +++ b/src/core/thread/mesh_forwarder_ftd.cpp @@ -613,7 +613,7 @@ void MeshForwarder::UpdateRoutes(uint8_t * aFrame, (aMeshDest.GetShort() == Get().GetShortAddress() || Get().IsMinimalChild(aMeshDest.GetShort()))) { - Get().AddCacheEntry(ip6Header.GetSource(), aMeshSource.GetShort()); + Get().AddSnoopedCacheEntry(ip6Header.GetSource(), aMeshSource.GetShort()); } } }