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 the behavior of `AddressResolver` and how the cache 38# table is managed. In particular it verifies behavior query timeout and 39# query retry and snoop optimization. 40# 41# Build network topology 42# 43# r3 ---- r1 ---- r2 44# | | 45# | | 46# c3 c2 47# 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 = 10 57cli.Node.set_time_speedup_factor(speedup) 58 59r1 = cli.Node() 60r2 = cli.Node() 61r3 = cli.Node() 62c2 = cli.Node() 63c3 = cli.Node() 64 65# ----------------------------------------------------------------------------------------------------------------------- 66# Form topology 67 68r1.allowlist_node(r2) 69r1.allowlist_node(r3) 70 71r2.allowlist_node(r1) 72r2.allowlist_node(c2) 73 74r3.allowlist_node(r1) 75r3.allowlist_node(c3) 76 77c2.allowlist_node(r2) 78c3.allowlist_node(r3) 79 80r1.form('addrrslvr') 81 82prefix = 'fd00:abba::' 83r1.add_prefix(prefix + '/64', 'pos', 'med') 84r1.register_netdata() 85 86r2.join(r1) 87r3.join(r1) 88c2.join(r1, cli.JOIN_TYPE_END_DEVICE) 89c3.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE) 90c3.set_pollperiod(400) 91 92verify(r1.get_state() == 'leader') 93verify(r2.get_state() == 'router') 94verify(r3.get_state() == 'router') 95verify(c2.get_state() == 'child') 96verify(c3.get_state() == 'child') 97 98# ----------------------------------------------------------------------------------------------------------------------- 99# Test Implementation 100 101# Wait till first router has either established a link or 102# has a valid "next hop" towards all other routers. 103 104r1_rloc16 = int(r1.get_rloc16(), 16) 105 106 107def check_r1_router_table(): 108 table = r1.get_router_table() 109 verify(len(table) == 3) 110 for entry in table: 111 verify(int(entry['RLOC16'], 0) == r1_rloc16 or int(entry['Link']) == 1 or int(entry['Next Hop']) != 63) 112 113 114verify_within(check_r1_router_table, 120) 115 116# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 117 118r1_rloc = int(r1.get_rloc16(), 16) 119r2_rloc = int(r2.get_rloc16(), 16) 120r3_rloc = int(r3.get_rloc16(), 16) 121c2_rloc = int(c2.get_rloc16(), 16) 122c3_rloc = int(c3.get_rloc16(), 16) 123 124# AddressResolver constants: 125 126max_cache_entries = 16 127max_snooped_non_evictable = 2 128 129# Add IPv6 addresses matching the on-mesh prefix on all nodes 130 131r1.add_ip_addr(prefix + '1') 132 133num_addresses = 4 # Number of addresses to add on r2, r3, c2, and c3 134 135for num in range(num_addresses): 136 r2.add_ip_addr(prefix + "2:" + str(num)) 137 r3.add_ip_addr(prefix + "3:" + str(num)) 138 c2.add_ip_addr(prefix + "c2:" + str(num)) 139 c3.add_ip_addr(prefix + "c3:" + str(num)) 140 141# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 142 143# From r1 send msg to a group of addresses that are not provided by 144# any nodes in network. 145 146num_queries = 5 147stagger_interval = 1.2 148port = 1234 149initial_retry_delay = 8 150 151r1.udp_open() 152 153for num in range(num_queries): 154 r1.udp_send(prefix + '800:' + str(num), port, 'hi_nobody') 155 # Wait before next tx to stagger the address queries 156 # request ensuring different timeouts 157 time.sleep(stagger_interval / (num_queries * speedup)) 158 159# Verify that we do see entries in cache table for all the addresses 160# and all are in "query" state 161 162cache_table = r1.get_eidcache() 163verify(len(cache_table) == num_queries) 164for entry in cache_table: 165 fields = entry.strip().split(' ') 166 verify(fields[2] == 'query') 167 verify(fields[3] == 'canEvict=0') 168 verify(fields[4].startswith('timeout=')) 169 verify(int(fields[4].split('=')[1]) > 0) 170 171# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 172# Check the retry-query behavior 173# 174# Wait till all the address queries time out and verify they 175# enter "retry-query" state. 176 177 178def check_cache_entry_switch_to_retry_state(): 179 cache_table = r1.get_eidcache() 180 for entry in cache_table: 181 fields = entry.strip().split(' ') 182 verify(fields[2] == 'retry') 183 verify(fields[3] == 'canEvict=1') 184 verify(fields[4].startswith('timeout=')) 185 verify(int(fields[4].split('=')[1]) >= 0) 186 verify(fields[5].startswith('retryDelay=')) 187 verify(int(fields[5].split('=')[1]) == initial_retry_delay) 188 189 190verify_within(check_cache_entry_switch_to_retry_state, 20) 191 192# Try sending again to same addresses which are all in "retry" state. 193 194for num in range(num_queries): 195 r1.udp_send(prefix + '800:' + str(num), port, 'hi_nobody') 196 197# Make sure the entries stayed in retry-query state as before. 198 199verify_within(check_cache_entry_switch_to_retry_state, 20) 200 201# Now wait for all entries to reach zero timeout. 202 203 204def check_cache_entry_in_retry_state_to_enter_rampdown(): 205 cache_table = r1.get_eidcache() 206 for entry in cache_table: 207 fields = entry.strip().split(' ') 208 verify(fields[2] == 'retry') 209 verify(fields[3] == 'canEvict=1') 210 verify(fields[4].startswith('timeout=')) 211 verify(fields[5].startswith('retryDelay=')) 212 verify(fields[6] == 'rampDown=1') 213 214 215verify_within(check_cache_entry_in_retry_state_to_enter_rampdown, 20) 216 217# Now send again to the same addresses. 218 219for num in range(num_queries): 220 r1.udp_send(prefix + '800:' + str(num), port, 'hi_nobody') 221 222# We expect now after the delay to see retries for same addresses. 223 224 225def check_cache_entry_switch_to_query_state(): 226 cache_table = r1.get_eidcache() 227 for entry in cache_table: 228 fields = entry.strip().split(' ') 229 verify(fields[2] == 'query') 230 verify(fields[3] == 'canEvict=1') 231 232 233verify_within(check_cache_entry_switch_to_query_state, 20) 234 235 236def check_cache_entry_switch_to_retry_state_with_double_retry_delay(): 237 cache_table = r1.get_eidcache() 238 for entry in cache_table: 239 fields = entry.strip().split(' ') 240 verify(fields[2] == 'retry') 241 verify(fields[3] == 'canEvict=1') 242 verify(fields[4].startswith('timeout=')) 243 verify(fields[5].startswith('retryDelay=')) 244 verify(int(fields[5].split('=')[1]) == 2 * initial_retry_delay) 245 246 247verify_within(check_cache_entry_switch_to_retry_state_with_double_retry_delay, 40) 248 249verify_within(check_cache_entry_in_retry_state_to_enter_rampdown, 40) 250 251 252def check_cache_entry_ramp_down_to_initial_retry_delay(): 253 cache_table = r1.get_eidcache() 254 for entry in cache_table: 255 fields = entry.strip().split(' ') 256 verify(fields[2] == 'retry') 257 verify(fields[3] == 'canEvict=1') 258 verify(fields[4].startswith('timeout=')) 259 verify(fields[5].startswith('retryDelay=')) 260 verify(int(fields[5].split('=')[1]) == initial_retry_delay) 261 verify(fields[6] == 'rampDown=1') 262 263 264verify_within(check_cache_entry_ramp_down_to_initial_retry_delay, 60) 265 266# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 267# Verify snoop optimization behavior. 268 269# Send to r1 from all addresses on r2. 270 271r2.udp_open() 272for num in range(num_addresses): 273 r2.udp_bind(prefix + '2:' + str(num), port) 274 r2.udp_send(prefix + '1', port, 'hi_r1_from_r2_snoop_me') 275 276# Verify that we see all addresses from r2 as snooped in cache table. 277# At most two of them should be marked as non-evictable. 278 279 280def check_cache_entry_contains_snooped_entries(): 281 cache_table = r1.get_eidcache() 282 verify(len(cache_table) >= num_addresses) 283 snooped_count = 0 284 snooped_non_evictable = 0 285 for entry in cache_table: 286 fields = entry.strip().split(' ') 287 if fields[2] == 'snoop': 288 verify(fields[0].startswith('fd00:abba:0:0:0:0:2:')) 289 verify(int(fields[1], 16) == r2_rloc) 290 snooped_count = snooped_count + 1 291 if fields[3] == 'canEvict=0': 292 snooped_non_evictable = snooped_non_evictable + 1 293 verify(snooped_count == num_addresses) 294 verify(snooped_non_evictable == max_snooped_non_evictable) 295 296 297verify_within(check_cache_entry_contains_snooped_entries, 20) 298 299# Now we use the snooped entries by sending from r1 to r2 using 300# all its addresses. 301 302for num in range(num_addresses): 303 r1.udp_send(prefix + '2:' + str(num), port, 'hi_back_r2_from_r1') 304 305time.sleep(0.1) 306 307# We expect to see the entries to be in "cached" state now. 308 309cache_table = r1.get_eidcache() 310verify(len(cache_table) >= num_addresses) 311match_count = 0 312for entry in cache_table: 313 fields = entry.strip().split(' ') 314 if fields[0].startswith('fd00:abba:0:0:0:0:2:'): 315 verify(fields[2] == 'cache') 316 verify(fields[3] == 'canEvict=1') 317 match_count = match_count + 1 318verify(match_count == num_addresses) 319 320# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 321# Check query requests and last transaction time 322 323# Send from r1 to all addresses on r3. Check entries 324# for r3 are at the top of cache table list. 325 326for num in range(num_addresses): 327 r1.udp_send(prefix + '3:' + str(num), port, 'hi_r3_from_r1') 328 329 330def check_cache_entry_contains_r3_entries(): 331 cache_table = r1.get_eidcache() 332 for num in range(num_addresses): 333 entry = cache_table[num] 334 fields = entry.strip().split(' ') 335 verify(fields[0].startswith('fd00:abba:0:0:0:0:3:')) 336 verify(int(fields[1], 16) == r3_rloc) 337 verify(fields[2] == 'cache') 338 verify(fields[3] == 'canEvict=1') 339 verify(fields[4] == 'transTime=0') 340 341 342verify_within(check_cache_entry_contains_r3_entries, 20) 343 344# Send from r1 to all addresses of c3 (sleepy child of r3) 345 346for num in range(num_addresses): 347 r1.udp_send(prefix + 'c3:' + str(num), port, 'hi_c3_from_r1') 348 349 350def check_cache_entry_contains_c3_entries(): 351 cache_table = r1.get_eidcache() 352 for num in range(num_addresses): 353 entry = cache_table[num] 354 fields = entry.strip().split(' ') 355 verify(fields[0].startswith('fd00:abba:0:0:0:0:c3:')) 356 verify(int(fields[1], 16) == r3_rloc) 357 verify(fields[2] == 'cache') 358 verify(fields[3] == 'canEvict=1') 359 verify(fields[4] == 'transTime=0') 360 361 362verify_within(check_cache_entry_contains_c3_entries, 20) 363 364# Send again to r2. This should cause the related cache entries to 365# be moved to top of the list. 366 367for num in range(num_addresses): 368 r1.udp_send(prefix + '2:' + str(num), port, 'hi_again_r2_from_r1') 369 370 371def check_cache_entry_contains_r2_entries(): 372 cache_table = r1.get_eidcache() 373 for num in range(num_addresses): 374 entry = cache_table[num] 375 fields = entry.strip().split(' ') 376 verify(fields[0].startswith('fd00:abba:0:0:0:0:2:')) 377 verify(int(fields[1], 16) == r2_rloc) 378 verify(fields[2] == 'cache') 379 verify(fields[3] == 'canEvict=1') 380 381 382verify_within(check_cache_entry_contains_r2_entries, 20) 383 384# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 385# Check behavior when address cache table is full. 386 387cache_table = r1.get_eidcache() 388verify(len(cache_table) == max_cache_entries) 389 390# From r1 send to non-existing addresses. 391 392for num in range(num_queries): 393 r1.udp_send(prefix + '900:' + str(num), port, 'hi_nobody!') 394 395cache_table = r1.get_eidcache() 396verify(len(cache_table) == max_cache_entries) 397 398# Send from c2 to r1 and verify that snoop optimization uses at most 399# `max_snooped_non_evictable` entries 400 401c2.udp_open() 402 403for num in range(num_addresses): 404 c2.udp_bind(prefix + 'c2:' + str(num), port) 405 c2.udp_send(prefix + '1', port, 'hi_r1_from_c2_snoop_me') 406 407 408def check_cache_entry_contains_max_allowed_snopped(): 409 cache_table = r1.get_eidcache() 410 snooped_non_evictable = 0 411 for entry in cache_table: 412 fields = entry.strip().split(' ') 413 if fields[2] == 'snoop': 414 verify(fields[0].startswith('fd00:abba:0:0:0:0:c2:')) 415 verify(fields[3] == 'canEvict=0') 416 snooped_non_evictable = snooped_non_evictable + 1 417 verify(snooped_non_evictable == max_snooped_non_evictable) 418 419 420verify_within(check_cache_entry_contains_max_allowed_snopped, 20) 421 422# Now send from r1 to c2, the snooped entries would be used 423# some other addresses will go through full address query. 424 425for num in range(num_addresses): 426 r1.udp_send(prefix + 'c2:' + str(num), port, 'hi_c2_from_r1') 427 428 429def check_cache_entry_contains_c2_entries(): 430 cache_table = r1.get_eidcache() 431 for num in range(num_addresses): 432 entry = cache_table[num] 433 fields = entry.strip().split(' ') 434 verify(fields[0].startswith('fd00:abba:0:0:0:0:c2:')) 435 verify(int(fields[1], 16) == r2_rloc) 436 verify(fields[2] == 'cache') 437 verify(fields[3] == 'canEvict=1') 438 439 440verify_within(check_cache_entry_contains_c2_entries, 20) 441 442# ----------------------------------------------------------------------------------------------------------------------- 443# Test finished 444 445cli.Node.finalize_all_nodes() 446 447print('\'{}\' passed.'.format(test_name)) 448