• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "common/as_core_type.hpp"
34 #include "common/code_utils.hpp"
35 #include "common/debug.hpp"
36 #include "common/instance.hpp"
37 #include "common/locator_getters.hpp"
38 #include "common/random.hpp"
39 #include "common/settings.hpp"
40 #include "common/string.hpp"
41 
42 /**
43  * @file
44  *   This file implements the SRP client.
45  */
46 
47 namespace ot {
48 namespace Srp {
49 
50 RegisterLogModule("SrpClient");
51 
52 //---------------------------------------------------------------------
53 // Client::HostInfo
54 
Init(void)55 void Client::HostInfo::Init(void)
56 {
57     Clearable<HostInfo>::Clear();
58 
59     // State is directly set on `mState` instead of using `SetState()`
60     // to avoid logging.
61     mState = OT_SRP_CLIENT_ITEM_STATE_REMOVED;
62 }
63 
Clear(void)64 void Client::HostInfo::Clear(void)
65 {
66     Clearable<HostInfo>::Clear();
67     SetState(kRemoved);
68 }
69 
SetState(ItemState aState)70 void Client::HostInfo::SetState(ItemState aState)
71 {
72     if (aState != GetState())
73     {
74         LogInfo("HostInfo %s -> %s", ItemStateToString(GetState()), ItemStateToString(aState));
75         mState = MapEnum(aState);
76     }
77 }
78 
EnableAutoAddress(void)79 void Client::HostInfo::EnableAutoAddress(void)
80 {
81     mAddresses    = nullptr;
82     mNumAddresses = 0;
83     mAutoAddress  = true;
84 
85     LogInfo("HostInfo enabled auto address", GetNumAddresses());
86 }
87 
SetAddresses(const Ip6::Address * aAddresses,uint8_t aNumAddresses)88 void Client::HostInfo::SetAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses)
89 {
90     mAddresses    = aAddresses;
91     mNumAddresses = aNumAddresses;
92     mAutoAddress  = false;
93 
94     LogInfo("HostInfo set %d addrs", GetNumAddresses());
95 
96     for (uint8_t index = 0; index < GetNumAddresses(); index++)
97     {
98         LogInfo("%s", GetAddress(index).ToString().AsCString());
99     }
100 }
101 
102 //---------------------------------------------------------------------
103 // Client::Service
104 
Init(void)105 Error Client::Service::Init(void)
106 {
107     Error error = kErrorNone;
108 
109     VerifyOrExit((GetName() != nullptr) && (GetInstanceName() != nullptr), error = kErrorInvalidArgs);
110     VerifyOrExit((GetTxtEntries() != nullptr) || (GetNumTxtEntries() == 0), error = kErrorInvalidArgs);
111 
112     // State is directly set on `mState` instead of using `SetState()`
113     // to avoid logging.
114     mState = OT_SRP_CLIENT_ITEM_STATE_REMOVED;
115 
116 exit:
117     return error;
118 }
119 
SetState(ItemState aState)120 void Client::Service::SetState(ItemState aState)
121 {
122     VerifyOrExit(GetState() != aState);
123 
124     LogInfo("Service %s -> %s, \"%s\" \"%s\"", ItemStateToString(GetState()), ItemStateToString(aState),
125             GetInstanceName(), GetName());
126 
127     if (aState == kToAdd)
128     {
129         constexpr uint16_t kSubTypeLabelStringSize = 80;
130 
131         String<kSubTypeLabelStringSize> string;
132 
133         // Log more details only when entering `kToAdd` state.
134 
135         if (HasSubType())
136         {
137             const char *label;
138 
139             for (uint16_t index = 0; (label = GetSubTypeLabelAt(index)) != nullptr; index++)
140             {
141                 string.Append("%s\"%s\"", (index != 0) ? ", " : "", label);
142             }
143         }
144 
145         LogInfo("subtypes:[%s] port:%d weight:%d prio:%d txts:%d", string.AsCString(), GetPort(), GetWeight(),
146                 GetPriority(), GetNumTxtEntries());
147     }
148 
149     mState = MapEnum(aState);
150 
151 exit:
152     return;
153 }
154 
Matches(const Service & aOther) const155 bool Client::Service::Matches(const Service &aOther) const
156 {
157     // This method indicates whether or not two service entries match,
158     // i.e., have the same service and instance names. This is intended
159     // for use by `LinkedList::FindMatching()` to search within the
160     // `mServices` list.
161 
162     return (strcmp(GetName(), aOther.GetName()) == 0) && (strcmp(GetInstanceName(), aOther.GetInstanceName()) == 0);
163 }
164 
165 //---------------------------------------------------------------------
166 // Client::AutoStart
167 
168 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
169 
AutoStart(void)170 Client::AutoStart::AutoStart(void)
171 {
172     Clear();
173     mState = kDefaultMode ? kSelectedNone : kDisabled;
174 }
175 
HasSelectedServer(void) const176 bool Client::AutoStart::HasSelectedServer(void) const
177 {
178     bool hasSelected = false;
179 
180     switch (mState)
181     {
182     case kDisabled:
183     case kSelectedNone:
184         break;
185 
186     case kSelectedUnicastPreferred:
187     case kSelectedUnicast:
188     case kSelectedAnycast:
189         hasSelected = true;
190         break;
191     }
192 
193     return hasSelected;
194 }
195 
SetState(State aState)196 void Client::AutoStart::SetState(State aState)
197 {
198     if (mState != aState)
199     {
200         LogInfo("AutoStartState %s -> %s", StateToString(mState), StateToString(aState));
201         mState = aState;
202     }
203 }
204 
SetCallback(AutoStartCallback aCallback,void * aContext)205 void Client::AutoStart::SetCallback(AutoStartCallback aCallback, void *aContext)
206 {
207     mCallback = aCallback;
208     mContext  = aContext;
209 }
210 
InvokeCallback(const Ip6::SockAddr * aServerSockAddr) const211 void Client::AutoStart::InvokeCallback(const Ip6::SockAddr *aServerSockAddr) const
212 {
213     if (mCallback != nullptr)
214     {
215         mCallback(aServerSockAddr, mContext);
216     }
217 }
218 
219 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
StateToString(State aState)220 const char *Client::AutoStart::StateToString(State aState)
221 {
222     static const char *const kStateStrings[] = {
223         "Disabled",    // (0) kDisabled
224         "Idle",        // (1) kSelectedNone
225         "Unicast-prf", // (2) kSelectedUnicastPreferred
226         "Anycast",     // (3) kSelectedAnycast
227         "Unicast",     // (4) kSelectedUnicast
228     };
229 
230     static_assert(0 == kDisabled, "kDisabled value is incorrect");
231     static_assert(1 == kSelectedNone, "kSelectedNone value is incorrect");
232     static_assert(2 == kSelectedUnicastPreferred, "kSelectedUnicastPreferred value is incorrect");
233     static_assert(3 == kSelectedAnycast, "kSelectedAnycast value is incorrect");
234     static_assert(4 == kSelectedUnicast, "kSelectedUnicast value is incorrect");
235 
236     return kStateStrings[aState];
237 }
238 #endif
239 
240 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
241 
242 //---------------------------------------------------------------------
243 // Client
244 
245 const char Client::kDefaultDomainName[] = "default.service.arpa";
246 
Client(Instance & aInstance)247 Client::Client(Instance &aInstance)
248     : InstanceLocator(aInstance)
249     , mState(kStateStopped)
250     , mTxFailureRetryCount(0)
251     , mShouldRemoveKeyLease(false)
252     , mAutoHostAddressAddedMeshLocal(false)
253 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
254     , mServiceKeyRecordEnabled(false)
255 #endif
256     , mUpdateMessageId(0)
257     , mRetryWaitInterval(kMinRetryWaitInterval)
258     , mAcceptedLeaseInterval(0)
259     , mTtl(0)
260     , mLeaseInterval(kDefaultLease)
261     , mKeyLeaseInterval(kDefaultKeyLease)
262     , mSocket(aInstance)
263     , mCallback(nullptr)
264     , mCallbackContext(nullptr)
265     , mDomainName(kDefaultDomainName)
266     , mTimer(aInstance, Client::HandleTimer)
267 {
268     mHostInfo.Init();
269 
270     // The `Client` implementation uses different constant array of
271     // `ItemState` to define transitions between states in `Pause()`,
272     // `Stop()`, `SendUpdate`, and `ProcessResponse()`, or to convert
273     // an `ItemState` to string. Here, we assert that the enumeration
274     // values are correct.
275 
276     static_assert(kToAdd == 0, "kToAdd value is not correct");
277     static_assert(kAdding == 1, "kAdding value is not correct");
278     static_assert(kToRefresh == 2, "kToRefresh value is not correct");
279     static_assert(kRefreshing == 3, "kRefreshing value is not correct");
280     static_assert(kToRemove == 4, "kToRemove value is not correct");
281     static_assert(kRemoving == 5, "kRemoving value is not correct");
282     static_assert(kRegistered == 6, "kRegistered value is not correct");
283     static_assert(kRemoved == 7, "kRemoved value is not correct");
284 }
285 
Start(const Ip6::SockAddr & aServerSockAddr,Requester aRequester)286 Error Client::Start(const Ip6::SockAddr &aServerSockAddr, Requester aRequester)
287 {
288     Error error;
289 
290     VerifyOrExit(GetState() == kStateStopped,
291                  error = (aServerSockAddr == GetServerAddress()) ? kErrorNone : kErrorBusy);
292 
293     SuccessOrExit(error = mSocket.Open(Client::HandleUdpReceive, this));
294     SuccessOrExit(error = mSocket.Connect(aServerSockAddr));
295 
296     LogInfo("%starting, server %s", (aRequester == kRequesterUser) ? "S" : "Auto-s",
297             aServerSockAddr.ToString().AsCString());
298 
299     Resume();
300 
301 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
302     if (aRequester == kRequesterAuto)
303     {
304 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE
305         Get<Dns::Client>().UpdateDefaultConfigAddress();
306 #endif
307         mAutoStart.InvokeCallback(&aServerSockAddr);
308     }
309 #endif
310 
311 exit:
312     return error;
313 }
314 
Stop(Requester aRequester,StopMode aMode)315 void Client::Stop(Requester aRequester, StopMode aMode)
316 {
317     // Change the state of host info and services so that they are
318     // added/removed again once the client is started back. In the
319     // case of `kAdding`, we intentionally move to `kToRefresh`
320     // instead of `kToAdd` since the server may receive our add
321     // request and the item may be registered on the server. This
322     // ensures that if we are later asked to remove the item, we do
323     // notify server.
324 
325     static const ItemState kNewStateOnStop[]{
326         /* (0) kToAdd      -> */ kToAdd,
327         /* (1) kAdding     -> */ kToRefresh,
328         /* (2) kToRefresh  -> */ kToRefresh,
329         /* (3) kRefreshing -> */ kToRefresh,
330         /* (4) kToRemove   -> */ kToRemove,
331         /* (5) kRemoving   -> */ kToRemove,
332         /* (6) kRegistered -> */ kToRefresh,
333         /* (7) kRemoved    -> */ kRemoved,
334     };
335 
336     VerifyOrExit(GetState() != kStateStopped);
337 
338     mSingleServiceMode.Disable();
339 
340     // State changes:
341     //   kAdding     -> kToRefresh
342     //   kRefreshing -> kToRefresh
343     //   kRemoving   -> kToRemove
344     //   kRegistered -> kToRefresh
345 
346     ChangeHostAndServiceStates(kNewStateOnStop);
347 
348     IgnoreError(mSocket.Close());
349 
350     mShouldRemoveKeyLease = false;
351     mTxFailureRetryCount  = 0;
352 
353     if (aMode == kResetRetryInterval)
354     {
355         ResetRetryWaitInterval();
356     }
357 
358     SetState(kStateStopped);
359 
360 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
361 #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
362     mAutoStart.ResetTimoutFailureCount();
363 #endif
364     if (aRequester == kRequesterAuto)
365     {
366         mAutoStart.InvokeCallback(nullptr);
367     }
368 #endif
369 
370 exit:
371 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
372     if (aRequester == kRequesterUser)
373     {
374         DisableAutoStartMode();
375     }
376 #endif
377 }
378 
SetCallback(Callback aCallback,void * aContext)379 void Client::SetCallback(Callback aCallback, void *aContext)
380 {
381     mCallback        = aCallback;
382     mCallbackContext = aContext;
383 }
384 
Resume(void)385 void Client::Resume(void)
386 {
387     SetState(kStateUpdated);
388     UpdateState();
389 }
390 
Pause(void)391 void Client::Pause(void)
392 {
393     // Change the state of host info and services that are are being
394     // added or removed so that they are added/removed again once the
395     // client is resumed or started back.
396 
397     static const ItemState kNewStateOnPause[]{
398         /* (0) kToAdd      -> */ kToAdd,
399         /* (1) kAdding     -> */ kToRefresh,
400         /* (2) kToRefresh  -> */ kToRefresh,
401         /* (3) kRefreshing -> */ kToRefresh,
402         /* (4) kToRemove   -> */ kToRemove,
403         /* (5) kRemoving   -> */ kToRemove,
404         /* (6) kRegistered -> */ kRegistered,
405         /* (7) kRemoved    -> */ kRemoved,
406     };
407 
408     mSingleServiceMode.Disable();
409 
410     // State changes:
411     //   kAdding     -> kToRefresh
412     //   kRefreshing -> kToRefresh
413     //   kRemoving   -> kToRemove
414 
415     ChangeHostAndServiceStates(kNewStateOnPause);
416 
417     SetState(kStatePaused);
418 }
419 
HandleNotifierEvents(Events aEvents)420 void Client::HandleNotifierEvents(Events aEvents)
421 {
422     if (aEvents.Contains(kEventThreadRoleChanged))
423     {
424         HandleRoleChanged();
425     }
426 
427 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
428     if (aEvents.ContainsAny(kEventThreadNetdataChanged | kEventThreadMeshLocalAddrChanged))
429     {
430         ProcessAutoStart();
431     }
432 #endif
433 
434     if (mHostInfo.IsAutoAddressEnabled())
435     {
436         Events::Flags eventFlags = (kEventIp6AddressAdded | kEventIp6AddressRemoved);
437 
438         if (mAutoHostAddressAddedMeshLocal)
439         {
440             eventFlags |= kEventThreadMeshLocalAddrChanged;
441         }
442 
443         if (aEvents.ContainsAny(eventFlags))
444         {
445             IgnoreError(UpdateHostInfoStateOnAddressChange());
446             UpdateState();
447         }
448     }
449 }
450 
HandleRoleChanged(void)451 void Client::HandleRoleChanged(void)
452 {
453     if (Get<Mle::Mle>().IsAttached())
454     {
455         VerifyOrExit(GetState() == kStatePaused);
456         Resume();
457     }
458     else
459     {
460         VerifyOrExit(GetState() != kStateStopped);
461         Pause();
462     }
463 
464 exit:
465     return;
466 }
467 
468 #if OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE
SetDomainName(const char * aName)469 Error Client::SetDomainName(const char *aName)
470 {
471     Error error = kErrorNone;
472 
473     VerifyOrExit((mHostInfo.GetState() == kToAdd) || (mHostInfo.GetState() == kRemoved), error = kErrorInvalidState);
474 
475     mDomainName = (aName != nullptr) ? aName : kDefaultDomainName;
476     LogInfo("Domain name \"%s\"", mDomainName);
477 
478 exit:
479     return error;
480 }
481 #endif
482 
SetHostName(const char * aName)483 Error Client::SetHostName(const char *aName)
484 {
485     Error error = kErrorNone;
486 
487     VerifyOrExit(aName != nullptr, error = kErrorInvalidArgs);
488 
489     VerifyOrExit((mHostInfo.GetState() == kToAdd) || (mHostInfo.GetState() == kRemoved), error = kErrorInvalidState);
490 
491     LogInfo("Host name \"%s\"", aName);
492     mHostInfo.SetName(aName);
493     mHostInfo.SetState(kToAdd);
494     UpdateState();
495 
496 exit:
497     return error;
498 }
499 
EnableAutoHostAddress(void)500 Error Client::EnableAutoHostAddress(void)
501 {
502     Error error = kErrorNone;
503 
504     VerifyOrExit(!mHostInfo.IsAutoAddressEnabled());
505     SuccessOrExit(error = UpdateHostInfoStateOnAddressChange());
506 
507     mHostInfo.EnableAutoAddress();
508     UpdateState();
509 
510 exit:
511     return error;
512 }
513 
SetHostAddresses(const Ip6::Address * aAddresses,uint8_t aNumAddresses)514 Error Client::SetHostAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses)
515 {
516     Error error = kErrorNone;
517 
518     VerifyOrExit((aAddresses != nullptr) && (aNumAddresses > 0), error = kErrorInvalidArgs);
519     SuccessOrExit(error = UpdateHostInfoStateOnAddressChange());
520 
521     mHostInfo.SetAddresses(aAddresses, aNumAddresses);
522     UpdateState();
523 
524 exit:
525     return error;
526 }
527 
UpdateHostInfoStateOnAddressChange(void)528 Error Client::UpdateHostInfoStateOnAddressChange(void)
529 {
530     Error error = kErrorNone;
531 
532     VerifyOrExit((mHostInfo.GetState() != kToRemove) && (mHostInfo.GetState() != kRemoving),
533                  error = kErrorInvalidState);
534 
535     if (mHostInfo.GetState() == kRemoved)
536     {
537         mHostInfo.SetState(kToAdd);
538     }
539     else if (mHostInfo.GetState() != kToAdd)
540     {
541         mHostInfo.SetState(kToRefresh);
542     }
543 
544 exit:
545     return error;
546 }
547 
AddService(Service & aService)548 Error Client::AddService(Service &aService)
549 {
550     Error error;
551 
552     VerifyOrExit(mServices.FindMatching(aService) == nullptr, error = kErrorAlready);
553 
554     SuccessOrExit(error = aService.Init());
555     mServices.Push(aService);
556 
557     aService.SetState(kToAdd);
558     UpdateState();
559 
560 exit:
561     return error;
562 }
563 
RemoveService(Service & aService)564 Error Client::RemoveService(Service &aService)
565 {
566     Error               error = kErrorNone;
567     LinkedList<Service> removedServices;
568 
569     VerifyOrExit(mServices.Contains(aService), error = kErrorNotFound);
570 
571     UpdateServiceStateToRemove(aService);
572 
573     // Check if the service was removed immediately, if so
574     // invoke the callback to report the removed service.
575     GetRemovedServices(removedServices);
576 
577     if (!removedServices.IsEmpty())
578     {
579         InvokeCallback(kErrorNone, mHostInfo, removedServices.GetHead());
580     }
581 
582     UpdateState();
583 
584 exit:
585     return error;
586 }
587 
UpdateServiceStateToRemove(Service & aService)588 void Client::UpdateServiceStateToRemove(Service &aService)
589 {
590     if (aService.GetState() == kToAdd)
591     {
592         // If the service has not been added yet, we can remove it immediately.
593         aService.SetState(kRemoved);
594     }
595     else if (aService.GetState() != kRemoving)
596     {
597         aService.SetState(kToRemove);
598     }
599 }
600 
ClearService(Service & aService)601 Error Client::ClearService(Service &aService)
602 {
603     Error error;
604 
605     SuccessOrExit(error = mServices.Remove(aService));
606     aService.SetNext(nullptr);
607     aService.SetState(kRemoved);
608     UpdateState();
609 
610 exit:
611     return error;
612 }
613 
RemoveHostAndServices(bool aShouldRemoveKeyLease,bool aSendUnregToServer)614 Error Client::RemoveHostAndServices(bool aShouldRemoveKeyLease, bool aSendUnregToServer)
615 {
616     Error error = kErrorNone;
617 
618     LogInfo("Remove host & services");
619 
620     VerifyOrExit(mHostInfo.GetState() != kRemoved, error = kErrorAlready);
621 
622     if ((mHostInfo.GetState() == kToRemove) || (mHostInfo.GetState() == kRemoving))
623     {
624         // Host info remove is already ongoing, if "key lease" remove mode is
625         // the same, there is no need to send a new update message.
626         VerifyOrExit(mShouldRemoveKeyLease != aShouldRemoveKeyLease);
627     }
628 
629     mShouldRemoveKeyLease = aShouldRemoveKeyLease;
630 
631     for (Service &service : mServices)
632     {
633         UpdateServiceStateToRemove(service);
634     }
635 
636     if ((mHostInfo.GetState() == kToAdd) && !aSendUnregToServer)
637     {
638         // Host info is not added yet (not yet registered with
639         // server), so we can remove it and all services immediately.
640         mHostInfo.SetState(kRemoved);
641         HandleUpdateDone();
642         ExitNow();
643     }
644 
645     mHostInfo.SetState(kToRemove);
646     UpdateState();
647 
648 exit:
649     return error;
650 }
651 
ClearHostAndServices(void)652 void Client::ClearHostAndServices(void)
653 {
654     LogInfo("Clear host & services");
655 
656     switch (GetState())
657     {
658     case kStateStopped:
659     case kStatePaused:
660         break;
661 
662     case kStateToUpdate:
663     case kStateUpdating:
664     case kStateUpdated:
665     case kStateToRetry:
666         SetState(kStateUpdated);
667         break;
668     }
669 
670     mTxFailureRetryCount = 0;
671     ResetRetryWaitInterval();
672 
673     mServices.Clear();
674     mHostInfo.Clear();
675 }
676 
SetState(State aState)677 void Client::SetState(State aState)
678 {
679     VerifyOrExit(aState != mState);
680 
681     LogInfo("State %s -> %s", StateToString(mState), StateToString(aState));
682     mState = aState;
683 
684     switch (mState)
685     {
686     case kStateStopped:
687     case kStatePaused:
688     case kStateUpdated:
689         mTimer.Stop();
690         break;
691 
692     case kStateToUpdate:
693         mTimer.Start(Random::NonCrypto::GetUint32InRange(kUpdateTxMinDelay, kUpdateTxMaxDelay));
694         break;
695 
696     case kStateUpdating:
697         mTimer.Start(GetRetryWaitInterval());
698         break;
699 
700     case kStateToRetry:
701         break;
702     }
703 exit:
704     return;
705 }
706 
ChangeHostAndServiceStates(const ItemState * aNewStates)707 void Client::ChangeHostAndServiceStates(const ItemState *aNewStates)
708 {
709 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
710     ItemState oldHostState = mHostInfo.GetState();
711 #endif
712 
713     mHostInfo.SetState(aNewStates[mHostInfo.GetState()]);
714 
715     for (Service &service : mServices)
716     {
717         if (mSingleServiceMode.IsEnabled() && mSingleServiceMode.GetService() != &service)
718         {
719             continue;
720         }
721 
722         service.SetState(aNewStates[service.GetState()]);
723     }
724 
725 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
726     if ((oldHostState != kRegistered) && (mHostInfo.GetState() == kRegistered))
727     {
728         Settings::SrpClientInfo info;
729 
730         switch (mAutoStart.GetState())
731         {
732         case AutoStart::kDisabled:
733         case AutoStart::kSelectedNone:
734             break;
735 
736         case AutoStart::kSelectedUnicastPreferred:
737         case AutoStart::kSelectedUnicast:
738             info.SetServerAddress(GetServerAddress().GetAddress());
739             info.SetServerPort(GetServerAddress().GetPort());
740             IgnoreError(Get<Settings>().Save(info));
741             break;
742 
743         case AutoStart::kSelectedAnycast:
744             IgnoreError(Get<Settings>().Delete<Settings::SrpClientInfo>());
745             break;
746         }
747     }
748 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
749 }
750 
InvokeCallback(Error aError) const751 void Client::InvokeCallback(Error aError) const
752 {
753     InvokeCallback(aError, mHostInfo, nullptr);
754 }
755 
InvokeCallback(Error aError,const HostInfo & aHostInfo,const Service * aRemovedServices) const756 void Client::InvokeCallback(Error aError, const HostInfo &aHostInfo, const Service *aRemovedServices) const
757 {
758     VerifyOrExit(mCallback != nullptr);
759     mCallback(aError, &aHostInfo, mServices.GetHead(), aRemovedServices, mCallbackContext);
760 
761 exit:
762     return;
763 }
764 
SendUpdate(void)765 void Client::SendUpdate(void)
766 {
767     static const ItemState kNewStateOnMessageTx[]{
768         /* (0) kToAdd      -> */ kAdding,
769         /* (1) kAdding     -> */ kAdding,
770         /* (2) kToRefresh  -> */ kRefreshing,
771         /* (3) kRefreshing -> */ kRefreshing,
772         /* (4) kToRemove   -> */ kRemoving,
773         /* (5) kRemoving   -> */ kRemoving,
774         /* (6) kRegistered -> */ kRegistered,
775         /* (7) kRemoved    -> */ kRemoved,
776     };
777 
778     Error    error   = kErrorNone;
779     Message *message = mSocket.NewMessage(0);
780     uint32_t length;
781 
782     VerifyOrExit(message != nullptr, error = kErrorNoBufs);
783     SuccessOrExit(error = PrepareUpdateMessage(*message));
784 
785     length = message->GetLength() + sizeof(Ip6::Udp::Header) + sizeof(Ip6::Header);
786 
787     if (length >= Ip6::kMaxDatagramLength)
788     {
789         LogInfo("Msg len %u is larger than MTU, enabling single service mode", length);
790         mSingleServiceMode.Enable();
791         IgnoreError(message->SetLength(0));
792         SuccessOrExit(error = PrepareUpdateMessage(*message));
793     }
794 
795     SuccessOrExit(error = mSocket.SendTo(*message, Ip6::MessageInfo()));
796 
797     LogInfo("Send update");
798 
799     // State changes:
800     //   kToAdd     -> kAdding
801     //   kToRefresh -> kRefreshing
802     //   kToRemove  -> kRemoving
803 
804     ChangeHostAndServiceStates(kNewStateOnMessageTx);
805 
806     // Remember the update message tx time to use later to determine the
807     // lease renew time.
808     mLeaseRenewTime      = TimerMilli::GetNow();
809     mTxFailureRetryCount = 0;
810 
811     SetState(kStateUpdating);
812 
813     if (!Get<Mle::Mle>().IsRxOnWhenIdle())
814     {
815         // If device is sleepy send fast polls while waiting for
816         // the response from server.
817         Get<DataPollSender>().SendFastPolls(kFastPollsAfterUpdateTx);
818     }
819 
820 exit:
821     if (error != kErrorNone)
822     {
823         // If there is an error in preparation or transmission of the
824         // update message (e.g., no buffer to allocate message), up to
825         // `kMaxTxFailureRetries` times, we wait for a short interval
826         // `kTxFailureRetryInterval` and try again. After this, we
827         // continue to retry using the `mRetryWaitInterval` (which keeps
828         // growing on each failure).
829 
830         LogInfo("Failed to send update: %s", ErrorToString(error));
831 
832         mSingleServiceMode.Disable();
833         FreeMessage(message);
834 
835         SetState(kStateToRetry);
836 
837         if (mTxFailureRetryCount < kMaxTxFailureRetries)
838         {
839             uint32_t interval;
840 
841             mTxFailureRetryCount++;
842             interval = Random::NonCrypto::AddJitter(kTxFailureRetryInterval, kTxFailureRetryJitter);
843             mTimer.Start(interval);
844 
845             LogInfo("Quick retry %d in %u msec", mTxFailureRetryCount, interval);
846 
847             // Do not report message preparation errors to user
848             // until `kMaxTxFailureRetries` are exhausted.
849         }
850         else
851         {
852             LogRetryWaitInterval();
853             mTimer.Start(Random::NonCrypto::AddJitter(GetRetryWaitInterval(), kRetryIntervalJitter));
854             GrowRetryWaitInterval();
855             InvokeCallback(error);
856         }
857     }
858 }
859 
PrepareUpdateMessage(Message & aMessage)860 Error Client::PrepareUpdateMessage(Message &aMessage)
861 {
862     constexpr uint16_t kHeaderOffset = 0;
863 
864     Error             error = kErrorNone;
865     Dns::UpdateHeader header;
866     Info              info;
867 
868     info.Clear();
869 
870     SuccessOrExit(error = ReadOrGenerateKey(info.mKeyPair));
871 
872     // Generate random Message ID and ensure it is different from last one
873     do
874     {
875         SuccessOrExit(error = header.SetRandomMessageId());
876     } while (header.GetMessageId() == mUpdateMessageId);
877 
878     mUpdateMessageId = header.GetMessageId();
879 
880     // SRP Update (DNS Update) message must have exactly one record in
881     // Zone section, no records in Prerequisite Section, can have
882     // multiple records in Update Section (tracked as they are added),
883     // and two records in Additional Data Section (OPT and SIG records).
884     // The SIG record itself should not be included in calculation of
885     // SIG(0) signature, so the addition record count is set to one
886     // here. After signature calculation and appending of SIG record,
887     // the additional record count is updated to two and the header is
888     // rewritten in the message.
889 
890     header.SetZoneRecordCount(1);
891     header.SetAdditionalRecordCount(1);
892     SuccessOrExit(error = aMessage.Append(header));
893 
894     // Prepare Zone section
895 
896     info.mDomainNameOffset = aMessage.GetLength();
897     SuccessOrExit(error = Dns::Name::AppendName(mDomainName, aMessage));
898     SuccessOrExit(error = aMessage.Append(Dns::Zone()));
899 
900     // Prepare Update section
901 
902     if ((mHostInfo.GetState() != kToRemove) && (mHostInfo.GetState() != kRemoving))
903     {
904         for (Service &service : mServices)
905         {
906             SuccessOrExit(error = AppendServiceInstructions(service, aMessage, info));
907 
908             if (mSingleServiceMode.IsEnabled() && (mSingleServiceMode.GetService() != nullptr))
909             {
910                 break;
911             }
912         }
913     }
914 
915     SuccessOrExit(error = AppendHostDescriptionInstruction(aMessage, info));
916 
917     header.SetUpdateRecordCount(info.mRecordCount);
918     aMessage.Write(kHeaderOffset, header);
919 
920     // Prepare Additional Data section
921 
922     SuccessOrExit(error = AppendUpdateLeaseOptRecord(aMessage));
923     SuccessOrExit(error = AppendSignature(aMessage, info));
924 
925     header.SetAdditionalRecordCount(2); // Lease OPT and SIG RRs
926     aMessage.Write(kHeaderOffset, header);
927 
928 exit:
929     return error;
930 }
931 
ReadOrGenerateKey(Crypto::Ecdsa::P256::KeyPair & aKeyPair)932 Error Client::ReadOrGenerateKey(Crypto::Ecdsa::P256::KeyPair &aKeyPair)
933 {
934     Error error;
935 
936     error = Get<Settings>().Read<Settings::SrpEcdsaKey>(aKeyPair);
937 
938     if (error == kErrorNone)
939     {
940         Crypto::Ecdsa::P256::PublicKey publicKey;
941 
942         if (aKeyPair.GetPublicKey(publicKey) == kErrorNone)
943         {
944             ExitNow();
945         }
946     }
947 
948     SuccessOrExit(error = aKeyPair.Generate());
949     IgnoreError(Get<Settings>().Save<Settings::SrpEcdsaKey>(aKeyPair));
950 
951 exit:
952     return error;
953 }
954 
AppendServiceInstructions(Service & aService,Message & aMessage,Info & aInfo)955 Error Client::AppendServiceInstructions(Service &aService, Message &aMessage, Info &aInfo)
956 {
957     Error               error = kErrorNone;
958     Dns::ResourceRecord rr;
959     Dns::SrvRecord      srv;
960     bool                removing;
961     uint16_t            serviceNameOffset;
962     uint16_t            instanceNameOffset;
963     uint16_t            offset;
964 
965     if (aService.GetState() == kRegistered)
966     {
967         // If the lease needs to be renewed or if we are close to the
968         // renewal time of a registered service, we refresh the service
969         // early and include it in this update. This helps put more
970         // services on the same lease refresh schedule.
971 
972         VerifyOrExit(ShouldRenewEarly(aService));
973         aService.SetState(kToRefresh);
974     }
975 
976     removing = ((aService.GetState() == kToRemove) || (aService.GetState() == kRemoving));
977 
978     //----------------------------------
979     // Service Discovery Instruction
980 
981     // PTR record
982 
983     // "service name labels" + (pointer to) domain name.
984     serviceNameOffset = aMessage.GetLength();
985     SuccessOrExit(error = Dns::Name::AppendMultipleLabels(aService.GetName(), aMessage));
986     SuccessOrExit(error = Dns::Name::AppendPointerLabel(aInfo.mDomainNameOffset, aMessage));
987 
988     // On remove, we use "Delete an RR from an RRSet" where class is set
989     // to NONE and TTL to zero (RFC 2136 - section 2.5.4).
990 
991     rr.Init(Dns::ResourceRecord::kTypePtr, removing ? Dns::PtrRecord::kClassNone : Dns::PtrRecord::kClassInternet);
992     rr.SetTtl(removing ? 0 : GetTtl());
993     offset = aMessage.GetLength();
994     SuccessOrExit(error = aMessage.Append(rr));
995 
996     // "Instance name" + (pointer to) service name.
997     instanceNameOffset = aMessage.GetLength();
998     SuccessOrExit(error = Dns::Name::AppendLabel(aService.GetInstanceName(), aMessage));
999     SuccessOrExit(error = Dns::Name::AppendPointerLabel(serviceNameOffset, aMessage));
1000 
1001     UpdateRecordLengthInMessage(rr, offset, aMessage);
1002     aInfo.mRecordCount++;
1003 
1004     if (aService.HasSubType() && !removing)
1005     {
1006         const char *subTypeLabel;
1007         uint16_t    subServiceNameOffset = 0;
1008 
1009         for (uint16_t index = 0; (subTypeLabel = aService.GetSubTypeLabelAt(index)) != nullptr; ++index)
1010         {
1011             // subtype label + "_sub" label + (pointer to) service name.
1012 
1013             SuccessOrExit(error = Dns::Name::AppendLabel(subTypeLabel, aMessage));
1014 
1015             if (index == 0)
1016             {
1017                 subServiceNameOffset = aMessage.GetLength();
1018                 SuccessOrExit(error = Dns::Name::AppendLabel("_sub", aMessage));
1019                 SuccessOrExit(error = Dns::Name::AppendPointerLabel(serviceNameOffset, aMessage));
1020             }
1021             else
1022             {
1023                 SuccessOrExit(error = Dns::Name::AppendPointerLabel(subServiceNameOffset, aMessage));
1024             }
1025 
1026             // `rr` is already initialized as PTR.
1027             offset = aMessage.GetLength();
1028             SuccessOrExit(error = aMessage.Append(rr));
1029 
1030             SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, aMessage));
1031             UpdateRecordLengthInMessage(rr, offset, aMessage);
1032             aInfo.mRecordCount++;
1033         }
1034     }
1035 
1036     //----------------------------------
1037     // Service Description Instruction
1038 
1039     // "Delete all RRsets from a name" for Instance Name.
1040 
1041     SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, aMessage));
1042     SuccessOrExit(error = AppendDeleteAllRrsets(aMessage));
1043     aInfo.mRecordCount++;
1044 
1045     VerifyOrExit(!removing);
1046 
1047     // SRV RR
1048 
1049     SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, aMessage));
1050     srv.Init();
1051     srv.SetTtl(GetTtl());
1052     srv.SetPriority(aService.GetPriority());
1053     srv.SetWeight(aService.GetWeight());
1054     srv.SetPort(aService.GetPort());
1055     offset = aMessage.GetLength();
1056     SuccessOrExit(error = aMessage.Append(srv));
1057     SuccessOrExit(error = AppendHostName(aMessage, aInfo));
1058     UpdateRecordLengthInMessage(srv, offset, aMessage);
1059     aInfo.mRecordCount++;
1060 
1061     // TXT RR
1062 
1063     SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, aMessage));
1064     rr.Init(Dns::ResourceRecord::kTypeTxt);
1065     offset = aMessage.GetLength();
1066     SuccessOrExit(error = aMessage.Append(rr));
1067     SuccessOrExit(error =
1068                       Dns::TxtEntry::AppendEntries(aService.GetTxtEntries(), aService.GetNumTxtEntries(), aMessage));
1069     UpdateRecordLengthInMessage(rr, offset, aMessage);
1070     aInfo.mRecordCount++;
1071 
1072 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
1073     if (mServiceKeyRecordEnabled)
1074     {
1075         // KEY RR is optional in "Service Description Instruction". It
1076         // is added here under `REFERENCE_DEVICE` config and is intended
1077         // for testing only.
1078 
1079         SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, aMessage));
1080         SuccessOrExit(error = AppendKeyRecord(aMessage, aInfo));
1081     }
1082 #endif
1083 
1084     if (mSingleServiceMode.IsEnabled())
1085     {
1086         mSingleServiceMode.SetService(aService);
1087     }
1088 
1089 exit:
1090     return error;
1091 }
1092 
AppendHostDescriptionInstruction(Message & aMessage,Info & aInfo)1093 Error Client::AppendHostDescriptionInstruction(Message &aMessage, Info &aInfo)
1094 {
1095     Error error = kErrorNone;
1096 
1097     //----------------------------------
1098     // Host Description Instruction
1099 
1100     // "Delete all RRsets from a name" for Host Name.
1101 
1102     SuccessOrExit(error = AppendHostName(aMessage, aInfo));
1103     SuccessOrExit(error = AppendDeleteAllRrsets(aMessage));
1104     aInfo.mRecordCount++;
1105 
1106     // AAAA RRs
1107 
1108     if (mHostInfo.IsAutoAddressEnabled())
1109     {
1110         // Append all addresses on Thread netif excluding link-local and
1111         // mesh-local addresses. If no address is appended, we include
1112         // the mesh local address.
1113 
1114         mAutoHostAddressAddedMeshLocal = true;
1115 
1116         for (const Ip6::Netif::UnicastAddress &unicastAddress : Get<ThreadNetif>().GetUnicastAddresses())
1117         {
1118             if (unicastAddress.GetAddress().IsLinkLocal() ||
1119                 Get<Mle::Mle>().IsMeshLocalAddress(unicastAddress.GetAddress()))
1120             {
1121                 continue;
1122             }
1123 
1124             SuccessOrExit(error = AppendAaaaRecord(unicastAddress.GetAddress(), aMessage, aInfo));
1125             mAutoHostAddressAddedMeshLocal = false;
1126         }
1127 
1128         if (mAutoHostAddressAddedMeshLocal)
1129         {
1130             SuccessOrExit(error = AppendAaaaRecord(Get<Mle::Mle>().GetMeshLocal64(), aMessage, aInfo));
1131         }
1132     }
1133     else
1134     {
1135         for (uint8_t index = 0; index < mHostInfo.GetNumAddresses(); index++)
1136         {
1137             SuccessOrExit(error = AppendAaaaRecord(mHostInfo.GetAddress(index), aMessage, aInfo));
1138         }
1139     }
1140 
1141     // KEY RR
1142 
1143     SuccessOrExit(error = AppendHostName(aMessage, aInfo));
1144     SuccessOrExit(error = AppendKeyRecord(aMessage, aInfo));
1145 
1146 exit:
1147     return error;
1148 }
1149 
AppendAaaaRecord(const Ip6::Address & aAddress,Message & aMessage,Info & aInfo) const1150 Error Client::AppendAaaaRecord(const Ip6::Address &aAddress, Message &aMessage, Info &aInfo) const
1151 {
1152     Error               error;
1153     Dns::ResourceRecord rr;
1154 
1155     rr.Init(Dns::ResourceRecord::kTypeAaaa);
1156     rr.SetTtl(GetTtl());
1157     rr.SetLength(sizeof(Ip6::Address));
1158 
1159     SuccessOrExit(error = AppendHostName(aMessage, aInfo));
1160     SuccessOrExit(error = aMessage.Append(rr));
1161     SuccessOrExit(error = aMessage.Append(aAddress));
1162     aInfo.mRecordCount++;
1163 
1164 exit:
1165     return error;
1166 }
1167 
AppendKeyRecord(Message & aMessage,Info & aInfo) const1168 Error Client::AppendKeyRecord(Message &aMessage, Info &aInfo) const
1169 {
1170     Error                          error;
1171     Dns::KeyRecord                 key;
1172     Crypto::Ecdsa::P256::PublicKey publicKey;
1173 
1174     key.Init();
1175     key.SetTtl(GetTtl());
1176     key.SetFlags(Dns::KeyRecord::kAuthConfidPermitted, Dns::KeyRecord::kOwnerNonZone,
1177                  Dns::KeyRecord::kSignatoryFlagGeneral);
1178     key.SetProtocol(Dns::KeyRecord::kProtocolDnsSec);
1179     key.SetAlgorithm(Dns::KeyRecord::kAlgorithmEcdsaP256Sha256);
1180     key.SetLength(sizeof(Dns::KeyRecord) - sizeof(Dns::ResourceRecord) + sizeof(Crypto::Ecdsa::P256::PublicKey));
1181     SuccessOrExit(error = aMessage.Append(key));
1182     SuccessOrExit(error = aInfo.mKeyPair.GetPublicKey(publicKey));
1183     SuccessOrExit(error = aMessage.Append(publicKey));
1184     aInfo.mRecordCount++;
1185 
1186 exit:
1187     return error;
1188 }
1189 
AppendDeleteAllRrsets(Message & aMessage) const1190 Error Client::AppendDeleteAllRrsets(Message &aMessage) const
1191 {
1192     // "Delete all RRsets from a name" (RFC 2136 - 2.5.3)
1193     // Name should be already appended in the message.
1194 
1195     Dns::ResourceRecord rr;
1196 
1197     rr.Init(Dns::ResourceRecord::kTypeAny, Dns::ResourceRecord::kClassAny);
1198     rr.SetTtl(0);
1199     rr.SetLength(0);
1200 
1201     return aMessage.Append(rr);
1202 }
1203 
AppendHostName(Message & aMessage,Info & aInfo,bool aDoNotCompress) const1204 Error Client::AppendHostName(Message &aMessage, Info &aInfo, bool aDoNotCompress) const
1205 {
1206     Error error;
1207 
1208     if (aDoNotCompress)
1209     {
1210         // Uncompressed (canonical form) of host name is used for SIG(0)
1211         // calculation.
1212         SuccessOrExit(error = Dns::Name::AppendMultipleLabels(mHostInfo.GetName(), aMessage));
1213         error = Dns::Name::AppendName(mDomainName, aMessage);
1214         ExitNow();
1215     }
1216 
1217     // If host name was previously added in the message, add it
1218     // compressed as pointer to the previous one. Otherwise,
1219     // append it and remember the offset.
1220 
1221     if (aInfo.mHostNameOffset != Info::kUnknownOffset)
1222     {
1223         ExitNow(error = Dns::Name::AppendPointerLabel(aInfo.mHostNameOffset, aMessage));
1224     }
1225 
1226     aInfo.mHostNameOffset = aMessage.GetLength();
1227     SuccessOrExit(error = Dns::Name::AppendMultipleLabels(mHostInfo.GetName(), aMessage));
1228     error = Dns::Name::AppendPointerLabel(aInfo.mDomainNameOffset, aMessage);
1229 
1230 exit:
1231     return error;
1232 }
1233 
AppendUpdateLeaseOptRecord(Message & aMessage) const1234 Error Client::AppendUpdateLeaseOptRecord(Message &aMessage) const
1235 {
1236     Error            error;
1237     Dns::OptRecord   optRecord;
1238     Dns::LeaseOption leaseOption;
1239 
1240     // Append empty (root domain) as OPT RR name.
1241     SuccessOrExit(error = Dns::Name::AppendTerminator(aMessage));
1242 
1243     // `Init()` sets the type and clears (set to zero) the extended
1244     // Response Code, version and all flags.
1245     optRecord.Init();
1246     optRecord.SetUdpPayloadSize(kUdpPayloadSize);
1247     optRecord.SetDnsSecurityFlag();
1248     optRecord.SetLength(sizeof(Dns::LeaseOption));
1249 
1250     SuccessOrExit(error = aMessage.Append(optRecord));
1251 
1252     leaseOption.Init();
1253 
1254     if ((mHostInfo.GetState() == kToRemove) || (mHostInfo.GetState() == kRemoving))
1255     {
1256         leaseOption.SetLeaseInterval(0);
1257         leaseOption.SetKeyLeaseInterval(mShouldRemoveKeyLease ? 0 : mKeyLeaseInterval);
1258     }
1259     else
1260     {
1261         leaseOption.SetLeaseInterval(mLeaseInterval);
1262         leaseOption.SetKeyLeaseInterval(mKeyLeaseInterval);
1263     }
1264 
1265     error = aMessage.Append(leaseOption);
1266 
1267 exit:
1268     return error;
1269 }
1270 
AppendSignature(Message & aMessage,Info & aInfo)1271 Error Client::AppendSignature(Message &aMessage, Info &aInfo)
1272 {
1273     Error                          error;
1274     Dns::SigRecord                 sig;
1275     Crypto::Sha256                 sha256;
1276     Crypto::Sha256::Hash           hash;
1277     Crypto::Ecdsa::P256::Signature signature;
1278     uint16_t                       offset;
1279     uint16_t                       len;
1280 
1281     // Prepare SIG RR: TTL, type covered, labels count should be set
1282     // to zero. Since we have no clock, inception and expiration time
1283     // are also set to zero. The RDATA length will be set later (not
1284     // yet known due to variably (and possible compression) of signer's
1285     // name.
1286 
1287     sig.Clear();
1288     sig.Init(Dns::ResourceRecord::kClassAny);
1289     sig.SetAlgorithm(Dns::KeyRecord::kAlgorithmEcdsaP256Sha256);
1290 
1291     // Append the SIG RR with full uncompressed form of the host name
1292     // as the signer's name. This is used for SIG(0) calculation only.
1293     // It will be overwritten with host name compressed.
1294 
1295     offset = aMessage.GetLength();
1296     SuccessOrExit(error = aMessage.Append(sig));
1297     SuccessOrExit(error = AppendHostName(aMessage, aInfo, /* aDoNotCompress */ true));
1298 
1299     // Calculate signature (RFC 2931): Calculated over "data" which is
1300     // concatenation of (1) the SIG RR RDATA wire format (including
1301     // the canonical form of the signer's name), entirely omitting the
1302     // signature subfield, (2) DNS query message, including DNS header
1303     // but not UDP/IP header before the header RR counts have been
1304     // adjusted for the inclusion of SIG(0).
1305 
1306     sha256.Start();
1307 
1308     // (1) SIG RR RDATA wire format
1309     len = aMessage.GetLength() - offset - sizeof(Dns::ResourceRecord);
1310     sha256.Update(aMessage, offset + sizeof(Dns::ResourceRecord), len);
1311 
1312     // (2) Message from DNS header before SIG
1313     sha256.Update(aMessage, 0, offset);
1314 
1315     sha256.Finish(hash);
1316     SuccessOrExit(error = aInfo.mKeyPair.Sign(hash, signature));
1317 
1318     // Move back in message and append SIG RR now with compressed host
1319     // name (as signer's name) along with the calculated signature.
1320 
1321     IgnoreError(aMessage.SetLength(offset));
1322 
1323     // SIG(0) uses owner name of root (single zero byte).
1324     SuccessOrExit(error = Dns::Name::AppendTerminator(aMessage));
1325 
1326     offset = aMessage.GetLength();
1327     SuccessOrExit(error = aMessage.Append(sig));
1328     SuccessOrExit(error = AppendHostName(aMessage, aInfo));
1329     SuccessOrExit(error = aMessage.Append(signature));
1330     UpdateRecordLengthInMessage(sig, offset, aMessage);
1331 
1332 exit:
1333     return error;
1334 }
1335 
UpdateRecordLengthInMessage(Dns::ResourceRecord & aRecord,uint16_t aOffset,Message & aMessage) const1336 void Client::UpdateRecordLengthInMessage(Dns::ResourceRecord &aRecord, uint16_t aOffset, Message &aMessage) const
1337 {
1338     // This method is used to calculate an RR DATA length and update
1339     // (rewrite) it in a message. This should be called immediately
1340     // after all the fields in the record are written in the message.
1341     // `aOffset` gives the offset in the message to the start of the
1342     // record.
1343 
1344     aRecord.SetLength(aMessage.GetLength() - aOffset - sizeof(Dns::ResourceRecord));
1345     aMessage.Write(aOffset, aRecord);
1346 }
1347 
HandleUdpReceive(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo)1348 void Client::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
1349 {
1350     OT_UNUSED_VARIABLE(aMessageInfo);
1351 
1352     static_cast<Client *>(aContext)->ProcessResponse(AsCoreType(aMessage));
1353 }
1354 
ProcessResponse(Message & aMessage)1355 void Client::ProcessResponse(Message &aMessage)
1356 {
1357     static const ItemState kNewStateOnUpdateDone[]{
1358         /* (0) kToAdd      -> */ kToAdd,
1359         /* (1) kAdding     -> */ kRegistered,
1360         /* (2) kToRefresh  -> */ kToRefresh,
1361         /* (3) kRefreshing -> */ kRegistered,
1362         /* (4) kToRemove   -> */ kToRemove,
1363         /* (5) kRemoving   -> */ kRemoved,
1364         /* (6) kRegistered -> */ kRegistered,
1365         /* (7) kRemoved    -> */ kRemoved,
1366     };
1367 
1368     Error               error = kErrorNone;
1369     Dns::UpdateHeader   header;
1370     uint16_t            offset = aMessage.GetOffset();
1371     uint16_t            recordCount;
1372     LinkedList<Service> removedServices;
1373 
1374     VerifyOrExit(GetState() == kStateUpdating);
1375 
1376     SuccessOrExit(error = aMessage.Read(offset, header));
1377 
1378     VerifyOrExit(header.GetType() == Dns::Header::kTypeResponse, error = kErrorParse);
1379     VerifyOrExit(header.GetQueryType() == Dns::Header::kQueryTypeUpdate, error = kErrorParse);
1380     VerifyOrExit(header.GetMessageId() == mUpdateMessageId, error = kErrorDrop);
1381 
1382     if (!Get<Mle::Mle>().IsRxOnWhenIdle())
1383     {
1384         Get<DataPollSender>().StopFastPolls();
1385     }
1386 
1387     // Response is for the earlier request message.
1388 
1389     LogInfo("Received response");
1390 
1391 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
1392     mAutoStart.ResetTimoutFailureCount();
1393 #endif
1394 
1395     error = Dns::Header::ResponseCodeToError(header.GetResponseCode());
1396 
1397     if (error != kErrorNone)
1398     {
1399         LogInfo("Server rejected %s code:%d", ErrorToString(error), header.GetResponseCode());
1400 
1401         if (mHostInfo.GetState() == kAdding)
1402         {
1403             // Since server rejected the update message, we go back to
1404             // `kToAdd` state to allow user to give a new name using
1405             // `SetHostName()`.
1406             mHostInfo.SetState(kToAdd);
1407         }
1408 
1409         // Wait for the timer to expire to retry. Note that timer is
1410         // already scheduled for the current wait interval when state
1411         // was changed to `kStateUpdating`.
1412 
1413         LogRetryWaitInterval();
1414         GrowRetryWaitInterval();
1415         SetState(kStateToRetry);
1416         InvokeCallback(error);
1417 
1418 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
1419         if ((error == kErrorDuplicated) || (error == kErrorSecurity))
1420         {
1421             // If the server rejects the update with specific errors
1422             // (indicating duplicate name and/or security error), we
1423             // try to switch the server (we check if another can be
1424             // found in the Network Data).
1425             //
1426             // Note that this is done after invoking the callback and
1427             // notifying the user of the error from server. This works
1428             // correctly even if user makes changes from callback
1429             // (e.g., calls SRP client APIs like `Stop` or disables
1430             // auto-start), since we have a guard check at the top of
1431             // `SelectNextServer()` to verify that client is still
1432             // running and auto-start is enabled and selected the
1433             // server.
1434 
1435             SelectNextServer(/* aDisallowSwitchOnRegisteredHost */ true);
1436         }
1437 #endif
1438         ExitNow(error = kErrorNone);
1439     }
1440 
1441     offset += sizeof(header);
1442 
1443     // Skip over all sections till Additional Data section
1444     // SPEC ENHANCEMENT: Server can echo the request back or not
1445     // include any of RRs. Would be good to explicitly require SRP server
1446     // to not echo back RRs.
1447 
1448     if (header.GetZoneRecordCount() != 0)
1449     {
1450         VerifyOrExit(header.GetZoneRecordCount() == 1, error = kErrorParse);
1451         SuccessOrExit(error = Dns::Name::ParseName(aMessage, offset));
1452         VerifyOrExit(offset + sizeof(Dns::Zone) <= aMessage.GetLength(), error = kErrorParse);
1453         offset += sizeof(Dns::Zone);
1454     }
1455 
1456     // Check for Update Lease OPT RR. This determines the lease
1457     // interval accepted by server. If not present, then use the
1458     // transmitted lease interval from the update request message.
1459 
1460     mAcceptedLeaseInterval = mLeaseInterval;
1461     recordCount =
1462         header.GetPrerequisiteRecordCount() + header.GetUpdateRecordCount() + header.GetAdditionalRecordCount();
1463 
1464     while (recordCount > 0)
1465     {
1466         uint16_t            startOffset = offset;
1467         Dns::ResourceRecord rr;
1468 
1469         SuccessOrExit(error = ReadResourceRecord(aMessage, offset, rr));
1470         recordCount--;
1471 
1472         if (rr.GetType() == Dns::ResourceRecord::kTypeOpt)
1473         {
1474             SuccessOrExit(error = ProcessOptRecord(aMessage, startOffset, static_cast<Dns::OptRecord &>(rr)));
1475         }
1476     }
1477 
1478     // Calculate the lease renew time based on update message tx time
1479     // and the lease time. `kLeaseRenewGuardInterval` is used to
1480     // ensure that we renew the lease before server expires it. In the
1481     // unlikely (but maybe useful for testing) case where the accepted
1482     // lease interval is too short (shorter than the guard time) we
1483     // just use half of the accepted lease interval.
1484 
1485     if (mAcceptedLeaseInterval > kLeaseRenewGuardInterval)
1486     {
1487         mLeaseRenewTime += Time::SecToMsec(mAcceptedLeaseInterval - kLeaseRenewGuardInterval);
1488     }
1489     else
1490     {
1491         mLeaseRenewTime += Time::SecToMsec(mAcceptedLeaseInterval) / 2;
1492     }
1493 
1494     for (Service &service : mServices)
1495     {
1496         if ((service.GetState() == kAdding) || (service.GetState() == kRefreshing))
1497         {
1498             service.SetLeaseRenewTime(mLeaseRenewTime);
1499         }
1500     }
1501 
1502     // State changes:
1503     //   kAdding     -> kRegistered
1504     //   kRefreshing -> kRegistered
1505     //   kRemoving   -> kRemoved
1506 
1507     ChangeHostAndServiceStates(kNewStateOnUpdateDone);
1508     mSingleServiceMode.Disable();
1509 
1510     HandleUpdateDone();
1511     UpdateState();
1512 
1513 exit:
1514     if (error != kErrorNone)
1515     {
1516         LogInfo("Failed to process response %s", ErrorToString(error));
1517     }
1518 }
1519 
HandleUpdateDone(void)1520 void Client::HandleUpdateDone(void)
1521 {
1522     HostInfo            hostInfoCopy = mHostInfo;
1523     LinkedList<Service> removedServices;
1524 
1525     if (mHostInfo.GetState() == kRemoved)
1526     {
1527         mHostInfo.Clear();
1528     }
1529 
1530     ResetRetryWaitInterval();
1531     SetState(kStateUpdated);
1532 
1533     GetRemovedServices(removedServices);
1534     InvokeCallback(kErrorNone, hostInfoCopy, removedServices.GetHead());
1535 }
1536 
GetRemovedServices(LinkedList<Service> & aRemovedServices)1537 void Client::GetRemovedServices(LinkedList<Service> &aRemovedServices)
1538 {
1539     mServices.RemoveAllMatching(kRemoved, aRemovedServices);
1540 }
1541 
ReadResourceRecord(const Message & aMessage,uint16_t & aOffset,Dns::ResourceRecord & aRecord)1542 Error Client::ReadResourceRecord(const Message &aMessage, uint16_t &aOffset, Dns::ResourceRecord &aRecord)
1543 {
1544     // Reads and skips over a Resource Record (RR) from message at
1545     // given offset. On success, `aOffset` is updated to point to end
1546     // of RR.
1547 
1548     Error error;
1549 
1550     SuccessOrExit(error = Dns::Name::ParseName(aMessage, aOffset));
1551     SuccessOrExit(error = aMessage.Read(aOffset, aRecord));
1552     VerifyOrExit(aOffset + aRecord.GetSize() <= aMessage.GetLength(), error = kErrorParse);
1553     aOffset += static_cast<uint16_t>(aRecord.GetSize());
1554 
1555 exit:
1556     return error;
1557 }
1558 
ProcessOptRecord(const Message & aMessage,uint16_t aOffset,const Dns::OptRecord & aOptRecord)1559 Error Client::ProcessOptRecord(const Message &aMessage, uint16_t aOffset, const Dns::OptRecord &aOptRecord)
1560 {
1561     // Read and process all options (in an OPT RR) from a message.
1562     // The `aOffset` points to beginning of record in `aMessage`.
1563 
1564     Error    error = kErrorNone;
1565     uint16_t len;
1566 
1567     IgnoreError(Dns::Name::ParseName(aMessage, aOffset));
1568     aOffset += sizeof(Dns::OptRecord);
1569 
1570     len = aOptRecord.GetLength();
1571 
1572     while (len > 0)
1573     {
1574         Dns::LeaseOption leaseOption;
1575         Dns::Option &    option = leaseOption;
1576         uint16_t         size;
1577 
1578         SuccessOrExit(error = aMessage.Read(aOffset, option));
1579 
1580         VerifyOrExit(aOffset + option.GetSize() <= aMessage.GetLength(), error = kErrorParse);
1581 
1582         if ((option.GetOptionCode() == Dns::Option::kUpdateLease) &&
1583             (option.GetOptionLength() >= Dns::LeaseOption::kOptionLength))
1584         {
1585             SuccessOrExit(error = aMessage.Read(aOffset, leaseOption));
1586 
1587             mAcceptedLeaseInterval = leaseOption.GetLeaseInterval();
1588 
1589             if (mAcceptedLeaseInterval > kMaxLease)
1590             {
1591                 mAcceptedLeaseInterval = kMaxLease;
1592             }
1593         }
1594 
1595         size = static_cast<uint16_t>(option.GetSize());
1596         aOffset += size;
1597         len -= size;
1598     }
1599 
1600 exit:
1601     return error;
1602 }
1603 
UpdateState(void)1604 void Client::UpdateState(void)
1605 {
1606     TimeMilli now               = TimerMilli::GetNow();
1607     TimeMilli earliestRenewTime = now.GetDistantFuture();
1608     bool      shouldUpdate      = false;
1609 
1610     VerifyOrExit((GetState() != kStateStopped) && (GetState() != kStatePaused));
1611     VerifyOrExit(mHostInfo.GetName() != nullptr);
1612 
1613     // Go through the host info and all the services to check if there
1614     // are any new changes (i.e., anything new to add or remove). This
1615     // is used to determine whether to send an SRP update message or
1616     // not. Also keep track of the earliest renew time among the
1617     // previously registered services. This is used to schedule the
1618     // timer for next refresh.
1619 
1620     switch (mHostInfo.GetState())
1621     {
1622     case kAdding:
1623     case kRefreshing:
1624     case kRemoving:
1625         break;
1626 
1627     case kRegistered:
1628         if (now < mLeaseRenewTime)
1629         {
1630             break;
1631         }
1632 
1633         mHostInfo.SetState(kToRefresh);
1634 
1635         // Fall through
1636 
1637     case kToAdd:
1638     case kToRefresh:
1639         // Make sure we have at least one service and at least one
1640         // host address, otherwise no need to send SRP update message.
1641         // The exception is when removing host info where we allow
1642         // for empty service list.
1643         VerifyOrExit(!mServices.IsEmpty() && (mHostInfo.IsAutoAddressEnabled() || (mHostInfo.GetNumAddresses() > 0)));
1644 
1645         // Fall through
1646 
1647     case kToRemove:
1648         shouldUpdate = true;
1649         break;
1650 
1651     case kRemoved:
1652         ExitNow();
1653     }
1654 
1655     // If host info is being removed, we skip over checking service list
1656     // for new adds (or removes). This handles the situation where while
1657     // remove is ongoing and before we get a response from the server,
1658     // user adds a new service to be registered. We wait for remove to
1659     // finish (receive response from server) before starting with a new
1660     // service adds.
1661 
1662     if (mHostInfo.GetState() != kRemoving)
1663     {
1664         for (Service &service : mServices)
1665         {
1666             switch (service.GetState())
1667             {
1668             case kToAdd:
1669             case kToRefresh:
1670             case kToRemove:
1671                 shouldUpdate = true;
1672                 break;
1673 
1674             case kRegistered:
1675                 if (service.GetLeaseRenewTime() <= now)
1676                 {
1677                     service.SetState(kToRefresh);
1678                     shouldUpdate = true;
1679                 }
1680                 else if (service.GetLeaseRenewTime() < earliestRenewTime)
1681                 {
1682                     earliestRenewTime = service.GetLeaseRenewTime();
1683                 }
1684 
1685                 break;
1686 
1687             case kAdding:
1688             case kRefreshing:
1689             case kRemoving:
1690             case kRemoved:
1691                 break;
1692             }
1693         }
1694     }
1695 
1696     if (shouldUpdate)
1697     {
1698         SetState(kStateToUpdate);
1699         ExitNow();
1700     }
1701 
1702     if ((GetState() == kStateUpdated) && (earliestRenewTime != now.GetDistantFuture()))
1703     {
1704         mTimer.FireAt(earliestRenewTime);
1705     }
1706 
1707 exit:
1708     return;
1709 }
1710 
GrowRetryWaitInterval(void)1711 void Client::GrowRetryWaitInterval(void)
1712 {
1713     mRetryWaitInterval =
1714         mRetryWaitInterval / kRetryIntervalGrowthFactorDenominator * kRetryIntervalGrowthFactorNumerator;
1715 
1716     if (mRetryWaitInterval > kMaxRetryWaitInterval)
1717     {
1718         mRetryWaitInterval = kMaxRetryWaitInterval;
1719     }
1720 }
1721 
GetBoundedLeaseInterval(uint32_t aInterval,uint32_t aDefaultInterval) const1722 uint32_t Client::GetBoundedLeaseInterval(uint32_t aInterval, uint32_t aDefaultInterval) const
1723 {
1724     uint32_t boundedInterval = aDefaultInterval;
1725 
1726     if (aInterval != 0)
1727     {
1728         boundedInterval = OT_MIN(aInterval, static_cast<uint32_t>(kMaxLease));
1729     }
1730 
1731     return boundedInterval;
1732 }
1733 
ShouldRenewEarly(const Service & aService) const1734 bool Client::ShouldRenewEarly(const Service &aService) const
1735 {
1736     // Check if we reached the service renew time or close to it. The
1737     // "early renew interval" is used to allow early refresh. It is
1738     // calculated as a factor of the `mAcceptedLeaseInterval`. The
1739     // "early lease renew factor" is given as a fraction (numerator and
1740     // denominator). If the denominator is set to zero (i.e., factor is
1741     // set to infinity), then service is always included in all SRP
1742     // update messages.
1743 
1744     bool shouldRenew;
1745 
1746 #if OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_DENOMINATOR != 0
1747     uint32_t earlyRenewInterval =
1748         Time::SecToMsec(mAcceptedLeaseInterval) / kEarlyLeaseRenewFactorDenominator * kEarlyLeaseRenewFactorNumerator;
1749 
1750     shouldRenew = (aService.GetLeaseRenewTime() <= TimerMilli::GetNow() + earlyRenewInterval);
1751 #else
1752     OT_UNUSED_VARIABLE(aService);
1753     shouldRenew = true;
1754 #endif
1755 
1756     return shouldRenew;
1757 }
1758 
HandleTimer(Timer & aTimer)1759 void Client::HandleTimer(Timer &aTimer)
1760 {
1761     aTimer.Get<Client>().HandleTimer();
1762 }
1763 
HandleTimer(void)1764 void Client::HandleTimer(void)
1765 {
1766     switch (GetState())
1767     {
1768     case kStateStopped:
1769     case kStatePaused:
1770         break;
1771 
1772     case kStateToUpdate:
1773     case kStateToRetry:
1774         SendUpdate();
1775         break;
1776 
1777     case kStateUpdating:
1778         mSingleServiceMode.Disable();
1779         LogRetryWaitInterval();
1780         LogInfo("Timed out, no response");
1781         GrowRetryWaitInterval();
1782         SetState(kStateToUpdate);
1783         InvokeCallback(kErrorResponseTimeout);
1784 
1785 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
1786 
1787         // After certain number of back-to-back timeout failures, we try
1788         // to switch the server. This is again done after invoking the
1789         // callback. It works correctly due to the guard check at the
1790         // top of `SelectNextServer()`.
1791 
1792         mAutoStart.IncrementTimoutFailureCount();
1793 
1794         if (mAutoStart.GetTimoutFailureCount() >= kMaxTimeoutFailuresToSwitchServer)
1795         {
1796             SelectNextServer(kDisallowSwitchOnRegisteredHost);
1797         }
1798 #endif
1799         break;
1800 
1801     case kStateUpdated:
1802         UpdateState();
1803         break;
1804     }
1805 }
1806 
1807 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
1808 
EnableAutoStartMode(AutoStartCallback aCallback,void * aContext)1809 void Client::EnableAutoStartMode(AutoStartCallback aCallback, void *aContext)
1810 {
1811     mAutoStart.SetCallback(aCallback, aContext);
1812 
1813     VerifyOrExit(mAutoStart.GetState() == AutoStart::kDisabled);
1814 
1815     mAutoStart.SetState(AutoStart::kSelectedNone);
1816     ProcessAutoStart();
1817 
1818 exit:
1819     return;
1820 }
1821 
ProcessAutoStart(void)1822 void Client::ProcessAutoStart(void)
1823 {
1824     Ip6::SockAddr       serverSockAddr;
1825     DnsSrpAnycast::Info anycastInfo;
1826     DnsSrpUnicast::Info unicastInfo;
1827     bool                shouldRestart = false;
1828 
1829     // If auto start mode is enabled, we check the Network Data entries
1830     // to discover and select the preferred SRP server to register with.
1831     // If we currently have a selected server, we ensure that it is
1832     // still present in the Network Data and is still the preferred one.
1833 
1834     VerifyOrExit(mAutoStart.GetState() != AutoStart::kDisabled);
1835 
1836     // If SRP client is running, we check to make sure that auto-start
1837     // did select the current server, and server was not specified by
1838     // user directly.
1839 
1840     if (IsRunning())
1841     {
1842         VerifyOrExit(mAutoStart.GetState() != AutoStart::kSelectedNone);
1843     }
1844 
1845     // There are three types of entries in Network Data:
1846     //
1847     // 1) Preferred unicast entries with address included in service data.
1848     // 2) Anycast entries (each having a seq number).
1849     // 3) Unicast entries with address info included in server data.
1850 
1851     serverSockAddr.Clear();
1852 
1853     if (SelectUnicastEntry(DnsSrpUnicast::kFromServiceData, unicastInfo) == kErrorNone)
1854     {
1855         mAutoStart.SetState(AutoStart::kSelectedUnicastPreferred);
1856         serverSockAddr = unicastInfo.mSockAddr;
1857     }
1858     else if (Get<NetworkData::Service::Manager>().FindPreferredDnsSrpAnycastInfo(anycastInfo) == kErrorNone)
1859     {
1860         serverSockAddr.SetAddress(anycastInfo.mAnycastAddress);
1861         serverSockAddr.SetPort(kAnycastServerPort);
1862 
1863         // We check if we are selecting an anycast entry for first
1864         // time, or if the seq number has changed. Even if the
1865         // anycast address remains the same as before, on a seq
1866         // number change, the client still needs to restart to
1867         // re-register its info.
1868 
1869         if ((mAutoStart.GetState() != AutoStart::kSelectedAnycast) ||
1870             (mAutoStart.GetAnycastSeqNum() != anycastInfo.mSequenceNumber))
1871         {
1872             shouldRestart = true;
1873             mAutoStart.SetAnycastSeqNum(anycastInfo.mSequenceNumber);
1874         }
1875 
1876         mAutoStart.SetState(AutoStart::kSelectedAnycast);
1877     }
1878     else if (SelectUnicastEntry(DnsSrpUnicast::kFromServerData, unicastInfo) == kErrorNone)
1879     {
1880         mAutoStart.SetState(AutoStart::kSelectedUnicast);
1881         serverSockAddr = unicastInfo.mSockAddr;
1882     }
1883 
1884     if (IsRunning())
1885     {
1886         VerifyOrExit((GetServerAddress() != serverSockAddr) || shouldRestart);
1887         Stop(kRequesterAuto, kResetRetryInterval);
1888     }
1889 
1890     if (!serverSockAddr.GetAddress().IsUnspecified())
1891     {
1892         IgnoreError(Start(serverSockAddr, kRequesterAuto));
1893     }
1894     else
1895     {
1896         mAutoStart.SetState(AutoStart::kSelectedNone);
1897     }
1898 
1899 exit:
1900     return;
1901 }
1902 
SelectUnicastEntry(DnsSrpUnicast::Origin aOrigin,DnsSrpUnicast::Info & aInfo) const1903 Error Client::SelectUnicastEntry(DnsSrpUnicast::Origin aOrigin, DnsSrpUnicast::Info &aInfo) const
1904 {
1905     Error                                   error = kErrorNotFound;
1906     DnsSrpUnicast::Info                     unicastInfo;
1907     NetworkData::Service::Manager::Iterator iterator;
1908 #if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
1909     Settings::SrpClientInfo savedInfo;
1910     bool                    hasSavedServerInfo = false;
1911 
1912     if (!IsRunning())
1913     {
1914         hasSavedServerInfo = (Get<Settings>().Read(savedInfo) == kErrorNone);
1915     }
1916 #endif
1917 
1918     while (Get<NetworkData::Service::Manager>().GetNextDnsSrpUnicastInfo(iterator, unicastInfo) == kErrorNone)
1919     {
1920         if (unicastInfo.mOrigin != aOrigin)
1921         {
1922             continue;
1923         }
1924 
1925         if (mAutoStart.HasSelectedServer() && (GetServerAddress() == unicastInfo.mSockAddr))
1926         {
1927             aInfo = unicastInfo;
1928             error = kErrorNone;
1929             ExitNow();
1930         }
1931 
1932 #if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
1933         if (hasSavedServerInfo && (unicastInfo.mSockAddr.GetAddress() == savedInfo.GetServerAddress()) &&
1934             (unicastInfo.mSockAddr.GetPort() == savedInfo.GetServerPort()))
1935         {
1936             // Stop the search if we see a match for the previously
1937             // saved server info in the network data entries.
1938 
1939             aInfo = unicastInfo;
1940             error = kErrorNone;
1941             ExitNow();
1942         }
1943 #endif
1944 
1945         // Prefer the numerically lowest server address
1946 
1947         if ((error == kErrorNotFound) || (unicastInfo.mSockAddr.GetAddress() < aInfo.mSockAddr.GetAddress()))
1948         {
1949             aInfo = unicastInfo;
1950             error = kErrorNone;
1951         }
1952     }
1953 
1954 exit:
1955     return error;
1956 }
1957 
1958 #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
SelectNextServer(bool aDisallowSwitchOnRegisteredHost)1959 void Client::SelectNextServer(bool aDisallowSwitchOnRegisteredHost)
1960 {
1961     // This method tries to find the next unicast server info entry in the
1962     // Network Data after the current one selected. If found, it
1963     // restarts the client with the new server (keeping the retry wait
1964     // interval as before).
1965 
1966     Ip6::SockAddr         serverSockAddr;
1967     bool                  selectNext = false;
1968     DnsSrpUnicast::Origin origin     = DnsSrpUnicast::kFromServiceData;
1969 
1970     serverSockAddr.Clear();
1971 
1972     // Ensure that client is running, auto-start is enabled and
1973     // auto-start selected the server and it is a unicast entry.
1974 
1975     VerifyOrExit(IsRunning());
1976 
1977     switch (mAutoStart.GetState())
1978     {
1979     case AutoStart::kSelectedUnicastPreferred:
1980         origin = DnsSrpUnicast::kFromServiceData;
1981         break;
1982 
1983     case AutoStart::kSelectedUnicast:
1984         origin = DnsSrpUnicast::kFromServerData;
1985         break;
1986 
1987     case AutoStart::kSelectedAnycast:
1988     case AutoStart::kDisabled:
1989     case AutoStart::kSelectedNone:
1990         ExitNow();
1991     }
1992 
1993     if (aDisallowSwitchOnRegisteredHost)
1994     {
1995         // Ensure that host info is not yet registered (indicating that no
1996         // service has yet been registered either).
1997         VerifyOrExit((mHostInfo.GetState() == kAdding) || (mHostInfo.GetState() == kToAdd));
1998     }
1999 
2000     // We go through all entries to find the one matching the currently
2001     // selected one, then set `selectNext` to `true` so to select the
2002     // next one.
2003 
2004     do
2005     {
2006         DnsSrpUnicast::Info                     unicastInfo;
2007         NetworkData::Service::Manager::Iterator iterator;
2008 
2009         while (Get<NetworkData::Service::Manager>().GetNextDnsSrpUnicastInfo(iterator, unicastInfo) == kErrorNone)
2010         {
2011             if (unicastInfo.mOrigin != origin)
2012             {
2013                 continue;
2014             }
2015 
2016             if (selectNext)
2017             {
2018                 serverSockAddr = unicastInfo.mSockAddr;
2019                 ExitNow();
2020             }
2021 
2022             if (GetServerAddress() == unicastInfo.mSockAddr)
2023             {
2024                 selectNext = true;
2025             }
2026         }
2027 
2028         // We loop back to handle the case where the current entry
2029         // is the last one.
2030 
2031     } while (selectNext);
2032 
2033     // If we reach here it indicates we could not find the entry
2034     // associated with currently selected server in the list. This
2035     // situation is rather unlikely but can still happen if Network
2036     // Data happens to be changed and the entry removed but
2037     // the "changed" event from `Notifier` may have not yet been
2038     // processed (note that events are emitted from their own
2039     // tasklet). In such a case we keep `serverSockAddr` as empty.
2040 
2041 exit:
2042     if (!serverSockAddr.GetAddress().IsUnspecified() && (GetServerAddress() != serverSockAddr))
2043     {
2044         // We specifically update `mHostInfo` to `kToAdd` state. This
2045         // ensures that `Stop()` will keep it as kToAdd` and we detect
2046         // that the host info has not been registered yet and allow the
2047         // `SelectNextServer()` to happen again if the timeouts/failures
2048         // continue to happen with the new server.
2049 
2050         mHostInfo.SetState(kToAdd);
2051         Stop(kRequesterAuto, kKeepRetryInterval);
2052         IgnoreError(Start(serverSockAddr, kRequesterAuto));
2053     }
2054 }
2055 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
2056 
2057 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
2058 
ItemStateToString(ItemState aState)2059 const char *Client::ItemStateToString(ItemState aState)
2060 {
2061     static const char *const kItemStateStrings[] = {
2062         "ToAdd",      // kToAdd      (0)
2063         "Adding",     // kAdding     (1)
2064         "ToRefresh",  // kToRefresh  (2)
2065         "Refreshing", // kRefreshing (3)
2066         "ToRemove",   // kToRemove   (4)
2067         "Removing",   // kRemoving   (5)
2068         "Registered", // kRegistered (6)
2069         "Removed",    // kRemoved    (7)
2070     };
2071 
2072     return kItemStateStrings[aState];
2073 }
2074 
2075 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
2076 
StateToString(State aState)2077 const char *Client::StateToString(State aState)
2078 {
2079     static const char *const kStateStrings[] = {
2080         "Stopped",  // kStateStopped  (0)
2081         "Paused",   // kStatePaused   (1)
2082         "ToUpdate", // kStateToUpdate (2)
2083         "Updating", // kStateUpdating (3)
2084         "Updated",  // kStateUpdated  (4)
2085         "ToRetry",  // kStateToRetry  (5)
2086     };
2087 
2088     static_assert(kStateStopped == 0, "kStateStopped value is not correct");
2089     static_assert(kStatePaused == 1, "kStatePaused value is not correct");
2090     static_assert(kStateToUpdate == 2, "kStateToUpdate value is not correct");
2091     static_assert(kStateUpdating == 3, "kStateUpdating value is not correct");
2092     static_assert(kStateUpdated == 4, "kStateUpdated value is not correct");
2093     static_assert(kStateToRetry == 5, "kStateToRetry value is not correct");
2094 
2095     return kStateStrings[aState];
2096 }
2097 
LogRetryWaitInterval(void) const2098 void Client::LogRetryWaitInterval(void) const
2099 {
2100     constexpr uint16_t kLogInMsecLimit = 5000; // Max interval (in msec) to log the value in msec unit
2101 
2102     uint32_t interval = GetRetryWaitInterval();
2103 
2104     LogInfo("Retry interval %u %s", (interval < kLogInMsecLimit) ? interval : Time::MsecToSec(interval),
2105             (interval < kLogInMsecLimit) ? "ms" : "sec");
2106 }
2107 
2108 #endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
2109 
2110 } // namespace Srp
2111 } // namespace ot
2112 
2113 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
2114