| /* |
| * Copyright (c) 2016, 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. |
| */ |
| |
| /** |
| * @file |
| * This file implements Thread's EID-to-RLOC mapping and caching. |
| */ |
| |
| #if OPENTHREAD_FTD |
| |
| #include "address_resolver.hpp" |
| |
| #include "coap/coap_message.hpp" |
| #include "common/code_utils.hpp" |
| #include "common/debug.hpp" |
| #include "common/encoding.hpp" |
| #include "common/instance.hpp" |
| #include "common/locator-getters.hpp" |
| #include "common/logging.hpp" |
| #include "mac/mac_types.hpp" |
| #include "thread/mesh_forwarder.hpp" |
| #include "thread/mle_router.hpp" |
| #include "thread/thread_netif.hpp" |
| #include "thread/thread_uri_paths.hpp" |
| |
| using ot::Encoding::BigEndian::HostSwap16; |
| |
| namespace ot { |
| |
| AddressResolver::AddressResolver(Instance &aInstance) |
| : InstanceLocator(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) |
| { |
| for (CacheEntry *entry = &mCacheEntries[0]; entry < OT_ARRAY_END(mCacheEntries); entry++) |
| { |
| entry->Init(GetInstance()); |
| mUnusedList.Push(*entry); |
| } |
| |
| Get<Coap::Coap>().AddResource(mAddressError); |
| Get<Coap::Coap>().AddResource(mAddressQuery); |
| Get<Coap::Coap>().AddResource(mAddressNotification); |
| |
| IgnoreError(Get<Ip6::Icmp>().RegisterHandler(mIcmpHandler)); |
| } |
| |
| void AddressResolver::Clear(void) |
| { |
| CacheEntryList *lists[] = {&mCachedList, &mSnoopedList, &mQueryList, &mQueryRetryList}; |
| |
| for (size_t index = 0; index < OT_ARRAY_LENGTH(lists); index++) |
| { |
| CacheEntryList *list = lists[index]; |
| CacheEntry * entry; |
| |
| while ((entry = list->Pop()) != nullptr) |
| { |
| if (list == &mQueryList) |
| { |
| Get<MeshForwarder>().HandleResolved(entry->GetTarget(), OT_ERROR_DROP); |
| } |
| |
| mUnusedList.Push(*entry); |
| } |
| } |
| } |
| |
| otError AddressResolver::GetNextCacheEntry(EntryInfo &aInfo, Iterator &aIterator) const |
| { |
| otError error = OT_ERROR_NONE; |
| const CacheEntryList *list; |
| const CacheEntry * entry; |
| |
| list = reinterpret_cast<const CacheEntryList *>(aIterator.mData[kIteratorListIndex]); |
| entry = reinterpret_cast<const CacheEntry *>(aIterator.mData[kIteratorEntryIndex]); |
| |
| while (entry == nullptr) |
| { |
| if (list == nullptr) |
| { |
| list = &mCachedList; |
| } |
| else if (list == &mCachedList) |
| { |
| list = &mSnoopedList; |
| } |
| else if (list == &mSnoopedList) |
| { |
| list = &mQueryList; |
| } |
| else if (list == &mQueryList) |
| { |
| list = &mQueryRetryList; |
| } |
| else |
| { |
| ExitNow(error = OT_ERROR_NOT_FOUND); |
| } |
| |
| entry = list->GetHead(); |
| } |
| |
| // Update the iterator then populate the `aInfo`. |
| |
| aIterator.mData[kIteratorEntryIndex] = entry->GetNext(); |
| aIterator.mData[kIteratorListIndex] = list; |
| |
| memset(&aInfo, 0, sizeof(aInfo)); |
| aInfo.mTarget = entry->GetTarget(); |
| aInfo.mRloc16 = entry->GetRloc16(); |
| |
| if (list == &mCachedList) |
| { |
| aInfo.mState = OT_CACHE_ENTRY_STATE_CACHED; |
| aInfo.mCanEvict = true; |
| aInfo.mValidLastTrans = entry->IsLastTransactionTimeValid(); |
| |
| VerifyOrExit(entry->IsLastTransactionTimeValid(), OT_NOOP); |
| |
| aInfo.mLastTransTime = entry->GetLastTransactionTime(); |
| static_cast<Ip6::Address &>(aInfo.mMeshLocalEid).SetPrefix(Get<Mle::MleRouter>().GetMeshLocalPrefix()); |
| static_cast<Ip6::Address &>(aInfo.mMeshLocalEid).SetIid(entry->GetMeshLocalIid()); |
| |
| ExitNow(); |
| } |
| |
| if (list == &mSnoopedList) |
| { |
| aInfo.mState = OT_CACHE_ENTRY_STATE_SNOOPED; |
| } |
| else if (list == &mQueryList) |
| { |
| aInfo.mState = OT_CACHE_ENTRY_STATE_QUERY; |
| } |
| else |
| { |
| aInfo.mState = OT_CACHE_ENTRY_STATE_RETRY_QUERY; |
| } |
| |
| aInfo.mCanEvict = entry->CanEvict(); |
| aInfo.mTimeout = entry->GetTimeout(); |
| aInfo.mRetryDelay = entry->GetRetryDelay(); |
| |
| exit: |
| return error; |
| } |
| |
| void AddressResolver::Remove(uint8_t aRouterId) |
| { |
| Remove(Mle::Mle::Rloc16FromRouterId(aRouterId), /* aMatchRouterId */ true); |
| } |
| |
| void AddressResolver::Remove(uint16_t aRloc16) |
| { |
| Remove(aRloc16, /* aMatchRouterId */ false); |
| } |
| |
| AddressResolver::CacheEntry *AddressResolver::GetEntryAfter(CacheEntry *aPrev, CacheEntryList &aList) |
| { |
| return (aPrev == nullptr) ? aList.GetHead() : aPrev->GetNext(); |
| } |
| |
| void AddressResolver::Remove(Mac::ShortAddress aRloc16, bool aMatchRouterId) |
| { |
| CacheEntryList *lists[] = {&mCachedList, &mSnoopedList}; |
| |
| for (size_t index = 0; index < OT_ARRAY_LENGTH(lists); index++) |
| { |
| CacheEntryList *list = lists[index]; |
| CacheEntry * prev = nullptr; |
| CacheEntry * entry; |
| |
| while ((entry = GetEntryAfter(prev, *list)) != nullptr) |
| { |
| if ((aMatchRouterId && Mle::Mle::RouterIdMatch(entry->GetRloc16(), aRloc16)) || |
| (!aMatchRouterId && (entry->GetRloc16() == aRloc16))) |
| { |
| RemoveCacheEntry(*entry, *list, prev, aMatchRouterId ? kReasonRemovingRouterId : kReasonRemovingRloc16); |
| mUnusedList.Push(*entry); |
| |
| // If the entry is removed from list, we keep the same |
| // `prev` pointer. |
| } |
| else |
| { |
| prev = entry; |
| } |
| } |
| } |
| } |
| |
| AddressResolver::CacheEntry *AddressResolver::FindCacheEntry(const Ip6::Address &aEid, |
| CacheEntryList *& aList, |
| CacheEntry *& aPrevEntry) |
| { |
| CacheEntry * entry = nullptr; |
| CacheEntryList *lists[] = {&mCachedList, &mSnoopedList, &mQueryList, &mQueryRetryList}; |
| |
| for (size_t index = 0; index < OT_ARRAY_LENGTH(lists); index++) |
| { |
| aList = lists[index]; |
| entry = aList->FindMatching(aEid, aPrevEntry); |
| VerifyOrExit(entry == nullptr, OT_NOOP); |
| } |
| |
| exit: |
| return entry; |
| } |
| |
| void AddressResolver::Remove(const Ip6::Address &aEid) |
| { |
| Remove(aEid, kReasonRemovingEid); |
| } |
| |
| void AddressResolver::Remove(const Ip6::Address &aEid, Reason aReason) |
| { |
| CacheEntry * entry; |
| CacheEntry * prev; |
| CacheEntryList *list; |
| |
| entry = FindCacheEntry(aEid, list, prev); |
| VerifyOrExit(entry != nullptr, OT_NOOP); |
| |
| RemoveCacheEntry(*entry, *list, prev, aReason); |
| mUnusedList.Push(*entry); |
| |
| exit: |
| return; |
| } |
| |
| AddressResolver::CacheEntry *AddressResolver::NewCacheEntry(bool aSnoopedEntry) |
| { |
| CacheEntry * newEntry = nullptr; |
| CacheEntry * prevEntry = nullptr; |
| CacheEntryList *lists[] = {&mSnoopedList, &mQueryRetryList, &mQueryList, &mCachedList}; |
| |
| // 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 == nullptr, OT_NOOP); |
| |
| for (size_t index = 0; index < OT_ARRAY_LENGTH(lists); index++) |
| { |
| CacheEntryList *list = lists[index]; |
| CacheEntry * prev; |
| CacheEntry * entry; |
| uint16_t numNonEvictable = 0; |
| |
| for (prev = nullptr; (entry = GetEntryAfter(prev, *list)) != nullptr; prev = entry) |
| { |
| if ((list != &mCachedList) && !entry->CanEvict()) |
| { |
| numNonEvictable++; |
| continue; |
| } |
| |
| newEntry = entry; |
| prevEntry = prev; |
| } |
| |
| if (newEntry != nullptr) |
| { |
| 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, OT_NOOP); |
| } |
| } |
| |
| exit: |
| return newEntry; |
| } |
| |
| void AddressResolver::RemoveCacheEntry(CacheEntry & aEntry, |
| CacheEntryList &aList, |
| CacheEntry * aPrevEntry, |
| Reason aReason) |
| { |
| aList.PopAfter(aPrevEntry); |
| |
| if (&aList == &mQueryList) |
| { |
| Get<MeshForwarder>().HandleResolved(aEntry.GetTarget(), OT_ERROR_DROP); |
| } |
| |
| LogCacheEntryChange(kEntryRemoved, aReason, aEntry, &aList); |
| } |
| |
| otError AddressResolver::UpdateCacheEntry(const Ip6::Address &aEid, Mac::ShortAddress aRloc16) |
| { |
| otError error = OT_ERROR_NONE; |
| CacheEntryList *list; |
| CacheEntry * entry; |
| CacheEntry * prev; |
| |
| entry = FindCacheEntry(aEid, list, prev); |
| VerifyOrExit(entry != nullptr, error = OT_ERROR_NOT_FOUND); |
| |
| if ((list == &mCachedList) || (list == &mSnoopedList)) |
| { |
| VerifyOrExit(entry->GetRloc16() != aRloc16, OT_NOOP); |
| 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`. |
| |
| list->PopAfter(prev); |
| |
| entry->SetRloc16(aRloc16); |
| entry->MarkLastTransactionTimeAsInvalid(); |
| mCachedList.Push(*entry); |
| |
| Get<MeshForwarder>().HandleResolved(aEid, OT_ERROR_NONE); |
| } |
| |
| LogCacheEntryChange(kEntryUpdated, kReasonSnoop, *entry); |
| |
| exit: |
| return error; |
| } |
| |
| void AddressResolver::AddSnoopedCacheEntry(const Ip6::Address &aEid, Mac::ShortAddress aRloc16) |
| { |
| uint16_t numNonEvictable = 0; |
| CacheEntry *entry; |
| |
| entry = NewCacheEntry(/* aSnoopedEntry */ true); |
| VerifyOrExit(entry != nullptr, OT_NOOP); |
| |
| for (CacheEntry *snooped = mSnoopedList.GetHead(); snooped != nullptr; 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); |
| |
| LogCacheEntryChange(kEntryAdded, kReasonSnoop, *entry); |
| |
| exit: |
| return; |
| } |
| |
| void AddressResolver::RestartAddressQueries(void) |
| { |
| 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 == nullptr) |
| { |
| mQueryList.SetHead(mQueryRetryList.GetHead()); |
| } |
| else |
| { |
| tail->SetNext(mQueryRetryList.GetHead()); |
| } |
| |
| mQueryRetryList.Clear(); |
| |
| for (CacheEntry *entry = mQueryList.GetHead(); entry != nullptr; entry = entry->GetNext()) |
| { |
| IgnoreError(SendAddressQuery(entry->GetTarget())); |
| |
| entry->SetTimeout(kAddressQueryTimeout); |
| entry->SetRetryDelay(kAddressQueryInitialRetryDelay); |
| entry->SetCanEvict(false); |
| } |
| } |
| |
| otError AddressResolver::Resolve(const Ip6::Address &aEid, uint16_t &aRloc16) |
| { |
| otError error = OT_ERROR_NONE; |
| CacheEntry * entry; |
| CacheEntry * prev = nullptr; |
| CacheEntryList *list; |
| |
| entry = FindCacheEntry(aEid, list, prev); |
| |
| if (entry == nullptr) |
| { |
| // 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 != nullptr, error = OT_ERROR_NO_BUFS); |
| |
| entry->SetTarget(aEid); |
| entry->SetRloc16(Mac::kShortAddrInvalid); |
| entry->SetRetryDelay(kAddressQueryInitialRetryDelay); |
| entry->SetCanEvict(false); |
| list = nullptr; |
| } |
| |
| if ((list == &mCachedList) || (list == &mSnoopedList)) |
| { |
| // Remove the entry from its current list and push it at the |
| // head of cached list. |
| |
| list->PopAfter(prev); |
| |
| if (list == &mSnoopedList) |
| { |
| entry->MarkLastTransactionTimeAsInvalid(); |
| } |
| |
| 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)); |
| |
| if (list == nullptr) |
| { |
| LogCacheEntryChange(kEntryAdded, kReasonQueryRequest, *entry); |
| } |
| |
| mQueryList.Push(*entry); |
| error = OT_ERROR_ADDRESS_QUERY; |
| |
| exit: |
| return error; |
| } |
| |
| otError AddressResolver::SendAddressQuery(const Ip6::Address &aEid) |
| { |
| otError error; |
| Coap::Message * message; |
| Ip6::MessageInfo messageInfo; |
| |
| VerifyOrExit((message = Get<Coap::Coap>().NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS); |
| |
| message->Init(OT_COAP_TYPE_NON_CONFIRMABLE, OT_COAP_CODE_POST); |
| SuccessOrExit(error = message->AppendUriPathOptions(OT_URI_PATH_ADDRESS_QUERY)); |
| SuccessOrExit(error = message->SetPayloadMarker()); |
| |
| SuccessOrExit(error = Tlv::AppendTlv(*message, ThreadTlv::kTarget, &aEid, sizeof(aEid))); |
| |
| messageInfo.GetPeerAddr().SetToRealmLocalAllRoutersMulticast(); |
| |
| messageInfo.SetSockAddr(Get<Mle::MleRouter>().GetMeshLocal16()); |
| messageInfo.SetPeerPort(kCoapUdpPort); |
| |
| SuccessOrExit(error = Get<Coap::Coap>().SendMessage(*message, messageInfo)); |
| |
| otLogInfoArp("Sending address query for %s", aEid.ToString().AsCString()); |
| |
| exit: |
| |
| if (!mTimer.IsRunning()) |
| { |
| mTimer.Start(kStateUpdatePeriod); |
| } |
| |
| if (error != OT_ERROR_NONE && message != nullptr) |
| { |
| message->Free(); |
| } |
| |
| return error; |
| } |
| |
| void AddressResolver::HandleAddressNotification(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) |
| { |
| static_cast<AddressResolver *>(aContext)->HandleAddressNotification( |
| *static_cast<Coap::Message *>(aMessage), *static_cast<const Ip6::MessageInfo *>(aMessageInfo)); |
| } |
| |
| 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; |
| CacheEntryList *list; |
| CacheEntry * entry; |
| CacheEntry * prev; |
| |
| VerifyOrExit(aMessage.IsConfirmable() && aMessage.GetCode() == OT_COAP_CODE_POST, OT_NOOP); |
| |
| SuccessOrExit(Tlv::FindTlv(aMessage, ThreadTlv::kTarget, &target, sizeof(target))); |
| SuccessOrExit(Tlv::FindTlv(aMessage, ThreadTlv::kMeshLocalEid, meshLocalIid, sizeof(meshLocalIid))); |
| SuccessOrExit(Tlv::FindUint16Tlv(aMessage, ThreadTlv::kRloc16, rloc16)); |
| |
| switch (Tlv::FindUint32Tlv(aMessage, ThreadTlv::kLastTransactionTime, lastTransactionTime)) |
| { |
| case OT_ERROR_NONE: |
| break; |
| case OT_ERROR_NOT_FOUND: |
| lastTransactionTime = 0; |
| break; |
| default: |
| ExitNow(); |
| } |
| |
| otLogInfoArp("Received address notification from 0x%04x for %s to 0x%04x", aMessageInfo.GetPeerAddr().GetLocator(), |
| target.ToString().AsCString(), rloc16); |
| |
| entry = FindCacheEntry(target, list, prev); |
| VerifyOrExit(entry != nullptr, OT_NOOP); |
| |
| if (list == &mCachedList) |
| { |
| if (entry->IsLastTransactionTimeValid()) |
| { |
| // 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. |
| |
| VerifyOrExit(entry->HasMeshLocalIid(meshLocalIid), SendAddressError(target, meshLocalIid, nullptr)); |
| |
| VerifyOrExit(lastTransactionTime < entry->GetLastTransactionTime(), OT_NOOP); |
| } |
| } |
| |
| entry->SetRloc16(rloc16); |
| entry->SetMeshLocalIid(meshLocalIid); |
| entry->SetLastTransactionTime(lastTransactionTime); |
| |
| list->PopAfter(prev); |
| mCachedList.Push(*entry); |
| |
| LogCacheEntryChange(kEntryUpdated, kReasonReceivedNotification, *entry); |
| |
| if (Get<Coap::Coap>().SendEmptyAck(aMessage, aMessageInfo) == OT_ERROR_NONE) |
| { |
| otLogInfoArp("Sending address notification acknowledgment"); |
| } |
| |
| Get<MeshForwarder>().HandleResolved(target, OT_ERROR_NONE); |
| |
| exit: |
| return; |
| } |
| |
| void AddressResolver::SendAddressError(const Ip6::Address &aTarget, |
| const uint8_t * aMeshLocalIid, |
| const Ip6::Address *aDestination) |
| { |
| otError error; |
| Coap::Message * message; |
| Ip6::MessageInfo messageInfo; |
| |
| VerifyOrExit((message = Get<Coap::Coap>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS); |
| |
| message->Init(aDestination == nullptr ? OT_COAP_TYPE_NON_CONFIRMABLE : OT_COAP_TYPE_CONFIRMABLE, OT_COAP_CODE_POST); |
| SuccessOrExit(error = message->AppendUriPathOptions(OT_URI_PATH_ADDRESS_ERROR)); |
| SuccessOrExit(error = message->SetPayloadMarker()); |
| |
| SuccessOrExit(error = Tlv::AppendTlv(*message, ThreadTlv::kTarget, &aTarget, sizeof(aTarget))); |
| SuccessOrExit(error = Tlv::AppendTlv(*message, ThreadTlv::kMeshLocalEid, aMeshLocalIid, |
| Ip6::Address::kInterfaceIdentifierSize)); |
| |
| if (aDestination == nullptr) |
| { |
| messageInfo.GetPeerAddr().SetToRealmLocalAllRoutersMulticast(); |
| } |
| else |
| { |
| messageInfo.SetPeerAddr(*aDestination); |
| } |
| |
| messageInfo.SetSockAddr(Get<Mle::MleRouter>().GetMeshLocal16()); |
| messageInfo.SetPeerPort(kCoapUdpPort); |
| |
| SuccessOrExit(error = Get<Coap::Coap>().SendMessage(*message, messageInfo)); |
| |
| otLogInfoArp("Sending address error for target %s", aTarget.ToString().AsCString()); |
| |
| exit: |
| |
| if (error != OT_ERROR_NONE) |
| { |
| otLogInfoArp("Failed to send address error: %s", otThreadErrorToString(error)); |
| |
| if (message != nullptr) |
| { |
| message->Free(); |
| } |
| } |
| } |
| |
| void AddressResolver::HandleAddressError(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) |
| { |
| static_cast<AddressResolver *>(aContext)->HandleAddressError(*static_cast<Coap::Message *>(aMessage), |
| *static_cast<const Ip6::MessageInfo *>(aMessageInfo)); |
| } |
| |
| void AddressResolver::HandleAddressError(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) |
| { |
| otError error = OT_ERROR_NONE; |
| Ip6::Address target; |
| uint8_t meshLocalIid[Ip6::Address::kInterfaceIdentifierSize]; |
| Mac::ExtAddress macAddr; |
| Ip6::Address destination; |
| |
| VerifyOrExit(aMessage.GetCode() == OT_COAP_CODE_POST, error = OT_ERROR_DROP); |
| |
| otLogInfoArp("Received address error notification"); |
| |
| if (aMessage.IsConfirmable() && !aMessageInfo.GetSockAddr().IsMulticast()) |
| { |
| if (Get<Coap::Coap>().SendEmptyAck(aMessage, aMessageInfo) == OT_ERROR_NONE) |
| { |
| otLogInfoArp("Sent address error notification acknowledgment"); |
| } |
| } |
| |
| SuccessOrExit(error = Tlv::FindTlv(aMessage, ThreadTlv::kTarget, &target, sizeof(target))); |
| SuccessOrExit(error = Tlv::FindTlv(aMessage, ThreadTlv::kMeshLocalEid, meshLocalIid, sizeof(meshLocalIid))); |
| |
| for (const Ip6::NetifUnicastAddress *address = Get<ThreadNetif>().GetUnicastAddresses(); address; |
| address = address->GetNext()) |
| { |
| if (address->GetAddress() == target && |
| memcmp(Get<Mle::MleRouter>().GetMeshLocal64().GetIid(), meshLocalIid, sizeof(meshLocalIid))) |
| { |
| // Target EID matches address and Mesh Local EID differs |
| Get<ThreadNetif>().RemoveUnicastAddress(*address); |
| ExitNow(); |
| } |
| } |
| |
| macAddr.Set(meshLocalIid); |
| macAddr.ToggleLocal(); |
| |
| for (ChildTable::Iterator iter(GetInstance(), Child::kInStateValid); !iter.IsDone(); iter++) |
| { |
| Child &child = *iter.GetChild(); |
| |
| if (child.IsFullThreadDevice()) |
| { |
| continue; |
| } |
| |
| if (child.GetExtAddress() != macAddr) |
| { |
| // Mesh Local EID differs, so check whether Target EID |
| // matches a child address and if so remove it. |
| |
| if (child.RemoveIp6Address(target) == OT_ERROR_NONE) |
| { |
| SuccessOrExit(error = Get<Mle::Mle>().GetLocatorAddress(destination, child.GetRloc16())); |
| |
| SendAddressError(target, meshLocalIid, &destination); |
| ExitNow(); |
| } |
| } |
| } |
| |
| exit: |
| |
| if (error != OT_ERROR_NONE) |
| { |
| otLogWarnArp("Error while processing address error notification: %s", otThreadErrorToString(error)); |
| } |
| } |
| |
| void AddressResolver::HandleAddressQuery(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) |
| { |
| static_cast<AddressResolver *>(aContext)->HandleAddressQuery(*static_cast<Coap::Message *>(aMessage), |
| *static_cast<const Ip6::MessageInfo *>(aMessageInfo)); |
| } |
| |
| void AddressResolver::HandleAddressQuery(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) |
| { |
| Ip6::Address target; |
| uint32_t lastTransactionTime; |
| |
| VerifyOrExit(aMessage.IsNonConfirmable() && aMessage.GetCode() == OT_COAP_CODE_POST, OT_NOOP); |
| |
| SuccessOrExit(Tlv::FindTlv(aMessage, ThreadTlv::kTarget, &target, sizeof(target))); |
| |
| otLogInfoArp("Received address query from 0x%04x for target %s", aMessageInfo.GetPeerAddr().GetLocator(), |
| target.ToString().AsCString()); |
| |
| if (Get<ThreadNetif>().HasUnicastAddress(target)) |
| { |
| SendAddressQueryResponse(target, Get<Mle::MleRouter>().GetMeshLocal64().GetIid(), nullptr, |
| aMessageInfo.GetPeerAddr()); |
| ExitNow(); |
| } |
| |
| for (ChildTable::Iterator iter(GetInstance(), Child::kInStateValid); !iter.IsDone(); iter++) |
| { |
| Child &child = *iter.GetChild(); |
| |
| if (child.IsFullThreadDevice() || child.GetLinkFailures() >= Mle::kFailedChildTransmissions) |
| { |
| continue; |
| } |
| |
| if (child.HasIp6Address(target)) |
| { |
| lastTransactionTime = TimerMilli::GetNow() - child.GetLastHeard(); |
| SendAddressQueryResponse(target, child.GetMeshLocalIid(), &lastTransactionTime, aMessageInfo.GetPeerAddr()); |
| ExitNow(); |
| } |
| } |
| |
| exit: |
| return; |
| } |
| |
| void AddressResolver::SendAddressQueryResponse(const Ip6::Address &aTarget, |
| const uint8_t * aMeshLocalIid, |
| const uint32_t * aLastTransactionTime, |
| const Ip6::Address &aDestination) |
| { |
| otError error; |
| Coap::Message * message; |
| Ip6::MessageInfo messageInfo; |
| |
| VerifyOrExit((message = Get<Coap::Coap>().NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS); |
| |
| message->Init(OT_COAP_TYPE_CONFIRMABLE, OT_COAP_CODE_POST); |
| SuccessOrExit(error = message->AppendUriPathOptions(OT_URI_PATH_ADDRESS_NOTIFY)); |
| SuccessOrExit(error = message->SetPayloadMarker()); |
| |
| SuccessOrExit(error = Tlv::AppendTlv(*message, ThreadTlv::kTarget, &aTarget, sizeof(aTarget))); |
| SuccessOrExit(error = Tlv::AppendTlv(*message, ThreadTlv::kMeshLocalEid, aMeshLocalIid, |
| Ip6::Address::kInterfaceIdentifierSize)); |
| SuccessOrExit(error = Tlv::AppendUint16Tlv(*message, ThreadTlv::kRloc16, Get<Mle::MleRouter>().GetRloc16())); |
| |
| if (aLastTransactionTime != nullptr) |
| { |
| SuccessOrExit(error = Tlv::AppendUint32Tlv(*message, ThreadTlv::kLastTransactionTime, *aLastTransactionTime)); |
| } |
| |
| messageInfo.SetPeerAddr(aDestination); |
| messageInfo.SetSockAddr(Get<Mle::MleRouter>().GetMeshLocal16()); |
| messageInfo.SetPeerPort(kCoapUdpPort); |
| |
| SuccessOrExit(error = Get<Coap::Coap>().SendMessage(*message, messageInfo)); |
| |
| otLogInfoArp("Sending address notification for target %s", aTarget.ToString().AsCString()); |
| |
| exit: |
| |
| if (error != OT_ERROR_NONE && message != nullptr) |
| { |
| message->Free(); |
| } |
| } |
| |
| void AddressResolver::HandleTimer(Timer &aTimer) |
| { |
| aTimer.GetOwner<AddressResolver>().HandleTimer(); |
| } |
| |
| void AddressResolver::HandleTimer(void) |
| { |
| bool continueTimer = false; |
| CacheEntry *prev; |
| CacheEntry *entry; |
| |
| for (entry = mSnoopedList.GetHead(); entry != nullptr; entry = entry->GetNext()) |
| { |
| if (entry->IsTimeoutZero()) |
| { |
| continue; |
| } |
| |
| continueTimer = true; |
| entry->DecrementTimeout(); |
| |
| if (entry->IsTimeoutZero()) |
| { |
| entry->SetCanEvict(true); |
| } |
| } |
| |
| for (entry = mQueryRetryList.GetHead(); entry != nullptr; entry = entry->GetNext()) |
| { |
| if (entry->IsTimeoutZero()) |
| { |
| continue; |
| } |
| |
| continueTimer = true; |
| entry->DecrementTimeout(); |
| } |
| |
| prev = nullptr; |
| |
| while ((entry = GetEntryAfter(prev, mQueryList)) != nullptr) |
| { |
| 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<MeshForwarder>().HandleResolved(entry->GetTarget(), OT_ERROR_DROP); |
| |
| // When the entry is removed from `mQueryList` |
| // we keep the `prev` pointer same as before. |
| } |
| else |
| { |
| prev = entry; |
| } |
| } |
| |
| if (continueTimer) |
| { |
| mTimer.Start(kStateUpdatePeriod); |
| } |
| } |
| |
| void AddressResolver::HandleIcmpReceive(void * aContext, |
| otMessage * aMessage, |
| const otMessageInfo *aMessageInfo, |
| const otIcmp6Header *aIcmpHeader) |
| { |
| OT_UNUSED_VARIABLE(aMessageInfo); |
| |
| static_cast<AddressResolver *>(aContext)->HandleIcmpReceive(*static_cast<Message *>(aMessage), |
| *static_cast<const Ip6::MessageInfo *>(aMessageInfo), |
| *static_cast<const Ip6::IcmpHeader *>(aIcmpHeader)); |
| } |
| |
| void AddressResolver::HandleIcmpReceive(Message & aMessage, |
| const Ip6::MessageInfo &aMessageInfo, |
| const Ip6::IcmpHeader & aIcmpHeader) |
| { |
| OT_UNUSED_VARIABLE(aMessageInfo); |
| |
| Ip6::Header ip6Header; |
| |
| VerifyOrExit(aIcmpHeader.GetType() == Ip6::IcmpHeader::kTypeDstUnreach, OT_NOOP); |
| VerifyOrExit(aIcmpHeader.GetCode() == Ip6::IcmpHeader::kCodeDstUnreachNoRoute, OT_NOOP); |
| VerifyOrExit(aMessage.Read(aMessage.GetOffset(), sizeof(ip6Header), &ip6Header) == sizeof(ip6Header), OT_NOOP); |
| |
| Remove(ip6Header.GetDestination(), kReasonReceivedIcmpDstUnreachNoRoute); |
| |
| exit: |
| return; |
| } |
| |
| // LCOV_EXCL_START |
| |
| #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_ARP == 1) |
| |
| void AddressResolver::LogCacheEntryChange(EntryChange aChange, |
| Reason aReason, |
| const CacheEntry &aEntry, |
| CacheEntryList * aList) |
| { |
| const char *change = ""; |
| const char *reason = ""; |
| |
| switch (aChange) |
| { |
| case kEntryAdded: |
| change = "added"; |
| break; |
| case kEntryUpdated: |
| change = "updated"; |
| break; |
| case kEntryRemoved: |
| change = "removed"; |
| break; |
| } |
| |
| switch (aReason) |
| { |
| case kReasonQueryRequest: |
| reason = "query request"; |
| break; |
| case kReasonSnoop: |
| reason = "snoop"; |
| break; |
| case kReasonReceivedNotification: |
| reason = "rx notification"; |
| break; |
| case kReasonRemovingRouterId: |
| reason = "removing router id"; |
| break; |
| case kReasonRemovingRloc16: |
| reason = "removing rloc16"; |
| break; |
| case kReasonReceivedIcmpDstUnreachNoRoute: |
| reason = "rx icmp no route"; |
| break; |
| case kReasonEvictingForNewEntry: |
| reason = "evicting for new entry"; |
| break; |
| case kReasonRemovingEid: |
| reason = "removing eid"; |
| break; |
| } |
| |
| otLogNoteArp("Cache entry %s: %s, 0x%04x%s%s - %s", change, aEntry.GetTarget().ToString().AsCString(), |
| aEntry.GetRloc16(), (aList == nullptr) ? "" : ", list:", ListToString(aList), reason); |
| } |
| |
| 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"); |
| |
| exit: |
| return str; |
| } |
| |
| #else // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_ARP == 1) |
| |
| void AddressResolver::LogCacheEntryChange(EntryChange, Reason, const CacheEntry &, CacheEntryList *) |
| { |
| } |
| |
| #endif // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_ARP == 1) |
| |
| // 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) ? nullptr : &Get<AddressResolver>().mCacheEntries[mNextIndex]; |
| } |
| |
| const AddressResolver::CacheEntry *AddressResolver::CacheEntry::GetNext(void) const |
| { |
| return (mNextIndex == kNoNextIndex) ? nullptr : &Get<AddressResolver>().mCacheEntries[mNextIndex]; |
| } |
| |
| void AddressResolver::CacheEntry::SetNext(CacheEntry *aEntry) |
| { |
| VerifyOrExit(aEntry != nullptr, mNextIndex = kNoNextIndex); |
| mNextIndex = static_cast<uint16_t>(aEntry - Get<AddressResolver>().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 |