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