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