[srp-client] add feature for switching server on failure (#6719)

This commit adds new feature in SRP client to switch server on failure
(when auto-start feature is used). 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 are timeouts
(no response) after multiple attempts.
This commit is contained in:
Abtin Keshavarzian
2021-06-22 10:21:58 -07:00
committed by GitHub
parent d5d6361669
commit cf66d1c09a
4 changed files with 218 additions and 7 deletions
+27
View File
@@ -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
*
+161 -5
View File
@@ -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<uint8_t>::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<NetworkData::Service::Manager>().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<NetworkData::Service::Manager>().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)
+20 -2
View File
@@ -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<Info>
{
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;
+10
View File
@@ -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;