• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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