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 on-mesh prefixes
36# within the Thread network).
37#
38# When enabled, wpantund would add a route on host primary interface for any prefix from thread network (with on-mesh
39# flag set). This in turn ensures that traffic destined to an IPv6 address matching the prefix would be correctly
40# forwarded to the `wpan` interface on host.
41
42test_name = __file__[:-3] if __file__.endswith('.py') else __file__
43print('-' * 120)
44print('Starting \'{}\''.format(test_name))
45
46# -----------------------------------------------------------------------------------------------------------------------
47# Utility functions
48
49
50def verify_interface_routes(node, route_list):
51    """
52    This function verifies that node has the same interface routes as given by `route_list` which is an array of
53    tuples of (route, prefix_len, metric).
54    """
55    node_routes = wpan.parse_interface_routes_result(node.get(wpan.WPAN_IP6_INTERFACE_ROUTES))
56
57    verify(len(route_list) == len(node_routes))
58
59    for route in route_list:
60        for node_route in node_routes:
61            if (node_route.route_prefix, node_route.prefix_len, node_route.metric) == route:
62                break
63        else:
64            raise wpan.VerifyError('Did not find route {} on node {}'.format(route, node))
65
66
67# -----------------------------------------------------------------------------------------------------------------------
68# Creating `wpan.Nodes` instances
69
70speedup = 4
71wpan.Node.set_time_speedup_factor(speedup)
72
73r1 = wpan.Node()
74r2 = wpan.Node()
75c2 = wpan.Node()
76
77# -----------------------------------------------------------------------------------------------------------------------
78# Init all nodes
79
80wpan.Node.init_all_nodes()
81
82# -----------------------------------------------------------------------------------------------------------------------
83# Build network topology
84#
85
86r1.form("prefix-route")
87
88r1.allowlist_node(r2)
89r2.allowlist_node(r1)
90r2.join_node(r1, wpan.JOIN_TYPE_ROUTER)
91
92c2.allowlist_node(r2)
93r2.allowlist_node(c2)
94c2.join_node(r2, wpan.JOIN_TYPE_END_DEVICE)
95
96# -----------------------------------------------------------------------------------------------------------------------
97# Test implementation
98
99PREFIX1 = 'fd00:abba::'
100ADDRESS1 = PREFIX1 + '1'
101LEN1 = 64
102
103PREFIX2 = 'fd00:cafe:feed::'
104LEN2 = 64
105
106PREFIX3 = 'fd00:1234::'
107LEN3 = 64
108
109ROUTE4 = 'fd00:1234::'
110LEN4 = 48
111
112ROUTE5 = 'fd00:beef::'
113LEN5 = 64
114
115MEDIUM_METRIC = 256
116MEDIUM_PRIORITY = 0
117
118WAIT_TIME = 10
119
120# Verify the default daemon configuration
121verify(r1.get(wpan.WPAN_DAEMON_ON_MESH_PREFIX_AUTO_ADD_AS_INTERFACE_ROUTE) == 'true')
122
123r1.set(wpan.WPAN_DAEMON_OFF_MESH_ROUTE_AUTO_ADD_ON_INTERFACE, 'false')
124verify(r1.get(wpan.WPAN_DAEMON_OFF_MESH_ROUTE_AUTO_ADD_ON_INTERFACE) == 'false')
125
126# Add on-mesh prefixes 1, 2, and 3 and off-mesh route 5 on r2.
127r2.add_prefix(PREFIX1, prefix_len=LEN1, on_mesh=True, slaac=False)
128r2.add_prefix(PREFIX2, prefix_len=LEN2, on_mesh=True, slaac=True)
129r2.add_prefix(PREFIX3, prefix_len=LEN2, on_mesh=False, slaac=False)
130r2.add_route(ROUTE5, prefix_len=LEN5, priority=MEDIUM_PRIORITY)
131
132
133# We expect to only see routes associated the first two (which are on-mesh) on r1.
134def check_routes_on_r1_is_prefix1_and_prefix2():
135    verify_interface_routes(r1, [(PREFIX1, LEN1, MEDIUM_METRIC), (PREFIX2, LEN2, MEDIUM_METRIC)])
136
137
138wpan.verify_within(check_routes_on_r1_is_prefix1_and_prefix2, WAIT_TIME)
139
140# Remove all prefixes
141r2.remove_prefix(PREFIX1, prefix_len=LEN1)
142r2.remove_prefix(PREFIX2, prefix_len=LEN2)
143r2.remove_prefix(PREFIX3, prefix_len=LEN2)
144r2.remove_route(ROUTE5, prefix_len=LEN5)
145
146
147# We expect all associated routes to be removed on r1
148def check_routes_on_r1_is_empty():
149    verify_interface_routes(r1, [])
150
151
152wpan.verify_within(check_routes_on_r1_is_empty, WAIT_TIME)
153
154# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
155# Test behavior when an address with same prefix in already added on the interface
156
157r1.add_ip6_address_on_interface(ADDRESS1, prefix_len=LEN1)
158r2.add_prefix(PREFIX1, prefix_len=LEN1, on_mesh=True, slaac=False)
159
160
161def check_routes_on_r1_is_only_prefix1():
162    verify_interface_routes(r1, [(PREFIX1, LEN1, MEDIUM_METRIC)])
163
164
165wpan.verify_within(check_routes_on_r1_is_only_prefix1, WAIT_TIME)
166
167r1.remove_ip6_address_on_interface(ADDRESS1, prefix_len=LEN1)
168wpan.verify_within(check_routes_on_r1_is_only_prefix1, WAIT_TIME)
169
170r2.remove_prefix(PREFIX1, prefix_len=LEN1)
171
172wpan.verify_within(check_routes_on_r1_is_empty, WAIT_TIME)
173
174# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
175# Test behavior when we have similar route and prefix (different prefix_len)
176
177r1.set(wpan.WPAN_DAEMON_OFF_MESH_ROUTE_AUTO_ADD_ON_INTERFACE, 'true')
178verify(r1.get(wpan.WPAN_DAEMON_OFF_MESH_ROUTE_AUTO_ADD_ON_INTERFACE) == 'true')
179
180r2.add_route(ROUTE5, prefix_len=LEN5, priority=MEDIUM_PRIORITY)
181r2.add_route(ROUTE4, prefix_len=LEN4, priority=MEDIUM_PRIORITY)
182r1.add_prefix(PREFIX3, prefix_len=LEN3, on_mesh=True, slaac=False)
183
184
185def check_routes_on_r1_is_prefix3_route4_and_route5():
186    route_list = [(PREFIX3, LEN3, MEDIUM_METRIC), (ROUTE4, LEN4, MEDIUM_METRIC), (ROUTE5, LEN5, MEDIUM_METRIC)]
187    verify_interface_routes(r1, route_list)
188
189
190wpan.verify_within(check_routes_on_r1_is_prefix3_route4_and_route5, WAIT_TIME)
191
192r2.remove_route(ROUTE5, prefix_len=LEN5)
193r1.remove_prefix(PREFIX3, prefix_len=LEN3)
194
195
196def check_routes_on_r1_is_only_route4():
197    verify_interface_routes(r1, [(ROUTE4, LEN4, MEDIUM_METRIC)])
198
199
200wpan.verify_within(check_routes_on_r1_is_only_route4, WAIT_TIME)
201
202# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
203# Test behavior when feature is disabled
204
205r1.set(wpan.WPAN_DAEMON_ON_MESH_PREFIX_AUTO_ADD_AS_INTERFACE_ROUTE, 'false')
206verify(r1.get(wpan.WPAN_DAEMON_ON_MESH_PREFIX_AUTO_ADD_AS_INTERFACE_ROUTE) == 'false')
207
208r1.add_prefix(PREFIX3, prefix_len=LEN3, on_mesh=True, slaac=False)
209wpan.verify_within(check_routes_on_r1_is_only_route4, WAIT_TIME)
210
211# -----------------------------------------------------------------------------------------------------------------------
212# Test finished
213
214wpan.Node.finalize_all_nodes()
215
216print('\'{}\' passed.'.format(test_name))
217