1#!/usr/bin/env python
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 wpan
30from wpan import verify
31
32# -----------------------------------------------------------------------------------------------------------------------
33# Test description:
34#
35# This test covers behavior of wpantund feature for managing of host interface routes (related to off-mesh routes
36# within the Thread network). This feature can be enabled using "Daemon:OffMeshRoute:AutoAddOnInterface" property (is
37# enabled by default).
38#
39# A route corresponding to an off-mesh route would be added on host primary interface (by wpantund),
40# if it is added by at least one (other) device within the network and
41#    (a) either it is not added by host/this-device, or
42#    (b) if it is also added by the device itself then
43#        - filtering of self added routes is not enabled, and
44#        - it is added at lower preference level.
45
46test_name = __file__[:-3] if __file__.endswith('.py') else __file__
47print('-' * 120)
48print('Starting \'{}\''.format(test_name))
49
50# -----------------------------------------------------------------------------------------------------------------------
51# Utility functions
52
53
54def verify_interface_routes(node, route_list):
55    """
56    This function verifies that node has the same interface routes as given by `route_list` which is an array of
57    tuples of (route, prefix_len, metric).
58    """
59    node_routes = wpan.parse_interface_routes_result(node.get(wpan.WPAN_IP6_INTERFACE_ROUTES))
60
61    verify(len(route_list) == len(node_routes))
62
63    for route in route_list:
64        for node_route in node_routes:
65            if (node_route.route_prefix, node_route.prefix_len, node_route.metric) == route:
66                break
67        else:
68            raise wpan.VerifyError('Did not find route {} on node {}'.format(route, node))
69
70
71# -----------------------------------------------------------------------------------------------------------------------
72# Creating `wpan.Nodes` instances
73
74speedup = 4
75wpan.Node.set_time_speedup_factor(speedup)
76
77r1 = wpan.Node()
78r2 = wpan.Node()
79r3 = wpan.Node()
80c3 = wpan.Node()
81
82# -----------------------------------------------------------------------------------------------------------------------
83# Init all nodes
84
85wpan.Node.init_all_nodes()
86
87# -----------------------------------------------------------------------------------------------------------------------
88# Build network topology
89#
90# Test topology:
91#
92#    r1 ---- r2
93#     \      /
94#      \    /
95#       \  /
96#        r3 ---- c3
97#
98# 3 routers, c3 is added to ensure r3 is promoted to a router quickly!
99
100r1.form("route-test")
101
102r1.allowlist_node(r2)
103r2.allowlist_node(r1)
104r2.join_node(r1, wpan.JOIN_TYPE_ROUTER)
105
106r3.allowlist_node(r2)
107r2.allowlist_node(r3)
108r3.join_node(r2, wpan.JOIN_TYPE_ROUTER)
109
110c3.allowlist_node(r3)
111r3.allowlist_node(c3)
112c3.join_node(r3, wpan.JOIN_TYPE_END_DEVICE)
113
114r3.allowlist_node(r1)
115r1.allowlist_node(r3)
116
117# -----------------------------------------------------------------------------------------------------------------------
118# Test implementation
119
120ROUTE1 = 'fd00:abba::'
121LEN1 = 64
122
123ROUTE2 = 'fd00:cafe:feed::'
124LEN2 = 64
125
126ROUTE3 = 'fd00:abba::'
127LEN3 = 48
128
129ROUTE4 = 'fd00:1234::'
130LEN4 = 64
131
132# Route Priority for off-mesh routes
133HIGH_PRIORITY = 1
134MEDIUM_PRIORITY = 0
135LOW_PRIORITY = -1
136
137# Host route metric mapping to off-mesh route (note lower metric value is higher priority)
138HIGH_METRIC = 1
139MEDIUM_METRIC = 256
140LOW_METRIC = 512
141
142WAIT_TIME = 10
143
144# Verify the default daemon configuration for managing host/off-mesh routes
145verify(r1.get(wpan.WPAN_DAEMON_OFF_MESH_ROUTE_AUTO_ADD_ON_INTERFACE) == 'true')
146verify(r1.get(wpan.WPAN_DAEMON_OFF_MESH_ROUTE_FILTER_SELF_AUTO_ADDED) == 'true')
147
148# Disable the auto route add on r2.
149r2.set(wpan.WPAN_DAEMON_OFF_MESH_ROUTE_AUTO_ADD_ON_INTERFACE, 'false')
150
151# Verify the host interface routes are empty when we start.
152verify_interface_routes(r1, [])
153
154# Add all 3 routes on r2.
155r2.add_route(ROUTE1, prefix_len=LEN1, priority=LOW_PRIORITY)
156r2.add_route(ROUTE2, prefix_len=LEN2, priority=MEDIUM_PRIORITY)
157r2.add_route(ROUTE3, prefix_len=LEN3, priority=HIGH_PRIORITY)
158
159
160# We expect to see all 3 routes added on r1 host interface with same priority levels as r2.
161def check_routes_on_r1_1():
162    verify_interface_routes(r1, [(ROUTE1, LEN1, LOW_METRIC), (ROUTE2, LEN2, MEDIUM_METRIC),
163                                 (ROUTE3, LEN3, HIGH_METRIC)])
164
165
166wpan.verify_within(check_routes_on_r1_1, WAIT_TIME)
167
168# - - - - - - - - - - - - - - - - - - - - - - - - - - - -
169
170# Add the same routes on r3 with different priorities.
171r3.add_route(ROUTE1, prefix_len=LEN1, priority=MEDIUM_PRIORITY)
172r3.add_route(ROUTE2, prefix_len=LEN2, priority=LOW_PRIORITY)
173
174
175# We expect the host interface routes on r1 to change accordingly
176def check_routes_on_r1_2():
177    route_list = [(ROUTE1, LEN1, MEDIUM_METRIC), (ROUTE2, LEN2, MEDIUM_METRIC), (ROUTE3, LEN3, HIGH_METRIC)]
178    verify_interface_routes(r1, route_list)
179
180
181wpan.verify_within(check_routes_on_r1_2, WAIT_TIME)
182verify_interface_routes(r2, [])
183
184# - - - - - - - - - - - - - - - - - - - - - - - - - - - -
185
186# Remove the previously added routes from r2.
187r2.remove_route(ROUTE1, prefix_len=LEN1)
188r2.remove_route(ROUTE2, prefix_len=LEN2)
189r2.remove_route(ROUTE3, prefix_len=LEN3)
190
191
192# We expect the host interface routes on r1 to again change accordingly:
193def check_routes_on_r1_3():
194    verify_interface_routes(r1, [(ROUTE1, LEN1, MEDIUM_METRIC), (ROUTE2, LEN2, LOW_METRIC)])
195
196
197wpan.verify_within(check_routes_on_r1_3, WAIT_TIME)
198verify_interface_routes(r2, [])
199
200# - - - - - - - - - - - - - - - - - - - - - - - - - - - -
201
202# Disable "Daemon:OffMeshRoute:FilterSelfAutoAdded" feature on wpantund.
203#
204# The route should be added on host primary interface, if it
205# is added by at least one other device within the network and,
206#  (a) either it is not added by host/this-device, or
207#  (b) if it is also added by device then
208#      - filtering of self added routes is not enabled, and
209#      - it is added at lower preference level.
210
211r1.set(wpan.WPAN_DAEMON_OFF_MESH_ROUTE_FILTER_SELF_AUTO_ADDED, 'false')
212verify(r1.get(wpan.WPAN_DAEMON_OFF_MESH_ROUTE_FILTER_SELF_AUTO_ADDED) == 'false')
213
214# Add ROUTE1 on r1 with low-priority. Since it's also present on r3 with
215# medium priority, we should still see the route on host (as medium).
216
217r1.add_route(ROUTE1, prefix_len=LEN1, priority=LOW_PRIORITY)
218
219verify_interface_routes(r1, [(ROUTE1, LEN1, MEDIUM_METRIC), (ROUTE2, LEN2, LOW_METRIC)])
220
221# Now change ROUTE1 on r1 to be same priority as on r2, now the route should
222# no longer be present on host interface routes.
223
224r1.remove_route(ROUTE1, prefix_len=LEN1)
225r1.add_route(ROUTE1, prefix_len=LEN1, priority=MEDIUM_PRIORITY)
226
227verify_interface_routes(r1, [(ROUTE2, LEN2, LOW_METRIC)])
228
229# Adding ROUTE2 with higher priority should remove it from interface routes
230r1.add_route(ROUTE2, prefix_len=LEN2, priority=MEDIUM_PRIORITY)
231
232verify_interface_routes(r1, [])
233
234# Adding a new ROUTE4 on r1 should not change anything related to interface host routes.
235r1.add_route(ROUTE4, prefix_len=LEN4, priority=MEDIUM_METRIC)
236verify_interface_routes(r1, [])
237
238# Removing ROUTE1 and ROUT2 on r1 should cause them to be added back on host
239# interface (since they are still present as off-mesh routes on r3).
240r1.remove_route(ROUTE1, prefix_len=LEN1)
241r1.remove_route(ROUTE2, prefix_len=LEN2)
242
243verify_interface_routes(r1, [(ROUTE1, LEN1, MEDIUM_METRIC), (ROUTE2, LEN2, LOW_METRIC)])
244
245verify_interface_routes(r2, [])
246
247# - - - - - - - - - - - - - - - - - - - - - - - - - - - -
248# Enable "Daemon:OffMeshRoute:FilterSelfAutoAdded" feature on wpantund.
249
250r1.set(wpan.WPAN_DAEMON_OFF_MESH_ROUTE_FILTER_SELF_AUTO_ADDED, 'true')
251verify(r1.get(wpan.WPAN_DAEMON_OFF_MESH_ROUTE_FILTER_SELF_AUTO_ADDED) == 'true')
252
253# Adding ROUTE1 with any priority should remove it from host interface routes.
254r1.add_route(ROUTE1, prefix_len=LEN1, priority=LOW_PRIORITY)
255
256verify_interface_routes(r1, [(ROUTE2, LEN2, LOW_METRIC)])
257
258r1.remove_route(ROUTE1, prefix_len=LEN1)
259
260verify_interface_routes(r1, [(ROUTE1, LEN1, MEDIUM_METRIC), (ROUTE2, LEN2, LOW_METRIC)])
261
262verify_interface_routes(r2, [])
263
264# -----------------------------------------------------------------------------------------------------------------------
265# Test finished
266
267wpan.Node.finalize_all_nodes()
268
269print('\'{}\' passed.'.format(test_name))
270