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_UNICAST = 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 service data unicast entries 267 268 num = 0 269 for node in routers: 270 node.netdata_publish_dnssrp_unicast(DNSSRP_ADDRESS, DNSSRP_PORT) 271 self.simulator.go(WAIT_TIME) 272 num += 1 273 services = leader.get_services() 274 self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) 275 self.verify_unicast_services(services) 276 277 for node in routers: 278 node.srp_server_set_enabled(True) 279 self.simulator.go(WAIT_TIME) 280 281 self.assertEqual(sum(node.srp_server_get_state() == 'running' for node in routers), 282 min(len(routers), DESIRED_NUM_DNSSRP_UNICAST)) 283 self.assertEqual(sum(node.srp_server_get_state() == 'stopped' for node in routers), 284 max(len(routers) - DESIRED_NUM_DNSSRP_UNICAST, 0)) 285 286 for node in routers: 287 node.netdata_unpublish_dnssrp() 288 self.simulator.go(WAIT_TIME) 289 num -= 1 290 services = leader.get_services() 291 self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) 292 self.verify_unicast_services(services) 293 for node in routers: 294 node.srp_server_set_enabled(False) 295 self.assertEqual(node.srp_server_get_state(), 'disabled') 296 297 #--------------------------------------------------------------------------------- 298 # DNS/SRP server data unicast entries 299 300 num = 0 301 for node in routers: 302 node.netdata_publish_dnssrp_unicast_mleid(DNSSRP_PORT) 303 self.simulator.go(WAIT_TIME) 304 num += 1 305 services = leader.get_services() 306 self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) 307 self.verify_unicast_services(services) 308 309 for node in routers: 310 node.srp_server_set_enabled(True) 311 self.simulator.go(WAIT_TIME) 312 self.assertEqual(sum(node.srp_server_get_state() == 'running' for node in routers), 313 min(len(routers), DESIRED_NUM_DNSSRP_UNICAST)) 314 self.assertEqual(sum(node.srp_server_get_state() == 'stopped' for node in routers), 315 max(len(routers) - DESIRED_NUM_DNSSRP_UNICAST, 0)) 316 317 for node in routers: 318 node.netdata_unpublish_dnssrp() 319 self.simulator.go(WAIT_TIME) 320 num -= 1 321 services = leader.get_services() 322 self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) 323 self.verify_unicast_services(services) 324 for node in routers: 325 node.srp_server_set_enabled(False) 326 self.assertEqual(node.srp_server_get_state(), 'disabled') 327 328 #--------------------------------------------------------------------------------- 329 # DNS/SRP server data unicast vs anycast 330 331 num = 0 332 for node in routers: 333 node.netdata_publish_dnssrp_unicast_mleid(DNSSRP_PORT) 334 self.simulator.go(WAIT_TIME) 335 num += 1 336 services = leader.get_services() 337 self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) 338 self.verify_unicast_services(services) 339 340 # Verify that publishing an anycast entry will update the 341 # limit for the server data unicast address entry and all are 342 # removed. 343 344 leader.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) 345 self.simulator.go(WAIT_TIME) 346 services = leader.get_services() 347 self.assertEqual(len(services), 1) 348 self.verify_anycast_services(services) 349 350 # Removing the anycast entry will cause the lower priority 351 # server data unicast entries to be added again. 352 353 leader.netdata_unpublish_dnssrp() 354 self.simulator.go(WAIT_TIME) 355 356 services = leader.get_services() 357 self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) 358 self.verify_unicast_services(services) 359 360 #--------------------------------------------------------------------------------- 361 # DNS/SRP server data unicast vs service data unicast 362 363 leader.netdata_publish_dnssrp_unicast(DNSSRP_ADDRESS, DNSSRP_PORT) 364 self.simulator.go(WAIT_TIME) 365 services = leader.get_services() 366 self.assertEqual(len(services), 1) 367 self.verify_unicast_services(services) 368 369 # Removing the service data unicast entry will cause the lower 370 # priority server data unicast entries to be added again. 371 372 leader.netdata_unpublish_dnssrp() 373 self.simulator.go(WAIT_TIME) 374 375 services = leader.get_services() 376 self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNICAST)) 377 self.verify_unicast_services(services) 378 379 for node in routers: 380 node.netdata_unpublish_dnssrp() 381 382 #--------------------------------------------------------------------------------- 383 # DNS/SRP entries: Verify publisher preference when removing 384 # entries. 385 # 386 # Publish DNS/SRP anycast on 8 nodes: leader, router1, 387 # router2, and all 5 end-devices. Afterwards, manually add 388 # the same service entry in Network Data on router3, router4, 389 # and router5 and at each step check that entry from one of 390 # the end-devices is removed (publisher prefers 391 # entries from routers over the ones from end-devices). 392 393 num = 0 394 test_routers = [leader, router1, router2] 395 for node in test_routers + end_devs: 396 node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) 397 self.simulator.go(WAIT_TIME) 398 num += 1 399 services = leader.get_services() 400 self.assertEqual(len(services), num) 401 self.verify_anycast_services(services) 402 403 self.assertEqual(num, DESIRED_NUM_DNSSRP_ANYCAST) 404 405 service_data = '%02x%02x' % (ANYCAST_SERVICE_NUM, int(ANYCAST_SEQ_NUM)) 406 for node in [router3, router4, router5]: 407 node.add_service(str(THREAD_ENTERPRISE_NUMBER), service_data, '00') 408 node.register_netdata() 409 self.simulator.go(WAIT_TIME) 410 411 services = leader.get_services() 412 self.assertEqual(len(services), num) 413 self.verify_anycast_services(services) 414 415 service_rlocs = [int(service[4], 16) for service in services] 416 test_routers.append(node) 417 418 for router in test_routers: 419 self.assertIn(router.get_addr16(), service_rlocs) 420 421 #--------------------------------------------------------------------------------- 422 # On-mesh prefix 423 424 # Publish the same on-mesh prefix on different nodes (low 425 # preference on end-devices, medium preference on routers, and 426 # high on leader) one by one and then unpublish them one by one. 427 # Verify that at each step the entries in the network data are 428 # correct. Particularly verify that that higher preference 429 # entries replace lower preference ones even when there are 430 # already desired number in network data. 431 432 num_low = 0 433 num_med = 0 434 num_high = 0 435 436 for node in end_devs: 437 node.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'low') 438 self.simulator.go(WAIT_TIME) 439 num_low += 1 440 prefixes = leader.get_prefixes() 441 self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) 442 443 # Now add the entry as 'med' on routers and check that we see those in the list. 444 for node in routers: 445 node.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'med') 446 self.simulator.go(WAIT_TIME) 447 num_med += 1 448 prefixes = leader.get_prefixes() 449 self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) 450 451 leader.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'high') 452 self.simulator.go(WAIT_TIME) 453 num_high += 1 454 prefixes = leader.get_prefixes() 455 self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) 456 457 for node in routers: 458 node.netdata_unpublish_prefix(ON_MESH_PREFIX) 459 self.simulator.go(WAIT_TIME) 460 num_med -= 1 461 prefixes = leader.get_prefixes() 462 self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) 463 464 leader.netdata_unpublish_prefix(ON_MESH_PREFIX) 465 self.simulator.go(WAIT_TIME) 466 num_high -= 1 467 prefixes = leader.get_prefixes() 468 self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) 469 470 for node in end_devs: 471 node.netdata_unpublish_prefix(ON_MESH_PREFIX) 472 self.simulator.go(WAIT_TIME) 473 num_low -= 1 474 prefixes = leader.get_prefixes() 475 self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) 476 477 #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 478 # Verify that when removing extra entries, non-preferred entries 479 # are removed first over preferred ones. Entries from routers are 480 # preferred over similar entries from end-devices. 481 482 # Publish prefix entry on `end_dev1` and verify that it is added. 483 484 end_dev1.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'med') 485 self.simulator.go(WAIT_TIME) 486 prefixes = leader.get_prefixes() 487 self.check_num_of_prefixes(prefixes, 0, 1, 0) 488 489 # Publish same prefix on all routers (again as `med` preference). 490 # Verify that we reach the desired number of prefix entries in network 491 # data and that the entry from `end_dev1` is present in network data. 492 493 for node in routers: 494 node.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'med') 495 self.simulator.go(WAIT_TIME) 496 prefixes = leader.get_prefixes() 497 self.check_num_of_prefixes(prefixes, 0, 1 + len(routers), 0) 498 self.assertTrue(1 + len(routers) >= DESIRED_NUM_ON_MESH_PREFIX) 499 # `prefixes` is a list of format 'fd00:1234:0:0::/64 paos low a802' 500 rlocs = [int(prefix.split(' ')[3], 16) for prefix in prefixes] 501 self.assertTrue(rlocs.count(end_dev1.get_addr16()) == 1) 502 503 # Publish same prefix now with `high` preference on leader. 504 # Since it is `high` preference, it is added to network data 505 # which leads to total number of entries to go above the desired 506 # number temporarily and trigger other nodes to try to remove 507 # their entry. The entries from routers should be preferred over 508 # the one from `end_dev1` so that is the one we expect to be 509 # removed. We check that this is the case (i.e., the entry from 510 # `end_dev1` is no longer present in network data). 511 512 leader.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'high') 513 self.simulator.go(WAIT_TIME) 514 prefixes = leader.get_prefixes() 515 self.check_num_of_prefixes(prefixes, 0, 1 + len(routers), 1) 516 rlocs = [int(prefix.split(' ')[3], 16) for prefix in prefixes] 517 self.assertTrue(rlocs.count(end_dev1.get_addr16()) == 0) 518 519 #--------------------------------------------------------------------------------- 520 # External route 521 522 # Publish same external route on all nodes with low preference. 523 524 num = 0 525 for node in nodes: 526 node.netdata_publish_route(EXTERNAL_ROUTE, EXTERNAL_FLAGS, 'low') 527 self.simulator.go(WAIT_TIME) 528 num += 1 529 routes = leader.get_routes() 530 self.check_num_of_routes(routes, num, 0, 0) 531 532 # Change the preference level of the existing entry on leader to high. 533 534 leader.netdata_publish_route(EXTERNAL_ROUTE, EXTERNAL_FLAGS, 'high') 535 self.simulator.go(WAIT_TIME) 536 routes = leader.get_routes() 537 self.check_num_of_routes(routes, num - 1, 0, 1) 538 539 # Replace the published route on leader with '::/0'. 540 leader.netdata_publish_replace(EXTERNAL_ROUTE, '::/0', EXTERNAL_FLAGS, 'med') 541 self.simulator.go(1) 542 routes = leader.get_routes() 543 self.assertEqual([route.split(' ')[0] == '::/0' for route in routes].count(True), 1) 544 545 # Replace it back to the original route. 546 leader.netdata_publish_replace('::/0', EXTERNAL_ROUTE, EXTERNAL_FLAGS, 'high') 547 self.simulator.go(WAIT_TIME) 548 routes = leader.get_routes() 549 self.assertEqual([route.split(' ')[0] == '::/0' for route in routes].count(True), 0) 550 self.check_num_of_routes(routes, num - 1, 0, 1) 551 552 # Publish the same prefix on leader as an on-mesh prefix. Make 553 # sure it is removed from external routes and now seen in the 554 # prefix list. 555 556 leader.netdata_publish_prefix(EXTERNAL_ROUTE, ON_MESH_FLAGS, 'low') 557 self.simulator.go(WAIT_TIME) 558 routes = leader.get_routes() 559 self.check_num_of_routes(routes, num - 1, 0, 0) 560 561 prefixes = leader.get_prefixes() 562 print(prefixes) 563 self.assertIn(EXTERNAL_ROUTE, [prefix.split()[0] for prefix in prefixes]) 564 565 566if __name__ == '__main__': 567 unittest.main() 568