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