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: Validate SLAAC module, addition/deprecation/removal of SLAAC addresses.
36#
37# Network topology
38#
39#    r1 ---- r2
40#
41
42test_name = __file__[:-3] if __file__.endswith('.py') else __file__
43print('-' * 120)
44print('Starting \'{}\''.format(test_name))
45
46# -----------------------------------------------------------------------------------------------------------------------
47# Creating `cli.Node` instances
48
49speedup = 40
50cli.Node.set_time_speedup_factor(speedup)
51
52r1 = cli.Node()
53r2 = cli.Node()
54c2 = cli.Node()
55
56# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - -
57
58
59def verify_slaac_address_for_prefix(prefix, preferred, nodes=[r1, r2], origin='slaac'):
60    # Verify that both nodes have SLAAC address based on `prefix`.
61    for node in nodes:
62        ip_addrs = node.get_ip_addrs('-v')
63        matched_addrs = [addr for addr in ip_addrs if addr.startswith(prefix)]
64        verify(len(matched_addrs) == 1)
65        addr = matched_addrs[0]
66        verify('origin:' + origin in addr)
67        verify('plen:64' in addr)
68        verify('valid:1' in addr)
69        if (preferred):
70            verify('preferred:1' in addr)
71        else:
72            verify('preferred:0' in addr)
73
74
75def verify_no_slaac_address_for_prefix(prefix):
76    for node in [r1, r2]:
77        ip_addrs = node.get_ip_addrs('-v')
78        matched_addrs = [addr for addr in ip_addrs if addr.startswith(prefix)]
79        verify(len(matched_addrs) == 0)
80
81
82def check_num_netdata_prefixes(num_prefixes):
83    for node in [r1, r2]:
84        verify(len(node.get_netdata_prefixes()) == num_prefixes)
85
86
87def check_num_netdata_routes(num_routes):
88    for node in [r1, r2]:
89        verify(len(node.get_netdata_routes()) == num_routes)
90
91
92# -----------------------------------------------------------------------------------------------------------------------
93# Form topology
94
95r1.allowlist_node(r2)
96r2.allowlist_node(r1)
97r2.allowlist_node(c2)
98c2.allowlist_node(r2)
99
100r1.form('slaac')
101r2.join(r1)
102c2.join(r2, cli.JOIN_TYPE_END_DEVICE)
103
104verify(r1.get_state() == 'leader')
105verify(r2.get_state() == 'router')
106verify(c2.get_state() == 'child')
107
108# Ensure `c2` is attached to `r2` as its parent
109verify(int(c2.get_parent_info()['Rloc'], 16) == int(r2.get_rloc16(), 16))
110
111# -----------------------------------------------------------------------------------------------------------------------
112# Test Implementation
113
114# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
115# Add first prefix `fd00:11::/64` and check that SLAAC
116# addressed are added based on it on all nodes.
117
118r1.add_prefix("fd00:11:0:0::/64", "paos")
119r1.register_netdata()
120verify_within(check_num_netdata_prefixes, 100, 1)
121
122verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
123
124# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
125# Add `fd00:22::/64` and check its related SLAAC addresses.
126
127r2.add_prefix("fd00:22:0:0::/64", "paos")
128r2.register_netdata()
129
130verify_within(check_num_netdata_prefixes, 100, 2)
131
132verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
133verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True)
134
135# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
136# Add prefix `fd00:11::/64` now from `r2`, and since this was
137# previously added by `r1`, there should not change to SLAAC
138# addresses.
139
140r2.add_prefix("fd00:11:0:0::/64", "paos")
141r2.register_netdata()
142
143verify_within(check_num_netdata_prefixes, 100, 3)
144
145verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
146verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True)
147
148# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
149# Remove prefix `fd00:11::/64` from `r2`, and since this is
150# also added by `r1`, there should not change to SLAAC addresses.
151
152r2.remove_prefix("fd00:11:0:0::/64")
153r2.register_netdata()
154
155verify_within(check_num_netdata_prefixes, 100, 2)
156
157verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
158verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True)
159
160# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
161# Remove all prefixes and validate that all SLAAC addresses
162# start deprecating.
163
164r1.remove_prefix("fd00:11:0:0::/64")
165r1.register_netdata()
166r2.remove_prefix("fd00:22:0:0::/64")
167r2.register_netdata()
168
169verify_within(check_num_netdata_prefixes, 100, 0)
170
171verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=False)
172verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=False)
173
174# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
175# Validate that the deprecating address can still be used
176# and proper route look up happens when it is used.
177
178# Add an off-mesh route `::/0` (default route) on `r1`.
179
180r1.add_route('::/0', 's', 'med')
181r1.register_netdata()
182
183verify_within(check_num_netdata_routes, 10, 1)
184
185# Now we ping from `r2` an off-mesh address, `r2` should pick one of
186# its deprecating addresses as the source address for this message.
187# Since the SLAAC module tracks the associated Domain ID from the
188# original Prefix TLV, the route lookup for this message should
189# successfully match to `r1` off-mesh route. We validate that the
190# message is indeed delivered to `r1` and passed to host.
191
192r1_counter = r1.get_br_counter_unicast_outbound_packets()
193
194r2.ping('fd00:ee::1', verify_success=False)
195
196verify(r1_counter + 1 == r1.get_br_counter_unicast_outbound_packets())
197
198# Repeat the same process but now ping from `c2` which is an
199# MTD child. `c2` would send the message to its parent `r2` which
200# should be able to successfully do route look up for the deprecating
201# prefix. The message should be delivered to `r1` and passed to host.
202
203r1_counter = r1.get_br_counter_unicast_outbound_packets()
204
205c2.ping('fd00:ff::1', verify_success=False)
206
207verify(r1_counter + 1 == r1.get_br_counter_unicast_outbound_packets())
208
209# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
210# Before the address is expired, re-add ``fd00:11::/64` prefix
211# and check that the address is added back as preferred.
212
213r2.add_prefix("fd00:11:0:0::/64", "paors")
214r2.register_netdata()
215
216verify_within(check_num_netdata_prefixes, 100, 1)
217
218verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
219verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=False)
220
221# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
222# Make sure prefix 2 is expired and removed
223
224
225def check_prefix_2_addr_expire():
226    verify_no_slaac_address_for_prefix('fd00:22:0:0:')
227
228
229verify_within(check_prefix_2_addr_expire, 100)
230
231verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
232
233# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
234# Manually add an address for prefix `fd00:33::/64` on `r1`
235# and `r2` before adding  a related on-mesh prefix. Validate that
236# the SLAAC module does not add addresses.
237
238r1.add_ip_addr('fd00:33::1')
239r2.add_ip_addr('fd00:33::2')
240
241r1.add_prefix("fd00:33:0:0::/64", "paos")
242r1.register_netdata()
243
244verify_within(check_num_netdata_prefixes, 100, 2)
245
246verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
247verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True, origin='manual')
248
249# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
250# Remove the manually added address on `r2` which should trigger
251# SLAAC module to add its own SLAAC address based on the prefix.
252
253r2.remove_ip_addr('fd00:33::2')
254time.sleep(0.1)
255
256verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
257verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True, nodes=[r2], origin='slaac')
258verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True, nodes=[r1], origin='manual')
259
260# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
261# Remove the prefix and make sure the manually added address
262# is not impacted, but the one added by SLAAC on `r2` should
263# be deprecating.
264
265r1.remove_prefix("fd00:33:0:0::/64")
266r1.register_netdata()
267
268verify_within(check_num_netdata_prefixes, 100, 1)
269
270verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
271verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=False, nodes=[r2], origin='slaac')
272verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True, nodes=[r1], origin='manual')
273
274r1.remove_ip_addr('fd00:33::1')
275
276# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
277# Toranj config sets the max number of SLAAC addresses to 4
278# Check that the max limit is applied by adding 5 prefixes.
279
280r1.add_prefix("fd00:22:0:0::/64", "paos")
281r1.add_prefix("fd00:33:0:0::/64", "paos")
282r1.add_prefix("fd00:44:0:0::/64", "paos")
283r1.register_netdata()
284
285verify_within(check_num_netdata_prefixes, 100, 4)
286
287verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
288verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True)
289verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True)
290verify_slaac_address_for_prefix('fd00:44:0:0:', preferred=True)
291
292r1.add_prefix("fd00:55:0:0::/64", "paos")
293r1.register_netdata()
294
295verify_within(check_num_netdata_prefixes, 100, 5)
296
297verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
298verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True)
299verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True)
300verify_slaac_address_for_prefix('fd00:44:0:0:', preferred=True)
301verify_no_slaac_address_for_prefix('fd00:55:0:0::/64')
302
303# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
304# Remove one of the prefixes which should deprecate an
305# existing SLAAC address which should then be evicted to
306# add address for the new `fd00:55::/64` prefix.
307
308r1.remove_prefix("fd00:44:0:0::/64")
309r1.register_netdata()
310
311verify_within(check_num_netdata_prefixes, 100, 4)
312
313verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
314verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True)
315verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=True)
316verify_slaac_address_for_prefix('fd00:55:0:0:', preferred=True)
317verify_no_slaac_address_for_prefix('fd00:44:0:0::/64')
318
319# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
320# Validate oldest entry is evicted when multiple addresses are deprecating
321# and new prefix is added.
322
323r1.remove_prefix("fd00:33:0:0::/64")
324r1.register_netdata()
325time.sleep(0.05)
326r1.remove_prefix("fd00:22:0:0::/64")
327r1.register_netdata()
328time.sleep(0.05)
329r1.remove_prefix("fd00:55:0:0::/64")
330r1.register_netdata()
331time.sleep(0.05)
332
333verify_within(check_num_netdata_prefixes, 100, 1)
334
335verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
336verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=False)
337verify_slaac_address_for_prefix('fd00:33:0:0:', preferred=False)
338verify_slaac_address_for_prefix('fd00:55:0:0:', preferred=False)
339
340# Now add a new prefix `fd00:66::/64`, the `fd00:33::` address
341# which was removed first should be evicted to make room for
342# the SLAAC address for the new prefix.
343
344r1.add_prefix("fd00:66:0:0::/64", "paos")
345r1.register_netdata()
346
347verify_within(check_num_netdata_prefixes, 100, 2)
348
349verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
350verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=False)
351verify_no_slaac_address_for_prefix('fd00:33:0:0:')
352verify_slaac_address_for_prefix('fd00:55:0:0:', preferred=False)
353verify_slaac_address_for_prefix('fd00:66:0:0:', preferred=True)
354
355# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
356# Validate re-adding of a prefix before its address is deprecated.
357
358r1.add_prefix("fd00:22:0:0::/64", "paos")
359r1.register_netdata()
360
361verify_within(check_num_netdata_prefixes, 100, 3)
362
363verify_slaac_address_for_prefix('fd00:11:0:0:', preferred=True)
364verify_slaac_address_for_prefix('fd00:22:0:0:', preferred=True)
365verify_no_slaac_address_for_prefix('fd00:33:0:0:')
366verify_slaac_address_for_prefix('fd00:55:0:0:', preferred=False)
367verify_slaac_address_for_prefix('fd00:66:0:0:', preferred=True)
368
369
370def check_prefix_5_addr_expire():
371    verify_no_slaac_address_for_prefix('fd00:55:0:0:')
372
373
374verify_within(check_prefix_5_addr_expire, 100)
375
376# -----------------------------------------------------------------------------------------------------------------------
377# Test finished
378
379cli.Node.finalize_all_nodes()
380
381print('\'{}\' passed.'.format(test_name))
382