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