diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index db37f9c28..c22d91c31 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -405,6 +405,7 @@ ot_nexus_test(netdata_publisher "core;nexus") ot_nexus_test(reed_address_solicit_rejected "core;nexus") ot_nexus_test(router_downgrade_on_sec_policy_change "core;nexus") ot_nexus_test(srp_auto_start "core;nexus") +ot_nexus_test(srp_client_change_lease "core;nexus") ot_nexus_test(srp_lease "core;nexus") ot_nexus_test(zero_len_external_route "core;nexus") diff --git a/tests/nexus/run_nexus_tests.sh b/tests/nexus/run_nexus_tests.sh index 893caa6df..5561416c3 100755 --- a/tests/nexus/run_nexus_tests.sh +++ b/tests/nexus/run_nexus_tests.sh @@ -233,6 +233,7 @@ DEFAULT_TESTS=( "1_4_PIC_TC_3" "1_4_PIC_TC_4" "1_4_CS_TC_3" + "srp_client_change_lease" "inform_previous_parent_on_reattach" ) diff --git a/tests/nexus/test_srp_client_change_lease.cpp b/tests/nexus/test_srp_client_change_lease.cpp new file mode 100644 index 000000000..24c8a68fc --- /dev/null +++ b/tests/nexus/test_srp_client_change_lease.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2026, 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. + */ + +#include + +#include "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" + +namespace ot { +namespace Nexus { + +/** + * Time to advance for a node to form a network and become leader, in milliseconds. + */ +static constexpr uint32_t kFormNetworkTime = 13 * 1000; + +/** + * Time to advance for a node to join a network, in milliseconds. + */ +static constexpr uint32_t kJoinNetworkTime = 10 * 1000; + +/** + * SRP Lease time in seconds. + */ +static constexpr uint32_t kLeaseTime = 60; +static constexpr uint32_t kNewLeaseTime = 120; +static constexpr uint32_t kKeyLeaseTime = 240; +static constexpr uint32_t kNewTtl = 10; + +/** + * SRP service and host names. + */ +static const char kSrpServiceType[] = "_ipps._tcp"; +static const char kSrpInstanceName[] = "my-service"; +static const char kSrpHostName[] = "my-host"; +static const char kSrpHostAddress[] = "2001::1"; +static constexpr uint16_t kSrpServicePort = 12345; + +void TestSrpClientChangeLease(const char *aJsonFileName) +{ + Core nexus; + Ip6::Address srpHostAddress; + Srp::Client::Service srpService; + + Node &server = nexus.CreateNode(); + Node &client = nexus.CreateNode(); + + server.SetName("SRP_SERVER"); + client.SetName("SRP_CLIENT"); + + SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote)); + + Log("Step 0: Start the server and client devices."); + + server.Form(); + nexus.AdvanceTime(kFormNetworkTime); + + { + Srp::Server::LeaseConfig leaseConfig; + leaseConfig.mMinLease = kLeaseTime; + leaseConfig.mMaxLease = kLeaseTime; + leaseConfig.mMinKeyLease = kKeyLeaseTime; + leaseConfig.mMaxKeyLease = kKeyLeaseTime; + SuccessOrQuit(server.Get().SetLeaseConfig(leaseConfig)); + } + server.Get().SetEnabled(true); + nexus.AdvanceTime(5 * 1000); + + client.Join(server, Node::kAsFed); + nexus.AdvanceTime(kJoinNetworkTime); + + Log("Step 1: Register a single service and verify that it works."); + + client.Get().EnableAutoStartMode(nullptr, nullptr); + + SuccessOrQuit(client.Get().SetHostName(kSrpHostName)); + + SuccessOrQuit(srpHostAddress.FromString(kSrpHostAddress)); + SuccessOrQuit(client.Get().SetHostAddresses(&srpHostAddress, 1)); + + ClearAllBytes(srpService); + srpService.mName = kSrpServiceType; + srpService.mInstanceName = kSrpInstanceName; + srpService.mPort = kSrpServicePort; + SuccessOrQuit(client.Get().AddService(srpService)); + + nexus.AdvanceTime(2 * 1000); + + VerifyOrQuit(client.Get().GetHostInfo().GetState() == Srp::Client::kRegistered); + + Log("Step 2: Change server lease range and client lease interval."); + { + Srp::Server::LeaseConfig leaseConfig; + leaseConfig.mMinLease = kNewLeaseTime; + leaseConfig.mMaxLease = kNewLeaseTime; + leaseConfig.mMinKeyLease = kKeyLeaseTime; + leaseConfig.mMaxKeyLease = kKeyLeaseTime; + SuccessOrQuit(server.Get().SetLeaseConfig(leaseConfig)); + } + client.Get().SetLeaseInterval(kNewLeaseTime); + nexus.AdvanceTime(kNewLeaseTime * 1000); + VerifyOrQuit(client.Get().GetHostInfo().GetState() == Srp::Client::kRegistered); + + Log("Step 3: Wait for KEY_LEASE_TIME * 2."); + nexus.AdvanceTime(kKeyLeaseTime * 2 * 1000); + VerifyOrQuit(client.Get().GetHostInfo().GetState() == Srp::Client::kRegistered); + + Log("Step 4: Change client TTL."); + client.Get().SetTtl(kNewTtl); + nexus.AdvanceTime(kNewLeaseTime * 1000); + VerifyOrQuit(client.Get().GetHostInfo().GetState() == Srp::Client::kRegistered); + + Log("Step 5: Set TTL to 0."); + client.Get().SetTtl(0); + nexus.AdvanceTime(kNewLeaseTime * 1000); + VerifyOrQuit(client.Get().GetHostInfo().GetState() == Srp::Client::kRegistered); + + nexus.SaveTestInfo(aJsonFileName); +} + +} // namespace Nexus +} // namespace ot + +int main(int argc, char *argv[]) +{ + ot::Nexus::TestSrpClientChangeLease((argc > 2) ? argv[2] : "test_srp_client_change_lease.json"); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/nexus/verify_srp_client_change_lease.py b/tests/nexus/verify_srp_client_change_lease.py new file mode 100644 index 000000000..b58f480ff --- /dev/null +++ b/tests/nexus/verify_srp_client_change_lease.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026, 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 sys +import os + +# Add the current directory to sys.path to find verify_utils +CUR_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(CUR_DIR) + +import verify_utils +from pktverify.packet_filter import PacketFilter + +DEFAULT_LEASE_TIME = 7200 +NEW_LEASE_TIME = 120 +NEW_TTL = 10 + + +def verify(pv): + pkts: PacketFilter = pv.pkts + pv.summary.show() + + CLIENT_EXT_ADDR = pv.vars['SRP_CLIENT'] + + # 1. Register with default lease + pkts.filter_wpan_src64(CLIENT_EXT_ADDR).filter('dns.flags.response == 0 and dns.resp.ttl == {DEFAULT_LEASE_TIME}', + DEFAULT_LEASE_TIME=DEFAULT_LEASE_TIME).must_next() + + # 2. Change server lease range and client lease interval to NEW_LEASE_TIME + pkts.filter_wpan_src64(CLIENT_EXT_ADDR).filter('dns.flags.response == 0 and dns.resp.ttl == {NEW_LEASE_TIME}', + NEW_LEASE_TIME=NEW_LEASE_TIME).must_next() + + # 4. Change client TTL to NEW_TTL + pkts.filter_wpan_src64(CLIENT_EXT_ADDR).filter('dns.flags.response == 0 and dns.resp.ttl == {NEW_TTL}', + NEW_TTL=NEW_TTL).must_next() + + # 5. Set TTL to 0 (which should use NEW_LEASE_TIME) + pkts.filter_wpan_src64(CLIENT_EXT_ADDR).filter('dns.flags.response == 0 and dns.resp.ttl == {NEW_LEASE_TIME}', + NEW_LEASE_TIME=NEW_LEASE_TIME).must_next() + + +if __name__ == '__main__': + verify_utils.run_main(verify) diff --git a/tests/scripts/thread-cert/test_srp_client_change_lease.py b/tests/scripts/thread-cert/test_srp_client_change_lease.py deleted file mode 100755 index 4c98c8c99..000000000 --- a/tests/scripts/thread-cert/test_srp_client_change_lease.py +++ /dev/null @@ -1,161 +0,0 @@ -#!/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 unittest - -import config -import thread_cert - -# Test description: -# This test verifies the SRP client lease changing works as expected. -# -# Topology: -# LEADER (SRP server) -# | -# | -# ROUTER (SRP client) -# -from pktverify.packet_filter import PacketFilter - -SERVER = 1 -CLIENT = 2 - -DEFAULT_LEASE_TIME = 7200 -LEASE_TIME = 60 -NEW_LEASE_TIME = 120 -KEY_LEASE_TIME = 240 -NEW_TTL = 10 - -assert LEASE_TIME < KEY_LEASE_TIME -assert NEW_LEASE_TIME < KEY_LEASE_TIME -assert NEW_TTL < NEW_LEASE_TIME - - -class SrpClientChangeLeaseTime(thread_cert.TestCase): - USE_MESSAGE_FACTORY = False - SUPPORT_NCP = False - - TOPOLOGY = { - SERVER: { - 'name': 'SRP_SERVER', - 'network_key': '00112233445566778899aabbccddeeff', - 'mode': 'rdn', - }, - CLIENT: { - 'name': 'SRP_CLIENT', - 'network_key': '00112233445566778899aabbccddeeff', - 'mode': 'rdn', - }, - } - - def test(self): - server = self.nodes[SERVER] - client = self.nodes[CLIENT] - - # - # 0. Start the server and client devices. - # - - server.srp_server_set_enabled(True) - server.srp_server_set_lease_range(LEASE_TIME, LEASE_TIME, KEY_LEASE_TIME, KEY_LEASE_TIME) - server.start() - self.simulator.go(config.LEADER_STARTUP_DELAY) - self.assertEqual(server.get_state(), 'leader') - self.simulator.go(5) - - client.start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) - self.assertEqual(client.get_state(), 'router') - - # - # 1. Register a single service and verify that it works. - # - - self.assertEqual(client.srp_client_get_auto_start_mode(), 'Enabled') - - client.srp_client_set_host_name('my-host') - client.srp_client_set_host_address('2001::1') - client.srp_client_add_service('my-service', '_ipps._tcp', 12345) - self.simulator.go(2) - - self.check_host_and_service(server, client) - - server.srp_server_set_lease_range(NEW_LEASE_TIME, NEW_LEASE_TIME, KEY_LEASE_TIME, KEY_LEASE_TIME) - client.srp_client_set_lease_interval(NEW_LEASE_TIME) - self.simulator.go(NEW_LEASE_TIME) - self.assertEqual(client.srp_client_get_host_state(), 'Registered') - - self.simulator.go(KEY_LEASE_TIME * 2) - self.assertEqual(client.srp_client_get_host_state(), 'Registered') - - client.srp_client_set_ttl(NEW_TTL) - self.simulator.go(NEW_LEASE_TIME) - self.assertEqual(client.srp_client_get_host_state(), 'Registered') - - client.srp_client_set_ttl(0) - self.simulator.go(NEW_LEASE_TIME) - self.assertEqual(client.srp_client_get_host_state(), 'Registered') - - def verify(self, pv): - pkts: PacketFilter = pv.pkts - pv.summary.show() - - CLIENT_SRC64 = pv.vars['SRP_CLIENT'] - - pkts.filter_wpan_src64(CLIENT_SRC64).filter('dns.flags.response == 0 and dns.resp.ttl == {DEFAULT_LEASE_TIME}', - DEFAULT_LEASE_TIME=DEFAULT_LEASE_TIME).must_next() - pkts.filter_wpan_src64(CLIENT_SRC64).filter('dns.flags.response == 0 and dns.resp.ttl == {NEW_LEASE_TIME}', - NEW_LEASE_TIME=NEW_LEASE_TIME).must_next() - pkts.filter_wpan_src64(CLIENT_SRC64).filter('dns.flags.response == 0 and dns.resp.ttl == {NEW_TTL}', - NEW_TTL=NEW_TTL).must_next() - pkts.filter_wpan_src64(CLIENT_SRC64).filter('dns.flags.response == 0 and dns.resp.ttl == {NEW_LEASE_TIME}', - NEW_LEASE_TIME=NEW_LEASE_TIME).must_next() - - def check_host_and_service(self, server, client): - """Check that we have properly registered host and service instance. - """ - - client_services = client.srp_client_get_services() - print(client_services) - self.assertEqual(len(client_services), 1) - client_service = client_services[0] - - # Verify that the client possesses correct service resources. - self.assertEqual(client_service['instance'], 'my-service') - self.assertEqual(client_service['name'], '_ipps._tcp') - self.assertEqual(int(client_service['port']), 12345) - self.assertEqual(int(client_service['priority']), 0) - self.assertEqual(int(client_service['weight']), 0) - - # Verify that the client received a SUCCESS response for the server. - self.assertEqual(client_service['state'], 'Registered') - - -if __name__ == '__main__': - unittest.main()