From 8707b638942fa396fe0c3c11a918f79082aadb8e Mon Sep 17 00:00:00 2001 From: Simon Lin Date: Tue, 27 Oct 2020 13:46:48 +0800 Subject: [PATCH] [scripts] add MLR Backbone multicast routing test (#5578) - backbone/test_mlr_multicast_routing.py verifies the basic features of Backbone multicast routing - backbone/test_mlr_multicast_routing_timeout.py verifies that MLR timeout works - backbone/test_mlr_multicast_routing_commissioner_timeout.py verifies that Commissioner MLR timeout works --- .github/workflows/simulation-1.2.yml | 2 +- script/test | 5 +- .../backbone/test_mlr_multicast_routing.py | 224 ++++++++++++++++++ ..._multicast_routing_commissioner_timeout.py | 137 +++++++++++ .../test_mlr_multicast_routing_timeout.py | 182 ++++++++++++++ tests/scripts/thread-cert/config.py | 1 - tests/scripts/thread-cert/node.py | 6 +- tests/scripts/thread-cert/pktverify/consts.py | 1 + tests/scripts/thread-cert/pktverify/packet.py | 1 + .../thread-cert/pktverify/packet_filter.py | 9 +- tests/scripts/thread-cert/run_bbr_tests.py | 2 +- tests/scripts/thread-cert/thread_cert.py | 4 +- 12 files changed, 565 insertions(+), 9 deletions(-) create mode 100644 tests/scripts/thread-cert/backbone/test_mlr_multicast_routing.py create mode 100644 tests/scripts/thread-cert/backbone/test_mlr_multicast_routing_commissioner_timeout.py create mode 100644 tests/scripts/thread-cert/backbone/test_mlr_multicast_routing_timeout.py diff --git a/.github/workflows/simulation-1.2.yml b/.github/workflows/simulation-1.2.yml index 41f3236e2..fd592319a 100644 --- a/.github/workflows/simulation-1.2.yml +++ b/.github/workflows/simulation-1.2.yml @@ -227,7 +227,7 @@ jobs: COVERAGE: 1 MULTIPLY: 3 PYTHONUNBUFFERED: 1 - OTBR_COMMIT: "bf28f461fa2a8551f0a4161c04b87f7240856608" # Date: Wed Oct 14 12:00:44 2020 +0800 + VERBOSE: 1 steps: - uses: actions/checkout@v2 - name: Build OTBR Docker diff --git a/script/test b/script/test index 8ab5dc5b6..fbe1c1855 100755 --- a/script/test +++ b/script/test @@ -271,8 +271,8 @@ do_build_otbr_docker() echo "Building OTBR Docker ..." local otdir local otbrdir - local otbr_options="-DOTBR_BACKBONE_ROUTER=ON -DOT_DUA=ON -DOT_MLR=ON -DOT_REFERENCE_DEVICE=ON -DOT_COVERAGE=ON" - local otbr_commit=${OTBR_COMMIT:-master} + local otbr_options="-DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON" + local otbr_commit=${OTBR_COMMIT:-ce1cc3f9141571505bc9a8b705d2eb775849c92c} # ot-br-posix #532 local otbr_docker_image=${OTBR_DOCKER_IMAGE:-otbr-ot12-backbone-ci} otbrdir=$(mktemp -d -t otbr_XXXXXX) @@ -289,6 +289,7 @@ do_build_otbr_docker() cp -r "${otdir}" third_party/openthread/repo rm -rf .git docker build -t "${otbr_docker_image}" -f etc/docker/Dockerfile . \ + --build-arg BACKBONE_ROUTER=1 \ --build-arg REFERENCE_DEVICE=1 \ --build-arg OT_BACKBONE_CI=1 \ --build-arg NAT64=0 \ diff --git a/tests/scripts/thread-cert/backbone/test_mlr_multicast_routing.py b/tests/scripts/thread-cert/backbone/test_mlr_multicast_routing.py new file mode 100644 index 000000000..289bc104f --- /dev/null +++ b/tests/scripts/thread-cert/backbone/test_mlr_multicast_routing.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, 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. +# +# This test verifies that the basic MLR feature works. +# +import logging +import unittest + +import config +import thread_cert +from pktverify.packet_verifier import PacketVerifier + +PBBR = 1 +SBBR = 2 +ROUTER = 3 +LEADER = 4 +FED = 5 +MED = 6 +SED = 7 +HOST = 8 + +SED_POLL_PERIOD = 1000 # Milliseconds + +MA1 = 'ff04::1234:777a:1' +MA2 = 'ff05::1234:777a:2' +MA3 = 'ff05::1234:777a:3' +MA4 = 'ff05::1234:777a:4' +MA5 = 'ff05::1234:777a:5' + +BBR_REGISTRATION_JITTER = 1 +WAIT_REDUNDANCE = 3 + + +class TestMlr(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + + # Topology: + # --------(eth)--------- + # | | | + # PBBR---SBBR HOST + # \ / \ + # \ / \ + # ROUTER--LEADER + # / | \ + # / | \ + # FED MED SED + # + # More links: + # PBBR---LEADER + # + TOPOLOGY = { + PBBR: { + 'name': 'PBBR', + 'allowlist': [SBBR, ROUTER, LEADER], + 'is_otbr': True, + 'version': '1.2', + 'router_selection_jitter': 1, + 'bbr_registration_jitter': BBR_REGISTRATION_JITTER, + }, + SBBR: { + 'name': 'SBBR', + 'allowlist': [PBBR, ROUTER, LEADER], + 'is_otbr': True, + 'version': '1.2', + 'router_selection_jitter': 1, + 'bbr_registration_jitter': BBR_REGISTRATION_JITTER, + }, + ROUTER: { + 'name': 'ROUTER', + 'allowlist': [PBBR, SBBR, LEADER, FED, MED, SED], + 'version': '1.2', + 'router_selection_jitter': 1, + }, + LEADER: { + 'name': 'LEADER', + 'allowlist': [PBBR, SBBR, ROUTER], + 'version': '1.2', + 'router_selection_jitter': 1, + }, + FED: { + 'name': 'FED', + 'allowlist': [ROUTER], + 'version': '1.2', + 'router_upgrade_threshold': 0, + }, + MED: { + 'name': 'MED', + 'allowlist': [ROUTER], + 'version': '1.2', + 'mode': 'rn', + }, + SED: { + 'name': 'SED', + 'allowlist': [ROUTER], + 'version': '1.2', + 'mode': 'n' + }, + HOST: { + 'name': 'Host', + 'is_host': True + }, + } + + def test(self): + # Bring up Host + self.nodes[HOST].start() + + # Bring up Leader + self.nodes[LEADER].start() + self.simulator.go(5) + self.assertEqual('leader', self.nodes[LEADER].get_state()) + + # Bring up Router + self.nodes[ROUTER].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[ROUTER].get_state()) + + # Bring up PBBR + self.nodes[PBBR].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[PBBR].get_state()) + self.nodes[PBBR].enable_backbone_router() + self.simulator.go(10) + self.assertTrue(self.nodes[PBBR].is_primary_backbone_router) + self.nodes[PBBR].add_prefix(config.DOMAIN_PREFIX, "parosD") + self.nodes[PBBR].register_netdata() + + # Bring up SBBR + self.nodes[SBBR].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[SBBR].get_state()) + self.nodes[SBBR].enable_backbone_router() + self.simulator.go(10) + self.assertFalse(self.nodes[SBBR].is_primary_backbone_router) + + # Bring up FED, MED, SED + self.nodes[FED].start() + self.nodes[MED].start() + self.nodes[SED].set_pollperiod(SED_POLL_PERIOD) + self.nodes[SED].start() + self.simulator.go(5) + self.assertEqual('child', self.nodes[FED].get_state()) + self.assertEqual('child', self.nodes[MED].get_state()) + self.assertEqual('child', self.nodes[SED].get_state()) + + # Verify Multicast Routing works for all devices + self._verify_multicast_routing(ROUTER, MA1) + self._verify_multicast_routing(LEADER, MA2) + self._verify_multicast_routing(FED, MA3) + self._verify_multicast_routing(MED, MA4, is_med=True) + self._verify_multicast_routing(SED, MA5, is_med=True, is_sed=True) + + # Verify MA_scope2 is not reachable from Host + MA_scope2 = 'ff02::10' + self.nodes[ROUTER].add_ipmaddr(MA_scope2) + self.simulator.go(3) + self.assertFalse(self.nodes[HOST].ping(MA_scope2, backbone=True, ttl=10)) + self.nodes[ROUTER].del_ipmaddr(MA_scope2) + + # Verify MA_scope3 is not reachable from Host + MA_scope3 = 'ff03::1234:777a:5' + self.nodes[ROUTER].add_ipmaddr(MA_scope3) + self.simulator.go(3) + self.assertFalse(self.nodes[HOST].ping(MA_scope3, backbone=True, ttl=10)) + self.nodes[ROUTER].del_ipmaddr(MA_scope3) + + # Router subscribes MA2 and MA3 at the same time and verify that they are both reachable + self.nodes[ROUTER].add_ipmaddr(MA2) + self.nodes[ROUTER].add_ipmaddr(MA3) + self.simulator.go(3) + self.assertTrue(self.nodes[HOST].ping(MA2, backbone=True, ttl=10)) + self.assertTrue(self.nodes[HOST].ping(MA3, backbone=True, ttl=10)) + self.nodes[ROUTER].del_ipmaddr(MA2) + self.nodes[ROUTER].del_ipmaddr(MA3) + self.simulator.go(1) + + def _verify_multicast_routing(self, nodeid: int, ma: str, is_med=False, is_sed=False): + logging.info('_verify_multicast_routing: nodeid=%d, MA=%s', nodeid, ma) + # Verify MA is not reachable from Host initially + self.assertFalse(self.nodes[HOST].ping(ma, backbone=True, ttl=10)) + + # Device subscribes MA + self.nodes[nodeid].add_ipmaddr(ma) + self.simulator.go(3 + (SED_POLL_PERIOD * 2 / 1000) * is_sed + config.PARENT_AGGREGATIOIN_DELAY * is_med + + WAIT_REDUNDANCE) + + # Verify MA is reachable from Host + self.assertTrue(self.nodes[HOST].ping(ma, backbone=True, ttl=10)) + + # Device unsubscribes MA + self.nodes[nodeid].del_ipmaddr(ma) + self.simulator.go(1) + + # Verify MA is not reachable from Host after unsubscribed + self.assertFalse(self.nodes[HOST].ping(ma, backbone=True, ttl=10)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/backbone/test_mlr_multicast_routing_commissioner_timeout.py b/tests/scripts/thread-cert/backbone/test_mlr_multicast_routing_commissioner_timeout.py new file mode 100644 index 000000000..4ba2b7b23 --- /dev/null +++ b/tests/scripts/thread-cert/backbone/test_mlr_multicast_routing_commissioner_timeout.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, 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. +# +# This test verifies that the MLR timeout configured by Commissioner works for multicast routing. +# +import unittest + +from pktverify.consts import NM_COMMISSIONER_SESSION_ID_TLV +from pktverify.packet_verifier import PacketVerifier +from test_mlr_multicast_routing_timeout import ROUTER, HOST +from test_mlr_multicast_routing_timeout import TestMlrTimeout, MLR_TIMEOUT, WAIT_REDUNDANCE, MA1 + +COMMISSIONER_MLR_TIMEOUT = 360 + +assert COMMISSIONER_MLR_TIMEOUT > MLR_TIMEOUT + WAIT_REDUNDANCE * 3, "COMMISSIONER_MLR_TIMEOUT should be significantly larger than MLR_TIMEOUT" + + +class TestMlrCommissionerTimeout(TestMlrTimeout): + + # Topology (same as TestMlrTimeout): + # --------(eth)--------- + # | | + # PBBR(Leader) HOST + # | + # ROUTER + # + + def test(self): + self._bootstrap() + + # Ping 1: PBBR should not forward to Thread network + self.assertFalse(self.nodes[HOST].ping(MA1, backbone=True, ttl=10)) + + # Router starts Commissioner + self.nodes[ROUTER].commissioner_start() + self.simulator.go(10) + self.assertEqual('active', self.nodes[ROUTER].commissioner_state()) + + # Router (Commissioner) registers MA1 + self.nodes[ROUTER].register_multicast_listener(MA1, timeout=COMMISSIONER_MLR_TIMEOUT) + self.simulator.go(WAIT_REDUNDANCE) + + # Ping 2: PBBR should forward to Thread network + self.assertFalse(self.nodes[HOST].ping(MA1, backbone=True, ttl=10)) + + self.simulator.go(MLR_TIMEOUT + WAIT_REDUNDANCE) + + # Ping 3: PBBR should forward to Thread network + self.assertFalse(self.nodes[HOST].ping(MA1, backbone=True, ttl=10)) + + self.simulator.go(COMMISSIONER_MLR_TIMEOUT - MLR_TIMEOUT) + + # Ping 4: PBBR should NOT forward to Thread network + self.assertFalse(self.nodes[HOST].ping(MA1, backbone=True, ttl=10)) + + def verify(self, pv: PacketVerifier): + pkts = pv.pkts + pv.add_common_vars() + pv.summary.show() + + ROUTER = pv.vars['ROUTER'] + PBBR = pv.vars['PBBR'] + MM = pv.vars['MM_PORT'] + HOST_ETH = pv.vars['Host_ETH'] + HOST_BGUA = pv.vars['Host_BGUA'] + + start = pkts.index + + # Verify that Router sends MLR.req for MA1 with Commissioner timeout + pkts.filter_wpan_src64(ROUTER).filter_coap_request('/n/mr', port=MM).must_next().must_verify( + """ + thread_meshcop.tlv.ipv6_addr == {ipv6_addr} + and thread_nm.tlv.timeout == {timeout} + and {NM_COMMISSIONER_SESSION_ID_TLV} in thread_nm.tlv.type + """, + ipv6_addr=[MA1], + timeout=COMMISSIONER_MLR_TIMEOUT, + NM_COMMISSIONER_SESSION_ID_TLV=NM_COMMISSIONER_SESSION_ID_TLV) + + mr_index = pkts.index + + # Ping 1: Host pings Router before MLR.reg + before_mr_pkts = pkts.range(start, mr_index, cascade=False) + ping1 = before_mr_pkts.filter_eth_src(HOST_ETH).filter_ipv6_dst(MA1).filter_ping_request().must_next() + + # PBBR should not forward Ping 1 to Thread network + before_mr_pkts.filter_wpan_src64(PBBR).filter_ipv6_dst(MA1).filter_ping_request( + identifier=ping1.icmpv6.echo.identifier).must_not_next() + + # Ping 2: Host pings Router after MLR.req + ping2 = pkts.filter_eth_src(HOST_ETH).filter_ipv6_dst(MA1).filter_ping_request().must_next() + # PBBR should forward this ping request to Thread network + pkts.filter_wpan_src64(PBBR).filter_AMPLFMA().filter_ping_request( + identifier=ping2.icmpv6.echo.identifier).must_next() + + # Ping 3: Host pings Router after delayed MLR Timeout + ping3 = pkts.filter_eth_src(HOST_ETH).filter_ipv6_dst(MA1).filter_ping_request().must_next() + # PBBR should forward this ping request to Thread network because MA doesn't expire yet + pkts.filter_wpan_src64(PBBR).filter_AMPLFMA().filter_ping_request( + identifier=ping3.icmpv6.echo.identifier).must_next() + + # Ping 4: Host pings Router after delayed Commissioner MLR Timeout + ping4 = pkts.filter_eth_src(HOST_ETH).filter_ipv6_dst(MA1).filter_ping_request().must_next() + # PBBR should NOT forward this ping request to Thread network because MLR has timeout + pkts.filter_wpan_src64(PBBR).filter_AMPLFMA().filter_ping_request( + identifier=ping4.icmpv6.echo.identifier).must_not_next() + + +del TestMlrTimeout + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/backbone/test_mlr_multicast_routing_timeout.py b/tests/scripts/thread-cert/backbone/test_mlr_multicast_routing_timeout.py new file mode 100644 index 000000000..3cdd80c45 --- /dev/null +++ b/tests/scripts/thread-cert/backbone/test_mlr_multicast_routing_timeout.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, 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. +# +# This test verifies that the MLR timeout works for multicast routing. +# +import unittest + +import config +import thread_cert +from pktverify.packet_verifier import PacketVerifier + +PBBR = 1 +ROUTER = 2 +HOST = 3 + +MA1 = 'ff05::1234:777a:1' + +BBR_REGISTRATION_JITTER = 1 +REG_DELAY = 10 +MLR_TIMEOUT = 300 +WAIT_REDUNDANCE = 3 + + +class TestMlrTimeout(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + + # Topology: + # --------(eth)--------- + # | | + # PBBR(Leader) HOST + # | + # ROUTER + # + TOPOLOGY = { + PBBR: { + 'name': 'PBBR', + 'allowlist': [ROUTER], + 'is_otbr': True, + 'version': '1.2', + 'router_selection_jitter': 1, + 'bbr_registration_jitter': BBR_REGISTRATION_JITTER, + }, + ROUTER: { + 'name': 'ROUTER', + 'allowlist': [PBBR], + 'version': '1.2', + 'router_selection_jitter': 1, + }, + HOST: { + 'name': 'Host', + 'is_host': True + }, + } + + def _bootstrap(self): + # Bring up Host + self.nodes[HOST].start() + + # Bring up PBBR + self.nodes[PBBR].start() + self.simulator.go(5) + self.assertEqual('leader', self.nodes[PBBR].get_state()) + self.nodes[PBBR].enable_backbone_router() + self.nodes[PBBR].set_backbone_router(reg_delay=REG_DELAY, mlr_timeout=MLR_TIMEOUT) + self.simulator.go(10) + self.assertTrue(self.nodes[PBBR].is_primary_backbone_router) + self.nodes[PBBR].add_prefix(config.DOMAIN_PREFIX, "parosD") + self.nodes[PBBR].register_netdata() + + # Bring up Router + self.nodes[ROUTER].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[ROUTER].get_state()) + + self.collect_ipaddrs() + + def test(self): + self._bootstrap() + + self.assertFalse(self.nodes[HOST].ping(MA1, backbone=True, ttl=10)) + + # Router subscribes MA + self.nodes[ROUTER].add_ipmaddr(MA1) + self.simulator.go(WAIT_REDUNDANCE) + + # Verify MA is reachable from Host + self.assertTrue(self.nodes[HOST].ping(MA1, backbone=True, ttl=10)) + self.simulator.go(WAIT_REDUNDANCE) + + # Router unsubscribes MA + self.nodes[ROUTER].del_ipmaddr(MA1) + self.simulator.go(WAIT_REDUNDANCE) + + # Verify MA is not reachable from Host after unsubscribed + # But PBBR should still forward the Ping Requests to Thread network + self.assertFalse(self.nodes[HOST].ping(MA1, backbone=True, ttl=10)) + + self.simulator.go(MLR_TIMEOUT + WAIT_REDUNDANCE) + + # Verify MA is not reachable from Host after MLR timeout + # PBBR should not forward the Ping Requests to Thread network + self.assertFalse(self.nodes[HOST].ping(MA1, backbone=True, ttl=10)) + + def verify(self, pv: PacketVerifier): + pkts = pv.pkts + pv.add_common_vars() + pv.summary.show() + + ROUTER = pv.vars['ROUTER'] + PBBR = pv.vars['PBBR'] + MM = pv.vars['MM_PORT'] + HOST_ETH = pv.vars['Host_ETH'] + HOST_BGUA = pv.vars['Host_BGUA'] + + start = pkts.index + + # Verify that Router sends MLR.req for MA1 + pkts.filter_wpan_src64(ROUTER).filter_coap_request('/n/mr', port=MM).must_next().must_verify( + 'thread_meshcop.tlv.ipv6_addr == {ipv6_addr}', ipv6_addr=[MA1]) + + mr_index = pkts.index + + # Host pings Router before MLR.reg + before_mr_pkts = pkts.range(start, mr_index, cascade=False) + ping = before_mr_pkts.filter_eth_src(HOST_ETH).filter_ipv6_dst(MA1).filter_ping_request().must_next() + + # PBBR should not forward this ping request to Thread network + before_mr_pkts.filter_wpan_src64(PBBR).filter_ipv6_dst(MA1).filter_ping_request( + identifier=ping.icmpv6.echo.identifier).must_not_next() + + # Host pings Router after MLR.req + ping = pkts.filter_eth_src(HOST_ETH).filter_ipv6_dst(MA1).filter_ping_request().must_next() + # PBBR should forward this ping request to Thread network + pkts.filter_wpan_src64(PBBR).filter_AMPLFMA().filter_ping_request( + identifier=ping.icmpv6.echo.identifier).must_next() + # Router should reply + pkts.filter_wpan_src64(ROUTER).filter_ipv6_dst(HOST_BGUA).filter_ping_reply( + identifier=ping.icmpv6.echo.identifier).must_next() + + # Host pings Router after Router unsubscribed MA + ping = pkts.filter_eth_src(HOST_ETH).filter_ipv6_dst(MA1).filter_ping_request().must_next() + # PBBR should forward this ping request to Thread network because MA doesn't expire yet + pkts.filter_wpan_src64(PBBR).filter_AMPLFMA().filter_ping_request( + identifier=ping.icmpv6.echo.identifier).must_next() + # Router should NOT reply because it has unsubcribed the multicast address + pkts.filter_wpan_src64(ROUTER).filter_ipv6_dst(HOST_BGUA).filter_ping_reply( + identifier=ping.icmpv6.echo.identifier).must_not_next() + + # Host pings Router after MLR timeout + ping = pkts.filter_eth_src(HOST_ETH).filter_ipv6_dst(MA1).filter_ping_request().must_next() + # PBBR should NOT forward this ping request to Thread network because MLR has timeout + pkts.filter_wpan_src64(PBBR).filter_AMPLFMA().filter_ping_request( + identifier=ping.icmpv6.echo.identifier).must_not_next() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index 2e032f611..9812594e7 100644 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -71,7 +71,6 @@ BACKBONE_IFNAME = 'eth0' OTBR_DOCKER_IMAGE = os.getenv('OTBR_DOCKER_IMAGE', 'otbr-ot12-backbone-ci') OTBR_DOCKER_NAME_PREFIX = f'otbr_{PORT_OFFSET}_' -OTBR_COMMIT = os.getenv('OTBR_COMMIT', 'master') ALL_NETWORK_BBRS_ADDRESS = 'ff32:40:fd00:db8:0:0:0:3' diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 5f1353726..afbe8b89a 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -1956,11 +1956,14 @@ class LinuxHost(): assert False, output - def ping_ether(self, ipaddr, num_responses=1, size=None, timeout=5) -> int: + def ping_ether(self, ipaddr, num_responses=1, size=None, timeout=5, ttl=None) -> int: cmd = f'ping -6 {ipaddr} -I eth0 -c {num_responses} -W {timeout}' if size is not None: cmd += f' -s {size}' + if ttl is not None: + cmd += f' -t {ttl}' + resp_count = 0 try: @@ -1990,6 +1993,7 @@ class LinuxHost(): class OtbrNode(LinuxHost, NodeImpl, OtbrDocker): is_otbr = True is_bbr = True # OTBR is also BBR + node_type = 'otbr-docker' def __repr__(self): return f'Otbr<{self.nodeid}>' diff --git a/tests/scripts/thread-cert/pktverify/consts.py b/tests/scripts/thread-cert/pktverify/consts.py index 3ae841d96..4f51a4c94 100644 --- a/tests/scripts/thread-cert/pktverify/consts.py +++ b/tests/scripts/thread-cert/pktverify/consts.py @@ -136,6 +136,7 @@ NL_ROUTER_MASK_TLV = 7 NL_ND_OPTION_TLV = 8 NL_ND_DATA_TLV = 9 NL_THREAD_NETWORK_DATA_TLV = 10 +NM_COMMISSIONER_SESSION_ID_TLV = 15 # Network Layer Status NL_SUCESS = 0 diff --git a/tests/scripts/thread-cert/pktverify/packet.py b/tests/scripts/thread-cert/pktverify/packet.py index 20f7a9dd3..6ed839646 100644 --- a/tests/scripts/thread-cert/pktverify/packet.py +++ b/tests/scripts/thread-cert/pktverify/packet.py @@ -128,6 +128,7 @@ class Packet(object): def must_verify(self, func: Union[str, Callable], **vars): if not self.verify(func, **vars): + self.debug_fields() raise errors.VerifyFailed(self) def must_not_verify(self, func: Union[str, Callable], **vars): diff --git a/tests/scripts/thread-cert/pktverify/packet_filter.py b/tests/scripts/thread-cert/pktverify/packet_filter.py index 0076a9f03..ba386bf0f 100644 --- a/tests/scripts/thread-cert/pktverify/packet_filter.py +++ b/tests/scripts/thread-cert/pktverify/packet_filter.py @@ -436,8 +436,10 @@ class PacketFilter(object): assert isinstance(addr, (str, ExtAddr)), addr return self.filter(lambda p: p.wpan.dst64 == addr, **kwargs) - def filter_ping_request(self, **kwargs): - return self.filter(lambda p: p.icmpv6.is_ping_request, **kwargs) + def filter_ping_request(self, identifier=None, **kwargs): + return self.filter( + lambda p: p.icmpv6.is_ping_request and (identifier is None or p.icmpv6.echo.identifier == identifier), + **kwargs) def filter_ping_reply(self, **kwargs): identifier = kwargs.pop('identifier', None) @@ -475,6 +477,9 @@ class PacketFilter(object): def filter_LLARMA(self, **kwargs): return self.filter(lambda p: p.ipv6.dst == consts.LINK_LOCAL_ALL_ROUTERS_MULTICAST_ADDRESS, **kwargs) + def filter_AMPLFMA(self, **kwargs): + return self.filter(lambda p: p.ipv6.dst == consts.ALL_MPL_FORWARDERS_MA, **kwargs) + def filter_mle(self, **kwargs): return self.filter(attrgetter('mle'), **kwargs) diff --git a/tests/scripts/thread-cert/run_bbr_tests.py b/tests/scripts/thread-cert/run_bbr_tests.py index 4701f86df..4ac5914a6 100644 --- a/tests/scripts/thread-cert/run_bbr_tests.py +++ b/tests/scripts/thread-cert/run_bbr_tests.py @@ -36,7 +36,7 @@ from collections import Counter import config -MULTIPLE_JOBS = 10 +MULTIPLE_JOBS = 20 logging.basicConfig(level=logging.DEBUG, format='File "%(pathname)s", line %(lineno)d, in %(funcName)s\n' diff --git a/tests/scripts/thread-cert/thread_cert.py b/tests/scripts/thread-cert/thread_cert.py index 8c4ab55ac..b3a16b70a 100644 --- a/tests/scripts/thread-cert/thread_cert.py +++ b/tests/scripts/thread-cert/thread_cert.py @@ -208,6 +208,9 @@ class TestCase(NcpSupportMixin, unittest.TestCase): if 'max_children' in params: self.nodes[i].set_max_children(params['max_children']) + if 'bbr_registration_jitter' in params: + self.nodes[i].set_bbr_registration_jitter(params['bbr_registration_jitter']) + # we have to add allowlist after nodes are all created for i, params in initial_topology.items(): allowlist = params['allowlist'] @@ -364,7 +367,6 @@ class TestCase(NcpSupportMixin, unittest.TestCase): 'interface': config.BACKBONE_DOCKER_NETWORK_NAME, 'prefix': config.BACKBONE_PREFIX, }, - 'otbr_commit': config.OTBR_COMMIT, 'domain_prefix': config.DOMAIN_PREFIX, 'env': { 'PORT_OFFSET': config.PORT_OFFSET,