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(LEASE) 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 # We previously had self.simulator.go(5) but got the issue that the client is scheduling 192 # the refresh timer with half of the lease time (10 seconds) and there will be chances 193 # that the client will be just in status "kToRefresh" after self.simulator.go(5). This will 194 # fail the checks in self.check_host_and_service() so updated to wait for 2 seconds. 195 self.simulator.go(2) 196 197 self.check_host_and_service(server, client, '2001::2', 'my-service') 198 self.host_check_mdns_service(host, '2001::2', 'my-service') 199 self.assertIsNone(host.discover_mdns_service('my-service-1', '_ipps._tcp', 'my-host')) 200 201 # Wait for KEY expiration of service 'my-service-1'. 202 # FIXME: workaround for https://github.com/openthread/ot-br-posix/issues/1071. 203 self.simulator.go(KEY_LEASE + 5) 204 205 # 206 # 8. Update both the host and the service in a loop and make sure the 207 # Advertising Proxy can follow. 208 # 209 210 for host_address_suffix, service_port in ((1, 12341), (2, 12342), (3, 12343), (2, 12345)): 211 host_address = f'2001::{host_address_suffix}' 212 client.srp_client_clear_service('my-service', '_ipps._tcp') 213 client.srp_client_set_host_address(host_address) 214 client.srp_client_add_service('my-service', '_ipps._tcp', service_port) 215 self.simulator.go(10) 216 217 self.check_host_and_service(server, client, host_address, 'my-service', service_port) 218 self.host_check_mdns_service(host, host_address, 'my-service', service_port) 219 220 # 221 # 9. Check if Advertising Proxy filters out Mesh Local and Link Local host addresses 222 # 223 client.srp_client_remove_host() 224 self.simulator.go(2) 225 client.srp_client_set_host_name('my-host') 226 client.srp_client_set_host_address('2001::1', '2002::2', 227 client.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], 228 client.get_ip6_address(config.ADDRESS_TYPE.LINK_LOCAL), 229 client.get_ip6_address(config.ADDRESS_TYPE.ML_EID)) 230 client.srp_client_add_service('my-service', '_ipps._tcp', 12345) 231 client.srp_client_enable_auto_start_mode() 232 self.simulator.go(10) 233 self.check_host_and_service(server, client, [ 234 '2001::1', '2002::2', 235 client.get_ip6_address(config.ADDRESS_TYPE.OMR)[0], 236 client.get_ip6_address(config.ADDRESS_TYPE.LINK_LOCAL), 237 client.get_ip6_address(config.ADDRESS_TYPE.ML_EID) 238 ], 'my-service', 12345) 239 self.host_check_mdns_service( 240 host, ['2001::1', '2002::2', client.get_ip6_address(config.ADDRESS_TYPE.OMR)[0]], 'my-service', 12345) 241 242 client.srp_client_set_host_address(client.get_ip6_address(config.ADDRESS_TYPE.LINK_LOCAL), 243 client.get_ip6_address(config.ADDRESS_TYPE.ML_EID), client.get_rloc()) 244 self.simulator.go(10) 245 self.check_host_and_service(server, client, [ 246 client.get_ip6_address(config.ADDRESS_TYPE.LINK_LOCAL), 247 client.get_ip6_address(config.ADDRESS_TYPE.ML_EID), 248 client.get_rloc() 249 ], 'my-service', 12345) 250 self.host_check_mdns_service(host, [], 'my-service', 12345) 251 252 client.srp_client_set_host_address('2005::3') 253 self.simulator.go(10) 254 self.check_host_and_service(server, client, '2005::3', 'my-service', 12345) 255 self.host_check_mdns_service(host, '2005::3', 'my-service', 12345) 256 257 # 258 # 10. Check if the expired service is removed by the Advertising Proxy. 259 # 260 client.srp_client_stop() 261 self.simulator.go(LEASE + 2) 262 263 self.assertIsNone(host.discover_mdns_service('my-service', '_ipps._tcp', 'my-host')) 264 self.assertIsNone(host.discover_mdns_service('my-service-1', '_ipps._tcp', 'my-host')) 265 266 def host_check_mdns_service(self, host, host_addrs, service_instance, service_port=12345): 267 if isinstance(host_addrs, str): 268 host_addrs = [host_addrs] 269 service = host.discover_mdns_service(service_instance, '_ipps._tcp', 'my-host') 270 self.assertIsNotNone(service) 271 self.assertEqual(service['instance'], service_instance) 272 self.assertEqual(service['name'], '_ipps._tcp') 273 self.assertEqual(service['port'], service_port) 274 self.assertEqual(service['priority'], 0) 275 self.assertEqual(service['weight'], 0) 276 self.assertEqual(service['host'], 'my-host') 277 self.assertEqual(len(service['addresses']), len(host_addrs)) 278 self.assertEqual(sorted(map(ipaddress.ip_address, service['addresses'])), 279 sorted(map(ipaddress.ip_address, host_addrs))) 280 281 def check_host_and_service(self, server, client, host_addrs, service_instance, service_port=12345): 282 """Check that we have properly registered host and service instance. 283 """ 284 285 if isinstance(host_addrs, str): 286 host_addrs = [host_addrs] 287 client_services = client.srp_client_get_services() 288 print(client_services) 289 client_services = [service for service in client_services if service['instance'] == service_instance] 290 self.assertEqual(len(client_services), 1) 291 client_service = client_services[0] 292 293 # Verify that the client possesses correct service resources. 294 self.assertEqual(client_service['instance'], service_instance) 295 self.assertEqual(client_service['name'], '_ipps._tcp') 296 self.assertEqual(int(client_service['port']), service_port) 297 self.assertEqual(int(client_service['priority']), 0) 298 self.assertEqual(int(client_service['weight']), 0) 299 300 # Verify that the client received a SUCCESS response for the server. 301 self.assertEqual(client_service['state'], 'Registered') 302 303 server_services = server.srp_server_get_services() 304 print(server_services) 305 server_services = [service for service in server_services if service['instance'] == service_instance] 306 self.assertEqual(len(server_services), 1) 307 server_service = server_services[0] 308 309 # Verify that the server accepted the SRP registration and stores 310 # the same service resources. 311 self.assertEqual(server_service['deleted'], 'false') 312 self.assertEqual(server_service['instance'], client_service['instance']) 313 self.assertEqual(server_service['name'], client_service['name']) 314 self.assertEqual(int(server_service['port']), int(client_service['port'])) 315 self.assertEqual(int(server_service['priority']), int(client_service['priority'])) 316 self.assertEqual(int(server_service['weight']), int(client_service['weight'])) 317 self.assertEqual(server_service['host'], 'my-host') 318 319 server_hosts = server.srp_server_get_hosts() 320 print(server_hosts) 321 self.assertEqual(len(server_hosts), 1) 322 server_host = server_hosts[0] 323 324 self.assertEqual(server_host['deleted'], 'false') 325 self.assertEqual(server_host['fullname'], server_service['host_fullname']) 326 self.assertEqual(sorted(map(ipaddress.ip_address, server_host['addresses'])), 327 sorted(map(ipaddress.ip_address, host_addrs))) 328 329 330class SrpClientRemoveNonExistingHost(thread_cert.TestCase): 331 USE_MESSAGE_FACTORY = False 332 333 TOPOLOGY = { 334 BR: { 335 'name': 'BR', 336 'allowlist': [ROUTER], 337 'is_otbr': True, 338 'version': '1.2', 339 }, 340 ROUTER: { 341 'name': 'Router', 342 'allowlist': [BR], 343 'version': '1.2', 344 } 345 } 346 347 def test(self): 348 server = self.nodes[BR] 349 client = self.nodes[ROUTER] 350 351 server.srp_server_set_enabled(True) 352 server.srp_server_set_lease_range(LEASE, LEASE, KEY_LEASE, KEY_LEASE) 353 server.start() 354 self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY) 355 self.assertEqual('leader', server.get_state()) 356 self.assertEqual(server.srp_server_get_state(), 'running') 357 358 client.start() 359 self.simulator.go(config.ROUTER_STARTUP_DELAY) 360 self.assertEqual('router', client.get_state()) 361 362 # Immediately remove a non-existing host. 363 364 client.srp_client_enable_auto_start_mode() 365 client.srp_client_set_host_name('my-host') 366 self.assertEqual('ToAdd', client.srp_client_get_host_state()) 367 368 client.srp_client_remove_host(remove_key=True, send_unreg_to_server=True) 369 self.simulator.go(2) 370 self.assertEqual('Removed', client.srp_client_get_host_state()) 371 372 373if __name__ == '__main__': 374 unittest.main() 375