1 /*
2  *  Copyright (c) 2016, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /**
30  * @file
31  *   This file implements Thread's EID-to-RLOC mapping and caching.
32  */
33 
34 #include "address_resolver.hpp"
35 
36 #include "coap/coap_message.hpp"
37 #include "common/as_core_type.hpp"
38 #include "common/code_utils.hpp"
39 #include "common/debug.hpp"
40 #include "common/encoding.hpp"
41 #include "common/locator_getters.hpp"
42 #include "common/log.hpp"
43 #include "common/time.hpp"
44 #include "instance/instance.hpp"
45 #include "mac/mac_types.hpp"
46 #include "thread/mesh_forwarder.hpp"
47 #include "thread/mle_router.hpp"
48 #include "thread/thread_netif.hpp"
49 #include "thread/uri_paths.hpp"
50 
51 namespace ot {
52 
53 RegisterLogModule("AddrResolver");
54 
AddressResolver(Instance & aInstance)55 AddressResolver::AddressResolver(Instance &aInstance)
56     : InstanceLocator(aInstance)
57 #if OPENTHREAD_FTD
58     , mCacheEntryPool(aInstance)
59     , mIcmpHandler(&AddressResolver::HandleIcmpReceive, this)
60 #endif
61 {
62 #if OPENTHREAD_FTD
63     IgnoreError(Get<Ip6::Icmp>().RegisterHandler(mIcmpHandler));
64 #endif
65 }
66 
67 #if OPENTHREAD_FTD
68 
Clear(void)69 void AddressResolver::Clear(void)
70 {
71     CacheEntryList *lists[] = {&mCachedList, &mSnoopedList, &mQueryList, &mQueryRetryList};
72 
73     for (CacheEntryList *list : lists)
74     {
75         CacheEntry *entry;
76 
77         while ((entry = list->Pop()) != nullptr)
78         {
79             if (list == &mQueryList)
80             {
81                 Get<MeshForwarder>().HandleResolved(entry->GetTarget(), kErrorDrop);
82             }
83 
84             mCacheEntryPool.Free(*entry);
85         }
86     }
87 }
88 
GetNextCacheEntry(EntryInfo & aInfo,Iterator & aIterator) const89 Error AddressResolver::GetNextCacheEntry(EntryInfo &aInfo, Iterator &aIterator) const
90 {
91     Error                 error = kErrorNone;
92     const CacheEntryList *list  = aIterator.GetList();
93     const CacheEntry     *entry = aIterator.GetEntry();
94 
95     while (entry == nullptr)
96     {
97         if (list == nullptr)
98         {
99             list = &mCachedList;
100         }
101         else if (list == &mCachedList)
102         {
103             list = &mSnoopedList;
104         }
105         else if (list == &mSnoopedList)
106         {
107             list = &mQueryList;
108         }
109         else if (list == &mQueryList)
110         {
111             list = &mQueryRetryList;
112         }
113         else
114         {
115             ExitNow(error = kErrorNotFound);
116         }
117 
118         entry = list->GetHead();
119     }
120 
121     // Update the iterator then populate the `aInfo`.
122 
123     aIterator.SetEntry(entry->GetNext());
124     aIterator.SetList(list);
125 
126     aInfo.Clear();
127     aInfo.mTarget = entry->GetTarget();
128     aInfo.mRloc16 = entry->GetRloc16();
129 
130     if (list == &mCachedList)
131     {
132         aInfo.mState          = MapEnum(EntryInfo::kStateCached);
133         aInfo.mCanEvict       = true;
134         aInfo.mValidLastTrans = entry->IsLastTransactionTimeValid();
135 
136         VerifyOrExit(entry->IsLastTransactionTimeValid());
137 
138         aInfo.mLastTransTime = entry->GetLastTransactionTime();
139         AsCoreType(&aInfo.mMeshLocalEid).SetPrefix(Get<Mle::MleRouter>().GetMeshLocalPrefix());
140         AsCoreType(&aInfo.mMeshLocalEid).SetIid(entry->GetMeshLocalIid());
141 
142         ExitNow();
143     }
144 
145     if (list == &mSnoopedList)
146     {
147         aInfo.mState = MapEnum(EntryInfo::kStateSnooped);
148     }
149     else if (list == &mQueryList)
150     {
151         aInfo.mState = MapEnum(EntryInfo::kStateQuery);
152     }
153     else
154     {
155         aInfo.mState    = MapEnum(EntryInfo::kStateRetryQuery);
156         aInfo.mRampDown = entry->IsInRampDown();
157     }
158 
159     aInfo.mCanEvict   = entry->CanEvict();
160     aInfo.mTimeout    = entry->GetTimeout();
161     aInfo.mRetryDelay = entry->GetRetryDelay();
162 
163 exit:
164     return error;
165 }
166 
RemoveEntriesForRouterId(uint8_t aRouterId)167 void AddressResolver::RemoveEntriesForRouterId(uint8_t aRouterId)
168 {
169     Remove(Mle::Rloc16FromRouterId(aRouterId), /* aMatchRouterId */ true);
170 }
171 
RemoveEntriesForRloc16(uint16_t aRloc16)172 void AddressResolver::RemoveEntriesForRloc16(uint16_t aRloc16) { Remove(aRloc16, /* aMatchRouterId */ false); }
173 
GetEntryAfter(CacheEntry * aPrev,CacheEntryList & aList)174 AddressResolver::CacheEntry *AddressResolver::GetEntryAfter(CacheEntry *aPrev, CacheEntryList &aList)
175 {
176     return (aPrev == nullptr) ? aList.GetHead() : aPrev->GetNext();
177 }
178 
Remove(uint16_t aRloc16,bool aMatchRouterId)179 void AddressResolver::Remove(uint16_t aRloc16, bool aMatchRouterId)
180 {
181     CacheEntryList *lists[] = {&mCachedList, &mSnoopedList};
182 
183     for (CacheEntryList *list : lists)
184     {
185         CacheEntry *prev = nullptr;
186         CacheEntry *entry;
187 
188         while ((entry = GetEntryAfter(prev, *list)) != nullptr)
189         {
190             if ((aMatchRouterId && Mle::RouterIdMatch(entry->GetRloc16(), aRloc16)) ||
191                 (!aMatchRouterId && (entry->GetRloc16() == aRloc16)))
192             {
193                 RemoveCacheEntry(*entry, *list, prev, aMatchRouterId ? kReasonRemovingRouterId : kReasonRemovingRloc16);
194                 mCacheEntryPool.Free(*entry);
195 
196                 // If the entry is removed from list, we keep the same
197                 // `prev` pointer.
198             }
199             else
200             {
201                 prev = entry;
202             }
203         }
204     }
205 }
206 
FindCacheEntry(const Ip6::Address & aEid,CacheEntryList * & aList,CacheEntry * & aPrevEntry)207 AddressResolver::CacheEntry *AddressResolver::FindCacheEntry(const Ip6::Address &aEid,
208                                                              CacheEntryList    *&aList,
209                                                              CacheEntry        *&aPrevEntry)
210 {
211     CacheEntry     *entry   = nullptr;
212     CacheEntryList *lists[] = {&mCachedList, &mSnoopedList, &mQueryList, &mQueryRetryList};
213 
214     for (CacheEntryList *list : lists)
215     {
216         aList = list;
217         entry = aList->FindMatching(aEid, aPrevEntry);
218         VerifyOrExit(entry == nullptr);
219     }
220 
221 exit:
222     return entry;
223 }
224 
RemoveEntryForAddress(const Ip6::Address & aEid)225 void AddressResolver::RemoveEntryForAddress(const Ip6::Address &aEid) { Remove(aEid, kReasonRemovingEid); }
226 
Remove(const Ip6::Address & aEid,Reason aReason)227 void AddressResolver::Remove(const Ip6::Address &aEid, Reason aReason)
228 {
229     CacheEntry     *entry;
230     CacheEntry     *prev;
231     CacheEntryList *list;
232 
233     entry = FindCacheEntry(aEid, list, prev);
234     VerifyOrExit(entry != nullptr);
235 
236     RemoveCacheEntry(*entry, *list, prev, aReason);
237     mCacheEntryPool.Free(*entry);
238 
239 exit:
240     return;
241 }
242 
ReplaceEntriesForRloc16(uint16_t aOldRloc16,uint16_t aNewRloc16)243 void AddressResolver::ReplaceEntriesForRloc16(uint16_t aOldRloc16, uint16_t aNewRloc16)
244 {
245     CacheEntryList *lists[] = {&mCachedList, &mSnoopedList};
246 
247     for (CacheEntryList *list : lists)
248     {
249         for (CacheEntry &entry : *list)
250         {
251             if (entry.GetRloc16() == aOldRloc16)
252             {
253                 entry.SetRloc16(aNewRloc16);
254             }
255         }
256     }
257 }
258 
NewCacheEntry(bool aSnoopedEntry)259 AddressResolver::CacheEntry *AddressResolver::NewCacheEntry(bool aSnoopedEntry)
260 {
261     CacheEntry     *newEntry  = nullptr;
262     CacheEntry     *prevEntry = nullptr;
263     CacheEntryList *lists[]   = {&mSnoopedList, &mQueryRetryList, &mQueryList, &mCachedList};
264 
265     // The following order is used when trying to allocate a new cache
266     // entry: First the cache pool is checked, followed by the list
267     // of snooped entries, then query-retry list (entries in delay
268     // retry timeout wait due to a prior query failing to get a
269     // response), then the query list (entries actively querying and
270     // waiting for address notification response), and finally the
271     // cached (in-use) list. Within each list the oldest entry is
272     // reclaimed first (the list's tail). We also make sure the entry
273     // can be evicted (e.g., first time query entries can not be
274     // evicted till timeout).
275 
276     newEntry = mCacheEntryPool.Allocate();
277     VerifyOrExit(newEntry == nullptr);
278 
279     for (CacheEntryList *list : lists)
280     {
281         CacheEntry *prev;
282         CacheEntry *entry;
283         uint16_t    numNonEvictable = 0;
284 
285         for (prev = nullptr; (entry = GetEntryAfter(prev, *list)) != nullptr; prev = entry)
286         {
287             if ((list != &mCachedList) && !entry->CanEvict())
288             {
289                 numNonEvictable++;
290                 continue;
291             }
292 
293             newEntry  = entry;
294             prevEntry = prev;
295         }
296 
297         if (newEntry != nullptr)
298         {
299             RemoveCacheEntry(*newEntry, *list, prevEntry, kReasonEvictingForNewEntry);
300             ExitNow();
301         }
302 
303         if (aSnoopedEntry && (list == &mSnoopedList))
304         {
305             // Check if the new entry is being requested for "snoop
306             // optimization" (i.e., inspection of a received message).
307             // When a new snooped entry is added, we do not allow it
308             // to be evicted for a short timeout. This allows some
309             // delay for a response message to use the entry (if entry
310             // is used it will be moved to the cached list). If a
311             // snooped entry is not used after the timeout, we allow
312             // it to be evicted. To ensure snooped entries do not
313             // overwrite other cached entries, we limit the number of
314             // snooped entries that are in timeout mode and cannot be
315             // evicted by `kMaxNonEvictableSnoopedEntries`.
316 
317             VerifyOrExit(numNonEvictable < kMaxNonEvictableSnoopedEntries);
318         }
319     }
320 
321 exit:
322     return newEntry;
323 }
324 
RemoveCacheEntry(CacheEntry & aEntry,CacheEntryList & aList,CacheEntry * aPrevEntry,Reason aReason)325 void AddressResolver::RemoveCacheEntry(CacheEntry     &aEntry,
326                                        CacheEntryList &aList,
327                                        CacheEntry     *aPrevEntry,
328                                        Reason          aReason)
329 {
330     aList.PopAfter(aPrevEntry);
331 
332     if (&aList == &mQueryList)
333     {
334         Get<MeshForwarder>().HandleResolved(aEntry.GetTarget(), kErrorDrop);
335     }
336 
337     LogCacheEntryChange(kEntryRemoved, aReason, aEntry, &aList);
338 }
339 
UpdateCacheEntry(const Ip6::Address & aEid,uint16_t aRloc16)340 Error AddressResolver::UpdateCacheEntry(const Ip6::Address &aEid, uint16_t aRloc16)
341 {
342     // This method updates an existing cache entry for the EID (if any).
343     // Returns `kErrorNone` if entry is found and successfully updated,
344     // `kErrorNotFound` if no matching entry.
345 
346     Error           error = kErrorNone;
347     CacheEntryList *list;
348     CacheEntry     *entry;
349     CacheEntry     *prev;
350 
351     entry = FindCacheEntry(aEid, list, prev);
352     VerifyOrExit(entry != nullptr, error = kErrorNotFound);
353 
354     if ((list == &mCachedList) || (list == &mSnoopedList))
355     {
356         VerifyOrExit(entry->GetRloc16() != aRloc16);
357         entry->SetRloc16(aRloc16);
358     }
359     else
360     {
361         // Entry is in `mQueryList` or `mQueryRetryList`. Remove it
362         // from its current list, update it, and then add it to the
363         // `mCachedList`.
364 
365         list->PopAfter(prev);
366 
367         entry->SetRloc16(aRloc16);
368         entry->MarkLastTransactionTimeAsInvalid();
369         mCachedList.Push(*entry);
370 
371         Get<MeshForwarder>().HandleResolved(aEid, kErrorNone);
372     }
373 
374     LogCacheEntryChange(kEntryUpdated, kReasonSnoop, *entry);
375 
376 exit:
377     return error;
378 }
379 
UpdateSnoopedCacheEntry(const Ip6::Address & aEid,uint16_t aRloc16,uint16_t aDest)380 void AddressResolver::UpdateSnoopedCacheEntry(const Ip6::Address &aEid, uint16_t aRloc16, uint16_t aDest)
381 {
382     uint16_t    numNonEvictable = 0;
383     CacheEntry *entry;
384 
385     VerifyOrExit(Get<Mle::MleRouter>().IsFullThreadDevice());
386 
387 #if OPENTHREAD_CONFIG_TMF_ALLOW_ADDRESS_RESOLUTION_USING_NET_DATA_SERVICES
388     {
389         uint16_t rloc16;
390 
391         VerifyOrExit(ResolveUsingNetDataServices(aEid, rloc16) != kErrorNone);
392     }
393 #endif
394 
395     VerifyOrExit(UpdateCacheEntry(aEid, aRloc16) != kErrorNone);
396 
397     // Skip if the `aRloc16` (i.e., the source of the snooped message)
398     // is this device or an MTD (minimal) child of the device itself.
399 
400     VerifyOrExit(!Get<Mle::Mle>().HasRloc16(aRloc16) && !Get<ChildTable>().HasMinimalChild(aRloc16));
401 
402     // Ensure that the destination of the snooped message is this device
403     // or a minimal child of this device.
404 
405     VerifyOrExit(Get<Mle::Mle>().HasRloc16(aDest) || Get<ChildTable>().HasMinimalChild(aDest));
406 
407     entry = NewCacheEntry(/* aSnoopedEntry */ true);
408     VerifyOrExit(entry != nullptr);
409 
410     for (CacheEntry &snooped : mSnoopedList)
411     {
412         if (!snooped.CanEvict())
413         {
414             numNonEvictable++;
415         }
416     }
417 
418     entry->SetTarget(aEid);
419     entry->SetRloc16(aRloc16);
420 
421     if (numNonEvictable < kMaxNonEvictableSnoopedEntries)
422     {
423         entry->SetCanEvict(false);
424         entry->SetTimeout(kSnoopBlockEvictionTimeout);
425 
426         Get<TimeTicker>().RegisterReceiver(TimeTicker::kAddressResolver);
427     }
428     else
429     {
430         entry->SetCanEvict(true);
431         entry->SetTimeout(0);
432     }
433 
434     mSnoopedList.Push(*entry);
435 
436     LogCacheEntryChange(kEntryAdded, kReasonSnoop, *entry);
437 
438 exit:
439     return;
440 }
441 
RestartAddressQueries(void)442 void AddressResolver::RestartAddressQueries(void)
443 {
444     CacheEntry *tail;
445 
446     // We move all entries from `mQueryRetryList` at the tail of
447     // `mQueryList` and then (re)send Address Query for all entries in
448     // the updated `mQueryList`.
449 
450     tail = mQueryList.GetTail();
451 
452     if (tail == nullptr)
453     {
454         mQueryList.SetHead(mQueryRetryList.GetHead());
455     }
456     else
457     {
458         tail->SetNext(mQueryRetryList.GetHead());
459     }
460 
461     mQueryRetryList.Clear();
462 
463     for (CacheEntry &entry : mQueryList)
464     {
465         IgnoreError(SendAddressQuery(entry.GetTarget()));
466 
467         entry.SetTimeout(kAddressQueryTimeout);
468         entry.SetRetryDelay(kAddressQueryInitialRetryDelay);
469         entry.SetCanEvict(false);
470     }
471 }
472 
LookUp(const Ip6::Address & aEid)473 uint16_t AddressResolver::LookUp(const Ip6::Address &aEid)
474 {
475     uint16_t rloc16 = Mle::kInvalidRloc16;
476 
477     IgnoreError(Resolve(aEid, rloc16, /* aAllowAddressQuery */ false));
478     return rloc16;
479 }
480 
Resolve(const Ip6::Address & aEid,uint16_t & aRloc16,bool aAllowAddressQuery)481 Error AddressResolver::Resolve(const Ip6::Address &aEid, uint16_t &aRloc16, bool aAllowAddressQuery)
482 {
483     Error           error = kErrorNone;
484     CacheEntry     *entry;
485     CacheEntry     *prev = nullptr;
486     CacheEntryList *list;
487 
488 #if OPENTHREAD_CONFIG_TMF_ALLOW_ADDRESS_RESOLUTION_USING_NET_DATA_SERVICES
489     VerifyOrExit(ResolveUsingNetDataServices(aEid, aRloc16) != kErrorNone);
490 #endif
491 
492     entry = FindCacheEntry(aEid, list, prev);
493 
494     if ((entry != nullptr) && ((list == &mCachedList) || (list == &mSnoopedList)))
495     {
496         bool isFresh;
497 
498         list->PopAfter(prev);
499 
500         // If the `entry->GetRloc16()` is unreachable (there is no
501         // valid next hop towards it), it may be a stale entry. We
502         // clear the entry to allow new address query to be sent for
503         // it, unless the entry has been recently updated, i.e., we
504         // have recently received an `AddressNotify` for it and its
505         // `FreshnessTimeout` has not expired yet.
506         //
507         // The `FreshnessTimeout` check prevents repeated address
508         // query transmissions when mesh routes are not yet
509         // discovered (e.g., after initial attach) or if there is a
510         // temporary link issue.
511 
512         isFresh = (list == &mCachedList) && !entry->IsFreshnessTimeoutZero();
513 
514         if (!isFresh && (Get<RouterTable>().GetNextHop(entry->GetRloc16()) == Mle::kInvalidRloc16))
515         {
516             mCacheEntryPool.Free(*entry);
517             entry = nullptr;
518         }
519 
520         if (entry != nullptr)
521         {
522             // Push the entry at the head of cached list.
523 
524             if (list == &mSnoopedList)
525             {
526                 entry->MarkLastTransactionTimeAsInvalid();
527             }
528 
529             mCachedList.Push(*entry);
530             aRloc16 = entry->GetRloc16();
531             ExitNow();
532         }
533     }
534 
535     if (entry == nullptr)
536     {
537         // If the entry is not present in any of the lists, try to
538         // allocate a new entry and perform address query. We do not
539         // allow first-time address query entries to be evicted till
540         // timeout.
541 
542         VerifyOrExit(aAllowAddressQuery, error = kErrorNotFound);
543 
544         entry = NewCacheEntry(/* aSnoopedEntry */ false);
545         VerifyOrExit(entry != nullptr, error = kErrorNoBufs);
546 
547         entry->SetTarget(aEid);
548         entry->SetRloc16(Mle::kInvalidRloc16);
549         entry->SetRetryDelay(kAddressQueryInitialRetryDelay);
550         entry->SetCanEvict(false);
551         list = nullptr;
552     }
553 
554     // Note that if `aAllowAddressQuery` is `false` then the `entry`
555     // is definitely already in a list, i.e., we cannot not get here
556     // with `aAllowAddressQuery` being `false` and `entry` being a
557     // newly allocated one, due to the `VerifyOrExit` check that
558     // `aAllowAddressQuery` is `true` before allocating a new cache
559     // entry.
560     VerifyOrExit(aAllowAddressQuery, error = kErrorNotFound);
561 
562     if (list == &mQueryList)
563     {
564         ExitNow(error = kErrorAddressQuery);
565     }
566 
567     if (list == &mQueryRetryList)
568     {
569         // Allow an entry in query-retry mode to resend an Address
570         // Query again only if it is in ramp down mode, i.e., the
571         // retry delay timeout is expired.
572 
573         VerifyOrExit(entry->IsInRampDown(), error = kErrorDrop);
574         mQueryRetryList.PopAfter(prev);
575     }
576 
577     entry->SetTimeout(kAddressQueryTimeout);
578 
579     error = SendAddressQuery(aEid);
580     VerifyOrExit(error == kErrorNone, mCacheEntryPool.Free(*entry));
581 
582     if (list == nullptr)
583     {
584         LogCacheEntryChange(kEntryAdded, kReasonQueryRequest, *entry);
585     }
586 
587     mQueryList.Push(*entry);
588     error = kErrorAddressQuery;
589 
590 exit:
591     return error;
592 }
593 
594 #if OPENTHREAD_CONFIG_TMF_ALLOW_ADDRESS_RESOLUTION_USING_NET_DATA_SERVICES
595 
ResolveUsingNetDataServices(const Ip6::Address & aEid,uint16_t & aRloc16)596 Error AddressResolver::ResolveUsingNetDataServices(const Ip6::Address &aEid, uint16_t &aRloc16)
597 {
598     // Tries to resolve `aEid` Network Data DNS/SRP Unicast address
599     // service entries.  Returns `kErrorNone` and updates `aRloc16`
600     // if successful, otherwise returns `kErrorNotFound`.
601 
602     Error                                   error = kErrorNotFound;
603     NetworkData::Service::Manager::Iterator iterator;
604     NetworkData::Service::DnsSrpUnicastInfo unicastInfo;
605     NetworkData::Service::DnsSrpUnicastType type = NetworkData::Service::kAddrInServerData;
606 
607     VerifyOrExit(Get<Mle::Mle>().GetDeviceMode().GetNetworkDataType() == NetworkData::kFullSet);
608 
609     while (Get<NetworkData::Service::Manager>().GetNextDnsSrpUnicastInfo(iterator, type, unicastInfo) == kErrorNone)
610     {
611         if (aEid == unicastInfo.mSockAddr.GetAddress())
612         {
613             aRloc16 = unicastInfo.mRloc16;
614             error   = kErrorNone;
615             ExitNow();
616         }
617     }
618 
619 exit:
620     return error;
621 }
622 
623 #endif // OPENTHREAD_CONFIG_TMF_ALLOW_ADDRESS_RESOLUTION_USING_NET_DATA_SERVICES
624 
SendAddressQuery(const Ip6::Address & aEid)625 Error AddressResolver::SendAddressQuery(const Ip6::Address &aEid)
626 {
627     Error            error;
628     Coap::Message   *message;
629     Tmf::MessageInfo messageInfo(GetInstance());
630 
631     message = Get<Tmf::Agent>().NewPriorityNonConfirmablePostMessage(kUriAddressQuery);
632     VerifyOrExit(message != nullptr, error = kErrorNoBufs);
633 
634     SuccessOrExit(error = Tlv::Append<ThreadTargetTlv>(*message, aEid));
635 
636     messageInfo.SetSockAddrToRlocPeerAddrToRealmLocalAllRoutersMulticast();
637 
638     SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*message, messageInfo));
639 
640     LogInfo("Sent %s for %s", UriToString<kUriAddressQuery>(), aEid.ToString().AsCString());
641 
642 exit:
643 
644     Get<TimeTicker>().RegisterReceiver(TimeTicker::kAddressResolver);
645     FreeMessageOnError(message, error);
646 
647 #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE
648     if (Get<BackboneRouter::Local>().IsPrimary() && Get<BackboneRouter::Leader>().IsDomainUnicast(aEid))
649     {
650         uint16_t selfRloc16 = Get<Mle::MleRouter>().GetRloc16();
651 
652         LogInfo("Extending %s to %s for target %s, rloc16=%04x(self)", UriToString<kUriAddressQuery>(),
653                 UriToString<kUriBackboneQuery>(), aEid.ToString().AsCString(), selfRloc16);
654         IgnoreError(Get<BackboneRouter::Manager>().SendBackboneQuery(aEid, selfRloc16));
655     }
656 #endif
657 
658     return error;
659 }
660 
661 template <>
HandleTmf(Coap::Message & aMessage,const Ip6::MessageInfo & aMessageInfo)662 void AddressResolver::HandleTmf<kUriAddressNotify>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
663 {
664     Ip6::Address             target;
665     Ip6::InterfaceIdentifier meshLocalIid;
666     uint16_t                 rloc16;
667     uint32_t                 lastTransactionTime;
668     CacheEntryList          *list;
669     CacheEntry              *entry;
670     CacheEntry              *prev;
671 
672     VerifyOrExit(aMessage.IsConfirmablePostRequest());
673 
674     SuccessOrExit(Tlv::Find<ThreadTargetTlv>(aMessage, target));
675     SuccessOrExit(Tlv::Find<ThreadMeshLocalEidTlv>(aMessage, meshLocalIid));
676     SuccessOrExit(Tlv::Find<ThreadRloc16Tlv>(aMessage, rloc16));
677 
678     switch (Tlv::Find<ThreadLastTransactionTimeTlv>(aMessage, lastTransactionTime))
679     {
680     case kErrorNone:
681         break;
682     case kErrorNotFound:
683         lastTransactionTime = 0;
684         break;
685     default:
686         ExitNow();
687     }
688 
689     LogInfo("Received %s from 0x%04x for %s to 0x%04x", UriToString<kUriAddressNotify>(),
690             aMessageInfo.GetPeerAddr().GetIid().GetLocator(), target.ToString().AsCString(), rloc16);
691 
692     entry = FindCacheEntry(target, list, prev);
693     VerifyOrExit(entry != nullptr);
694 
695     if (list == &mCachedList)
696     {
697         if (entry->IsLastTransactionTimeValid())
698         {
699             // Receiving multiple Address Notification for an EID from
700             // different mesh-local IIDs indicates address is in use
701             // by more than one device. Try to resolve the duplicate
702             // address by sending an Address Error message.
703 
704             VerifyOrExit(entry->GetMeshLocalIid() == meshLocalIid, SendAddressError(target, meshLocalIid, nullptr));
705 
706             VerifyOrExit(lastTransactionTime < entry->GetLastTransactionTime());
707         }
708     }
709 
710     entry->SetRloc16(rloc16);
711     entry->SetMeshLocalIid(meshLocalIid);
712     entry->SetLastTransactionTime(lastTransactionTime);
713     entry->ResetFreshnessTimeout();
714     Get<TimeTicker>().RegisterReceiver(TimeTicker::kAddressResolver);
715 
716     list->PopAfter(prev);
717     mCachedList.Push(*entry);
718 
719     LogCacheEntryChange(kEntryUpdated, kReasonReceivedNotification, *entry);
720 
721     if (Get<Tmf::Agent>().SendEmptyAck(aMessage, aMessageInfo) == kErrorNone)
722     {
723         LogInfo("Sent %s ack", UriToString<kUriAddressNotify>());
724     }
725 
726     Get<MeshForwarder>().HandleResolved(target, kErrorNone);
727 
728 exit:
729     return;
730 }
731 
SendAddressError(const Ip6::Address & aTarget,const Ip6::InterfaceIdentifier & aMeshLocalIid,const Ip6::Address * aDestination)732 void AddressResolver::SendAddressError(const Ip6::Address             &aTarget,
733                                        const Ip6::InterfaceIdentifier &aMeshLocalIid,
734                                        const Ip6::Address             *aDestination)
735 {
736     Error            error;
737     Coap::Message   *message;
738     Tmf::MessageInfo messageInfo(GetInstance());
739 
740     VerifyOrExit((message = Get<Tmf::Agent>().NewMessage()) != nullptr, error = kErrorNoBufs);
741 
742     message->Init(aDestination == nullptr ? Coap::kTypeNonConfirmable : Coap::kTypeConfirmable, Coap::kCodePost);
743     SuccessOrExit(error = message->AppendUriPathOptions(PathForUri(kUriAddressError)));
744     SuccessOrExit(error = message->SetPayloadMarker());
745 
746     SuccessOrExit(error = Tlv::Append<ThreadTargetTlv>(*message, aTarget));
747     SuccessOrExit(error = Tlv::Append<ThreadMeshLocalEidTlv>(*message, aMeshLocalIid));
748 
749     if (aDestination == nullptr)
750     {
751         messageInfo.SetSockAddrToRlocPeerAddrToRealmLocalAllRoutersMulticast();
752     }
753     else
754     {
755         messageInfo.SetSockAddrToRlocPeerAddrTo(*aDestination);
756     }
757 
758     SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*message, messageInfo));
759 
760     LogInfo("Sent %s for target %s", UriToString<kUriAddressError>(), aTarget.ToString().AsCString());
761 
762 exit:
763 
764     if (error != kErrorNone)
765     {
766         FreeMessage(message);
767         LogInfo("Failed to send %s: %s", UriToString<kUriAddressError>(), ErrorToString(error));
768     }
769 }
770 
771 #endif // OPENTHREAD_FTD
772 
773 template <>
HandleTmf(Coap::Message & aMessage,const Ip6::MessageInfo & aMessageInfo)774 void AddressResolver::HandleTmf<kUriAddressError>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
775 {
776     Error                    error = kErrorNone;
777     Ip6::Address             target;
778     Ip6::InterfaceIdentifier meshLocalIid;
779 #if OPENTHREAD_FTD
780     Mac::ExtAddress extAddr;
781     Ip6::Address    destination;
782 #endif
783 
784     VerifyOrExit(aMessage.IsPostRequest(), error = kErrorDrop);
785 
786     LogInfo("Received %s", UriToString<kUriAddressError>());
787 
788     if (aMessage.IsConfirmable() && !aMessageInfo.GetSockAddr().IsMulticast())
789     {
790         if (Get<Tmf::Agent>().SendEmptyAck(aMessage, aMessageInfo) == kErrorNone)
791         {
792             LogInfo("Sent %s ack", UriToString<kUriAddressError>());
793         }
794     }
795 
796     SuccessOrExit(error = Tlv::Find<ThreadTargetTlv>(aMessage, target));
797     SuccessOrExit(error = Tlv::Find<ThreadMeshLocalEidTlv>(aMessage, meshLocalIid));
798 
799     for (Ip6::Netif::UnicastAddress &address : Get<ThreadNetif>().GetUnicastAddresses())
800     {
801         if (address.GetAddress() == target && Get<Mle::MleRouter>().GetMeshLocalEid().GetIid() != meshLocalIid)
802         {
803             // Target EID matches address and Mesh Local EID differs
804 #if OPENTHREAD_CONFIG_DUA_ENABLE
805             if (Get<BackboneRouter::Leader>().IsDomainUnicast(address.GetAddress()))
806             {
807                 Get<DuaManager>().NotifyDuplicateDomainUnicastAddress();
808             }
809             else
810 #endif
811             {
812                 Get<ThreadNetif>().RemoveUnicastAddress(address);
813             }
814 
815             ExitNow();
816         }
817     }
818 
819 #if OPENTHREAD_FTD
820     meshLocalIid.ConvertToExtAddress(extAddr);
821 
822     for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
823     {
824         if (child.IsFullThreadDevice())
825         {
826             continue;
827         }
828 
829         if (child.GetExtAddress() != extAddr)
830         {
831             // Mesh Local EID differs, so check whether Target EID
832             // matches a child address and if so remove it.
833 
834             if (child.RemoveIp6Address(target) == kErrorNone)
835             {
836                 destination.SetToRoutingLocator(Get<Mle::Mle>().GetMeshLocalPrefix(), child.GetRloc16());
837 
838                 SendAddressError(target, meshLocalIid, &destination);
839                 ExitNow();
840             }
841         }
842     }
843 #endif // OPENTHREAD_FTD
844 
845 exit:
846 
847     if (error != kErrorNone)
848     {
849         LogWarn("Error %s when processing %s", ErrorToString(error), UriToString<kUriAddressError>());
850     }
851 }
852 
853 #if OPENTHREAD_FTD
854 
855 template <>
HandleTmf(Coap::Message & aMessage,const Ip6::MessageInfo & aMessageInfo)856 void AddressResolver::HandleTmf<kUriAddressQuery>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
857 {
858     Ip6::Address target;
859     uint32_t     lastTransactionTime;
860 
861     VerifyOrExit(aMessage.IsNonConfirmablePostRequest());
862 
863     SuccessOrExit(Tlv::Find<ThreadTargetTlv>(aMessage, target));
864 
865     LogInfo("Received %s from 0x%04x for target %s", UriToString<kUriAddressQuery>(),
866             aMessageInfo.GetPeerAddr().GetIid().GetLocator(), target.ToString().AsCString());
867 
868     if (Get<ThreadNetif>().HasUnicastAddress(target))
869     {
870         SendAddressQueryResponse(target, Get<Mle::MleRouter>().GetMeshLocalEid().GetIid(), nullptr,
871                                  aMessageInfo.GetPeerAddr());
872         ExitNow();
873     }
874 
875     for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
876     {
877         if (child.IsFullThreadDevice() || child.GetLinkFailures() >= Mle::kFailedChildTransmissions)
878         {
879             continue;
880         }
881 
882         if (child.HasIp6Address(target))
883         {
884             lastTransactionTime = Time::MsecToSec(TimerMilli::GetNow() - child.GetLastHeard());
885             SendAddressQueryResponse(target, child.GetMeshLocalIid(), &lastTransactionTime, aMessageInfo.GetPeerAddr());
886             ExitNow();
887         }
888     }
889 
890 #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE
891     if (Get<BackboneRouter::Local>().IsPrimary() && Get<BackboneRouter::Leader>().IsDomainUnicast(target))
892     {
893         uint16_t srcRloc16 = aMessageInfo.GetPeerAddr().GetIid().GetLocator();
894 
895         LogInfo("Extending %s to %s for target %s rloc16=%04x", UriToString<kUriAddressQuery>(),
896                 UriToString<kUriBackboneQuery>(), target.ToString().AsCString(), srcRloc16);
897         IgnoreError(Get<BackboneRouter::Manager>().SendBackboneQuery(target, srcRloc16));
898     }
899 #endif
900 
901 exit:
902     return;
903 }
904 
SendAddressQueryResponse(const Ip6::Address & aTarget,const Ip6::InterfaceIdentifier & aMeshLocalIid,const uint32_t * aLastTransactionTime,const Ip6::Address & aDestination)905 void AddressResolver::SendAddressQueryResponse(const Ip6::Address             &aTarget,
906                                                const Ip6::InterfaceIdentifier &aMeshLocalIid,
907                                                const uint32_t                 *aLastTransactionTime,
908                                                const Ip6::Address             &aDestination)
909 {
910     Error            error;
911     Coap::Message   *message;
912     Tmf::MessageInfo messageInfo(GetInstance());
913 
914     message = Get<Tmf::Agent>().NewPriorityConfirmablePostMessage(kUriAddressNotify);
915     VerifyOrExit(message != nullptr, error = kErrorNoBufs);
916 
917     SuccessOrExit(error = Tlv::Append<ThreadTargetTlv>(*message, aTarget));
918     SuccessOrExit(error = Tlv::Append<ThreadMeshLocalEidTlv>(*message, aMeshLocalIid));
919     SuccessOrExit(error = Tlv::Append<ThreadRloc16Tlv>(*message, Get<Mle::MleRouter>().GetRloc16()));
920 
921     if (aLastTransactionTime != nullptr)
922     {
923         SuccessOrExit(error = Tlv::Append<ThreadLastTransactionTimeTlv>(*message, *aLastTransactionTime));
924     }
925 
926     messageInfo.SetSockAddrToRlocPeerAddrTo(aDestination);
927 
928     SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*message, messageInfo));
929 
930     LogInfo("Sent %s for target %s", UriToString<kUriAddressNotify>(), aTarget.ToString().AsCString());
931 
932 exit:
933     FreeMessageOnError(message, error);
934 }
935 
HandleTimeTick(void)936 void AddressResolver::HandleTimeTick(void)
937 {
938     bool continueRxingTicks = false;
939 
940     for (CacheEntry &entry : mCachedList)
941     {
942         if (!entry.IsFreshnessTimeoutZero())
943         {
944             entry.DecrementFreshnessTimeout();
945             continueRxingTicks = true;
946         }
947     }
948 
949     for (CacheEntry &entry : mSnoopedList)
950     {
951         if (entry.IsTimeoutZero())
952         {
953             continue;
954         }
955 
956         continueRxingTicks = true;
957         entry.DecrementTimeout();
958 
959         if (entry.IsTimeoutZero())
960         {
961             entry.SetCanEvict(true);
962         }
963     }
964 
965     for (CacheEntry &entry : mQueryRetryList)
966     {
967         if (entry.IsTimeoutZero())
968         {
969             continue;
970         }
971 
972         continueRxingTicks = true;
973         entry.DecrementTimeout();
974 
975         if (entry.IsTimeoutZero())
976         {
977             if (!entry.IsInRampDown())
978             {
979                 entry.SetRampDown(true);
980                 entry.SetTimeout(kAddressQueryMaxRetryDelay);
981 
982                 LogInfo("Starting ramp down of %s retry-delay:%u", entry.GetTarget().ToString().AsCString(),
983                         entry.GetTimeout());
984             }
985             else
986             {
987                 uint16_t retryDelay = entry.GetRetryDelay();
988 
989                 retryDelay >>= 1;
990                 retryDelay = Max(retryDelay, kAddressQueryInitialRetryDelay);
991 
992                 if (retryDelay != entry.GetRetryDelay())
993                 {
994                     entry.SetRetryDelay(retryDelay);
995                     entry.SetTimeout(kAddressQueryMaxRetryDelay);
996 
997                     LogInfo("Ramping down %s retry-delay:%u", entry.GetTarget().ToString().AsCString(), retryDelay);
998                 }
999             }
1000         }
1001     }
1002 
1003     {
1004         CacheEntry *prev = nullptr;
1005         CacheEntry *entry;
1006 
1007         while ((entry = GetEntryAfter(prev, mQueryList)) != nullptr)
1008         {
1009             OT_ASSERT(!entry->IsTimeoutZero());
1010 
1011             continueRxingTicks = true;
1012             entry->DecrementTimeout();
1013 
1014             if (entry->IsTimeoutZero())
1015             {
1016                 uint16_t retryDelay = entry->GetRetryDelay();
1017 
1018                 entry->SetTimeout(retryDelay);
1019 
1020                 retryDelay <<= 1;
1021                 retryDelay = Min(retryDelay, kAddressQueryMaxRetryDelay);
1022 
1023                 entry->SetRetryDelay(retryDelay);
1024                 entry->SetCanEvict(true);
1025                 entry->SetRampDown(false);
1026 
1027                 // Move the entry from `mQueryList` to `mQueryRetryList`
1028                 mQueryList.PopAfter(prev);
1029                 mQueryRetryList.Push(*entry);
1030 
1031                 LogInfo("Timed out waiting for %s for %s, retry: %d", UriToString<kUriAddressNotify>(),
1032                         entry->GetTarget().ToString().AsCString(), entry->GetTimeout());
1033 
1034                 Get<MeshForwarder>().HandleResolved(entry->GetTarget(), kErrorDrop);
1035 
1036                 // When the entry is removed from `mQueryList`
1037                 // we keep the `prev` pointer same as before.
1038             }
1039             else
1040             {
1041                 prev = entry;
1042             }
1043         }
1044     }
1045 
1046     if (!continueRxingTicks)
1047     {
1048         Get<TimeTicker>().UnregisterReceiver(TimeTicker::kAddressResolver);
1049     }
1050 }
1051 
HandleIcmpReceive(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo,const otIcmp6Header * aIcmpHeader)1052 void AddressResolver::HandleIcmpReceive(void                *aContext,
1053                                         otMessage           *aMessage,
1054                                         const otMessageInfo *aMessageInfo,
1055                                         const otIcmp6Header *aIcmpHeader)
1056 {
1057     OT_UNUSED_VARIABLE(aMessageInfo);
1058 
1059     static_cast<AddressResolver *>(aContext)->HandleIcmpReceive(AsCoreType(aMessage), AsCoreType(aMessageInfo),
1060                                                                 AsCoreType(aIcmpHeader));
1061 }
1062 
HandleIcmpReceive(Message & aMessage,const Ip6::MessageInfo & aMessageInfo,const Ip6::Icmp::Header & aIcmpHeader)1063 void AddressResolver::HandleIcmpReceive(Message                 &aMessage,
1064                                         const Ip6::MessageInfo  &aMessageInfo,
1065                                         const Ip6::Icmp::Header &aIcmpHeader)
1066 {
1067     OT_UNUSED_VARIABLE(aMessageInfo);
1068 
1069     Ip6::Header ip6Header;
1070 
1071     VerifyOrExit(aIcmpHeader.GetType() == Ip6::Icmp::Header::kTypeDstUnreach);
1072     VerifyOrExit(aIcmpHeader.GetCode() == Ip6::Icmp::Header::kCodeDstUnreachNoRoute);
1073     SuccessOrExit(aMessage.Read(aMessage.GetOffset(), ip6Header));
1074 
1075     Remove(ip6Header.GetDestination(), kReasonReceivedIcmpDstUnreachNoRoute);
1076 
1077 exit:
1078     return;
1079 }
1080 
1081 // LCOV_EXCL_START
1082 
1083 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
1084 
LogCacheEntryChange(EntryChange aChange,Reason aReason,const CacheEntry & aEntry,CacheEntryList * aList)1085 void AddressResolver::LogCacheEntryChange(EntryChange       aChange,
1086                                           Reason            aReason,
1087                                           const CacheEntry &aEntry,
1088                                           CacheEntryList   *aList)
1089 {
1090     static const char *const kChangeStrings[] = {
1091         "added",   // (0) kEntryAdded
1092         "updated", // (1) kEntryUpdated
1093         "removed", // (2) kEntryRemoved
1094     };
1095 
1096     static const char *const kReasonStrings[] = {
1097         "query request",          // (0) kReasonQueryRequest
1098         "snoop",                  // (1) kReasonSnoop
1099         "rx notification",        // (2) kReasonReceivedNotification
1100         "removing router id",     // (3) kReasonRemovingRouterId
1101         "removing rloc16",        // (4) kReasonRemovingRloc16
1102         "rx icmp no route",       // (5) kReasonReceivedIcmpDstUnreachNoRoute
1103         "evicting for new entry", // (6) kReasonEvictingForNewEntry
1104         "removing eid",           // (7) kReasonRemovingEid
1105     };
1106 
1107     static_assert(0 == kEntryAdded, "kEntryAdded value is incorrect");
1108     static_assert(1 == kEntryUpdated, "kEntryUpdated value is incorrect");
1109     static_assert(2 == kEntryRemoved, "kEntryRemoved value is incorrect");
1110 
1111     static_assert(0 == kReasonQueryRequest, "kReasonQueryRequest value is incorrect");
1112     static_assert(1 == kReasonSnoop, "kReasonSnoop value is incorrect");
1113     static_assert(2 == kReasonReceivedNotification, "kReasonReceivedNotification value is incorrect");
1114     static_assert(3 == kReasonRemovingRouterId, "kReasonRemovingRouterId value is incorrect");
1115     static_assert(4 == kReasonRemovingRloc16, "kReasonRemovingRloc16 value is incorrect");
1116     static_assert(5 == kReasonReceivedIcmpDstUnreachNoRoute, "kReasonReceivedIcmpDstUnreachNoRoute value is incorrect");
1117     static_assert(6 == kReasonEvictingForNewEntry, "kReasonEvictingForNewEntry value is incorrect");
1118     static_assert(7 == kReasonRemovingEid, "kReasonRemovingEid value is incorrect");
1119 
1120     LogInfo("Cache entry %s: %s, 0x%04x%s%s - %s", kChangeStrings[aChange], aEntry.GetTarget().ToString().AsCString(),
1121             aEntry.GetRloc16(), (aList == nullptr) ? "" : ", list:", ListToString(aList), kReasonStrings[aReason]);
1122 }
1123 
ListToString(const CacheEntryList * aList) const1124 const char *AddressResolver::ListToString(const CacheEntryList *aList) const
1125 {
1126     const char *str = "";
1127 
1128     VerifyOrExit(aList != &mCachedList, str = "cached");
1129     VerifyOrExit(aList != &mSnoopedList, str = "snooped");
1130     VerifyOrExit(aList != &mQueryList, str = "query");
1131     VerifyOrExit(aList != &mQueryRetryList, str = "query-retry");
1132 
1133 exit:
1134     return str;
1135 }
1136 
1137 #else // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
1138 
LogCacheEntryChange(EntryChange,Reason,const CacheEntry &,CacheEntryList *)1139 void AddressResolver::LogCacheEntryChange(EntryChange, Reason, const CacheEntry &, CacheEntryList *) {}
1140 
1141 #endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_NOTE)
1142 
1143 // LCOV_EXCL_STOP
1144 
1145 //---------------------------------------------------------------------------------------------------------------------
1146 // AddressResolver::CacheEntry
1147 
Init(Instance & aInstance)1148 void AddressResolver::CacheEntry::Init(Instance &aInstance)
1149 {
1150     InstanceLocatorInit::Init(aInstance);
1151     mNextIndex        = kNoNextIndex;
1152     mFreshnessTimeout = 0;
1153 }
1154 
GetNext(void)1155 AddressResolver::CacheEntry *AddressResolver::CacheEntry::GetNext(void)
1156 {
1157     return (mNextIndex == kNoNextIndex) ? nullptr : &Get<AddressResolver>().GetCacheEntryPool().GetEntryAt(mNextIndex);
1158 }
1159 
GetNext(void) const1160 const AddressResolver::CacheEntry *AddressResolver::CacheEntry::GetNext(void) const
1161 {
1162     return (mNextIndex == kNoNextIndex) ? nullptr : &Get<AddressResolver>().GetCacheEntryPool().GetEntryAt(mNextIndex);
1163 }
1164 
SetNext(CacheEntry * aEntry)1165 void AddressResolver::CacheEntry::SetNext(CacheEntry *aEntry)
1166 {
1167     VerifyOrExit(aEntry != nullptr, mNextIndex = kNoNextIndex);
1168     mNextIndex = Get<AddressResolver>().GetCacheEntryPool().GetIndexOf(*aEntry);
1169 
1170 exit:
1171     return;
1172 }
1173 
1174 #endif // OPENTHREAD_FTD
1175 
1176 } // namespace ot
1177