1#!/usr/bin/env python3 2# 3# Copyright (c) 2022, 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: Address Cache Table 36# 37# This test verifies that address cache entries associated with SED child 38# addresses are removed from new parent node ensuring we would not have a 39# routing loop. 40# 41# 42# r1 ---- r2 ---- r3 43# | 44# | 45# c 46# 47# c is initially attached to r3 but it switches parent during test to r2 and then r1. 48 49test_name = __file__[:-3] if __file__.endswith('.py') else __file__ 50print('-' * 120) 51print('Starting \'{}\''.format(test_name)) 52 53# ----------------------------------------------------------------------------------------------------------------------- 54# Creating `cli.Node` instances 55 56speedup = 40 57cli.Node.set_time_speedup_factor(speedup) 58 59r1 = cli.Node() 60r2 = cli.Node() 61r3 = cli.Node() 62c = cli.Node() 63 64# ----------------------------------------------------------------------------------------------------------------------- 65# Form topology 66 67r1.allowlist_node(r2) 68 69r2.allowlist_node(r1) 70r2.allowlist_node(r3) 71 72r3.allowlist_node(r2) 73r3.allowlist_node(c) 74 75c.allowlist_node(r3) 76 77r1.form('sed-cache') 78 79prefix = 'fd00:abba::' 80r1.add_prefix(prefix + '/64', 'paos', 'med') 81r1.register_netdata() 82 83r2.join(r1) 84r3.join(r1) 85c.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE) 86c.set_pollperiod(400) 87 88verify(r1.get_state() == 'leader') 89verify(r2.get_state() == 'router') 90verify(r3.get_state() == 'router') 91verify(c.get_state() == 'child') 92 93# ----------------------------------------------------------------------------------------------------------------------- 94# Test Implementation 95 96# Wait till first router has either established a link or 97# has a valid "next hop" towards all other routers. 98 99r1_rloc16 = int(r1.get_rloc16(), 16) 100 101 102def check_r1_router_table(): 103 table = r1.get_router_table() 104 verify(len(table) == 3) 105 for entry in table: 106 verify(int(entry['RLOC16'], 0) == r1_rloc16 or int(entry['Link']) == 1 or int(entry['Next Hop']) != 63) 107 108 109verify_within(check_r1_router_table, 120) 110 111# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 112 113r1_rloc = int(r1.get_rloc16(), 16) 114r2_rloc = int(r2.get_rloc16(), 16) 115r3_rloc = int(r3.get_rloc16(), 16) 116c_rloc = int(c.get_rloc16(), 16) 117 118r1_address = next(addr for addr in r1.get_ip_addrs() if addr.startswith('fd00:abba:')) 119r2_address = next(addr for addr in r2.get_ip_addrs() if addr.startswith('fd00:abba:')) 120r3_address = next(addr for addr in r3.get_ip_addrs() if addr.startswith('fd00:abba:')) 121c_address = next(addr for addr in c.get_ip_addrs() if addr.startswith('fd00:abba:')) 122 123port = 4321 124 125# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 126# Send a single UDP message from r2 to c. This adds an address cache 127# entry on r2 for c pointing to r3 (the current parent of c). 128 129r2.udp_open() 130r2.udp_send(c_address, port, 'hi_from_r2_to_c') 131 132 133def check_r2_cache_table(): 134 cache_table = r2.get_eidcache() 135 for entry in cache_table: 136 fields = entry.strip().split(' ') 137 if (fields[0] == c_address): 138 verify(int(fields[1], 16) == r3_rloc) 139 verify(fields[2] == 'cache') 140 break 141 else: 142 verify(False) 143 144 145verify_within(check_r2_cache_table, 20) 146 147# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 148# Force c to switch its parent from r3 to r2 149# 150# r1 ---- r2 ---- r3 151# | 152# | 153# c 154 155r3.un_allowlist_node(c) 156r2.allowlist_node(c) 157c.allowlist_node(r2) 158c.un_allowlist_node(r3) 159 160c.thread_stop() 161c.thread_start() 162 163 164def check_c_attaches_to_r2(): 165 verify(c.get_state() == 'child') 166 verify(int(c.get_parent_info()['Rloc'], 16) == r2_rloc) 167 168 169verify_within(check_c_attaches_to_r2, 10) 170 171# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 172# Send a single UDP message from r3 to c. This adds an address cache 173# entry on r3 for c pointing to r2 (the current parent of c). 174 175r3.udp_open() 176r3.udp_send(c_address, port, 'hi_from_r3_to_c') 177 178 179def check_r3_cache_table(): 180 cache_table = r3.get_eidcache() 181 for entry in cache_table: 182 fields = entry.strip().split(' ') 183 if (fields[0] == c_address): 184 verify(int(fields[1], 16) == r2_rloc) 185 verify(fields[2] == 'cache') 186 break 187 else: 188 verify(False) 189 190 191verify_within(check_r3_cache_table, 20) 192 193# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 194# Force c to switch its parent again now to r1 195# 196# r1 ---- r2 ---- r3 197# | 198# | 199# c 200 201r2.un_allowlist_node(c) 202r1.allowlist_node(c) 203c.allowlist_node(r1) 204c.un_allowlist_node(r2) 205 206c.thread_stop() 207c.thread_start() 208 209 210def check_c_attaches_to_r1(): 211 verify(c.get_state() == 'child') 212 verify(int(c.get_parent_info()['Rloc'], 16) == r1_rloc) 213 214 215verify_within(check_c_attaches_to_r1, 10) 216 217# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 218# Now ping c from r2. 219# 220# If r2 address cache entry is not cleared when c attached to r1, r2 221# will still have an entry pointing to r3, and r3 will have an entry 222# pointing to r2, thus creating a loop (the msg will not be delivered 223# to r3) 224 225r2.ping(c_address) 226 227# ----------------------------------------------------------------------------------------------------------------------- 228# Test finished 229 230cli.Node.finalize_all_nodes() 231 232print('\'{}\' passed.'.format(test_name)) 233