From ef259cdd6b65f1178cea0a938de2378a2da792ac Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Fri, 19 Feb 2021 10:00:19 -0800 Subject: [PATCH] [srp-client] adding auto-start feature (#6140) This commit adds auto-start SRP client feature. Config option `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` adds support for this feature in SRP client allowing auto-start mode to be enabled or disabled. When enabled, the SRP client will monitor the Thread Network Data for SRP Server Service entries and automatically start and stop the client when an SRP server is detected. If multiple SRP servers are found, a random one will be selected. If the selected SRP server is no longer detected (not longer present in the Thread Network Data), the SRP client will be stopped and then it may switch to another SRP server (if available). This commit also adds support for the new feature in CLI and adds a new test `test_srp_auto_start_mode.py` covering the behavior of this feature. --- include/openthread/instance.h | 2 +- include/openthread/srp_client.h | 69 ++++++++ src/cli/README_SRP.md | 27 ++-- src/cli/README_SRP_CLIENT.md | 43 ++++- src/cli/cli_srp_client.cpp | 47 ++++++ src/cli/cli_srp_client.hpp | 4 + src/core/api/srp_client_api.cpp | 23 +++ src/core/config/srp_client.h | 25 +++ src/core/net/srp_client.cpp | 131 +++++++++++++-- src/core/net/srp_client.hpp | 99 +++++++++++- tests/scripts/thread-cert/Makefile.am | 2 + tests/scripts/thread-cert/node.py | 28 ++++ .../thread-cert/test_srp_auto_start_mode.py | 151 ++++++++++++++++++ 13 files changed, 619 insertions(+), 32 deletions(-) create mode 100755 tests/scripts/thread-cert/test_srp_auto_start_mode.py diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 9832c2dfb..9c206631a 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (73) +#define OPENTHREAD_API_VERSION (74) /** * @addtogroup api-instance diff --git a/include/openthread/srp_client.h b/include/openthread/srp_client.h index d0a94c54f..2f7b9df79 100644 --- a/include/openthread/srp_client.h +++ b/include/openthread/srp_client.h @@ -171,6 +171,21 @@ typedef void (*otSrpClientCallback)(otError aError, const otSrpClientService * aRemovedServices, void * aContext); +/** + * This function pointer type defines the callback used by SRP client to notify user when it is auto-started or stopped. + * + * This is only used when auto-start feature `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` is enabled. + * + * This callback is invoked when auto-start mode is enabled and the SRP client is either automatically started or + * stopped. + * + * @param[in] aSeverSockAddress A non-NULL pointer indicates SRP sever was started and pointer will give the + * selected server socket address. A NULL pointer indicates SRP sever was stopped. + * @param[in] aContext A pointer to an arbitrary context (provided when callback was registered). + * + */ +typedef void (*otSrpClientAutoStartCallback)(const otSockAddr *aServerSockAddr, void *aContext); + /** * This function starts the SRP client operation. * @@ -245,6 +260,60 @@ const otSockAddr *otSrpClientGetServerAddress(otInstance *aInstance); */ void otSrpClientSetCallback(otInstance *aInstance, otSrpClientCallback aCallback, void *aContext); +/** + * This function enables the auto-start mode. + * + * This is only available when auto-start feature `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` is enabled. + * + * Config option `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE` specifies the default auto-start mode (whether + * it is enabled or disabled at the start of OT stack). + * + * When auto-start is enabled, the SRP client will monitor the Thread Network Data for SRP Server Service entries + * and automatically start and stop the client when an SRP server is detected. + * + * If multiple SRP servers are found, a random one will be selected. If the selected SRP server is no longer + * detected (not longer present in the Thread Network Data), the SRP client will be stopped and then it may switch + * to another SRP server (if available). + * + * When the SRP client is explicitly started through a successful call to `otSrpClientStart()`, the given SRP server + * address in `otSrpClientStart()` will continue to be used regardless of the state of auto-start mode and whether the + * same SRP server address is discovered or not in the Thread Network Data. In this case, only an explicit + * `otSrpClientStop()` call will stop the client. + * + * @param[in] aInstance A pointer to the OpenThread instance. + * @param[in] aCallback A callback to notify when client is auto-started/stopped. Can be NULL if not needed. + * @param[in] aContext A context to be passed when invoking @p aCallback. + * + */ +void otSrpClientEnableAutoStartMode(otInstance *aInstance, otSrpClientAutoStartCallback aCallback, void *aContext); + +/** + * This function disables the auto-start mode. + * + * This is only available when auto-start feature `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` is enabled. + * + * Disabling the auto-start mode will not stop the client if it is already running but the client stops monitoring + * the Thread Network Data to verify that the selected SRP server is still present in it. + * + * Note that a call to `otSrpClientStop()` will also disable the auto-start mode. + * + * @param[in] aInstance A pointer to the OpenThread instance. + * + */ +void otSrpClientDisableAutoStartMode(otInstance *aInstance); + +/** + * This function indicates the current state of auto-start mode (enabled or disabled). + * + * This is only available when auto-start feature `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` is enabled. + * + * @param[in] aInstance A pointer to the OpenThread instance. + * + * @returns TRUE if the auto-start mode is enabled, FALSE otherwise. + * + */ +bool otSrpClientIsAutoStartModeEnabled(otInstance *aInstance); + /** * This function gets the lease interval used in SRP update requests. * diff --git a/src/cli/README_SRP.md b/src/cli/README_SRP.md index 6d928676e..08c0b6f63 100644 --- a/src/cli/README_SRP.md +++ b/src/cli/README_SRP.md @@ -57,16 +57,8 @@ fe80:0:0:0:a8cd:6e23:df3d:4193 Done > srp server enable Done -> netdata show -Prefixes: -Routes: -Services: -44970 5d c002 s 8400 -Done ``` -The SRP Server listening UDP port which is `c002`(`49154`) is included in the Server Data (listed by the `netdata show` command). - ### Start SRP Client Start the SRP Client node: @@ -100,12 +92,29 @@ Done Done > srp client service add my-service _ipps._tcp 12345 Done -srp client start fded:5114:8263:1fe1:68bc:ec03:c1ad:9325 49154 +> srp client autostart enable Done ``` +The last command enables the auto-start mode on the client which then monitors the network data to discover available SRP servers within the Thread network and automatically starts the client. + +Alternatively, the client can be started manually using the `srp client start`. + +The SRP Server listening UDP port (which is `c002`(`49154`) in the example below) can be found from the Server Data (listed by the `netdata show` command). + Make sure the SRP Server address & port are used for the `srp client start` command. +```bash +> netdata show +Prefixes: +Routes: +Services: +44970 5d c002 s 8400 +Done +srp client start fded:5114:8263:1fe1:68bc:ec03:c1ad:9325 49154 +Done +``` + ### Verify the service status Check if the host and service has been successfully registered on the client node: diff --git a/src/cli/README_SRP_CLIENT.md b/src/cli/README_SRP_CLIENT.md index adf6a106f..0baa1f561 100644 --- a/src/cli/README_SRP_CLIENT.md +++ b/src/cli/README_SRP_CLIENT.md @@ -5,6 +5,7 @@ Usage : `srp client [command] ...` - [help](#help) +- [autostart](#autostart) - [callback](#callback) - [host](#host) - [keyleaseinterval](#keyleaseinterval) @@ -12,6 +13,7 @@ Usage : `srp client [command] ...` - [server](#server) - [service](#service) - [start](#start) +- [state](#state) - [stop](#stop) ## Command Details @@ -24,6 +26,7 @@ Print SRP client help menu. ```bash > srp client help +autostart callback help host @@ -31,10 +34,36 @@ keyleaseinterval leaseinterval service start +state stop Done ``` +### autostart + +Usage `srp client autostart [enable|disable]` + +Enable/Disable auto start mode in SRP client. This command requires `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` feature to be enabled. + +Get the current autostart mode. + +```bash +> srp client autostart +Disabled +Done +``` + +Set the autostart mode. + +```bash +> srp client autostart enable +Done + +> srp client autostart +Enabled +Done +``` + ### callback Usage `srp client callback [enable|disable]` @@ -303,13 +332,25 @@ Done Usage: `srp client start ` -Start the SRP client with a given server IPv6 address and port number +Start the SRP client with a given server IPv6 address and port number. ```bash > srp client start fd00::d88a:618b:384d:e760 4724 Done ``` +### state + +Usage: `srp client state` + +Indicates the state of SRP client, i.e., whether it is enabled or disabled. + +```bash +> srp client state +Enabled +Done +``` + ### stop Usage: `srp client stop` diff --git a/src/cli/cli_srp_client.cpp b/src/cli/cli_srp_client.cpp index c57d82a91..db9ba1465 100644 --- a/src/cli/cli_srp_client.cpp +++ b/src/cli/cli_srp_client.cpp @@ -96,6 +96,39 @@ exit: return error; } +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + +otError SrpClient::ProcessAutoStart(uint8_t aArgsLength, char *aArgs[]) +{ + otError error = OT_ERROR_NONE; + + if (aArgsLength == 0) + { + mInterpreter.OutputEnabledDisabledStatus(otSrpClientIsAutoStartModeEnabled(mInterpreter.mInstance)); + ExitNow(); + } + + VerifyOrExit(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS); + + if (strcmp(aArgs[0], "enable") == 0) + { + otSrpClientEnableAutoStartMode(mInterpreter.mInstance, /* aCallback */ nullptr, /* aContext */ nullptr); + } + else if (strcmp(aArgs[0], "disable") == 0) + { + otSrpClientDisableAutoStartMode(mInterpreter.mInstance); + } + else + { + error = OT_ERROR_INVALID_COMMAND; + } + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + otError SrpClient::ProcessCallback(uint8_t aArgsLength, char *aArgs[]) { otError error = OT_ERROR_NONE; @@ -463,6 +496,20 @@ exit: return error; } +otError SrpClient::ProcessState(uint8_t aArgsLength, char *aArgs[]) +{ + OT_UNUSED_VARIABLE(aArgs); + + otError error = OT_ERROR_NONE; + + VerifyOrExit(aArgsLength == 0, error = OT_ERROR_INVALID_ARGS); + + mInterpreter.OutputEnabledDisabledStatus(otSrpClientIsRunning(mInterpreter.mInstance)); + +exit: + return error; +} + otError SrpClient::ProcessStop(uint8_t aArgsLength, char *aArgs[]) { OT_UNUSED_VARIABLE(aArgs); diff --git a/src/cli/cli_srp_client.hpp b/src/cli/cli_srp_client.hpp index 4ce70ed88..ef541c10e 100644 --- a/src/cli/cli_srp_client.hpp +++ b/src/cli/cli_srp_client.hpp @@ -100,6 +100,7 @@ private: otError (SrpClient::*mHandler)(uint8_t aArgsLength, char *aArgs[]); }; + otError ProcessAutoStart(uint8_t aArgsLength, char *aArgs[]); otError ProcessCallback(uint8_t aArgsLength, char *aArgs[]); otError ProcessHelp(uint8_t aArgsLength, char *aArgs[]); otError ProcessHost(uint8_t aArgsLength, char *aArgs[]); @@ -108,6 +109,7 @@ private: otError ProcessServer(uint8_t aArgsLength, char *aArgs[]); otError ProcessService(uint8_t aArgsLength, char *aArgs[]); otError ProcessStart(uint8_t aArgsLength, char *aArgs[]); + otError ProcessState(uint8_t aArgsLength, char *aArgs[]); otError ProcessStop(uint8_t aArgsLength, char *aArgs[]); void OutputHostInfo(uint8_t aIndentSize, const otSrpClientHostInfo &aHostInfo); @@ -125,6 +127,7 @@ private: const otSrpClientService * aRemovedServices); static constexpr Command sCommands[] = { + {"autostart", &SrpClient::ProcessAutoStart}, {"callback", &SrpClient::ProcessCallback}, {"help", &SrpClient::ProcessHelp}, {"host", &SrpClient::ProcessHost}, @@ -133,6 +136,7 @@ private: {"server", &SrpClient::ProcessServer}, {"service", &SrpClient::ProcessService}, {"start", &SrpClient::ProcessStart}, + {"state", &SrpClient::ProcessState}, {"stop", &SrpClient::ProcessStop}, }; diff --git a/src/core/api/srp_client_api.cpp b/src/core/api/srp_client_api.cpp index e9c88810e..0417d7587 100644 --- a/src/core/api/srp_client_api.cpp +++ b/src/core/api/srp_client_api.cpp @@ -78,6 +78,29 @@ void otSrpClientSetCallback(otInstance *aInstance, otSrpClientCallback aCallback instance.Get().SetCallback(aCallback, aContext); } +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE +void otSrpClientEnableAutoStartMode(otInstance *aInstance, otSrpClientAutoStartCallback aCallback, void *aContext) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().EnableAutoStartMode(aCallback, aContext); +} + +void otSrpClientDisableAutoStartMode(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().DisableAutoStartMode(); +} + +bool otSrpClientIsAutoStartModeEnabled(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().IsAutoStartModeEnabled(); +} +#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + uint32_t otSrpClientGetLeaseInterval(otInstance *aInstance) { Instance &instance = *static_cast(aInstance); diff --git a/src/core/config/srp_client.h b/src/core/config/srp_client.h index 09c7856e4..e25115177 100644 --- a/src/core/config/srp_client.h +++ b/src/core/config/srp_client.h @@ -45,6 +45,31 @@ #define OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + * + * Define to 1 to enable SRP Client auto-start feature and its APIs. + * + * When enabled, the SRP client can be configured to automatically start when it detects the presence of an SRP server + * (by monitoring the Thread Network Data for SRP Server Service entries). + * + */ +#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE +#define OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE 1 +#endif + +/** + * @def OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE + * + * Define the default mode (enabled or disabled) of auto-start mode. + * + * This config is applicable/used only when `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` is enabled. + * + */ +#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE +#define OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE 0 +#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 dcccd2593..5b471e872 100644 --- a/src/core/net/srp_client.cpp +++ b/src/core/net/srp_client.cpp @@ -33,8 +33,10 @@ #include "common/instance.hpp" #include "common/locator-getters.hpp" #include "common/logging.hpp" +#include "common/random.hpp" #include "common/settings.hpp" #include "common/string.hpp" +#include "thread/network_data_service.hpp" #if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE @@ -135,6 +137,10 @@ Client::Client(Instance &aInstance) , mState(kStateStopped) , mTxFailureRetryCount(0) , mShouldRemoveKeyLease(false) +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + , mAutoStartModeEnabled(kAutoStartDefaultMode) + , mAutoStartDidSelectServer(false) +#endif , mUpdateMessageId(0) , mRetryWaitInterval(kMinRetryWaitInterval) , mAcceptedLeaseInterval(0) @@ -143,6 +149,10 @@ Client::Client(Instance &aInstance) , mSocket(aInstance) , mCallback(nullptr) , mCallbackContext(nullptr) +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + , mAutoStartCallback(nullptr) + , mAutoStartContext(nullptr) +#endif , mDomainName(kDefaultDomainName) , mTimer(aInstance, Client::HandleTimer) { @@ -164,28 +174,33 @@ Client::Client(Instance &aInstance) static_assert(kRemoved == 7, "kRemoved value is not correct"); } -otError Client::Start(const Ip6::SockAddr &aServerSockAddr) +otError Client::Start(const Ip6::SockAddr &aServerSockAddr, Requester aRequester) { - otError error = OT_ERROR_NONE; + otError error; - if (GetState() != kStateStopped) - { - VerifyOrExit(aServerSockAddr == mSocket.GetPeerName(), error = OT_ERROR_BUSY); - ExitNow(); - } + VerifyOrExit(GetState() == kStateStopped, + error = (aServerSockAddr == GetServerAddress()) ? OT_ERROR_NONE : OT_ERROR_BUSY); SuccessOrExit(error = mSocket.Open(Client::HandleUdpReceive, this)); SuccessOrExit(error = mSocket.Connect(aServerSockAddr)); - otLogInfoSrp("[client] Starting, server %s", aServerSockAddr.ToString().AsCString()); + otLogInfoSrp("[client] %starting, server %s", (aRequester == kRequesterUser) ? "S" : "Auto-s", + aServerSockAddr.ToString().AsCString()); Resume(); +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + mAutoStartDidSelectServer = (aRequester == kRequesterAuto); + + VerifyOrExit((aRequester == kRequesterAuto) && (mAutoStartCallback != nullptr)); + mAutoStartCallback(&aServerSockAddr, mAutoStartContext); +#endif + exit: return error; } -void Client::Stop(void) +void Client::Stop(Requester aRequester) { // Change the state of host info and services so that they are // added/removed again once the client is started back. In the @@ -222,8 +237,18 @@ void Client::Stop(void) ResetRetryWaitInterval(); SetState(kStateStopped); +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + VerifyOrExit((aRequester == kRequesterAuto) && (mAutoStartCallback != nullptr)); + mAutoStartCallback(nullptr, mAutoStartContext); +#endif + exit: - return; +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + if (aRequester == kRequesterUser) + { + DisableAutoStartMode(); + } +#endif } void Client::SetCallback(Callback aCallback, void *aContext) @@ -267,14 +292,21 @@ void Client::Pause(void) void Client::HandleNotifierEvents(Events aEvents) { - VerifyOrExit(aEvents.Contains(kEventThreadRoleChanged)); - - if (Get().IsDisabled()) + if (aEvents.Contains(kEventThreadRoleChanged)) { - Stop(); - ExitNow(); + HandleRoleChanged(); } +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + if (aEvents.Contains(kEventThreadNetdataChanged)) + { + ProcessAutoStart(); + } +#endif +} + +void Client::HandleRoleChanged(void) +{ if (Get().IsAttached()) { VerifyOrExit(GetState() == kStatePaused); @@ -1428,6 +1460,75 @@ void Client::HandleTimer(void) } } +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + +void Client::EnableAutoStartMode(AutoStartCallback aCallback, void *aContext) +{ + mAutoStartCallback = aCallback; + mAutoStartContext = aContext; + + VerifyOrExit(!mAutoStartModeEnabled); + mAutoStartModeEnabled = true; + ProcessAutoStart(); + +exit: + return; +} + +void Client::ProcessAutoStart(void) +{ + uint16_t numServers = 0; + NetworkData::Service::SrpServer::Info selectedServer; + NetworkData::Service::SrpServer::Info server; + NetworkData::Service::Manager::Iterator iterator; + + VerifyOrExit(mAutoStartModeEnabled); + + // If the client is not running we check if there is any SRP sever + // info in Network Data and select one randomly and then start the + // client. If the client is already running with a server that was + // selected by the auto-start feature, we verify that the selected + // server is still present in the Network Data. + + VerifyOrExit(!IsRunning() || mAutoStartDidSelectServer); + + while (Get().GetNextSrpServerInfo(iterator, server) == OT_ERROR_NONE) + { + numServers++; + + // Choose a server randomly (with uniform distribution) from + // the list of servers. As we iterate through server entries, + // with probability `1/numServers`, we choose to switch the + // current selected server with the new entry. This approach + // results in a uniform/same probability of selection among + // all server entries. + + if ((numServers == 1) || (Random::NonCrypto::GetUint16InRange(0, numServers) == 0)) + { + selectedServer = server; + } + + if (IsRunning() && mAutoStartDidSelectServer && (GetServerAddress() == server.mSockAddr)) + { + ExitNow(); + } + } + + if (IsRunning()) + { + otLogInfoSrp("[client] Server %s is no longer present in net data", GetServerAddress().ToString().AsCString()); + Stop(kRequesterAuto); + } + + VerifyOrExit(numServers > 0); + IgnoreError(Start(selectedServer.mSockAddr, kRequesterAuto)); + +exit: + return; +} + +#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + const char *Client::ItemStateToString(ItemState aState) { static const char *const kItemStateStrings[] = { diff --git a/src/core/net/srp_client.hpp b/src/core/net/srp_client.hpp index 809dd9991..86c321c16 100644 --- a/src/core/net/srp_client.hpp +++ b/src/core/net/srp_client.hpp @@ -276,7 +276,7 @@ public: * @retval OT_ERROR_FAILED Failed to open/connect the client's UDP socket. * */ - otError Start(const Ip6::SockAddr &aServerSockAddr); + otError Start(const Ip6::SockAddr &aServerSockAddr) { return Start(aServerSockAddr, kRequesterUser); } /** * This method stops the SRP client operation. @@ -284,8 +284,63 @@ public: * This method stops any further interactions with the SRP server. Note that it does not remove or clear host info * and/or list of services. It marks all services to be added/removed again once the client is started again. * + * If `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` (auto-start feature) is enabled, a call to this method + * also disables the auto-start mode. + * */ - void Stop(void); + void Stop(void) { Stop(kRequesterUser); } + +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + /** + * This function pointer type defines the callback used by SRP client to notify user when it is auto-started or + * stopped. + * + */ + typedef otSrpClientAutoStartCallback AutoStartCallback; + + /** + * This method enables the auto-start mode. + * + * Config option `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE` specifies the default auto-start mode + * (whether it is enabled or disabled at the start of OT stack). + * + * When auto-start is enabled, the SRP client will monitor the Thread Network Data for SRP Server Service entries + * and automatically start and stop the client when an SRP server is detected. + * + * If multiple SRP servers are found, a random one will be selected. If the selected SRP server is no longer + * detected (not longer present in the Thread Network Data), the SRP client will be stopped and then it may switch + * to another SRP server (if available). + * + * When the SRP client is explicitly started through a successful call to `Start()`, the given SRP server address + * in `Start()` will continue to be used regardless of the state of auto-start mode and whether the same SRP + * server address is discovered or not in the Thread Network Data. In this case, only an explicit `Stop()` call + * will stop the client. + * + * @param[in] aCallback A callback to notify when client is auto-started/stopped. Can be `nullptr` if not needed. + * @param[in] aContext A context to be passed when invoking @p aCallback. + * + */ + void EnableAutoStartMode(AutoStartCallback aCallback, void *aContext); + + /** + * This method disables the auto-start mode. + * + * Disabling the auto-start mode will not stop the client if it is already running but the client stops monitoring + * the Thread Network Data to verify that the selected SRP server is still present in it. + * + * Note that a call to `Stop()` will also disable the auto-start mode. + * + */ + void DisableAutoStartMode(void) { mAutoStartModeEnabled = false; } + + /** + * This method indicates the current state of auto-start mode (enabled or disabled). + * + * @returns TRUE if the auto-start mode is enabled, FALSE otherwise. + * + */ + bool IsAutoStartModeEnabled(void) const { return mAutoStartModeEnabled; } +#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE /** * This method indicates whether the SRP client is running or not. @@ -623,6 +678,22 @@ private: kStateToRetry, // SRP update tx failed, waiting to retry. }; + enum : bool + { + kAutoStartDefaultMode = OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE, + }; + + // This enumeration type is used by the private `Start()` and + // `Stop()` methods to indicate whether it is being requested by the + // user or by the auto-start feature. + enum Requester + { + kRequesterUser, +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + kRequesterAuto, +#endif + }; + struct Info : public Clearable { enum : uint16_t @@ -636,9 +707,12 @@ private: Crypto::Ecdsa::P256::KeyPair mKeyPair; // The ECDSA key pair. }; + otError Start(const Ip6::SockAddr &aServerSockAddr, Requester aRequester); + void Stop(Requester aRequester); void Resume(void); void Pause(void); void HandleNotifierEvents(Events aEvents); + void HandleRoleChanged(void); void UpdateServiceStateToRemove(Service &aService); State GetState(void) const { return mState; } void SetState(State aState); @@ -671,6 +745,9 @@ private: bool ShouldRenewEarly(const Service &aService) const; static void HandleTimer(Timer &aTimer); void HandleTimer(void); +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + void ProcessAutoStart(void); +#endif #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_SRP == 1) static const char *StateToString(State aState); @@ -681,11 +758,15 @@ private: static const char kDefaultDomainName[]; - static_assert(kMaxTxFailureRetries < 128, "kMaxTxFailureRetries exceed the range of mTxFailureRetryCount (7-bit)"); + static_assert(kMaxTxFailureRetries < 16, "kMaxTxFailureRetries exceed the range of mTxFailureRetryCount (4-bit)"); State mState; - uint8_t mTxFailureRetryCount : 7; + uint8_t mTxFailureRetryCount : 4; bool mShouldRemoveKeyLease : 1; +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + bool mAutoStartModeEnabled : 1; + bool mAutoStartDidSelectServer : 1; +#endif uint16_t mUpdateMessageId; uint32_t mRetryWaitInterval; @@ -697,8 +778,14 @@ private: Ip6::Udp::Socket mSocket; - Callback mCallback; - void * mCallbackContext; + Callback mCallback; + void * mCallbackContext; + +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + AutoStartCallback mAutoStartCallback; + void * mAutoStartContext; +#endif + const char * mDomainName; HostInfo mHostInfo; LinkedList mServices; diff --git a/tests/scripts/thread-cert/Makefile.am b/tests/scripts/thread-cert/Makefile.am index fe9b35a90..4088a2e62 100644 --- a/tests/scripts/thread-cert/Makefile.am +++ b/tests/scripts/thread-cert/Makefile.am @@ -172,6 +172,7 @@ EXTRA_DIST = \ test_route_table.py \ test_router_reattach.py \ test_service.py \ + test_srp_auto_start_mode.py \ test_srp_lease.py \ test_srp_name_conflicts.py \ test_srp_register_single_service.py \ @@ -225,6 +226,7 @@ check_SCRIPTS = \ test_route_table.py \ test_router_reattach.py \ test_service.py \ + test_srp_auto_start_mode.py \ test_srp_lease.py \ test_srp_name_conflicts.py \ test_srp_register_single_service.py \ diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 9cec245db..1facf847a 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -928,6 +928,34 @@ class NodeImpl: self.send_command(f'srp client stop') self._expect_done() + def srp_client_get_state(self): + cmd = 'srp client state' + self.send_command(cmd) + return self._expect_command_output(cmd)[0] + + def srp_client_get_auto_start_mode(self): + cmd = 'srp client autostart' + self.send_command(cmd) + return self._expect_command_output(cmd)[0] + + def srp_client_enable_auto_start_mode(self): + self.send_command(f'srp client autostart enable') + self._expect_done() + + def srp_client_disable_auto_start_mode(self): + self.send_command(f'srp client autostart able') + self._expect_done() + + def srp_client_get_server_address(self): + cmd = 'srp client server address' + self.send_command(cmd) + return self._expect_command_output(cmd)[0] + + def srp_client_get_server_port(self): + cmd = 'srp client server port' + self.send_command(cmd) + return int(self._expect_command_output(cmd)[0]) + def srp_client_get_host_state(self): cmd = 'srp client host state' self.send_command(cmd) diff --git a/tests/scripts/thread-cert/test_srp_auto_start_mode.py b/tests/scripts/thread-cert/test_srp_auto_start_mode.py new file mode 100755 index 000000000..f0a9c140b --- /dev/null +++ b/tests/scripts/thread-cert/test_srp_auto_start_mode.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2021, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +import ipaddress +import unittest + +import command +import thread_cert + +# Test description: +# This test verifies SRP client auto-start functionality that SRP client can +# correctly discover and connect to SRP server. +# +# Topology: +# +# CLIENT (leader) -- SERVER1 (router) +# | +# | +# SERVER2 (router) +# + +CLIENT = 1 +SERVER1 = 2 +SERVER2 = 3 + + +class SrpAutoStartMode(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + SUPPORT_NCP = False + + TOPOLOGY = { + CLIENT: { + 'name': 'SRP_CLIENT', + 'masterkey': '00112233445566778899aabbccddeeff', + 'mode': 'rdn', + 'panid': 0xface + }, + SERVER1: { + 'name': 'SRP_SERVER1', + 'masterkey': '00112233445566778899aabbccddeeff', + 'mode': 'rdn', + 'panid': 0xface, + 'router_selection_jitter': 1 + }, + SERVER2: { + 'name': 'SRP_SERVER2', + 'masterkey': '00112233445566778899aabbccddeeff', + 'mode': 'rdn', + 'panid': 0xface, + 'router_selection_jitter': 1 + }, + } + + def test(self): + client = self.nodes[CLIENT] + server1 = self.nodes[SERVER1] + server2 = self.nodes[SERVER2] + + # + # 0. Start the server & client devices. + # + + client.srp_server_set_enabled(False) + client.start() + self.simulator.go(5) + self.assertEqual(client.get_state(), 'leader') + + server1.srp_server_set_enabled(True) + server2.srp_server_set_enabled(False) + server1.start() + server2.start() + self.simulator.go(5) + self.assertEqual(server1.get_state(), 'router') + self.assertEqual(server2.get_state(), 'router') + + # + # 1. Enable auto start mode on client and check that server1 is used. + # + + self.assertEqual(client.srp_client_get_state(), 'Disabled') + client.srp_client_enable_auto_start_mode() + self.assertEqual(client.srp_client_get_auto_start_mode(), 'Enabled') + self.simulator.go(2) + + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertEqual(client.srp_client_get_server_port(), client.get_srp_server_port()) + + # + # 2. Disable server1 and check client is stopped/disabled. + # + + server1.srp_server_set_enabled(False) + self.simulator.go(5) + self.assertEqual(client.srp_client_get_state(), 'Disabled') + + # + # 3. Enable server2 and check client starts again. + # + + server2.srp_server_set_enabled(True) + self.simulator.go(5) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + prev_port = client.srp_client_get_server_port() + + # + # 4. Enable both servers and check client stays with server2. + # + + server1.srp_server_set_enabled(True) + self.simulator.go(2) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertEqual(client.srp_client_get_server_port(), prev_port) + + # + # 5. Disable server2 and check client switches to server1. + # + + server2.srp_server_set_enabled(False) + self.simulator.go(5) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertNotEqual(client.srp_client_get_server_port(), prev_port) + + +if __name__ == '__main__': + unittest.main()