xref: /aosp_15_r20/external/openthread/tests/toranj/cli/test-018-next-hop-and-path-cost.py (revision cfb92d1480a9e65faed56933e9c12405f45898b4)
1#!/usr/bin/env python3
2#
3#  Copyright (c) 2023, 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
29from cli import verify
30from cli import verify_within
31import cli
32import time
33
34# -----------------------------------------------------------------------------------------------------------------------
35# Test description: Next hop and path cost calculation.
36#
37# Network topology
38#
39#     r1 ---- r2...2...r3
40#    / |       \      /  \
41#   /  |        \    /    \
42# fed1 fed2       r4...1...r5 ---- fed3
43#
44# Link r2 --> r3 is configured to be at link quality of 2.
45# Link r5 --> r4 is configured to be at link quality of 1.
46# Link r1 --> fed2 is configured to be at link quality 1.
47# Other links are at link quality 3 (best possible link quality).
48#
49
50test_name = __file__[:-3] if __file__.endswith('.py') else __file__
51print('-' * 120)
52print('Starting \'{}\''.format(test_name))
53
54# -----------------------------------------------------------------------------------------------------------------------
55# Creating `cli.Node` instances
56
57speedup = 40
58cli.Node.set_time_speedup_factor(speedup)
59
60r1 = cli.Node()
61r2 = cli.Node()
62r3 = cli.Node()
63r4 = cli.Node()
64r5 = cli.Node()
65fed1 = cli.Node()
66fed2 = cli.Node()
67fed3 = cli.Node()
68
69# -----------------------------------------------------------------------------------------------------------------------
70# Form topology
71
72r1.allowlist_node(r2)
73r1.allowlist_node(fed1)
74r1.allowlist_node(fed2)
75
76r2.allowlist_node(r1)
77r2.allowlist_node(r3)
78r2.allowlist_node(r4)
79
80r3.allowlist_node(r2)
81r3.allowlist_node(r4)
82r3.allowlist_node(r5)
83r3.set_macfilter_lqi_to_node(r2, 2)
84
85r4.allowlist_node(r2)
86r4.allowlist_node(r3)
87r4.allowlist_node(r5)
88r4.set_macfilter_lqi_to_node(r5, 1)
89
90r5.allowlist_node(r4)
91r5.allowlist_node(r3)
92r5.allowlist_node(fed3)
93
94fed1.allowlist_node(r1)
95fed2.allowlist_node(r1)
96fed3.allowlist_node(r5)
97fed2.set_macfilter_lqi_to_node(r1, 1)
98
99r1.form('hop-cost')
100r2.join(r1)
101r3.join(r1)
102r4.join(r1)
103r5.join(r1)
104fed1.join(r1, cli.JOIN_TYPE_REED)
105fed2.join(r1, cli.JOIN_TYPE_REED)
106fed3.join(r1, cli.JOIN_TYPE_REED)
107
108# -----------------------------------------------------------------------------------------------------------------------
109# Test Implementation
110
111r1_rloc = int(r1.get_rloc16(), 16)
112r2_rloc = int(r2.get_rloc16(), 16)
113r3_rloc = int(r3.get_rloc16(), 16)
114r4_rloc = int(r4.get_rloc16(), 16)
115r5_rloc = int(r5.get_rloc16(), 16)
116
117fed1_rloc = int(fed1.get_rloc16(), 16)
118fed2_rloc = int(fed2.get_rloc16(), 16)
119fed3_rloc = int(fed3.get_rloc16(), 16)
120
121
122def parse_nexthop(line):
123    # Example: "0x5000 cost:3" -> (0x5000, 3).
124    items = line.strip().split(' ', 2)
125    return (int(items[0], 16), int(items[1].split(':')[1]))
126
127
128def check_nexthops_and_costs():
129    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
130    # `r1` next hops and costs
131    verify(parse_nexthop(r1.get_nexthop(r1_rloc)) == (r1_rloc, 0))
132    verify(parse_nexthop(r1.get_nexthop(r2_rloc)) == (r2_rloc, 1))
133    verify(parse_nexthop(r1.get_nexthop(r3_rloc)) == (r2_rloc, 3))
134    verify(parse_nexthop(r1.get_nexthop(r4_rloc)) == (r2_rloc, 2))
135    verify(parse_nexthop(r1.get_nexthop(r5_rloc)) == (r2_rloc, 4))
136    verify(parse_nexthop(r1.get_nexthop(fed3_rloc)) == (r2_rloc, 5))
137    # On `r1` its children can be reached directly.
138    verify(parse_nexthop(r1.get_nexthop(fed1_rloc)) == (fed1_rloc, 1))
139    verify(parse_nexthop(r1.get_nexthop(fed2_rloc)) == (fed2_rloc, 1))
140
141    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
142    # `r2` next hops and costs
143    verify(parse_nexthop(r2.get_nexthop(r1_rloc)) == (r1_rloc, 1))
144    verify(parse_nexthop(r2.get_nexthop(r2_rloc)) == (r2_rloc, 0))
145    # On `r2` the direct link to `r3` and the path through `r4` both
146    # have the same cost, but the direct link should be preferred.
147    verify(parse_nexthop(r2.get_nexthop(r3_rloc)) == (r3_rloc, 2))
148    verify(parse_nexthop(r2.get_nexthop(r4_rloc)) == (r4_rloc, 1))
149    # On 'r2' the path to `r5` can go through `r3` or `r4`
150    # as both have the same cost.
151    (nexthop, cost) = parse_nexthop(r2.get_nexthop(r5_rloc))
152    verify(cost == 3)
153    verify(nexthop in [r3_rloc, r4_rloc])
154    verify(parse_nexthop(r2.get_nexthop(fed1_rloc)) == (r1_rloc, 2))
155
156    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
157    # `r3` next hops and costs
158    verify(parse_nexthop(r3.get_nexthop(r3_rloc)) == (r3_rloc, 0))
159    verify(parse_nexthop(r3.get_nexthop(r5_rloc)) == (r5_rloc, 1))
160    verify(parse_nexthop(r3.get_nexthop(r4_rloc)) == (r4_rloc, 1))
161    verify(parse_nexthop(r3.get_nexthop(r2_rloc)) == (r2_rloc, 2))
162    # On `r3` the path to `r1` can go through `r2` or `r4`
163    # as both have the same cost.
164    (nexthop, cost) = parse_nexthop(r3.get_nexthop(r1_rloc))
165    verify(cost == 3)
166    verify(nexthop in [r2_rloc, r4_rloc])
167    # On `r3` the path to fed1 should use the same next hop as `r1`
168    verify(parse_nexthop(r3.get_nexthop(fed2_rloc)) == (nexthop, 4))
169
170    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
171    # `r4` next hops and costs
172    verify(parse_nexthop(r4.get_nexthop(fed1_rloc)) == (r2_rloc, 3))
173    verify(parse_nexthop(r4.get_nexthop(r1_rloc)) == (r2_rloc, 2))
174    verify(parse_nexthop(r4.get_nexthop(r2_rloc)) == (r2_rloc, 1))
175    verify(parse_nexthop(r4.get_nexthop(r3_rloc)) == (r3_rloc, 1))
176    verify(parse_nexthop(r4.get_nexthop(r4_rloc)) == (r4_rloc, 0))
177    # On `r4` even though we have a direct link to `r5`
178    # the path cost through `r3` has a smaller cost over
179    # the direct link cost.
180    verify(parse_nexthop(r4.get_nexthop(r5_rloc)) == (r3_rloc, 2))
181    verify(parse_nexthop(r4.get_nexthop(fed3_rloc)) == (r3_rloc, 3))
182
183    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
184    # `r5` next hops and costs
185    verify(parse_nexthop(r5.get_nexthop(fed3_rloc)) == (fed3_rloc, 1))
186    verify(parse_nexthop(r5.get_nexthop(r5_rloc)) == (r5_rloc, 0))
187    verify(parse_nexthop(r5.get_nexthop(r3_rloc)) == (r3_rloc, 1))
188    verify(parse_nexthop(r5.get_nexthop(r4_rloc)) == (r3_rloc, 2))
189    verify(parse_nexthop(r5.get_nexthop(r2_rloc)) == (r3_rloc, 3))
190    verify(parse_nexthop(r5.get_nexthop(r1_rloc)) == (r3_rloc, 4))
191    verify(parse_nexthop(r5.get_nexthop(fed1_rloc)) == (r3_rloc, 5))
192
193    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
194    # `fed1` next hops and costs
195    verify(parse_nexthop(fed1.get_nexthop(fed1_rloc)) == (fed1_rloc, 0))
196    verify(parse_nexthop(fed1.get_nexthop(r1_rloc)) == (r1_rloc, 1))
197    verify(parse_nexthop(fed1.get_nexthop(r2_rloc)) == (r1_rloc, 2))
198    verify(parse_nexthop(fed1.get_nexthop(r3_rloc)) == (r1_rloc, 4))
199    verify(parse_nexthop(fed1.get_nexthop(r4_rloc)) == (r1_rloc, 3))
200    verify(parse_nexthop(fed1.get_nexthop(r5_rloc)) == (r1_rloc, 5))
201    verify(parse_nexthop(fed1.get_nexthop(fed3_rloc)) == (r1_rloc, 6))
202    # On `fed1`, path to `fed2` should go through our parent.
203    verify(parse_nexthop(fed1.get_nexthop(fed2_rloc)) == (r1_rloc, 2))
204
205    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
206    # `fed2` next hops and costs
207    verify(parse_nexthop(fed2.get_nexthop(fed2_rloc)) == (fed2_rloc, 0))
208    verify(parse_nexthop(fed2.get_nexthop(r1_rloc)) == (r1_rloc, 4))
209    verify(parse_nexthop(fed2.get_nexthop(r2_rloc)) == (r1_rloc, 5))
210    verify(parse_nexthop(fed2.get_nexthop(r3_rloc)) == (r1_rloc, 7))
211    verify(parse_nexthop(fed2.get_nexthop(r4_rloc)) == (r1_rloc, 6))
212    verify(parse_nexthop(fed2.get_nexthop(r5_rloc)) == (r1_rloc, 8))
213    verify(parse_nexthop(fed2.get_nexthop(fed3_rloc)) == (r1_rloc, 9))
214    verify(parse_nexthop(fed2.get_nexthop(fed1_rloc)) == (r1_rloc, 5))
215
216
217verify_within(check_nexthops_and_costs, 5)
218
219#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
220# Disable `r4` and check nexthop and cost on it and on other
221# nodes.
222#
223#
224#     r1 ---- r2...2...r3
225#    / |                 \
226#   /  |                  \
227# fed1 fed2       r4       r5 ---- fed3
228
229r4.thread_stop()
230r4.interface_down()
231
232verify(parse_nexthop(r4.get_nexthop(r2_rloc)) == (0xfffe, 16))
233verify(parse_nexthop(r4.get_nexthop(r4_rloc)) == (0xfffe, 16))
234
235
236def check_nexthops_and_costs_after_r4_detach():
237    # Make sure we have no next hop towards `r4`.
238    verify(parse_nexthop(r1.get_nexthop(r4_rloc)) == (0xfffe, 16))
239    verify(parse_nexthop(r2.get_nexthop(r4_rloc)) == (0xfffe, 16))
240    verify(parse_nexthop(r3.get_nexthop(r4_rloc)) == (0xfffe, 16))
241    verify(parse_nexthop(r5.get_nexthop(r4_rloc)) == (0xfffe, 16))
242    verify(parse_nexthop(fed3.get_nexthop(r4_rloc)) == (r5_rloc, 16))
243    # Check cost and next hop on other nodes
244    verify(parse_nexthop(r1.get_nexthop(r5_rloc)) == (r2_rloc, 4))
245    verify(parse_nexthop(r2.get_nexthop(r3_rloc)) == (r3_rloc, 2))
246    verify(parse_nexthop(r3.get_nexthop(r2_rloc)) == (r2_rloc, 2))
247    verify(parse_nexthop(fed3.get_nexthop(fed1_rloc)) == (r5_rloc, 6))
248
249
250verify_within(check_nexthops_and_costs_after_r4_detach, 45)
251verify(r1.get_state() == 'leader')
252
253#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
254# Disable `r1` (which was previous leader) and check
255# routes on other nodes
256#
257#
258#     r1      r2...2...r3
259#    / |                 \
260#   /  |                  \
261# fed1 fed2       r4       r5 ---- fed3
262
263r1.thread_stop()
264r1.interface_down()
265fed1.thread_stop()
266fed1.interface_down()
267fed2.thread_stop()
268fed2.interface_down()
269
270verify(parse_nexthop(r1.get_nexthop(r2_rloc)) == (0xfffe, 16))
271verify(parse_nexthop(r1.get_nexthop(r1_rloc)) == (0xfffe, 16))
272verify(parse_nexthop(r1.get_nexthop(fed1_rloc)) == (0xfffe, 16))
273
274
275def check_nexthops_and_costs_after_r1_detach():
276    verify(parse_nexthop(r2.get_nexthop(r1_rloc)) == (0xfffe, 16))
277    verify(parse_nexthop(r3.get_nexthop(r1_rloc)) == (0xfffe, 16))
278    verify(parse_nexthop(r5.get_nexthop(r1_rloc)) == (0xfffe, 16))
279    verify(parse_nexthop(fed3.get_nexthop(r1_rloc)) == (r5_rloc, 16))
280    verify(parse_nexthop(r2.get_nexthop(r5_rloc)) == (r3_rloc, 3))
281    verify(parse_nexthop(fed3.get_nexthop(r2_rloc)) == (r5_rloc, 4))
282
283
284verify_within(check_nexthops_and_costs_after_r1_detach, 30)
285
286# -----------------------------------------------------------------------------------------------------------------------
287# Test finished
288
289cli.Node.finalize_all_nodes()
290
291print('\'{}\' passed.'.format(test_name))
292