diff --git a/src/core/config/srp_client.h b/src/core/config/srp_client.h index 43fe02d8a..8744d30d7 100644 --- a/src/core/config/srp_client.h +++ b/src/core/config/srp_client.h @@ -70,6 +70,33 @@ #define OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE 0 #endif +/** + * @def OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE + * + * Define to 1 to enable SRP client to switch server on failure (when auto-start feature is used). + * + * This config is only used when `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` is enabled. + * + * When enabled, the client will try to select the next server from entries in Thread Network Data when the current + * server rejects an SRP update or there is no response (timeout waiting for response from server). + * + */ +#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE +#define OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE 1 +#endif + +/** + * @def OPENTHREAD_CONFIG_SRP_CLIENT_MAX_TIMEOUT_FAILURES_TO_SWITCH_SERVER + * + * Specifies number of timeout failures to trigger a switch of server. + * + * This is applicable only when `OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE` is used. + * + */ +#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_MAX_TIMEOUT_FAILURES_TO_SWITCH_SERVER +#define OPENTHREAD_CONFIG_SRP_CLIENT_MAX_TIMEOUT_FAILURES_TO_SWITCH_SERVER 3 +#endif + /** * @def OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE * diff --git a/src/core/net/srp_client.cpp b/src/core/net/srp_client.cpp index 4ceb8f1e5..61d94379c 100644 --- a/src/core/net/srp_client.cpp +++ b/src/core/net/srp_client.cpp @@ -35,6 +35,7 @@ #include "common/instance.hpp" #include "common/locator_getters.hpp" #include "common/logging.hpp" +#include "common/numeric_limits.hpp" #include "common/random.hpp" #include "common/settings.hpp" #include "common/string.hpp" @@ -167,6 +168,9 @@ Client::Client(Instance &aInstance) , mAutoStartCallback(nullptr) , mAutoStartContext(nullptr) , mServerSequenceNumber(0) +#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE + , mTimoutFailureCount(0) +#endif #endif , mDomainName(kDefaultDomainName) , mTimer(aInstance, Client::HandleTimer) @@ -215,7 +219,7 @@ exit: return error; } -void Client::Stop(Requester aRequester) +void Client::Stop(Requester aRequester, StopMode aMode) { // Change the state of host info and services so that they are // added/removed again once the client is started back. In the @@ -247,14 +251,28 @@ void Client::Stop(Requester aRequester) ChangeHostAndServiceStates(kNewStateOnStop); IgnoreError(mSocket.Close()); + mShouldRemoveKeyLease = false; mTxFailureRetryCount = 0; - ResetRetryWaitInterval(); + + if (aMode == kResetRetryInterval) + { + ResetRetryWaitInterval(); + } + SetState(kStateStopped); #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE - VerifyOrExit((aRequester == kRequesterAuto) && (mAutoStartCallback != nullptr)); - mAutoStartCallback(nullptr, mAutoStartContext); +#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE + mTimoutFailureCount = 0; +#endif + + mAutoStartDidSelectServer = false; + + if ((aRequester == kRequesterAuto) && (mAutoStartCallback != nullptr)) + { + mAutoStartCallback(nullptr, mAutoStartContext); + } #endif exit: @@ -1137,6 +1155,10 @@ void Client::ProcessResponse(Message &aMessage) otLogInfoSrp("[client] Received response"); +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE + mTimoutFailureCount = 0; +#endif + error = Dns::Header::ResponseCodeToError(header.GetResponseCode()); if (error != kErrorNone) @@ -1159,6 +1181,27 @@ void Client::ProcessResponse(Message &aMessage) GrowRetryWaitInterval(); SetState(kStateToRetry); InvokeCallback(error); + +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE + if ((error == kErrorDuplicated) || (error == kErrorSecurity)) + { + // If the server rejects the update with specific errors + // (indicating duplicate name and/or security error), we + // try to switch the server (we check if another can be + // found in the Network Data). + // + // Note that this is done after invoking the callback and + // notifying the user of the error from server. This works + // correctly even if user makes changes from callback + // (e.g., calls SRP client APIs like `Stop` or disables + // auto-start), since we have a guard check at the top of + // `SelectNextServer()` to verify that client is still + // running and auto-start is enabled and selected the + // server. + + SelectNextServer(); + } +#endif ExitNow(error = kErrorNone); } @@ -1523,6 +1566,24 @@ void Client::HandleTimer(void) GrowRetryWaitInterval(); SetState(kStateToUpdate); InvokeCallback(kErrorResponseTimeout); + +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE + + // After certain number of back-to-back timeout failures, we try + // to switch the server. This is again done after invoking the + // callback. It works correctly due to the guard check at the + // top of `SelectNextServer()`. + + if (mTimoutFailureCount < NumericLimits::kMax) + { + mTimoutFailureCount++; + } + + if (mTimoutFailureCount >= kMaxTimeoutFailuresToSwitchServer) + { + SelectNextServer(); + } +#endif break; case kStateUpdated: @@ -1643,7 +1704,7 @@ void Client::ProcessAutoStart(void) if (IsRunning()) { - Stop(kRequesterAuto); + Stop(kRequesterAuto, kResetRetryInterval); } VerifyOrExit(!serverSockAddr.GetAddress().IsUnspecified()); @@ -1655,6 +1716,101 @@ exit: return; } +#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE +void Client::SelectNextServer(void) +{ + // This method tries to find the next server info entry in the + // Network Data after the current one selected. If found, it + // restarts the client with the new server (keeping the retry wait + // interval as before). + + Ip6::SockAddr serverSockAddr; + bool selectNext = false; + + serverSockAddr.Clear(); + + // Ensure that client is running, auto-start is enabled and + // auto-start selected the server and that host info is not yet + // registered (indicating that no service has yet been registered + // either). + + VerifyOrExit(IsRunning() && mAutoStartModeEnabled && mAutoStartDidSelectServer); + VerifyOrExit((mHostInfo.GetState() == kAdding) || (mHostInfo.GetState() == kToAdd)); + + // We go through all entries to find the one matching the currently + // selected one, then set `selectNext` to `true` so to select the + // next one. + + do + { + NetworkData::Service::DnsSrpAnycast::Info anycastInfo; + NetworkData::Service::DnsSrpUnicast::Info unicastInfo; + NetworkData::Service::Manager::Iterator iterator; + + while (Get().GetNextDnsSrpAnycastInfo(iterator, anycastInfo) == kErrorNone) + { + if (selectNext) + { + serverSockAddr.SetAddress(anycastInfo.mAnycastAddress); + serverSockAddr.SetPort(kAnycastServerPort); + mServerSequenceNumber = anycastInfo.mSequenceNumber; + mAutoStartIsUsingAnycastAddress = true; + ExitNow(); + } + + if (mAutoStartIsUsingAnycastAddress && (GetServerAddress().GetAddress() == anycastInfo.mAnycastAddress) && + (GetServerAddress().GetPort() == kAnycastServerPort)) + { + selectNext = true; + } + } + + iterator.Reset(); + + while (Get().GetNextDnsSrpUnicastInfo(iterator, unicastInfo) == kErrorNone) + { + if (selectNext) + { + serverSockAddr = unicastInfo.mSockAddr; + mAutoStartIsUsingAnycastAddress = false; + ExitNow(); + } + + if (GetServerAddress() == unicastInfo.mSockAddr) + { + selectNext = true; + } + } + + // We loop back to handle the case where the current entry + // is the last one. + + } while (selectNext); + + // If we reach here it indicates we could not find the entry + // associated with currently selected server in the list. This + // situation is rather unlikely but can still happen if Network + // Data happens to be changed and the entry removed but + // the "changed" event from `Notifier` may have not yet been + // processed (note that events are emitted from their own + // tasklet). In such a case we keep `serverSockAddr` as empty. + +exit: + if (!serverSockAddr.GetAddress().IsUnspecified() && (GetServerAddress() != serverSockAddr)) + { + // We specifically update `mHostInfo` to `kToAdd` state. This + // ensures that `Stop()` will keep it as kToAdd` and we detect + // that the host info has not been registered yet and allow the + // `SelectNextServer()` to happen again if the timeouts/failures + // continue to happen with the new server. + + mHostInfo.SetState(kToAdd); + Stop(kRequesterAuto, kKeepRetryInterval); + IgnoreError(Start(serverSockAddr, kRequesterAuto)); + } +} +#endif // OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE + #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE const char *Client::ItemStateToString(ItemState aState) diff --git a/src/core/net/srp_client.hpp b/src/core/net/srp_client.hpp index c31c5fc5e..40bdf2019 100644 --- a/src/core/net/srp_client.hpp +++ b/src/core/net/srp_client.hpp @@ -290,7 +290,7 @@ public: * also disables the auto-start mode. * */ - void Stop(void) { Stop(kRequesterUser); } + void Stop(void) { Stop(kRequesterUser, kResetRetryInterval); } #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE /** @@ -619,6 +619,10 @@ private: enum : uint8_t { kFastPollsAfterUpdateTx = 11, // Number of fast data polls after SRP Update tx (11x 188ms = ~2 seconds) + +#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE + kMaxTimeoutFailuresToSwitchServer = OPENTHREAD_CONFIG_SRP_CLIENT_MAX_TIMEOUT_FAILURES_TO_SWITCH_SERVER, +#endif }; enum : uint16_t @@ -742,6 +746,14 @@ private: #endif }; + // This enumeration is used as an input to private `Stop()` to + // indicate whether to reset the retry interval or keep it as is. + enum StopMode : uint8_t + { + kResetRetryInterval, + kKeepRetryInterval, + }; + struct Info : public Clearable { enum : uint16_t @@ -756,7 +768,7 @@ private: }; Error Start(const Ip6::SockAddr &aServerSockAddr, Requester aRequester); - void Stop(Requester aRequester); + void Stop(Requester aRequester, StopMode aMode); void Resume(void); void Pause(void); void HandleNotifierEvents(Events aEvents); @@ -795,6 +807,9 @@ private: void HandleTimer(void); #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE void ProcessAutoStart(void); +#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE + void SelectNextServer(void); +#endif #endif #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_SRP == 1) @@ -837,6 +852,9 @@ private: AutoStartCallback mAutoStartCallback; void * mAutoStartContext; uint8_t mServerSequenceNumber; +#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE + uint8_t mTimoutFailureCount; +#endif #endif const char * mDomainName; diff --git a/src/core/thread/network_data_service.hpp b/src/core/thread/network_data_service.hpp index ab372be37..3a7ceef62 100644 --- a/src/core/thread/network_data_service.hpp +++ b/src/core/thread/network_data_service.hpp @@ -399,6 +399,16 @@ public: { } + /** + * This method resets the iterator to start from beginning. + * + */ + void Reset(void) + { + mServiceTlv = nullptr; + mServerSubTlv = nullptr; + } + private: const ServiceTlv *mServiceTlv; const ServerTlv * mServerSubTlv;