diff --git a/src/core/net/srp_client.cpp b/src/core/net/srp_client.cpp index 7eeb2db8d..0667d3096 100644 --- a/src/core/net/srp_client.cpp +++ b/src/core/net/srp_client.cpp @@ -35,12 +35,9 @@ #include "common/debug.hpp" #include "common/instance.hpp" #include "common/locator_getters.hpp" - -#include "common/numeric_limits.hpp" #include "common/random.hpp" #include "common/settings.hpp" #include "common/string.hpp" -#include "thread/network_data_service.hpp" /** * @file @@ -155,6 +152,83 @@ bool Client::Service::Matches(const Service &aOther) const return (strcmp(GetName(), aOther.GetName()) == 0) && (strcmp(GetInstanceName(), aOther.GetInstanceName()) == 0); } +//--------------------------------------------------------------------- +// Client::AutoStart + +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + +Client::AutoStart::AutoStart(void) +{ + Clear(); + mState = kDefaultMode ? kSelectedNone : kDisabled; +} + +bool Client::AutoStart::HasSelectedServer(void) const +{ + bool hasSelected = false; + + switch (mState) + { + case kDisabled: + case kSelectedNone: + break; + + case kSelectedUnicastPreferred: + case kSelectedUnicast: + case kSelectedAnycast: + hasSelected = true; + break; + } + + return hasSelected; +} + +void Client::AutoStart::SetState(State aState) +{ + if (mState != aState) + { + LogInfo("AutoStartState %s -> %s", StateToString(mState), StateToString(aState)); + mState = aState; + } +} + +void Client::AutoStart::SetCallback(AutoStartCallback aCallback, void *aContext) +{ + mCallback = aCallback; + mContext = aContext; +} + +void Client::AutoStart::InvokeCallback(const Ip6::SockAddr *aServerSockAddr) const +{ + if (mCallback != nullptr) + { + mCallback(aServerSockAddr, mContext); + } +} + +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) +const char *Client::AutoStart::StateToString(State aState) +{ + static const char *const kStateStrings[] = { + "Disabled", // (0) kDisabled + "Idle", // (1) kSelectedNone + "Unicast-prf", // (2) kSelectedUnicastPreferred + "Anycast", // (3) kSelectedAnycast + "Unicast", // (4) kSelectedUnicast + }; + + static_assert(0 == kDisabled, "kDisabled value is incorrect"); + static_assert(1 == kSelectedNone, "kSelectedNone value is incorrect"); + static_assert(2 == kSelectedUnicastPreferred, "kSelectedUnicastPreferred value is incorrect"); + static_assert(3 == kSelectedAnycast, "kSelectedAnycast value is incorrect"); + static_assert(4 == kSelectedUnicast, "kSelectedUnicast value is incorrect"); + + return kStateStrings[aState]; +} +#endif + +#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + //--------------------------------------------------------------------- // Client @@ -165,11 +239,6 @@ Client::Client(Instance &aInstance) , mState(kStateStopped) , mTxFailureRetryCount(0) , mShouldRemoveKeyLease(false) -#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE - , mAutoStartModeEnabled(kAutoStartDefaultMode) - , mAutoStartDidSelectServer(false) - , mAutoStartIsUsingAnycastAddress(false) -#endif #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE , mServiceKeyRecordEnabled(false) #endif @@ -181,14 +250,6 @@ Client::Client(Instance &aInstance) , mSocket(aInstance) , mCallback(nullptr) , mCallbackContext(nullptr) -#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE - , 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) { @@ -226,18 +287,12 @@ Error Client::Start(const Ip6::SockAddr &aServerSockAddr, Requester aRequester) Resume(); #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE - mAutoStartDidSelectServer = (aRequester == kRequesterAuto); - - if (mAutoStartDidSelectServer) + if (aRequester == kRequesterAuto) { #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE Get().UpdateDefaultConfigAddress(); #endif - - if (mAutoStartCallback != nullptr) - { - mAutoStartCallback(&aServerSockAddr, mAutoStartContext); - } + mAutoStart.InvokeCallback(&aServerSockAddr); } #endif @@ -290,14 +345,11 @@ void Client::Stop(Requester aRequester, StopMode aMode) #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE - mTimoutFailureCount = 0; + mAutoStart.ResetTimoutFailureCount(); #endif - - mAutoStartDidSelectServer = false; - - if ((aRequester == kRequesterAuto) && (mAutoStartCallback != nullptr)) + if (aRequester == kRequesterAuto) { - mAutoStartCallback(nullptr, mAutoStartContext); + mAutoStart.InvokeCallback(nullptr); } #endif @@ -611,24 +663,29 @@ void Client::ChangeHostAndServiceStates(const ItemState *aNewStates) } #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE - if (mAutoStartModeEnabled && mAutoStartDidSelectServer && (oldHostState != kRegistered) && - (mHostInfo.GetState() == kRegistered)) + if ((oldHostState != kRegistered) && (mHostInfo.GetState() == kRegistered)) { - if (mAutoStartIsUsingAnycastAddress) - { - IgnoreError(Get().Delete()); - } - else - { - Settings::SrpClientInfo info; + Settings::SrpClientInfo info; + switch (mAutoStart.GetState()) + { + case AutoStart::kDisabled: + case AutoStart::kSelectedNone: + break; + + case AutoStart::kSelectedUnicastPreferred: + case AutoStart::kSelectedUnicast: info.SetServerAddress(GetServerAddress().GetAddress()); info.SetServerPort(GetServerAddress().GetPort()); - IgnoreError(Get().Save(info)); + break; + + case AutoStart::kSelectedAnycast: + IgnoreError(Get().Delete()); + break; } } -#endif +#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE } void Client::InvokeCallback(Error aError) const @@ -1211,7 +1268,7 @@ void Client::ProcessResponse(Message &aMessage) LogInfo("Received response"); #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE - mTimoutFailureCount = 0; + mAutoStart.ResetTimoutFailureCount(); #endif error = Dns::Header::ResponseCodeToError(header.GetResponseCode()); @@ -1609,12 +1666,9 @@ void Client::HandleTimer(void) // callback. It works correctly due to the guard check at the // top of `SelectNextServer()`. - if (mTimoutFailureCount < NumericLimits::kMax) - { - mTimoutFailureCount++; - } + mAutoStart.IncrementTimoutFailureCount(); - if (mTimoutFailureCount >= kMaxTimeoutFailuresToSwitchServer) + if (mAutoStart.GetTimoutFailureCount() >= kMaxTimeoutFailuresToSwitchServer) { SelectNextServer(kDisallowSwitchOnRegisteredHost); } @@ -1631,11 +1685,11 @@ void Client::HandleTimer(void) void Client::EnableAutoStartMode(AutoStartCallback aCallback, void *aContext) { - mAutoStartCallback = aCallback; - mAutoStartContext = aContext; + mAutoStart.SetCallback(aCallback, aContext); - VerifyOrExit(!mAutoStartModeEnabled); - mAutoStartModeEnabled = true; + VerifyOrExit(mAutoStart.GetState() == AutoStart::kDisabled); + + mAutoStart.SetState(AutoStart::kSelectedNone); ProcessAutoStart(); exit: @@ -1644,130 +1698,181 @@ exit: void Client::ProcessAutoStart(void) { - Ip6::SockAddr serverSockAddr; - bool serverIsAnycast = false; - NetworkData::Service::DnsSrpAnycast::Info anycastInfo; -#if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE - Settings::SrpClientInfo savedInfo; - bool hasSavedServerInfo = false; -#endif + Ip6::SockAddr serverSockAddr; + DnsSrpAnycast::Info anycastInfo; + DnsSrpUnicast::Info unicastInfo; + bool shouldRestart = false; - VerifyOrExit(mAutoStartModeEnabled); + // If auto start mode is enabled, we check the Network Data entries + // to discover and select the preferred SRP server to register with. + // If we currently have a selected server, we ensure that it is + // still present in the Network Data and is still the preferred one. + + VerifyOrExit(mAutoStart.GetState() != AutoStart::kDisabled); + + // If SRP client is running, we check to make sure that auto-start + // did select the current server, and server was not specified by + // user directly. + + if (IsRunning()) + { + VerifyOrExit(mAutoStart.GetState() != AutoStart::kSelectedNone); + } + + // There are three types of entries in Network Data: + // + // 1) Preferred unicast entries with address included in service data. + // 2) Anycast entries (each having a seq number). + // 3) Unicast entries with address info included in server data. serverSockAddr.Clear(); - // If the SRP client is not running and auto start mode is - // enabled, we check if we can find any SRP server info in the - // Thread Network Data. If it is already running and the server - // was chosen by the auto-start feature, then we ensure that the - // selected server is still present in the Network Data. - // - // Two types of "DNS/SRP Service" entries can be present in - // Network Data, "DNS/SRP Service Anycast Address" model and - // "DNS/SRP Service Unicast" model. The Anycast entries are - // preferred over the Unicast entries. + if (SelectUnicastEntry(DnsSrpUnicast::kFromServiceData, unicastInfo) == kErrorNone) + { + mAutoStart.SetState(AutoStart::kSelectedUnicastPreferred); + serverSockAddr = unicastInfo.mSockAddr; + } + else if (Get().FindPreferredDnsSrpAnycastInfo(anycastInfo) == kErrorNone) + { + serverSockAddr.SetAddress(anycastInfo.mAnycastAddress); + serverSockAddr.SetPort(kAnycastServerPort); - VerifyOrExit(!IsRunning() || mAutoStartDidSelectServer); + // We check if we are selecting an anycast entry for first + // time, or if the seq number has changed. Even if the + // anycast address remains the same as before, on a seq + // number change, the client still needs to restart to + // re-register its info. - // Now `IsRunning()` implies `mAutoStartDidSelectServer`. + if ((mAutoStart.GetState() != AutoStart::kSelectedAnycast) || + (mAutoStart.GetAnycastSeqNum() != anycastInfo.mSequenceNumber)) + { + shouldRestart = true; + mAutoStart.SetAnycastSeqNum(anycastInfo.mSequenceNumber); + } + mAutoStart.SetState(AutoStart::kSelectedAnycast); + } + else if (SelectUnicastEntry(DnsSrpUnicast::kFromServerData, unicastInfo) == kErrorNone) + { + mAutoStart.SetState(AutoStart::kSelectedUnicast); + serverSockAddr = unicastInfo.mSockAddr; + } + + if (IsRunning()) + { + VerifyOrExit((GetServerAddress() != serverSockAddr) || shouldRestart); + Stop(kRequesterAuto, kResetRetryInterval); + } + + if (!serverSockAddr.GetAddress().IsUnspecified()) + { + IgnoreError(Start(serverSockAddr, kRequesterAuto)); + } + else + { + mAutoStart.SetState(AutoStart::kSelectedNone); + } + +exit: + return; +} + +Error Client::SelectUnicastEntry(DnsSrpUnicast::Origin aOrigin, DnsSrpUnicast::Info &aInfo) const +{ + Error error = kErrorNotFound; + DnsSrpUnicast::Info unicastInfo; + NetworkData::Service::Manager::Iterator iterator; + uint16_t numServers = 0; #if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE + Settings::SrpClientInfo savedInfo; + bool hasSavedServerInfo = false; + if (!IsRunning()) { hasSavedServerInfo = (Get().Read(savedInfo) == kErrorNone); } #endif - if (Get().FindPreferredDnsSrpAnycastInfo(anycastInfo) == kErrorNone) + while (Get().GetNextDnsSrpUnicastInfo(iterator, unicastInfo) == kErrorNone) { - if (IsRunning() && mAutoStartIsUsingAnycastAddress && (mServerSequenceNumber == anycastInfo.mSequenceNumber) && - (GetServerAddress().GetAddress() == anycastInfo.mAnycastAddress)) + if (unicastInfo.mOrigin != aOrigin) { - // Client is already using the same anycast address. + continue; + } + + if (mAutoStart.HasSelectedServer() && (GetServerAddress() == unicastInfo.mSockAddr)) + { + aInfo = unicastInfo; + error = kErrorNone; ExitNow(); } - LogInfo("Found anycast server %d", anycastInfo.mSequenceNumber); - - serverSockAddr.SetAddress(anycastInfo.mAnycastAddress); - serverSockAddr.SetPort(kAnycastServerPort); - mServerSequenceNumber = anycastInfo.mSequenceNumber; - serverIsAnycast = true; - } - else - { - uint16_t numServers = 0; - NetworkData::Service::DnsSrpUnicast::Info unicastInfo; - NetworkData::Service::Manager::Iterator iterator; - - while (Get().GetNextDnsSrpUnicastInfo(iterator, unicastInfo) == kErrorNone) - { - if (IsRunning() && !mAutoStartIsUsingAnycastAddress && (GetServerAddress() == unicastInfo.mSockAddr)) - { - ExitNow(); - } - #if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE - if (hasSavedServerInfo && (unicastInfo.mSockAddr.GetAddress() == savedInfo.GetServerAddress()) && - (unicastInfo.mSockAddr.GetPort() == savedInfo.GetServerPort())) - { - // Stop the search if we see a match for the previously - // saved server info in the network data entries. + if (hasSavedServerInfo && (unicastInfo.mSockAddr.GetAddress() == savedInfo.GetServerAddress()) && + (unicastInfo.mSockAddr.GetPort() == savedInfo.GetServerPort())) + { + // Stop the search if we see a match for the previously + // saved server info in the network data entries. - serverSockAddr = unicastInfo.mSockAddr; - serverIsAnycast = false; - break; - } + aInfo = unicastInfo; + error = kErrorNone; + ExitNow(); + } #endif + numServers++; - 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. - // 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)) - { - serverSockAddr = unicastInfo.mSockAddr; - serverIsAnycast = false; - } + if ((numServers == 1) || (Random::NonCrypto::GetUint16InRange(0, numServers) == 0)) + { + aInfo = unicastInfo; + error = kErrorNone; } } - if (IsRunning()) - { - Stop(kRequesterAuto, kResetRetryInterval); - } - - VerifyOrExit(!serverSockAddr.GetAddress().IsUnspecified()); - - mAutoStartIsUsingAnycastAddress = serverIsAnycast; - IgnoreError(Start(serverSockAddr, kRequesterAuto)); - exit: - return; + return error; } #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE void Client::SelectNextServer(bool aDisallowSwitchOnRegisteredHost) { - // This method tries to find the next server info entry in the + // This method tries to find the next unicast 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; + Ip6::SockAddr serverSockAddr; + bool selectNext = false; + DnsSrpUnicast::Origin origin = DnsSrpUnicast::kFromServiceData; serverSockAddr.Clear(); // Ensure that client is running, auto-start is enabled and - // auto-start selected the server. + // auto-start selected the server and it is a unicast entry. - VerifyOrExit(IsRunning() && mAutoStartModeEnabled && mAutoStartDidSelectServer); + VerifyOrExit(IsRunning()); + + switch (mAutoStart.GetState()) + { + case AutoStart::kSelectedUnicastPreferred: + origin = DnsSrpUnicast::kFromServiceData; + break; + + case AutoStart::kSelectedUnicast: + origin = DnsSrpUnicast::kFromServerData; + break; + + case AutoStart::kSelectedAnycast: + case AutoStart::kDisabled: + case AutoStart::kSelectedNone: + ExitNow(); + } if (aDisallowSwitchOnRegisteredHost) { @@ -1782,36 +1887,19 @@ void Client::SelectNextServer(bool aDisallowSwitchOnRegisteredHost) 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(); + DnsSrpUnicast::Info unicastInfo; + NetworkData::Service::Manager::Iterator iterator; while (Get().GetNextDnsSrpUnicastInfo(iterator, unicastInfo) == kErrorNone) { + if (unicastInfo.mOrigin != origin) + { + continue; + } + if (selectNext) { - serverSockAddr = unicastInfo.mSockAddr; - mAutoStartIsUsingAnycastAddress = false; + serverSockAddr = unicastInfo.mSockAddr; ExitNow(); } diff --git a/src/core/net/srp_client.hpp b/src/core/net/srp_client.hpp index ebec59d06..80b381ffd 100644 --- a/src/core/net/srp_client.hpp +++ b/src/core/net/srp_client.hpp @@ -43,11 +43,13 @@ #include "common/message.hpp" #include "common/non_copyable.hpp" #include "common/notifier.hpp" +#include "common/numeric_limits.hpp" #include "common/timer.hpp" #include "crypto/ecdsa.hpp" #include "net/dns_types.hpp" #include "net/ip6.hpp" #include "net/udp6.hpp" +#include "thread/network_data_service.hpp" /** * @file @@ -69,6 +71,9 @@ class Client : public InstanceLocator, private NonCopyable { friend class ot::Notifier; + using DnsSrpUnicast = NetworkData::Service::DnsSrpUnicast; + using DnsSrpAnycast = NetworkData::Service::DnsSrpAnycast; + public: /** * This enumeration types represents an SRP client item (service or host info) state. @@ -355,7 +360,7 @@ public: * Note that a call to `Stop()` will also disable the auto-start mode. * */ - void DisableAutoStartMode(void) { mAutoStartModeEnabled = false; } + void DisableAutoStartMode(void) { mAutoStart.SetState(AutoStart::kDisabled); } /** * This method indicates the current state of auto-start mode (enabled or disabled). @@ -363,7 +368,7 @@ public: * @returns TRUE if the auto-start mode is enabled, FALSE otherwise. * */ - bool IsAutoStartModeEnabled(void) const { return mAutoStartModeEnabled; } + bool IsAutoStartModeEnabled(void) const { return mAutoStart.GetState() != AutoStart::kDisabled; } /** * This method indicates whether or not the current SRP server's address is selected by auto-start. @@ -371,7 +376,7 @@ public: * @returns TRUE if the SRP server's address is selected by auto-start, FALSE otherwise. * */ - bool IsServerSelectedByAutoStart(void) const { return mAutoStartDidSelectServer; } + bool IsServerSelectedByAutoStart(void) const { return mAutoStart.HasSelectedServer(); } #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE /** @@ -790,6 +795,54 @@ private: kKeepRetryInterval, }; +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + class AutoStart : Clearable + { + public: + enum State : uint8_t{ + kDisabled, // AutoStart is disabled. + kSelectedNone, // AutoStart is enabled but not yet selected any servers. + kSelectedUnicastPreferred, // AutoStart selected a preferred unicast entry (address in service data). + kSelectedAnycast, // AutoStart selected an anycast entry with `mAnycastSeqNum`. + kSelectedUnicast, // AutoStart selected a unicast entry (address in server data). + }; + + AutoStart(void); + bool HasSelectedServer(void) const; + State GetState(void) const { return mState; } + void SetState(State aState); + uint8_t GetAnycastSeqNum(void) const { return mAnycastSeqNum; } + void SetAnycastSeqNum(uint8_t aAnycastSeqNum) { mAnycastSeqNum = aAnycastSeqNum; } + void SetCallback(AutoStartCallback aCallback, void *aContext); + void InvokeCallback(const Ip6::SockAddr *aServerSockAddr) const; + +#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE + uint8_t GetTimoutFailureCount(void) const { return mTimoutFailureCount; } + void ResetTimoutFailureCount(void) { mTimoutFailureCount = 0; } + void IncrementTimoutFailureCount(void) + { + if (mTimoutFailureCount < NumericLimits::kMax) + { + mTimoutFailureCount++; + } + } +#endif + + private: + static constexpr bool kDefaultMode = OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE; + + static const char *StateToString(State aState); + + AutoStartCallback mCallback; + void * mContext; + State mState; + uint8_t mAnycastSeqNum; +#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE + uint8_t mTimoutFailureCount; // Number of no-response timeout failures with the currently selected server. +#endif + }; +#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + struct Info : public Clearable { static constexpr uint16_t kUnknownOffset = 0; // Unknown offset value (used when offset is not yet set). @@ -839,7 +892,8 @@ private: static void HandleTimer(Timer &aTimer); void HandleTimer(void); #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE - void ProcessAutoStart(void); + void ProcessAutoStart(void); + Error SelectUnicastEntry(DnsSrpUnicast::Origin aOrigin, DnsSrpUnicast::Info &aInfo) const; #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE void SelectNextServer(bool aDisallowSwitchOnRegisteredHost); #endif @@ -859,11 +913,6 @@ private: State mState; uint8_t mTxFailureRetryCount : 4; bool mShouldRemoveKeyLease : 1; -#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE - bool mAutoStartModeEnabled : 1; - bool mAutoStartDidSelectServer : 1; - bool mAutoStartIsUsingAnycastAddress : 1; -#endif #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE bool mServiceKeyRecordEnabled : 1; #endif @@ -878,22 +927,15 @@ private: Ip6::Udp::Socket mSocket; - Callback mCallback; - void * mCallbackContext; - -#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE - AutoStartCallback mAutoStartCallback; - void * mAutoStartContext; - uint8_t mServerSequenceNumber; -#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE - uint8_t mTimoutFailureCount; -#endif -#endif - + Callback mCallback; + void * mCallbackContext; const char * mDomainName; HostInfo mHostInfo; LinkedList mServices; TimerMilli mTimer; +#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE + AutoStart mAutoStart; +#endif }; } // namespace Srp diff --git a/tests/scripts/thread-cert/test_srp_auto_start_mode.py b/tests/scripts/thread-cert/test_srp_auto_start_mode.py index 2b18264e5..9fc4c09b5 100755 --- a/tests/scripts/thread-cert/test_srp_auto_start_mode.py +++ b/tests/scripts/thread-cert/test_srp_auto_start_mode.py @@ -31,23 +31,22 @@ import ipaddress import unittest import command +import config import thread_cert # Test description: # This test verifies SRP client auto-start functionality that SRP client can -# correctly discover and connect to SRP server. +# correctly discovers and connects to SRP server. # # Topology: # -# CLIENT (leader) -- SERVER1 (router) -# | -# | -# SERVER2 (router) +# Four routers, one acting as SRP client, others as SRP server. # CLIENT = 1 SERVER1 = 2 SERVER2 = 3 +SERVER3 = 4 class SrpAutoStartMode(thread_cert.TestCase): @@ -57,17 +56,18 @@ class SrpAutoStartMode(thread_cert.TestCase): TOPOLOGY = { CLIENT: { 'name': 'SRP_CLIENT', - 'networkkey': '00112233445566778899aabbccddeeff', 'mode': 'rdn', }, SERVER1: { 'name': 'SRP_SERVER1', - 'networkkey': '00112233445566778899aabbccddeeff', 'mode': 'rdn', }, SERVER2: { 'name': 'SRP_SERVER2', - 'networkkey': '00112233445566778899aabbccddeeff', + 'mode': 'rdn', + }, + SERVER3: { + 'name': 'SRP_SERVER3', 'mode': 'rdn', }, } @@ -76,27 +76,38 @@ class SrpAutoStartMode(thread_cert.TestCase): client = self.nodes[CLIENT] server1 = self.nodes[SERVER1] server2 = self.nodes[SERVER2] + server3 = self.nodes[SERVER3] - # - # 0. Start the server & client devices. - # + #------------------------------------------------------------------- + # Form the network. 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() + server3.start() self.simulator.go(5) self.assertEqual(server1.get_state(), 'router') self.assertEqual(server2.get_state(), 'router') + self.assertEqual(server3.get_state(), 'router') - # - # 1. Enable auto start mode on client and check that server1 is used. - # + server1_mleid = server1.get_mleid() + server2_mleid = server2.get_mleid() + server3_mleid = server3.get_mleid() + anycast_port = 53 + + #------------------------------------------------------------------- + # Enable server1 with unicast address mode + + server1.srp_server_set_addr_mode('unicast') + server1.srp_server_set_enabled(True) + self.simulator.go(5) + + #------------------------------------------------------------------- + # Enable auto start mode on client and check that server1 is selected self.assertEqual(client.srp_client_get_state(), 'Disabled') client.srp_client_enable_auto_start_mode() @@ -104,42 +115,174 @@ class SrpAutoStartMode(thread_cert.TestCase): self.simulator.go(2) self.assertEqual(client.srp_client_get_state(), 'Enabled') - self.assertTrue(server1.has_ipaddr(client.srp_client_get_server_address())) + self.assertEqual(client.srp_client_get_server_address(), server1_mleid) - # - # 2. Disable server1 and check client is stopped/disabled. - # + #------------------------------------------------------------------- + # 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. - # + #------------------------------------------------------------------- + # Enable server2 with unicast address mode and check client starts + # again. + server1.srp_server_set_addr_mode('unicast') server2.srp_server_set_enabled(True) self.simulator.go(5) self.assertEqual(client.srp_client_get_state(), 'Enabled') - server2_address = client.srp_client_get_server_address() + self.assertEqual(client.srp_client_get_server_address(), server2_mleid) - # - # 4. Enable both servers and check client stays with server2. - # + #------------------------------------------------------------------- + # Enable server1 and check that client stays with server2 server1.srp_server_set_enabled(True) self.simulator.go(5) self.assertEqual(client.srp_client_get_state(), 'Enabled') - self.assertEqual(client.srp_client_get_server_address(), server2_address) + self.assertEqual(client.srp_client_get_server_address(), server2_mleid) - # - # 5. Disable server2 and check client switches to server1. - # + #------------------------------------------------------------------- + # 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_address(), server2_address) + self.assertEqual(client.srp_client_get_server_address(), server1_mleid) + + #------------------------------------------------------------------- + # Enable server2 with anycast mode seq-num 1, and check that client + # switched to it. + + server2.srp_server_set_addr_mode('anycast') + server2.srp_server_set_anycast_seq_num(1) + server2.srp_server_set_enabled(True) + self.simulator.go(5) + server2_alocs = server2.get_ip6_address(config.ADDRESS_TYPE.ALOC) + self.assertEqual(server2.srp_server_get_anycast_seq_num(), 1) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertIn(client.srp_client_get_server_address(), server2_alocs) + self.assertEqual(client.srp_client_get_server_port(), anycast_port) + + #------------------------------------------------------------------- + # Enable server3 with anycast mode seq-num 2, and check that client + # switched to it since seq number is higher. + + server3.srp_server_set_addr_mode('anycast') + server3.srp_server_set_anycast_seq_num(2) + server3.srp_server_set_enabled(True) + self.simulator.go(5) + server3_alocs = server3.get_ip6_address(config.ADDRESS_TYPE.ALOC) + self.assertEqual(server3.srp_server_get_anycast_seq_num(), 2) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertIn(client.srp_client_get_server_address(), server3_alocs) + self.assertEqual(client.srp_client_get_server_port(), anycast_port) + + #------------------------------------------------------------------- + # Disable server3 and check that client goes back to server2. + + server3.srp_server_set_enabled(False) + self.simulator.go(5) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertIn(client.srp_client_get_server_address(), server2_alocs) + self.assertEqual(client.srp_client_get_server_port(), anycast_port) + + #------------------------------------------------------------------- + # Enable server3 with anycast mode seq-num 0 (which is smaller than + # server2 seq-num 1) and check that client stays with server2. + + server3.srp_server_set_anycast_seq_num(0) + server3.srp_server_set_enabled(True) + self.simulator.go(5) + self.assertEqual(server3.srp_server_get_anycast_seq_num(), 0) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertIn(client.srp_client_get_server_address(), server2_alocs) + self.assertEqual(client.srp_client_get_server_port(), anycast_port) + + #------------------------------------------------------------------- + # Disable server2 and check that client goes back to server3. + + server2.srp_server_set_enabled(False) + self.simulator.go(5) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + server3_alocs = server3.get_ip6_address(config.ADDRESS_TYPE.ALOC) + self.assertIn(client.srp_client_get_server_address(), server3_alocs) + self.assertEqual(client.srp_client_get_server_port(), anycast_port) + + #------------------------------------------------------------------- + # Disable server3 and check that client goes back to server1 with + # unicast address. + + server3.srp_server_set_enabled(False) + self.simulator.go(5) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertEqual(client.srp_client_get_server_address(), server1_mleid) + + #------------------------------------------------------------------- + # Enable server2 with anycast mode seq-num 5, and check that client + # switched to it. + + server2.srp_server_set_addr_mode('anycast') + server2.srp_server_set_anycast_seq_num(5) + server2.srp_server_set_enabled(True) + self.simulator.go(5) + server2_alocs = server2.get_ip6_address(config.ADDRESS_TYPE.ALOC) + self.assertEqual(server2.srp_server_get_anycast_seq_num(), 5) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertIn(client.srp_client_get_server_address(), server2_alocs) + self.assertEqual(client.srp_client_get_server_port(), anycast_port) + + #------------------------------------------------------------------- + # Publish an entry on server3 with specific unicast address + # This entry should be now preferred over anycast of server2. + + unicast_addr3 = 'fd00:0:0:0:0:3333:beef:cafe' + unicast_port3 = 1234 + server3.netdata_publish_dnssrp_unicast(unicast_addr3, unicast_port3) + self.simulator.go(65) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertEqual(client.srp_client_get_server_address(), unicast_addr3) + self.assertEqual(client.srp_client_get_server_port(), unicast_port3) + + #------------------------------------------------------------------- + # Publish an entry on server1 with specific unicast address + # Client should still stay with server3 which it originally selected. + + unicast_addr1 = 'fd00:0:0:0:0:2222:beef:cafe' + unicast_port1 = 10203 + server1.srp_server_set_enabled(False) + server1.netdata_publish_dnssrp_unicast(unicast_addr1, unicast_port1) + self.simulator.go(65) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertEqual(client.srp_client_get_server_address(), unicast_addr3) + self.assertEqual(client.srp_client_get_server_port(), unicast_port3) + + #------------------------------------------------------------------- + # Unpublish the entry on server3. Now client should switch to entry + # from server1. + + server3.netdata_unpublish_dnssrp() + self.simulator.go(65) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertEqual(client.srp_client_get_server_address(), unicast_addr1) + self.assertEqual(client.srp_client_get_server_port(), unicast_port1) + + #------------------------------------------------------------------- + # Unpublish the entry on server1 and check client goes back to anycast + # entry from server2. + + server1.netdata_unpublish_dnssrp() + self.simulator.go(65) + self.assertEqual(client.srp_client_get_state(), 'Enabled') + self.assertIn(client.srp_client_get_server_address(), server2_alocs) + self.assertEqual(client.srp_client_get_server_port(), anycast_port) + + #------------------------------------------------------------------- + # Finally disable server2, and check that client is disabled. + + server2.srp_server_set_enabled(False) + self.simulator.go(5) + self.assertEqual(client.srp_client_get_state(), 'Disabled') if __name__ == '__main__':