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 unittest 31 32import config 33import thread_cert 34 35# Test description: 36# This test verifies the basic functionality of advertising proxy. 37# 38# Topology: 39# ----------------(eth)-------------------- 40# | | 41# BR (Leader) HOST (mDNS Browser) 42# | 43# ROUTER 44# 45 46BR = 1 47ROUTER = 2 48HOST = 3 49LEASE = 10 # Seconds 50KEY_LEASE = 20 # Seconds 51 52 53class SingleHostAndService(thread_cert.TestCase): 54 USE_MESSAGE_FACTORY = False 55 56 TOPOLOGY = { 57 BR: { 58 'name': 'BR_1', 59 'allowlist': [ROUTER], 60 'is_otbr': True, 61 'version': '1.2', 62 }, 63 ROUTER: { 64 'name': 'Router_1', 65 'allowlist': [BR], 66 'version': '1.2', 67 }, 68 HOST: { 69 'name': 'Host', 70 'is_host': True 71 }, 72 } 73 74 def test(self): 75 host = self.nodes[HOST] 76 server = self.nodes[BR] 77 client = self.nodes[ROUTER] 78 79 server.srp_server_set_enabled(False) 80 host.start(start_radvd=False) 81 self.simulator.go(5) 82 83 # Reserve UDP ports to verify that SRP server can skip the unavailable 84 # ports correctly 85 server.reserve_udp_port(53535) 86 server.reserve_udp_port(53536) 87 server.reserve_udp_port(53537) 88 89 self.assertEqual(server.srp_server_get_state(), 'disabled') 90 server.srp_server_set_enabled(True) 91 server.srp_server_set_lease_range(LEASE, LEASE, KEY_LEASE, KEY_LEASE) 92 server.start() 93 self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY) 94 self.assertEqual('leader', server.get_state()) 95 self.assertEqual(server.srp_server_get_state(), 'running') 96 self.assertNotIn(server.get_srp_server_port(), [53535, 53536, 53537]) 97 98 client.start() 99 self.simulator.go(config.ROUTER_STARTUP_DELAY) 100 self.assertEqual('router', client.get_state()) 101 102 # 103 # 1. Register a single service. 104 # 105 106 client.srp_client_set_host_name('my-host') 107 client.srp_client_set_host_address('2001::1') 108 client.srp_client_add_service('my-service', '_ipps._tcp', 12345) 109 client.srp_client_add_service('my-service-1', '_ipps._tcp', 12345) 110 client.srp_client_enable_auto_start_mode() 111 self.simulator.go(2) 112 113 self.check_host_and_service(server, client, '2001::1', 'my-service') 114 self.check_host_and_service(server, client, '2001::1', 'my-service-1') 115 116 # 117 # 2. Discover the service by the HOST on the ethernet. This makes sure 118 # the Advertising Proxy multicasts the same service on ethernet. 119 # 120 121 self.host_check_mdns_service(host, '2001::1', 'my-service') 122 self.host_check_mdns_service(host, '2001::1', 'my-service-1') 123 124 # 125 # 3. Check if the Advertising Proxy removes the service from ethernet 126 # when the SRP client removes it. 127 # 128 129 client.srp_client_remove_host() 130 self.simulator.go(2) 131 132 self.assertIsNone(host.discover_mdns_service('my-service', '_ipps._tcp', 'my-host')) 133 self.assertIsNone(host.discover_mdns_service('my-service-1', '_ipps._tcp', 'my-host')) 134 135 # 136 # 4. Check if we can discover the mDNS service again when re-registering the 137 # service from the SRP client. 138 # 139 140 client.srp_client_set_host_name('my-host') 141 client.srp_client_set_host_address('2001::1') 142 client.srp_client_add_service('my-service', '_ipps._tcp', 12345) 143 client.srp_client_add_service('my-service-1', '_ipps._tcp', 12345) 144 self.simulator.go(2) 145 146 self.check_host_and_service(server, client, '2001::1', 'my-service') 147 self.check_host_and_service(server, client, '2001::1', 'my-service-1') 148 self.host_check_mdns_service(host, '2001::1', 'my-service') 149 self.host_check_mdns_service(host, '2001::1', 'my-service-1') 150 151 # 152 # 5. Update the SRP host address and make sure the Advertising Proxy 153 # will follow it. 154 # 155 156 client.srp_client_set_host_address('2001::2') 157 self.simulator.go(8) 158 159 self.check_host_and_service(server, client, '2001::2', 'my-service') 160 self.check_host_and_service(server, client, '2001::2', 'my-service-1') 161 self.host_check_mdns_service(host, '2001::2', 'my-service') 162 self.host_check_mdns_service(host, '2001::2', 'my-service-1') 163 164 # 165 # 6. Check if the service is removed by the Advertising Proxy when the SRP server is stopped. 166 # 167 168 server.srp_server_set_enabled(False) 169 self.simulator.go(5) 170 171 self.assertEqual(len(server.srp_server_get_hosts()), 0) 172 self.assertEqual(len(server.srp_server_get_services()), 0) 173 self.assertIsNone(host.discover_mdns_service('my-service', '_ipps._tcp', 'my-host')) 174 self.assertIsNone(host.discover_mdns_service('my-service-1', '_ipps._tcp', 'my-host')) 175 176 server.srp_server_set_enabled(True) 177 self.simulator.go(20) 178 179 self.check_host_and_service(server, client, '2001::2', 'my-service') 180 self.check_host_and_service(server, client, '2001::2', 'my-service-1') 181 self.host_check_mdns_service(host, '2001::2', 'my-service') 182 self.host_check_mdns_service(host, '2001::2', 'my-service-1') 183 184 # 185 # 7. Remove a single service and verify that the remaining one can still 186 # be discovered. 187 # 188 189 client.srp_client_remove_service('my-service-1', '_ipps._tcp') 190 191 self.simulator.go(5) 192 193 self.check_host_and_service(server, client, '2001::2', 'my-service') 194 self.host_check_mdns_service(host, '2001::2', 'my-service') 195 self.assertIsNone(host.discover_mdns_service('my-service-1', '_ipps._tcp', 'my-host')) 196 197 # Wait for KEY expiration of service 'my-service-1'. 198 # FIXME: workaround for https://github.com/openthread/ot-br-posix/issues/1071. 199 self.simulator.go(KEY_LEASE + 5) 200 201 # 202 # 8. Update both the host and the service in a loop and make sure the 203 # Advertising Proxy can follow. 204 # 205 206 for host_address_suffix, service_port in ((1, 12341), (2, 12342), (3, 12343), (2, 12345)): 207 host_address = f'2001::{host_address_suffix}' 208 client.srp_client_clear_service('my-service', '_ipps._tcp') 209 client.srp_client_set_host_address(host_address) 210 client.srp_client_add_service('my-service', '_ipps._tcp', service_port) 211 self.simulator.go(10) 212 213 self.check_host_and_service(server, client, host_address, 'my-service', service_port) 214 self.host_check_mdns_service(host, host_address, 'my-service', service_port) 215 216 # 217 # 9. Check if Advertising Proxy filters out Mesh Local and Link Local host addresses 218 # 219 client.srp_client_remove_host() 220 self.simulator.go(2) 221 client.srp_client_set_host_name('my-host') 222 client.srp_client_set_host_address('2001::1', '2002::2', 223 client.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], 224 client.get_ip6_address(config.ADDRESS_TYPE.LINK_LOCAL), 225 client.get_ip6_address(config.ADDRESS_TYPE.ML_EID)) 226 client.srp_client_add_service('my-service', '_ipps._tcp', 12345) 227 client.srp_client_enable_auto_start_mode() 228 self.simulator.go(10) 229 self.check_host_and_service(server, client, [ 230 '2001::1', '2002::2', 231 client.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], 232 client.get_ip6_address(config.ADDRESS_TYPE.LINK_LOCAL), 233 client.get_ip6_address(config.ADDRESS_TYPE.ML_EID) 234 ], 'my-service', 12345) 235 self.host_check_mdns_service( 236 host, ['2001::1', '2002::2', client.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]], 'my-service', 12345) 237 238 client.srp_client_set_host_address(client.get_ip6_address(config.ADDRESS_TYPE.LINK_LOCAL), 239 client.get_ip6_address(config.ADDRESS_TYPE.ML_EID), client.get_rloc()) 240 self.simulator.go(10) 241 self.check_host_and_service(server, client, [ 242 client.get_ip6_address(config.ADDRESS_TYPE.LINK_LOCAL), 243 client.get_ip6_address(config.ADDRESS_TYPE.ML_EID), 244 client.get_rloc() 245 ], 'my-service', 12345) 246 self.host_check_mdns_service(host, [], 'my-service', 12345) 247 248 client.srp_client_set_host_address('2005::3') 249 self.simulator.go(10) 250 self.check_host_and_service(server, client, '2005::3', 'my-service', 12345) 251 self.host_check_mdns_service(host, '2005::3', 'my-service', 12345) 252 253 # 254 # 10. Check if the expired service is removed by the Advertising Proxy. 255 # 256 client.srp_client_stop() 257 self.simulator.go(LEASE + 2) 258 259 self.assertIsNone(host.discover_mdns_service('my-service', '_ipps._tcp', 'my-host')) 260 self.assertIsNone(host.discover_mdns_service('my-service-1', '_ipps._tcp', 'my-host')) 261 262 def host_check_mdns_service(self, host, host_addrs, service_instance, service_port=12345): 263 if isinstance(host_addrs, str): 264 host_addrs = [host_addrs] 265 service = host.discover_mdns_service(service_instance, '_ipps._tcp', 'my-host') 266 self.assertIsNotNone(service) 267 self.assertEqual(service['instance'], service_instance) 268 self.assertEqual(service['name'], '_ipps._tcp') 269 self.assertEqual(service['port'], service_port) 270 self.assertEqual(service['priority'], 0) 271 self.assertEqual(service['weight'], 0) 272 self.assertEqual(service['host'], 'my-host') 273 self.assertEqual(len(service['addresses']), len(host_addrs)) 274 self.assertEqual(sorted(map(ipaddress.ip_address, service['addresses'])), 275 sorted(map(ipaddress.ip_address, host_addrs))) 276 277 def check_host_and_service(self, server, client, host_addrs, service_instance, service_port=12345): 278 """Check that we have properly registered host and service instance. 279 """ 280 281 if isinstance(host_addrs, str): 282 host_addrs = [host_addrs] 283 client_services = client.srp_client_get_services() 284 print(client_services) 285 client_services = [service for service in client_services if service['instance'] == service_instance] 286 self.assertEqual(len(client_services), 1) 287 client_service = client_services[0] 288 289 # Verify that the client possesses correct service resources. 290 self.assertEqual(client_service['instance'], service_instance) 291 self.assertEqual(client_service['name'], '_ipps._tcp') 292 self.assertEqual(int(client_service['port']), service_port) 293 self.assertEqual(int(client_service['priority']), 0) 294 self.assertEqual(int(client_service['weight']), 0) 295 296 # Verify the client successfully registered its service with 297 # the server. Due to the short lease times (10 seconds) used 298 # in this test, the client will refresh the registered 299 # service quickly. During the test, we accept any of the 300 # following states as indicating successful registration: 301 # `Registered`, `ToRefresh`, or `Refreshing`. 302 303 self.assertIn(client_service['state'], ['Registered', 'ToRefresh', 'Refreshing']) 304 305 server_services = server.srp_server_get_services() 306 print(server_services) 307 server_services = [service for service in server_services if service['instance'] == service_instance] 308 self.assertEqual(len(server_services), 1) 309 server_service = server_services[0] 310 311 # Verify that the server accepted the SRP registration and stores 312 # the same service resources. 313 self.assertEqual(server_service['deleted'], 'false') 314 self.assertEqual(server_service['instance'], client_service['instance']) 315 self.assertEqual(server_service['name'], client_service['name']) 316 self.assertEqual(int(server_service['port']), int(client_service['port'])) 317 self.assertEqual(int(server_service['priority']), int(client_service['priority'])) 318 self.assertEqual(int(server_service['weight']), int(client_service['weight'])) 319 self.assertEqual(server_service['host'], 'my-host') 320 321 server_hosts = server.srp_server_get_hosts() 322 print(server_hosts) 323 self.assertEqual(len(server_hosts), 1) 324 server_host = server_hosts[0] 325 326 self.assertEqual(server_host['deleted'], 'false') 327 self.assertEqual(server_host['fullname'], server_service['host_fullname']) 328 self.assertEqual(sorted(map(ipaddress.ip_address, server_host['addresses'])), 329 sorted(map(ipaddress.ip_address, host_addrs))) 330 331 332class SrpClientRemoveNonExistingHost(thread_cert.TestCase): 333 USE_MESSAGE_FACTORY = False 334 335 TOPOLOGY = { 336 BR: { 337 'name': 'BR', 338 'allowlist': [ROUTER], 339 'is_otbr': True, 340 'version': '1.2', 341 }, 342 ROUTER: { 343 'name': 'Router', 344 'allowlist': [BR], 345 'version': '1.2', 346 } 347 } 348 349 def test(self): 350 server = self.nodes[BR] 351 client = self.nodes[ROUTER] 352 353 server.srp_server_set_enabled(True) 354 server.srp_server_set_lease_range(LEASE, LEASE, KEY_LEASE, KEY_LEASE) 355 server.start() 356 self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY) 357 self.assertEqual('leader', server.get_state()) 358 self.assertEqual(server.srp_server_get_state(), 'running') 359 360 client.start() 361 self.simulator.go(config.ROUTER_STARTUP_DELAY) 362 self.assertEqual('router', client.get_state()) 363 364 # Immediately remove a non-existing host. 365 366 client.srp_client_enable_auto_start_mode() 367 client.srp_client_set_host_name('my-host') 368 self.assertEqual('ToAdd', client.srp_client_get_host_state()) 369 370 client.srp_client_remove_host(remove_key=True, send_unreg_to_server=True) 371 self.simulator.go(2) 372 self.assertEqual('Removed', client.srp_client_get_host_state()) 373 374 375if __name__ == '__main__': 376 unittest.main() 377