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/code_utils.hpp"
39 #include "common/debug.hpp"
40 #include "common/instance.hpp"
41 #include "common/locator_getters.hpp"
42 #include "common/string.hpp"
43 #include "common/timer.hpp"
44
45 namespace ot {
46 namespace Utils {
47
48 //---------------------------------------------------------------------------------------------------------------------
49 // HistoryTracker
50
HistoryTracker(Instance & aInstance)51 HistoryTracker::HistoryTracker(Instance &aInstance)
52 : InstanceLocator(aInstance)
53 , mTimer(aInstance, HandleTimer)
54 {
55 mTimer.Start(kAgeCheckPeriod);
56 }
57
RecordNetworkInfo(void)58 void HistoryTracker::RecordNetworkInfo(void)
59 {
60 NetworkInfo * entry = mNetInfoHistory.AddNewEntry();
61 Mle::DeviceMode mode;
62
63 VerifyOrExit(entry != nullptr);
64
65 entry->mRole = static_cast<otDeviceRole>(Get<Mle::Mle>().GetRole());
66 entry->mRloc16 = Get<Mle::Mle>().GetRloc16();
67 entry->mPartitionId = Get<Mle::Mle>().GetLeaderData().GetPartitionId();
68 mode = Get<Mle::Mle>().GetDeviceMode();
69 mode.Get(entry->mMode);
70
71 exit:
72 return;
73 }
74
RecordMessage(const Message & aMessage,const Mac::Address & aMacAddresss,MessageType aType)75 void HistoryTracker::RecordMessage(const Message &aMessage, const Mac::Address &aMacAddresss, MessageType aType)
76 {
77 MessageInfo * entry = nullptr;
78 Ip6::Header ip6Header;
79 Ip6::Icmp::Header icmp6Header;
80 uint8_t ip6Proto;
81 uint16_t checksum;
82 uint16_t sourcePort;
83 uint16_t destPort;
84
85 VerifyOrExit(aMessage.GetType() == Message::kTypeIp6);
86
87 SuccessOrExit(MeshForwarder::ParseIp6UdpTcpHeader(aMessage, ip6Header, checksum, sourcePort, destPort));
88
89 ip6Proto = ip6Header.GetNextHeader();
90
91 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_EXCLUDE_THREAD_CONTROL_MESSAGES
92 if (ip6Proto == Ip6::kProtoUdp)
93 {
94 uint16_t port = 0;
95
96 switch (aType)
97 {
98 case kRxMessage:
99 port = destPort;
100 break;
101
102 case kTxMessage:
103 port = sourcePort;
104 break;
105 }
106
107 VerifyOrExit((port != Mle::kUdpPort) && (port != Tmf::kUdpPort));
108 }
109 #endif
110
111 if (ip6Proto == Ip6::kProtoIcmp6)
112 {
113 SuccessOrExit(aMessage.Read(sizeof(Ip6::Header), icmp6Header));
114 checksum = icmp6Header.GetChecksum();
115 }
116 else
117 {
118 icmp6Header.Clear();
119 }
120
121 switch (aType)
122 {
123 case kRxMessage:
124 entry = mRxHistory.AddNewEntry();
125 break;
126
127 case kTxMessage:
128 entry = mTxHistory.AddNewEntry();
129 break;
130 }
131
132 VerifyOrExit(entry != nullptr);
133
134 entry->mPayloadLength = ip6Header.GetPayloadLength();
135 entry->mNeighborRloc16 = aMacAddresss.IsShort() ? aMacAddresss.GetShort() : kInvalidRloc16;
136 entry->mSource.mAddress = ip6Header.GetSource();
137 entry->mSource.mPort = sourcePort;
138 entry->mDestination.mAddress = ip6Header.GetDestination();
139 entry->mDestination.mPort = destPort;
140 entry->mChecksum = checksum;
141 entry->mIpProto = ip6Proto;
142 entry->mIcmp6Type = icmp6Header.GetType();
143 entry->mAveRxRss = (aType == kRxMessage) ? aMessage.GetRssAverager().GetAverage() : kInvalidRss;
144 entry->mLinkSecurity = aMessage.IsLinkSecurityEnabled();
145 entry->mTxSuccess = (aType == kTxMessage) ? aMessage.GetTxSuccess() : true;
146 entry->mPriority = aMessage.GetPriority();
147
148 if (aMacAddresss.IsExtended())
149 {
150 Neighbor *neighbor = Get<NeighborTable>().FindNeighbor(aMacAddresss, Neighbor::kInStateAnyExceptInvalid);
151
152 if (neighbor != nullptr)
153 {
154 entry->mNeighborRloc16 = neighbor->GetRloc16();
155 }
156 }
157
158 #if OPENTHREAD_CONFIG_MULTI_RADIO
159 if (aMessage.IsRadioTypeSet())
160 {
161 switch (aMessage.GetRadioType())
162 {
163 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
164 case Mac::kRadioTypeIeee802154:
165 entry->mRadioIeee802154 = true;
166 break;
167 #endif
168 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
169 case Mac::kRadioTypeTrel:
170 entry->mRadioTrelUdp6 = true;
171 break;
172 #endif
173 }
174
175 // Radio type may not be set on a tx message indicating that it
176 // was sent over all radio types (e.g., for broadcast frame).
177 // In such a case, we set all supported radios from `else`
178 // block below.
179 }
180 else
181 #endif // OPENTHREAD_CONFIG_MULTI_RADIO
182 {
183 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
184 entry->mRadioIeee802154 = true;
185 #endif
186
187 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
188 entry->mRadioTrelUdp6 = true;
189 #endif
190 }
191
192 exit:
193 return;
194 }
195
RecordNeighborEvent(NeighborTable::Event aEvent,const NeighborTable::EntryInfo & aInfo)196 void HistoryTracker::RecordNeighborEvent(NeighborTable::Event aEvent, const NeighborTable::EntryInfo &aInfo)
197 {
198 NeighborInfo *entry = mNeighborHistory.AddNewEntry();
199
200 VerifyOrExit(entry != nullptr);
201
202 switch (aEvent)
203 {
204 case NeighborTable::kChildAdded:
205 case NeighborTable::kChildRemoved:
206 case NeighborTable::kChildModeChanged:
207 entry->mExtAddress = aInfo.mInfo.mChild.mExtAddress;
208 entry->mRloc16 = aInfo.mInfo.mChild.mRloc16;
209 entry->mAverageRssi = aInfo.mInfo.mChild.mAverageRssi;
210 entry->mRxOnWhenIdle = aInfo.mInfo.mChild.mRxOnWhenIdle;
211 entry->mFullThreadDevice = aInfo.mInfo.mChild.mFullThreadDevice;
212 entry->mFullNetworkData = aInfo.mInfo.mChild.mFullNetworkData;
213 entry->mIsChild = true;
214 break;
215
216 case NeighborTable::kRouterAdded:
217 case NeighborTable::kRouterRemoved:
218 entry->mExtAddress = aInfo.mInfo.mRouter.mExtAddress;
219 entry->mRloc16 = aInfo.mInfo.mRouter.mRloc16;
220 entry->mAverageRssi = aInfo.mInfo.mRouter.mAverageRssi;
221 entry->mRxOnWhenIdle = aInfo.mInfo.mRouter.mRxOnWhenIdle;
222 entry->mFullThreadDevice = aInfo.mInfo.mRouter.mFullThreadDevice;
223 entry->mFullNetworkData = aInfo.mInfo.mRouter.mFullNetworkData;
224 entry->mIsChild = false;
225 break;
226 }
227
228 switch (aEvent)
229 {
230 case NeighborTable::kChildAdded:
231 if (aInfo.mInfo.mChild.mIsStateRestoring)
232 {
233 entry->mEvent = kNeighborRestoring;
234 break;
235 }
236
237 OT_FALL_THROUGH;
238
239 case NeighborTable::kRouterAdded:
240 entry->mEvent = kNeighborAdded;
241 break;
242
243 case NeighborTable::kChildRemoved:
244 case NeighborTable::kRouterRemoved:
245 entry->mEvent = kNeighborRemoved;
246 break;
247
248 case NeighborTable::kChildModeChanged:
249 entry->mEvent = kNeighborChanged;
250 break;
251 }
252
253 exit:
254 return;
255 }
256
HandleNotifierEvents(Events aEvents)257 void HistoryTracker::HandleNotifierEvents(Events aEvents)
258 {
259 if (aEvents.ContainsAny(kEventThreadRoleChanged | kEventThreadRlocAdded | kEventThreadRlocRemoved |
260 kEventThreadPartitionIdChanged))
261 {
262 RecordNetworkInfo();
263 }
264 }
265
HandleTimer(Timer & aTimer)266 void HistoryTracker::HandleTimer(Timer &aTimer)
267 {
268 aTimer.Get<HistoryTracker>().HandleTimer();
269 }
270
HandleTimer(void)271 void HistoryTracker::HandleTimer(void)
272 {
273 mNetInfoHistory.UpdateAgedEntries();
274 mRxHistory.UpdateAgedEntries();
275 mTxHistory.UpdateAgedEntries();
276 mNeighborHistory.UpdateAgedEntries();
277
278 mTimer.Start(kAgeCheckPeriod);
279 }
280
EntryAgeToString(uint32_t aEntryAge,char * aBuffer,uint16_t aSize)281 void HistoryTracker::EntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize)
282 {
283 StringWriter writer(aBuffer, aSize);
284
285 if (aEntryAge >= kMaxAge)
286 {
287 writer.Append("more than %u days", kMaxAge / kOneDayInMsec);
288 }
289 else
290 {
291 uint32_t days = aEntryAge / kOneDayInMsec;
292
293 if (days > 0)
294 {
295 writer.Append("%u day%s ", days, (days == 1) ? "" : "s");
296 aEntryAge -= days * kOneDayInMsec;
297 }
298
299 writer.Append("%02u:%02u:%02u.%03u", (aEntryAge / kOneHourInMsec),
300 (aEntryAge % kOneDayInMsec) / kOneMinuteInMsec, (aEntryAge % kOneMinuteInMsec) / kOneSecondInMsec,
301 (aEntryAge % kOneSecondInMsec));
302 }
303 }
304
305 //---------------------------------------------------------------------------------------------------------------------
306 // HistoryTracker::Timestamp
307
SetToNow(void)308 void HistoryTracker::Timestamp::SetToNow(void)
309 {
310 mTime = TimerMilli::GetNow();
311
312 // If the current time happens to be the special value which we
313 // use to indicate "distant past", decrement the time by one.
314
315 if (mTime.GetValue() == kDistantPast)
316 {
317 mTime.SetValue(mTime.GetValue() - 1);
318 }
319 }
320
GetDurationTill(TimeMilli aTime) const321 uint32_t HistoryTracker::Timestamp::GetDurationTill(TimeMilli aTime) const
322 {
323 return IsDistantPast() ? kMaxAge : OT_MIN(aTime - mTime, kMaxAge);
324 }
325
326 //---------------------------------------------------------------------------------------------------------------------
327 // HistoryTracker::List
328
List(void)329 HistoryTracker::List::List(void)
330 : mStartIndex(0)
331 , mSize(0)
332 {
333 }
334
Clear(void)335 void HistoryTracker::List::Clear(void)
336 {
337 mStartIndex = 0;
338 mSize = 0;
339 }
340
Add(uint16_t aMaxSize,Timestamp aTimestamps[])341 uint16_t HistoryTracker::List::Add(uint16_t aMaxSize, Timestamp aTimestamps[])
342 {
343 // Add a new entry and return its list index. Overwrites the
344 // oldest entry if list is full.
345 //
346 // Entries are saved in the order they are added such that
347 // `mStartIndex` is the newest entry and the entries after up
348 // to `mSize` are the previously added entries.
349
350 mStartIndex = (mStartIndex == 0) ? aMaxSize - 1 : mStartIndex - 1;
351 mSize += (mSize == aMaxSize) ? 0 : 1;
352
353 aTimestamps[mStartIndex].SetToNow();
354
355 return mStartIndex;
356 }
357
Iterate(uint16_t aMaxSize,const Timestamp aTimestamps[],Iterator & aIterator,uint16_t & aListIndex,uint32_t & aEntryAge) const358 Error HistoryTracker::List::Iterate(uint16_t aMaxSize,
359 const Timestamp aTimestamps[],
360 Iterator & aIterator,
361 uint16_t & aListIndex,
362 uint32_t & aEntryAge) const
363 {
364 Error error = kErrorNone;
365
366 VerifyOrExit(aIterator.GetEntryNumber() < mSize, error = kErrorNotFound);
367
368 aListIndex = MapEntryNumberToListIndex(aIterator.GetEntryNumber(), aMaxSize);
369 aEntryAge = aTimestamps[aListIndex].GetDurationTill(aIterator.GetInitTime());
370
371 aIterator.IncrementEntryNumber();
372
373 exit:
374 return error;
375 }
376
MapEntryNumberToListIndex(uint16_t aEntryNumber,uint16_t aMaxSize) const377 uint16_t HistoryTracker::List::MapEntryNumberToListIndex(uint16_t aEntryNumber, uint16_t aMaxSize) const
378 {
379 // Map the `aEntryNumber` to the list index. `aEntryNumber` value
380 // of zero corresponds to the newest (the most recently added)
381 // entry and value one to next one and so on. List index
382 // warps at the end of array to start of array. Caller MUST
383 // ensure `aEntryNumber` is smaller than `mSize`.
384
385 uint32_t index;
386
387 OT_ASSERT(aEntryNumber < mSize);
388
389 index = static_cast<uint32_t>(aEntryNumber) + mStartIndex;
390 index -= (index >= aMaxSize) ? aMaxSize : 0;
391
392 return static_cast<uint16_t>(index);
393 }
394
UpdateAgedEntries(uint16_t aMaxSize,Timestamp aTimestamps[])395 void HistoryTracker::List::UpdateAgedEntries(uint16_t aMaxSize, Timestamp aTimestamps[])
396 {
397 TimeMilli now = TimerMilli::GetNow();
398
399 // We go through the entries in reverse (starting with the oldest
400 // entry) and check if the entry's age is larger than `kMaxAge`
401 // and if so mark it as "distant past". We can stop as soon as we
402 // get to an entry with age smaller than max.
403 //
404 // The `for()` loop condition is `(entryNumber < mSize)` which
405 // ensures that we go through the loop body for `entryNumber`
406 // value of zero and then in the next iteration (when the
407 // `entryNumber` rolls over) we stop.
408
409 for (uint16_t entryNumber = mSize - 1; entryNumber < mSize; entryNumber--)
410 {
411 uint16_t index = MapEntryNumberToListIndex(entryNumber, aMaxSize);
412
413 if (aTimestamps[index].GetDurationTill(now) < kMaxAge)
414 {
415 break;
416 }
417
418 aTimestamps[index].MarkAsDistantPast();
419 }
420 }
421
422 } // namespace Utils
423 } // namespace ot
424
425 #endif // #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
426