From 4c70cbb162b04fd4c90b51b0a0f133771f9d189c Mon Sep 17 00:00:00 2001 From: Song GUO Date: Thu, 22 Dec 2022 09:52:00 +0800 Subject: [PATCH] [nat64] NAT64 translator should be active only when local prefix is published (#8558) If an infrastructure-provided NAT64 prefix is present, we should bypass the NAT64 translator to let the border router forward the packet to the infra NAT64 service. --- src/core/border_router/routing_manager.cpp | 13 +++++++-- src/core/net/nat64_translator.cpp | 16 +++++++++- src/core/net/nat64_translator.hpp | 10 +++++-- .../nat64/test_multi_border_routers.py | 17 ++--------- .../nat64/test_with_infrastructure_prefix.py | 29 +++++++++++++++++++ tests/scripts/thread-cert/thread_cert.py | 15 +++++++++- 6 files changed, 79 insertions(+), 21 deletions(-) diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index 07f760859..00b415755 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -2702,7 +2702,7 @@ void RoutingManager::Nat64PrefixManager::Stop(void) mTimer.Stop(); #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE - Get().SetNat64Prefix(mPublishedPrefix); + Get().ClearNat64Prefix(); #endif } @@ -2784,7 +2784,16 @@ void RoutingManager::Nat64PrefixManager::Evaluate(void) } #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE - Get().SetNat64Prefix(mPublishedPrefix); + // When there is an prefix other than mLocalPrefix, means there is an external translator available. So we bypass + // the NAT64 translator by clearing the NAT64 prefix in the translator. + if (mPublishedPrefix == mLocalPrefix) + { + Get().SetNat64Prefix(mLocalPrefix); + } + else + { + Get().ClearNat64Prefix(); + } #endif exit: diff --git a/src/core/net/nat64_translator.cpp b/src/core/net/nat64_translator.cpp index d21ba15e1..d642aa353 100644 --- a/src/core/net/nat64_translator.cpp +++ b/src/core/net/nat64_translator.cpp @@ -515,13 +515,27 @@ exit: void Translator::SetNat64Prefix(const Ip6::Prefix &aNat64Prefix) { - if (mNat64Prefix != aNat64Prefix) + if (aNat64Prefix.GetLength() == 0) + { + ClearNat64Prefix(); + } + else if (mNat64Prefix != aNat64Prefix) { LogInfo("IPv6 Prefix for NAT64 updated to %s", aNat64Prefix.ToString().AsCString()); mNat64Prefix = aNat64Prefix; + UpdateState(); } +} +void Translator::ClearNat64Prefix(void) +{ + VerifyOrExit(mNat64Prefix.GetLength() != 0); + mNat64Prefix.Clear(); + LogInfo("IPv6 Prefix for NAT64 cleared"); UpdateState(); + +exit: + return; } void Translator::HandleMappingExpirerTimer(void) diff --git a/src/core/net/nat64_translator.hpp b/src/core/net/nat64_translator.hpp index 689731fe3..b670aa1c2 100644 --- a/src/core/net/nat64_translator.hpp +++ b/src/core/net/nat64_translator.hpp @@ -253,14 +253,20 @@ public: /** * Sets the prefix of NAT64-mapped addresses in the thread network. The address mapping table will not be cleared. - * If an empty NAT64 prefix is set, the translator will return kNotTranslated for all IPv6 datagrams and kDrop for - * all IPv4 datagrams. + * This function equals to `ClearNat64Prefix` when an empty prefix is provided. * * @param[in] aNat64Prefix The prefix of the NAT64-mapped addresses. * */ void SetNat64Prefix(const Ip6::Prefix &aNat64Prefix); + /** + * Clear the prefix of NAT64-mapped addresses in the thread network. The address mapping table will not be cleared. + * The translator will return kNotTranslated for all IPv6 datagrams and kDrop for all IPv4 datagrams. + * + */ + void ClearNat64Prefix(void); + /** * Initializes an `otNat64AddressMappingIterator`. * diff --git a/tests/scripts/thread-cert/border_router/nat64/test_multi_border_routers.py b/tests/scripts/thread-cert/border_router/nat64/test_multi_border_routers.py index e4f4b9654..7a4791d44 100644 --- a/tests/scripts/thread-cert/border_router/nat64/test_multi_border_routers.py +++ b/tests/scripts/thread-cert/border_router/nat64/test_multi_border_routers.py @@ -26,7 +26,6 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # -from typing import Mapping import unittest import config @@ -85,19 +84,6 @@ class Nat64MultiBorderRouter(thread_cert.TestCase): }, } - def assertDictIncludes(self, actual: Mapping[str, str], expected: Mapping[str, str]): - """ Asserts the `actual` dict includes the `expected` dict. - - Args: - actual: A dict for checking. - expected: The expected items that the actual dict should contains. - """ - for k, v in expected.items(): - if k not in actual: - raise AssertionError(f"key {k} is not found in first dict") - if v != actual[k]: - raise AssertionError(f"{repr(actual[k])} != {repr(v)} for key {k}") - def test(self): br1 = self.nodes[BR1] router = self.nodes[ROUTER] @@ -123,6 +109,7 @@ class Nat64MultiBorderRouter(thread_cert.TestCase): # # Case 1. BR2 with an infrastructure prefix joins the network later and # it will add the infrastructure nat64 prefix to Network Data. + # Note: NAT64 translator will be bypassed. # br2.start() # When feature flag is enabled, NAT64 might be disabled by default. So @@ -146,7 +133,7 @@ class Nat64MultiBorderRouter(thread_cert.TestCase): }) self.assertDictIncludes(br2.nat64_state, { 'PrefixManager': NAT64_STATE_ACTIVE, - 'Translator': NAT64_STATE_ACTIVE + 'Translator': NAT64_STATE_NOT_RUNNING }) # diff --git a/tests/scripts/thread-cert/border_router/nat64/test_with_infrastructure_prefix.py b/tests/scripts/thread-cert/border_router/nat64/test_with_infrastructure_prefix.py index d0f6b2c56..465b5a7a1 100644 --- a/tests/scripts/thread-cert/border_router/nat64/test_with_infrastructure_prefix.py +++ b/tests/scripts/thread-cert/border_router/nat64/test_with_infrastructure_prefix.py @@ -52,6 +52,11 @@ SMALL_NAT64_PREFIX = "2000:0:0:1:0:0::/96" NAT64_PREFIX_REFRESH_DELAY = 305 +NAT64_STATE_DISABLED = 'disabled' +NAT64_STATE_NOT_RUNNING = 'not_running' +NAT64_STATE_IDLE = 'idle' +NAT64_STATE_ACTIVE = 'active' + class Nat64SingleBorderRouter(thread_cert.TestCase): USE_MESSAGE_FACTORY = False @@ -91,6 +96,10 @@ class Nat64SingleBorderRouter(thread_cert.TestCase): self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) nat64_prefix = br.get_netdata_nat64_prefix()[0] self.assertEqual(nat64_prefix, infra_nat64_prefix) + self.assertDictIncludes(br.nat64_state, { + 'PrefixManager': NAT64_STATE_ACTIVE, + 'Translator': NAT64_STATE_NOT_RUNNING + }) # Case 2 Withdraw infrastructure prefix when a smaller prefix in medium # preference is present @@ -100,6 +109,10 @@ class Nat64SingleBorderRouter(thread_cert.TestCase): self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) self.assertNotEqual(infra_nat64_prefix, br.get_netdata_nat64_prefix()[0]) + self.assertDictIncludes(br.nat64_state, { + 'PrefixManager': NAT64_STATE_IDLE, + 'Translator': NAT64_STATE_NOT_RUNNING + }) br.remove_route(SMALL_NAT64_PREFIX) br.register_netdata() @@ -115,6 +128,10 @@ class Nat64SingleBorderRouter(thread_cert.TestCase): self.assertEqual(len(br.get_netdata_nat64_prefix()), 2) self.assertEqual(br.get_netdata_nat64_prefix(), [infra_nat64_prefix, SMALL_NAT64_PREFIX]) + self.assertDictIncludes(br.nat64_state, { + 'PrefixManager': NAT64_STATE_ACTIVE, + 'Translator': NAT64_STATE_NOT_RUNNING + }) br.remove_route(SMALL_NAT64_PREFIX) br.register_netdata() @@ -128,6 +145,10 @@ class Nat64SingleBorderRouter(thread_cert.TestCase): self.assertNotEqual(local_nat64_prefix, infra_nat64_prefix) self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) self.assertEqual(br.get_netdata_nat64_prefix()[0], local_nat64_prefix) + self.assertDictIncludes(br.nat64_state, { + 'PrefixManager': NAT64_STATE_ACTIVE, + 'Translator': NAT64_STATE_ACTIVE + }) # Case 5 Infrastructure nat64 prefix is recovered br.bash("service bind9 start") @@ -136,6 +157,10 @@ class Nat64SingleBorderRouter(thread_cert.TestCase): self.assertEqual(br.get_br_favored_nat64_prefix(), infra_nat64_prefix) self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) self.assertEqual(br.get_netdata_nat64_prefix()[0], infra_nat64_prefix) + self.assertDictIncludes(br.nat64_state, { + 'PrefixManager': NAT64_STATE_ACTIVE, + 'Translator': NAT64_STATE_NOT_RUNNING + }) # Case 6 Change infrastructure nat64 prefix br.bash("sed -i 's/dns64 /\/\/dns64 /' /etc/bind/named.conf.options") @@ -146,6 +171,10 @@ class Nat64SingleBorderRouter(thread_cert.TestCase): self.assertEqual(br.get_br_favored_nat64_prefix(), SMALL_NAT64_PREFIX) self.assertEqual(len(br.get_netdata_nat64_prefix()), 1) self.assertEqual(br.get_netdata_nat64_prefix()[0], SMALL_NAT64_PREFIX) + self.assertDictIncludes(br.nat64_state, { + 'PrefixManager': NAT64_STATE_ACTIVE, + 'Translator': NAT64_STATE_NOT_RUNNING + }) if __name__ == '__main__': diff --git a/tests/scripts/thread-cert/thread_cert.py b/tests/scripts/thread-cert/thread_cert.py index 369ed5075..7277d3bb9 100755 --- a/tests/scripts/thread-cert/thread_cert.py +++ b/tests/scripts/thread-cert/thread_cert.py @@ -38,7 +38,7 @@ import sys import time import traceback import unittest -from typing import Optional, Callable, Union, Any +from typing import Optional, Callable, Union, Mapping, Any import config import debug @@ -592,3 +592,16 @@ class TestCase(NcpSupportMixin, unittest.TestCase): else: raise Exception("Route between node %d and %d is not established" % (node1, node2)) + + def assertDictIncludes(self, actual: Mapping[str, str], expected: Mapping[str, str]): + """ Asserts the `actual` dict includes the `expected` dict. + + Args: + actual: A dict for checking. + expected: The expected items that the actual dict should contains. + """ + for k, v in expected.items(): + if k not in actual: + raise AssertionError(f"key {k} is not found in first dict") + if v != actual[k]: + raise AssertionError(f"{repr(actual[k])} != {repr(v)} for key {k}")