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) { aTrickleTimer.Get<Client>().HandleTrickleTimer(); }
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()) != 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     LogInfo("solicit");
286 
287 exit:
288     if (error != kErrorNone)
289     {
290         FreeMessage(message);
291         LogWarn("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(AsCoreType(aMessage), AsCoreType(aMessageInfo));
401 }
402 
HandleUdpReceive(Message & aMessage,const Ip6::MessageInfo & aMessageInfo)403 void Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
404 {
405     OT_UNUSED_VARIABLE(aMessageInfo);
406 
407     Header header;
408 
409     SuccessOrExit(aMessage.Read(aMessage.GetOffset(), header));
410     aMessage.MoveOffset(sizeof(header));
411 
412     if ((header.GetType() == kTypeReply) && (header.GetTransactionId() == mTransactionId))
413     {
414         ProcessReply(aMessage);
415     }
416 
417 exit:
418     return;
419 }
420 
ProcessReply(Message & aMessage)421 void Client::ProcessReply(Message &aMessage)
422 {
423     uint16_t offset = aMessage.GetOffset();
424     uint16_t length = aMessage.GetLength() - aMessage.GetOffset();
425     uint16_t optionOffset;
426 
427     if ((optionOffset = FindOption(aMessage, offset, length, kOptionStatusCode)) > 0)
428     {
429         SuccessOrExit(ProcessStatusCode(aMessage, optionOffset));
430     }
431 
432     // Server Identifier
433     VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionServerIdentifier)) > 0);
434     SuccessOrExit(ProcessServerIdentifier(aMessage, optionOffset));
435 
436     // Client Identifier
437     VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionClientIdentifier)) > 0);
438     SuccessOrExit(ProcessClientIdentifier(aMessage, optionOffset));
439 
440     // Rapid Commit
441     VerifyOrExit(FindOption(aMessage, offset, length, kOptionRapidCommit) > 0);
442 
443     // IA_NA
444     VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionIaNa)) > 0);
445     SuccessOrExit(ProcessIaNa(aMessage, optionOffset));
446 
447     HandleTrickleTimer();
448 
449 exit:
450     return;
451 }
452 
FindOption(Message & aMessage,uint16_t aOffset,uint16_t aLength,Dhcp6::Code aCode)453 uint16_t Client::FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, Dhcp6::Code aCode)
454 {
455     uint32_t offset = aOffset;
456     uint16_t end    = aOffset + aLength;
457     uint16_t rval   = 0;
458 
459     while (offset <= end)
460     {
461         Option option;
462 
463         SuccessOrExit(aMessage.Read(static_cast<uint16_t>(offset), option));
464 
465         if (option.GetCode() == aCode)
466         {
467             ExitNow(rval = static_cast<uint16_t>(offset));
468         }
469 
470         offset += sizeof(option) + option.GetLength();
471     }
472 
473 exit:
474     return rval;
475 }
476 
ProcessServerIdentifier(Message & aMessage,uint16_t aOffset)477 Error Client::ProcessServerIdentifier(Message &aMessage, uint16_t aOffset)
478 {
479     Error            error = kErrorNone;
480     ServerIdentifier option;
481 
482     SuccessOrExit(aMessage.Read(aOffset, option));
483     VerifyOrExit(((option.GetDuidType() == kDuidLinkLayerAddressPlusTime) &&
484                   (option.GetDuidHardwareType() == kHardwareTypeEthernet)) ||
485                      ((option.GetLength() == (sizeof(option) - sizeof(Option))) &&
486                       (option.GetDuidType() == kDuidLinkLayerAddress) &&
487                       (option.GetDuidHardwareType() == kHardwareTypeEui64)),
488                  error = kErrorParse);
489 exit:
490     return error;
491 }
492 
ProcessClientIdentifier(Message & aMessage,uint16_t aOffset)493 Error Client::ProcessClientIdentifier(Message &aMessage, uint16_t aOffset)
494 {
495     Error            error = kErrorNone;
496     ClientIdentifier option;
497     Mac::ExtAddress  eui64;
498 
499     Get<Radio>().GetIeeeEui64(eui64);
500 
501     SuccessOrExit(error = aMessage.Read(aOffset, option));
502     VerifyOrExit(
503         (option.GetLength() == (sizeof(option) - sizeof(Option))) && (option.GetDuidType() == kDuidLinkLayerAddress) &&
504             (option.GetDuidHardwareType() == kHardwareTypeEui64) && (option.GetDuidLinkLayerAddress() == eui64),
505         error = kErrorParse);
506 exit:
507     return error;
508 }
509 
ProcessIaNa(Message & aMessage,uint16_t aOffset)510 Error Client::ProcessIaNa(Message &aMessage, uint16_t aOffset)
511 {
512     Error    error = kErrorNone;
513     IaNa     option;
514     uint16_t optionOffset;
515     uint16_t length;
516 
517     SuccessOrExit(error = aMessage.Read(aOffset, option));
518 
519     aOffset += sizeof(option);
520     length = option.GetLength() - (sizeof(option) - sizeof(Option));
521 
522     VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = kErrorParse);
523 
524     if ((optionOffset = FindOption(aMessage, aOffset, length, kOptionStatusCode)) > 0)
525     {
526         SuccessOrExit(error = ProcessStatusCode(aMessage, optionOffset));
527     }
528 
529     while (length > 0)
530     {
531         if ((optionOffset = FindOption(aMessage, aOffset, length, kOptionIaAddress)) == 0)
532         {
533             ExitNow();
534         }
535 
536         SuccessOrExit(error = ProcessIaAddress(aMessage, optionOffset));
537 
538         length -= ((optionOffset - aOffset) + sizeof(IaAddress));
539         aOffset = optionOffset + sizeof(IaAddress);
540     }
541 
542 exit:
543     return error;
544 }
545 
ProcessStatusCode(Message & aMessage,uint16_t aOffset)546 Error Client::ProcessStatusCode(Message &aMessage, uint16_t aOffset)
547 {
548     Error      error = kErrorNone;
549     StatusCode option;
550 
551     SuccessOrExit(error = aMessage.Read(aOffset, option));
552     VerifyOrExit((option.GetLength() >= sizeof(option) - sizeof(Option)) && (option.GetStatusCode() == kStatusSuccess),
553                  error = kErrorParse);
554 
555 exit:
556     return error;
557 }
558 
ProcessIaAddress(Message & aMessage,uint16_t aOffset)559 Error Client::ProcessIaAddress(Message &aMessage, uint16_t aOffset)
560 {
561     Error     error;
562     IaAddress option;
563 
564     SuccessOrExit(error = aMessage.Read(aOffset, option));
565     VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = kErrorParse);
566 
567     for (IdentityAssociation &idAssociation : mIdentityAssociations)
568     {
569         if (idAssociation.mStatus == kIaStatusInvalid || idAssociation.mValidLifetime != 0)
570         {
571             continue;
572         }
573 
574         if (idAssociation.mNetifAddress.GetAddress().PrefixMatch(option.GetAddress()) >=
575             idAssociation.mNetifAddress.mPrefixLength)
576         {
577             idAssociation.mNetifAddress.mAddress       = option.GetAddress();
578             idAssociation.mPreferredLifetime           = option.GetPreferredLifetime();
579             idAssociation.mValidLifetime               = option.GetValidLifetime();
580             idAssociation.mNetifAddress.mAddressOrigin = Ip6::Netif::kOriginDhcp6;
581             idAssociation.mNetifAddress.mPreferred     = option.GetPreferredLifetime() != 0;
582             idAssociation.mNetifAddress.mValid         = option.GetValidLifetime() != 0;
583             idAssociation.mStatus                      = kIaStatusSolicitReplied;
584             Get<ThreadNetif>().AddUnicastAddress(idAssociation.mNetifAddress);
585             ExitNow(error = kErrorNone);
586         }
587     }
588 
589     error = kErrorNotFound;
590 
591 exit:
592     return error;
593 }
594 
595 } // namespace Dhcp6
596 } // namespace ot
597 
598 #endif // OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
599