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 "instance/instance.hpp"
39
40 namespace ot {
41 namespace Dhcp6 {
42
43 RegisterLogModule("Dhcp6Client");
44
Client(Instance & aInstance)45 Client::Client(Instance &aInstance)
46 : InstanceLocator(aInstance)
47 , mSocket(aInstance, *this)
48 , mTrickleTimer(aInstance, Client::HandleTrickleTimer)
49 , mStartTime(0)
50 , mIdentityAssociationCurrent(nullptr)
51 {
52 ClearAllBytes(mIdentityAssociations);
53 }
54
MatchNetifAddressWithPrefix(const Ip6::Netif::UnicastAddress & aNetifAddress,const Ip6::Prefix & aIp6Prefix)55 bool Client::MatchNetifAddressWithPrefix(const Ip6::Netif::UnicastAddress &aNetifAddress, const Ip6::Prefix &aIp6Prefix)
56 {
57 return aNetifAddress.HasPrefix(aIp6Prefix);
58 }
59
UpdateAddresses(void)60 void Client::UpdateAddresses(void)
61 {
62 bool found = false;
63 bool doesAgentExist = false;
64 NetworkData::Iterator iterator;
65 NetworkData::OnMeshPrefixConfig config;
66
67 // remove addresses directly if prefix not valid in network data
68 for (IdentityAssociation &idAssociation : mIdentityAssociations)
69 {
70 if (idAssociation.mStatus == kIaStatusInvalid || idAssociation.mValidLifetime == 0)
71 {
72 continue;
73 }
74
75 found = false;
76 iterator = NetworkData::kIteratorInit;
77
78 while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == kErrorNone)
79 {
80 if (!config.mDhcp)
81 {
82 continue;
83 }
84
85 if (MatchNetifAddressWithPrefix(idAssociation.mNetifAddress, config.GetPrefix()))
86 {
87 found = true;
88 break;
89 }
90 }
91
92 if (!found)
93 {
94 Get<ThreadNetif>().RemoveUnicastAddress(idAssociation.mNetifAddress);
95 idAssociation.mStatus = kIaStatusInvalid;
96 }
97 }
98
99 // add IdentityAssociation for new configured prefix
100 iterator = NetworkData::kIteratorInit;
101
102 while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == kErrorNone)
103 {
104 IdentityAssociation *idAssociation = nullptr;
105
106 if (!config.mDhcp)
107 {
108 continue;
109 }
110
111 doesAgentExist = true;
112 found = false;
113
114 for (IdentityAssociation &ia : mIdentityAssociations)
115 {
116 if (ia.mStatus == kIaStatusInvalid)
117 {
118 // record an available IdentityAssociation
119 if (idAssociation == nullptr)
120 {
121 idAssociation = &ia;
122 }
123 }
124 else if (MatchNetifAddressWithPrefix(ia.mNetifAddress, config.GetPrefix()))
125 {
126 found = true;
127 idAssociation = &ia;
128 break;
129 }
130 }
131
132 if (!found)
133 {
134 if (idAssociation != nullptr)
135 {
136 idAssociation->mNetifAddress.mAddress = config.mPrefix.mPrefix;
137 idAssociation->mNetifAddress.mPrefixLength = config.mPrefix.mLength;
138 idAssociation->mStatus = kIaStatusSolicit;
139 idAssociation->mValidLifetime = 0;
140 }
141 else
142 {
143 LogWarn("Insufficient memory for new DHCP prefix");
144 continue;
145 }
146 }
147
148 idAssociation->mPrefixAgentRloc = config.mRloc16;
149 }
150
151 if (doesAgentExist)
152 {
153 Start();
154 }
155 else
156 {
157 Stop();
158 }
159 }
160
Start(void)161 void Client::Start(void)
162 {
163 VerifyOrExit(!mSocket.IsBound());
164
165 IgnoreError(mSocket.Open(Ip6::kNetifThreadInternal));
166 IgnoreError(mSocket.Bind(kDhcpClientPort));
167
168 ProcessNextIdentityAssociation();
169
170 exit:
171 return;
172 }
173
Stop(void)174 void Client::Stop(void)
175 {
176 mTrickleTimer.Stop();
177 IgnoreError(mSocket.Close());
178 }
179
ProcessNextIdentityAssociation(void)180 bool Client::ProcessNextIdentityAssociation(void)
181 {
182 bool rval = false;
183
184 // not interrupt in-progress solicit
185 VerifyOrExit(mIdentityAssociationCurrent == nullptr || mIdentityAssociationCurrent->mStatus != kIaStatusSoliciting);
186
187 mTrickleTimer.Stop();
188
189 for (IdentityAssociation &idAssociation : mIdentityAssociations)
190 {
191 if (idAssociation.mStatus != kIaStatusSolicit)
192 {
193 continue;
194 }
195
196 // new transaction id
197 IgnoreError(mTransactionId.GenerateRandom());
198
199 mIdentityAssociationCurrent = &idAssociation;
200
201 mTrickleTimer.Start(TrickleTimer::kModeTrickle, Time::SecToMsec(kTrickleTimerImin),
202 Time::SecToMsec(kTrickleTimerImax));
203
204 mTrickleTimer.IndicateInconsistent();
205
206 ExitNow(rval = true);
207 }
208
209 exit:
210 return rval;
211 }
212
HandleTrickleTimer(TrickleTimer & aTrickleTimer)213 void Client::HandleTrickleTimer(TrickleTimer &aTrickleTimer) { aTrickleTimer.Get<Client>().HandleTrickleTimer(); }
214
HandleTrickleTimer(void)215 void Client::HandleTrickleTimer(void)
216 {
217 OT_ASSERT(mSocket.IsBound());
218
219 VerifyOrExit(mIdentityAssociationCurrent != nullptr, mTrickleTimer.Stop());
220
221 switch (mIdentityAssociationCurrent->mStatus)
222 {
223 case kIaStatusSolicit:
224 mStartTime = TimerMilli::GetNow();
225 mIdentityAssociationCurrent->mStatus = kIaStatusSoliciting;
226
227 OT_FALL_THROUGH;
228
229 case kIaStatusSoliciting:
230 Solicit(mIdentityAssociationCurrent->mPrefixAgentRloc);
231 break;
232
233 case kIaStatusSolicitReplied:
234 mIdentityAssociationCurrent = nullptr;
235
236 if (!ProcessNextIdentityAssociation())
237 {
238 Stop();
239 mTrickleTimer.Stop();
240 }
241
242 break;
243
244 default:
245 break;
246 }
247
248 exit:
249 return;
250 }
251
Solicit(uint16_t aRloc16)252 void Client::Solicit(uint16_t aRloc16)
253 {
254 Error error = kErrorNone;
255 Message *message;
256 Ip6::MessageInfo messageInfo;
257
258 VerifyOrExit((message = mSocket.NewMessage()) != nullptr, error = kErrorNoBufs);
259
260 SuccessOrExit(error = AppendHeader(*message));
261 SuccessOrExit(error = AppendElapsedTime(*message));
262 SuccessOrExit(error = AppendClientIdentifier(*message));
263 SuccessOrExit(error = AppendIaNa(*message, aRloc16));
264 // specify which prefixes to solicit
265 SuccessOrExit(error = AppendIaAddress(*message, aRloc16));
266 SuccessOrExit(error = AppendRapidCommit(*message));
267
268 #if OPENTHREAD_ENABLE_DHCP6_MULTICAST_SOLICIT
269 messageInfo.GetPeerAddr().SetToRealmLocalAllRoutersMulticast();
270 #else
271 messageInfo.GetPeerAddr().SetToRoutingLocator(Get<Mle::MleRouter>().GetMeshLocalPrefix(), aRloc16);
272 #endif
273 messageInfo.SetSockAddr(Get<Mle::MleRouter>().GetMeshLocalRloc());
274 messageInfo.mPeerPort = kDhcpServerPort;
275
276 SuccessOrExit(error = mSocket.SendTo(*message, messageInfo));
277 LogInfo("solicit");
278
279 exit:
280 if (error != kErrorNone)
281 {
282 FreeMessage(message);
283 LogWarnOnError(error, "send DHCPv6 Solicit");
284 }
285 }
286
AppendHeader(Message & aMessage)287 Error Client::AppendHeader(Message &aMessage)
288 {
289 Header header;
290
291 header.Clear();
292 header.SetType(kTypeSolicit);
293 header.SetTransactionId(mTransactionId);
294 return aMessage.Append(header);
295 }
296
AppendElapsedTime(Message & aMessage)297 Error Client::AppendElapsedTime(Message &aMessage)
298 {
299 ElapsedTime option;
300
301 option.Init();
302 option.SetElapsedTime(static_cast<uint16_t>(Time::MsecToSec(TimerMilli::GetNow() - mStartTime)));
303 return aMessage.Append(option);
304 }
305
AppendClientIdentifier(Message & aMessage)306 Error Client::AppendClientIdentifier(Message &aMessage)
307 {
308 ClientIdentifier option;
309 Mac::ExtAddress eui64;
310
311 Get<Radio>().GetIeeeEui64(eui64);
312
313 option.Init();
314 option.SetDuidType(kDuidLinkLayerAddress);
315 option.SetDuidHardwareType(kHardwareTypeEui64);
316 option.SetDuidLinkLayerAddress(eui64);
317
318 return aMessage.Append(option);
319 }
320
AppendIaNa(Message & aMessage,uint16_t aRloc16)321 Error Client::AppendIaNa(Message &aMessage, uint16_t aRloc16)
322 {
323 Error error = kErrorNone;
324 uint8_t count = 0;
325 uint16_t length = 0;
326 IaNa option;
327
328 VerifyOrExit(mIdentityAssociationCurrent != nullptr, error = kErrorDrop);
329
330 for (IdentityAssociation &idAssociation : mIdentityAssociations)
331 {
332 if (idAssociation.mStatus == kIaStatusInvalid || idAssociation.mStatus == kIaStatusSolicitReplied)
333 {
334 continue;
335 }
336
337 if (idAssociation.mPrefixAgentRloc == aRloc16)
338 {
339 count++;
340 }
341 }
342
343 // compute the right length
344 length = sizeof(IaNa) + sizeof(IaAddress) * count - sizeof(Option);
345
346 option.Init();
347 option.SetLength(length);
348 option.SetIaid(0);
349 option.SetT1(0);
350 option.SetT2(0);
351 SuccessOrExit(error = aMessage.Append(option));
352
353 exit:
354 return error;
355 }
356
AppendIaAddress(Message & aMessage,uint16_t aRloc16)357 Error Client::AppendIaAddress(Message &aMessage, uint16_t aRloc16)
358 {
359 Error error = kErrorNone;
360 IaAddress option;
361
362 VerifyOrExit(mIdentityAssociationCurrent, error = kErrorDrop);
363
364 option.Init();
365
366 for (IdentityAssociation &idAssociation : mIdentityAssociations)
367 {
368 if ((idAssociation.mStatus == kIaStatusSolicit || idAssociation.mStatus == kIaStatusSoliciting) &&
369 (idAssociation.mPrefixAgentRloc == aRloc16))
370 {
371 option.SetAddress(idAssociation.mNetifAddress.GetAddress());
372 option.SetPreferredLifetime(0);
373 option.SetValidLifetime(0);
374 SuccessOrExit(error = aMessage.Append(option));
375 }
376 }
377
378 exit:
379 return error;
380 }
381
AppendRapidCommit(Message & aMessage)382 Error Client::AppendRapidCommit(Message &aMessage)
383 {
384 RapidCommit option;
385
386 option.Init();
387 return aMessage.Append(option);
388 }
389
HandleUdpReceive(Message & aMessage,const Ip6::MessageInfo & aMessageInfo)390 void Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
391 {
392 OT_UNUSED_VARIABLE(aMessageInfo);
393
394 Header header;
395
396 SuccessOrExit(aMessage.Read(aMessage.GetOffset(), header));
397 aMessage.MoveOffset(sizeof(header));
398
399 if ((header.GetType() == kTypeReply) && (header.GetTransactionId() == mTransactionId))
400 {
401 ProcessReply(aMessage);
402 }
403
404 exit:
405 return;
406 }
407
ProcessReply(Message & aMessage)408 void Client::ProcessReply(Message &aMessage)
409 {
410 uint16_t offset = aMessage.GetOffset();
411 uint16_t length = aMessage.GetLength() - aMessage.GetOffset();
412 uint16_t optionOffset;
413
414 if ((optionOffset = FindOption(aMessage, offset, length, kOptionStatusCode)) > 0)
415 {
416 SuccessOrExit(ProcessStatusCode(aMessage, optionOffset));
417 }
418
419 // Server Identifier
420 VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionServerIdentifier)) > 0);
421 SuccessOrExit(ProcessServerIdentifier(aMessage, optionOffset));
422
423 // Client Identifier
424 VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionClientIdentifier)) > 0);
425 SuccessOrExit(ProcessClientIdentifier(aMessage, optionOffset));
426
427 // Rapid Commit
428 VerifyOrExit(FindOption(aMessage, offset, length, kOptionRapidCommit) > 0);
429
430 // IA_NA
431 VerifyOrExit((optionOffset = FindOption(aMessage, offset, length, kOptionIaNa)) > 0);
432 SuccessOrExit(ProcessIaNa(aMessage, optionOffset));
433
434 HandleTrickleTimer();
435
436 exit:
437 return;
438 }
439
FindOption(Message & aMessage,uint16_t aOffset,uint16_t aLength,Dhcp6::Code aCode)440 uint16_t Client::FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, Dhcp6::Code aCode)
441 {
442 uint32_t offset = aOffset;
443 uint16_t end = aOffset + aLength;
444 uint16_t rval = 0;
445
446 while (offset <= end)
447 {
448 Option option;
449
450 SuccessOrExit(aMessage.Read(static_cast<uint16_t>(offset), option));
451
452 if (option.GetCode() == aCode)
453 {
454 ExitNow(rval = static_cast<uint16_t>(offset));
455 }
456
457 offset += sizeof(option) + option.GetLength();
458 }
459
460 exit:
461 return rval;
462 }
463
ProcessServerIdentifier(Message & aMessage,uint16_t aOffset)464 Error Client::ProcessServerIdentifier(Message &aMessage, uint16_t aOffset)
465 {
466 Error error = kErrorNone;
467 ServerIdentifier option;
468
469 SuccessOrExit(aMessage.Read(aOffset, option));
470 VerifyOrExit(((option.GetDuidType() == kDuidLinkLayerAddressPlusTime) &&
471 (option.GetDuidHardwareType() == kHardwareTypeEthernet)) ||
472 ((option.GetLength() == (sizeof(option) - sizeof(Option))) &&
473 (option.GetDuidType() == kDuidLinkLayerAddress) &&
474 (option.GetDuidHardwareType() == kHardwareTypeEui64)),
475 error = kErrorParse);
476 exit:
477 return error;
478 }
479
ProcessClientIdentifier(Message & aMessage,uint16_t aOffset)480 Error Client::ProcessClientIdentifier(Message &aMessage, uint16_t aOffset)
481 {
482 Error error = kErrorNone;
483 ClientIdentifier option;
484 Mac::ExtAddress eui64;
485
486 Get<Radio>().GetIeeeEui64(eui64);
487
488 SuccessOrExit(error = aMessage.Read(aOffset, option));
489 VerifyOrExit(
490 (option.GetLength() == (sizeof(option) - sizeof(Option))) && (option.GetDuidType() == kDuidLinkLayerAddress) &&
491 (option.GetDuidHardwareType() == kHardwareTypeEui64) && (option.GetDuidLinkLayerAddress() == eui64),
492 error = kErrorParse);
493 exit:
494 return error;
495 }
496
ProcessIaNa(Message & aMessage,uint16_t aOffset)497 Error Client::ProcessIaNa(Message &aMessage, uint16_t aOffset)
498 {
499 Error error = kErrorNone;
500 IaNa option;
501 uint16_t optionOffset;
502 uint16_t length;
503
504 SuccessOrExit(error = aMessage.Read(aOffset, option));
505
506 aOffset += sizeof(option);
507 length = option.GetLength() - (sizeof(option) - sizeof(Option));
508
509 VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = kErrorParse);
510
511 if ((optionOffset = FindOption(aMessage, aOffset, length, kOptionStatusCode)) > 0)
512 {
513 SuccessOrExit(error = ProcessStatusCode(aMessage, optionOffset));
514 }
515
516 while (length > 0)
517 {
518 if ((optionOffset = FindOption(aMessage, aOffset, length, kOptionIaAddress)) == 0)
519 {
520 ExitNow();
521 }
522
523 SuccessOrExit(error = ProcessIaAddress(aMessage, optionOffset));
524
525 length -= ((optionOffset - aOffset) + sizeof(IaAddress));
526 aOffset = optionOffset + sizeof(IaAddress);
527 }
528
529 exit:
530 return error;
531 }
532
ProcessStatusCode(Message & aMessage,uint16_t aOffset)533 Error Client::ProcessStatusCode(Message &aMessage, uint16_t aOffset)
534 {
535 Error error = kErrorNone;
536 StatusCode option;
537
538 SuccessOrExit(error = aMessage.Read(aOffset, option));
539 VerifyOrExit((option.GetLength() >= sizeof(option) - sizeof(Option)) && (option.GetStatusCode() == kStatusSuccess),
540 error = kErrorParse);
541
542 exit:
543 return error;
544 }
545
ProcessIaAddress(Message & aMessage,uint16_t aOffset)546 Error Client::ProcessIaAddress(Message &aMessage, uint16_t aOffset)
547 {
548 Error error;
549 IaAddress option;
550
551 SuccessOrExit(error = aMessage.Read(aOffset, option));
552 VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = kErrorParse);
553
554 for (IdentityAssociation &idAssociation : mIdentityAssociations)
555 {
556 if (idAssociation.mStatus == kIaStatusInvalid || idAssociation.mValidLifetime != 0)
557 {
558 continue;
559 }
560
561 if (idAssociation.mNetifAddress.GetAddress().PrefixMatch(option.GetAddress()) >=
562 idAssociation.mNetifAddress.mPrefixLength)
563 {
564 idAssociation.mNetifAddress.mAddress = option.GetAddress();
565 idAssociation.mPreferredLifetime = option.GetPreferredLifetime();
566 idAssociation.mValidLifetime = option.GetValidLifetime();
567 idAssociation.mNetifAddress.mAddressOrigin = Ip6::Netif::kOriginDhcp6;
568 idAssociation.mNetifAddress.mPreferred = option.GetPreferredLifetime() != 0;
569 idAssociation.mNetifAddress.mValid = option.GetValidLifetime() != 0;
570 idAssociation.mStatus = kIaStatusSolicitReplied;
571 Get<ThreadNetif>().AddUnicastAddress(idAssociation.mNetifAddress);
572 ExitNow(error = kErrorNone);
573 }
574 }
575
576 error = kErrorNotFound;
577
578 exit:
579 return error;
580 }
581
582 } // namespace Dhcp6
583 } // namespace ot
584
585 #endif // OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
586