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