• 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 DHCPv6 Client.
32  */
33 
34 #include "dhcp6_client.hpp"
35 
36 #if OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
37 
38 #include "instance/instance.hpp"
39 
40 namespace ot {
41 namespace Dhcp6 {
42 
43 RegisterLogModule("Dhcp6Client");
44 
Client(Instance & aInstance)45 Client::Client(Instance &aInstance)
46     : InstanceLocator(aInstance)
47     , mSocket(aInstance, *this)
48     , mTrickleTimer(aInstance, Client::HandleTrickleTimer)
49     , mStartTime(0)
50     , mIdentityAssociationCurrent(nullptr)
51 {
52     ClearAllBytes(mIdentityAssociations);
53 }
54 
MatchNetifAddressWithPrefix(const Ip6::Netif::UnicastAddress & aNetifAddress,const Ip6::Prefix & aIp6Prefix)55 bool Client::MatchNetifAddressWithPrefix(const Ip6::Netif::UnicastAddress &aNetifAddress, const Ip6::Prefix &aIp6Prefix)
56 {
57     return aNetifAddress.HasPrefix(aIp6Prefix);
58 }
59 
UpdateAddresses(void)60 void Client::UpdateAddresses(void)
61 {
62     bool                            found          = false;
63     bool                            doesAgentExist = false;
64     NetworkData::Iterator           iterator;
65     NetworkData::OnMeshPrefixConfig config;
66 
67     // remove addresses directly if prefix not valid in network data
68     for (IdentityAssociation &idAssociation : mIdentityAssociations)
69     {
70         if (idAssociation.mStatus == kIaStatusInvalid || idAssociation.mValidLifetime == 0)
71         {
72             continue;
73         }
74 
75         found    = false;
76         iterator = NetworkData::kIteratorInit;
77 
78         while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == kErrorNone)
79         {
80             if (!config.mDhcp)
81             {
82                 continue;
83             }
84 
85             if (MatchNetifAddressWithPrefix(idAssociation.mNetifAddress, config.GetPrefix()))
86             {
87                 found = true;
88                 break;
89             }
90         }
91 
92         if (!found)
93         {
94             Get<ThreadNetif>().RemoveUnicastAddress(idAssociation.mNetifAddress);
95             idAssociation.mStatus = kIaStatusInvalid;
96         }
97     }
98 
99     // add IdentityAssociation for new configured prefix
100     iterator = NetworkData::kIteratorInit;
101 
102     while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == kErrorNone)
103     {
104         IdentityAssociation *idAssociation = nullptr;
105 
106         if (!config.mDhcp)
107         {
108             continue;
109         }
110 
111         doesAgentExist = true;
112         found          = false;
113 
114         for (IdentityAssociation &ia : mIdentityAssociations)
115         {
116             if (ia.mStatus == kIaStatusInvalid)
117             {
118                 // record an available IdentityAssociation
119                 if (idAssociation == nullptr)
120                 {
121                     idAssociation = &ia;
122                 }
123             }
124             else if (MatchNetifAddressWithPrefix(ia.mNetifAddress, config.GetPrefix()))
125             {
126                 found         = true;
127                 idAssociation = &ia;
128                 break;
129             }
130         }
131 
132         if (!found)
133         {
134             if (idAssociation != nullptr)
135             {
136                 idAssociation->mNetifAddress.mAddress      = config.mPrefix.mPrefix;
137                 idAssociation->mNetifAddress.mPrefixLength = config.mPrefix.mLength;
138                 idAssociation->mStatus                     = kIaStatusSolicit;
139                 idAssociation->mValidLifetime              = 0;
140             }
141             else
142             {
143                 LogWarn("Insufficient memory for new DHCP prefix");
144                 continue;
145             }
146         }
147 
148         idAssociation->mPrefixAgentRloc = config.mRloc16;
149     }
150 
151     if (doesAgentExist)
152     {
153         Start();
154     }
155     else
156     {
157         Stop();
158     }
159 }
160 
Start(void)161 void Client::Start(void)
162 {
163     VerifyOrExit(!mSocket.IsBound());
164 
165     IgnoreError(mSocket.Open(Ip6::kNetifThreadInternal));
166     IgnoreError(mSocket.Bind(kDhcpClientPort));
167 
168     ProcessNextIdentityAssociation();
169 
170 exit:
171     return;
172 }
173 
Stop(void)174 void Client::Stop(void)
175 {
176     mTrickleTimer.Stop();
177     IgnoreError(mSocket.Close());
178 }
179 
ProcessNextIdentityAssociation(void)180 bool Client::ProcessNextIdentityAssociation(void)
181 {
182     bool rval = false;
183 
184     // not interrupt in-progress solicit
185     VerifyOrExit(mIdentityAssociationCurrent == nullptr || mIdentityAssociationCurrent->mStatus != kIaStatusSoliciting);
186 
187     mTrickleTimer.Stop();
188 
189     for (IdentityAssociation &idAssociation : mIdentityAssociations)
190     {
191         if (idAssociation.mStatus != kIaStatusSolicit)
192         {
193             continue;
194         }
195 
196         // new transaction id
197         IgnoreError(mTransactionId.GenerateRandom());
198 
199         mIdentityAssociationCurrent = &idAssociation;
200 
201         mTrickleTimer.Start(TrickleTimer::kModeTrickle, Time::SecToMsec(kTrickleTimerImin),
202                             Time::SecToMsec(kTrickleTimerImax));
203 
204         mTrickleTimer.IndicateInconsistent();
205 
206         ExitNow(rval = true);
207     }
208 
209 exit:
210     return rval;
211 }
212 
HandleTrickleTimer(TrickleTimer & aTrickleTimer)213 void Client::HandleTrickleTimer(TrickleTimer &aTrickleTimer) { aTrickleTimer.Get<Client>().HandleTrickleTimer(); }
214 
HandleTrickleTimer(void)215 void Client::HandleTrickleTimer(void)
216 {
217     OT_ASSERT(mSocket.IsBound());
218 
219     VerifyOrExit(mIdentityAssociationCurrent != nullptr, mTrickleTimer.Stop());
220 
221     switch (mIdentityAssociationCurrent->mStatus)
222     {
223     case kIaStatusSolicit:
224         mStartTime                           = TimerMilli::GetNow();
225         mIdentityAssociationCurrent->mStatus = kIaStatusSoliciting;
226 
227         OT_FALL_THROUGH;
228 
229     case kIaStatusSoliciting:
230         Solicit(mIdentityAssociationCurrent->mPrefixAgentRloc);
231         break;
232 
233     case kIaStatusSolicitReplied:
234         mIdentityAssociationCurrent = nullptr;
235 
236         if (!ProcessNextIdentityAssociation())
237         {
238             Stop();
239             mTrickleTimer.Stop();
240         }
241 
242         break;
243 
244     default:
245         break;
246     }
247 
248 exit:
249     return;
250 }
251 
Solicit(uint16_t aRloc16)252 void Client::Solicit(uint16_t aRloc16)
253 {
254     Error            error = kErrorNone;
255     Message         *message;
256     Ip6::MessageInfo messageInfo;
257 
258     VerifyOrExit((message = mSocket.NewMessage()) != nullptr, error = kErrorNoBufs);
259 
260     SuccessOrExit(error = AppendHeader(*message));
261     SuccessOrExit(error = AppendElapsedTime(*message));
262     SuccessOrExit(error = AppendClientIdentifier(*message));
263     SuccessOrExit(error = AppendIaNa(*message, aRloc16));
264     // specify which prefixes to solicit
265     SuccessOrExit(error = AppendIaAddress(*message, aRloc16));
266     SuccessOrExit(error = AppendRapidCommit(*message));
267 
268 #if OPENTHREAD_ENABLE_DHCP6_MULTICAST_SOLICIT
269     messageInfo.GetPeerAddr().SetToRealmLocalAllRoutersMulticast();
270 #else
271     messageInfo.GetPeerAddr().SetToRoutingLocator(Get<Mle::MleRouter>().GetMeshLocalPrefix(), aRloc16);
272 #endif
273     messageInfo.SetSockAddr(Get<Mle::MleRouter>().GetMeshLocalRloc());
274     messageInfo.mPeerPort = kDhcpServerPort;
275 
276     SuccessOrExit(error = mSocket.SendTo(*message, messageInfo));
277     LogInfo("solicit");
278 
279 exit:
280     if (error != kErrorNone)
281     {
282         FreeMessage(message);
283         LogWarnOnError(error, "send DHCPv6 Solicit");
284     }
285 }
286 
AppendHeader(Message & aMessage)287 Error Client::AppendHeader(Message &aMessage)
288 {
289     Header header;
290 
291     header.Clear();
292     header.SetType(kTypeSolicit);
293     header.SetTransactionId(mTransactionId);
294     return aMessage.Append(header);
295 }
296 
AppendElapsedTime(Message & aMessage)297 Error Client::AppendElapsedTime(Message &aMessage)
298 {
299     ElapsedTime option;
300 
301     option.Init();
302     option.SetElapsedTime(static_cast<uint16_t>(Time::MsecToSec(TimerMilli::GetNow() - mStartTime)));
303     return aMessage.Append(option);
304 }
305 
AppendClientIdentifier(Message & aMessage)306 Error Client::AppendClientIdentifier(Message &aMessage)
307 {
308     ClientIdentifier option;
309     Mac::ExtAddress  eui64;
310 
311     Get<Radio>().GetIeeeEui64(eui64);
312 
313     option.Init();
314     option.SetDuidType(kDuidLinkLayerAddress);
315     option.SetDuidHardwareType(kHardwareTypeEui64);
316     option.SetDuidLinkLayerAddress(eui64);
317 
318     return aMessage.Append(option);
319 }
320 
AppendIaNa(Message & aMessage,uint16_t aRloc16)321 Error Client::AppendIaNa(Message &aMessage, uint16_t aRloc16)
322 {
323     Error    error  = kErrorNone;
324     uint8_t  count  = 0;
325     uint16_t length = 0;
326     IaNa     option;
327 
328     VerifyOrExit(mIdentityAssociationCurrent != nullptr, error = kErrorDrop);
329 
330     for (IdentityAssociation &idAssociation : mIdentityAssociations)
331     {
332         if (idAssociation.mStatus == kIaStatusInvalid || idAssociation.mStatus == kIaStatusSolicitReplied)
333         {
334             continue;
335         }
336 
337         if (idAssociation.mPrefixAgentRloc == aRloc16)
338         {
339             count++;
340         }
341     }
342 
343     // compute the right length
344     length = sizeof(IaNa) + sizeof(IaAddress) * count - sizeof(Option);
345 
346     option.Init();
347     option.SetLength(length);
348     option.SetIaid(0);
349     option.SetT1(0);
350     option.SetT2(0);
351     SuccessOrExit(error = aMessage.Append(option));
352 
353 exit:
354     return error;
355 }
356 
AppendIaAddress(Message & aMessage,uint16_t aRloc16)357 Error Client::AppendIaAddress(Message &aMessage, uint16_t aRloc16)
358 {
359     Error     error = kErrorNone;
360     IaAddress option;
361 
362     VerifyOrExit(mIdentityAssociationCurrent, error = kErrorDrop);
363 
364     option.Init();
365 
366     for (IdentityAssociation &idAssociation : mIdentityAssociations)
367     {
368         if ((idAssociation.mStatus == kIaStatusSolicit || idAssociation.mStatus == kIaStatusSoliciting) &&
369             (idAssociation.mPrefixAgentRloc == aRloc16))
370         {
371             option.SetAddress(idAssociation.mNetifAddress.GetAddress());
372             option.SetPreferredLifetime(0);
373             option.SetValidLifetime(0);
374             SuccessOrExit(error = aMessage.Append(option));
375         }
376     }
377 
378 exit:
379     return error;
380 }
381 
AppendRapidCommit(Message & aMessage)382 Error Client::AppendRapidCommit(Message &aMessage)
383 {
384     RapidCommit option;
385 
386     option.Init();
387     return aMessage.Append(option);
388 }
389 
HandleUdpReceive(Message & aMessage,const Ip6::MessageInfo & aMessageInfo)390 void Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
391 {
392     OT_UNUSED_VARIABLE(aMessageInfo);
393 
394     Header header;
395 
396     SuccessOrExit(aMessage.Read(aMessage.GetOffset(), header));
397     aMessage.MoveOffset(sizeof(header));
398 
399     if ((header.GetType() == kTypeReply) && (header.GetTransactionId() == mTransactionId))
400     {
401         ProcessReply(aMessage);
402     }
403 
404 exit:
405     return;
406 }
407 
ProcessReply(Message & aMessage)408 void Client::ProcessReply(Message &aMessage)
409 {
410     uint16_t offset = aMessage.GetOffset();
411     uint16_t length = aMessage.GetLength() - aMessage.GetOffset();
412     uint16_t optionOffset;
413 
414     if ((optionOffset = FindOption(aMessage, offset, length, kOptionStatusCode)) > 0)
415     {
416         SuccessOrExit(ProcessStatusCode(aMessage, optionOffset));
417     }
418 
419     // Server Identifier
420     VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionServerIdentifier)) > 0);
421     SuccessOrExit(ProcessServerIdentifier(aMessage, optionOffset));
422 
423     // Client Identifier
424     VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionClientIdentifier)) > 0);
425     SuccessOrExit(ProcessClientIdentifier(aMessage, optionOffset));
426 
427     // Rapid Commit
428     VerifyOrExit(FindOption(aMessage, offset, length, kOptionRapidCommit) > 0);
429 
430     // IA_NA
431     VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionIaNa)) > 0);
432     SuccessOrExit(ProcessIaNa(aMessage, optionOffset));
433 
434     HandleTrickleTimer();
435 
436 exit:
437     return;
438 }
439 
FindOption(Message & aMessage,uint16_t aOffset,uint16_t aLength,Dhcp6::Code aCode)440 uint16_t Client::FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, Dhcp6::Code aCode)
441 {
442     uint32_t offset = aOffset;
443     uint16_t end    = aOffset + aLength;
444     uint16_t rval   = 0;
445 
446     while (offset <= end)
447     {
448         Option option;
449 
450         SuccessOrExit(aMessage.Read(static_cast<uint16_t>(offset), option));
451 
452         if (option.GetCode() == aCode)
453         {
454             ExitNow(rval = static_cast<uint16_t>(offset));
455         }
456 
457         offset += sizeof(option) + option.GetLength();
458     }
459 
460 exit:
461     return rval;
462 }
463 
ProcessServerIdentifier(Message & aMessage,uint16_t aOffset)464 Error Client::ProcessServerIdentifier(Message &aMessage, uint16_t aOffset)
465 {
466     Error            error = kErrorNone;
467     ServerIdentifier option;
468 
469     SuccessOrExit(aMessage.Read(aOffset, option));
470     VerifyOrExit(((option.GetDuidType() == kDuidLinkLayerAddressPlusTime) &&
471                   (option.GetDuidHardwareType() == kHardwareTypeEthernet)) ||
472                      ((option.GetLength() == (sizeof(option) - sizeof(Option))) &&
473                       (option.GetDuidType() == kDuidLinkLayerAddress) &&
474                       (option.GetDuidHardwareType() == kHardwareTypeEui64)),
475                  error = kErrorParse);
476 exit:
477     return error;
478 }
479 
ProcessClientIdentifier(Message & aMessage,uint16_t aOffset)480 Error Client::ProcessClientIdentifier(Message &aMessage, uint16_t aOffset)
481 {
482     Error            error = kErrorNone;
483     ClientIdentifier option;
484     Mac::ExtAddress  eui64;
485 
486     Get<Radio>().GetIeeeEui64(eui64);
487 
488     SuccessOrExit(error = aMessage.Read(aOffset, option));
489     VerifyOrExit(
490         (option.GetLength() == (sizeof(option) - sizeof(Option))) && (option.GetDuidType() == kDuidLinkLayerAddress) &&
491             (option.GetDuidHardwareType() == kHardwareTypeEui64) && (option.GetDuidLinkLayerAddress() == eui64),
492         error = kErrorParse);
493 exit:
494     return error;
495 }
496 
ProcessIaNa(Message & aMessage,uint16_t aOffset)497 Error Client::ProcessIaNa(Message &aMessage, uint16_t aOffset)
498 {
499     Error    error = kErrorNone;
500     IaNa     option;
501     uint16_t optionOffset;
502     uint16_t length;
503 
504     SuccessOrExit(error = aMessage.Read(aOffset, option));
505 
506     aOffset += sizeof(option);
507     length = option.GetLength() - (sizeof(option) - sizeof(Option));
508 
509     VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = kErrorParse);
510 
511     if ((optionOffset = FindOption(aMessage, aOffset, length, kOptionStatusCode)) > 0)
512     {
513         SuccessOrExit(error = ProcessStatusCode(aMessage, optionOffset));
514     }
515 
516     while (length > 0)
517     {
518         if ((optionOffset = FindOption(aMessage, aOffset, length, kOptionIaAddress)) == 0)
519         {
520             ExitNow();
521         }
522 
523         SuccessOrExit(error = ProcessIaAddress(aMessage, optionOffset));
524 
525         length -= ((optionOffset - aOffset) + sizeof(IaAddress));
526         aOffset = optionOffset + sizeof(IaAddress);
527     }
528 
529 exit:
530     return error;
531 }
532 
ProcessStatusCode(Message & aMessage,uint16_t aOffset)533 Error Client::ProcessStatusCode(Message &aMessage, uint16_t aOffset)
534 {
535     Error      error = kErrorNone;
536     StatusCode option;
537 
538     SuccessOrExit(error = aMessage.Read(aOffset, option));
539     VerifyOrExit((option.GetLength() >= sizeof(option) - sizeof(Option)) && (option.GetStatusCode() == kStatusSuccess),
540                  error = kErrorParse);
541 
542 exit:
543     return error;
544 }
545 
ProcessIaAddress(Message & aMessage,uint16_t aOffset)546 Error Client::ProcessIaAddress(Message &aMessage, uint16_t aOffset)
547 {
548     Error     error;
549     IaAddress option;
550 
551     SuccessOrExit(error = aMessage.Read(aOffset, option));
552     VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = kErrorParse);
553 
554     for (IdentityAssociation &idAssociation : mIdentityAssociations)
555     {
556         if (idAssociation.mStatus == kIaStatusInvalid || idAssociation.mValidLifetime != 0)
557         {
558             continue;
559         }
560 
561         if (idAssociation.mNetifAddress.GetAddress().PrefixMatch(option.GetAddress()) >=
562             idAssociation.mNetifAddress.mPrefixLength)
563         {
564             idAssociation.mNetifAddress.mAddress       = option.GetAddress();
565             idAssociation.mPreferredLifetime           = option.GetPreferredLifetime();
566             idAssociation.mValidLifetime               = option.GetValidLifetime();
567             idAssociation.mNetifAddress.mAddressOrigin = Ip6::Netif::kOriginDhcp6;
568             idAssociation.mNetifAddress.mPreferred     = option.GetPreferredLifetime() != 0;
569             idAssociation.mNetifAddress.mValid         = option.GetValidLifetime() != 0;
570             idAssociation.mStatus                      = kIaStatusSolicitReplied;
571             Get<ThreadNetif>().AddUnicastAddress(idAssociation.mNetifAddress);
572             ExitNow(error = kErrorNone);
573         }
574     }
575 
576     error = kErrorNotFound;
577 
578 exit:
579     return error;
580 }
581 
582 } // namespace Dhcp6
583 } // namespace ot
584 
585 #endif // OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
586