From fad92841e74d4830c7a39c42ecfa3a265aa588f8 Mon Sep 17 00:00:00 2001 From: Simon Lin Date: Mon, 28 Sep 2020 02:32:33 +0800 Subject: [PATCH] [dua] handle AddressError (#5516) This commit enhances DUA feature to handle Address Error by increasing the DAD counter and generating new DUA address if a DUA conflict is detected. --- src/core/thread/address_resolver.cpp | 11 +- src/core/thread/dua_manager.cpp | 22 +-- src/core/thread/dua_manager.hpp | 6 + tests/scripts/thread-cert/config.py | 1 + tests/scripts/thread-cert/node.py | 15 ++ tests/scripts/thread-cert/thread_cert.py | 2 + .../v1_2_test_dua_handle_address_error.py | 151 ++++++++++++++++++ 7 files changed, 198 insertions(+), 10 deletions(-) create mode 100755 tests/scripts/thread-cert/v1_2_test_dua_handle_address_error.py diff --git a/src/core/thread/address_resolver.cpp b/src/core/thread/address_resolver.cpp index f577572ee..b013b94f7 100644 --- a/src/core/thread/address_resolver.cpp +++ b/src/core/thread/address_resolver.cpp @@ -704,7 +704,16 @@ void AddressResolver::HandleAddressError(Coap::Message &aMessage, const Ip6::Mes if (address->GetAddress() == target && Get().GetMeshLocal64().GetIid() != meshLocalIid) { // Target EID matches address and Mesh Local EID differs - Get().RemoveUnicastAddress(*address); +#if OPENTHREAD_CONFIG_DUA_ENABLE + if (Get().IsDomainUnicast(address->GetAddress())) + { + Get().NotifyDuplicateDomainUnicastAddress(); + } + else +#endif + { + Get().RemoveUnicastAddress(*address); + } ExitNow(); } } diff --git a/src/core/thread/dua_manager.cpp b/src/core/thread/dua_manager.cpp index 2deb26597..f41777007 100644 --- a/src/core/thread/dua_manager.cpp +++ b/src/core/thread/dua_manager.cpp @@ -258,7 +258,18 @@ void DuaManager::UpdateRegistrationDelay(uint8_t aDelay) UpdateTimeTickerRegistration(); } } -#endif + +void DuaManager::NotifyDuplicateDomainUnicastAddress(void) +{ + RemoveDomainUnicastAddress(); + mDadCounter++; + + if (GenerateDomainUnicastAddressIid() == OT_ERROR_NONE) + { + AddDomainUnicastAddress(); + } +} +#endif // OPENTHREAD_CONFIG_DUA_ENABLE void DuaManager::UpdateReregistrationDelay(void) { @@ -584,14 +595,7 @@ otError DuaManager::ProcessDuaResponse(Coap::Message &aMessage) RemoveDomainUnicastAddress(); break; case ThreadStatusTlv::kDuaDuplicate: - RemoveDomainUnicastAddress(); - mDadCounter++; - - if (GenerateDomainUnicastAddressIid() == OT_ERROR_NONE) - { - AddDomainUnicastAddress(); - } - + NotifyDuplicateDomainUnicastAddress(); break; case ThreadStatusTlv::kDuaNoResources: case ThreadStatusTlv::kDuaNotPrimary: diff --git a/src/core/thread/dua_manager.hpp b/src/core/thread/dua_manager.hpp index 2ff92532d..3ec82baa1 100644 --- a/src/core/thread/dua_manager.hpp +++ b/src/core/thread/dua_manager.hpp @@ -153,6 +153,12 @@ public: * */ void Restore(void); + + /** + * This method notifies duplicated Domain Unicast Address. + * + */ + void NotifyDuplicateDomainUnicastAddress(void); #endif #if OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index ed720b2b4..ce182541a 100644 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -127,6 +127,7 @@ AQ_TIMEOUT = 3 ADDRESS_QUERY_INITIAL_RETRY_DELAY = 15 DEFAULT_CHILD_TIMEOUT = 6 VIRTUAL_TIME = int(os.getenv('VIRTUAL_TIME', 0)) +PARENT_AGGREGATIOIN_DELAY = 5 LEADER_NOTIFY_SED_BY_CHILD_UPDATE_REQUEST = True diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 849491455..136294f27 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -959,6 +959,16 @@ class NodeImpl: self.send_command('routerdowngradethreshold') return int(self._expect_result(r'\d+')) + def set_router_eligible(self, enable: bool): + cmd = f'routereligible {"enable" if enable else "disable"}' + self.send_command(cmd) + self._expect('Done') + + def get_router_eligible(self) -> bool: + states = [r'Disabled', r'Enabled'] + self.send_command('routereligible') + return self._expect_result(states) == 'Enabled' + def prefer_router_id(self, router_id): cmd = 'preferrouterid %d' % router_id self.send_command(cmd) @@ -1007,6 +1017,11 @@ class NodeImpl: self.send_command(cmd) self._expect('Done') + def del_ipaddr(self, ipaddr): + cmd = 'ipaddr del %s' % ipaddr + self.send_command(cmd) + self._expect('Done') + def add_ipmaddr(self, ipmaddr): cmd = 'ipmaddr add %s' % ipmaddr self.send_command(cmd) diff --git a/tests/scripts/thread-cert/thread_cert.py b/tests/scripts/thread-cert/thread_cert.py index 8655b17dd..dab0410d9 100644 --- a/tests/scripts/thread-cert/thread_cert.py +++ b/tests/scripts/thread-cert/thread_cert.py @@ -174,6 +174,8 @@ class TestCase(NcpSupportMixin, unittest.TestCase): self.nodes[i].set_router_upgrade_threshold(params['router_upgrade_threshold']) if 'router_downgrade_threshold' in params: self.nodes[i].set_router_downgrade_threshold(params['router_downgrade_threshold']) + if 'router_eligible' in params: + self.nodes[i].set_router_eligible(params['router_eligible']) if 'timeout' in params: self.nodes[i].set_timeout(params['timeout']) diff --git a/tests/scripts/thread-cert/v1_2_test_dua_handle_address_error.py b/tests/scripts/thread-cert/v1_2_test_dua_handle_address_error.py new file mode 100755 index 000000000..beeb90824 --- /dev/null +++ b/tests/scripts/thread-cert/v1_2_test_dua_handle_address_error.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# This script tests how node would handle duplicated Domain Unicast Address. +# + +import unittest + +import config +import thread_cert + +BBR_1 = 1 # Collapsed with Leader Role +ROUTER = 2 +FED = 3 +MED = 4 + +WAIT_ATTACH = 5 +WAIT_REDUNDANCE = 3 +ROUTER_SELECTION_JITTER = 1 +BBR_REGISTRATION_JITTER = 5 +SED_POLL_PERIOD = 2000 # 2s +MED_TIMEOUT = 20 # 20s +""" + Topology + + BBR_1 (Leader) + | + ROUTER_1---FED + | + MED +""" + +WAIT_REDUNDANCE = 3 +REG_DELAY = 5 + + +class TestDomainUnicastAddress(thread_cert.TestCase): + TOPOLOGY = { + BBR_1: { + 'version': '1.2', + 'allowlist': [ROUTER], + 'router_selection_jitter': ROUTER_SELECTION_JITTER, + 'is_bbr': True + }, + ROUTER: { + 'version': '1.2', + 'allowlist': [BBR_1, FED, MED], + 'router_selection_jitter': ROUTER_SELECTION_JITTER, + }, + FED: { + 'version': '1.2', + 'allowlist': [ROUTER], + 'router_eligible': False, + 'mode': 'rsdn', + }, + MED: { + 'version': '1.2', + 'allowlist': [ROUTER], + 'mode': 'rsn', + }, + } + + def test(self): + self.simulator.set_lowpan_context(1, config.DOMAIN_PREFIX) + + # 1) Bring up BBR_1, BBR_1 becomes Leader and Primary Backbone Router, with Domain + # Prefix without `P_slaac`. + self.nodes[BBR_1].set_bbr_registration_jitter(BBR_REGISTRATION_JITTER) + self.nodes[BBR_1].set_backbone_router(seqno=1, reg_delay=REG_DELAY) + self.nodes[BBR_1].start() + + self.simulator.go(WAIT_ATTACH + ROUTER_SELECTION_JITTER) + self.assertEqual(self.nodes[BBR_1].get_state(), 'leader') + self.nodes[BBR_1].enable_backbone_router() + self.simulator.go(BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE) + self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(), 'Primary') + + self.nodes[BBR_1].set_domain_prefix(config.DOMAIN_PREFIX, 'prosD') + self.simulator.go(WAIT_REDUNDANCE) + assert self.nodes[BBR_1].has_ipmaddr(config.ALL_DOMAIN_BBRS_ADDRESS) + + # 2) Bring up ROUTER_1 + self.nodes[ROUTER].start() + self.simulator.go(WAIT_ATTACH + REG_DELAY + WAIT_REDUNDANCE) + self.assertEqual(self.nodes[ROUTER].get_state(), 'router') + + # Bring up FED + self.nodes[FED].start() + self.simulator.go(WAIT_ATTACH + REG_DELAY + WAIT_REDUNDANCE) + self.assertEqual(self.nodes[FED].get_state(), 'child') + + # Bring up MED + self.nodes[MED].start() + self.simulator.go(WAIT_ATTACH + config.PARENT_AGGREGATIOIN_DELAY + REG_DELAY + WAIT_REDUNDANCE) + self.assertEqual(self.nodes[MED].get_state(), 'child') + + self._verify_dua_handle_address_error(ROUTER) + self._verify_dua_handle_address_error(FED) + self._verify_dua_handle_address_error(MED, is_med=True) + + def _verify_dua_handle_address_error(self, nodeid, is_med=False): + dua = self.nodes[nodeid].get_addr(config.DOMAIN_PREFIX) + self.assertIsNotNone(dua) + + # Ping the DUA to verify reachability, and also fill the EID cache on BBR_1 + self.assertTrue(self.nodes[BBR_1].ping(dua)) + + self.simulator.go(WAIT_REDUNDANCE) + + # Send fake /a/an from ROUTER to BBR_1 for the node's DUA + pbbr_rloc = self.nodes[BBR_1].get_ip6_address(config.ADDRESS_TYPE.RLOC) + self.nodes[ROUTER].send_address_notification(pbbr_rloc, dua, f'000000000000{nodeid:04x}') + + self.simulator.go(config.PARENT_AGGREGATIOIN_DELAY * is_med + REG_DELAY + WAIT_REDUNDANCE + 50) + + # Make sure device handles /a/ae correctly by generating new DUA + new_dua = self.nodes[nodeid].get_addr(config.DOMAIN_PREFIX) + self.assertNotEqual(dua, new_dua) + self.assertTrue(self.nodes[BBR_1].ping(new_dua)) + self.assertFalse(self.nodes[BBR_1].ping(dua)) + + self.simulator.go(3) + + +if __name__ == '__main__': + unittest.main()