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