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 time 30import wpan 31from wpan import verify 32 33# ----------------------------------------------------------------------------------------------------------------------- 34# Test description: Address Cache Table 35# 36# This test verifies the behavior of `AddressResolver` and how the cache 37# table is managed. In particular it verifies behavior query timeout and 38# query retry and snoop optimization. 39 40test_name = __file__[:-3] if __file__.endswith('.py') else __file__ 41print('-' * 120) 42print('Starting \'{}\''.format(test_name)) 43 44# ----------------------------------------------------------------------------------------------------------------------- 45# Creating `wpan.Nodes` instances 46 47speedup = 4 48wpan.Node.set_time_speedup_factor(speedup) 49 50r1 = wpan.Node() 51r2 = wpan.Node() 52r3 = wpan.Node() 53 54c2 = wpan.Node() 55c3 = wpan.Node() 56 57# ----------------------------------------------------------------------------------------------------------------------- 58# Init all nodes 59 60wpan.Node.init_all_nodes() 61 62# ----------------------------------------------------------------------------------------------------------------------- 63# Build network topology 64# 65# r3 ---- r1 ---- r2 66# | | 67# | | 68# c3 c2 69# 70 71PREFIX = "fd00:1234::" 72POLL_INTERVAL = 200 73 74MAX_SNOOPED_NON_EVICTABLE = 2 75INITIAL_RETRY_DELAY = 4 76MAX_CACHE_ENTRIES = 16 77 78r1.form("sekiro") # shadows die twice! 79 80r1.add_prefix(PREFIX, stable=True, on_mesh=True, slaac=False, preferred=True) 81 82r1.allowlist_node(r2) 83r2.allowlist_node(r1) 84r2.join_node(r1, wpan.JOIN_TYPE_ROUTER) 85 86c2.allowlist_node(r2) 87r2.allowlist_node(c2) 88c2.join_node(r2, wpan.JOIN_TYPE_END_DEVICE) 89 90r1.allowlist_node(r3) 91r3.allowlist_node(r1) 92r3.join_node(r1, wpan.JOIN_TYPE_ROUTER) 93 94c3.allowlist_node(r3) 95r3.allowlist_node(c3) 96c3.join_node(r3, wpan.JOIN_TYPE_SLEEPY_END_DEVICE) 97c3.set(wpan.WPAN_POLL_INTERVAL, str(POLL_INTERVAL)) 98 99# ----------------------------------------------------------------------------------------------------------------------- 100# Test implementation 101# 102 103# Add IPv6 addresses on different nodes. 104 105r1_address = PREFIX + "1" 106r1.add_ip6_address_on_interface(r1_address) 107 108WAIT_TIME = 10 109PORT = 1234 110 111NUM_ADDRESSES = 4 # Number of addresses to add on r2, r3, c2, and c3 112 113for num in range(NUM_ADDRESSES): 114 r2.add_ip6_address_on_interface(PREFIX + "2:" + str(num)) 115 r3.add_ip6_address_on_interface(PREFIX + "3:" + str(num)) 116 c2.add_ip6_address_on_interface(PREFIX + "c2:" + str(num)) 117 c3.add_ip6_address_on_interface(PREFIX + "c3:" + str(num)) 118 119# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 120 121# From r1 send msg to a group of addresses (not provided by any nodes in network). 122 123NUM_QUERY_ADDRS = 5 124MAX_STAGGER_INTERVAL = 2.5 125 126for num in range(NUM_QUERY_ADDRS): 127 sender = r1.prepare_tx((r1_address, PORT), (PREFIX + "800:" + str(num), PORT), "hi nobody!", 1) 128 wpan.Node.perform_async_tx_rx() 129 verify(sender.was_successful) 130 # Wait before next tx to stagger the address queries 131 # request ensuring different timeouts 132 time.sleep(MAX_STAGGER_INTERVAL / (NUM_QUERY_ADDRS * speedup)) 133 134r2_rloc = int(r2.get(wpan.WPAN_THREAD_RLOC16), 16) 135c2_rloc = int(c2.get(wpan.WPAN_THREAD_RLOC16), 16) 136r3_rloc = int(r3.get(wpan.WPAN_THREAD_RLOC16), 16) 137 138# Verify that we do see entries in cache table for all the addresses and all are in "query" state 139 140addr_cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 141 142verify(len(addr_cache_table) == NUM_QUERY_ADDRS) 143for entry in addr_cache_table: 144 verify(entry.state == wpan.ADDRESS_CACHE_ENTRY_STATE_QUERY) 145 verify(not entry.can_evict()) 146 verify(entry.timeout > 0) 147 verify(entry.retry_delay == INITIAL_RETRY_DELAY) 148 149# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 150 151# Check the retry-query behavior 152 153# Wait till all the address queries time out and verify they enter "retry-query" state. 154 155 156def check_cache_entry_switch_to_retry_state(): 157 cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 158 for index in range(NUM_QUERY_ADDRS): 159 verify(cache_table[index].state == wpan.ADDRESS_CACHE_ENTRY_STATE_RETRY_QUERY) 160 verify(cache_table[index].retry_delay == 2 * INITIAL_RETRY_DELAY) 161 162 163wpan.verify_within(check_cache_entry_switch_to_retry_state, WAIT_TIME) 164 165# Try sending again to same addresses which are in "retry-delay" state. 166 167for num in range(NUM_QUERY_ADDRS): 168 sender = r1.prepare_tx((r1_address, PORT), (PREFIX + "800:" + str(num), PORT), "hi nobody!", 1) 169 wpan.Node.perform_async_tx_rx() 170 verify(sender.was_successful) 171 172# Make sure the entries stayed in retry-delay as before. 173 174wpan.verify_within(check_cache_entry_switch_to_retry_state, WAIT_TIME) 175 176# Now wait for them to get to zero timeout. 177 178cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 179 180 181def check_cache_entry_in_retry_state_to_get_to_zero_timeout(): 182 cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 183 for index in range(NUM_QUERY_ADDRS): 184 verify(cache_table[index].state == wpan.ADDRESS_CACHE_ENTRY_STATE_RETRY_QUERY) 185 verify(cache_table[index].timeout == 0) 186 187 188wpan.verify_within(check_cache_entry_in_retry_state_to_get_to_zero_timeout, WAIT_TIME) 189 190# Now try again using the same addresses. 191 192for num in range(NUM_QUERY_ADDRS): 193 sender = r1.prepare_tx((r1_address, PORT), (PREFIX + "800:" + str(num), PORT), "hi again nobody!", 1) 194 wpan.Node.perform_async_tx_rx() 195 verify(sender.was_successful) 196 197# We expect now after the delay to see retries for same addresses. 198 199 200def check_cache_entry_switch_to_query_state(): 201 cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 202 for index in range(NUM_QUERY_ADDRS): 203 verify(cache_table[index].state == wpan.ADDRESS_CACHE_ENTRY_STATE_QUERY) 204 verify(cache_table[index].can_evict() == True) 205 206 207wpan.verify_within(check_cache_entry_switch_to_query_state, WAIT_TIME) 208 209# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 210 211# Verify snoop optimization. 212 213for num in range(NUM_ADDRESSES): 214 sender = r2.prepare_tx((PREFIX + "2:" + str(num), PORT), (r1_address, PORT), "hi r1 from r2 (snoop me)", 1) 215 recver = r1.prepare_rx(sender) 216 wpan.Node.perform_async_tx_rx() 217 verify(sender.was_successful) 218 verify(recver.was_successful) 219 220cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 221 222# We expect to see new "snooped" entries at the top of list. 223# Also verify that only MAX_SNOOPED_NON_EVICTABLE of snooped entries are non-evictable. 224 225verify(len(cache_table) >= NUM_ADDRESSES) 226 227for index in range(NUM_ADDRESSES): 228 verify(cache_table[index].address == PREFIX + "2:" + str(NUM_ADDRESSES - index - 1)) 229 verify(cache_table[index].rloc16 == r2_rloc) 230 verify(cache_table[index].state == wpan.ADDRESS_CACHE_ENTRY_STATE_SNOOPED) 231 if index < NUM_ADDRESSES - MAX_SNOOPED_NON_EVICTABLE: 232 verify(cache_table[index].can_evict() == True) 233 verify(cache_table[index].timeout == 0) 234 else: 235 verify(cache_table[index].can_evict() == False) 236 verify(cache_table[index].timeout > 0) 237 238# From r1 send to r2 using the addresses from snooped entries: 239 240for num in range(NUM_ADDRESSES): 241 sender = r1.prepare_tx((r1_address, PORT), (PREFIX + "2:" + str(num), PORT), "hi back r2 from r1", 1) 242 recver = r2.prepare_rx(sender) 243 wpan.Node.perform_async_tx_rx() 244 verify(sender.was_successful) 245 verify(recver.was_successful) 246 247cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 248 249# We expect to see the entries to be in "cached" state now. 250 251verify(len(cache_table) >= NUM_ADDRESSES) 252 253for index in range(NUM_ADDRESSES): 254 verify(cache_table[index].address == PREFIX + "2:" + str(NUM_ADDRESSES - index - 1)) 255 verify(cache_table[index].rloc16 == r2_rloc) 256 verify(cache_table[index].state == wpan.ADDRESS_CACHE_ENTRY_STATE_CACHED) 257 258# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 259 260# Check query requests, last transaction time 261 262# Send from r1 to all addresses on r3. 263 264for num in range(NUM_ADDRESSES): 265 sender = r1.prepare_tx((r1_address, PORT), (PREFIX + "3:" + str(num), PORT), "hi r3 from r1", 1) 266 recver = r3.prepare_rx(sender) 267 wpan.Node.perform_async_tx_rx() 268 verify(sender.was_successful) 269 verify(recver.was_successful) 270 cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 271 272# We expect to see the cache entries for the addresses pointing to r3. 273 274cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 275 276for index in range(NUM_ADDRESSES): 277 verify(cache_table[index].address == PREFIX + "3:" + str(NUM_ADDRESSES - index - 1)) 278 verify(cache_table[index].rloc16 == r3_rloc) 279 verify(cache_table[index].state == wpan.ADDRESS_CACHE_ENTRY_STATE_CACHED) 280 verify(cache_table[index].last_trans == 0) 281 282# Send from r1 to all addresses on c3 (sleepy child of r3) 283 284for num in range(NUM_ADDRESSES): 285 sender = r1.prepare_tx((r1_address, PORT), (PREFIX + "c3:" + str(num), PORT), "hi c3 from r1", 1) 286 recver = c3.prepare_rx(sender) 287 wpan.Node.perform_async_tx_rx() 288 verify(sender.was_successful) 289 verify(recver.was_successful) 290 291# We expect to see the cache entries for c3 addresses pointing to r3. 292 293cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 294 295for index in range(NUM_ADDRESSES): 296 verify(cache_table[index].address == PREFIX + "c3:" + str(NUM_ADDRESSES - index - 1)) 297 verify(cache_table[index].rloc16 == r3_rloc) 298 verify(cache_table[index].state == wpan.ADDRESS_CACHE_ENTRY_STATE_CACHED) 299 # SED's keep-alive period (`POLL_PERIOD`) is 200ms, `last_trans` should always be 0 as it is 300 # the number of seconds since a keep-alive was last received from the child. 301 verify(cache_table[index].last_trans == 0) 302 303# Send again to r2. This should cause the related cache entries to be moved to top of the list: 304 305for num in range(NUM_ADDRESSES): 306 sender = r1.prepare_tx((r1_address, PORT), (PREFIX + "2:" + str(num), PORT), "hi again r2 from r1", 1) 307 recver = r2.prepare_rx(sender) 308 wpan.Node.perform_async_tx_rx() 309 verify(sender.was_successful) 310 verify(recver.was_successful) 311 312cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 313 314for index in range(NUM_ADDRESSES): 315 verify(cache_table[index].address == PREFIX + "2:" + str(NUM_ADDRESSES - index - 1)) 316 verify(cache_table[index].rloc16 == r2_rloc) 317 verify(cache_table[index].state == wpan.ADDRESS_CACHE_ENTRY_STATE_CACHED) 318 319# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 320 321# Check behavior when cache table is full. 322 323verify(len(cache_table) == MAX_CACHE_ENTRIES) 324 325for num in range(NUM_QUERY_ADDRS): 326 sender = r1.prepare_tx((r1_address, PORT), (PREFIX + "900:" + str(num), PORT), "hi nobody!", 1) 327 wpan.Node.perform_async_tx_rx() 328 verify(sender.was_successful) 329 330cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 331 332verify(len(cache_table) == MAX_CACHE_ENTRIES) 333 334# Send from c2 to r1 and verify that snoop optimization uses at most 335# `MAX_SNOOPED_NON_EVICTABLE` entries. 336 337for num in range(NUM_ADDRESSES): 338 sender = c2.prepare_tx((PREFIX + "c2:" + str(num), PORT), (r1_address, PORT), "hi r1 from c2 (snoop me)", 1) 339 recver = r1.prepare_rx(sender) 340 wpan.Node.perform_async_tx_rx() 341 verify(sender.was_successful) 342 verify(recver.was_successful) 343 344cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 345 346verify(len(cache_table) == MAX_CACHE_ENTRIES) 347 348snooped_entries = [entry for entry in cache_table if entry.state == wpan.ADDRESS_CACHE_ENTRY_STATE_SNOOPED] 349verify(len(snooped_entries) == MAX_SNOOPED_NON_EVICTABLE) 350 351for entry in snooped_entries: 352 verify(entry.rloc16 == c2_rloc) 353 verify(entry.state == wpan.ADDRESS_CACHE_ENTRY_STATE_SNOOPED) 354 verify(entry.can_evict() == False) 355 verify(entry.timeout > 0) 356 357# Now send from r1 to c2, some of the snooped entries would be used, 358# others would go through full address query. 359 360for num in range(NUM_ADDRESSES): 361 sender = r1.prepare_tx((r1_address, PORT), (PREFIX + "c2:" + str(num), PORT), "hi c2 from r1", 1) 362 recver = c2.prepare_rx(sender) 363 wpan.Node.perform_async_tx_rx() 364 verify(sender.was_successful) 365 verify(recver.was_successful) 366 367cache_table = wpan.parse_address_cache_table_result(r1.get(wpan.WPAN_THREAD_ADDRESS_CACHE_TABLE)) 368 369verify(len(cache_table) == MAX_CACHE_ENTRIES) 370 371# Verify that c2 entries are now at the top of cache list. 372 373for index in range(NUM_ADDRESSES): 374 verify(cache_table[index].address == PREFIX + "c2:" + str(NUM_ADDRESSES - index - 1)) 375 verify(cache_table[index].rloc16 == c2_rloc) 376 verify(cache_table[index].state == wpan.ADDRESS_CACHE_ENTRY_STATE_CACHED) 377 378# ----------------------------------------------------------------------------------------------------------------------- 379# Test finished 380 381wpan.Node.finalize_all_nodes() 382 383print('\'{}\' passed.'.format(test_name)) 384