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# 29import ipaddress 30import typing 31import unittest 32 33import config 34import thread_cert 35 36# Test description: 37# 38# This test verifies DNS-SD server and DNS client behavior 39# (browsing for services and/or subtype services, resolving an 40# address, or resolving a service). It also indirectly covers the SRP 41# client and server behavior and the interactions of DNS-SD server 42# with SRP server). 43# 44# Topology: 45# Four nodes, leader acting as SRP and DNS-SD servers, with 3 router 46# nodes acting as SRP and DNS clients. 47# 48 49SERVER = 1 50CLIENT1 = 2 51CLIENT2 = 3 52CLIENT3 = 4 53 54DOMAIN = 'default.service.arpa.' 55SERVICE = '_ipps._tcp' 56 57 58class TestDnssd(thread_cert.TestCase): 59 SUPPORT_NCP = False 60 USE_MESSAGE_FACTORY = False 61 62 TOPOLOGY = { 63 SERVER: { 64 'mode': 'rdn', 65 }, 66 CLIENT1: { 67 'mode': 'rdn', 68 }, 69 CLIENT2: { 70 'mode': 'rdn', 71 }, 72 CLIENT3: { 73 'mode': 'rdn', 74 } 75 } 76 77 def test(self): 78 server = self.nodes[SERVER] 79 client1 = self.nodes[CLIENT1] 80 client2 = self.nodes[CLIENT2] 81 client3 = self.nodes[CLIENT3] 82 83 #--------------------------------------------------------------- 84 # Start the server & client devices. 85 86 server.start() 87 self.simulator.go(config.LEADER_STARTUP_DELAY) 88 self.assertEqual(server.get_state(), 'leader') 89 server.srp_server_set_enabled(True) 90 91 client1.start() 92 client2.start() 93 client3.start() 94 self.simulator.go(config.ROUTER_STARTUP_DELAY) 95 self.assertEqual(client1.get_state(), 'router') 96 self.assertEqual(client2.get_state(), 'router') 97 self.assertEqual(client3.get_state(), 'router') 98 99 #--------------------------------------------------------------- 100 # Register services on clients 101 102 client1_addrs = [client1.get_mleid(), client1.get_rloc()] 103 client2_addrs = [client2.get_mleid(), client2.get_rloc()] 104 client3_addrs = [client3.get_mleid(), client2.get_rloc()] 105 106 self._config_srp_client_services(client1, server, 'ins1', 'host1', 11111, 1, 1, client1_addrs, ",_s1,_s2") 107 self._config_srp_client_services(client2, server, 'ins2', 'HOST2', 22222, 2, 2, client2_addrs) 108 self._config_srp_client_services(client3, server, 'ins3', 'host3', 33333, 3, 3, client3_addrs, ",_S1") 109 110 #--------------------------------------------------------------- 111 # Resolve address (AAAA records) 112 113 answers = client1.dns_resolve(f"host1.{DOMAIN}".upper(), server.get_mleid(), 53) 114 self.assertEqual(set(ipaddress.IPv6Address(ip) for ip, _ in answers), 115 set(map(ipaddress.IPv6Address, client1_addrs))) 116 117 answers = client1.dns_resolve(f"host2.{DOMAIN}", server.get_mleid(), 53) 118 self.assertEqual(set(ipaddress.IPv6Address(ip) for ip, _ in answers), 119 set(map(ipaddress.IPv6Address, client2_addrs))) 120 121 #--------------------------------------------------------------- 122 # Browsing for services 123 124 instance1_verify_info = { 125 'port': 11111, 126 'priority': 1, 127 'weight': 1, 128 'host': 'host1.default.service.arpa.', 129 'address': client1_addrs, 130 'txt_data': '', 131 'srv_ttl': lambda x: x > 0, 132 'txt_ttl': lambda x: x > 0, 133 'aaaa_ttl': lambda x: x > 0, 134 } 135 136 instance2_verify_info = { 137 'port': 22222, 138 'priority': 2, 139 'weight': 2, 140 'host': 'host2.default.service.arpa.', 141 'address': client2_addrs, 142 'txt_data': '', 143 'srv_ttl': lambda x: x > 0, 144 'txt_ttl': lambda x: x > 0, 145 'aaaa_ttl': lambda x: x > 0, 146 } 147 148 instance3_verify_info = { 149 'port': 33333, 150 'priority': 3, 151 'weight': 3, 152 'host': 'host3.default.service.arpa.', 153 'address': client3_addrs, 154 'txt_data': '', 155 'srv_ttl': lambda x: x > 0, 156 'txt_ttl': lambda x: x > 0, 157 'aaaa_ttl': lambda x: x > 0, 158 } 159 160 instance4_verify_info = { 161 'port': 44444, 162 'priority': 4, 163 'weight': 4, 164 'host': 'host3.default.service.arpa.', 165 'address': client3_addrs, 166 'txt_data': 'KEY=414243', # KEY=ABC 167 'srv_ttl': lambda x: x > 0, 168 'txt_ttl': lambda x: x > 0, 169 'aaaa_ttl': lambda x: x > 0, 170 } 171 172 # Browse for main service 173 service_instances = client1.dns_browse(f'{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53) 174 self.assertEqual({'ins1', 'ins2', 'ins3'}, set(service_instances.keys())) 175 176 # Browse for service sub-type _s1. 177 service_instances = client1.dns_browse(f'_s1._sub.{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53) 178 self.assertEqual({'ins1', 'ins3'}, set(service_instances.keys())) 179 180 # Browse for service sub-type _s2. 181 # Since there is only one matching instance, validate that 182 # server included the service info in additional section. 183 service_instances = client1.dns_browse(f'_s2._sub.{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53) 184 self.assertEqual({'ins1'}, set(service_instances.keys())) 185 self._assert_service_instance_equal(service_instances['ins1'], instance1_verify_info) 186 187 #--------------------------------------------------------------- 188 # Resolve service 189 190 service_instance = client1.dns_resolve_service('ins1', f'{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53) 191 self._assert_service_instance_equal(service_instance, instance1_verify_info) 192 193 service_instance = client1.dns_resolve_service('ins2', f'{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53) 194 self._assert_service_instance_equal(service_instance, instance2_verify_info) 195 196 service_instance = client1.dns_resolve_service('ins3', f'{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53) 197 self._assert_service_instance_equal(service_instance, instance3_verify_info) 198 199 #--------------------------------------------------------------- 200 # Add another service with TXT entries to the existing host and 201 # verify that it is properly merged. 202 203 client3.srp_client_add_service('ins4', (SERVICE + ",_s1").upper(), 44444, 4, 4, txt_entries=['KEY=ABC']) 204 self.simulator.go(5) 205 206 service_instances = client1.dns_browse(f'{SERVICE}.{DOMAIN}', server.get_mleid(), 53) 207 self.assertEqual({'ins1', 'ins2', 'ins3', 'ins4'}, set(service_instances.keys())) 208 209 service_instance = client1.dns_resolve_service('ins4', f'{SERVICE}.{DOMAIN}'.upper(), server.get_mleid(), 53) 210 self._assert_service_instance_equal(service_instance, instance4_verify_info) 211 212 def _assert_service_instance_equal(self, instance, info): 213 self.assertEqual(instance['host'].lower(), info['host'].lower(), instance) 214 for f in ('port', 'priority', 'weight', 'txt_data'): 215 self.assertEqual(instance[f], info[f], instance) 216 217 verify_addresses = info['address'] 218 if not isinstance(verify_addresses, typing.Collection): 219 verify_addresses = [verify_addresses] 220 self.assertIn(ipaddress.IPv6Address(instance['address']), map(ipaddress.IPv6Address, verify_addresses), 221 instance) 222 223 for ttl_f in ('srv_ttl', 'txt_ttl', 'aaaa_ttl'): 224 check_ttl = info[ttl_f] 225 if not callable(check_ttl): 226 check_ttl = lambda x: x == check_ttl 227 228 self.assertTrue(check_ttl(instance[ttl_f]), instance) 229 230 def _config_srp_client_services(self, 231 client, 232 server, 233 instancename, 234 hostname, 235 port, 236 priority, 237 weight, 238 addrs, 239 subtypes=''): 240 client.netdata_show() 241 srp_server_port = client.get_srp_server_port() 242 243 client.srp_client_start(server.get_mleid(), srp_server_port) 244 client.srp_client_set_host_name(hostname) 245 client.srp_client_set_host_address(*addrs) 246 client.srp_client_add_service(instancename, SERVICE + subtypes, port, priority, weight) 247 248 self.simulator.go(5) 249 self.assertEqual(client.srp_client_get_host_state(), 'Registered') 250 251 252if __name__ == '__main__': 253 unittest.main() 254