diff --git a/tests/scripts/thread-cert/border_router/test_multi_ail.py b/tests/scripts/thread-cert/border_router/test_multi_ail.py new file mode 100755 index 000000000..85423adb7 --- /dev/null +++ b/tests/scripts/thread-cert/border_router/test_multi_ail.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024, 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 ipaddress + +import config +import thread_cert + +IPV4_CIDR_ADDR_CMD = f'ip addr show {config.BACKBONE_IFNAME} | grep -w inet | grep -Eo "[0-9.]+/[0-9]+"' + + +class TwoBorderRoutersOnTwoInfrastructures(thread_cert.TestCase): + """ + Test that two border routers on different infrastructures can ping each other via Thread interface. + + Topology: + + -------(backbone0)-------- | ---------(backbone1)------- + | | + BR1 (Leader) .............. BR2 (Router) + + """ + USE_MESSAGE_FACTORY = False + + BR1 = 1 + BR2 = 2 + + TOPOLOGY = { + BR1: { + 'name': 'BR_1', + 'backbone_network': 'backbone0', + 'allowlist': [BR2], + 'is_otbr': True, + 'version': '1.3', + }, + BR2: { + 'name': 'BR_2', + 'backbone_network': 'backbone1', + 'allowlist': [BR1], + 'is_otbr': True, + 'version': '1.3', + } + } + + def test(self): + br1 = self.nodes[self.BR1] + br2 = self.nodes[self.BR2] + + # start nodes + br1.start() + self.simulator.go(2) + br2.start() + self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY) + + # check roles + self.assertEqual('leader', br1.get_state()) + self.assertEqual('router', br2.get_state()) + + # check two BRs AIL are in different subnets + br1_infra_ip_addr = br1.bash(IPV4_CIDR_ADDR_CMD) + br2_infra_ip_addr = br2.bash(IPV4_CIDR_ADDR_CMD) + + self.assertEqual(len(br1_infra_ip_addr), 1) + self.assertEqual(len(br2_infra_ip_addr), 1) + self.assertNotEqual(ipaddress.ip_network(br1_infra_ip_addr[0].strip(), strict=False), + ipaddress.ip_network(br2_infra_ip_addr[0].strip(), strict=False)) + + # ping each other + self.assertTrue(br1.ping(br2.get_ip6_address(config.ADDRESS_TYPE.ML_EID))) + self.assertTrue(br2.ping(br1.get_ip6_address(config.ADDRESS_TYPE.ML_EID))) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index ebb90dd47..e7141d6df 100644 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -64,8 +64,10 @@ DOMAIN_PREFIX_REGEX_PATTERN = '^fd00:7d03:7d03:7d03:' DOMAIN_PREFIX_ALTER = 'fd00:7d04:7d04:7d04::/64' PORT_OFFSET = int(os.getenv('PORT_OFFSET', '0')) -BACKBONE_PREFIX = f'{0x9100 + PORT_OFFSET:04x}::/64' -BACKBONE_PREFIX_REGEX_PATTERN = f'^{0x9100 + PORT_OFFSET:04x}:' +BACKBONE_IPV6_ADDR_START_BASE = 0x9100 +BACKBONE_IPV6_ADDR_START = BACKBONE_IPV6_ADDR_START_BASE + PORT_OFFSET +BACKBONE_PREFIX = f'{BACKBONE_IPV6_ADDR_START:04x}::/64' +BACKBONE_PREFIX_REGEX_PATTERN = f'^{BACKBONE_IPV6_ADDR_START:04x}:' BACKBONE_DOCKER_NETWORK_NAME = f'backbone{PORT_OFFSET}' BACKBONE_IFNAME = 'eth0' THREAD_IFNAME = 'wpan0' diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 8a5f370ab..35e8f23f4 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -64,8 +64,9 @@ class OtbrDocker: _docker_proc = None _border_routing_counters = None - def __init__(self, nodeid: int, **kwargs): + def __init__(self, nodeid: int, backbone_network: str, **kwargs): self.verbose = int(float(os.getenv('VERBOSE', 0))) + self.backbone_network = backbone_network try: self._docker_name = config.OTBR_DOCKER_NAME_PREFIX + str(nodeid) self._prepare_ot_rcp_sim(nodeid) @@ -121,7 +122,7 @@ class OtbrDocker: '--name', self._docker_name, '--network', - config.BACKBONE_DOCKER_NETWORK_NAME, + self.backbone_network, ] + dns + [ '-i', '--sysctl', @@ -151,7 +152,7 @@ class OtbrDocker: try: subprocess.check_call(f'docker exec -i {self._docker_name} ot-ctl state', shell=True) launch_ok = True - logging.info("OTBR Docker %s Is Ready!", self._docker_name) + logging.info("OTBR Docker %s on %s Is Ready!", self._docker_name, self.backbone_network) break except subprocess.CalledProcessError: time.sleep(5) diff --git a/tests/scripts/thread-cert/thread_cert.py b/tests/scripts/thread-cert/thread_cert.py index 5de900405..58a9aebf5 100644 --- a/tests/scripts/thread-cert/thread_cert.py +++ b/tests/scripts/thread-cert/thread_cert.py @@ -162,14 +162,13 @@ class TestCase(NcpSupportMixin, unittest.TestCase): else: nodeclass = Node - node = nodeclass( - i, - is_mtd=params['is_mtd'], - simulator=self.simulator, - name=params.get('name'), - version=params['version'], - is_bbr=params['is_bbr'], - ) + node = nodeclass(i, + is_mtd=params['is_mtd'], + simulator=self.simulator, + name=params.get('name'), + version=params['version'], + is_bbr=params['is_bbr'], + backbone_network=params['backbone_network']) if 'boot_delay' in params: self.simulator.go(params['boot_delay']) @@ -501,6 +500,9 @@ class TestCase(NcpSupportMixin, unittest.TestCase): assert params.get('version', '') == '', params params['version'] = '' + # set default backbone network for bbr/otbr/host if not specified + params.setdefault('backbone_network', config.BACKBONE_DOCKER_NETWORK_NAME) + # use 1.3 node for 1.2 tests if params.get('version') == '1.2': params['version'] = '1.3' @@ -524,10 +526,30 @@ class TestCase(NcpSupportMixin, unittest.TestCase): return False def _prepare_backbone_network(self): - network_name = config.BACKBONE_DOCKER_NETWORK_NAME - self.assure_run_ok( - f'docker network create --driver bridge --ipv6 --subnet {config.BACKBONE_PREFIX} -o "com.docker.network.bridge.name"="{network_name}" {network_name} || true', - shell=True) + """ + Prepares one or multiple backbone network(s). + + This method creates the backbone network based on the `backbone` values defined in the TOPOLOGY in each test. + If no backbone values are defined, it sets the default backbone network as BACKBONE_DOCKER_NETWORK_NAME + from the config module. + """ + # Use backbone_set to store all the backbone values in TOPOLOGY + backbone_set = set() + for node in self.TOPOLOGY: + backbone = self.TOPOLOGY[node].get('backbone_network') + if backbone: + backbone_set.add(backbone) + + # Set default backbone network if backbone_set is empty + if not backbone_set: + backbone_set.add(config.BACKBONE_DOCKER_NETWORK_NAME) + + # Iterate over the backbone_set and create backbone network(s) + for offset, backbone in enumerate(backbone_set, start=PORT_OFFSET): + backbone_prefix = f'{config.BACKBONE_IPV6_ADDR_START_BASE + offset:04x}::/64' + self.assure_run_ok( + f'docker network create --driver bridge --ipv6 --subnet {backbone_prefix} -o "com.docker.network.bridge.name"="{backbone}" {backbone} || true', + shell=True) def _remove_backbone_network(self): network_name = config.BACKBONE_DOCKER_NETWORK_NAME