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