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