1 /*
2 * Copyright (c) 2021, 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 the History Tracker module.
32 */
33
34 #include "history_tracker.hpp"
35
36 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
37
38 #include "common/as_core_type.hpp"
39 #include "common/code_utils.hpp"
40 #include "common/debug.hpp"
41 #include "common/instance.hpp"
42 #include "common/locator_getters.hpp"
43 #include "common/string.hpp"
44 #include "common/timer.hpp"
45 #include "net/ip6_headers.hpp"
46
47 namespace ot {
48 namespace Utils {
49
50 //---------------------------------------------------------------------------------------------------------------------
51 // HistoryTracker
52
HistoryTracker(Instance & aInstance)53 HistoryTracker::HistoryTracker(Instance &aInstance)
54 : InstanceLocator(aInstance)
55 , mTimer(aInstance, HandleTimer)
56 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_DATA
57 , mPreviousNetworkData(aInstance, mNetworkDataTlvBuffer, 0, sizeof(mNetworkDataTlvBuffer))
58 #endif
59 {
60 mTimer.Start(kAgeCheckPeriod);
61 }
62
RecordNetworkInfo(void)63 void HistoryTracker::RecordNetworkInfo(void)
64 {
65 NetworkInfo * entry = mNetInfoHistory.AddNewEntry();
66 Mle::DeviceMode mode;
67
68 VerifyOrExit(entry != nullptr);
69
70 entry->mRole = MapEnum(Get<Mle::Mle>().GetRole());
71 entry->mRloc16 = Get<Mle::Mle>().GetRloc16();
72 entry->mPartitionId = Get<Mle::Mle>().GetLeaderData().GetPartitionId();
73 mode = Get<Mle::Mle>().GetDeviceMode();
74 mode.Get(entry->mMode);
75
76 exit:
77 return;
78 }
79
RecordMessage(const Message & aMessage,const Mac::Address & aMacAddresss,MessageType aType)80 void HistoryTracker::RecordMessage(const Message &aMessage, const Mac::Address &aMacAddresss, MessageType aType)
81 {
82 MessageInfo *entry = nullptr;
83 Ip6::Headers headers;
84
85 VerifyOrExit(aMessage.GetType() == Message::kTypeIp6);
86
87 SuccessOrExit(headers.ParseFrom(aMessage));
88
89 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_EXCLUDE_THREAD_CONTROL_MESSAGES
90 if (headers.IsUdp())
91 {
92 uint16_t port = 0;
93
94 switch (aType)
95 {
96 case kRxMessage:
97 port = headers.GetDestinationPort();
98 break;
99
100 case kTxMessage:
101 port = headers.GetSourcePort();
102 break;
103 }
104
105 VerifyOrExit((port != Mle::kUdpPort) && (port != Tmf::kUdpPort));
106 }
107 #endif
108
109 switch (aType)
110 {
111 case kRxMessage:
112 entry = mRxHistory.AddNewEntry();
113 break;
114
115 case kTxMessage:
116 entry = mTxHistory.AddNewEntry();
117 break;
118 }
119
120 VerifyOrExit(entry != nullptr);
121
122 entry->mPayloadLength = headers.GetIp6Header().GetPayloadLength();
123 entry->mNeighborRloc16 = aMacAddresss.IsShort() ? aMacAddresss.GetShort() : kInvalidRloc16;
124 entry->mSource.mAddress = headers.GetSourceAddress();
125 entry->mSource.mPort = headers.GetSourcePort();
126 entry->mDestination.mAddress = headers.GetDestinationAddress();
127 entry->mDestination.mPort = headers.GetDestinationPort();
128 entry->mChecksum = headers.GetChecksum();
129 entry->mIpProto = headers.GetIpProto();
130 entry->mIcmp6Type = headers.IsIcmp6() ? headers.GetIcmpHeader().GetType() : 0;
131 entry->mAveRxRss = (aType == kRxMessage) ? aMessage.GetRssAverager().GetAverage() : kInvalidRss;
132 entry->mLinkSecurity = aMessage.IsLinkSecurityEnabled();
133 entry->mTxSuccess = (aType == kTxMessage) ? aMessage.GetTxSuccess() : true;
134 entry->mPriority = aMessage.GetPriority();
135
136 if (aMacAddresss.IsExtended())
137 {
138 Neighbor *neighbor = Get<NeighborTable>().FindNeighbor(aMacAddresss, Neighbor::kInStateAnyExceptInvalid);
139
140 if (neighbor != nullptr)
141 {
142 entry->mNeighborRloc16 = neighbor->GetRloc16();
143 }
144 }
145
146 #if OPENTHREAD_CONFIG_MULTI_RADIO
147 if (aMessage.IsRadioTypeSet())
148 {
149 switch (aMessage.GetRadioType())
150 {
151 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
152 case Mac::kRadioTypeIeee802154:
153 entry->mRadioIeee802154 = true;
154 break;
155 #endif
156 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
157 case Mac::kRadioTypeTrel:
158 entry->mRadioTrelUdp6 = true;
159 break;
160 #endif
161 }
162
163 // Radio type may not be set on a tx message indicating that it
164 // was sent over all radio types (e.g., for broadcast frame).
165 // In such a case, we set all supported radios from `else`
166 // block below.
167 }
168 else
169 #endif // OPENTHREAD_CONFIG_MULTI_RADIO
170 {
171 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
172 entry->mRadioIeee802154 = true;
173 #endif
174
175 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
176 entry->mRadioTrelUdp6 = true;
177 #endif
178 }
179
180 exit:
181 return;
182 }
183
RecordNeighborEvent(NeighborTable::Event aEvent,const NeighborTable::EntryInfo & aInfo)184 void HistoryTracker::RecordNeighborEvent(NeighborTable::Event aEvent, const NeighborTable::EntryInfo &aInfo)
185 {
186 NeighborInfo *entry = mNeighborHistory.AddNewEntry();
187
188 VerifyOrExit(entry != nullptr);
189
190 switch (aEvent)
191 {
192 case NeighborTable::kChildAdded:
193 case NeighborTable::kChildRemoved:
194 case NeighborTable::kChildModeChanged:
195 entry->mExtAddress = aInfo.mInfo.mChild.mExtAddress;
196 entry->mRloc16 = aInfo.mInfo.mChild.mRloc16;
197 entry->mAverageRssi = aInfo.mInfo.mChild.mAverageRssi;
198 entry->mRxOnWhenIdle = aInfo.mInfo.mChild.mRxOnWhenIdle;
199 entry->mFullThreadDevice = aInfo.mInfo.mChild.mFullThreadDevice;
200 entry->mFullNetworkData = aInfo.mInfo.mChild.mFullNetworkData;
201 entry->mIsChild = true;
202 break;
203
204 case NeighborTable::kRouterAdded:
205 case NeighborTable::kRouterRemoved:
206 entry->mExtAddress = aInfo.mInfo.mRouter.mExtAddress;
207 entry->mRloc16 = aInfo.mInfo.mRouter.mRloc16;
208 entry->mAverageRssi = aInfo.mInfo.mRouter.mAverageRssi;
209 entry->mRxOnWhenIdle = aInfo.mInfo.mRouter.mRxOnWhenIdle;
210 entry->mFullThreadDevice = aInfo.mInfo.mRouter.mFullThreadDevice;
211 entry->mFullNetworkData = aInfo.mInfo.mRouter.mFullNetworkData;
212 entry->mIsChild = false;
213 break;
214 }
215
216 switch (aEvent)
217 {
218 case NeighborTable::kChildAdded:
219 if (aInfo.mInfo.mChild.mIsStateRestoring)
220 {
221 entry->mEvent = kNeighborRestoring;
222 break;
223 }
224
225 OT_FALL_THROUGH;
226
227 case NeighborTable::kRouterAdded:
228 entry->mEvent = kNeighborAdded;
229 break;
230
231 case NeighborTable::kChildRemoved:
232 case NeighborTable::kRouterRemoved:
233 entry->mEvent = kNeighborRemoved;
234 break;
235
236 case NeighborTable::kChildModeChanged:
237 entry->mEvent = kNeighborChanged;
238 break;
239 }
240
241 exit:
242 return;
243 }
244
RecordAddressEvent(Ip6::Netif::AddressEvent aEvent,const Ip6::Netif::UnicastAddress & aUnicastAddress)245 void HistoryTracker::RecordAddressEvent(Ip6::Netif::AddressEvent aEvent,
246 const Ip6::Netif::UnicastAddress &aUnicastAddress)
247 {
248 UnicastAddressInfo *entry = mUnicastAddressHistory.AddNewEntry();
249
250 VerifyOrExit(entry != nullptr);
251
252 entry->mAddress = aUnicastAddress.GetAddress();
253 entry->mPrefixLength = aUnicastAddress.GetPrefixLength();
254 entry->mAddressOrigin = aUnicastAddress.GetOrigin();
255 entry->mEvent = (aEvent == Ip6::Netif::kAddressAdded) ? kAddressAdded : kAddressRemoved;
256 entry->mScope = (aUnicastAddress.GetScope() & 0xf);
257 entry->mPreferred = aUnicastAddress.mPreferred;
258 entry->mValid = aUnicastAddress.mValid;
259 entry->mRloc = aUnicastAddress.mRloc;
260
261 exit:
262 return;
263 }
264
RecordAddressEvent(Ip6::Netif::AddressEvent aEvent,const Ip6::Netif::MulticastAddress & aMulticastAddress,Ip6::Netif::AddressOrigin aAddressOrigin)265 void HistoryTracker::RecordAddressEvent(Ip6::Netif::AddressEvent aEvent,
266 const Ip6::Netif::MulticastAddress &aMulticastAddress,
267 Ip6::Netif::AddressOrigin aAddressOrigin)
268 {
269 MulticastAddressInfo *entry = mMulticastAddressHistory.AddNewEntry();
270
271 VerifyOrExit(entry != nullptr);
272
273 entry->mAddress = aMulticastAddress.GetAddress();
274 entry->mAddressOrigin = aAddressOrigin;
275 entry->mEvent = (aEvent == Ip6::Netif::kAddressAdded) ? kAddressAdded : kAddressRemoved;
276
277 exit:
278 return;
279 }
280
281 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_DATA
RecordNetworkDataChange(void)282 void HistoryTracker::RecordNetworkDataChange(void)
283 {
284 NetworkData::Iterator iterator;
285 NetworkData::OnMeshPrefixConfig prefix;
286 NetworkData::ExternalRouteConfig route;
287
288 // On mesh prefix entries
289
290 iterator = NetworkData::kIteratorInit;
291
292 while (mPreviousNetworkData.GetNextOnMeshPrefix(iterator, prefix) == kErrorNone)
293 {
294 if (!Get<NetworkData::Leader>().ContainsOnMeshPrefix(prefix))
295 {
296 RecordOnMeshPrefixEvent(kNetDataEntryRemoved, prefix);
297 }
298 }
299
300 iterator = NetworkData::kIteratorInit;
301
302 while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefix) == kErrorNone)
303 {
304 if (!mPreviousNetworkData.ContainsOnMeshPrefix(prefix))
305 {
306 RecordOnMeshPrefixEvent(kNetDataEntryAdded, prefix);
307 }
308 }
309
310 // External route entries
311
312 iterator = NetworkData::kIteratorInit;
313
314 while (mPreviousNetworkData.GetNextExternalRoute(iterator, route) == kErrorNone)
315 {
316 if (!Get<NetworkData::Leader>().ContainsExternalRoute(route))
317 {
318 RecordExternalRouteEvent(kNetDataEntryRemoved, route);
319 }
320 }
321
322 iterator = NetworkData::kIteratorInit;
323
324 while (Get<NetworkData::Leader>().GetNextExternalRoute(iterator, route) == kErrorNone)
325 {
326 if (!mPreviousNetworkData.ContainsExternalRoute(route))
327 {
328 RecordExternalRouteEvent(kNetDataEntryAdded, route);
329 }
330 }
331
332 SuccessOrAssert(Get<NetworkData::Leader>().CopyNetworkData(NetworkData::kFullSet, mPreviousNetworkData));
333 }
334
RecordOnMeshPrefixEvent(NetDataEvent aEvent,const NetworkData::OnMeshPrefixConfig & aPrefix)335 void HistoryTracker::RecordOnMeshPrefixEvent(NetDataEvent aEvent, const NetworkData::OnMeshPrefixConfig &aPrefix)
336 {
337 OnMeshPrefixInfo *entry = mOnMeshPrefixHistory.AddNewEntry();
338
339 VerifyOrExit(entry != nullptr);
340 entry->mPrefix = aPrefix;
341 entry->mEvent = aEvent;
342
343 exit:
344 return;
345 }
346
RecordExternalRouteEvent(NetDataEvent aEvent,const NetworkData::ExternalRouteConfig & aRoute)347 void HistoryTracker::RecordExternalRouteEvent(NetDataEvent aEvent, const NetworkData::ExternalRouteConfig &aRoute)
348 {
349 ExternalRouteInfo *entry = mExternalRouteHistory.AddNewEntry();
350
351 VerifyOrExit(entry != nullptr);
352 entry->mRoute = aRoute;
353 entry->mEvent = aEvent;
354
355 exit:
356 return;
357 }
358
359 #endif // OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_DATA
360
HandleNotifierEvents(Events aEvents)361 void HistoryTracker::HandleNotifierEvents(Events aEvents)
362 {
363 if (aEvents.ContainsAny(kEventThreadRoleChanged | kEventThreadRlocAdded | kEventThreadRlocRemoved |
364 kEventThreadPartitionIdChanged))
365 {
366 RecordNetworkInfo();
367 }
368
369 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_DATA
370 if (aEvents.Contains(kEventThreadNetdataChanged))
371 {
372 RecordNetworkDataChange();
373 }
374 #endif
375 }
376
HandleTimer(Timer & aTimer)377 void HistoryTracker::HandleTimer(Timer &aTimer)
378 {
379 aTimer.Get<HistoryTracker>().HandleTimer();
380 }
381
HandleTimer(void)382 void HistoryTracker::HandleTimer(void)
383 {
384 mNetInfoHistory.UpdateAgedEntries();
385 mUnicastAddressHistory.UpdateAgedEntries();
386 mMulticastAddressHistory.UpdateAgedEntries();
387 mRxHistory.UpdateAgedEntries();
388 mTxHistory.UpdateAgedEntries();
389 mNeighborHistory.UpdateAgedEntries();
390 mOnMeshPrefixHistory.UpdateAgedEntries();
391 mExternalRouteHistory.UpdateAgedEntries();
392
393 mTimer.Start(kAgeCheckPeriod);
394 }
395
EntryAgeToString(uint32_t aEntryAge,char * aBuffer,uint16_t aSize)396 void HistoryTracker::EntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize)
397 {
398 StringWriter writer(aBuffer, aSize);
399
400 if (aEntryAge >= kMaxAge)
401 {
402 writer.Append("more than %u days", kMaxAge / Time::kOneDayInMsec);
403 }
404 else
405 {
406 uint32_t days = aEntryAge / Time::kOneDayInMsec;
407
408 if (days > 0)
409 {
410 writer.Append("%u day%s ", days, (days == 1) ? "" : "s");
411 aEntryAge -= days * Time::kOneDayInMsec;
412 }
413
414 writer.Append("%02u:%02u:%02u.%03u", (aEntryAge / Time::kOneHourInMsec),
415 (aEntryAge % Time::kOneHourInMsec) / Time::kOneMinuteInMsec,
416 (aEntryAge % Time::kOneMinuteInMsec) / Time::kOneSecondInMsec,
417 (aEntryAge % Time::kOneSecondInMsec));
418 }
419 }
420
421 //---------------------------------------------------------------------------------------------------------------------
422 // HistoryTracker::Timestamp
423
SetToNow(void)424 void HistoryTracker::Timestamp::SetToNow(void)
425 {
426 mTime = TimerMilli::GetNow();
427
428 // If the current time happens to be the special value which we
429 // use to indicate "distant past", decrement the time by one.
430
431 if (mTime.GetValue() == kDistantPast)
432 {
433 mTime.SetValue(mTime.GetValue() - 1);
434 }
435 }
436
GetDurationTill(TimeMilli aTime) const437 uint32_t HistoryTracker::Timestamp::GetDurationTill(TimeMilli aTime) const
438 {
439 return IsDistantPast() ? kMaxAge : OT_MIN(aTime - mTime, kMaxAge);
440 }
441
442 //---------------------------------------------------------------------------------------------------------------------
443 // HistoryTracker::List
444
List(void)445 HistoryTracker::List::List(void)
446 : mStartIndex(0)
447 , mSize(0)
448 {
449 }
450
Clear(void)451 void HistoryTracker::List::Clear(void)
452 {
453 mStartIndex = 0;
454 mSize = 0;
455 }
456
Add(uint16_t aMaxSize,Timestamp aTimestamps[])457 uint16_t HistoryTracker::List::Add(uint16_t aMaxSize, Timestamp aTimestamps[])
458 {
459 // Add a new entry and return its list index. Overwrites the
460 // oldest entry if list is full.
461 //
462 // Entries are saved in the order they are added such that
463 // `mStartIndex` is the newest entry and the entries after up
464 // to `mSize` are the previously added entries.
465
466 mStartIndex = (mStartIndex == 0) ? aMaxSize - 1 : mStartIndex - 1;
467 mSize += (mSize == aMaxSize) ? 0 : 1;
468
469 aTimestamps[mStartIndex].SetToNow();
470
471 return mStartIndex;
472 }
473
Iterate(uint16_t aMaxSize,const Timestamp aTimestamps[],Iterator & aIterator,uint16_t & aListIndex,uint32_t & aEntryAge) const474 Error HistoryTracker::List::Iterate(uint16_t aMaxSize,
475 const Timestamp aTimestamps[],
476 Iterator & aIterator,
477 uint16_t & aListIndex,
478 uint32_t & aEntryAge) const
479 {
480 Error error = kErrorNone;
481
482 VerifyOrExit(aIterator.GetEntryNumber() < mSize, error = kErrorNotFound);
483
484 aListIndex = MapEntryNumberToListIndex(aIterator.GetEntryNumber(), aMaxSize);
485 aEntryAge = aTimestamps[aListIndex].GetDurationTill(aIterator.GetInitTime());
486
487 aIterator.IncrementEntryNumber();
488
489 exit:
490 return error;
491 }
492
MapEntryNumberToListIndex(uint16_t aEntryNumber,uint16_t aMaxSize) const493 uint16_t HistoryTracker::List::MapEntryNumberToListIndex(uint16_t aEntryNumber, uint16_t aMaxSize) const
494 {
495 // Map the `aEntryNumber` to the list index. `aEntryNumber` value
496 // of zero corresponds to the newest (the most recently added)
497 // entry and value one to next one and so on. List index
498 // warps at the end of array to start of array. Caller MUST
499 // ensure `aEntryNumber` is smaller than `mSize`.
500
501 uint32_t index;
502
503 OT_ASSERT(aEntryNumber < mSize);
504
505 index = static_cast<uint32_t>(aEntryNumber) + mStartIndex;
506 index -= (index >= aMaxSize) ? aMaxSize : 0;
507
508 return static_cast<uint16_t>(index);
509 }
510
UpdateAgedEntries(uint16_t aMaxSize,Timestamp aTimestamps[])511 void HistoryTracker::List::UpdateAgedEntries(uint16_t aMaxSize, Timestamp aTimestamps[])
512 {
513 TimeMilli now = TimerMilli::GetNow();
514
515 // We go through the entries in reverse (starting with the oldest
516 // entry) and check if the entry's age is larger than `kMaxAge`
517 // and if so mark it as "distant past". We can stop as soon as we
518 // get to an entry with age smaller than max.
519 //
520 // The `for()` loop condition is `(entryNumber < mSize)` which
521 // ensures that we go through the loop body for `entryNumber`
522 // value of zero and then in the next iteration (when the
523 // `entryNumber` rolls over) we stop.
524
525 for (uint16_t entryNumber = mSize - 1; entryNumber < mSize; entryNumber--)
526 {
527 uint16_t index = MapEntryNumberToListIndex(entryNumber, aMaxSize);
528
529 if (aTimestamps[index].GetDurationTill(now) < kMaxAge)
530 {
531 break;
532 }
533
534 aTimestamps[index].MarkAsDistantPast();
535 }
536 }
537
538 } // namespace Utils
539 } // namespace ot
540
541 #endif // #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
542