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