1#!/usr/bin/env python3 2# 3# Copyright (c) 2021, 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# 29 30import ipaddress 31import unittest 32 33import command 34import config 35import thread_cert 36 37# Test description: 38# This test verifies network data publisher behavior with DNS/SRP service entries and on-mesh prefix and external 39# route entries. 40# 41# Topology: 42# 43# 1 leader, 5 routers and 5 end-devices all connected. 44# 45 46LEADER = 1 47ROUTER1 = 2 48ROUTER2 = 3 49ROUTER3 = 4 50ROUTER4 = 5 51ROUTER5 = 6 52END_DEV1 = 7 53END_DEV2 = 8 54END_DEV3 = 9 55END_DEV4 = 10 56END_DEV5 = 11 57 58WAIT_TIME = 55 59 60ON_MESH_PREFIX = 'fd00:1234:0:0::/64' 61ON_MESH_FLAGS = 'paso' 62 63EXTERNAL_ROUTE = 'fd00:abce:0:0::/64' 64EXTERNAL_FLAGS = 's' 65 66ANYCAST_SEQ_NUM = 4 67 68DNSSRP_ADDRESS = 'fd00::cdef' 69DNSSRP_PORT = 49152 70 71# The desired number of entries (based on related config). 72DESIRED_NUM_DNSSRP_ANYCAST = 8 73DESIRED_NUM_DNSSRP_UNCIAST = 2 74DESIRED_NUM_ON_MESH_PREFIX = 3 75DESIRED_NUM_EXTERNAL_ROUTE = 10 76 77THREAD_ENTERPRISE_NUMBER = 44970 78ANYCAST_SERVICE_NUM = 0x5c 79UNICAST_SERVICE_NUM = 0x5d 80 81 82class NetDataPublisher(thread_cert.TestCase): 83 USE_MESSAGE_FACTORY = False 84 SUPPORT_NCP = False 85 86 TOPOLOGY = { 87 LEADER: { 88 'name': 'LEADER', 89 'mode': 'rdn', 90 }, 91 ROUTER1: { 92 'name': 'ROUTER1', 93 'mode': 'rdn', 94 }, 95 ROUTER2: { 96 'name': 'ROUTER2', 97 'mode': 'rdn', 98 }, 99 ROUTER3: { 100 'name': 'ROUTER3', 101 'mode': 'rdn', 102 }, 103 ROUTER4: { 104 'name': 'ROUTER4', 105 'mode': 'rdn', 106 }, 107 ROUTER5: { 108 'name': 'ROUTER5', 109 'mode': 'rdn', 110 }, 111 END_DEV1: { 112 'name': 'END_DEV1', 113 'mode': 'rn', 114 }, 115 END_DEV2: { 116 'name': 'END_DEV2', 117 'mode': 'rn', 118 }, 119 END_DEV3: { 120 'name': 'END_DEV3', 121 'mode': 'rn', 122 }, 123 END_DEV4: { 124 'name': 'END_DEV4', 125 'mode': 'rn', 126 }, 127 END_DEV5: { 128 'name': 'END_DEV5', 129 'mode': 'rn', 130 }, 131 } 132 133 def verify_anycast_service(self, service): 134 # Verify the data in a single anycast `service` from `get_services()` 135 # Example of `service`: ['44970', '5c04', '', 's', 'bc00'] 136 self.assertEqual(int(service[0]), THREAD_ENTERPRISE_NUMBER) 137 # Check service data 138 service_data = bytes.fromhex(service[1]) 139 self.assertTrue(len(service_data) >= 2) 140 self.assertEqual(service_data[0], ANYCAST_SERVICE_NUM) 141 self.assertEqual(service_data[1], int(ANYCAST_SEQ_NUM)) 142 # Verify that it stable 143 self.assertEqual(service[3], 's') 144 145 def verify_anycast_services(self, services): 146 # Verify a list of anycast `services` from `get_services()` 147 for service in services: 148 self.verify_anycast_service(service) 149 150 def verify_unicast_service(self, service): 151 # Verify the data in a single unicast `service` from `get_services()` 152 # Example of `service`: ['44970', '5d', 'fd000db800000000c6b0e5ee81f940e8223d', 's', '7000'] 153 self.assertEqual(int(service[0]), THREAD_ENTERPRISE_NUMBER) 154 # Check service data 155 service_data = bytes.fromhex(service[1]) 156 self.assertTrue(len(service_data) >= 1) 157 self.assertEqual(service_data[0], UNICAST_SERVICE_NUM) 158 # Verify that it stable 159 self.assertEqual(service[3], 's') 160 161 def verify_unicast_services(self, services): 162 # Verify a list of unicast `services` from `get_services()` 163 for service in services: 164 self.verify_unicast_service(service) 165 166 def check_num_of_prefixes(self, prefixes, num_low, num_med, num_high): 167 # Check and validate the prefix entries in network data (from 168 # `prefixes`) based on number of published prefix entries at 169 # different preference levels given by `num_low`, `num_med`, 170 # `num_high`. Prefixes is a list of the format 171 # 'fd00:1234:0:0::/64 paos low a802'. 172 self.assertEqual(len(prefixes), min(num_high + num_med + num_low, DESIRED_NUM_ON_MESH_PREFIX)) 173 prfs = [prefix.split(' ')[2] for prefix in prefixes] 174 self.assertEqual(prfs.count('high'), min(num_high, DESIRED_NUM_ON_MESH_PREFIX)) 175 self.assertEqual(prfs.count('med'), min(num_med, max(0, DESIRED_NUM_ON_MESH_PREFIX - num_high))) 176 self.assertEqual(prfs.count('low'), min(num_low, max(0, DESIRED_NUM_ON_MESH_PREFIX - num_high - num_med))) 177 178 def check_num_of_routes(self, routes, num_low, num_med, num_high): 179 # Check and validate the prefix entries in network data (from 180 # `routes`) based on number of published prefix entries at 181 # different preference levels given by `num_low`, `num_med`, 182 # `num_high`. Prefixes is a list of the format 183 # 'fd00:abce:0:0::/64 s med 6c01'. 184 self.assertEqual(len(routes), min(num_high + num_med + num_low, DESIRED_NUM_EXTERNAL_ROUTE)) 185 prfs = [route.split(' ')[2] for route in routes] 186 self.assertEqual(prfs.count('high'), min(num_high, DESIRED_NUM_EXTERNAL_ROUTE)) 187 self.assertEqual(prfs.count('med'), min(num_med, max(0, DESIRED_NUM_EXTERNAL_ROUTE - num_high))) 188 self.assertEqual(prfs.count('low'), min(num_low, max(0, DESIRED_NUM_EXTERNAL_ROUTE - num_high - num_med))) 189 190 def test(self): 191 leader = self.nodes[LEADER] 192 router1 = self.nodes[ROUTER1] 193 router2 = self.nodes[ROUTER2] 194 router3 = self.nodes[ROUTER3] 195 router4 = self.nodes[ROUTER4] 196 router5 = self.nodes[ROUTER5] 197 end_dev1 = self.nodes[END_DEV1] 198 end_dev2 = self.nodes[END_DEV2] 199 end_dev3 = self.nodes[END_DEV3] 200 end_dev4 = self.nodes[END_DEV4] 201 end_dev5 = self.nodes[END_DEV5] 202 203 nodes = self.nodes.values() 204 routers = [router1, router2, router3, router4, router5] 205 end_devs = [end_dev1, end_dev2, end_dev3, end_dev4, end_dev5] 206 207 # Start the nodes 208 209 leader.start() 210 self.simulator.go(config.LEADER_STARTUP_DELAY) 211 self.assertEqual(leader.get_state(), 'leader') 212 213 for router in routers: 214 router.start() 215 self.simulator.go(config.ROUTER_STARTUP_DELAY) 216 self.assertEqual(router.get_state(), 'router') 217 218 for end_dev in end_devs: 219 end_dev.start() 220 self.simulator.go(5) 221 self.assertEqual(end_dev.get_state(), 'child') 222 223 #--------------------------------------------------------------------------------- 224 # DNS/SRP anycast entries 225 226 # Publish DNS/SRP anycast on leader and all routers (6 nodes). 227 228 leader.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) 229 for node in routers: 230 node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) 231 self.simulator.go(WAIT_TIME) 232 233 # Check all entries are present in the network data 234 235 services = leader.get_services() 236 self.assertEqual(len(services), min(1 + len(routers), DESIRED_NUM_DNSSRP_ANYCAST)) 237 self.verify_anycast_services(services) 238 239 # Publish same entry on all end-devices (5 nodes). 240 241 for node in end_devs: 242 node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) 243 print(node.name) 244 self.simulator.go(WAIT_TIME) 245 246 # Check number of entries in the network data is limited to 247 # the desired number (8 entries). 248 249 services = leader.get_services() 250 self.assertEqual(len(leader.get_services()), min(len(nodes), DESIRED_NUM_DNSSRP_ANYCAST)) 251 self.verify_anycast_services(services) 252 253 # Unpublish the entry from nodes one by one starting from leader 254 # and check that number of entries is correct in each step. 255 256 num = len(nodes) 257 for node in nodes: 258 node.netdata_unpublish_dnssrp() 259 self.simulator.go(WAIT_TIME) 260 num -= 1 261 services = leader.get_services() 262 self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_ANYCAST)) 263 self.verify_anycast_services(services) 264 265 #--------------------------------------------------------------------------------- 266 # DNS/SRP unicast entries 267 268 # Publish DNS/SRP unicast address on all routers, first using 269 # MLE-EID address, then change to use specific address. Verify 270 # that number of entries in network data is correct in each step 271 # and that entries are switched correctly. 272 num = 0 273 for node in routers: 274 node.netdata_publish_dnssrp_unicast_mleid(DNSSRP_PORT) 275 self.simulator.go(WAIT_TIME) 276 num += 1 277 services = leader.get_services() 278 self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNCIAST)) 279 self.verify_unicast_services(services) 280 281 for node in routers: 282 node.netdata_publish_dnssrp_unicast(DNSSRP_ADDRESS, DNSSRP_PORT) 283 self.simulator.go(WAIT_TIME) 284 services = leader.get_services() 285 self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNCIAST)) 286 self.verify_unicast_services(services) 287 288 for node in routers: 289 node.srp_server_set_enabled(True) 290 self.simulator.go(WAIT_TIME) 291 self.assertEqual(sum(node.srp_server_get_state() == 'running' for node in routers), 292 min(len(routers), DESIRED_NUM_DNSSRP_UNCIAST)) 293 self.assertEqual(sum(node.srp_server_get_state() == 'stopped' for node in routers), 294 max(len(routers) - DESIRED_NUM_DNSSRP_UNCIAST, 0)) 295 296 for node in routers: 297 node.netdata_unpublish_dnssrp() 298 self.simulator.go(WAIT_TIME) 299 num -= 1 300 services = leader.get_services() 301 self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNCIAST)) 302 self.verify_unicast_services(services) 303 for node in routers: 304 node.srp_server_set_enabled(False) 305 self.assertEqual(node.srp_server_get_state(), 'disabled') 306 307 #--------------------------------------------------------------------------------- 308 # DNS/SRP entries: Verify publisher preference when removing 309 # entries. 310 # 311 # Publish DNS/SRP anycast on 8 nodes: leader, router1, 312 # router2, and all 5 end-devices. Afterwards, manually add 313 # the same service entry in Network Data on router3, router4, 314 # and router5 and at each step check that entry from one of 315 # the end-devices is removed (publisher prefers 316 # entries from routers over the ones from end-devices). 317 318 num = 0 319 test_routers = [leader, router1, router2] 320 for node in test_routers + end_devs: 321 node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) 322 self.simulator.go(WAIT_TIME) 323 num += 1 324 services = leader.get_services() 325 self.assertEqual(len(services), num) 326 self.verify_anycast_services(services) 327 328 self.assertEqual(num, DESIRED_NUM_DNSSRP_ANYCAST) 329 330 service_data = '%02x%02x' % (ANYCAST_SERVICE_NUM, int(ANYCAST_SEQ_NUM)) 331 for node in [router3, router4, router5]: 332 node.add_service(str(THREAD_ENTERPRISE_NUMBER), service_data, '00') 333 node.register_netdata() 334 self.simulator.go(WAIT_TIME) 335 336 services = leader.get_services() 337 self.assertEqual(len(services), num) 338 self.verify_anycast_services(services) 339 340 service_rlocs = [int(service[4], 16) for service in services] 341 test_routers.append(node) 342 343 for router in test_routers: 344 self.assertIn(router.get_addr16(), service_rlocs) 345 346 #--------------------------------------------------------------------------------- 347 # On-mesh prefix 348 349 # Publish the same on-mesh prefix on different nodes (low 350 # preference on end-devices, medium preference on routers, and 351 # high on leader) one by one and then unpublish them one by one. 352 # Verify that at each step the entries in the network data are 353 # correct. Particularly verify that that higher preference 354 # entries replace lower preference ones even when there are 355 # already desired number in network data. 356 357 num_low = 0 358 num_med = 0 359 num_high = 0 360 361 for node in end_devs: 362 node.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'low') 363 self.simulator.go(WAIT_TIME) 364 num_low += 1 365 prefixes = leader.get_prefixes() 366 self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) 367 368 # Now add the entry as 'med' on routers and check that we see those in the list. 369 for node in routers: 370 node.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'med') 371 self.simulator.go(WAIT_TIME) 372 num_med += 1 373 prefixes = leader.get_prefixes() 374 self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) 375 376 leader.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'high') 377 self.simulator.go(WAIT_TIME) 378 num_high += 1 379 prefixes = leader.get_prefixes() 380 self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) 381 382 for node in routers: 383 node.netdata_unpublish_prefix(ON_MESH_PREFIX) 384 self.simulator.go(WAIT_TIME) 385 num_med -= 1 386 prefixes = leader.get_prefixes() 387 self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) 388 389 leader.netdata_unpublish_prefix(ON_MESH_PREFIX) 390 self.simulator.go(WAIT_TIME) 391 num_high -= 1 392 prefixes = leader.get_prefixes() 393 self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) 394 395 for node in end_devs: 396 node.netdata_unpublish_prefix(ON_MESH_PREFIX) 397 self.simulator.go(WAIT_TIME) 398 num_low -= 1 399 prefixes = leader.get_prefixes() 400 self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) 401 402 #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 403 # Verify that when removing extra entries, non-preferred entries 404 # are removed first over preferred ones. Entries from routers are 405 # preferred over similar entries from end-devices. 406 407 # Publish prefix entry on `end_dev1` and verify that it is added. 408 409 end_dev1.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'med') 410 self.simulator.go(WAIT_TIME) 411 prefixes = leader.get_prefixes() 412 self.check_num_of_prefixes(prefixes, 0, 1, 0) 413 414 # Publish same prefix on all routers (again as `med` preference). 415 # Verify that we reach the desired number of prefix entries in network 416 # data and that the entry from `end_dev1` is present in network data. 417 418 for node in routers: 419 node.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'med') 420 self.simulator.go(WAIT_TIME) 421 prefixes = leader.get_prefixes() 422 self.check_num_of_prefixes(prefixes, 0, 1 + len(routers), 0) 423 self.assertTrue(1 + len(routers) >= DESIRED_NUM_ON_MESH_PREFIX) 424 # `prefixes` is a list of format 'fd00:1234:0:0::/64 paos low a802' 425 rlocs = [int(prefix.split(' ')[3], 16) for prefix in prefixes] 426 self.assertTrue(rlocs.count(end_dev1.get_addr16()) == 1) 427 428 # Publish same prefix now with `high` preference on leader. 429 # Since it is `high` preference, it is added to network data 430 # which leads to total number of entries to go above the desired 431 # number temporarily and trigger other nodes to try to remove 432 # their entry. The entries from routers should be preferred over 433 # the one from `end_dev1` so that is the one we expect to be 434 # removed. We check that this is the case (i.e., the entry from 435 # `end_dev1` is no longer present in network data). 436 437 leader.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'high') 438 self.simulator.go(WAIT_TIME) 439 prefixes = leader.get_prefixes() 440 self.check_num_of_prefixes(prefixes, 0, 1 + len(routers), 1) 441 rlocs = [int(prefix.split(' ')[3], 16) for prefix in prefixes] 442 self.assertTrue(rlocs.count(end_dev1.get_addr16()) == 0) 443 444 #--------------------------------------------------------------------------------- 445 # External route 446 447 # Publish same external route on all nodes with low preference. 448 449 num = 0 450 for node in nodes: 451 node.netdata_publish_route(EXTERNAL_ROUTE, EXTERNAL_FLAGS, 'low') 452 self.simulator.go(WAIT_TIME) 453 num += 1 454 routes = leader.get_routes() 455 self.check_num_of_routes(routes, num, 0, 0) 456 457 # Change the preference level of the existing entry on leader to high. 458 459 leader.netdata_publish_route(EXTERNAL_ROUTE, EXTERNAL_FLAGS, 'high') 460 self.simulator.go(WAIT_TIME) 461 routes = leader.get_routes() 462 self.check_num_of_routes(routes, num - 1, 0, 1) 463 464 # Publish the same prefix on leader as an on-mesh prefix. Make 465 # sure it is removed from external routes and now seen in the 466 # prefix list. 467 468 leader.netdata_publish_prefix(EXTERNAL_ROUTE, ON_MESH_FLAGS, 'low') 469 self.simulator.go(WAIT_TIME) 470 routes = leader.get_routes() 471 self.check_num_of_routes(routes, num - 1, 0, 0) 472 473 prefixes = leader.get_prefixes() 474 print(prefixes) 475 self.assertIn(EXTERNAL_ROUTE, [prefix.split()[0] for prefix in prefixes]) 476 477 478if __name__ == '__main__': 479 unittest.main() 480