1#!/usr/bin/env python3
2#
3#  Copyright (c) 2020, The OpenThread Authors.
4#  All rights reserved.
5#
6#  Redistribution and use in source and binary forms, with or without
7#  modification, are permitted provided that the following conditions are met:
8#  1. Redistributions of source code must retain the above copyright
9#     notice, this list of conditions and the following disclaimer.
10#  2. Redistributions in binary form must reproduce the above copyright
11#     notice, this list of conditions and the following disclaimer in the
12#     documentation and/or other materials provided with the distribution.
13#  3. Neither the name of the copyright holder nor the
14#     names of its contributors may be used to endorse or promote products
15#     derived from this software without specific prior written permission.
16#
17#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
18#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27#  POSSIBILITY OF SUCH DAMAGE.
28#
29import logging
30import unittest
31from ipaddress import IPv6Network
32
33import config
34import thread_cert
35
36# Test description:
37#   This test verifies that a single OMR and on-link prefix is chosen
38#   and advertised when there are multiple Border Routers in the same
39#   Thread and infrastructure network.
40#
41# Topology:
42#    ----------------(eth)------------------
43#           |                  |     |
44#          BR1 (Leader) ----- BR2   HOST
45#           |                  |
46#        ROUTER1            ROUTER2
47#
48
49BR1 = 1
50ROUTER1 = 2
51BR2 = 3
52ROUTER2 = 4
53HOST = 5
54
55CHANNEL = 18
56
57
58class MultiBorderRouters(thread_cert.TestCase):
59    USE_MESSAGE_FACTORY = False
60
61    TOPOLOGY = {
62        BR1: {
63            'name': 'BR_1',
64            'allowlist': [ROUTER1, BR2],
65            'is_otbr': True,
66            'version': '1.2',
67            'channel': CHANNEL,
68        },
69        ROUTER1: {
70            'name': 'Router_1',
71            'allowlist': [BR1],
72            'version': '1.2',
73            'channel': CHANNEL,
74        },
75        BR2: {
76            'name': 'BR_2',
77            'allowlist': [BR1, ROUTER2],
78            'is_otbr': True,
79            'version': '1.2',
80            'channel': CHANNEL,
81        },
82        ROUTER2: {
83            'name': 'Router_2',
84            'allowlist': [BR2],
85            'version': '1.2',
86            'channel': CHANNEL,
87        },
88        HOST: {
89            'name': 'Host',
90            'is_host': True
91        },
92    }
93
94    def test(self):
95        br1 = self.nodes[BR1]
96        router1 = self.nodes[ROUTER1]
97        br2 = self.nodes[BR2]
98        router2 = self.nodes[ROUTER2]
99        host = self.nodes[HOST]
100
101        host.start(start_radvd=False)
102        self.simulator.go(5)
103
104        br1.start()
105        self.simulator.go(config.LEADER_STARTUP_DELAY)
106        self.assertEqual('leader', br1.get_state())
107
108        router1.start()
109        self.simulator.go(config.ROUTER_STARTUP_DELAY)
110        self.assertEqual('router', router1.get_state())
111
112        self.simulator.go(5)
113
114        br2.start()
115        self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY)
116        self.assertEqual('router', br2.get_state())
117
118        router2.start()
119        self.simulator.go(config.ROUTER_STARTUP_DELAY)
120        self.assertEqual('router', router2.get_state())
121
122        #
123        # Case 1. bi-directional connectivity when there are two BRs.
124        #
125
126        self.simulator.go(10)
127        self.collect_ipaddrs()
128
129        logging.info("BR1     addrs: %r", br1.get_addrs())
130        logging.info("ROUTER1 addrs: %r", router1.get_addrs())
131        logging.info("BR2     addrs: %r", br2.get_addrs())
132        logging.info("ROUTER2 addrs: %r", router2.get_addrs())
133        logging.info("HOST    addrs: %r", host.get_addrs())
134
135        self.assertEqual(len(br1.get_netdata_omr_prefixes()), 1)
136        self.assertEqual(len(router1.get_netdata_omr_prefixes()), 1)
137        self.assertEqual(len(br2.get_netdata_omr_prefixes()), 1)
138        self.assertEqual(len(router2.get_netdata_omr_prefixes()), 1)
139
140        br1_omr_prefix = br1.get_br_omr_prefix()
141        self.assertEqual(br1_omr_prefix, br1.get_netdata_omr_prefixes()[0])
142
143        # Each BR should independently register an external route for the on-link prefix.
144        self.assertEqual(len(br1.get_netdata_non_nat64_routes()), 2)
145        self.assertEqual(len(router1.get_netdata_non_nat64_routes()), 2)
146        self.assertEqual(len(br2.get_netdata_non_nat64_routes()), 2)
147        self.assertEqual(len(router2.get_netdata_non_nat64_routes()), 2)
148
149        br1_on_link_prefix = br1.get_br_on_link_prefix()
150
151        self.assertEqual(len(br1.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
152        self.assertEqual(len(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
153        self.assertEqual(len(br2.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
154        self.assertEqual(len(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
155        self.assertEqual(len(host.get_matched_ula_addresses(br1_on_link_prefix)), 1)
156
157        # Router1 and Router2 can ping each other inside the Thread network.
158        self.assertTrue(router1.ping(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
159        self.assertTrue(router2.ping(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
160
161        # Both Router1 and Router2 can ping to/from the Host on infra link.
162        self.assertTrue(router1.ping(host.get_matched_ula_addresses(br1_on_link_prefix)[0]))
163        self.assertTrue(host.ping(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], backbone=True))
164        self.assertTrue(router2.ping(host.get_matched_ula_addresses(br1_on_link_prefix)[0]))
165        self.assertTrue(host.ping(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], backbone=True))
166
167        #
168        # Case 2. Another BR continues providing Border Routing when current one is disabled.
169        #
170
171        br1.disable_br()
172
173        self.simulator.go(315)
174        self.collect_ipaddrs()
175
176        logging.info("BR1     addrs: %r", br1.get_addrs())
177        logging.info("ROUTER1 addrs: %r", router1.get_addrs())
178        logging.info("BR2     addrs: %r", br2.get_addrs())
179        logging.info("ROUTER2 addrs: %r", router2.get_addrs())
180        logging.info("HOST    addrs: %r", host.get_addrs())
181
182        self.assertGreaterEqual(len(host.get_addrs()), 3)
183
184        self.assertEqual(len(br1.get_netdata_omr_prefixes()), 1)
185        self.assertEqual(len(router1.get_netdata_omr_prefixes()), 1)
186        self.assertEqual(len(br2.get_netdata_omr_prefixes()), 1)
187        self.assertEqual(len(router2.get_netdata_omr_prefixes()), 1)
188
189        br2_omr_prefix = br2.get_br_omr_prefix()
190        self.assertEqual(br2_omr_prefix, br2.get_netdata_omr_prefixes()[0])
191
192        # There should be no changes to the external route for the
193        # on-link prefix, given that the on-link prefix is derived
194        # from the Extended PAN ID.
195        self.assertEqual(len(br1.get_netdata_non_nat64_routes()), 1)
196        self.assertEqual(len(router1.get_netdata_non_nat64_routes()), 1)
197        self.assertEqual(len(br2.get_netdata_non_nat64_routes()), 1)
198        self.assertEqual(len(router2.get_netdata_non_nat64_routes()), 1)
199
200        br2_on_link_prefix = br2.get_br_on_link_prefix()
201
202        self.assertEqual(len(br1.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
203        self.assertEqual(len(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
204        self.assertEqual(len(br2.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
205        self.assertEqual(len(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)), 1)
206
207        self.assertEqual(len(host.get_matched_ula_addresses(br2_on_link_prefix)), 1)
208
209        # Router1 and Router2 can ping each other inside the Thread network.
210        self.assertTrue(router1.ping(router2.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
211        self.assertTrue(router2.ping(router1.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
212
213        # Both Router1 and Router2 can ping to/from the Host on infra link.
214        for router in [router1, router2]:
215            self.assertTrue(router.ping(host.get_matched_ula_addresses(br2_on_link_prefix)[0]))
216            self.assertTrue(host.ping(router.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], backbone=True))
217
218
219if __name__ == '__main__':
220    unittest.main()
221