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