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