|
| 1 | +# Copyright 2020 Google LLC |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | +"""Test traffic flow to off-mesh addresses resulted from addition of off-mesh routes (on routers and FEDs). |
| 15 | +""" |
| 16 | + |
| 17 | +import enum |
| 18 | +import random |
| 19 | +import time |
| 20 | +import unittest |
| 21 | + |
| 22 | +from silk.config import wpan_constants as wpan |
| 23 | +from silk.node.wpan_node import WpanCredentials |
| 24 | +from silk.utils import process_cleanup |
| 25 | +from silk.tools import wpan_table_parser |
| 26 | +from silk.tools.wpan_util import verify_within |
| 27 | +from silk.unit_tests.test_utils import random_string |
| 28 | +import silk.hw.hw_resource as hwr |
| 29 | +import silk.node.fifteen_four_dev_board as ffdb |
| 30 | +import silk.tests.testcase as testcase |
| 31 | + |
| 32 | +hwr.global_instance() |
| 33 | + |
| 34 | +WAIT_TIME = 10 |
| 35 | +NUM_ROUTES = 3 |
| 36 | +NUM_ROUTES_LOCAL = 1 |
| 37 | +ON_MESH_PREFIX = "fd00:1234::" |
| 38 | +OFF_MESH_ROUTE_1 = "fd00:abba::" |
| 39 | +OFF_MESH_ROUTE_2 = "fd00:cafe::" |
| 40 | +OFF_MESH_ROUTE_3 = "fd00:baba::" |
| 41 | +OFF_MESH_ADDR_1 = OFF_MESH_ROUTE_1 + "1" |
| 42 | +OFF_MESH_ADDR_2 = OFF_MESH_ROUTE_2 + "2" |
| 43 | +OFF_MESH_ADDR_3 = OFF_MESH_ROUTE_3 + "3" |
| 44 | +POLL_INTERVAL = 400 |
| 45 | + |
| 46 | + |
| 47 | +class TestOffMeshRouteTraffic(testcase.TestCase): |
| 48 | + # Test description: Adding off-mesh routes (on routers and FEDs) and traffic flow to off-mesh addresses. |
| 49 | + # |
| 50 | + # Test topology: |
| 51 | + # |
| 52 | + # r1 ---- r2 |
| 53 | + # | | |
| 54 | + # | | |
| 55 | + # fed1 sed2 |
| 56 | + # |
| 57 | + # The off-mesh-routes are added as follows: |
| 58 | + # - `r1` adds `OFF_MESH_ROUTE_1`, |
| 59 | + # - `r2` adds `OFF_MESH_ROUTE_2`, |
| 60 | + # - `fed1` adds `OFF_MESH_ROUTE_3`. |
| 61 | + # |
| 62 | + # Traffic flow: |
| 63 | + # - From `sed2` to an address matching `OFF_MESH_ROUTE_1` (verify it is received on `r1`), |
| 64 | + # - From `r1` to an address matching `OFF_MESH_ROUTE_2` (verify it is received on `r2`), |
| 65 | + # - From `r2` to an address matching `OFF_MESH_ROUTE_3` (verify it is received on `fed1`) |
| 66 | + # |
| 67 | + |
| 68 | + @classmethod |
| 69 | + def hardware_select(cls: 'TestOffMeshRouteTraffic'): |
| 70 | + cls.r1 = ffdb.ThreadDevBoard() |
| 71 | + cls.fed1 = ffdb.ThreadDevBoard() |
| 72 | + cls.r2 = ffdb.ThreadDevBoard() |
| 73 | + cls.sed2 = ffdb.ThreadDevBoard() |
| 74 | + |
| 75 | + cls.all_nodes = [cls.r1, cls.fed1, cls.r2, cls.sed2] |
| 76 | + |
| 77 | + @classmethod |
| 78 | + @testcase.setup_class_decorator |
| 79 | + def setUpClass(cls: 'TestOffMeshRouteTraffic'): |
| 80 | + # Check and clean up wpantund process if any left over |
| 81 | + process_cleanup.ps_cleanup() |
| 82 | + |
| 83 | + cls.hardware_select() |
| 84 | + |
| 85 | + for device in cls.all_nodes: |
| 86 | + device.set_logger(cls.logger) |
| 87 | + cls.add_test_device(device) |
| 88 | + device.set_up() |
| 89 | + |
| 90 | + cls.network_data = WpanCredentials(network_name="SILK-{0:04X}".format(random.randint(0, 0xffff)), |
| 91 | + psk="00112233445566778899aabbccdd{0:04x}".format(random.randint(0, 0xffff)), |
| 92 | + channel=random.randint(11, 25), |
| 93 | + fabric_id="{0:06x}dead".format(random.randint(0, 0xffffff))) |
| 94 | + |
| 95 | + cls.thread_sniffer_init(cls.network_data.channel) |
| 96 | + |
| 97 | + @classmethod |
| 98 | + @testcase.teardown_class_decorator |
| 99 | + def tearDownClass(cls: 'TestOffMeshRouteTraffic'): |
| 100 | + for device in cls.device_list: |
| 101 | + device.tear_down() |
| 102 | + |
| 103 | + @testcase.setup_decorator |
| 104 | + def setUp(self): |
| 105 | + pass |
| 106 | + |
| 107 | + @testcase.teardown_decorator |
| 108 | + def tearDown(self): |
| 109 | + pass |
| 110 | + |
| 111 | + @testcase.test_method_decorator |
| 112 | + def test01_disable_autoupdate_interface_address_on_ncp(self): |
| 113 | + for node in self.all_nodes: |
| 114 | + # Disable `AutoUpdateInterfaceAddrsOnNCP` feature on wpantund |
| 115 | + # for all nodes. This ensures that added IPv6 address (on linux |
| 116 | + # interface) are not pushed to NCP (and therefore are not |
| 117 | + # on-mesh). |
| 118 | + node.setprop("Daemon:IPv6:AutoUpdateInterfaceAddrsOnNCP", "false") |
| 119 | + self.assertEqual(node.getprop("Daemon:IPv6:AutoUpdateInterfaceAddrsOnNCP"), "false") |
| 120 | + |
| 121 | + @testcase.test_method_decorator |
| 122 | + def test02_pairing(self): |
| 123 | + # allowlisting between leader and router |
| 124 | + self.r1.allowlist_node(self.r2) |
| 125 | + self.r2.allowlist_node(self.r1) |
| 126 | + |
| 127 | + # allowlisting between leader and end device |
| 128 | + self.r1.allowlist_node(self.fed1) |
| 129 | + self.fed1.allowlist_node(self.r1) |
| 130 | + |
| 131 | + # allowlisting between router and sleepy-end-device |
| 132 | + self.r2.allowlist_node(self.sed2) |
| 133 | + self.sed2.allowlist_node(self.r2) |
| 134 | + |
| 135 | + self.r1.form(self.network_data, "router") |
| 136 | + self.r1.permit_join(60) |
| 137 | + self.wait_for_completion(self.device_list) |
| 138 | + |
| 139 | + self.logger.info(self.r1.ip6_lla) |
| 140 | + self.logger.info(self.r1.ip6_thread_ula) |
| 141 | + |
| 142 | + self.network_data.xpanid = self.r1.xpanid |
| 143 | + self.network_data.panid = self.r1.panid |
| 144 | + |
| 145 | + self.r2.join(self.network_data, "router") |
| 146 | + self.wait_for_completion(self.device_list) |
| 147 | + |
| 148 | + self.fed1.join(self.network_data, "end-node") |
| 149 | + self.wait_for_completion(self.device_list) |
| 150 | + |
| 151 | + self.sed2.join(self.network_data, "sleepy-end-device") |
| 152 | + self.sed2.set_sleep_poll_interval(POLL_INTERVAL) |
| 153 | + self.wait_for_completion(self.device_list) |
| 154 | + |
| 155 | + @testcase.test_method_decorator |
| 156 | + def test03_verify_off_mesh_routes(self): |
| 157 | + # Add on-mesh prefix |
| 158 | + self.r1.config_gateway(ON_MESH_PREFIX) |
| 159 | + |
| 160 | + # The off-mesh-routes are added as follows: |
| 161 | + # - `r1` adds OFF_MESH_ROUTE_1, |
| 162 | + # - `r2` adds OFF_MESH_ROUTE_2, |
| 163 | + # - `fed1` adds OFF_MESH_ROUTE_3. |
| 164 | + |
| 165 | + self.r1.add_route_using_prefix(OFF_MESH_ROUTE_1) |
| 166 | + self.r1.add_ip6_address_on_interface(OFF_MESH_ADDR_1, prefix_len=64) |
| 167 | + self.wait_for_completion(self.device_list) |
| 168 | + |
| 169 | + self.r2.add_route_using_prefix(OFF_MESH_ROUTE_2) |
| 170 | + self.r2.add_ip6_address_on_interface(OFF_MESH_ADDR_2, prefix_len=64) |
| 171 | + self.wait_for_completion(self.device_list) |
| 172 | + |
| 173 | + self.fed1.add_route_using_prefix(OFF_MESH_ROUTE_3) |
| 174 | + self.fed1.add_ip6_address_on_interface(OFF_MESH_ADDR_3, prefix_len=64) |
| 175 | + self.wait_for_completion(self.device_list) |
| 176 | + |
| 177 | + # Wait till network data is updated on r1, r2, and sed2 and they all see all |
| 178 | + # the added off-mesh routes. |
| 179 | + time.sleep(WAIT_TIME) |
| 180 | + |
| 181 | + def check_off_mesh_routes(): |
| 182 | + # If a node itself adds a route, the route entry will be seen twice in |
| 183 | + # its WPAN_THREAD_OFF_MESH_ROUTES list (one time as part of network-wide |
| 184 | + # network data and again as part of the local network data). Note that |
| 185 | + # `r1 and `r2` each add a route, while `sed2` does not. |
| 186 | + r1_routes = self.r1.wpanctl("get", "get " + wpan.WPAN_THREAD_OFF_MESH_ROUTES, 2) |
| 187 | + r1_routes = wpan_table_parser.parse_list(r1_routes) |
| 188 | + self.assertEqual(len(r1_routes), NUM_ROUTES + NUM_ROUTES_LOCAL) |
| 189 | + |
| 190 | + r2_routes = self.r2.wpanctl("get", "get " + wpan.WPAN_THREAD_OFF_MESH_ROUTES, 2) |
| 191 | + r2_routes = wpan_table_parser.parse_list(r2_routes) |
| 192 | + self.assertEqual(len(r2_routes), NUM_ROUTES + NUM_ROUTES_LOCAL) |
| 193 | + |
| 194 | + sed2_routes = self.sed2.wpanctl("get", "get " + wpan.WPAN_THREAD_OFF_MESH_ROUTES, 2) |
| 195 | + sed2_routes = wpan_table_parser.parse_list(sed2_routes) |
| 196 | + self.assertEqual(len(sed2_routes), NUM_ROUTES) |
| 197 | + |
| 198 | + verify_within(check_off_mesh_routes, WAIT_TIME) |
| 199 | + |
| 200 | + @testcase.test_method_decorator |
| 201 | + def test04_transmit_receive(self): |
| 202 | + # Traffic from `sed2` to `OFF_MESH_ADDR_1` (verify that it is received on`r1`). |
| 203 | + # Traffic from `r1` to `OFF_MESH_ADDR_2` (verify that it is received on `r2`), |
| 204 | + # Traffic from `r2` to `OFF_MESH_ADDR_3` (verify that it is received on `fed1`) |
| 205 | + class AddressType(enum.Enum): |
| 206 | + Prefix = 0 |
| 207 | + |
| 208 | + addresses = [ |
| 209 | + (self.sed2, self.r1, AddressType.Prefix, OFF_MESH_ADDR_1), |
| 210 | + (self.r1, self.r2, AddressType.Prefix, OFF_MESH_ADDR_2), |
| 211 | + (self.r2, self.fed1, AddressType.Prefix, OFF_MESH_ADDR_3), |
| 212 | + ] |
| 213 | + |
| 214 | + timeout = 5 |
| 215 | + delay = 1 |
| 216 | + for i, (src, dst, src_type, dst_address) in enumerate(addresses): |
| 217 | + port = random.randint(10000 + i * 100, 10099 + i * 100) |
| 218 | + message = random_string(10) |
| 219 | + src_address = "" |
| 220 | + if src_type == AddressType.Prefix: |
| 221 | + address = src.find_ip6_address_with_prefix(ON_MESH_PREFIX) |
| 222 | + self.wait_for_completion(self.device_list) |
| 223 | + src_address = f"{address}%{src.netns}" |
| 224 | + |
| 225 | + dst.receive_udp_data(port, message, timeout) |
| 226 | + time.sleep(delay) |
| 227 | + src.send_udp_data(dst_address, port, message, src_address) |
| 228 | + |
| 229 | + time.sleep(timeout - delay) |
| 230 | + |
| 231 | + |
| 232 | +if __name__ == "__main__": |
| 233 | + unittest.main() |
0 commit comments