From 63ec21ea77520828803b78dbb5793042176e3ea6 Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Tue, 31 Mar 2026 07:39:13 -0700 Subject: [PATCH] [nexus] add 1_4_TREL_TC_1 for Thread over Infrastructure (#12803) This commit adds the Nexus test case 1_4_TREL_TC_1 which verifies connectivity between multi-radio (15.4 and TREL) and single-radio (15.4 only) devices, as per the Thread 1.4 test specification. The implementation includes: - tests/nexus/test_1_4_TREL_TC_1.cpp: Implements the test sequence. It sets up a topology with a Border Router (DUT) and two Routers. BR and Router_1 support both 15.4 and TREL, while Router_2 supports only 15.4. The test verifies that nodes can correctly detect neighbor radio capabilities and establishes connectivity using both radio types. - tests/nexus/verify_1_4_TREL_TC_1.py: Performs automated packet verification. It ensures that traffic between multi-radio nodes preferentially uses TREL (simulated over the infrastructure link via UDP), while traffic involving the single-radio node uses 15.4. It also validates successful ping exchange across the mixed-radio topology. - Integrated the new test into tests/nexus/CMakeLists.txt and tests/nexus/run_nexus_tests.sh. The test validates that the Thread stack correctly manages multiple radio links and ensures seamless connectivity across different physical layers. --- tests/nexus/CMakeLists.txt | 1 + tests/nexus/build.sh | 4 + tests/nexus/platform/nexus_core.cpp | 12 ++ tests/nexus/run_nexus_tests.sh | 1 + tests/nexus/test_1_4_TREL_TC_1.cpp | 214 ++++++++++++++++++++++++++ tests/nexus/verify_1_4_TREL_TC_1.py | 224 ++++++++++++++++++++++++++++ tests/nexus/verify_utils.py | 13 ++ 7 files changed, 469 insertions(+) create mode 100644 tests/nexus/test_1_4_TREL_TC_1.cpp create mode 100644 tests/nexus/verify_1_4_TREL_TC_1.py diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index bc81e7a3e..c99f0c737 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -283,6 +283,7 @@ ot_nexus_test(1_3_SRPC_TC_5 "cert;nexus") ot_nexus_test(1_3_SRPC_TC_7 "cert;nexus") ot_nexus_test(1_3_DIAG_TC_1 "cert;nexus") ot_nexus_test(1_3_DIAG_TC_2 "cert;nexus") +ot_nexus_test(1_4_TREL_TC_1 "cert;nexus") # Misc tests ot_nexus_test(border_admitter "core;nexus") diff --git a/tests/nexus/build.sh b/tests/nexus/build.sh index 922826c79..4429cd8b6 100755 --- a/tests/nexus/build.sh +++ b/tests/nexus/build.sh @@ -49,6 +49,10 @@ case $1 in trel=ON fifteenfour=OFF ;; + multi-radio) + trel=ON + fifteenfour=ON + ;; *) trel=ON fifteenfour=ON diff --git a/tests/nexus/platform/nexus_core.cpp b/tests/nexus/platform/nexus_core.cpp index 115ec8265..137e41f0f 100644 --- a/tests/nexus/platform/nexus_core.cpp +++ b/tests/nexus/platform/nexus_core.cpp @@ -238,6 +238,18 @@ void Core::SaveTestInfo(const char *aFilename, Node *aLeaderNode) } fprintf(file, " },\n"); + fprintf(file, " \"trel_udp_ports\": {\n"); + for (Node &node : mNodes) + { +#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE + fprintf(file, " \"%u\": %u%s\n", node.GetInstance().GetId(), node.mTrel.mUdpPort, + (&node == tail) ? "" : ","); +#else + fprintf(file, " \"%u\": 0%s\n", node.GetInstance().GetId(), (&node == tail) ? "" : ","); +#endif + } + fprintf(file, " },\n"); + fprintf(file, " \"ipaddrs\": {\n"); for (Node &node : mNodes) { diff --git a/tests/nexus/run_nexus_tests.sh b/tests/nexus/run_nexus_tests.sh index a909d72c1..a9b89e4af 100755 --- a/tests/nexus/run_nexus_tests.sh +++ b/tests/nexus/run_nexus_tests.sh @@ -218,6 +218,7 @@ DEFAULT_TESTS=( "1_3_SRPC_TC_7" "1_3_DIAG_TC_1" "1_3_DIAG_TC_2" + "1_4_TREL_TC_1" ) # Use provided arguments or the default test list diff --git a/tests/nexus/test_1_4_TREL_TC_1.cpp b/tests/nexus/test_1_4_TREL_TC_1.cpp new file mode 100644 index 000000000..068ef95fe --- /dev/null +++ b/tests/nexus/test_1_4_TREL_TC_1.cpp @@ -0,0 +1,214 @@ +/* + * 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 { + +/** + * 8.1. [1.4] [CERT] Attach and connectivity (unicast/broadcast) between multi-radio and single-radio devices + * + * 8.1.1. Purpose + * To test the following: + * 1. Attaching Thread Devices with different radio links. + * 2. Verifies the detection of radio link types supported by neighbors/children. + * 3. Verifies connectivity between nodes (unicast and broadcast frame exchange with multi-radio support). + * + * 8.1.2. Topology + * - BR Leader (DUT) - Support multi-radio (15.4 and TREL). + * - Router_1 - Reference device that supports multi-radio (15.4 and TREL). + * - Router_2 - Reference device that supports 15.4 radio only. + * + * Spec Reference | V1.1 Section | V1.3.0 Section + * -------------------------|--------------|--------------- + * TREL / Multi-radio Links | N/A | 8.1 + */ + +#if OPENTHREAD_CONFIG_MULTI_RADIO + +static constexpr uint32_t kFormNetworkTime = 13 * 1000; +static constexpr uint32_t kAttachToRouterTime = 200 * 1000; +static constexpr uint32_t kStabilizationTime = 30 * 1000; +static constexpr uint32_t kInfraIfIndex = 1; +static constexpr uint16_t kPingPayloadSize = 500; +static constexpr uint32_t kPingResponseTimeout = 10000; + +void Test1_4_Trel_Tc_1(void) +{ + Core nexus; + Node &br = nexus.CreateNode(); + Node &router1 = nexus.CreateNode(); + Node &router2 = nexus.CreateNode(); + + br.SetName("BR_DUT"); + router1.SetName("ROUTER_1"); + router2.SetName("ROUTER_2"); + + nexus.AdvanceTime(0); + + SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote)); + + /** + * Step 1 + * - Device: BR (DUT), Router_1, Router_2 + * - Description (TREL-8.1): Form the topology. Wait for Router_1 and Router_2 to become routers. Note: Router_1 + * and Router_2 MUST NOT be able to communicate directly and not be in each other’s neighbor table (e.g. this + * may be realized by using the allow/deny list mechanism). + * - Pass Criteria: + * - Verify that topology is formed. + * - The DUT MUST be the network Leader. + * - Router_1 MUST only see Leader as its immediate neighbor (Router_2 MUST not be present in Router_1’s + * neighbor table). + * - Router_2 MUST only see Leader as its immediate neighbor and not Router_1. + */ + Log("Step 1: Form the topology"); + + SuccessOrQuit(br.Get().SetEnabled(true, kInfraIfIndex)); + SuccessOrQuit(router1.Get().SetEnabled(true, kInfraIfIndex)); + + br.AllowList(router1); + br.AllowList(router2); + router1.AllowList(br); + router2.AllowList(br); + + br.Form(); + nexus.AdvanceTime(kFormNetworkTime); + VerifyOrQuit(br.Get().IsLeader()); + + router1.Join(br); + router2.Join(br); + nexus.AdvanceTime(kAttachToRouterTime); + + VerifyOrQuit(router1.Get().IsRouter()); + VerifyOrQuit(router2.Get().IsRouter()); + + nexus.AdvanceTime(kStabilizationTime); + + // Verify neighbor tables + VerifyOrQuit(router1.Get().FindNeighbor(br.Get().GetExtAddress()) != nullptr); + VerifyOrQuit(router1.Get().FindNeighbor(router2.Get().GetExtAddress()) == nullptr); + VerifyOrQuit(router2.Get().FindNeighbor(br.Get().GetExtAddress()) != nullptr); + VerifyOrQuit(router2.Get().FindNeighbor(router1.Get().GetExtAddress()) == nullptr); + + /** + * Step 2 + * - Device: Router_1 + * - Description (TREL-8.1): Harness instructs device to report its neighbor multi-radio info (which indicates + * which radio links are detected per neighbor/parent) + * - Pass Criteria: + * - Router_1 MUST have correctly detected that the DUT supports both TREL and 15.4 radios (multi-radio neighbor + * info). + * - The DUT MUST send TREL frames using the correct format + */ + Log("Step 2: Router_1 reports its neighbor multi-radio info"); + + { + const Neighbor *neighbor = router1.Get().FindNeighbor(br.Get().GetExtAddress()); + + VerifyOrQuit(neighbor != nullptr); + VerifyOrQuit(neighbor->GetSupportedRadioTypes().Contains(Mac::kRadioTypeIeee802154)); + VerifyOrQuit(neighbor->GetSupportedRadioTypes().Contains(Mac::kRadioTypeTrel)); + } + + /** + * Step 3 + * - Device: Router_1 + * - Description (TREL-8.1): Harness instructs device to send a ping (of size 500 bytes or more) to the DUT using + * the link-local IPv6 address as the destination. Note: The link-local IPv6 address may be derived from its + * extended address by retrieving neighbor info on Router_1 (at step 2). A larger ping size is used to verify + * behavior with a larger MTU payload on TREL. + * - Pass Criteria: + * - The DUT MUST send a ping response that is successfully received by Router_1. + * - The ping response packet MUST NOT be present in the 802.15.4 capture. (I.e. travels over TREL link.) + * - The TREL frames sent by DUT MUST use the correct TREL format. + */ + Log("Step 3: Router_1 pings the DUT using link-local address"); + nexus.SendAndVerifyEchoRequest(router1, br.Get().GetLinkLocalAddress(), kPingPayloadSize, + Ip6::kDefaultHopLimit, kPingResponseTimeout); + + /** + * Step 4 + * - Device: Router_2 + * - Description (TREL-8.1): Harness instructs device to send a ping (of size 500 bytes or more) to the DUT using + * the link-local IPv6 address as the destination. + * - Pass Criteria: + * - The DUT MUST send a ping response that is successfully received by Router_2. + */ + Log("Step 4: Router_2 pings the DUT using link-local address"); + nexus.SendAndVerifyEchoRequest(router2, br.Get().GetLinkLocalAddress(), kPingPayloadSize, + Ip6::kDefaultHopLimit, kPingResponseTimeout); + + /** + * Step 5 + * - Device: Router_1 + * - Description (TREL-8.1): Harness instructs device to send a ping (of size 500 bytes or more) to Router_2 using + * mesh-local EID of Router_2 as the destination. Note: this step indirectly verifies the broadcast + * communication by performing address query/resolution. + * - Pass Criteria: + * - The DUT MUST forward a ping response that is successfully received by Router_1 + * - The TREL frames sent by DUT MUST use the correct TREL frame format. + */ + Log("Step 5: Router_1 pings Router_2 using mesh-local EID"); + nexus.SendAndVerifyEchoRequest(router1, router2.Get().GetMeshLocalEid(), kPingPayloadSize, + Ip6::kDefaultHopLimit, kPingResponseTimeout); + + /** + * Step 6 + * - Device: Router_2 + * - Description (TREL-8.1): Harness instructs device to send a ping (of size 500 bytes or more) to Router_1 using + * mesh-local EID of Router_1 as destination. + * - Pass Criteria: + * - The DUT MUST forward a ping response that is successfully received by Router_2. + * - TREL frames sent by DUT MUST use the correct TREL format. + */ + Log("Step 6: Router_2 pings Router_1 using mesh-local EID"); + nexus.SendAndVerifyEchoRequest(router2, router1.Get().GetMeshLocalEid(), kPingPayloadSize, + Ip6::kDefaultHopLimit, kPingResponseTimeout); + + nexus.SaveTestInfo("test_1_4_TREL_TC_1.json"); +} + +#endif // OPENTHREAD_CONFIG_MULTI_RADIO + +} // namespace Nexus +} // namespace ot + +int main(void) +{ +#if OPENTHREAD_CONFIG_MULTI_RADIO + ot::Nexus::Test1_4_Trel_Tc_1(); + printf("All tests passed\n"); +#else + printf("Multi-radio is not enabled - test skipped\n"); +#endif + return 0; +} diff --git a/tests/nexus/verify_1_4_TREL_TC_1.py b/tests/nexus/verify_1_4_TREL_TC_1.py new file mode 100644 index 000000000..3523f1b5f --- /dev/null +++ b/tests/nexus/verify_1_4_TREL_TC_1.py @@ -0,0 +1,224 @@ +#!/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 import consts + + +def verify(pv): + # 8.1. [1.4] [CERT] Attach and connectivity (unicast/broadcast) between multi-radio and single-radio devices + # + # 8.1.1. Purpose + # To test the following: + # 1. Attaching Thread Devices with different radio links. + # 2. Verifies the detection of radio link types supported by neighbors/children. + # 3. Verifies connectivity between nodes (unicast and broadcast frame exchange with multi-radio support). + # + # Spec Reference | V1.1 Section | V1.3.0 Section + # -------------------------|--------------|--------------- + # TREL / Multi-radio Links | N/A | 8.1 + + pkts = pv.pkts + pv.summary.show() + + BR_DUT = pv.vars['BR_DUT'] + ROUTER_1 = pv.vars['ROUTER_1'] + ROUTER_2 = pv.vars['ROUTER_2'] + + BR_DUT_RLOC16 = pv.vars['BR_DUT_RLOC16'] + ROUTER_1_RLOC16 = pv.vars['ROUTER_1_RLOC16'] + ROUTER_2_RLOC16 = pv.vars['ROUTER_2_RLOC16'] + + # Step 1 + # - Device: BR (DUT), Router_1, Router_2 + # - Description (TREL-8.1): Form the topology. Wait for Router_1 and Router_2 to become routers. Note: Router_1 + # and Router_2 MUST NOT be able to communicate directly and not be in each other’s neighbor table (e.g. this + # may be realized by using the allow/deny list mechanism). + # - Pass Criteria: + # - Verify that topology is formed. + # - The DUT MUST be the network Leader. + # - Router_1 MUST only see Leader as its immediate neighbor (Router_2 MUST not be present in Router_1’s + # neighbor table). + # - Router_2 MUST only see Leader as its immediate neighbor and not Router_1. + print("Step 1: Form the topology") + # Verify DUT is leader + pkts.filter_wpan_src64(BR_DUT).\ + filter_mle_cmd(consts.MLE_ADVERTISEMENT).\ + filter(lambda p: p.mle.tlv.leader_data.router_id == p.mle.tlv.source_addr >> 10).\ + must_next() + + # Verify Router_1 and Router_2 do not communicate directly on 15.4 + pkts.filter_wpan_src64(ROUTER_1).filter_wpan_dst64(ROUTER_2).must_not_next() + pkts.filter_wpan_src64(ROUTER_2).filter_wpan_dst64(ROUTER_1).must_not_next() + + # Step 2 + # - Device: Router_1 + # - Description (TREL-8.1): Harness instructs device to report its neighbor multi-radio info (which indicates + # which radio links are detected per neighbor/parent) + # - Pass Criteria: + # - Router_1 MUST have correctly detected that the DUT supports both TREL and 15.4 radios (multi-radio neighbor + # info). + # - The DUT MUST send TREL frames using the correct format + print("Step 2: Router_1 reports its neighbor multi-radio info") + # Verified by C++ code internally + + # Step 3: Router_1 pings the DUT using link-local address + print("Step 3: Router_1 pings the DUT using link-local address") + # Verify ping request from ROUTER_1 to BR_DUT over TREL + # We can't easily decode ICMPv6 inside TREL if tshark fails, so we filter by UDP port and size. + pkts.filter_eth_src(pv.vars['ROUTER_1_ETH']).\ + filter(lambda p: p.eth.dst == pv.vars['BR_DUT_ETH']).\ + filter(lambda p: p.udp).\ + filter(lambda p: p.udp.dstport == pv.vars['BR_DUT_TREL_PORT']).\ + filter(lambda p: int(p.udp.length) > 500).\ + must_next() + + _idx_req = pkts.index + + # Verify ping reply from BR_DUT to ROUTER_1 over TREL + pkts.filter_eth_src(pv.vars['BR_DUT_ETH']).\ + filter(lambda p: p.eth.dst == pv.vars['ROUTER_1_ETH']).\ + filter(lambda p: p.udp).\ + filter(lambda p: p.udp.dstport == pv.vars['ROUTER_1_TREL_PORT']).\ + filter(lambda p: int(p.udp.length) > 500).\ + must_next() + + _idx_reply = pkts.index + + # Verify that there are no ping replies for this exchange on 15.4 + pkts.range(_idx_req, _idx_reply).\ + filter_ping_reply().\ + filter(lambda p: p.wpan).\ + must_not_next() + + # Step 4: Router_2 pings the DUT using link-local address + print("Step 4: Router_2 pings the DUT using link-local address") + # Router_2 is 15.4 only, so ping should be over 15.4 + _pkt = pkts.filter_ping_request().\ + filter_ipv6_src(pv.vars['ROUTER_2_LLA']).\ + filter_ipv6_dst(pv.vars['BR_DUT_LLA']).\ + filter(lambda p: p.wpan).\ + must_next() + + pkts.filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\ + filter_ipv6_src(pv.vars['BR_DUT_LLA']).\ + filter_ipv6_dst(pv.vars['ROUTER_2_LLA']).\ + filter(lambda p: p.wpan).\ + must_next() + + # Step 5: Router_1 pings Router_2 using mesh-local EID + print("Step 5: Router_1 pings Router_2 using mesh-local EID") + # Path: Router_1 -> BR_DUT (TREL) -> Router_2 (15.4) + + # 1. Check forwarded request on 15.4 to get identifier + _pkt_154_req = pkts.filter_ping_request().\ + filter_ipv6_src(pv.vars['ROUTER_1_MLEID']).\ + filter_ipv6_dst(pv.vars['ROUTER_2_MLEID']).\ + filter_wpan_src16_dst16(BR_DUT_RLOC16, ROUTER_2_RLOC16).\ + filter(lambda p: p.wpan).\ + must_next() + + _identifier = _pkt_154_req.icmpv6.echo.identifier + + # 2. Check original request on TREL (before the 15.4 one) + pkts.range((0, 0), pkts.index).\ + filter_eth_src(pv.vars['ROUTER_1_ETH']).\ + filter(lambda p: p.eth.dst == pv.vars['BR_DUT_ETH']).\ + filter(lambda p: p.udp).\ + filter(lambda p: p.udp.dstport == pv.vars['BR_DUT_TREL_PORT']).\ + must_next() + + # 3. Check reply from Router_2 back to BR_DUT over 15.4 + _pkt_154_reply = pkts.filter_ping_reply(identifier=_identifier).\ + filter_ipv6_src(pv.vars['ROUTER_2_MLEID']).\ + filter_ipv6_dst(pv.vars['ROUTER_1_MLEID']).\ + filter_wpan_src16_dst16(ROUTER_2_RLOC16, BR_DUT_RLOC16).\ + filter(lambda p: p.wpan).\ + must_next() + + # 4. Check forwarded reply on TREL (after the 15.4 one) + pkts.range(pkts.index).\ + filter_eth_src(pv.vars['BR_DUT_ETH']).\ + filter(lambda p: p.eth.dst == pv.vars['ROUTER_1_ETH']).\ + filter(lambda p: p.udp).\ + filter(lambda p: p.udp.dstport == pv.vars['ROUTER_1_TREL_PORT']).\ + must_next() + + # Step 6: Router_2 pings Router_1 using mesh-local EID + print("Step 6: Router_2 pings Router_1 using mesh-local EID") + # Path: Router_2 -> BR_DUT (15.4) -> Router_1 (TREL) + + # 1. Check request from Router_2 to BR_DUT over 15.4 + _pkt_154_req = pkts.filter_ping_request().\ + filter_ipv6_src(pv.vars['ROUTER_2_MLEID']).\ + filter_ipv6_dst(pv.vars['ROUTER_1_MLEID']).\ + filter_wpan_src16_dst16(ROUTER_2_RLOC16, BR_DUT_RLOC16).\ + filter(lambda p: p.wpan).\ + must_next() + + _identifier = _pkt_154_req.icmpv6.echo.identifier + + # 2. Check forwarded request on TREL + _idx_154_req = pkts.index + pkts.range(_idx_154_req).\ + filter_eth_src(pv.vars['BR_DUT_ETH']).\ + filter(lambda p: p.eth.dst == pv.vars['ROUTER_1_ETH']).\ + filter(lambda p: p.udp).\ + filter(lambda p: p.udp.dstport == pv.vars['ROUTER_1_TREL_PORT']).\ + must_next() + + # 3. Check reply from Router_1 back to BR_DUT over TREL + _idx_trel_req = pkts.index + pkts.range(_idx_trel_req).\ + filter_eth_src(pv.vars['ROUTER_1_ETH']).\ + filter(lambda p: p.eth.dst == pv.vars['BR_DUT_ETH']).\ + filter(lambda p: p.udp).\ + filter(lambda p: p.udp.dstport == pv.vars['BR_DUT_TREL_PORT']).\ + must_next() + + # 4. Check forwarded reply on 15.4 + _idx_trel_reply = pkts.index + pkts.range(_idx_trel_reply).\ + filter_ping_reply(identifier=_identifier).\ + filter_ipv6_src(pv.vars['ROUTER_1_MLEID']).\ + filter_ipv6_dst(pv.vars['ROUTER_2_MLEID']).\ + filter_wpan_src16_dst16(BR_DUT_RLOC16, ROUTER_2_RLOC16).\ + filter(lambda p: p.wpan).\ + must_next() + + +if __name__ == '__main__': + verify_utils.run_main(verify) diff --git a/tests/nexus/verify_utils.py b/tests/nexus/verify_utils.py index 09e2c812c..5a0329c24 100644 --- a/tests/nexus/verify_utils.py +++ b/tests/nexus/verify_utils.py @@ -495,6 +495,11 @@ def apply_patches(): from pktverify import consts, layer_fields consts.VALID_LAYER_NAMES.add('wpan_tap') consts.VALID_LAYER_NAMES.add('wpan-tap') + consts.VALID_LAYER_NAMES.add('trel') + consts.REAL_LAYER_NAMES.add('trel') + + # Add TREL decoding entries for common Nexus ports + consts.WIRESHARK_DECODE_AS_ENTRIES['udp.port==12343'] = 'trel' # Patch _get_candidate_layers to map wpan_tap to wpan-tap old_get_candidate_layers = layer_fields._get_candidate_layers @@ -708,6 +713,14 @@ def run_main(verify_func): name = pv.test_info.get_node_name(int(node_id)) pv.add_vars(**{f'{name}_RLOC16': int(rloc16, 16)}) + # Add TREL UDP port variables + for node_id, port in data.get('trel_udp_ports', {}).items(): + name = pv.test_info.get_node_name(int(node_id)) + port = int(port) + if port > 0: + pv.add_vars(**{f'{name}_TREL_PORT': port}) + consts.WIRESHARK_DECODE_AS_ENTRIES[f'udp.port=={port}'] = 'trel' + # Add channel variables for node_id, channel in data.get('channels', {}).items(): name = pv.test_info.get_node_name(int(node_id))