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)
222 {
223 aTrickleTimer.Get<Client>().HandleTrickleTimer();
224 }
225
HandleTrickleTimer(void)226 void Client::HandleTrickleTimer(void)
227 {
228 OT_ASSERT(mSocket.IsBound());
229
230 VerifyOrExit(mIdentityAssociationCurrent != nullptr, mTrickleTimer.Stop());
231
232 switch (mIdentityAssociationCurrent->mStatus)
233 {
234 case kIaStatusSolicit:
235 mStartTime = TimerMilli::GetNow();
236 mIdentityAssociationCurrent->mStatus = kIaStatusSoliciting;
237
238 OT_FALL_THROUGH;
239
240 case kIaStatusSoliciting:
241 Solicit(mIdentityAssociationCurrent->mPrefixAgentRloc);
242 break;
243
244 case kIaStatusSolicitReplied:
245 mIdentityAssociationCurrent = nullptr;
246
247 if (!ProcessNextIdentityAssociation())
248 {
249 Stop();
250 mTrickleTimer.Stop();
251 }
252
253 break;
254
255 default:
256 break;
257 }
258
259 exit:
260 return;
261 }
262
Solicit(uint16_t aRloc16)263 void Client::Solicit(uint16_t aRloc16)
264 {
265 Error error = kErrorNone;
266 Message * message;
267 Ip6::MessageInfo messageInfo;
268
269 VerifyOrExit((message = mSocket.NewMessage(0)) != nullptr, error = kErrorNoBufs);
270
271 SuccessOrExit(error = AppendHeader(*message));
272 SuccessOrExit(error = AppendElapsedTime(*message));
273 SuccessOrExit(error = AppendClientIdentifier(*message));
274 SuccessOrExit(error = AppendIaNa(*message, aRloc16));
275 // specify which prefixes to solicit
276 SuccessOrExit(error = AppendIaAddress(*message, aRloc16));
277 SuccessOrExit(error = AppendRapidCommit(*message));
278
279 #if OPENTHREAD_ENABLE_DHCP6_MULTICAST_SOLICIT
280 messageInfo.GetPeerAddr().SetToRealmLocalAllRoutersMulticast();
281 #else
282 messageInfo.GetPeerAddr().SetToRoutingLocator(Get<Mle::MleRouter>().GetMeshLocalPrefix(), aRloc16);
283 #endif
284 messageInfo.SetSockAddr(Get<Mle::MleRouter>().GetMeshLocal16());
285 messageInfo.mPeerPort = kDhcpServerPort;
286
287 SuccessOrExit(error = mSocket.SendTo(*message, messageInfo));
288 LogInfo("solicit");
289
290 exit:
291 if (error != kErrorNone)
292 {
293 FreeMessage(message);
294 LogWarn("Failed to send DHCPv6 Solicit: %s", ErrorToString(error));
295 }
296 }
297
AppendHeader(Message & aMessage)298 Error Client::AppendHeader(Message &aMessage)
299 {
300 Header header;
301
302 header.Clear();
303 header.SetType(kTypeSolicit);
304 header.SetTransactionId(mTransactionId);
305 return aMessage.Append(header);
306 }
307
AppendElapsedTime(Message & aMessage)308 Error Client::AppendElapsedTime(Message &aMessage)
309 {
310 ElapsedTime option;
311
312 option.Init();
313 option.SetElapsedTime(static_cast<uint16_t>(Time::MsecToSec(TimerMilli::GetNow() - mStartTime)));
314 return aMessage.Append(option);
315 }
316
AppendClientIdentifier(Message & aMessage)317 Error Client::AppendClientIdentifier(Message &aMessage)
318 {
319 ClientIdentifier option;
320 Mac::ExtAddress eui64;
321
322 Get<Radio>().GetIeeeEui64(eui64);
323
324 option.Init();
325 option.SetDuidType(kDuidLinkLayerAddress);
326 option.SetDuidHardwareType(kHardwareTypeEui64);
327 option.SetDuidLinkLayerAddress(eui64);
328
329 return aMessage.Append(option);
330 }
331
AppendIaNa(Message & aMessage,uint16_t aRloc16)332 Error Client::AppendIaNa(Message &aMessage, uint16_t aRloc16)
333 {
334 Error error = kErrorNone;
335 uint8_t count = 0;
336 uint16_t length = 0;
337 IaNa option;
338
339 VerifyOrExit(mIdentityAssociationCurrent != nullptr, error = kErrorDrop);
340
341 for (IdentityAssociation &idAssociation : mIdentityAssociations)
342 {
343 if (idAssociation.mStatus == kIaStatusInvalid || idAssociation.mStatus == kIaStatusSolicitReplied)
344 {
345 continue;
346 }
347
348 if (idAssociation.mPrefixAgentRloc == aRloc16)
349 {
350 count++;
351 }
352 }
353
354 // compute the right length
355 length = sizeof(IaNa) + sizeof(IaAddress) * count - sizeof(Option);
356
357 option.Init();
358 option.SetLength(length);
359 option.SetIaid(0);
360 option.SetT1(0);
361 option.SetT2(0);
362 SuccessOrExit(error = aMessage.Append(option));
363
364 exit:
365 return error;
366 }
367
AppendIaAddress(Message & aMessage,uint16_t aRloc16)368 Error Client::AppendIaAddress(Message &aMessage, uint16_t aRloc16)
369 {
370 Error error = kErrorNone;
371 IaAddress option;
372
373 VerifyOrExit(mIdentityAssociationCurrent, error = kErrorDrop);
374
375 option.Init();
376
377 for (IdentityAssociation &idAssociation : mIdentityAssociations)
378 {
379 if ((idAssociation.mStatus == kIaStatusSolicit || idAssociation.mStatus == kIaStatusSoliciting) &&
380 (idAssociation.mPrefixAgentRloc == aRloc16))
381 {
382 option.SetAddress(idAssociation.mNetifAddress.GetAddress());
383 option.SetPreferredLifetime(0);
384 option.SetValidLifetime(0);
385 SuccessOrExit(error = aMessage.Append(option));
386 }
387 }
388
389 exit:
390 return error;
391 }
392
AppendRapidCommit(Message & aMessage)393 Error Client::AppendRapidCommit(Message &aMessage)
394 {
395 RapidCommit option;
396
397 option.Init();
398 return aMessage.Append(option);
399 }
400
HandleUdpReceive(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo)401 void Client::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
402 {
403 static_cast<Client *>(aContext)->HandleUdpReceive(AsCoreType(aMessage), AsCoreType(aMessageInfo));
404 }
405
HandleUdpReceive(Message & aMessage,const Ip6::MessageInfo & aMessageInfo)406 void Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
407 {
408 OT_UNUSED_VARIABLE(aMessageInfo);
409
410 Header header;
411
412 SuccessOrExit(aMessage.Read(aMessage.GetOffset(), header));
413 aMessage.MoveOffset(sizeof(header));
414
415 if ((header.GetType() == kTypeReply) && (header.GetTransactionId() == mTransactionId))
416 {
417 ProcessReply(aMessage);
418 }
419
420 exit:
421 return;
422 }
423
ProcessReply(Message & aMessage)424 void Client::ProcessReply(Message &aMessage)
425 {
426 uint16_t offset = aMessage.GetOffset();
427 uint16_t length = aMessage.GetLength() - aMessage.GetOffset();
428 uint16_t optionOffset;
429
430 if ((optionOffset = FindOption(aMessage, offset, length, kOptionStatusCode)) > 0)
431 {
432 SuccessOrExit(ProcessStatusCode(aMessage, optionOffset));
433 }
434
435 // Server Identifier
436 VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionServerIdentifier)) > 0);
437 SuccessOrExit(ProcessServerIdentifier(aMessage, optionOffset));
438
439 // Client Identifier
440 VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionClientIdentifier)) > 0);
441 SuccessOrExit(ProcessClientIdentifier(aMessage, optionOffset));
442
443 // Rapid Commit
444 VerifyOrExit(FindOption(aMessage, offset, length, kOptionRapidCommit) > 0);
445
446 // IA_NA
447 VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionIaNa)) > 0);
448 SuccessOrExit(ProcessIaNa(aMessage, optionOffset));
449
450 HandleTrickleTimer();
451
452 exit:
453 return;
454 }
455
FindOption(Message & aMessage,uint16_t aOffset,uint16_t aLength,Dhcp6::Code aCode)456 uint16_t Client::FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, Dhcp6::Code aCode)
457 {
458 uint32_t offset = aOffset;
459 uint16_t end = aOffset + aLength;
460 uint16_t rval = 0;
461
462 while (offset <= end)
463 {
464 Option option;
465
466 SuccessOrExit(aMessage.Read(static_cast<uint16_t>(offset), option));
467
468 if (option.GetCode() == aCode)
469 {
470 ExitNow(rval = static_cast<uint16_t>(offset));
471 }
472
473 offset += sizeof(option) + option.GetLength();
474 }
475
476 exit:
477 return rval;
478 }
479
ProcessServerIdentifier(Message & aMessage,uint16_t aOffset)480 Error Client::ProcessServerIdentifier(Message &aMessage, uint16_t aOffset)
481 {
482 Error error = kErrorNone;
483 ServerIdentifier option;
484
485 SuccessOrExit(aMessage.Read(aOffset, option));
486 VerifyOrExit(((option.GetDuidType() == kDuidLinkLayerAddressPlusTime) &&
487 (option.GetDuidHardwareType() == kHardwareTypeEthernet)) ||
488 ((option.GetLength() == (sizeof(option) - sizeof(Option))) &&
489 (option.GetDuidType() == kDuidLinkLayerAddress) &&
490 (option.GetDuidHardwareType() == kHardwareTypeEui64)),
491 error = kErrorParse);
492 exit:
493 return error;
494 }
495
ProcessClientIdentifier(Message & aMessage,uint16_t aOffset)496 Error Client::ProcessClientIdentifier(Message &aMessage, uint16_t aOffset)
497 {
498 Error error = kErrorNone;
499 ClientIdentifier option;
500 Mac::ExtAddress eui64;
501
502 Get<Radio>().GetIeeeEui64(eui64);
503
504 SuccessOrExit(error = aMessage.Read(aOffset, option));
505 VerifyOrExit(
506 (option.GetLength() == (sizeof(option) - sizeof(Option))) && (option.GetDuidType() == kDuidLinkLayerAddress) &&
507 (option.GetDuidHardwareType() == kHardwareTypeEui64) && (option.GetDuidLinkLayerAddress() == eui64),
508 error = kErrorParse);
509 exit:
510 return error;
511 }
512
ProcessIaNa(Message & aMessage,uint16_t aOffset)513 Error Client::ProcessIaNa(Message &aMessage, uint16_t aOffset)
514 {
515 Error error = kErrorNone;
516 IaNa option;
517 uint16_t optionOffset;
518 uint16_t length;
519
520 SuccessOrExit(error = aMessage.Read(aOffset, option));
521
522 aOffset += sizeof(option);
523 length = option.GetLength() - (sizeof(option) - sizeof(Option));
524
525 VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = kErrorParse);
526
527 if ((optionOffset = FindOption(aMessage, aOffset, length, kOptionStatusCode)) > 0)
528 {
529 SuccessOrExit(error = ProcessStatusCode(aMessage, optionOffset));
530 }
531
532 while (length > 0)
533 {
534 if ((optionOffset = FindOption(aMessage, aOffset, length, kOptionIaAddress)) == 0)
535 {
536 ExitNow();
537 }
538
539 SuccessOrExit(error = ProcessIaAddress(aMessage, optionOffset));
540
541 length -= ((optionOffset - aOffset) + sizeof(IaAddress));
542 aOffset = optionOffset + sizeof(IaAddress);
543 }
544
545 exit:
546 return error;
547 }
548
ProcessStatusCode(Message & aMessage,uint16_t aOffset)549 Error Client::ProcessStatusCode(Message &aMessage, uint16_t aOffset)
550 {
551 Error error = kErrorNone;
552 StatusCode option;
553
554 SuccessOrExit(error = aMessage.Read(aOffset, option));
555 VerifyOrExit((option.GetLength() >= sizeof(option) - sizeof(Option)) && (option.GetStatusCode() == kStatusSuccess),
556 error = kErrorParse);
557
558 exit:
559 return error;
560 }
561
ProcessIaAddress(Message & aMessage,uint16_t aOffset)562 Error Client::ProcessIaAddress(Message &aMessage, uint16_t aOffset)
563 {
564 Error error;
565 IaAddress option;
566
567 SuccessOrExit(error = aMessage.Read(aOffset, option));
568 VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = kErrorParse);
569
570 for (IdentityAssociation &idAssociation : mIdentityAssociations)
571 {
572 if (idAssociation.mStatus == kIaStatusInvalid || idAssociation.mValidLifetime != 0)
573 {
574 continue;
575 }
576
577 if (idAssociation.mNetifAddress.GetAddress().PrefixMatch(option.GetAddress()) >=
578 idAssociation.mNetifAddress.mPrefixLength)
579 {
580 idAssociation.mNetifAddress.mAddress = option.GetAddress();
581 idAssociation.mPreferredLifetime = option.GetPreferredLifetime();
582 idAssociation.mValidLifetime = option.GetValidLifetime();
583 idAssociation.mNetifAddress.mAddressOrigin = Ip6::Netif::kOriginDhcp6;
584 idAssociation.mNetifAddress.mPreferred = option.GetPreferredLifetime() != 0;
585 idAssociation.mNetifAddress.mValid = option.GetValidLifetime() != 0;
586 idAssociation.mStatus = kIaStatusSolicitReplied;
587 Get<ThreadNetif>().AddUnicastAddress(idAssociation.mNetifAddress);
588 ExitNow(error = kErrorNone);
589 }
590 }
591
592 error = kErrorNotFound;
593
594 exit:
595 return error;
596 }
597
598 } // namespace Dhcp6
599 } // namespace ot
600
601 #endif // OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
602