diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 763676e7e..6708dd717 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -410,6 +410,7 @@ ot_nexus_test(nat64_translator "core;nexus") ot_nexus_test(netdata_publisher "core;nexus") ot_nexus_test(on_mesh_prefix "core;nexus") ot_nexus_test(pbbr_aloc "core;nexus") +ot_nexus_test(ping_lla_src "core;nexus") ot_nexus_test(reed_address_solicit_rejected "core;nexus") ot_nexus_test(reset "core;nexus") ot_nexus_test(router_downgrade_on_sec_policy_change "core;nexus") diff --git a/tests/nexus/platform/nexus_core.cpp b/tests/nexus/platform/nexus_core.cpp index ce0d30f67..b51890aa3 100644 --- a/tests/nexus/platform/nexus_core.cpp +++ b/tests/nexus/platform/nexus_core.cpp @@ -1093,7 +1093,8 @@ void Core::SendAndVerifyEchoRequest(Node &aSender, const Ip6::Address &aDestination, uint16_t aPayloadSize, uint8_t aHopLimit, - uint32_t aResponseTimeout) + uint32_t aResponseTimeout, + bool aForceSource) { static constexpr uint16_t kIdentifier = 0x1234; @@ -1105,11 +1106,56 @@ void Core::SendAndVerifyEchoRequest(Node &aSender, SuccessOrQuit(aSender.Get().RegisterHandler(icmpHandler)); - aSender.SendEchoRequest(aDestination, kIdentifier, aPayloadSize, aHopLimit); + aSender.SendEchoRequest(aDestination, kIdentifier, aPayloadSize, aHopLimit, + aForceSource ? &aExpectedSource : nullptr); AdvanceTime(aResponseTimeout); - VerifyOrQuit(icmpContext.mResponseReceived); SuccessOrQuit(aSender.Get().UnregisterHandler(icmpHandler)); + + VerifyOrQuit(icmpContext.mResponseReceived); +} + +void Core::SendAndVerifyNoEchoResponse(Node &aSender, + const Ip6::Address &aDestination, + uint16_t aPayloadSize, + uint8_t aHopLimit, + uint32_t aResponseTimeout) +{ + static constexpr uint16_t kIdentifier = 0x1234; + + IcmpEchoResponseContext icmpContext(aSender, kIdentifier); + Ip6::Icmp::Handler icmpHandler(HandleIcmpResponse, &icmpContext); + + SuccessOrQuit(aSender.Get().RegisterHandler(icmpHandler)); + + aSender.SendEchoRequest(aDestination, kIdentifier, aPayloadSize, aHopLimit); + AdvanceTime(aResponseTimeout); + + SuccessOrQuit(aSender.Get().UnregisterHandler(icmpHandler)); + + VerifyOrQuit(!icmpContext.mResponseReceived); +} + +void Core::SendAndVerifyNoEchoResponse(Node &aSender, + const Ip6::Address &aSrcAddress, + const Ip6::Address &aDestination, + uint16_t aPayloadSize, + uint8_t aHopLimit, + uint32_t aResponseTimeout) +{ + static constexpr uint16_t kIdentifier = 0x1234; + + IcmpEchoResponseContext icmpContext(aSender, kIdentifier); + Ip6::Icmp::Handler icmpHandler(HandleIcmpResponse, &icmpContext); + + SuccessOrQuit(aSender.Get().RegisterHandler(icmpHandler)); + + aSender.SendEchoRequest(aDestination, kIdentifier, aPayloadSize, aHopLimit, &aSrcAddress); + AdvanceTime(aResponseTimeout); + + SuccessOrQuit(aSender.Get().UnregisterHandler(icmpHandler)); + + VerifyOrQuit(!icmpContext.mResponseReceived); } } // namespace Nexus diff --git a/tests/nexus/platform/nexus_core.hpp b/tests/nexus/platform/nexus_core.hpp index 40309038e..89bb64f25 100644 --- a/tests/nexus/platform/nexus_core.hpp +++ b/tests/nexus/platform/nexus_core.hpp @@ -96,7 +96,21 @@ public: const Ip6::Address &aDestination, uint16_t aPayloadSize = 0, uint8_t aHopLimit = Ip6::kDefaultHopLimit, - uint32_t aResponseTimeout = 1000); + uint32_t aResponseTimeout = 1000, + bool aForceSource = true); + + void SendAndVerifyNoEchoResponse(Node &aSender, + const Ip6::Address &aDestination, + uint16_t aPayloadSize = 0, + uint8_t aHopLimit = Ip6::kDefaultHopLimit, + uint32_t aResponseTimeout = 1000); + + void SendAndVerifyNoEchoResponse(Node &aSender, + const Ip6::Address &aSrcAddress, + const Ip6::Address &aDestination, + uint16_t aPayloadSize = 0, + uint8_t aHopLimit = Ip6::kDefaultHopLimit, + uint32_t aResponseTimeout = 1000); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Used by platform implementation diff --git a/tests/nexus/test_ipv6_source_selection.cpp b/tests/nexus/test_ipv6_source_selection.cpp index eee1210bb..3f7b73113 100644 --- a/tests/nexus/test_ipv6_source_selection.cpp +++ b/tests/nexus/test_ipv6_source_selection.cpp @@ -78,25 +78,25 @@ void TestIPv6SourceSelection(void) Log("---------------------------------------------------------------------------------------"); Log("Step 1: RLOC source for RLOC destination"); - nexus.SendAndVerifyEchoRequest(router, routerRloc, leaderRloc); + nexus.SendAndVerifyEchoRequest(router, routerRloc, leaderRloc, 0, Ip6::kDefaultHopLimit, 1000, false); Log("---------------------------------------------------------------------------------------"); Log("Step 2: ML-EID source for ALOC destination"); - nexus.SendAndVerifyEchoRequest(router, routerMleid, leaderAloc); + nexus.SendAndVerifyEchoRequest(router, routerMleid, leaderAloc, 0, Ip6::kDefaultHopLimit, 1000, false); Log("---------------------------------------------------------------------------------------"); Log("Step 3: ML-EID source for ML-EID destination"); - nexus.SendAndVerifyEchoRequest(router, routerMleid, leaderMleid); + nexus.SendAndVerifyEchoRequest(router, routerMleid, leaderMleid, 0, Ip6::kDefaultHopLimit, 1000, false); Log("---------------------------------------------------------------------------------------"); Log("Step 4: Link-local source for Link-local destination"); - nexus.SendAndVerifyEchoRequest(router, routerLinkLocal, leaderLinkLocal); + nexus.SendAndVerifyEchoRequest(router, routerLinkLocal, leaderLinkLocal, 0, Ip6::kDefaultHopLimit, 1000, false); Log("---------------------------------------------------------------------------------------"); Log("Step 5: ML-EID source for Realm-local multicast destination"); Ip6::Address multicastAddr; SuccessOrQuit(multicastAddr.FromString("ff03::1")); - nexus.SendAndVerifyEchoRequest(router, routerMleid, multicastAddr); + nexus.SendAndVerifyEchoRequest(router, routerMleid, multicastAddr, 0, Ip6::kDefaultHopLimit, 1000, false); Log("---------------------------------------------------------------------------------------"); Log("Step 6: GUA source for GUA destination"); @@ -120,7 +120,7 @@ void TestIPv6SourceSelection(void) VerifyOrQuit(!leaderGua.IsUnspecified()); VerifyOrQuit(!routerGua.IsUnspecified()); - nexus.SendAndVerifyEchoRequest(router, routerGua, leaderGua); + nexus.SendAndVerifyEchoRequest(router, routerGua, leaderGua, 0, Ip6::kDefaultHopLimit, 1000, false); Log("---------------------------------------------------------------------------------------"); Log("Step 7: GUA source for external address (default route)"); @@ -137,7 +137,7 @@ void TestIPv6SourceSelection(void) extNetifAddr.mValid = true; SuccessOrQuit(otIp6AddUnicastAddress(&leader.GetInstance(), &extNetifAddr)); - nexus.SendAndVerifyEchoRequest(router, routerGua, externalAddr); + nexus.SendAndVerifyEchoRequest(router, routerGua, externalAddr, 0, Ip6::kDefaultHopLimit, 1000, false); nexus.SaveTestInfo("test_ipv6_source_selection.json"); } diff --git a/tests/nexus/test_ping_lla_src.cpp b/tests/nexus/test_ping_lla_src.cpp new file mode 100644 index 000000000..49ca54803 --- /dev/null +++ b/tests/nexus/test_ping_lla_src.cpp @@ -0,0 +1,123 @@ +/* + * 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 { + +void TestPingLlaSrc(void) +{ + /** + * Test ping with LLA source address. + * + * Topology: + * ROUTER_2 ----- ROUTER_1 ---- ROUTER_3 + * + * ROUTER_1 is leader. ROUTER_2 and ROUTER_3 are routers. + */ + + Core nexus; + + Node &router1 = nexus.CreateNode(); + Node &router2 = nexus.CreateNode(); + Node &router3 = nexus.CreateNode(); + + router1.SetName("Router_1"); + router2.SetName("Router_2"); + router3.SetName("Router_3"); + + AllowLinkBetween(router1, router2); + AllowLinkBetween(router1, router3); + + nexus.AdvanceTime(0); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 1: Form network"); + + router1.Form(); + nexus.AdvanceTime(13 * 1000); // kFormNetworkTime + VerifyOrQuit(router1.Get().IsLeader()); + + router2.Join(router1); + nexus.AdvanceTime(200 * 1000); // kAttachToRouterTime + VerifyOrQuit(router2.Get().IsRouter()); + + router3.Join(router1); + nexus.AdvanceTime(200 * 1000); // kAttachToRouterTime + VerifyOrQuit(router3.Get().IsRouter()); + + Ip6::Address router1Mleid = router1.Get().GetMeshLocalEid(); + Ip6::Address router2LinkLocal = router2.Get().GetLinkLocalAddress(); + Ip6::Address router3Mleid = router3.Get().GetMeshLocalEid(); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 2: Ping a neighbor with LLA src should succeed"); + // router2 pings router1's MLEID using its own LLA as source. + nexus.SendAndVerifyEchoRequest(router2, router2LinkLocal, router1Mleid); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 3: Ping a non-neighbor with LLA src should fail"); + // router2 pings router3's MLEID using its own LLA as source. + // It should fail because LLA is only valid for one-hop. + nexus.SendAndVerifyNoEchoResponse(router2, router2LinkLocal, router3Mleid); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 4: Make sure external routes are not used for LLA src"); + + NetworkData::ExternalRouteConfig routeConfig; + routeConfig.Clear(); + SuccessOrQuit(routeConfig.GetPrefix().FromString("2001::/64")); + routeConfig.mStable = true; + + SuccessOrQuit(router1.Get().AddHasRoutePrefix(routeConfig)); + router1.Get().HandleServerDataUpdated(); + nexus.AdvanceTime(10 * 1000); + + Ip6::Address externalAddr; + SuccessOrQuit(externalAddr.FromString("2001::1")); + + // router2 pings external address using its own LLA as source. + // External routes should not be used for LLA source. + nexus.SendAndVerifyNoEchoResponse(router2, router2LinkLocal, externalAddr); + + nexus.SaveTestInfo("test_ping_lla_src.json"); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestPingLlaSrc(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/scripts/thread-cert/test_ping_lla_src.py b/tests/scripts/thread-cert/test_ping_lla_src.py deleted file mode 100755 index e6f9b26d1..000000000 --- a/tests/scripts/thread-cert/test_ping_lla_src.py +++ /dev/null @@ -1,119 +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 logging -import unittest - -import pktverify -from pktverify import packet_verifier -import config -import thread_cert - -# Test description: -# The purpose of this test is to verify the Router's routing behavior using LLA as source address. -# -# Topology: -# -# -# ROUTER_2 ----- ROUTER_1 ---- ROUTER_3 -# -# - -ROUTER_1 = 1 -ROUTER_2 = 2 -ROUTER_3 = 3 - - -class TestPing(thread_cert.TestCase): - USE_MESSAGE_FACTORY = False - SUPPORT_NCP = False - - TOPOLOGY = { - ROUTER_1: { - 'name': 'Router_1', - 'allowlist': [ROUTER_2, ROUTER_3], - }, - ROUTER_2: { - 'name': 'Router_2', - 'allowlist': [ROUTER_1], - }, - ROUTER_3: { - 'name': 'Router_3', - 'allowlist': [ROUTER_1], - }, - } - - def test(self): - router1 = self.nodes[ROUTER_1] - router2 = self.nodes[ROUTER_2] - router3 = self.nodes[ROUTER_3] - - router1.start() - self.simulator.go(config.LEADER_STARTUP_DELAY) - self.assertEqual('leader', router1.get_state()) - - router2.start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) - self.assertEqual('router', router2.get_state()) - - router3.start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) - self.assertEqual('router', router3.get_state()) - - self.simulator.go(10) - - left, center, right = router2, router1, router3 - - left_lla = left.get_ip6_address(config.ADDRESS_TYPE.LINK_LOCAL) - - # Ping a neighbor with LLA src should succeed. - self.assertTrue(left.ping(center.get_mleid(), interface=left_lla)) - self.simulator.go(3) - - # Ping a non-neighbor with LLA src should fail. - self.assertFalse(left.ping(right.get_mleid(), interface=left_lla)) - self.simulator.go(3) - - center.add_route('2001::/64', stable=True) - center.register_netdata() - - self.simulator.go(3) - - # Make sure external routes are not used for LLA src. - self.assertFalse(left.ping("2001::1", interface=left_lla)) - self.simulator.go(3) - - def verify(self, pv: pktverify.packet_verifier.PacketVerifier): - pkts = pv.pkts - # Make sure the Ping Request to 2001::1 was not sent - pkts.filter_ipv6_dst('2001::1').filter_ping_request().must_not_next() - - -if __name__ == '__main__': - unittest.main()