From 92b954ae1d20c19a612e849ff8d3c7dafb706a7c Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 8 May 2023 20:05:21 -0700 Subject: [PATCH] [netdata] add `otNetDataReplacePublishedExternalRoute()` API (#9013) This commit adds a public OT API to replace a previously published external route entry in the Thread Network Data. It also adds a related CLI command, and updates `test_netdata_publisher` to validate the new behavior. --- include/openthread/instance.h | 2 +- include/openthread/netdata_publisher.h | 36 +++++++++++++++++++ src/cli/README_NETDATA.md | 15 ++++++++ src/cli/cli_network_data.cpp | 23 ++++++++++++ src/core/api/netdata_publisher_api.cpp | 8 +++++ src/core/thread/network_data_publisher.hpp | 14 +++++--- tests/scripts/thread-cert/node.py | 4 +++ .../thread-cert/test_netdata_publisher.py | 13 +++++++ 8 files changed, 110 insertions(+), 5 deletions(-) diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 755dc3be2..14c9999f2 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (318) +#define OPENTHREAD_API_VERSION (319) /** * @addtogroup api-instance diff --git a/include/openthread/netdata_publisher.h b/include/openthread/netdata_publisher.h index 0e68b0ec4..10346c254 100644 --- a/include/openthread/netdata_publisher.h +++ b/include/openthread/netdata_publisher.h @@ -237,6 +237,42 @@ otError otNetDataPublishOnMeshPrefix(otInstance *aInstance, const otBorderRouter */ otError otNetDataPublishExternalRoute(otInstance *aInstance, const otExternalRouteConfig *aConfig); +/** + * This function replaces a previously published external route in the Thread Network Data. + * + * This function requires the feature `OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE` to be enabled. + * + * If there is no previously published external route matching @p aPrefix, this function behaves similarly to + * `otNetDataPublishExternalRoute()`, i.e., it will start the process of publishing @a aConfig as an external route in + * the Thread Network Data. + * + * If there is a previously published route entry matching @p aPrefix, it will be replaced with the new prefix from + * @p aConfig. + * + * - If the @p aPrefix was already added in the Network Data, the change to the new prefix in @p aConfig is immediately + * reflected in the Network Data. This ensures that route entries in the Network Data are not abruptly removed and + * the transition from aPrefix to the new prefix is smooth. + * + * - If the old published @p aPrefix was not added in the Network Data, it will be replaced with the new @p aConfig + * prefix but it will not be immediately added. Instead, it will start the process of publishing it in the Network + * Data (monitoring the Network Data to determine when/if to add the prefix, depending on the number of similar + * prefixes present in the Network Data). + * + * @param[in] aPrefix The previously published external route prefix to replace. + * @param[in] aConfig The external route config to publish. + * @param[in] aRequester The requester (`kFromUser` or `kFromRoutingManager` module). + * + * @retval OT_ERROR_NONE The external route is published successfully. + * @retval OT_ERROR_INVALID_ARGS The @p aConfig is not valid (bad prefix, invalid flag combinations, or not stable). + * @retval OT_ERROR_NO_BUFS Could not allocate an entry for the new request. Publisher supports a limited number + * of entries (shared between on-mesh prefix and external route) determined by config + * `OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_PREFIX_ENTRIES`. + * + */ +otError otNetDataReplacePublishedExternalRoute(otInstance *aInstance, + const otIp6Prefix *aPrefix, + const otExternalRouteConfig *aConfig); + /** * This function indicates whether or not currently a published prefix entry (on-mesh or external route) is added to * the Thread Network Data. diff --git a/src/cli/README_NETDATA.md b/src/cli/README_NETDATA.md index b5c8ff0c5..5fff19f09 100644 --- a/src/cli/README_NETDATA.md +++ b/src/cli/README_NETDATA.md @@ -269,6 +269,21 @@ Publish an external route entry. Done ``` +### publish replace \ \ [sn][prf] + +Replace a previously published external route entry. + +If there is no previously published external route matching old prefix, this command behaves similarly to `netdata publish route`. If there is a previously published route entry, it will be replaced with the new prefix. In particular, if the old prefix was already added in the Network Data, the change to the new prefix is immediately reflected in the Network Data (i.e., old prefix is removed and the new prefix is added in the same Network Data registration request to leader). This ensures that route entries in the Network Data are not abruptly removed. + +- s: Stable flag +- n: NAT64 flag +- prf: Preference, which may be: 'high', 'med', or 'low'. + +```bash +> netdata publish replace ::/0 fd00:1234:5678::/64 s high +Done +``` + ### register Usage: `netdata register` diff --git a/src/cli/cli_network_data.cpp b/src/cli/cli_network_data.cpp index 499a5fbdb..36fcf3de5 100644 --- a/src/cli/cli_network_data.cpp +++ b/src/cli/cli_network_data.cpp @@ -354,6 +354,29 @@ template <> otError NetworkData::Process(Arg aArgs[]) error = otNetDataPublishExternalRoute(GetInstancePtr(), &config); ExitNow(); } + + /** + * @cli netdata publish replace + * @code + * netdata publish replace ::/0 fd00:1234:5678::/64 s high + * Done + * @endcode + * @cparam netdata publish replace @ca{oldprefix} @ca{prefix} [@ca{sn}] [@ca{high}|@ca{med}|@ca{low}] + * OT CLI uses mapped arguments to configure #otExternalRouteConfig values. @moreinfo{the @overview}. + * @par + * Replaces a previously published external route entry. @moreinfo{@netdata}. + * @sa otNetDataReplacePublishedExternalRoute + */ + if (aArgs[0] == "replace") + { + otIp6Prefix prefix; + otExternalRouteConfig config; + + SuccessOrExit(error = aArgs[1].ParseAsIp6Prefix(prefix)); + SuccessOrExit(error = Interpreter::ParseRoute(aArgs + 2, config)); + error = otNetDataReplacePublishedExternalRoute(GetInstancePtr(), &prefix, &config); + ExitNow(); + } #endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE error = OT_ERROR_INVALID_ARGS; diff --git a/src/core/api/netdata_publisher_api.cpp b/src/core/api/netdata_publisher_api.cpp index 62b48f5d1..9101b0a29 100644 --- a/src/core/api/netdata_publisher_api.cpp +++ b/src/core/api/netdata_publisher_api.cpp @@ -92,6 +92,14 @@ otError otNetDataPublishExternalRoute(otInstance *aInstance, const otExternalRou NetworkData::Publisher::kFromUser); } +otError otNetDataReplacePublishedExternalRoute(otInstance *aInstance, + const otIp6Prefix *aPrefix, + const otExternalRouteConfig *aConfig) +{ + return AsCoreType(aInstance).Get().ReplacePublishedExternalRoute( + AsCoreType(aPrefix), AsCoreType(aConfig), NetworkData::Publisher::kFromUser); +} + bool otNetDataIsPrefixAdded(otInstance *aInstance, const otIp6Prefix *aPrefix) { return AsCoreType(aInstance).Get().IsPrefixAdded(AsCoreType(aPrefix)); diff --git a/src/core/thread/network_data_publisher.hpp b/src/core/thread/network_data_publisher.hpp index 025462d5a..3d3a37f27 100644 --- a/src/core/thread/network_data_publisher.hpp +++ b/src/core/thread/network_data_publisher.hpp @@ -276,10 +276,16 @@ public: * Thread Network Data. * * If there is a previously published route entry matching @p aPrefix, it will be replaced with the new prefix from - * @p aConfig. In particular, if the @p aPrefix was already added in the Network Data, the change to the new prefix - * in @p aConfig is immediately reflected in the Network Data (i.e., @p aPrefix is removed and the new prefix is - * added in the same Network Data registration request to leader). This ensures that route entries in the Network - * Data are not abruptly removed and the transition from @p aPrefix to new prefix is smooth. + * @p aConfig. + * + * - If the @p aPrefix was already added in the Network Data, the change to the new prefix in @p aConfig is + * immediately reflected in the Network Data. This ensures that route entries in the Network Data are not + * abruptly removed and the transition from aPrefix to the new prefix is smooth. + * + * - If the old published @p aPrefix was not added in the Network Data, it will be replaced with the new @p aConfig + * prefix but it will not be immediately added. Instead, it will start the process of publishing it in the + * Network Data (monitoring the Network Data to determine when/if to add the prefix, depending on the number of + * similar prefixes present in the Network Data). * * @param[in] aPrefix The previously published external route prefix to replace. * @param[in] aConfig The external route config to publish. diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index b1a9d28ef..57a41b2d8 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -2380,6 +2380,10 @@ class NodeImpl: self.send_command(f'netdata publish route {prefix} {flags} {prf}') self._expect_done() + def netdata_publish_replace(self, old_prefix, prefix, flags='s', prf='med'): + self.send_command(f'netdata publish replace {old_prefix} {prefix} {flags} {prf}') + self._expect_done() + def netdata_unpublish_prefix(self, prefix): self.send_command(f'netdata unpublish {prefix}') self._expect_done() diff --git a/tests/scripts/thread-cert/test_netdata_publisher.py b/tests/scripts/thread-cert/test_netdata_publisher.py index 548544081..f1a1c5d35 100755 --- a/tests/scripts/thread-cert/test_netdata_publisher.py +++ b/tests/scripts/thread-cert/test_netdata_publisher.py @@ -461,6 +461,19 @@ class NetDataPublisher(thread_cert.TestCase): routes = leader.get_routes() self.check_num_of_routes(routes, num - 1, 0, 1) + # Replace the published route on leader with '::/0'. + leader.netdata_publish_replace(EXTERNAL_ROUTE, '::/0', EXTERNAL_FLAGS, 'med') + self.simulator.go(0.2) + routes = leader.get_routes() + self.assertEqual([route.split(' ')[0] == '::/0' for route in routes].count(True), 1) + self.check_num_of_routes(routes, num - 1, 1, 0) + + # Replace it back to the original route. + leader.netdata_publish_replace('::/0', EXTERNAL_ROUTE, EXTERNAL_FLAGS, 'high') + self.simulator.go(WAIT_TIME) + routes = leader.get_routes() + self.check_num_of_routes(routes, num - 1, 0, 1) + # Publish the same prefix on leader as an on-mesh prefix. Make # sure it is removed from external routes and now seen in the # prefix list.