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