diff --git a/src/core/net/srp_client.cpp b/src/core/net/srp_client.cpp index a65fe9251..ee8f0403a 100644 --- a/src/core/net/srp_client.cpp +++ b/src/core/net/srp_client.cpp @@ -335,6 +335,8 @@ void Client::Stop(Requester aRequester, StopMode aMode) VerifyOrExit(GetState() != kStateStopped); + mSingleServiceMode.Disable(); + // State changes: // kAdding -> kToRefresh // kRefreshing -> kToRefresh @@ -403,6 +405,8 @@ void Client::Pause(void) /* (7) kRemoved -> */ kRemoved, }; + mSingleServiceMode.Disable(); + // State changes: // kAdding -> kToRefresh // kRefreshing -> kToRefresh @@ -710,6 +714,11 @@ void Client::ChangeHostAndServiceStates(const ItemState *aNewStates) for (Service &service : mServices) { + if (mSingleServiceMode.IsEnabled() && mSingleServiceMode.GetService() != &service) + { + continue; + } + service.SetState(aNewStates[service.GetState()]); } @@ -768,9 +777,21 @@ void Client::SendUpdate(void) Error error = kErrorNone; Message *message = mSocket.NewMessage(0); + uint32_t length; VerifyOrExit(message != nullptr, error = kErrorNoBufs); SuccessOrExit(error = PrepareUpdateMessage(*message)); + + length = message->GetLength() + sizeof(Ip6::Udp::Header) + sizeof(Ip6::Header); + + if (length >= Ip6::kMaxDatagramLength) + { + LogInfo("Msg len %u is larger than MTU, enabling single service mode", length); + mSingleServiceMode.Enable(); + IgnoreError(message->SetLength(0)); + SuccessOrExit(error = PrepareUpdateMessage(*message)); + } + SuccessOrExit(error = mSocket.SendTo(*message, Ip6::MessageInfo())); LogInfo("Send update"); @@ -808,6 +829,7 @@ exit: LogInfo("Failed to send update: %s", ErrorToString(error)); + mSingleServiceMode.Disable(); FreeMessage(message); SetState(kStateToRetry); @@ -882,6 +904,11 @@ Error Client::PrepareUpdateMessage(Message &aMessage) for (Service &service : mServices) { SuccessOrExit(error = AppendServiceInstructions(service, aMessage, info)); + + if (mSingleServiceMode.IsEnabled() && (mSingleServiceMode.GetService() != nullptr)) + { + break; + } } } @@ -1054,6 +1081,11 @@ Error Client::AppendServiceInstructions(Service &aService, Message &aMessage, In } #endif + if (mSingleServiceMode.IsEnabled()) + { + mSingleServiceMode.SetService(aService); + } + exit: return error; } @@ -1473,6 +1505,7 @@ void Client::ProcessResponse(Message &aMessage) // kRemoving -> kRemoved ChangeHostAndServiceStates(kNewStateOnUpdateDone); + mSingleServiceMode.Disable(); HandleUpdateDone(); UpdateState(); @@ -1742,6 +1775,7 @@ void Client::HandleTimer(void) break; case kStateUpdating: + mSingleServiceMode.Disable(); LogRetryWaitInterval(); LogInfo("Timed out, no response"); GrowRetryWaitInterval(); diff --git a/src/core/net/srp_client.hpp b/src/core/net/srp_client.hpp index 4a3502bb6..fb367719d 100644 --- a/src/core/net/srp_client.hpp +++ b/src/core/net/srp_client.hpp @@ -854,6 +854,26 @@ private: kKeepRetryInterval, }; + class SingleServiceMode + { + public: + SingleServiceMode(void) + : mEnabled(false) + , mService(nullptr) + { + } + + void Enable(void) { mEnabled = true, mService = nullptr; } + void Disable(void) { mEnabled = false; } + bool IsEnabled(void) const { return mEnabled; } + Service *GetService(void) { return mService; } + void SetService(Service &aService) { mService = &aService; } + + private: + bool mEnabled; + Service *mService; + }; + #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE class AutoStart : Clearable { @@ -995,6 +1015,7 @@ private: const char * mDomainName; HostInfo mHostInfo; LinkedList mServices; + SingleServiceMode mSingleServiceMode; TimerMilli mTimer; #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE AutoStart mAutoStart; diff --git a/src/posix/platform/openthread-core-posix-config.h b/src/posix/platform/openthread-core-posix-config.h index a9754656e..6708751f2 100644 --- a/src/posix/platform/openthread-core-posix-config.h +++ b/src/posix/platform/openthread-core-posix-config.h @@ -287,4 +287,16 @@ #define OPENTHREAD_CONFIG_LOG_PREPEND_UPTIME 1 #endif +/** + * @def OPENTHREAD_CONFIG_SRP_CLIENT_BUFFERS_MAX_SERVICES + * + * Specifies number of service entries in the SRP client service pool. + * + * This config is applicable only when `OPENTHREAD_CONFIG_SRP_CLIENT_BUFFERS_ENABLE` is enabled. + * + */ +#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_BUFFERS_MAX_SERVICES +#define OPENTHREAD_CONFIG_SRP_CLIENT_BUFFERS_MAX_SERVICES 20 +#endif + #endif // OPENTHREAD_CORE_POSIX_CONFIG_H_ diff --git a/tests/scripts/thread-cert/Makefile.am b/tests/scripts/thread-cert/Makefile.am index 200c46a3c..2604dc504 100644 --- a/tests/scripts/thread-cert/Makefile.am +++ b/tests/scripts/thread-cert/Makefile.am @@ -193,6 +193,7 @@ EXTRA_DIST = \ test_srp_client_remove_host.py \ test_srp_client_save_server_info.py \ test_srp_lease.py \ + test_srp_many_services_mtu_check.py \ test_srp_name_conflicts.py \ test_srp_register_single_service.py \ test_srp_server_anycast_mode.py \ @@ -270,6 +271,7 @@ check_SCRIPTS = \ test_srp_client_remove_host.py \ test_srp_client_save_server_info.py \ test_srp_lease.py \ + test_srp_many_services_mtu_check.py \ test_srp_name_conflicts.py \ test_srp_register_single_service.py \ test_srp_server_anycast_mode.py \ diff --git a/tests/scripts/thread-cert/test_srp_many_services_mtu_check.py b/tests/scripts/thread-cert/test_srp_many_services_mtu_check.py new file mode 100755 index 000000000..87ea05210 --- /dev/null +++ b/tests/scripts/thread-cert/test_srp_many_services_mtu_check.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2022, 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 config +import thread_cert + +# Test description: +# +# This test verifies SRP client behavior when there are many services +# causing SRP Update message going over the IPv6 MTU size. In such +# a case the SRP client attempts to register a single service at +# a time. +# +# Topology: +# +# LEADER (SRP server) +# | +# | +# ROUTER (SRP client) +# + +SERVER = 1 +CLIENT = 2 + + +class SrpManyServicesMtuCheck(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + SUPPORT_NCP = False + + TOPOLOGY = { + SERVER: { + 'name': 'SRP_SERVER', + 'mode': 'rdn', + }, + CLIENT: { + 'name': 'SRP_CLIENT', + 'mode': 'rdn', + }, + } + + def test(self): + server = self.nodes[SERVER] + client = self.nodes[CLIENT] + + # Start the server & client devices. + + server.start() + self.simulator.go(5) + self.assertEqual(server.get_state(), 'leader') + + client.start() + self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.assertEqual(client.get_state(), 'router') + + server.srp_server_set_enabled(True) + client.srp_client_enable_auto_start_mode() + self.simulator.go(5) + + # Register 8 services with long name, 6 sub-types and long txt record. + # The 8 services won't be fit in a single MTU (1280 bytes) UDP message + # SRP Client would then register services one by one. + + num_services = 8 + + client.srp_client_set_host_name('hosthosthosthosthosthosthosthosthosthosthosthosthosthosthosthos') + client.srp_client_set_host_address('2001::1') + + txt_info = ["xyz=987654321", "uvw=abcdefghijklmnopqrstu", "qwp=564321abcdefghij"] + + for num in range(num_services): + name = chr(ord('a') + num) * 63 + client.srp_client_add_service( + name, + '_longsrvname._udp,_subtype1,_subtype2,_subtype3,_subtype4,_subtype5,_subtype6', + 1977, + txt_entries=txt_info) + + self.simulator.go(10) + + # Make sure all services are successfully registered + # (from both client and server perspectives). + + client_services = client.srp_client_get_services() + self.assertEqual(len(client_services), num_services) + + for service in client_services: + self.assertEqual(service['state'], 'Registered') + + server_services = server.srp_server_get_services() + self.assertEqual(len(server_services), num_services) + + +if __name__ == '__main__': + unittest.main()