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