1 /*
2 * Copyright (c) 2020, 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 #include "srp_client.hpp"
30
31 #if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
32
33 #include "instance/instance.hpp"
34
35 /**
36 * @file
37 * This file implements the SRP client.
38 */
39
40 namespace ot {
41 namespace Srp {
42
43 RegisterLogModule("SrpClient");
44
45 //---------------------------------------------------------------------
46 // Client::HostInfo
47
Init(void)48 void Client::HostInfo::Init(void)
49 {
50 Clearable<HostInfo>::Clear();
51
52 // State is directly set on `mState` instead of using `SetState()`
53 // to avoid logging.
54 mState = OT_SRP_CLIENT_ITEM_STATE_REMOVED;
55 }
56
Clear(void)57 void Client::HostInfo::Clear(void)
58 {
59 Clearable<HostInfo>::Clear();
60 SetState(kRemoved);
61 }
62
SetState(ItemState aState)63 bool Client::HostInfo::SetState(ItemState aState)
64 {
65 bool didChange;
66
67 VerifyOrExit(aState != GetState(), didChange = false);
68
69 LogInfo("HostInfo %s -> %s", ItemStateToString(GetState()), ItemStateToString(aState));
70
71 mState = MapEnum(aState);
72 didChange = true;
73
74 exit:
75 return didChange;
76 }
77
EnableAutoAddress(void)78 void Client::HostInfo::EnableAutoAddress(void)
79 {
80 mAddresses = nullptr;
81 mNumAddresses = 0;
82 mAutoAddress = true;
83
84 LogInfo("HostInfo enabled auto address");
85 }
86
SetAddresses(const Ip6::Address * aAddresses,uint8_t aNumAddresses)87 void Client::HostInfo::SetAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses)
88 {
89 mAddresses = aAddresses;
90 mNumAddresses = aNumAddresses;
91 mAutoAddress = false;
92
93 LogInfo("HostInfo set %d addrs", GetNumAddresses());
94
95 for (uint8_t index = 0; index < GetNumAddresses(); index++)
96 {
97 LogInfo("%s", GetAddress(index).ToString().AsCString());
98 }
99 }
100
101 //---------------------------------------------------------------------
102 // Client::Service
103
Init(void)104 Error Client::Service::Init(void)
105 {
106 Error error = kErrorNone;
107
108 VerifyOrExit((GetName() != nullptr) && (GetInstanceName() != nullptr), error = kErrorInvalidArgs);
109 VerifyOrExit((GetTxtEntries() != nullptr) || (GetNumTxtEntries() == 0), error = kErrorInvalidArgs);
110
111 // State is directly set on `mState` instead of using `SetState()`
112 // to avoid logging.
113 mState = OT_SRP_CLIENT_ITEM_STATE_REMOVED;
114
115 mLease = Min(mLease, kMaxLease);
116 mKeyLease = Min(mKeyLease, kMaxLease);
117
118 exit:
119 return error;
120 }
121
SetState(ItemState aState)122 bool Client::Service::SetState(ItemState aState)
123 {
124 bool didChange;
125
126 VerifyOrExit(GetState() != aState, didChange = false);
127
128 LogInfo("Service %s -> %s, \"%s\" \"%s\"", ItemStateToString(GetState()), ItemStateToString(aState),
129 GetInstanceName(), GetName());
130
131 if (aState == kToAdd)
132 {
133 constexpr uint16_t kSubTypeLabelStringSize = 80;
134
135 String<kSubTypeLabelStringSize> string;
136
137 // Log more details only when entering `kToAdd` state.
138
139 if (HasSubType())
140 {
141 const char *label;
142
143 for (uint16_t index = 0; (label = GetSubTypeLabelAt(index)) != nullptr; index++)
144 {
145 string.Append("%s\"%s\"", (index != 0) ? ", " : "", label);
146 }
147 }
148
149 LogInfo("subtypes:[%s] port:%d weight:%d prio:%d txts:%d", string.AsCString(), GetPort(), GetWeight(),
150 GetPriority(), GetNumTxtEntries());
151 }
152
153 mState = MapEnum(aState);
154 didChange = true;
155
156 exit:
157 return didChange;
158 }
159
Matches(const Service & aOther) const160 bool Client::Service::Matches(const Service &aOther) const
161 {
162 // This method indicates whether or not two service entries match,
163 // i.e., have the same service and instance names. This is intended
164 // for use by `LinkedList::FindMatching()` to search within the
165 // `mServices` list.
166
167 return StringMatch(GetName(), aOther.GetName()) && StringMatch(GetInstanceName(), aOther.GetInstanceName());
168 }
169
170 //---------------------------------------------------------------------
171 // Client::TxJitter
172
173 const uint32_t Client::TxJitter::kMaxJitters[] = {
174 Client::kMaxTxJitterOnDeviceReboot, // (0) kOnDeviceReboot
175 Client::kMaxTxJitterOnServerStart, // (1) kOnServerStart
176 Client::kMaxTxJitterOnServerRestart, // (2) kOnServerRestart
177 Client::kMaxTxJitterOnServerSwitch, // (3) kOnServerSwitch
178 Client::kMaxTxJitterOnSlaacAddrAdd, // (4) kOnSlaacAddrAdd
179 Client::kMaxTxJitterOnSlaacAddrRemove, // (5) kOnSlaacAddrRemove
180 };
181
Request(Reason aReason)182 void Client::TxJitter::Request(Reason aReason)
183 {
184 struct EnumCheck
185 {
186 InitEnumValidatorCounter();
187 ValidateNextEnum(kOnDeviceReboot);
188 ValidateNextEnum(kOnServerStart);
189 ValidateNextEnum(kOnServerRestart);
190 ValidateNextEnum(kOnServerSwitch);
191 ValidateNextEnum(kOnSlaacAddrAdd);
192 ValidateNextEnum(kOnSlaacAddrRemove);
193 };
194
195 uint32_t maxJitter = kMaxJitters[aReason];
196
197 LogInfo("Requesting max tx jitter %lu (%s)", ToUlong(maxJitter), ReasonToString(aReason));
198
199 if (mRequestedMax != 0)
200 {
201 // If we have a previous request, adjust the `mRequestedMax`
202 // based on the time elapsed since that request was made.
203
204 uint32_t duration = TimerMilli::GetNow() - mRequestTime;
205
206 mRequestedMax = (mRequestedMax > duration) ? mRequestedMax - duration : 0;
207 }
208
209 mRequestedMax = Max(mRequestedMax, maxJitter);
210 mRequestTime = TimerMilli::GetNow();
211 }
212
DetermineDelay(void)213 uint32_t Client::TxJitter::DetermineDelay(void)
214 {
215 uint32_t delay;
216 uint32_t maxJitter = kMaxTxJitterDefault;
217
218 if (mRequestedMax != 0)
219 {
220 uint32_t duration = TimerMilli::GetNow() - mRequestTime;
221
222 if (duration >= mRequestedMax)
223 {
224 LogInfo("Requested max tx jitter %lu already expired", ToUlong(mRequestedMax));
225 }
226 else
227 {
228 maxJitter = Max(mRequestedMax - duration, kMaxTxJitterDefault);
229 LogInfo("Applying remaining max jitter %lu", ToUlong(maxJitter));
230 }
231
232 mRequestedMax = 0;
233 }
234
235 delay = Random::NonCrypto::GetUint32InRange(kMinTxJitter, maxJitter);
236 LogInfo("Use random tx jitter %lu from [%lu, %lu]", ToUlong(delay), ToUlong(kMinTxJitter), ToUlong(maxJitter));
237
238 return delay;
239 }
240
241 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
ReasonToString(Reason aReason)242 const char *Client::TxJitter::ReasonToString(Reason aReason)
243 {
244 static const char *const kReasonStrings[] = {
245 "OnDeviceReboot", // (0) kOnDeviceReboot
246 "OnServerStart", // (1) kOnServerStart
247 "OnServerRestart", // (2) kOnServerRestart
248 "OnServerSwitch", // (3) kOnServerSwitch
249 "OnSlaacAddrAdd", // (4) kOnSlaacAddrAdd
250 "OnSlaacAddrRemove", // (5) kOnSlaacAddrRemove
251 };
252
253 struct EnumCheck
254 {
255 InitEnumValidatorCounter();
256 ValidateNextEnum(kOnDeviceReboot);
257 ValidateNextEnum(kOnServerStart);
258 ValidateNextEnum(kOnServerRestart);
259 ValidateNextEnum(kOnServerSwitch);
260 ValidateNextEnum(kOnSlaacAddrAdd);
261 ValidateNextEnum(kOnSlaacAddrRemove);
262 };
263
264 return kReasonStrings[aReason];
265 }
266 #endif
267
268 //---------------------------------------------------------------------
269 // Client::AutoStart
270
271 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
272
AutoStart(void)273 Client::AutoStart::AutoStart(void)
274 {
275 Clear();
276 mState = kDefaultMode ? kFirstTimeSelecting : kDisabled;
277 }
278
HasSelectedServer(void) const279 bool Client::AutoStart::HasSelectedServer(void) const
280 {
281 bool hasSelected = false;
282
283 switch (mState)
284 {
285 case kDisabled:
286 case kFirstTimeSelecting:
287 case kReselecting:
288 break;
289
290 case kSelectedUnicastPreferred:
291 case kSelectedUnicast:
292 case kSelectedAnycast:
293 hasSelected = true;
294 break;
295 }
296
297 return hasSelected;
298 }
299
SetState(State aState)300 void Client::AutoStart::SetState(State aState)
301 {
302 if (mState != aState)
303 {
304 LogInfo("AutoStartState %s -> %s", StateToString(mState), StateToString(aState));
305 mState = aState;
306 }
307 }
308
InvokeCallback(const Ip6::SockAddr * aServerSockAddr) const309 void Client::AutoStart::InvokeCallback(const Ip6::SockAddr *aServerSockAddr) const
310 {
311 mCallback.InvokeIfSet(aServerSockAddr);
312 }
313
314 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
StateToString(State aState)315 const char *Client::AutoStart::StateToString(State aState)
316 {
317 static const char *const kStateStrings[] = {
318 "Disabled", // (0) kDisabled
319 "1stTimeSelect", // (1) kFirstTimeSelecting
320 "Reselect", // (2) kReselecting
321 "Unicast-prf", // (3) kSelectedUnicastPreferred
322 "Anycast", // (4) kSelectedAnycast
323 "Unicast", // (5) kSelectedUnicast
324 };
325
326 struct EnumCheck
327 {
328 InitEnumValidatorCounter();
329 ValidateNextEnum(kDisabled);
330 ValidateNextEnum(kFirstTimeSelecting);
331 ValidateNextEnum(kReselecting);
332 ValidateNextEnum(kSelectedUnicastPreferred);
333 ValidateNextEnum(kSelectedAnycast);
334 ValidateNextEnum(kSelectedUnicast);
335 };
336
337 return kStateStrings[aState];
338 }
339 #endif
340
341 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
342
343 //---------------------------------------------------------------------
344 // Client
345
346 const char Client::kDefaultDomainName[] = "default.service.arpa";
347
Client(Instance & aInstance)348 Client::Client(Instance &aInstance)
349 : InstanceLocator(aInstance)
350 , mState(kStateStopped)
351 , mTxFailureRetryCount(0)
352 , mShouldRemoveKeyLease(false)
353 , mSingleServiceMode(false)
354 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
355 , mServiceKeyRecordEnabled(false)
356 , mUseShortLeaseOption(false)
357 #endif
358 , mNextMessageId(0)
359 , mResponseMessageId(0)
360 , mAutoHostAddressCount(0)
361 , mRetryWaitInterval(kMinRetryWaitInterval)
362 , mTtl(0)
363 , mLease(0)
364 , mKeyLease(0)
365 , mDefaultLease(kDefaultLease)
366 , mDefaultKeyLease(kDefaultKeyLease)
367 , mSocket(aInstance, *this)
368 , mDomainName(kDefaultDomainName)
369 , mTimer(aInstance)
370 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
371 , mGuardTimer(aInstance)
372 #endif
373 {
374 // The `Client` implementation uses different constant array of
375 // `ItemState` to define transitions between states in `Pause()`,
376 // `Stop()`, `SendUpdate`, and `ProcessResponse()`, or to convert
377 // an `ItemState` to string. Here, we assert that the enumeration
378 // values are correct.
379
380 struct EnumCheck
381 {
382 InitEnumValidatorCounter();
383 ValidateNextEnum(kToAdd);
384 ValidateNextEnum(kAdding);
385 ValidateNextEnum(kToRefresh);
386 ValidateNextEnum(kRefreshing);
387 ValidateNextEnum(kToRemove);
388 ValidateNextEnum(kRemoving);
389 ValidateNextEnum(kRegistered);
390 ValidateNextEnum(kRemoved);
391 };
392
393 mHostInfo.Init();
394 }
395
Start(const Ip6::SockAddr & aServerSockAddr,Requester aRequester)396 Error Client::Start(const Ip6::SockAddr &aServerSockAddr, Requester aRequester)
397 {
398 Error error;
399
400 VerifyOrExit(GetState() == kStateStopped,
401 error = (aServerSockAddr == GetServerAddress()) ? kErrorNone : kErrorBusy);
402
403 SuccessOrExit(error = mSocket.Open(Ip6::kNetifThreadInternal));
404
405 error = mSocket.Connect(aServerSockAddr);
406
407 if (error != kErrorNone)
408 {
409 LogInfo("Failed to connect to server %s: %s", aServerSockAddr.GetAddress().ToString().AsCString(),
410 ErrorToString(error));
411 IgnoreError(mSocket.Close());
412 ExitNow();
413 }
414
415 LogInfo("%starting, server %s", (aRequester == kRequesterUser) ? "S" : "Auto-s",
416 aServerSockAddr.ToString().AsCString());
417
418 Resume();
419
420 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
421 if (aRequester == kRequesterAuto)
422 {
423 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE
424 Get<Dns::Client>().UpdateDefaultConfigAddress();
425 #endif
426 mAutoStart.InvokeCallback(&aServerSockAddr);
427 }
428 #endif
429
430 exit:
431 return error;
432 }
433
Stop(Requester aRequester,StopMode aMode)434 void Client::Stop(Requester aRequester, StopMode aMode)
435 {
436 // Change the state of host info and services so that they are
437 // added/removed again once the client is started back. In the
438 // case of `kAdding`, we intentionally move to `kToRefresh`
439 // instead of `kToAdd` since the server may receive our add
440 // request and the item may be registered on the server. This
441 // ensures that if we are later asked to remove the item, we do
442 // notify server.
443
444 static const ItemState kNewStateOnStop[]{
445 /* (0) kToAdd -> */ kToAdd,
446 /* (1) kAdding -> */ kToRefresh,
447 /* (2) kToRefresh -> */ kToRefresh,
448 /* (3) kRefreshing -> */ kToRefresh,
449 /* (4) kToRemove -> */ kToRemove,
450 /* (5) kRemoving -> */ kToRemove,
451 /* (6) kRegistered -> */ kToRefresh,
452 /* (7) kRemoved -> */ kRemoved,
453 };
454
455 VerifyOrExit(GetState() != kStateStopped);
456
457 mSingleServiceMode = false;
458
459 // State changes:
460 // kAdding -> kToRefresh
461 // kRefreshing -> kToRefresh
462 // kRemoving -> kToRemove
463 // kRegistered -> kToRefresh
464
465 ChangeHostAndServiceStates(kNewStateOnStop, kForAllServices);
466
467 IgnoreError(mSocket.Close());
468
469 mShouldRemoveKeyLease = false;
470 mTxFailureRetryCount = 0;
471 mResponseMessageId = mNextMessageId;
472
473 if (aMode == kResetRetryInterval)
474 {
475 ResetRetryWaitInterval();
476 }
477
478 SetState(kStateStopped);
479
480 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
481 #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
482 mAutoStart.ResetTimeoutFailureCount();
483 #endif
484 if (aRequester == kRequesterAuto)
485 {
486 mAutoStart.InvokeCallback(nullptr);
487 }
488 #endif
489
490 exit:
491 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
492 if (aRequester == kRequesterUser)
493 {
494 DisableAutoStartMode();
495 }
496 #endif
497 }
498
Resume(void)499 void Client::Resume(void)
500 {
501 SetState(kStateUpdated);
502 UpdateState();
503 }
504
Pause(void)505 void Client::Pause(void)
506 {
507 // Change the state of host info and services that are are being
508 // added or removed so that they are added/removed again once the
509 // client is resumed or started back.
510
511 static const ItemState kNewStateOnPause[]{
512 /* (0) kToAdd -> */ kToAdd,
513 /* (1) kAdding -> */ kToRefresh,
514 /* (2) kToRefresh -> */ kToRefresh,
515 /* (3) kRefreshing -> */ kToRefresh,
516 /* (4) kToRemove -> */ kToRemove,
517 /* (5) kRemoving -> */ kToRemove,
518 /* (6) kRegistered -> */ kRegistered,
519 /* (7) kRemoved -> */ kRemoved,
520 };
521
522 mSingleServiceMode = false;
523
524 // State changes:
525 // kAdding -> kToRefresh
526 // kRefreshing -> kToRefresh
527 // kRemoving -> kToRemove
528
529 ChangeHostAndServiceStates(kNewStateOnPause, kForAllServices);
530
531 SetState(kStatePaused);
532 }
533
HandleNotifierEvents(Events aEvents)534 void Client::HandleNotifierEvents(Events aEvents)
535 {
536 if (aEvents.Contains(kEventThreadRoleChanged))
537 {
538 HandleRoleChanged();
539 }
540
541 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
542 if (aEvents.ContainsAny(kEventThreadNetdataChanged | kEventThreadMeshLocalAddrChanged))
543 {
544 ProcessAutoStart();
545 }
546 #endif
547
548 if (aEvents.ContainsAny(kEventIp6AddressAdded | kEventIp6AddressRemoved | kEventThreadMeshLocalAddrChanged) &&
549 ShouldUpdateHostAutoAddresses())
550 {
551 IgnoreError(UpdateHostInfoStateOnAddressChange());
552 UpdateState();
553 }
554 }
555
HandleRoleChanged(void)556 void Client::HandleRoleChanged(void)
557 {
558 if (Get<Mle::Mle>().IsAttached())
559 {
560 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
561 ApplyAutoStartGuardOnAttach();
562 #endif
563
564 VerifyOrExit(GetState() == kStatePaused);
565 Resume();
566 }
567 else
568 {
569 VerifyOrExit(GetState() != kStateStopped);
570 Pause();
571 }
572
573 exit:
574 return;
575 }
576
577 #if OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE
SetDomainName(const char * aName)578 Error Client::SetDomainName(const char *aName)
579 {
580 Error error = kErrorNone;
581
582 VerifyOrExit((mHostInfo.GetState() == kToAdd) || (mHostInfo.GetState() == kRemoved), error = kErrorInvalidState);
583
584 mDomainName = (aName != nullptr) ? aName : kDefaultDomainName;
585 LogInfo("Domain name \"%s\"", mDomainName);
586
587 exit:
588 return error;
589 }
590 #endif
591
SetHostName(const char * aName)592 Error Client::SetHostName(const char *aName)
593 {
594 Error error = kErrorNone;
595
596 VerifyOrExit(aName != nullptr, error = kErrorInvalidArgs);
597
598 VerifyOrExit((mHostInfo.GetState() == kToAdd) || (mHostInfo.GetState() == kRemoved), error = kErrorInvalidState);
599
600 LogInfo("Host name \"%s\"", aName);
601 mHostInfo.SetName(aName);
602 mHostInfo.SetState(kToAdd);
603 UpdateState();
604
605 exit:
606 return error;
607 }
608
EnableAutoHostAddress(void)609 Error Client::EnableAutoHostAddress(void)
610 {
611 Error error = kErrorNone;
612
613 VerifyOrExit(!mHostInfo.IsAutoAddressEnabled());
614 SuccessOrExit(error = UpdateHostInfoStateOnAddressChange());
615
616 for (Ip6::Netif::UnicastAddress &unicastAddress : Get<ThreadNetif>().GetUnicastAddresses())
617 {
618 unicastAddress.mSrpRegistered = false;
619 }
620
621 mAutoHostAddressCount = 0;
622
623 mHostInfo.EnableAutoAddress();
624 UpdateState();
625
626 exit:
627 return error;
628 }
629
SetHostAddresses(const Ip6::Address * aAddresses,uint8_t aNumAddresses)630 Error Client::SetHostAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses)
631 {
632 Error error = kErrorNone;
633
634 VerifyOrExit((aAddresses != nullptr) && (aNumAddresses > 0), error = kErrorInvalidArgs);
635 SuccessOrExit(error = UpdateHostInfoStateOnAddressChange());
636
637 mHostInfo.SetAddresses(aAddresses, aNumAddresses);
638 UpdateState();
639
640 exit:
641 return error;
642 }
643
HandleUnicastAddressEvent(Ip6::Netif::AddressEvent aEvent,const Ip6::Netif::UnicastAddress & aAddress)644 void Client::HandleUnicastAddressEvent(Ip6::Netif::AddressEvent aEvent, const Ip6::Netif::UnicastAddress &aAddress)
645 {
646 // This callback from `Netif` signals an impending addition or
647 // removal of a unicast address, occurring before `Notifier`
648 // events. If `AutoAddress` is enabled, we check whether the
649 // address origin is SLAAC (e.g., an OMR address) and request a
650 // longer `TxJitter`. This helps randomize the next SRP
651 // update transmission time when triggered by an OMR prefix
652 // change.
653
654 VerifyOrExit(IsRunning());
655 VerifyOrExit(mHostInfo.IsAutoAddressEnabled());
656
657 VerifyOrExit(aAddress.GetOrigin() == Ip6::Netif::kOriginSlaac);
658
659 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
660 // The `mGuardTimer`, started by `ApplyAutoStartGuardOnAttach()`,
661 // tracks a guard interval after the attach event. If an
662 // address change occurs within this short window, we do not
663 // apply a longer TX jitter, as this likely indicates a device
664 // reboot.
665 VerifyOrExit(!mGuardTimer.IsRunning());
666 #endif
667
668 mTxJitter.Request((aEvent == Ip6::Netif::kAddressAdded) ? TxJitter::kOnSlaacAddrAdd : TxJitter::kOnSlaacAddrRemove);
669
670 exit:
671 return;
672 }
673
ShouldUpdateHostAutoAddresses(void) const674 bool Client::ShouldUpdateHostAutoAddresses(void) const
675 {
676 // Determine if any changes to the addresses on `ThreadNetif`
677 // require registration with the server when `AutoHostAddress` is
678 // enabled. This includes registering all preferred addresses,
679 // excluding link-local and mesh-local addresses. If no eligible
680 // address is available, the ML-EID will be registered.
681
682 bool shouldUpdate = false;
683 uint16_t registeredCount = 0;
684 Ip6::Netif::UnicastAddress &mlEid = Get<Mle::Mle>().GetMeshLocalEidUnicastAddress();
685
686 VerifyOrExit(mHostInfo.IsAutoAddressEnabled());
687
688 // We iterate through all eligible addresses on the `ThreadNetif`.
689 // If we encounter a new address that should be registered but
690 // isn't, or a previously registered address has been removed, we
691 // trigger an SRP update to reflect these changes. However, if a
692 // previously registered address is being deprecated (e.g., due
693 // to an OMR prefix removal from Network Data), we defer the SRP
694 // update. The client will re-register after the deprecation
695 // time has elapsed and the address is removed. In the meantime,
696 // if any other event triggers the client to send an SRP update,
697 // the updated address list will be included in that update.
698
699 for (const Ip6::Netif::UnicastAddress &unicastAddress : Get<ThreadNetif>().GetUnicastAddresses())
700 {
701 if (&unicastAddress == &mlEid)
702 {
703 continue;
704 }
705
706 if (ShouldHostAutoAddressRegister(unicastAddress) != unicastAddress.mSrpRegistered)
707 {
708 // If this address was previously registered but is no
709 // longer eligible, we skip sending an immediate update
710 // only if the address is currently being deprecated
711 // (it's still valid but no longer preferred).
712
713 bool skip = unicastAddress.mSrpRegistered && unicastAddress.mValid && !unicastAddress.mPreferred;
714
715 if (!skip)
716 {
717 ExitNow(shouldUpdate = true);
718 }
719 }
720
721 if (unicastAddress.mSrpRegistered)
722 {
723 registeredCount++;
724 }
725 }
726
727 if (registeredCount == 0)
728 {
729 ExitNow(shouldUpdate = !mlEid.mSrpRegistered);
730 }
731
732 // Compare the current number of addresses that are marked as
733 // registered with the previous value `mAutoHostAddressCount`.
734 // This check handles the case where a previously registered address
735 // has been removed.
736
737 shouldUpdate = (registeredCount != mAutoHostAddressCount);
738
739 exit:
740 return shouldUpdate;
741 }
742
ShouldHostAutoAddressRegister(const Ip6::Netif::UnicastAddress & aUnicastAddress) const743 bool Client::ShouldHostAutoAddressRegister(const Ip6::Netif::UnicastAddress &aUnicastAddress) const
744 {
745 bool shouldRegister = false;
746
747 VerifyOrExit(aUnicastAddress.mValid);
748 VerifyOrExit(aUnicastAddress.mPreferred);
749 VerifyOrExit(!aUnicastAddress.GetAddress().IsLinkLocalUnicast());
750 VerifyOrExit(!Get<Mle::Mle>().IsMeshLocalAddress(aUnicastAddress.GetAddress()));
751
752 shouldRegister = true;
753
754 exit:
755 return shouldRegister;
756 }
757
UpdateHostInfoStateOnAddressChange(void)758 Error Client::UpdateHostInfoStateOnAddressChange(void)
759 {
760 Error error = kErrorNone;
761
762 VerifyOrExit((mHostInfo.GetState() != kToRemove) && (mHostInfo.GetState() != kRemoving),
763 error = kErrorInvalidState);
764
765 if (mHostInfo.GetState() == kRemoved)
766 {
767 mHostInfo.SetState(kToAdd);
768 }
769 else if (mHostInfo.GetState() != kToAdd)
770 {
771 mHostInfo.SetState(kToRefresh);
772 }
773
774 exit:
775 return error;
776 }
777
AddService(Service & aService)778 Error Client::AddService(Service &aService)
779 {
780 Error error;
781
782 VerifyOrExit(mServices.FindMatching(aService) == nullptr, error = kErrorAlready);
783
784 SuccessOrExit(error = aService.Init());
785 mServices.Push(aService);
786
787 aService.SetState(kToAdd);
788 UpdateState();
789
790 exit:
791 return error;
792 }
793
RemoveService(Service & aService)794 Error Client::RemoveService(Service &aService)
795 {
796 Error error = kErrorNone;
797 LinkedList<Service> removedServices;
798
799 VerifyOrExit(mServices.Contains(aService), error = kErrorNotFound);
800
801 UpdateServiceStateToRemove(aService);
802 UpdateState();
803
804 exit:
805 return error;
806 }
807
UpdateServiceStateToRemove(Service & aService)808 void Client::UpdateServiceStateToRemove(Service &aService)
809 {
810 if (aService.GetState() != kRemoving)
811 {
812 aService.SetState(kToRemove);
813 }
814 }
815
ClearService(Service & aService)816 Error Client::ClearService(Service &aService)
817 {
818 Error error;
819
820 SuccessOrExit(error = mServices.Remove(aService));
821 aService.SetNext(nullptr);
822 aService.SetState(kRemoved);
823 UpdateState();
824
825 exit:
826 return error;
827 }
828
RemoveHostAndServices(bool aShouldRemoveKeyLease,bool aSendUnregToServer)829 Error Client::RemoveHostAndServices(bool aShouldRemoveKeyLease, bool aSendUnregToServer)
830 {
831 Error error = kErrorNone;
832
833 LogInfo("Remove host & services");
834
835 VerifyOrExit(mHostInfo.GetState() != kRemoved, error = kErrorAlready);
836
837 if ((mHostInfo.GetState() == kToRemove) || (mHostInfo.GetState() == kRemoving))
838 {
839 // Host info remove is already ongoing, if "key lease" remove mode is
840 // the same, there is no need to send a new update message.
841 VerifyOrExit(mShouldRemoveKeyLease != aShouldRemoveKeyLease);
842 }
843
844 mShouldRemoveKeyLease = aShouldRemoveKeyLease;
845
846 for (Service &service : mServices)
847 {
848 UpdateServiceStateToRemove(service);
849 }
850
851 if ((mHostInfo.GetState() == kToAdd) && !aSendUnregToServer)
852 {
853 // Host info is not added yet (not yet registered with
854 // server), so we can remove it and all services immediately.
855 mHostInfo.SetState(kRemoved);
856 HandleUpdateDone();
857 ExitNow();
858 }
859
860 mHostInfo.SetState(kToRemove);
861 UpdateState();
862
863 exit:
864 return error;
865 }
866
ClearHostAndServices(void)867 void Client::ClearHostAndServices(void)
868 {
869 LogInfo("Clear host & services");
870
871 switch (GetState())
872 {
873 case kStateStopped:
874 case kStatePaused:
875 break;
876
877 case kStateToUpdate:
878 case kStateUpdating:
879 case kStateUpdated:
880 case kStateToRetry:
881 SetState(kStateUpdated);
882 break;
883 }
884
885 mTxFailureRetryCount = 0;
886 ResetRetryWaitInterval();
887
888 mServices.Clear();
889 mHostInfo.Clear();
890 }
891
SetState(State aState)892 void Client::SetState(State aState)
893 {
894 VerifyOrExit(aState != mState);
895
896 LogInfo("State %s -> %s", StateToString(mState), StateToString(aState));
897 mState = aState;
898
899 switch (mState)
900 {
901 case kStateStopped:
902 case kStatePaused:
903 case kStateUpdated:
904 mTimer.Stop();
905 break;
906
907 case kStateToUpdate:
908 mTimer.Start(mTxJitter.DetermineDelay());
909 break;
910
911 case kStateUpdating:
912 mTimer.Start(GetRetryWaitInterval());
913 break;
914
915 case kStateToRetry:
916 break;
917 }
918 exit:
919 return;
920 }
921
ChangeHostAndServiceStates(const ItemState * aNewStates,ServiceStateChangeMode aMode)922 bool Client::ChangeHostAndServiceStates(const ItemState *aNewStates, ServiceStateChangeMode aMode)
923 {
924 bool anyChanged;
925
926 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
927 ItemState oldHostState = mHostInfo.GetState();
928 #endif
929
930 anyChanged = mHostInfo.SetState(aNewStates[mHostInfo.GetState()]);
931
932 for (Service &service : mServices)
933 {
934 if ((aMode == kForServicesAppendedInMessage) && !service.IsAppendedInMessage())
935 {
936 continue;
937 }
938
939 anyChanged |= service.SetState(aNewStates[service.GetState()]);
940 }
941
942 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
943 if ((oldHostState != kRegistered) && (mHostInfo.GetState() == kRegistered))
944 {
945 Settings::SrpClientInfo info;
946
947 switch (mAutoStart.GetState())
948 {
949 case AutoStart::kDisabled:
950 case AutoStart::kFirstTimeSelecting:
951 case AutoStart::kReselecting:
952 break;
953
954 case AutoStart::kSelectedUnicastPreferred:
955 case AutoStart::kSelectedUnicast:
956 info.SetServerAddress(GetServerAddress().GetAddress());
957 info.SetServerPort(GetServerAddress().GetPort());
958 IgnoreError(Get<Settings>().Save(info));
959 break;
960
961 case AutoStart::kSelectedAnycast:
962 IgnoreError(Get<Settings>().Delete<Settings::SrpClientInfo>());
963 break;
964 }
965 }
966 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
967
968 return anyChanged;
969 }
970
InvokeCallback(Error aError) const971 void Client::InvokeCallback(Error aError) const { InvokeCallback(aError, mHostInfo, nullptr); }
972
InvokeCallback(Error aError,const HostInfo & aHostInfo,const Service * aRemovedServices) const973 void Client::InvokeCallback(Error aError, const HostInfo &aHostInfo, const Service *aRemovedServices) const
974 {
975 mCallback.InvokeIfSet(aError, &aHostInfo, mServices.GetHead(), aRemovedServices);
976 }
977
SendUpdate(void)978 void Client::SendUpdate(void)
979 {
980 static const ItemState kNewStateOnMessageTx[]{
981 /* (0) kToAdd -> */ kAdding,
982 /* (1) kAdding -> */ kAdding,
983 /* (2) kToRefresh -> */ kRefreshing,
984 /* (3) kRefreshing -> */ kRefreshing,
985 /* (4) kToRemove -> */ kRemoving,
986 /* (5) kRemoving -> */ kRemoving,
987 /* (6) kRegistered -> */ kRegistered,
988 /* (7) kRemoved -> */ kRemoved,
989 };
990
991 Error error = kErrorNone;
992 MsgInfo info;
993 uint32_t length;
994 bool anyChanged;
995
996 info.mMessage.Reset(mSocket.NewMessage());
997 VerifyOrExit(info.mMessage != nullptr, error = kErrorNoBufs);
998
999 SuccessOrExit(error = PrepareUpdateMessage(info));
1000
1001 length = info.mMessage->GetLength() + sizeof(Ip6::Udp::Header) + sizeof(Ip6::Header);
1002
1003 if (length >= Ip6::kMaxDatagramLength)
1004 {
1005 LogInfo("Msg len %lu is larger than MTU, enabling single service mode", ToUlong(length));
1006 mSingleServiceMode = true;
1007 IgnoreError(info.mMessage->SetLength(0));
1008 SuccessOrExit(error = PrepareUpdateMessage(info));
1009 }
1010
1011 SuccessOrExit(error = mSocket.SendTo(*info.mMessage, Ip6::MessageInfo()));
1012
1013 // Ownership of the message is transferred to the socket upon a
1014 // successful `SendTo()` call.
1015
1016 info.mMessage.Release();
1017
1018 LogInfo("Send update, msg-id:0x%x", mNextMessageId);
1019
1020 // State changes:
1021 // kToAdd -> kAdding
1022 // kToRefresh -> kRefreshing
1023 // kToRemove -> kRemoving
1024
1025 anyChanged = ChangeHostAndServiceStates(kNewStateOnMessageTx, kForServicesAppendedInMessage);
1026
1027 // `mNextMessageId` tracks the message ID used in the prepared
1028 // update message. It is incremented after a successful
1029 // `mSocket.SendTo()` call. If unsuccessful, the same ID can be
1030 // reused for the next update.
1031 //
1032 // Acceptable response message IDs fall within the range starting
1033 // at `mResponseMessageId ` and ending before `mNextMessageId`.
1034 //
1035 // `anyChanged` tracks if any host or service states have changed.
1036 // If not, the prepared message is identical to the last one with
1037 // the same hosts/services, allowing us to accept earlier message
1038 // IDs. If changes occur, `mResponseMessageId ` is updated to
1039 // ensure only responses to the latest message are accepted.
1040
1041 if (anyChanged)
1042 {
1043 mResponseMessageId = mNextMessageId;
1044 }
1045
1046 mNextMessageId++;
1047
1048 // Remember the update message tx time to use later to determine the
1049 // lease renew time.
1050 mLeaseRenewTime = TimerMilli::GetNow();
1051 mTxFailureRetryCount = 0;
1052
1053 SetState(kStateUpdating);
1054
1055 if (!Get<Mle::Mle>().IsRxOnWhenIdle())
1056 {
1057 // If device is sleepy send fast polls while waiting for
1058 // the response from server.
1059 Get<DataPollSender>().SendFastPolls(kFastPollsAfterUpdateTx);
1060 }
1061
1062 exit:
1063 if (error != kErrorNone)
1064 {
1065 // If there is an error in preparation or transmission of the
1066 // update message (e.g., no buffer to allocate message), up to
1067 // `kMaxTxFailureRetries` times, we wait for a short interval
1068 // `kTxFailureRetryInterval` and try again. After this, we
1069 // continue to retry using the `mRetryWaitInterval` (which keeps
1070 // growing on each failure).
1071
1072 LogInfo("Failed to send update: %s", ErrorToString(error));
1073
1074 mSingleServiceMode = false;
1075
1076 SetState(kStateToRetry);
1077
1078 if (mTxFailureRetryCount < kMaxTxFailureRetries)
1079 {
1080 uint32_t interval;
1081
1082 mTxFailureRetryCount++;
1083 interval = Random::NonCrypto::AddJitter(kTxFailureRetryInterval, kTxFailureRetryJitter);
1084 mTimer.Start(interval);
1085
1086 LogInfo("Quick retry %u in %lu msec", mTxFailureRetryCount, ToUlong(interval));
1087
1088 // Do not report message preparation errors to user
1089 // until `kMaxTxFailureRetries` are exhausted.
1090 }
1091 else
1092 {
1093 LogRetryWaitInterval();
1094 mTimer.Start(Random::NonCrypto::AddJitter(GetRetryWaitInterval(), kRetryIntervalJitter));
1095 GrowRetryWaitInterval();
1096 InvokeCallback(error);
1097 }
1098 }
1099 }
1100
PrepareUpdateMessage(MsgInfo & aInfo)1101 Error Client::PrepareUpdateMessage(MsgInfo &aInfo)
1102 {
1103 constexpr uint16_t kHeaderOffset = 0;
1104
1105 Error error = kErrorNone;
1106 Dns::UpdateHeader header;
1107
1108 aInfo.mDomainNameOffset = MsgInfo::kUnknownOffset;
1109 aInfo.mHostNameOffset = MsgInfo::kUnknownOffset;
1110 aInfo.mRecordCount = 0;
1111
1112 #if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
1113 aInfo.mKeyInfo.SetKeyRef(Get<Crypto::Storage::KeyRefManager>().KeyRefFor(Crypto::Storage::KeyRefManager::kEcdsa));
1114 #endif
1115
1116 SuccessOrExit(error = ReadOrGenerateKey(aInfo.mKeyInfo));
1117
1118 header.SetMessageId(mNextMessageId);
1119
1120 // SRP Update (DNS Update) message must have exactly one record in
1121 // Zone section, no records in Prerequisite Section, can have
1122 // multiple records in Update Section (tracked as they are added),
1123 // and two records in Additional Data Section (OPT and SIG records).
1124 // The SIG record itself should not be included in calculation of
1125 // SIG(0) signature, so the addition record count is set to one
1126 // here. After signature calculation and appending of SIG record,
1127 // the additional record count is updated to two and the header is
1128 // rewritten in the message.
1129
1130 header.SetZoneRecordCount(1);
1131 header.SetAdditionalRecordCount(1);
1132 SuccessOrExit(error = aInfo.mMessage->Append(header));
1133
1134 // Prepare Zone section
1135
1136 aInfo.mDomainNameOffset = aInfo.mMessage->GetLength();
1137 SuccessOrExit(error = Dns::Name::AppendName(mDomainName, *aInfo.mMessage));
1138 SuccessOrExit(error = aInfo.mMessage->Append(Dns::Zone()));
1139
1140 // Prepare Update section
1141
1142 SuccessOrExit(error = AppendServiceInstructions(aInfo));
1143 SuccessOrExit(error = AppendHostDescriptionInstruction(aInfo));
1144
1145 header.SetUpdateRecordCount(aInfo.mRecordCount);
1146 aInfo.mMessage->Write(kHeaderOffset, header);
1147
1148 // Prepare Additional Data section
1149
1150 SuccessOrExit(error = AppendUpdateLeaseOptRecord(aInfo));
1151 SuccessOrExit(error = AppendSignature(aInfo));
1152
1153 header.SetAdditionalRecordCount(2); // Lease OPT and SIG RRs
1154 aInfo.mMessage->Write(kHeaderOffset, header);
1155
1156 exit:
1157 return error;
1158 }
1159
1160 #if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
ReadOrGenerateKey(KeyInfo & aKeyInfo)1161 Error Client::ReadOrGenerateKey(KeyInfo &aKeyInfo)
1162 {
1163 Error error = kErrorNone;
1164 Crypto::Ecdsa::P256::KeyPair keyPair;
1165
1166 VerifyOrExit(!Crypto::Storage::HasKey(aKeyInfo.GetKeyRef()));
1167 error = Get<Settings>().Read<Settings::SrpEcdsaKey>(keyPair);
1168
1169 if (error == kErrorNone)
1170 {
1171 if (aKeyInfo.ImportKeyPair(keyPair) != kErrorNone)
1172 {
1173 SuccessOrExit(error = aKeyInfo.Generate());
1174 }
1175 IgnoreError(Get<Settings>().Delete<Settings::SrpEcdsaKey>());
1176 }
1177 else
1178 {
1179 SuccessOrExit(error = aKeyInfo.Generate());
1180 }
1181 exit:
1182 return error;
1183 }
1184 #else
ReadOrGenerateKey(KeyInfo & aKeyInfo)1185 Error Client::ReadOrGenerateKey(KeyInfo &aKeyInfo)
1186 {
1187 Error error;
1188
1189 error = Get<Settings>().Read<Settings::SrpEcdsaKey>(aKeyInfo);
1190
1191 if (error == kErrorNone)
1192 {
1193 Crypto::Ecdsa::P256::PublicKey publicKey;
1194
1195 if (aKeyInfo.GetPublicKey(publicKey) == kErrorNone)
1196 {
1197 ExitNow();
1198 }
1199 }
1200
1201 SuccessOrExit(error = aKeyInfo.Generate());
1202 IgnoreError(Get<Settings>().Save<Settings::SrpEcdsaKey>(aKeyInfo));
1203
1204 exit:
1205 return error;
1206 }
1207 #endif // OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
1208
AppendServiceInstructions(MsgInfo & aInfo)1209 Error Client::AppendServiceInstructions(MsgInfo &aInfo)
1210 {
1211 Error error = kErrorNone;
1212
1213 if ((mHostInfo.GetState() == kToRemove) || (mHostInfo.GetState() == kRemoving))
1214 {
1215 // When host is being removed, there is no need to include
1216 // services in the message (server is expected to remove any
1217 // previously registered services by this client). However, we
1218 // still mark all services as if they are appended in the message
1219 // so to ensure to update their state after sending the message.
1220
1221 for (Service &service : mServices)
1222 {
1223 service.MarkAsAppendedInMessage();
1224 }
1225
1226 mLease = 0;
1227 mKeyLease = mShouldRemoveKeyLease ? 0 : mDefaultKeyLease;
1228 ExitNow();
1229 }
1230
1231 mLease = kUnspecifiedInterval;
1232 mKeyLease = kUnspecifiedInterval;
1233
1234 // We first go through all services which are being updated (in any
1235 // of `...ing` states) and determine the lease and key lease intervals
1236 // associated with them. By the end of the loop either of `mLease` or
1237 // `mKeyLease` may be set or may still remain `kUnspecifiedInterval`.
1238
1239 for (Service &service : mServices)
1240 {
1241 uint32_t lease = DetermineLeaseInterval(service.GetLease(), mDefaultLease);
1242 uint32_t keyLease = Max(DetermineLeaseInterval(service.GetKeyLease(), mDefaultKeyLease), lease);
1243
1244 service.ClearAppendedInMessageFlag();
1245
1246 switch (service.GetState())
1247 {
1248 case kAdding:
1249 case kRefreshing:
1250 OT_ASSERT((mLease == kUnspecifiedInterval) || (mLease == lease));
1251 mLease = lease;
1252
1253 OT_FALL_THROUGH;
1254
1255 case kRemoving:
1256 OT_ASSERT((mKeyLease == kUnspecifiedInterval) || (mKeyLease == keyLease));
1257 mKeyLease = keyLease;
1258 break;
1259
1260 case kToAdd:
1261 case kToRefresh:
1262 case kToRemove:
1263 case kRegistered:
1264 case kRemoved:
1265 break;
1266 }
1267 }
1268
1269 // We go through all services again and append the services that
1270 // match the selected `mLease` and `mKeyLease`. If the lease intervals
1271 // are not yet set, the first appended service will determine them.
1272
1273 for (Service &service : mServices)
1274 {
1275 // Skip over services that are already registered in this loop.
1276 // They may be added from the loop below once the lease intervals
1277 // are determined.
1278
1279 if ((service.GetState() != kRegistered) && CanAppendService(service))
1280 {
1281 SuccessOrExit(error = AppendServiceInstruction(service, aInfo));
1282
1283 if (mSingleServiceMode)
1284 {
1285 // In "single service mode", we allow only one service
1286 // to be appended in the message.
1287 break;
1288 }
1289 }
1290 }
1291
1292 if (!mSingleServiceMode)
1293 {
1294 for (Service &service : mServices)
1295 {
1296 if ((service.GetState() == kRegistered) && CanAppendService(service) && ShouldRenewEarly(service))
1297 {
1298 // If the lease needs to be renewed or if we are close to the
1299 // renewal time of a registered service, we refresh the service
1300 // early and include it in this update. This helps put more
1301 // services on the same lease refresh schedule.
1302
1303 service.SetState(kToRefresh);
1304 SuccessOrExit(error = AppendServiceInstruction(service, aInfo));
1305 }
1306 }
1307 }
1308
1309 // `mLease` or `mKeylease` may be determined from the set of
1310 // services included in the message. If they are not yet set we
1311 // use the default intervals.
1312
1313 mLease = DetermineLeaseInterval(mLease, mDefaultLease);
1314 mKeyLease = DetermineLeaseInterval(mKeyLease, mDefaultKeyLease);
1315
1316 // When message only contains removal of a previously registered
1317 // service, then `mKeyLease` is set but `mLease` remains unspecified.
1318 // In such a case, we end up using `mDefaultLease` but then we need
1319 // to make sure it is not greater than the selected `mKeyLease`.
1320
1321 mLease = Min(mLease, mKeyLease);
1322
1323 exit:
1324 return error;
1325 }
1326
CanAppendService(const Service & aService)1327 bool Client::CanAppendService(const Service &aService)
1328 {
1329 // Check the lease intervals associated with `aService` to see if
1330 // it can be included in this message. When removing a service,
1331 // only key lease interval should match. In all other cases, both
1332 // lease and key lease should match. The `mLease` and/or `mKeyLease`
1333 // may be updated if they were unspecified.
1334
1335 bool canAppend = false;
1336 uint32_t lease = DetermineLeaseInterval(aService.GetLease(), mDefaultLease);
1337 uint32_t keyLease = Max(DetermineLeaseInterval(aService.GetKeyLease(), mDefaultKeyLease), lease);
1338
1339 switch (aService.GetState())
1340 {
1341 case kToAdd:
1342 case kAdding:
1343 case kToRefresh:
1344 case kRefreshing:
1345 case kRegistered:
1346 VerifyOrExit((mLease == kUnspecifiedInterval) || (mLease == lease));
1347 VerifyOrExit((mKeyLease == kUnspecifiedInterval) || (mKeyLease == keyLease));
1348 mLease = lease;
1349 mKeyLease = keyLease;
1350 canAppend = true;
1351 break;
1352
1353 case kToRemove:
1354 case kRemoving:
1355 VerifyOrExit((mKeyLease == kUnspecifiedInterval) || (mKeyLease == keyLease));
1356 mKeyLease = keyLease;
1357 canAppend = true;
1358 break;
1359
1360 case kRemoved:
1361 break;
1362 }
1363
1364 exit:
1365 return canAppend;
1366 }
1367
AppendServiceInstruction(Service & aService,MsgInfo & aInfo)1368 Error Client::AppendServiceInstruction(Service &aService, MsgInfo &aInfo)
1369 {
1370 Error error = kErrorNone;
1371 bool removing = ((aService.GetState() == kToRemove) || (aService.GetState() == kRemoving));
1372 Dns::ResourceRecord rr;
1373 Dns::SrvRecord srv;
1374 uint16_t serviceNameOffset;
1375 uint16_t instanceNameOffset;
1376 uint16_t offset;
1377
1378 aService.MarkAsAppendedInMessage();
1379
1380 //----------------------------------
1381 // Service Discovery Instruction
1382
1383 // PTR record
1384
1385 // "service name labels" + (pointer to) domain name.
1386 serviceNameOffset = aInfo.mMessage->GetLength();
1387 SuccessOrExit(error = Dns::Name::AppendMultipleLabels(aService.GetName(), *aInfo.mMessage));
1388 SuccessOrExit(error = Dns::Name::AppendPointerLabel(aInfo.mDomainNameOffset, *aInfo.mMessage));
1389
1390 // On remove, we use "Delete an RR from an RRSet" where class is set
1391 // to NONE and TTL to zero (RFC 2136 - section 2.5.4).
1392
1393 rr.Init(Dns::ResourceRecord::kTypePtr, removing ? Dns::PtrRecord::kClassNone : Dns::PtrRecord::kClassInternet);
1394 rr.SetTtl(removing ? 0 : DetermineTtl());
1395 offset = aInfo.mMessage->GetLength();
1396 SuccessOrExit(error = aInfo.mMessage->Append(rr));
1397
1398 // "Instance name" + (pointer to) service name.
1399 instanceNameOffset = aInfo.mMessage->GetLength();
1400 SuccessOrExit(error = Dns::Name::AppendLabel(aService.GetInstanceName(), *aInfo.mMessage));
1401 SuccessOrExit(error = Dns::Name::AppendPointerLabel(serviceNameOffset, *aInfo.mMessage));
1402
1403 UpdateRecordLengthInMessage(rr, offset, *aInfo.mMessage);
1404 aInfo.mRecordCount++;
1405
1406 if (aService.HasSubType() && !removing)
1407 {
1408 const char *subTypeLabel;
1409 uint16_t subServiceNameOffset = 0;
1410
1411 for (uint16_t index = 0; (subTypeLabel = aService.GetSubTypeLabelAt(index)) != nullptr; ++index)
1412 {
1413 // subtype label + "_sub" label + (pointer to) service name.
1414
1415 SuccessOrExit(error = Dns::Name::AppendLabel(subTypeLabel, *aInfo.mMessage));
1416
1417 if (index == 0)
1418 {
1419 subServiceNameOffset = aInfo.mMessage->GetLength();
1420 SuccessOrExit(error = Dns::Name::AppendLabel("_sub", *aInfo.mMessage));
1421 SuccessOrExit(error = Dns::Name::AppendPointerLabel(serviceNameOffset, *aInfo.mMessage));
1422 }
1423 else
1424 {
1425 SuccessOrExit(error = Dns::Name::AppendPointerLabel(subServiceNameOffset, *aInfo.mMessage));
1426 }
1427
1428 // `rr` is already initialized as PTR.
1429 offset = aInfo.mMessage->GetLength();
1430 SuccessOrExit(error = aInfo.mMessage->Append(rr));
1431
1432 SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, *aInfo.mMessage));
1433 UpdateRecordLengthInMessage(rr, offset, *aInfo.mMessage);
1434 aInfo.mRecordCount++;
1435 }
1436 }
1437
1438 //----------------------------------
1439 // Service Description Instruction
1440
1441 // "Delete all RRsets from a name" for Instance Name.
1442
1443 SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, *aInfo.mMessage));
1444 SuccessOrExit(error = AppendDeleteAllRrsets(aInfo));
1445 aInfo.mRecordCount++;
1446
1447 VerifyOrExit(!removing);
1448
1449 // SRV RR
1450
1451 SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, *aInfo.mMessage));
1452 srv.Init();
1453 srv.SetTtl(DetermineTtl());
1454 srv.SetPriority(aService.GetPriority());
1455 srv.SetWeight(aService.GetWeight());
1456 srv.SetPort(aService.GetPort());
1457 offset = aInfo.mMessage->GetLength();
1458 SuccessOrExit(error = aInfo.mMessage->Append(srv));
1459 SuccessOrExit(error = AppendHostName(aInfo));
1460 UpdateRecordLengthInMessage(srv, offset, *aInfo.mMessage);
1461 aInfo.mRecordCount++;
1462
1463 // TXT RR
1464
1465 SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, *aInfo.mMessage));
1466 rr.Init(Dns::ResourceRecord::kTypeTxt);
1467 offset = aInfo.mMessage->GetLength();
1468 SuccessOrExit(error = aInfo.mMessage->Append(rr));
1469 SuccessOrExit(
1470 error = Dns::TxtEntry::AppendEntries(aService.GetTxtEntries(), aService.GetNumTxtEntries(), *aInfo.mMessage));
1471 UpdateRecordLengthInMessage(rr, offset, *aInfo.mMessage);
1472 aInfo.mRecordCount++;
1473
1474 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
1475 if (mServiceKeyRecordEnabled)
1476 {
1477 // KEY RR is optional in "Service Description Instruction". It
1478 // is added here under `REFERENCE_DEVICE` config and is intended
1479 // for testing only.
1480
1481 SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, *aInfo.mMessage));
1482 SuccessOrExit(error = AppendKeyRecord(aInfo));
1483 }
1484 #endif
1485
1486 exit:
1487 return error;
1488 }
1489
AppendHostDescriptionInstruction(MsgInfo & aInfo)1490 Error Client::AppendHostDescriptionInstruction(MsgInfo &aInfo)
1491 {
1492 Error error = kErrorNone;
1493
1494 //----------------------------------
1495 // Host Description Instruction
1496
1497 // "Delete all RRsets from a name" for Host Name.
1498
1499 SuccessOrExit(error = AppendHostName(aInfo));
1500 SuccessOrExit(error = AppendDeleteAllRrsets(aInfo));
1501 aInfo.mRecordCount++;
1502
1503 // AAAA RRs
1504
1505 if (mHostInfo.IsAutoAddressEnabled())
1506 {
1507 // Append all preferred addresses on Thread netif excluding link-local
1508 // and mesh-local addresses. If no address is appended, we include
1509 // the mesh local EID.
1510
1511 mAutoHostAddressCount = 0;
1512
1513 for (Ip6::Netif::UnicastAddress &unicastAddress : Get<ThreadNetif>().GetUnicastAddresses())
1514 {
1515 if (ShouldHostAutoAddressRegister(unicastAddress))
1516 {
1517 SuccessOrExit(error = AppendAaaaRecord(unicastAddress.GetAddress(), aInfo));
1518 unicastAddress.mSrpRegistered = true;
1519 mAutoHostAddressCount++;
1520 }
1521 else
1522 {
1523 unicastAddress.mSrpRegistered = false;
1524 }
1525 }
1526
1527 if (mAutoHostAddressCount == 0)
1528 {
1529 Ip6::Netif::UnicastAddress &mlEid = Get<Mle::Mle>().GetMeshLocalEidUnicastAddress();
1530
1531 SuccessOrExit(error = AppendAaaaRecord(mlEid.GetAddress(), aInfo));
1532 mlEid.mSrpRegistered = true;
1533 mAutoHostAddressCount++;
1534 }
1535 }
1536 else
1537 {
1538 for (uint8_t index = 0; index < mHostInfo.GetNumAddresses(); index++)
1539 {
1540 SuccessOrExit(error = AppendAaaaRecord(mHostInfo.GetAddress(index), aInfo));
1541 }
1542 }
1543
1544 // KEY RR
1545
1546 SuccessOrExit(error = AppendHostName(aInfo));
1547 SuccessOrExit(error = AppendKeyRecord(aInfo));
1548
1549 exit:
1550 return error;
1551 }
1552
AppendAaaaRecord(const Ip6::Address & aAddress,MsgInfo & aInfo) const1553 Error Client::AppendAaaaRecord(const Ip6::Address &aAddress, MsgInfo &aInfo) const
1554 {
1555 Error error;
1556 Dns::ResourceRecord rr;
1557
1558 rr.Init(Dns::ResourceRecord::kTypeAaaa);
1559 rr.SetTtl(DetermineTtl());
1560 rr.SetLength(sizeof(Ip6::Address));
1561
1562 SuccessOrExit(error = AppendHostName(aInfo));
1563 SuccessOrExit(error = aInfo.mMessage->Append(rr));
1564 SuccessOrExit(error = aInfo.mMessage->Append(aAddress));
1565 aInfo.mRecordCount++;
1566
1567 exit:
1568 return error;
1569 }
1570
AppendKeyRecord(MsgInfo & aInfo) const1571 Error Client::AppendKeyRecord(MsgInfo &aInfo) const
1572 {
1573 Error error;
1574 Dns::KeyRecord key;
1575 Crypto::Ecdsa::P256::PublicKey publicKey;
1576
1577 key.Init();
1578 key.SetTtl(DetermineTtl());
1579 key.SetFlags(Dns::KeyRecord::kAuthConfidPermitted, Dns::KeyRecord::kOwnerNonZone,
1580 Dns::KeyRecord::kSignatoryFlagGeneral);
1581 key.SetProtocol(Dns::KeyRecord::kProtocolDnsSec);
1582 key.SetAlgorithm(Dns::KeyRecord::kAlgorithmEcdsaP256Sha256);
1583 key.SetLength(sizeof(Dns::KeyRecord) - sizeof(Dns::ResourceRecord) + sizeof(Crypto::Ecdsa::P256::PublicKey));
1584 SuccessOrExit(error = aInfo.mMessage->Append(key));
1585 SuccessOrExit(error = aInfo.mKeyInfo.GetPublicKey(publicKey));
1586 SuccessOrExit(error = aInfo.mMessage->Append(publicKey));
1587 aInfo.mRecordCount++;
1588
1589 exit:
1590 return error;
1591 }
1592
AppendDeleteAllRrsets(MsgInfo & aInfo) const1593 Error Client::AppendDeleteAllRrsets(MsgInfo &aInfo) const
1594 {
1595 // "Delete all RRsets from a name" (RFC 2136 - 2.5.3)
1596 // Name should be already appended in the message.
1597
1598 Dns::ResourceRecord rr;
1599
1600 rr.Init(Dns::ResourceRecord::kTypeAny, Dns::ResourceRecord::kClassAny);
1601 rr.SetTtl(0);
1602 rr.SetLength(0);
1603
1604 return aInfo.mMessage->Append(rr);
1605 }
1606
AppendHostName(MsgInfo & aInfo,bool aDoNotCompress) const1607 Error Client::AppendHostName(MsgInfo &aInfo, bool aDoNotCompress) const
1608 {
1609 Error error;
1610
1611 if (aDoNotCompress)
1612 {
1613 // Uncompressed (canonical form) of host name is used for SIG(0)
1614 // calculation.
1615 SuccessOrExit(error = Dns::Name::AppendMultipleLabels(mHostInfo.GetName(), *aInfo.mMessage));
1616 error = Dns::Name::AppendName(mDomainName, *aInfo.mMessage);
1617 ExitNow();
1618 }
1619
1620 // If host name was previously added in the message, add it
1621 // compressed as pointer to the previous one. Otherwise,
1622 // append it and remember the offset.
1623
1624 if (aInfo.mHostNameOffset != MsgInfo::kUnknownOffset)
1625 {
1626 ExitNow(error = Dns::Name::AppendPointerLabel(aInfo.mHostNameOffset, *aInfo.mMessage));
1627 }
1628
1629 aInfo.mHostNameOffset = aInfo.mMessage->GetLength();
1630 SuccessOrExit(error = Dns::Name::AppendMultipleLabels(mHostInfo.GetName(), *aInfo.mMessage));
1631 error = Dns::Name::AppendPointerLabel(aInfo.mDomainNameOffset, *aInfo.mMessage);
1632
1633 exit:
1634 return error;
1635 }
1636
AppendUpdateLeaseOptRecord(MsgInfo & aInfo)1637 Error Client::AppendUpdateLeaseOptRecord(MsgInfo &aInfo)
1638 {
1639 Error error;
1640 Dns::OptRecord optRecord;
1641 Dns::LeaseOption leaseOption;
1642 uint16_t optionSize;
1643
1644 // Append empty (root domain) as OPT RR name.
1645 SuccessOrExit(error = Dns::Name::AppendTerminator(*aInfo.mMessage));
1646
1647 // `Init()` sets the type and clears (set to zero) the extended
1648 // Response Code, version and all flags.
1649 optRecord.Init();
1650 optRecord.SetUdpPayloadSize(kUdpPayloadSize);
1651 optRecord.SetDnsSecurityFlag();
1652
1653 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
1654 if (mUseShortLeaseOption)
1655 {
1656 LogInfo("Test mode - appending short variant of Lease Option");
1657 mKeyLease = mLease;
1658 leaseOption.InitAsShortVariant(mLease);
1659 }
1660 else
1661 #endif
1662 {
1663 leaseOption.InitAsLongVariant(mLease, mKeyLease);
1664 }
1665
1666 optionSize = static_cast<uint16_t>(leaseOption.GetSize());
1667
1668 optRecord.SetLength(optionSize);
1669
1670 SuccessOrExit(error = aInfo.mMessage->Append(optRecord));
1671 error = aInfo.mMessage->AppendBytes(&leaseOption, optionSize);
1672
1673 exit:
1674 return error;
1675 }
1676
AppendSignature(MsgInfo & aInfo)1677 Error Client::AppendSignature(MsgInfo &aInfo)
1678 {
1679 Error error;
1680 Dns::SigRecord sig;
1681 Crypto::Sha256 sha256;
1682 Crypto::Sha256::Hash hash;
1683 Crypto::Ecdsa::P256::Signature signature;
1684 uint16_t offset;
1685 uint16_t len;
1686
1687 // Prepare SIG RR: TTL, type covered, labels count should be set
1688 // to zero. Since we have no clock, inception and expiration time
1689 // are also set to zero. The RDATA length will be set later (not
1690 // yet known due to variably (and possible compression) of signer's
1691 // name.
1692
1693 sig.Clear();
1694 sig.Init(Dns::ResourceRecord::kClassAny);
1695 sig.SetAlgorithm(Dns::KeyRecord::kAlgorithmEcdsaP256Sha256);
1696
1697 // Append the SIG RR with full uncompressed form of the host name
1698 // as the signer's name. This is used for SIG(0) calculation only.
1699 // It will be overwritten with host name compressed.
1700
1701 offset = aInfo.mMessage->GetLength();
1702 SuccessOrExit(error = aInfo.mMessage->Append(sig));
1703 SuccessOrExit(error = AppendHostName(aInfo, /* aDoNotCompress */ true));
1704
1705 // Calculate signature (RFC 2931): Calculated over "data" which is
1706 // concatenation of (1) the SIG RR RDATA wire format (including
1707 // the canonical form of the signer's name), entirely omitting the
1708 // signature subfield, (2) DNS query message, including DNS header
1709 // but not UDP/IP header before the header RR counts have been
1710 // adjusted for the inclusion of SIG(0).
1711
1712 sha256.Start();
1713
1714 // (1) SIG RR RDATA wire format
1715 len = aInfo.mMessage->GetLength() - offset - sizeof(Dns::ResourceRecord);
1716 sha256.Update(*aInfo.mMessage, offset + sizeof(Dns::ResourceRecord), len);
1717
1718 // (2) Message from DNS header before SIG
1719 sha256.Update(*aInfo.mMessage, 0, offset);
1720
1721 sha256.Finish(hash);
1722 SuccessOrExit(error = aInfo.mKeyInfo.Sign(hash, signature));
1723
1724 // Move back in message and append SIG RR now with compressed host
1725 // name (as signer's name) along with the calculated signature.
1726
1727 IgnoreError(aInfo.mMessage->SetLength(offset));
1728
1729 // SIG(0) uses owner name of root (single zero byte).
1730 SuccessOrExit(error = Dns::Name::AppendTerminator(*aInfo.mMessage));
1731
1732 offset = aInfo.mMessage->GetLength();
1733 SuccessOrExit(error = aInfo.mMessage->Append(sig));
1734 SuccessOrExit(error = AppendHostName(aInfo));
1735 SuccessOrExit(error = aInfo.mMessage->Append(signature));
1736 UpdateRecordLengthInMessage(sig, offset, *aInfo.mMessage);
1737
1738 exit:
1739 return error;
1740 }
1741
UpdateRecordLengthInMessage(Dns::ResourceRecord & aRecord,uint16_t aOffset,Message & aMessage) const1742 void Client::UpdateRecordLengthInMessage(Dns::ResourceRecord &aRecord, uint16_t aOffset, Message &aMessage) const
1743 {
1744 // This method is used to calculate an RR DATA length and update
1745 // (rewrite) it in a message. This should be called immediately
1746 // after all the fields in the record are written in the message.
1747 // `aOffset` gives the offset in the message to the start of the
1748 // record.
1749
1750 aRecord.SetLength(aMessage.GetLength() - aOffset - sizeof(Dns::ResourceRecord));
1751 aMessage.Write(aOffset, aRecord);
1752 }
1753
HandleUdpReceive(Message & aMessage,const Ip6::MessageInfo & aMessageInfo)1754 void Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
1755 {
1756 OT_UNUSED_VARIABLE(aMessageInfo);
1757
1758 ProcessResponse(aMessage);
1759 }
1760
ProcessResponse(Message & aMessage)1761 void Client::ProcessResponse(Message &aMessage)
1762 {
1763 static const ItemState kNewStateOnUpdateDone[]{
1764 /* (0) kToAdd -> */ kToAdd,
1765 /* (1) kAdding -> */ kRegistered,
1766 /* (2) kToRefresh -> */ kToRefresh,
1767 /* (3) kRefreshing -> */ kRegistered,
1768 /* (4) kToRemove -> */ kToRemove,
1769 /* (5) kRemoving -> */ kRemoved,
1770 /* (6) kRegistered -> */ kRegistered,
1771 /* (7) kRemoved -> */ kRemoved,
1772 };
1773
1774 Error error = kErrorNone;
1775 Dns::UpdateHeader header;
1776 uint16_t offset = aMessage.GetOffset();
1777 uint16_t recordCount;
1778 LinkedList<Service> removedServices;
1779
1780 switch (GetState())
1781 {
1782 case kStateToUpdate:
1783 case kStateUpdating:
1784 case kStateToRetry:
1785 break;
1786 case kStateStopped:
1787 case kStatePaused:
1788 case kStateUpdated:
1789 ExitNow();
1790 }
1791
1792 SuccessOrExit(error = aMessage.Read(offset, header));
1793
1794 VerifyOrExit(header.GetType() == Dns::Header::kTypeResponse, error = kErrorParse);
1795 VerifyOrExit(header.GetQueryType() == Dns::Header::kQueryTypeUpdate, error = kErrorParse);
1796
1797 VerifyOrExit(IsResponseMessageIdValid(header.GetMessageId()), error = kErrorDrop);
1798 mResponseMessageId = header.GetMessageId() + 1;
1799
1800 if (!Get<Mle::Mle>().IsRxOnWhenIdle())
1801 {
1802 Get<DataPollSender>().StopFastPolls();
1803 }
1804
1805 LogInfo("Received response, msg-id:0x%x", header.GetMessageId());
1806
1807 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
1808 mAutoStart.ResetTimeoutFailureCount();
1809 #endif
1810
1811 error = Dns::Header::ResponseCodeToError(header.GetResponseCode());
1812
1813 if (error != kErrorNone)
1814 {
1815 LogInfo("Server rejected %s code:%d", ErrorToString(error), header.GetResponseCode());
1816
1817 if (mHostInfo.GetState() == kAdding)
1818 {
1819 // Since server rejected the update message, we go back to
1820 // `kToAdd` state to allow user to give a new name using
1821 // `SetHostName()`.
1822 mHostInfo.SetState(kToAdd);
1823 }
1824
1825 // Wait for the timer to expire to retry. Note that timer is
1826 // already scheduled for the current wait interval when state
1827 // was changed to `kStateUpdating`.
1828
1829 LogRetryWaitInterval();
1830 GrowRetryWaitInterval();
1831 SetState(kStateToRetry);
1832 InvokeCallback(error);
1833
1834 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
1835 if ((error == kErrorDuplicated) || (error == kErrorSecurity))
1836 {
1837 // If the server rejects the update with specific errors
1838 // (indicating duplicate name and/or security error), we
1839 // try to switch the server (we check if another can be
1840 // found in the Network Data).
1841 //
1842 // Note that this is done after invoking the callback and
1843 // notifying the user of the error from server. This works
1844 // correctly even if user makes changes from callback
1845 // (e.g., calls SRP client APIs like `Stop` or disables
1846 // auto-start), since we have a guard check at the top of
1847 // `SelectNextServer()` to verify that client is still
1848 // running and auto-start is enabled and selected the
1849 // server.
1850
1851 SelectNextServer(/* aDisallowSwitchOnRegisteredHost */ true);
1852 }
1853 #endif
1854 ExitNow(error = kErrorNone);
1855 }
1856
1857 offset += sizeof(header);
1858
1859 // Skip over all sections till Additional Data section
1860 // SPEC ENHANCEMENT: Server can echo the request back or not
1861 // include any of RRs. Would be good to explicitly require SRP server
1862 // to not echo back RRs.
1863
1864 if (header.GetZoneRecordCount() != 0)
1865 {
1866 VerifyOrExit(header.GetZoneRecordCount() == 1, error = kErrorParse);
1867 SuccessOrExit(error = Dns::Name::ParseName(aMessage, offset));
1868 VerifyOrExit(offset + sizeof(Dns::Zone) <= aMessage.GetLength(), error = kErrorParse);
1869 offset += sizeof(Dns::Zone);
1870 }
1871
1872 // Check for Update Lease OPT RR. This determines the lease
1873 // interval accepted by server. If not present, then use the
1874 // transmitted lease interval from the update request message.
1875
1876 recordCount =
1877 header.GetPrerequisiteRecordCount() + header.GetUpdateRecordCount() + header.GetAdditionalRecordCount();
1878
1879 while (recordCount > 0)
1880 {
1881 uint16_t startOffset = offset;
1882 Dns::ResourceRecord rr;
1883
1884 SuccessOrExit(error = ReadResourceRecord(aMessage, offset, rr));
1885 recordCount--;
1886
1887 if (rr.GetType() == Dns::ResourceRecord::kTypeOpt)
1888 {
1889 SuccessOrExit(error = ProcessOptRecord(aMessage, startOffset, static_cast<Dns::OptRecord &>(rr)));
1890 }
1891 }
1892
1893 // Calculate the lease renew time based on update message tx time
1894 // and the lease time. `kLeaseRenewGuardInterval` is used to
1895 // ensure that we renew the lease before server expires it. In the
1896 // unlikely (but maybe useful for testing) case where the accepted
1897 // lease interval is too short (shorter than twice the guard time)
1898 // we just use half of the accepted lease interval.
1899
1900 if (mLease > 2 * kLeaseRenewGuardInterval)
1901 {
1902 uint32_t interval = Time::SecToMsec(mLease - kLeaseRenewGuardInterval);
1903
1904 mLeaseRenewTime += Random::NonCrypto::AddJitter(interval, kLeaseRenewJitter);
1905 }
1906 else
1907 {
1908 mLeaseRenewTime += Time::SecToMsec(mLease) / 2;
1909 }
1910
1911 for (Service &service : mServices)
1912 {
1913 if ((service.GetState() == kAdding) || (service.GetState() == kRefreshing))
1914 {
1915 service.SetLeaseRenewTime(mLeaseRenewTime);
1916 }
1917 }
1918
1919 // State changes:
1920 // kAdding -> kRegistered
1921 // kRefreshing -> kRegistered
1922 // kRemoving -> kRemoved
1923
1924 ChangeHostAndServiceStates(kNewStateOnUpdateDone, kForServicesAppendedInMessage);
1925
1926 HandleUpdateDone();
1927 UpdateState();
1928
1929 exit:
1930 if (error != kErrorNone)
1931 {
1932 LogInfo("Failed to process response %s", ErrorToString(error));
1933 }
1934 }
1935
IsResponseMessageIdValid(uint16_t aId) const1936 bool Client::IsResponseMessageIdValid(uint16_t aId) const
1937 {
1938 // Semantically equivalent to `(aId >= mResponseMessageId) && (aId < mNextMessageId)`
1939
1940 return !SerialNumber::IsLess(aId, mResponseMessageId) && SerialNumber::IsLess(aId, mNextMessageId);
1941 }
1942
HandleUpdateDone(void)1943 void Client::HandleUpdateDone(void)
1944 {
1945 HostInfo hostInfoCopy = mHostInfo;
1946 LinkedList<Service> removedServices;
1947
1948 if (mHostInfo.GetState() == kRemoved)
1949 {
1950 mHostInfo.Clear();
1951 }
1952
1953 ResetRetryWaitInterval();
1954 SetState(kStateUpdated);
1955
1956 GetRemovedServices(removedServices);
1957 InvokeCallback(kErrorNone, hostInfoCopy, removedServices.GetHead());
1958 }
1959
GetRemovedServices(LinkedList<Service> & aRemovedServices)1960 void Client::GetRemovedServices(LinkedList<Service> &aRemovedServices)
1961 {
1962 mServices.RemoveAllMatching(aRemovedServices, kRemoved);
1963 }
1964
ReadResourceRecord(const Message & aMessage,uint16_t & aOffset,Dns::ResourceRecord & aRecord)1965 Error Client::ReadResourceRecord(const Message &aMessage, uint16_t &aOffset, Dns::ResourceRecord &aRecord)
1966 {
1967 // Reads and skips over a Resource Record (RR) from message at
1968 // given offset. On success, `aOffset` is updated to point to end
1969 // of RR.
1970
1971 Error error;
1972
1973 SuccessOrExit(error = Dns::Name::ParseName(aMessage, aOffset));
1974 SuccessOrExit(error = aMessage.Read(aOffset, aRecord));
1975 VerifyOrExit(aOffset + aRecord.GetSize() <= aMessage.GetLength(), error = kErrorParse);
1976 aOffset += static_cast<uint16_t>(aRecord.GetSize());
1977
1978 exit:
1979 return error;
1980 }
1981
ProcessOptRecord(const Message & aMessage,uint16_t aOffset,const Dns::OptRecord & aOptRecord)1982 Error Client::ProcessOptRecord(const Message &aMessage, uint16_t aOffset, const Dns::OptRecord &aOptRecord)
1983 {
1984 // Read and process all options (in an OPT RR) from a message.
1985 // The `aOffset` points to beginning of record in `aMessage`.
1986
1987 Error error = kErrorNone;
1988 Dns::LeaseOption leaseOption;
1989
1990 IgnoreError(Dns::Name::ParseName(aMessage, aOffset));
1991 aOffset += sizeof(Dns::OptRecord);
1992
1993 switch (error = leaseOption.ReadFrom(aMessage, aOffset, aOptRecord.GetLength()))
1994 {
1995 case kErrorNone:
1996 mLease = Min(leaseOption.GetLeaseInterval(), kMaxLease);
1997 mKeyLease = Min(leaseOption.GetKeyLeaseInterval(), kMaxLease);
1998 break;
1999
2000 case kErrorNotFound:
2001 // If server does not include a lease option in its response, it
2002 // indicates that it accepted what we requested.
2003 error = kErrorNone;
2004 break;
2005
2006 default:
2007 ExitNow();
2008 }
2009
2010 exit:
2011 return error;
2012 }
2013
UpdateState(void)2014 void Client::UpdateState(void)
2015 {
2016 NextFireTime nextRenewTime;
2017 bool shouldUpdate = false;
2018
2019 VerifyOrExit((GetState() != kStateStopped) && (GetState() != kStatePaused));
2020 VerifyOrExit(mHostInfo.GetName() != nullptr);
2021
2022 // Go through the host info and all the services to check if there
2023 // are any new changes (i.e., anything new to add or remove). This
2024 // is used to determine whether to send an SRP update message or
2025 // not. Also keep track of the earliest renew time among the
2026 // previously registered services. This is used to schedule the
2027 // timer for next refresh.
2028
2029 switch (mHostInfo.GetState())
2030 {
2031 case kAdding:
2032 case kRefreshing:
2033 case kRemoving:
2034 break;
2035
2036 case kRegistered:
2037 if (nextRenewTime.GetNow() < mLeaseRenewTime)
2038 {
2039 break;
2040 }
2041
2042 mHostInfo.SetState(kToRefresh);
2043
2044 // Fall through
2045
2046 case kToAdd:
2047 case kToRefresh:
2048 // Make sure we have at least one service and at least one
2049 // host address, otherwise no need to send SRP update message.
2050 // The exception is when removing host info where we allow
2051 // for empty service list.
2052 VerifyOrExit(!mServices.IsEmpty() && (mHostInfo.IsAutoAddressEnabled() || (mHostInfo.GetNumAddresses() > 0)));
2053
2054 // Fall through
2055
2056 case kToRemove:
2057 shouldUpdate = true;
2058 break;
2059
2060 case kRemoved:
2061 ExitNow();
2062 }
2063
2064 // If host info is being removed, we skip over checking service list
2065 // for new adds (or removes). This handles the situation where while
2066 // remove is ongoing and before we get a response from the server,
2067 // user adds a new service to be registered. We wait for remove to
2068 // finish (receive response from server) before starting with a new
2069 // service adds.
2070
2071 if (mHostInfo.GetState() != kRemoving)
2072 {
2073 for (Service &service : mServices)
2074 {
2075 switch (service.GetState())
2076 {
2077 case kToAdd:
2078 case kToRefresh:
2079 case kToRemove:
2080 shouldUpdate = true;
2081 break;
2082
2083 case kRegistered:
2084 if (service.GetLeaseRenewTime() <= nextRenewTime.GetNow())
2085 {
2086 service.SetState(kToRefresh);
2087 shouldUpdate = true;
2088 }
2089 else
2090 {
2091 nextRenewTime.UpdateIfEarlier(service.GetLeaseRenewTime());
2092 }
2093
2094 break;
2095
2096 case kAdding:
2097 case kRefreshing:
2098 case kRemoving:
2099 case kRemoved:
2100 break;
2101 }
2102 }
2103 }
2104
2105 if (shouldUpdate)
2106 {
2107 SetState(kStateToUpdate);
2108 ExitNow();
2109 }
2110
2111 if (GetState() == kStateUpdated)
2112 {
2113 mTimer.FireAt(nextRenewTime);
2114 }
2115
2116 exit:
2117 return;
2118 }
2119
GrowRetryWaitInterval(void)2120 void Client::GrowRetryWaitInterval(void)
2121 {
2122 mRetryWaitInterval =
2123 mRetryWaitInterval / kRetryIntervalGrowthFactorDenominator * kRetryIntervalGrowthFactorNumerator;
2124 mRetryWaitInterval = Min(mRetryWaitInterval, kMaxRetryWaitInterval);
2125 }
2126
DetermineLeaseInterval(uint32_t aInterval,uint32_t aDefaultInterval) const2127 uint32_t Client::DetermineLeaseInterval(uint32_t aInterval, uint32_t aDefaultInterval) const
2128 {
2129 // Determine the lease or key lease interval.
2130 //
2131 // We use `aInterval` if it is non-zero, otherwise, use the
2132 // `aDefaultInterval`. We also ensure that the returned value is
2133 // never greater than `kMaxLease`. The `kMaxLease` is selected
2134 // such the lease intervals in msec can still fit in a `uint32_t`
2135 // `Time` variable (`kMaxLease` is ~ 24.8 days).
2136
2137 return Min(kMaxLease, (aInterval != kUnspecifiedInterval) ? aInterval : aDefaultInterval);
2138 }
2139
DetermineTtl(void) const2140 uint32_t Client::DetermineTtl(void) const
2141 {
2142 // Determine the TTL to use based on current `mLease`.
2143 // If `mLease == 0`, it indicates we are removing host
2144 // and so we use `mDefaultLease` instead.
2145
2146 uint32_t lease = (mLease == 0) ? mDefaultLease : mLease;
2147
2148 return (mTtl == kUnspecifiedInterval) ? lease : Min(mTtl, lease);
2149 }
2150
ShouldRenewEarly(const Service & aService) const2151 bool Client::ShouldRenewEarly(const Service &aService) const
2152 {
2153 // Check if we reached the service renew time or close to it. The
2154 // "early renew interval" is used to allow early refresh. It is
2155 // calculated as a factor of the service requested lease interval.
2156 // The "early lease renew factor" is given as a fraction (numerator
2157 // and denominator). If the denominator is set to zero (i.e., factor
2158 // is set to infinity), then service is always included in all SRP
2159 // update messages.
2160
2161 bool shouldRenew;
2162
2163 #if OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_DENOMINATOR != 0
2164 uint32_t earlyRenewInterval;
2165
2166 earlyRenewInterval = Time::SecToMsec(DetermineLeaseInterval(aService.GetLease(), mDefaultLease));
2167 earlyRenewInterval = earlyRenewInterval / kEarlyLeaseRenewFactorDenominator * kEarlyLeaseRenewFactorNumerator;
2168
2169 shouldRenew = (aService.GetLeaseRenewTime() <= TimerMilli::GetNow() + earlyRenewInterval);
2170 #else
2171 OT_UNUSED_VARIABLE(aService);
2172 shouldRenew = true;
2173 #endif
2174
2175 return shouldRenew;
2176 }
2177
HandleTimer(void)2178 void Client::HandleTimer(void)
2179 {
2180 switch (GetState())
2181 {
2182 case kStateStopped:
2183 case kStatePaused:
2184 break;
2185
2186 case kStateToUpdate:
2187 case kStateToRetry:
2188 SendUpdate();
2189 break;
2190
2191 case kStateUpdating:
2192 mSingleServiceMode = false;
2193 LogRetryWaitInterval();
2194 LogInfo("Timed out, no response");
2195 GrowRetryWaitInterval();
2196 SetState(kStateToUpdate);
2197 InvokeCallback(kErrorResponseTimeout);
2198
2199 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
2200
2201 // After certain number of back-to-back timeout failures, we try
2202 // to switch the server. This is again done after invoking the
2203 // callback. It works correctly due to the guard check at the
2204 // top of `SelectNextServer()`.
2205
2206 mAutoStart.IncrementTimeoutFailureCount();
2207
2208 if (mAutoStart.GetTimeoutFailureCount() >= kMaxTimeoutFailuresToSwitchServer)
2209 {
2210 SelectNextServer(kDisallowSwitchOnRegisteredHost);
2211 }
2212 #endif
2213 break;
2214
2215 case kStateUpdated:
2216 UpdateState();
2217 break;
2218 }
2219 }
2220
2221 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
2222
EnableAutoStartMode(AutoStartCallback aCallback,void * aContext)2223 void Client::EnableAutoStartMode(AutoStartCallback aCallback, void *aContext)
2224 {
2225 mAutoStart.SetCallback(aCallback, aContext);
2226
2227 VerifyOrExit(mAutoStart.GetState() == AutoStart::kDisabled);
2228
2229 mAutoStart.SetState(AutoStart::kFirstTimeSelecting);
2230 ApplyAutoStartGuardOnAttach();
2231
2232 ProcessAutoStart();
2233
2234 exit:
2235 return;
2236 }
2237
ApplyAutoStartGuardOnAttach(void)2238 void Client::ApplyAutoStartGuardOnAttach(void)
2239 {
2240 VerifyOrExit(Get<Mle::Mle>().IsAttached());
2241 VerifyOrExit(!IsRunning());
2242 VerifyOrExit(mAutoStart.GetState() == AutoStart::kFirstTimeSelecting);
2243
2244 // The `mGuardTimer` tracks a guard interval after the attach
2245 // event while `AutoStart` has yet to select a server for the
2246 // first time.
2247 //
2248 // This is used by `ProcessAutoStart()` to apply different TX
2249 // jitter values. If server selection occurs within this short
2250 // window, a shorter TX jitter is used. This typically represents
2251 // the device rebooting or being paired.
2252 //
2253 // The guard time is also checked when handling SLAAC address change
2254 // events, to decide whether or not to request longer TX jitter.
2255
2256 mGuardTimer.Start(kGuardTimeAfterAttachToUseShorterTxJitter);
2257
2258 exit:
2259 return;
2260 }
2261
ProcessAutoStart(void)2262 void Client::ProcessAutoStart(void)
2263 {
2264 Ip6::SockAddr serverSockAddr;
2265 DnsSrpAnycastInfo anycastInfo;
2266 DnsSrpUnicastInfo unicastInfo;
2267 AutoStart::State oldAutoStartState = mAutoStart.GetState();
2268 bool shouldRestart = false;
2269
2270 // If auto start mode is enabled, we check the Network Data entries
2271 // to discover and select the preferred SRP server to register with.
2272 // If we currently have a selected server, we ensure that it is
2273 // still present in the Network Data and is still the preferred one.
2274
2275 VerifyOrExit(mAutoStart.GetState() != AutoStart::kDisabled);
2276
2277 // If SRP client is running, we check to make sure that auto-start
2278 // did select the current server, and server was not specified by
2279 // user directly.
2280
2281 if (IsRunning())
2282 {
2283 VerifyOrExit(mAutoStart.HasSelectedServer());
2284 }
2285
2286 // There are three types of entries in Network Data:
2287 //
2288 // 1) Preferred unicast entries with address included in service data.
2289 // 2) Anycast entries (each having a seq number).
2290 // 3) Unicast entries with address info included in server data.
2291
2292 serverSockAddr.Clear();
2293
2294 if (SelectUnicastEntry(NetworkData::Service::kAddrInServiceData, unicastInfo) == kErrorNone)
2295 {
2296 mAutoStart.SetState(AutoStart::kSelectedUnicastPreferred);
2297 serverSockAddr = unicastInfo.mSockAddr;
2298 }
2299 else if (Get<NetworkData::Service::Manager>().FindPreferredDnsSrpAnycastInfo(anycastInfo) == kErrorNone)
2300 {
2301 serverSockAddr.SetAddress(anycastInfo.mAnycastAddress);
2302 serverSockAddr.SetPort(kAnycastServerPort);
2303
2304 // We check if we are selecting an anycast entry for first
2305 // time, or if the seq number has changed. Even if the
2306 // anycast address remains the same as before, on a seq
2307 // number change, the client still needs to restart to
2308 // re-register its info.
2309
2310 if ((mAutoStart.GetState() != AutoStart::kSelectedAnycast) ||
2311 (mAutoStart.GetAnycastSeqNum() != anycastInfo.mSequenceNumber))
2312 {
2313 shouldRestart = true;
2314 mAutoStart.SetAnycastSeqNum(anycastInfo.mSequenceNumber);
2315 }
2316
2317 mAutoStart.SetState(AutoStart::kSelectedAnycast);
2318 }
2319 else if (SelectUnicastEntry(NetworkData::Service::kAddrInServerData, unicastInfo) == kErrorNone)
2320 {
2321 mAutoStart.SetState(AutoStart::kSelectedUnicast);
2322 serverSockAddr = unicastInfo.mSockAddr;
2323 }
2324
2325 if (IsRunning())
2326 {
2327 VerifyOrExit((GetServerAddress() != serverSockAddr) || shouldRestart);
2328 Stop(kRequesterAuto, kResetRetryInterval);
2329 }
2330
2331 if (serverSockAddr.GetAddress().IsUnspecified())
2332 {
2333 if (mAutoStart.HasSelectedServer())
2334 {
2335 mAutoStart.SetState(AutoStart::kReselecting);
2336 }
2337
2338 ExitNow();
2339 }
2340
2341 // Before calling `Start()`, determine the trigger reason for
2342 // starting the client with the newly discovered server based on
2343 // `AutoStart` state transitions. This reason is then used to
2344 // select the appropriate TX jitter interval (randomizing the
2345 // initial SRP update transmission to the new server).
2346
2347 switch (oldAutoStartState)
2348 {
2349 case AutoStart::kDisabled:
2350 break;
2351
2352 case AutoStart::kFirstTimeSelecting:
2353
2354 // If the device is attaching to an established Thread mesh
2355 // (e.g., after a reboot or pairing), the Network Data it
2356 // receives should already include a server entry, leading to
2357 // a quick server selection after attachment. The `mGuardTimer`,
2358 // started by `ApplyAutoStartGuardOnAttach()`, tracks a guard
2359 // interval after the attach event. If server selection
2360 // occurs within this short window, a shorter TX jitter is
2361 // used (`TxJitter::kOnDeviceReboot`), allowing the device to
2362 // register quickly and become discoverable.
2363 //
2364 // If server discovery takes longer, a longer TX jitter
2365 // is used (`TxJitter::kOnServerStart`). This situation
2366 // can indicate a server/BR starting up or a network-wide
2367 // restart of many nodes (e.g., due to a power outage).
2368
2369 if (mGuardTimer.IsRunning())
2370 {
2371 mTxJitter.Request(TxJitter::kOnDeviceReboot);
2372 }
2373 else
2374 {
2375 mTxJitter.Request(TxJitter::kOnServerStart);
2376 }
2377
2378 break;
2379
2380 case AutoStart::kReselecting:
2381 // Server is restarted (or possibly a new server started).
2382 mTxJitter.Request(TxJitter::kOnServerRestart);
2383 break;
2384
2385 case AutoStart::kSelectedUnicastPreferred:
2386 case AutoStart::kSelectedAnycast:
2387 case AutoStart::kSelectedUnicast:
2388 mTxJitter.Request(TxJitter::kOnServerSwitch);
2389 break;
2390 }
2391
2392 IgnoreError(Start(serverSockAddr, kRequesterAuto));
2393
2394 exit:
2395 return;
2396 }
2397
SelectUnicastEntry(DnsSrpUnicastType aType,DnsSrpUnicastInfo & aInfo) const2398 Error Client::SelectUnicastEntry(DnsSrpUnicastType aType, DnsSrpUnicastInfo &aInfo) const
2399 {
2400 Error error = kErrorNotFound;
2401 DnsSrpUnicastInfo unicastInfo;
2402 NetworkData::Service::Manager::Iterator iterator;
2403 #if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
2404 Settings::SrpClientInfo savedInfo;
2405 bool hasSavedServerInfo = false;
2406
2407 if (!IsRunning())
2408 {
2409 hasSavedServerInfo = (Get<Settings>().Read(savedInfo) == kErrorNone);
2410 }
2411 #endif
2412
2413 while (Get<NetworkData::Service::Manager>().GetNextDnsSrpUnicastInfo(iterator, aType, unicastInfo) == kErrorNone)
2414 {
2415 bool preferNewEntry;
2416
2417 if (mAutoStart.HasSelectedServer() && (GetServerAddress() == unicastInfo.mSockAddr))
2418 {
2419 aInfo = unicastInfo;
2420 error = kErrorNone;
2421 ExitNow();
2422 }
2423
2424 #if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
2425 if (hasSavedServerInfo && (unicastInfo.mSockAddr.GetAddress() == savedInfo.GetServerAddress()) &&
2426 (unicastInfo.mSockAddr.GetPort() == savedInfo.GetServerPort()))
2427 {
2428 // Stop the search if we see a match for the previously
2429 // saved server info in the network data entries.
2430
2431 aInfo = unicastInfo;
2432 error = kErrorNone;
2433 ExitNow();
2434 }
2435 #endif
2436 // Prefer the server with higher version number, if equal
2437 // then pick the one with numerically smaller IPv6 address.
2438
2439 preferNewEntry = (error == kErrorNotFound) || (unicastInfo.mVersion > aInfo.mVersion);
2440
2441 if (!preferNewEntry && (unicastInfo.mVersion == aInfo.mVersion))
2442 {
2443 preferNewEntry = (unicastInfo.mSockAddr.GetAddress() < aInfo.mSockAddr.GetAddress());
2444 }
2445
2446 if (preferNewEntry)
2447 {
2448 aInfo = unicastInfo;
2449 error = kErrorNone;
2450 }
2451 }
2452
2453 exit:
2454 return error;
2455 }
2456
2457 #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
SelectNextServer(bool aDisallowSwitchOnRegisteredHost)2458 void Client::SelectNextServer(bool aDisallowSwitchOnRegisteredHost)
2459 {
2460 // This method tries to find the next unicast server info entry in the
2461 // Network Data after the current one selected. If found, it
2462 // restarts the client with the new server (keeping the retry wait
2463 // interval as before).
2464
2465 Ip6::SockAddr serverSockAddr;
2466 bool selectNext = false;
2467 DnsSrpUnicastType type = NetworkData::Service::kAddrInServiceData;
2468
2469 serverSockAddr.Clear();
2470
2471 // Ensure that client is running, auto-start is enabled and
2472 // auto-start selected the server and it is a unicast entry.
2473
2474 VerifyOrExit(IsRunning());
2475
2476 switch (mAutoStart.GetState())
2477 {
2478 case AutoStart::kSelectedUnicastPreferred:
2479 type = NetworkData::Service::kAddrInServiceData;
2480 break;
2481
2482 case AutoStart::kSelectedUnicast:
2483 type = NetworkData::Service::kAddrInServerData;
2484 break;
2485
2486 case AutoStart::kSelectedAnycast:
2487 case AutoStart::kDisabled:
2488 case AutoStart::kFirstTimeSelecting:
2489 case AutoStart::kReselecting:
2490 ExitNow();
2491 }
2492
2493 if (aDisallowSwitchOnRegisteredHost)
2494 {
2495 // Ensure that host info is not yet registered (indicating that no
2496 // service has yet been registered either).
2497 VerifyOrExit((mHostInfo.GetState() == kAdding) || (mHostInfo.GetState() == kToAdd));
2498 }
2499
2500 // We go through all entries to find the one matching the currently
2501 // selected one, then set `selectNext` to `true` so to select the
2502 // next one.
2503
2504 do
2505 {
2506 DnsSrpUnicastInfo unicastInfo;
2507 NetworkData::Service::Manager::Iterator iterator;
2508
2509 while (Get<NetworkData::Service::Manager>().GetNextDnsSrpUnicastInfo(iterator, type, unicastInfo) == kErrorNone)
2510 {
2511 if (selectNext)
2512 {
2513 serverSockAddr = unicastInfo.mSockAddr;
2514 ExitNow();
2515 }
2516
2517 if (GetServerAddress() == unicastInfo.mSockAddr)
2518 {
2519 selectNext = true;
2520 }
2521 }
2522
2523 // We loop back to handle the case where the current entry
2524 // is the last one.
2525
2526 } while (selectNext);
2527
2528 // If we reach here it indicates we could not find the entry
2529 // associated with currently selected server in the list. This
2530 // situation is rather unlikely but can still happen if Network
2531 // Data happens to be changed and the entry removed but
2532 // the "changed" event from `Notifier` may have not yet been
2533 // processed (note that events are emitted from their own
2534 // tasklet). In such a case we keep `serverSockAddr` as empty.
2535
2536 exit:
2537 if (!serverSockAddr.GetAddress().IsUnspecified() && (GetServerAddress() != serverSockAddr))
2538 {
2539 // We specifically update `mHostInfo` to `kToAdd` state. This
2540 // ensures that `Stop()` will keep it as kToAdd` and we detect
2541 // that the host info has not been registered yet and allow the
2542 // `SelectNextServer()` to happen again if the timeouts/failures
2543 // continue to happen with the new server.
2544
2545 mHostInfo.SetState(kToAdd);
2546 Stop(kRequesterAuto, kKeepRetryInterval);
2547 IgnoreError(Start(serverSockAddr, kRequesterAuto));
2548 }
2549 }
2550 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
2551
2552 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
2553
ItemStateToString(ItemState aState)2554 const char *Client::ItemStateToString(ItemState aState)
2555 {
2556 static const char *const kItemStateStrings[] = {
2557 "ToAdd", // kToAdd (0)
2558 "Adding", // kAdding (1)
2559 "ToRefresh", // kToRefresh (2)
2560 "Refreshing", // kRefreshing (3)
2561 "ToRemove", // kToRemove (4)
2562 "Removing", // kRemoving (5)
2563 "Registered", // kRegistered (6)
2564 "Removed", // kRemoved (7)
2565 };
2566
2567 return kItemStateStrings[aState];
2568 }
2569
2570 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
2571
StateToString(State aState)2572 const char *Client::StateToString(State aState)
2573 {
2574 static const char *const kStateStrings[] = {
2575 "Stopped", // kStateStopped (0)
2576 "Paused", // kStatePaused (1)
2577 "ToUpdate", // kStateToUpdate (2)
2578 "Updating", // kStateUpdating (3)
2579 "Updated", // kStateUpdated (4)
2580 "ToRetry", // kStateToRetry (5)
2581 };
2582
2583 struct EnumCheck
2584 {
2585 InitEnumValidatorCounter();
2586 ValidateNextEnum(kStateStopped);
2587 ValidateNextEnum(kStatePaused);
2588 ValidateNextEnum(kStateToUpdate);
2589 ValidateNextEnum(kStateUpdating);
2590 ValidateNextEnum(kStateUpdated);
2591 ValidateNextEnum(kStateToRetry);
2592 };
2593
2594 return kStateStrings[aState];
2595 }
2596
LogRetryWaitInterval(void) const2597 void Client::LogRetryWaitInterval(void) const
2598 {
2599 constexpr uint16_t kLogInMsecLimit = 5000; // Max interval (in msec) to log the value in msec unit
2600
2601 uint32_t interval = GetRetryWaitInterval();
2602
2603 LogInfo("Retry interval %lu %s", ToUlong((interval < kLogInMsecLimit) ? interval : Time::MsecToSec(interval)),
2604 (interval < kLogInMsecLimit) ? "ms" : "sec");
2605 }
2606
2607 #endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
2608
2609 } // namespace Srp
2610 } // namespace ot
2611
2612 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
2613