1#!/usr/bin/env python3
2#
3#  Copyright (c) 2019, 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: SLAAC address
35#
36# This test covers the behavior of SLAAC module in OpenThread (NCP):
37#
38# - Verify that adding prefix (with SLAAC flag) causes a corresponding SLAAC IPv6 address to be added.
39# - Verify that removing the prefix, would remove the SLAAC address.
40# - Verify behavior when same prefix is added/removed on multiple nodes (with or without SLAAC flag).
41# - Check behavior when a user-added address with the same prefix already exists.
42# - Check behavior when a user-added address with same prefix is removed (SLAAC module should add a SLAAC address).
43# - Ensure removal of prefix does not remove user-added address with same prefix.
44# - Ensure disabling SLAAC module removes previously added SLAAC addresses, and re-enabling it adds them back.
45# - Check behavior when prefix is added while SLAAC module is disabled and then enabled later.
46
47test_name = __file__[:-3] if __file__.endswith('.py') else __file__
48print('-' * 120)
49print('Starting \'{}\''.format(test_name))
50
51# -----------------------------------------------------------------------------------------------------------------------
52# Utility functions
53
54
55def verify_address(node_list, prefix):
56    """
57    This function verifies that all nodes in the `node_list` contain an IPv6 address with the given `prefix`.
58    """
59    for node in node_list:
60        all_addrs = wpan.parse_list(node.get(wpan.WPAN_IP6_ALL_ADDRESSES))
61        verify(any([addr.startswith(prefix[:-1]) for addr in all_addrs]))
62
63
64def verify_no_address(node_list, prefix):
65    """
66    This function verifies that none of nodes in the `node_list` contain an IPv6 address with the given `prefix`.
67    """
68    for node in node_list:
69        all_addrs = wpan.parse_list(node.get(wpan.WPAN_IP6_ALL_ADDRESSES))
70        verify(all([not addr.startswith(prefix[:-1]) for addr in all_addrs]))
71
72
73def verify_prefix(
74    node_list,
75    prefix,
76    prefix_len=64,
77    stable=True,
78    priority='med',
79    on_mesh=False,
80    slaac=False,
81    dhcp=False,
82    configure=False,
83    default_route=False,
84    preferred=False,
85):
86    """
87    This function verifies that the `prefix` is present on all nodes in the `node_list`.
88    """
89    for node in node_list:
90        prefixes = wpan.parse_on_mesh_prefix_result(node.get(wpan.WPAN_THREAD_ON_MESH_PREFIXES))
91        for p in prefixes:
92            if p.prefix == prefix:
93                if (int(p.prefix_len) == prefix_len and p.is_stable() == stable and p.is_on_mesh() == on_mesh and
94                        p.is_def_route() == default_route and p.is_slaac() == slaac and p.is_dhcp() == dhcp and
95                        p.is_config() == configure and p.is_preferred() == preferred and p.priority == priority):
96                    break
97        else:
98            raise wpan.VerifyError("Did not find prefix {} on node {}".format(prefix, node))
99
100
101def verify_no_prefix(node_list, prefix):
102    """
103    This function verifies that the `prefix` is NOT present on any node in the `node_list`.
104    """
105    for node in node_list:
106        prefixes = wpan.parse_on_mesh_prefix_result(node.get(wpan.WPAN_THREAD_ON_MESH_PREFIXES))
107        for p in prefixes:
108            verify(not p.prefix == prefix)
109
110
111# -----------------------------------------------------------------------------------------------------------------------
112# Creating `wpan.Nodes` instances
113
114speedup = 4
115wpan.Node.set_time_speedup_factor(speedup)
116
117r1 = wpan.Node()
118r2 = wpan.Node()
119c2 = wpan.Node()
120
121all_nodes = [r1, r2, c2]
122
123# -----------------------------------------------------------------------------------------------------------------------
124# Init all nodes
125
126wpan.Node.init_all_nodes()
127
128# -----------------------------------------------------------------------------------------------------------------------
129# Build network topology
130
131r1.form('slaac-ncp')
132
133r1.allowlist_node(r2)
134r2.allowlist_node(r1)
135
136r2.join_node(r1, node_type=wpan.JOIN_TYPE_ROUTER)
137
138c2.allowlist_node(r2)
139r2.allowlist_node(c2)
140
141c2.join_node(r2, node_type=wpan.JOIN_TYPE_END_DEVICE)
142
143# -----------------------------------------------------------------------------------------------------------------------
144# Test implementation
145
146# This test covers the SLAAC address management by NCP. So before starting the test we ensure that SLAAC module
147# on NCP is enabled on all nodes, and disable it on wpantund.
148
149for node in all_nodes:
150    verify(node.get(wpan.WPAN_OT_SLAAC_ENABLED) == 'true')
151    node.set("Daemon:IPv6:AutoAddSLAACAddress", 'false')
152    verify(node.get("Daemon:IPv6:AutoAddSLAACAddress") == 'false')
153
154WAIT_INTERVAL = 5
155
156PREFIX = 'fd00:1234::'
157
158# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
159# Add prefix and check all nodes get the prefix and add a corresponding
160# SLAAC address.
161
162r1.add_prefix(PREFIX, stable=True, on_mesh=True, slaac=True)
163
164
165def check_prefix_and_slaac_address_are_added():
166    verify_prefix(all_nodes, PREFIX, stable=True, on_mesh=True, slaac=True)
167    verify_address(all_nodes, PREFIX)
168
169
170wpan.verify_within(check_prefix_and_slaac_address_are_added, WAIT_INTERVAL)
171
172# Save the assigned SLAAC addresses.
173slaac_addrs = [node.find_ip6_address_with_prefix(PREFIX) for node in all_nodes]
174
175# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
176# Check recovery after reseting r1 and c1 (same SLAAC address to be added)
177
178r1.reset()
179wpan.verify_within(check_prefix_and_slaac_address_are_added, WAIT_INTERVAL)
180verify([node.find_ip6_address_with_prefix(PREFIX) for node in all_nodes] == slaac_addrs)
181
182# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
183# Remove the prefix on r1 and verify that the address and prefix are
184# removed on all nodes.
185
186r1.remove_prefix(PREFIX)
187
188
189def check_prefix_and_slaac_address_are_removed():
190    verify_no_prefix(all_nodes, PREFIX)
191    verify_no_address(all_nodes, PREFIX)
192
193
194wpan.verify_within(check_prefix_and_slaac_address_are_removed, WAIT_INTERVAL)
195
196# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
197# Add the prefix on both r1 and r2, then remove from r1 and ensure SLAAC
198# addresses are assigned on all nodes.
199
200# Add prefix on r2
201r2.add_prefix(PREFIX, stable=True, on_mesh=True, slaac=True)
202wpan.verify_within(check_prefix_and_slaac_address_are_added, WAIT_INTERVAL)
203verify([node.find_ip6_address_with_prefix(PREFIX) for node in all_nodes] == slaac_addrs)
204
205# Add same prefix on r1 and verify prefix and addresses stay as before
206r1.add_prefix(PREFIX, stable=True, on_mesh=True, slaac=True)
207wpan.verify_within(check_prefix_and_slaac_address_are_added, WAIT_INTERVAL)
208verify([node.find_ip6_address_with_prefix(PREFIX) for node in all_nodes] == slaac_addrs)
209
210# Remove on r1, addresses and prefixes should stay as before (r2 still has
211# the same prefix)
212r1.remove_prefix(PREFIX)
213time.sleep(0.5)
214wpan.verify_within(check_prefix_and_slaac_address_are_added, WAIT_INTERVAL)
215verify([node.find_ip6_address_with_prefix(PREFIX) for node in all_nodes] == slaac_addrs)
216
217# Remove the prefix on r2 and verify that the address and prefix are now
218# removed on all nodes.
219r2.remove_prefix(PREFIX)
220wpan.verify_within(check_prefix_and_slaac_address_are_removed, WAIT_INTERVAL)
221
222# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
223# Add the prefix on r1 (without SLAAC flag) and r2 (with SLAAC flag)
224
225# Add prefix on r1 without SLAAC flag, and on r2 with SLAAC flag
226r1.add_prefix(PREFIX, stable=True, on_mesh=True, slaac=False)
227r2.add_prefix(PREFIX, stable=True, on_mesh=True, slaac=True)
228wpan.verify_within(check_prefix_and_slaac_address_are_added, WAIT_INTERVAL)
229verify([node.find_ip6_address_with_prefix(PREFIX) for node in all_nodes] == slaac_addrs)
230
231# Now remove the prefix on r2 and verify that SLAAC address is removed
232r2.remove_prefix(PREFIX)
233
234
235def check_slaac_address_is_removed():
236    verify_no_address(all_nodes, PREFIX)
237
238
239wpan.verify_within(check_slaac_address_is_removed, WAIT_INTERVAL)
240
241r1.remove_prefix(PREFIX)
242wpan.verify_within(check_prefix_and_slaac_address_are_removed, WAIT_INTERVAL)
243
244# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
245# Check behavior when a user-added address with same prefix already exists.
246
247IP_ADDRESS = PREFIX + "1234"
248
249# Explicitly add an address with the prefix on r1
250r1.add_ip6_address_on_interface(IP_ADDRESS)
251
252# Afterwards, add the prefix on r2 (with SLAAC flag)
253r2.add_prefix(PREFIX, stable=True, on_mesh=True, slaac=True)
254wpan.verify_within(check_prefix_and_slaac_address_are_added, WAIT_INTERVAL)
255
256# Verify that on r1 we do see the user-added address
257r1_addrs = wpan.parse_list(r1.get(wpan.WPAN_IP6_ALL_ADDRESSES))
258verify(IP_ADDRESS in r1_addrs)
259
260# Also verify that adding the prefix did not add a SLAAC address for same
261# prefix on r1
262r1_addrs.remove(IP_ADDRESS)
263verify(all([not addr.startswith(PREFIX[:-1]) for addr in r1_addrs]))
264
265# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
266# Check behavior when a user-added address with same prefix is removed
267# (SLAAC module should add a SLAAC address).
268
269r1.remove_ip6_address_on_interface(IP_ADDRESS)
270wpan.verify_within(check_prefix_and_slaac_address_are_added, WAIT_INTERVAL)
271verify([node.find_ip6_address_with_prefix(PREFIX) for node in all_nodes] == slaac_addrs)
272
273# Re-add the address
274r1.add_ip6_address_on_interface(IP_ADDRESS)
275
276# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
277# Ensure removal of prefix does not remove user-added address with same prefix.
278
279r2.remove_prefix(PREFIX)
280
281
282def check_ip6_addresses():
283    # Verify that SLAAC addresses are removed on r2 and c2
284    verify_no_address([r2, c2], PREFIX)
285    # And that user-added address matching the prefix is not removed on r1
286    r1_addrs = wpan.parse_list(r1.get(wpan.WPAN_IP6_ALL_ADDRESSES))
287    verify(IP_ADDRESS in r1_addrs)
288
289
290wpan.verify_within(check_ip6_addresses, WAIT_INTERVAL)
291
292r1.remove_ip6_address_on_interface(IP_ADDRESS)
293
294# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
295# Ensure disabling SLAAC module removes previously added SLAAC addresses,
296# and re-enabling it adds them back.
297
298r1.add_prefix(PREFIX, stable=True, on_mesh=True, slaac=True)
299wpan.verify_within(check_prefix_and_slaac_address_are_added, WAIT_INTERVAL)
300verify([node.find_ip6_address_with_prefix(PREFIX) for node in all_nodes] == slaac_addrs)
301
302for node in all_nodes:
303    node.set(wpan.WPAN_OT_SLAAC_ENABLED, 'false')
304wpan.verify_within(check_slaac_address_is_removed, WAIT_INTERVAL)
305
306# Re-enable SLAAC support on NCP and verify addresses are re-added back.
307for node in all_nodes:
308    node.set(wpan.WPAN_OT_SLAAC_ENABLED, 'true')
309wpan.verify_within(check_prefix_and_slaac_address_are_added, WAIT_INTERVAL)
310verify([node.find_ip6_address_with_prefix(PREFIX) for node in all_nodes] == slaac_addrs)
311
312r1.remove_prefix(PREFIX)
313wpan.verify_within(check_prefix_and_slaac_address_are_removed, WAIT_INTERVAL)
314
315# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
316# Check behavior when prefix is added while SLAAC is disabled and then
317# enabled later.
318
319for node in all_nodes:
320    node.set(wpan.WPAN_OT_SLAAC_ENABLED, 'false')
321
322# Add prefix and verify that there is no address added
323r1.add_prefix(PREFIX, stable=True, on_mesh=True, slaac=True)
324wpan.verify_within(check_slaac_address_is_removed, WAIT_INTERVAL)
325
326for node in all_nodes:
327    node.set(wpan.WPAN_OT_SLAAC_ENABLED, 'true')
328wpan.verify_within(check_prefix_and_slaac_address_are_added, WAIT_INTERVAL)
329verify([node.find_ip6_address_with_prefix(PREFIX) for node in all_nodes] == slaac_addrs)
330
331# -----------------------------------------------------------------------------------------------------------------------
332# Test finished
333
334wpan.Node.finalize_all_nodes()
335
336print('\'{}\' passed.'.format(test_name))
337