xref: /aosp_15_r20/external/openthread/tests/scripts/thread-cert/test_dnssd.py (revision cfb92d1480a9e65faed56933e9c12405f45898b4)
1*cfb92d14SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*cfb92d14SAndroid Build Coastguard Worker#
3*cfb92d14SAndroid Build Coastguard Worker#  Copyright (c) 2021, The OpenThread Authors.
4*cfb92d14SAndroid Build Coastguard Worker#  All rights reserved.
5*cfb92d14SAndroid Build Coastguard Worker#
6*cfb92d14SAndroid Build Coastguard Worker#  Redistribution and use in source and binary forms, with or without
7*cfb92d14SAndroid Build Coastguard Worker#  modification, are permitted provided that the following conditions are met:
8*cfb92d14SAndroid Build Coastguard Worker#  1. Redistributions of source code must retain the above copyright
9*cfb92d14SAndroid Build Coastguard Worker#     notice, this list of conditions and the following disclaimer.
10*cfb92d14SAndroid Build Coastguard Worker#  2. Redistributions in binary form must reproduce the above copyright
11*cfb92d14SAndroid Build Coastguard Worker#     notice, this list of conditions and the following disclaimer in the
12*cfb92d14SAndroid Build Coastguard Worker#     documentation and/or other materials provided with the distribution.
13*cfb92d14SAndroid Build Coastguard Worker#  3. Neither the name of the copyright holder nor the
14*cfb92d14SAndroid Build Coastguard Worker#     names of its contributors may be used to endorse or promote products
15*cfb92d14SAndroid Build Coastguard Worker#     derived from this software without specific prior written permission.
16*cfb92d14SAndroid Build Coastguard Worker#
17*cfb92d14SAndroid Build Coastguard Worker#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18*cfb92d14SAndroid Build Coastguard Worker#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19*cfb92d14SAndroid Build Coastguard Worker#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20*cfb92d14SAndroid Build Coastguard Worker#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21*cfb92d14SAndroid Build Coastguard Worker#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22*cfb92d14SAndroid Build Coastguard Worker#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23*cfb92d14SAndroid Build Coastguard Worker#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24*cfb92d14SAndroid Build Coastguard Worker#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25*cfb92d14SAndroid Build Coastguard Worker#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26*cfb92d14SAndroid Build Coastguard Worker#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27*cfb92d14SAndroid Build Coastguard Worker#  POSSIBILITY OF SUCH DAMAGE.
28*cfb92d14SAndroid Build Coastguard Worker#
29*cfb92d14SAndroid Build Coastguard Workerimport ipaddress
30*cfb92d14SAndroid Build Coastguard Workerimport typing
31*cfb92d14SAndroid Build Coastguard Workerimport unittest
32*cfb92d14SAndroid Build Coastguard Worker
33*cfb92d14SAndroid Build Coastguard Workerimport config
34*cfb92d14SAndroid Build Coastguard Workerimport thread_cert
35*cfb92d14SAndroid Build Coastguard Worker
36*cfb92d14SAndroid Build Coastguard Worker# Test description:
37*cfb92d14SAndroid Build Coastguard Worker#
38*cfb92d14SAndroid Build Coastguard Worker#   This test verifies DNS-SD server and DNS client behavior
39*cfb92d14SAndroid Build Coastguard Worker#   (browsing for services and/or subtype services, resolving an
40*cfb92d14SAndroid Build Coastguard Worker#   address, or resolving a service). It also indirectly covers the SRP
41*cfb92d14SAndroid Build Coastguard Worker#   client and server behavior and the interactions of DNS-SD server
42*cfb92d14SAndroid Build Coastguard Worker#   with SRP server).
43*cfb92d14SAndroid Build Coastguard Worker#
44*cfb92d14SAndroid Build Coastguard Worker# Topology:
45*cfb92d14SAndroid Build Coastguard Worker#    Four nodes, leader acting as SRP and DNS-SD servers, with 3 router
46*cfb92d14SAndroid Build Coastguard Worker#    nodes acting as SRP and DNS clients.
47*cfb92d14SAndroid Build Coastguard Worker#
48*cfb92d14SAndroid Build Coastguard Worker
49*cfb92d14SAndroid Build Coastguard WorkerSERVER = 1
50*cfb92d14SAndroid Build Coastguard WorkerCLIENT1 = 2
51*cfb92d14SAndroid Build Coastguard WorkerCLIENT2 = 3
52*cfb92d14SAndroid Build Coastguard WorkerCLIENT3 = 4
53*cfb92d14SAndroid Build Coastguard Worker
54*cfb92d14SAndroid Build Coastguard WorkerDOMAIN = 'default.service.arpa.'
55*cfb92d14SAndroid Build Coastguard WorkerSERVICE = '_ipps._tcp'
56*cfb92d14SAndroid Build Coastguard Worker
57*cfb92d14SAndroid Build Coastguard Worker
58*cfb92d14SAndroid Build Coastguard Workerclass TestDnssd(thread_cert.TestCase):
59*cfb92d14SAndroid Build Coastguard Worker    SUPPORT_NCP = False
60*cfb92d14SAndroid Build Coastguard Worker    USE_MESSAGE_FACTORY = False
61*cfb92d14SAndroid Build Coastguard Worker
62*cfb92d14SAndroid Build Coastguard Worker    TOPOLOGY = {
63*cfb92d14SAndroid Build Coastguard Worker        SERVER: {
64*cfb92d14SAndroid Build Coastguard Worker            'mode': 'rdn',
65*cfb92d14SAndroid Build Coastguard Worker        },
66*cfb92d14SAndroid Build Coastguard Worker        CLIENT1: {
67*cfb92d14SAndroid Build Coastguard Worker            'mode': 'rdn',
68*cfb92d14SAndroid Build Coastguard Worker        },
69*cfb92d14SAndroid Build Coastguard Worker        CLIENT2: {
70*cfb92d14SAndroid Build Coastguard Worker            'mode': 'rdn',
71*cfb92d14SAndroid Build Coastguard Worker        },
72*cfb92d14SAndroid Build Coastguard Worker        CLIENT3: {
73*cfb92d14SAndroid Build Coastguard Worker            'mode': 'rdn',
74*cfb92d14SAndroid Build Coastguard Worker        }
75*cfb92d14SAndroid Build Coastguard Worker    }
76*cfb92d14SAndroid Build Coastguard Worker
77*cfb92d14SAndroid Build Coastguard Worker    def test(self):
78*cfb92d14SAndroid Build Coastguard Worker        server = self.nodes[SERVER]
79*cfb92d14SAndroid Build Coastguard Worker        client1 = self.nodes[CLIENT1]
80*cfb92d14SAndroid Build Coastguard Worker        client2 = self.nodes[CLIENT2]
81*cfb92d14SAndroid Build Coastguard Worker        client3 = self.nodes[CLIENT3]
82*cfb92d14SAndroid Build Coastguard Worker
83*cfb92d14SAndroid Build Coastguard Worker        #---------------------------------------------------------------
84*cfb92d14SAndroid Build Coastguard Worker        # Start the server & client devices.
85*cfb92d14SAndroid Build Coastguard Worker
86*cfb92d14SAndroid Build Coastguard Worker        server.start()
87*cfb92d14SAndroid Build Coastguard Worker        self.simulator.go(config.LEADER_STARTUP_DELAY)
88*cfb92d14SAndroid Build Coastguard Worker        self.assertEqual(server.get_state(), 'leader')
89*cfb92d14SAndroid Build Coastguard Worker        server.srp_server_set_enabled(True)
90*cfb92d14SAndroid Build Coastguard Worker
91*cfb92d14SAndroid Build Coastguard Worker        client1.start()
92*cfb92d14SAndroid Build Coastguard Worker        client2.start()
93*cfb92d14SAndroid Build Coastguard Worker        client3.start()
94*cfb92d14SAndroid Build Coastguard Worker        self.simulator.go(config.ROUTER_STARTUP_DELAY)
95*cfb92d14SAndroid Build Coastguard Worker        self.assertEqual(client1.get_state(), 'router')
96*cfb92d14SAndroid Build Coastguard Worker        self.assertEqual(client2.get_state(), 'router')
97*cfb92d14SAndroid Build Coastguard Worker        self.assertEqual(client3.get_state(), 'router')
98*cfb92d14SAndroid Build Coastguard Worker
99*cfb92d14SAndroid Build Coastguard Worker        #---------------------------------------------------------------
100*cfb92d14SAndroid Build Coastguard Worker        # Register services on clients
101*cfb92d14SAndroid Build Coastguard Worker
102*cfb92d14SAndroid Build Coastguard Worker        client1_addrs = [client1.get_mleid(), client1.get_rloc()]
103*cfb92d14SAndroid Build Coastguard Worker        client2_addrs = [client2.get_mleid(), client2.get_rloc()]
104*cfb92d14SAndroid Build Coastguard Worker        client3_addrs = [client3.get_mleid(), client2.get_rloc()]
105*cfb92d14SAndroid Build Coastguard Worker
106*cfb92d14SAndroid Build Coastguard Worker        self._config_srp_client_services(client1, server, 'ins1', 'host1', 11111, 1, 1, client1_addrs, ",_s1,_s2")
107*cfb92d14SAndroid Build Coastguard Worker        self._config_srp_client_services(client2, server, 'ins2', 'HOST2', 22222, 2, 2, client2_addrs)
108*cfb92d14SAndroid Build Coastguard Worker        self._config_srp_client_services(client3, server, 'ins3', 'host3', 33333, 3, 3, client3_addrs, ",_S1")
109*cfb92d14SAndroid Build Coastguard Worker
110*cfb92d14SAndroid Build Coastguard Worker        #---------------------------------------------------------------
111*cfb92d14SAndroid Build Coastguard Worker        # Resolve address (AAAA records)
112*cfb92d14SAndroid Build Coastguard Worker
113*cfb92d14SAndroid Build Coastguard Worker        answers = client1.dns_resolve(f"host1.{DOMAIN}".upper(), server.get_mleid(), 53)
114*cfb92d14SAndroid Build Coastguard Worker        self.assertEqual(set(ipaddress.IPv6Address(ip) for ip, _ in answers),
115*cfb92d14SAndroid Build Coastguard Worker                         set(map(ipaddress.IPv6Address, client1_addrs)))
116*cfb92d14SAndroid Build Coastguard Worker
117*cfb92d14SAndroid Build Coastguard Worker        answers = client1.dns_resolve(f"host2.{DOMAIN}", server.get_mleid(), 53)
118*cfb92d14SAndroid Build Coastguard Worker        self.assertEqual(set(ipaddress.IPv6Address(ip) for ip, _ in answers),
119*cfb92d14SAndroid Build Coastguard Worker                         set(map(ipaddress.IPv6Address, client2_addrs)))
120*cfb92d14SAndroid Build Coastguard Worker
121*cfb92d14SAndroid Build Coastguard Worker        #---------------------------------------------------------------
122*cfb92d14SAndroid Build Coastguard Worker        # Browsing for services
123*cfb92d14SAndroid Build Coastguard Worker
124*cfb92d14SAndroid Build Coastguard Worker        instance1_verify_info = {
125*cfb92d14SAndroid Build Coastguard Worker            'port': 11111,
126*cfb92d14SAndroid Build Coastguard Worker            'priority': 1,
127*cfb92d14SAndroid Build Coastguard Worker            'weight': 1,
128*cfb92d14SAndroid Build Coastguard Worker            'host': 'host1.default.service.arpa.',
129*cfb92d14SAndroid Build Coastguard Worker            'address': client1_addrs,
130*cfb92d14SAndroid Build Coastguard Worker            'txt_data': '',
131*cfb92d14SAndroid Build Coastguard Worker            'srv_ttl': lambda x: x > 0,
132*cfb92d14SAndroid Build Coastguard Worker            'txt_ttl': lambda x: x > 0,
133*cfb92d14SAndroid Build Coastguard Worker            'aaaa_ttl': lambda x: x > 0,
134*cfb92d14SAndroid Build Coastguard Worker        }
135*cfb92d14SAndroid Build Coastguard Worker
136*cfb92d14SAndroid Build Coastguard Worker        instance2_verify_info = {
137*cfb92d14SAndroid Build Coastguard Worker            'port': 22222,
138*cfb92d14SAndroid Build Coastguard Worker            'priority': 2,
139*cfb92d14SAndroid Build Coastguard Worker            'weight': 2,
140*cfb92d14SAndroid Build Coastguard Worker            'host': 'host2.default.service.arpa.',
141*cfb92d14SAndroid Build Coastguard Worker            'address': client2_addrs,
142*cfb92d14SAndroid Build Coastguard Worker            'txt_data': '',
143*cfb92d14SAndroid Build Coastguard Worker            'srv_ttl': lambda x: x > 0,
144*cfb92d14SAndroid Build Coastguard Worker            'txt_ttl': lambda x: x > 0,
145*cfb92d14SAndroid Build Coastguard Worker            'aaaa_ttl': lambda x: x > 0,
146*cfb92d14SAndroid Build Coastguard Worker        }
147*cfb92d14SAndroid Build Coastguard Worker
148*cfb92d14SAndroid Build Coastguard Worker        instance3_verify_info = {
149*cfb92d14SAndroid Build Coastguard Worker            'port': 33333,
150*cfb92d14SAndroid Build Coastguard Worker            'priority': 3,
151*cfb92d14SAndroid Build Coastguard Worker            'weight': 3,
152*cfb92d14SAndroid Build Coastguard Worker            'host': 'host3.default.service.arpa.',
153*cfb92d14SAndroid Build Coastguard Worker            'address': client3_addrs,
154*cfb92d14SAndroid Build Coastguard Worker            'txt_data': '',
155*cfb92d14SAndroid Build Coastguard Worker            'srv_ttl': lambda x: x > 0,
156*cfb92d14SAndroid Build Coastguard Worker            'txt_ttl': lambda x: x > 0,
157*cfb92d14SAndroid Build Coastguard Worker            'aaaa_ttl': lambda x: x > 0,
158*cfb92d14SAndroid Build Coastguard Worker        }
159*cfb92d14SAndroid Build Coastguard Worker
160*cfb92d14SAndroid Build Coastguard Worker        instance4_verify_info = {
161*cfb92d14SAndroid Build Coastguard Worker            'port': 44444,
162*cfb92d14SAndroid Build Coastguard Worker            'priority': 4,
163*cfb92d14SAndroid Build Coastguard Worker            'weight': 4,
164*cfb92d14SAndroid Build Coastguard Worker            'host': 'host3.default.service.arpa.',
165*cfb92d14SAndroid Build Coastguard Worker            'address': client3_addrs,
166*cfb92d14SAndroid Build Coastguard Worker            'txt_data': 'KEY=414243',  # KEY=ABC
167*cfb92d14SAndroid Build Coastguard Worker            'srv_ttl': lambda x: x > 0,
168*cfb92d14SAndroid Build Coastguard Worker            'txt_ttl': lambda x: x > 0,
169*cfb92d14SAndroid Build Coastguard Worker            'aaaa_ttl': lambda x: x > 0,
170*cfb92d14SAndroid Build Coastguard Worker        }
171*cfb92d14SAndroid Build Coastguard Worker
172*cfb92d14SAndroid Build Coastguard Worker        # Browse for main service
173*cfb92d14SAndroid Build Coastguard Worker        service_instances = client1.dns_browse(f'{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53)
174*cfb92d14SAndroid Build Coastguard Worker        self.assertEqual({'ins1', 'ins2', 'ins3'}, set(service_instances.keys()))
175*cfb92d14SAndroid Build Coastguard Worker
176*cfb92d14SAndroid Build Coastguard Worker        # Browse for service sub-type _s1.
177*cfb92d14SAndroid Build Coastguard Worker        service_instances = client1.dns_browse(f'_s1._sub.{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53)
178*cfb92d14SAndroid Build Coastguard Worker        self.assertEqual({'ins1', 'ins3'}, set(service_instances.keys()))
179*cfb92d14SAndroid Build Coastguard Worker
180*cfb92d14SAndroid Build Coastguard Worker        # Browse for service sub-type _s2.
181*cfb92d14SAndroid Build Coastguard Worker        # Since there is only one matching instance, validate that
182*cfb92d14SAndroid Build Coastguard Worker        # server included the service info in additional section.
183*cfb92d14SAndroid Build Coastguard Worker        service_instances = client1.dns_browse(f'_s2._sub.{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53)
184*cfb92d14SAndroid Build Coastguard Worker        self.assertEqual({'ins1'}, set(service_instances.keys()))
185*cfb92d14SAndroid Build Coastguard Worker        self._assert_service_instance_equal(service_instances['ins1'], instance1_verify_info)
186*cfb92d14SAndroid Build Coastguard Worker
187*cfb92d14SAndroid Build Coastguard Worker        #---------------------------------------------------------------
188*cfb92d14SAndroid Build Coastguard Worker        # Resolve service
189*cfb92d14SAndroid Build Coastguard Worker
190*cfb92d14SAndroid Build Coastguard Worker        service_instance = client1.dns_resolve_service('ins1', f'{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53)
191*cfb92d14SAndroid Build Coastguard Worker        self._assert_service_instance_equal(service_instance, instance1_verify_info)
192*cfb92d14SAndroid Build Coastguard Worker
193*cfb92d14SAndroid Build Coastguard Worker        service_instance = client1.dns_resolve_service('ins2', f'{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53)
194*cfb92d14SAndroid Build Coastguard Worker        self._assert_service_instance_equal(service_instance, instance2_verify_info)
195*cfb92d14SAndroid Build Coastguard Worker
196*cfb92d14SAndroid Build Coastguard Worker        service_instance = client1.dns_resolve_service('ins3', f'{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53)
197*cfb92d14SAndroid Build Coastguard Worker        self._assert_service_instance_equal(service_instance, instance3_verify_info)
198*cfb92d14SAndroid Build Coastguard Worker
199*cfb92d14SAndroid Build Coastguard Worker        #---------------------------------------------------------------
200*cfb92d14SAndroid Build Coastguard Worker        # Add another service with TXT entries to the existing host and
201*cfb92d14SAndroid Build Coastguard Worker        # verify that it is properly merged.
202*cfb92d14SAndroid Build Coastguard Worker
203*cfb92d14SAndroid Build Coastguard Worker        client3.srp_client_add_service('ins4', (SERVICE + ",_s1").upper(), 44444, 4, 4, txt_entries=['KEY=ABC'])
204*cfb92d14SAndroid Build Coastguard Worker        self.simulator.go(5)
205*cfb92d14SAndroid Build Coastguard Worker
206*cfb92d14SAndroid Build Coastguard Worker        service_instances = client1.dns_browse(f'{SERVICE}.{DOMAIN}', server.get_mleid(), 53)
207*cfb92d14SAndroid Build Coastguard Worker        self.assertEqual({'ins1', 'ins2', 'ins3', 'ins4'}, set(service_instances.keys()))
208*cfb92d14SAndroid Build Coastguard Worker
209*cfb92d14SAndroid Build Coastguard Worker        service_instance = client1.dns_resolve_service('ins4', f'{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53)
210*cfb92d14SAndroid Build Coastguard Worker        self._assert_service_instance_equal(service_instance, instance4_verify_info)
211*cfb92d14SAndroid Build Coastguard Worker
212*cfb92d14SAndroid Build Coastguard Worker    def _assert_service_instance_equal(self, instance, info):
213*cfb92d14SAndroid Build Coastguard Worker        self.assertEqual(instance['host'].lower(), info['host'].lower(), instance)
214*cfb92d14SAndroid Build Coastguard Worker        for f in ('port', 'priority', 'weight', 'txt_data'):
215*cfb92d14SAndroid Build Coastguard Worker            self.assertEqual(instance[f], info[f], instance)
216*cfb92d14SAndroid Build Coastguard Worker
217*cfb92d14SAndroid Build Coastguard Worker        verify_addresses = info['address']
218*cfb92d14SAndroid Build Coastguard Worker        if not isinstance(verify_addresses, typing.Collection):
219*cfb92d14SAndroid Build Coastguard Worker            verify_addresses = [verify_addresses]
220*cfb92d14SAndroid Build Coastguard Worker        self.assertIn(ipaddress.IPv6Address(instance['address']), map(ipaddress.IPv6Address, verify_addresses),
221*cfb92d14SAndroid Build Coastguard Worker                      instance)
222*cfb92d14SAndroid Build Coastguard Worker
223*cfb92d14SAndroid Build Coastguard Worker        for ttl_f in ('srv_ttl', 'txt_ttl', 'aaaa_ttl'):
224*cfb92d14SAndroid Build Coastguard Worker            check_ttl = info[ttl_f]
225*cfb92d14SAndroid Build Coastguard Worker            if not callable(check_ttl):
226*cfb92d14SAndroid Build Coastguard Worker                check_ttl = lambda x: x == check_ttl
227*cfb92d14SAndroid Build Coastguard Worker
228*cfb92d14SAndroid Build Coastguard Worker            self.assertTrue(check_ttl(instance[ttl_f]), instance)
229*cfb92d14SAndroid Build Coastguard Worker
230*cfb92d14SAndroid Build Coastguard Worker    def _config_srp_client_services(self,
231*cfb92d14SAndroid Build Coastguard Worker                                    client,
232*cfb92d14SAndroid Build Coastguard Worker                                    server,
233*cfb92d14SAndroid Build Coastguard Worker                                    instancename,
234*cfb92d14SAndroid Build Coastguard Worker                                    hostname,
235*cfb92d14SAndroid Build Coastguard Worker                                    port,
236*cfb92d14SAndroid Build Coastguard Worker                                    priority,
237*cfb92d14SAndroid Build Coastguard Worker                                    weight,
238*cfb92d14SAndroid Build Coastguard Worker                                    addrs,
239*cfb92d14SAndroid Build Coastguard Worker                                    subtypes=''):
240*cfb92d14SAndroid Build Coastguard Worker        client.srp_client_set_host_name(hostname)
241*cfb92d14SAndroid Build Coastguard Worker        client.srp_client_set_host_address(*addrs)
242*cfb92d14SAndroid Build Coastguard Worker        client.srp_client_add_service(instancename, SERVICE + subtypes, port, priority, weight)
243*cfb92d14SAndroid Build Coastguard Worker
244*cfb92d14SAndroid Build Coastguard Worker        self.simulator.go(5)
245*cfb92d14SAndroid Build Coastguard Worker        self.assertEqual(client.srp_client_get_host_state(), 'Registered')
246*cfb92d14SAndroid Build Coastguard Worker
247*cfb92d14SAndroid Build Coastguard Worker
248*cfb92d14SAndroid Build Coastguard Workerif __name__ == '__main__':
249*cfb92d14SAndroid Build Coastguard Worker    unittest.main()
250