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