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 MPL.
32 */
33
34 #include "ip6_mpl.hpp"
35
36 #include "instance/instance.hpp"
37
38 namespace ot {
39 namespace Ip6 {
40
Mpl(Instance & aInstance)41 Mpl::Mpl(Instance &aInstance)
42 : InstanceLocator(aInstance)
43 , mSequence(0)
44 #if OPENTHREAD_FTD
45 , mRetransmissionTimer(aInstance)
46 #endif
47 {
48 ClearAllBytes(mSeedSet);
49 }
50
Init(SeedIdLength aSeedIdLength)51 void MplOption::Init(SeedIdLength aSeedIdLength)
52 {
53 SetType(kType);
54
55 switch (aSeedIdLength)
56 {
57 case kSeedIdLength0:
58 SetLength(sizeof(*this) - sizeof(Option) - sizeof(mSeedId));
59 break;
60 case kSeedIdLength2:
61 SetLength(sizeof(*this) - sizeof(Option));
62 break;
63 default:
64 OT_ASSERT(false);
65 }
66
67 mControl = aSeedIdLength;
68 }
69
InitOption(MplOption & aOption,const Address & aAddress)70 void Mpl::InitOption(MplOption &aOption, const Address &aAddress)
71 {
72 if (aAddress == Get<Mle::Mle>().GetMeshLocalRloc())
73 {
74 // Seed ID can be elided when `aAddress` is RLOC.
75 aOption.Init(MplOption::kSeedIdLength0);
76 }
77 else
78 {
79 aOption.Init(MplOption::kSeedIdLength2);
80 aOption.SetSeedId(Get<Mle::Mle>().GetRloc16());
81 }
82
83 aOption.SetSequence(mSequence++);
84 }
85
ProcessOption(Message & aMessage,const OffsetRange & aOffsetRange,const Address & aAddress,bool & aReceive)86 Error Mpl::ProcessOption(Message &aMessage, const OffsetRange &aOffsetRange, const Address &aAddress, bool &aReceive)
87 {
88 Error error;
89 MplOption option;
90
91 // Read the min size bytes first, then check the expected
92 // `SeedIdLength` and read the full `MplOption` if needed.
93 SuccessOrExit(error = aMessage.Read(aOffsetRange, &option, MplOption::kMinSize));
94
95 switch (option.GetSeedIdLength())
96 {
97 case MplOption::kSeedIdLength0:
98 // Retrieve Seed ID from the IPv6 Source Address RLOC.
99 VerifyOrExit(aAddress.GetIid().IsLocator(), error = kErrorDrop);
100 option.SetSeedId(aAddress.GetIid().GetLocator());
101 break;
102
103 case MplOption::kSeedIdLength2:
104 SuccessOrExit(error = aMessage.Read(aOffsetRange, option));
105 break;
106
107 case MplOption::kSeedIdLength8:
108 case MplOption::kSeedIdLength16:
109 ExitNow(error = kErrorParse);
110 }
111
112 // Check if the MPL Data Message is new.
113 error = UpdateSeedSet(option.GetSeedId(), option.GetSequence());
114
115 if (error == kErrorNone)
116 {
117 #if OPENTHREAD_FTD
118 AddBufferedMessage(aMessage, option.GetSeedId(), option.GetSequence());
119 #endif
120 }
121 else if (!aMessage.IsOriginThreadNetif())
122 {
123 aReceive = false;
124 // In case MPL Data Message is generated locally, ignore potential error of the MPL Seed Set
125 // to allow subsequent retransmissions with the same sequence number.
126 ExitNow(error = kErrorNone);
127 }
128
129 exit:
130 return error;
131 }
132
133 /*
134 * mSeedSet stores recently received (Seed ID, Sequence) values.
135 * - (Seed ID, Sequence) values are grouped by Seed ID.
136 * - (Seed ID, Sequence) groups are not sorted by Seed ID relative to other groups.
137 * - (Seed ID, Sequence) values within a group are sorted by Sequence.
138 * - All unused entries (marked by 0 lifetime) are grouped at the end.
139 *
140 * Update process:
141 *
142 * - Eviction selection:
143 * - If there are unused entries, mark the first unused entry for "eviction"
144 * - Otherwise, pick the first entry of the group that has the most entries.
145 *
146 * - Insert selection:
147 * - If there exists a group matching the Seed ID, select insert entry based on Sequence ordering.
148 * - Otherwise, set insert entry equal to evict entry.
149 *
150 * - If evicting a valid entry (lifetime non-zero):
151 * - Require group size to have >=2 entries.
152 * - If inserting into existing group, require Sequence to be larger than oldest stored Sequence in group.
153 */
UpdateSeedSet(uint16_t aSeedId,uint8_t aSequence)154 Error Mpl::UpdateSeedSet(uint16_t aSeedId, uint8_t aSequence)
155 {
156 Error error = kErrorNone;
157 SeedEntry *insert = nullptr;
158 SeedEntry *group = mSeedSet;
159 SeedEntry *evict = mSeedSet;
160 uint8_t curCount = 0;
161 uint8_t maxCount = 0;
162
163 for (uint32_t i = 0; i < kNumSeedEntries; i++, curCount++)
164 {
165 if (mSeedSet[i].mLifetime == 0)
166 {
167 // unused entries exist
168
169 if (insert == nullptr)
170 {
171 // no existing group, set insert and evict entry to be the same
172 insert = &mSeedSet[i];
173 }
174
175 // mark first unused entry for eviction
176 evict = &mSeedSet[i];
177 break;
178 }
179
180 if (mSeedSet[i].mSeedId != group->mSeedId)
181 {
182 // processing new group
183
184 if (aSeedId == group->mSeedId && insert == nullptr)
185 {
186 // insert at end of existing group
187 insert = &mSeedSet[i];
188 curCount++;
189 }
190
191 if (maxCount < curCount)
192 {
193 // look to evict an entry from the seed with the most entries
194 evict = group;
195 maxCount = curCount;
196 }
197
198 group = &mSeedSet[i];
199 curCount = 0;
200 }
201
202 if (aSeedId == mSeedSet[i].mSeedId)
203 {
204 // have existing entries for aSeedId
205
206 if (aSequence == mSeedSet[i].mSequence)
207 {
208 // already received, drop message
209
210 mSeedSet[i].mLifetime = kSeedEntryLifetime;
211 ExitNow(error = kErrorDrop);
212 }
213 else if (insert == nullptr && SerialNumber::IsLess(aSequence, mSeedSet[i].mSequence))
214 {
215 // insert in order of sequence
216 insert = &mSeedSet[i];
217 curCount++;
218 }
219 }
220 }
221
222 if (evict->mLifetime != 0)
223 {
224 // no free entries available, look to evict an existing entry
225 OT_ASSERT(curCount != 0);
226
227 if (aSeedId == group->mSeedId && insert == nullptr)
228 {
229 // insert at end of existing group
230 insert = &mSeedSet[kNumSeedEntries];
231 curCount++;
232 }
233
234 if (maxCount < curCount)
235 {
236 // look to evict an entry from the seed with the most entries
237 evict = group;
238 maxCount = curCount;
239 }
240
241 // require evict group size to have >= 2 entries
242 VerifyOrExit(maxCount > 1, error = kErrorDrop);
243
244 if (insert == nullptr)
245 {
246 // no existing entries for aSeedId
247 insert = evict;
248 }
249 else
250 {
251 // require Sequence to be larger than oldest stored Sequence in group
252 VerifyOrExit(insert > mSeedSet && aSeedId == (insert - 1)->mSeedId, error = kErrorDrop);
253 }
254 }
255
256 if (evict > insert)
257 {
258 OT_ASSERT(insert >= mSeedSet);
259 memmove(insert + 1, insert, static_cast<size_t>(evict - insert) * sizeof(SeedEntry));
260 }
261 else if (evict < insert)
262 {
263 OT_ASSERT(evict >= mSeedSet);
264 memmove(evict, evict + 1, static_cast<size_t>(insert - 1 - evict) * sizeof(SeedEntry));
265 insert--;
266 }
267
268 insert->mSeedId = aSeedId;
269 insert->mSequence = aSequence;
270 insert->mLifetime = kSeedEntryLifetime;
271
272 Get<TimeTicker>().RegisterReceiver(TimeTicker::kIp6Mpl);
273
274 exit:
275 return error;
276 }
277
HandleTimeTick(void)278 void Mpl::HandleTimeTick(void)
279 {
280 bool continueRxingTicks = false;
281 int j = 0;
282
283 for (int i = 0; i < kNumSeedEntries && mSeedSet[i].mLifetime; i++)
284 {
285 mSeedSet[i].mLifetime--;
286
287 if (mSeedSet[i].mLifetime > 0)
288 {
289 mSeedSet[j++] = mSeedSet[i];
290 continueRxingTicks = true;
291 }
292 }
293
294 for (; j < kNumSeedEntries && mSeedSet[j].mLifetime; j++)
295 {
296 mSeedSet[j].mLifetime = 0;
297 }
298
299 if (!continueRxingTicks)
300 {
301 Get<TimeTicker>().UnregisterReceiver(TimeTicker::kIp6Mpl);
302 }
303 }
304
305 #if OPENTHREAD_FTD
306
DetermineMaxRetransmissions(void) const307 uint8_t Mpl::DetermineMaxRetransmissions(void) const
308 {
309 uint8_t maxRetx = 0;
310
311 switch (Get<Mle::Mle>().GetRole())
312 {
313 case Mle::kRoleDisabled:
314 case Mle::kRoleDetached:
315 break;
316
317 case Mle::kRoleChild:
318 maxRetx = kChildRetransmissions;
319 break;
320
321 case Mle::kRoleRouter:
322 case Mle::kRoleLeader:
323 maxRetx = kRouterRetransmissions;
324 break;
325 }
326
327 return maxRetx;
328 }
329
AddBufferedMessage(Message & aMessage,uint16_t aSeedId,uint8_t aSequence)330 void Mpl::AddBufferedMessage(Message &aMessage, uint16_t aSeedId, uint8_t aSequence)
331 {
332 Error error = kErrorNone;
333 Message *messageCopy = nullptr;
334 Metadata metadata;
335 uint8_t hopLimit = 0;
336 uint8_t interval;
337
338 #if OPENTHREAD_CONFIG_MPL_DYNAMIC_INTERVAL_ENABLE
339 // adjust the first MPL forward interval dynamically according to the network scale
340 interval = (kDataMessageInterval / Mle::kMaxRouters) * Get<RouterTable>().GetNeighborCount(kLinkQuality1);
341 #else
342 interval = kDataMessageInterval;
343 #endif
344
345 VerifyOrExit(DetermineMaxRetransmissions() > 0);
346 VerifyOrExit((messageCopy = aMessage.Clone()) != nullptr, error = kErrorNoBufs);
347
348 if (aMessage.IsOriginThreadNetif())
349 {
350 IgnoreError(aMessage.Read(Header::kHopLimitFieldOffset, hopLimit));
351 VerifyOrExit(hopLimit-- > 1, error = kErrorDrop);
352 messageCopy->Write(Header::kHopLimitFieldOffset, hopLimit);
353 }
354
355 // If the message originates from Thread Netif (i.e., it was
356 // received over Thread radio), set the `mTransmissionCount` to
357 // zero. Otherwise, the message originates from the host and will
358 // be forwarded by `Ip6` to the Thread mesh, so the message itself
359 // will be the first transmission and we set `mTransmissionCount`
360 // to one.
361
362 metadata.mSeedId = aSeedId;
363 metadata.mSequence = aSequence;
364 metadata.mTransmissionCount = aMessage.IsOriginThreadNetif() ? 0 : 1;
365 metadata.mIntervalOffset = 0;
366 metadata.GenerateNextTransmissionTime(TimerMilli::GetNow(), interval);
367
368 SuccessOrExit(error = metadata.AppendTo(*messageCopy));
369 mBufferedMessageSet.Enqueue(*messageCopy);
370
371 mRetransmissionTimer.FireAtIfEarlier(metadata.mTransmissionTime);
372
373 exit:
374 FreeMessageOnError(messageCopy, error);
375 }
376
HandleRetransmissionTimer(void)377 void Mpl::HandleRetransmissionTimer(void)
378 {
379 NextFireTime nextTime;
380
381 for (Message &message : mBufferedMessageSet)
382 {
383 Metadata metadata;
384 Message *messageCopy;
385 uint8_t maxRetx;
386
387 metadata.ReadFrom(message);
388
389 if (nextTime.GetNow() < metadata.mTransmissionTime)
390 {
391 nextTime.UpdateIfEarlier(metadata.mTransmissionTime);
392 continue;
393 }
394
395 metadata.mTransmissionCount++;
396
397 maxRetx = DetermineMaxRetransmissions();
398
399 if (metadata.mTransmissionCount > maxRetx)
400 {
401 // If the number of tx already exceeds the limit, remove
402 // the message. This situation can potentially happen on
403 // a device role change, which then updates the max MPL
404 // retx.
405
406 mBufferedMessageSet.DequeueAndFree(message);
407 continue;
408 }
409
410 if (metadata.mTransmissionCount < maxRetx)
411 {
412 metadata.GenerateNextTransmissionTime(nextTime.GetNow(), kDataMessageInterval);
413 metadata.UpdateIn(message);
414
415 nextTime.UpdateIfEarlier(metadata.mTransmissionTime);
416
417 messageCopy = message.Clone();
418 }
419 else
420 {
421 // This is the last retx of message, we can use the
422 // `message` directly.
423
424 mBufferedMessageSet.Dequeue(message);
425 messageCopy = &message;
426 }
427
428 if (messageCopy != nullptr)
429 {
430 if (metadata.mTransmissionCount > 1)
431 {
432 // Mark all transmissions after the first one as "MPL
433 // retx". This is used to decide whether to send this
434 // message to the device's sleepy children.
435
436 messageCopy->SetSubType(Message::kSubTypeMplRetransmission);
437 }
438
439 metadata.RemoveFrom(*messageCopy);
440 messageCopy->SetLoopbackToHostAllowed(true);
441 messageCopy->SetOrigin(Message::kOriginHostTrusted);
442 Get<Ip6>().EnqueueDatagram(*messageCopy);
443 }
444 }
445
446 mRetransmissionTimer.FireAt(nextTime);
447 }
448
GenerateNextTransmissionTime(TimeMilli aCurrentTime,uint8_t aInterval)449 void Mpl::Metadata::GenerateNextTransmissionTime(TimeMilli aCurrentTime, uint8_t aInterval)
450 {
451 // Emulate Trickle timer behavior and set up the next retransmission within [0,I) range.
452 uint8_t t = (aInterval == 0) ? aInterval : Random::NonCrypto::GetUint8InRange(0, aInterval);
453
454 // Set transmission time at the beginning of the next interval.
455 mTransmissionTime = aCurrentTime + static_cast<uint32_t>(mIntervalOffset + t);
456 mIntervalOffset = aInterval - t;
457 }
458
459 #endif // OPENTHREAD_FTD
460
461 } // namespace Ip6
462 } // namespace ot
463