diff --git a/Android.mk b/Android.mk index 03b3a837a..baf2ea22c 100644 --- a/Android.mk +++ b/Android.mk @@ -190,6 +190,7 @@ LOCAL_SRC_FILES := \ src/core/api/message_api.cpp \ src/core/api/multi_radio_api.cpp \ src/core/api/netdata_api.cpp \ + src/core/api/netdata_publisher_api.cpp \ src/core/api/netdiag_api.cpp \ src/core/api/network_time_api.cpp \ src/core/api/ping_sender_api.cpp \ @@ -322,6 +323,7 @@ LOCAL_SRC_FILES := \ src/core/thread/network_data_leader_ftd.cpp \ src/core/thread/network_data_local.cpp \ src/core/thread/network_data_notifier.cpp \ + src/core/thread/network_data_publisher.cpp \ src/core/thread/network_data_service.cpp \ src/core/thread/network_data_tlvs.cpp \ src/core/thread/network_data_types.cpp \ diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 8dc577541..e8f2c91c8 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -269,6 +269,11 @@ if(OT_NEIGHBOR_DISCOVERY_AGENT) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_NEIGHBOR_DISCOVERY_AGENT_ENABLE=1") endif() +option(OT_NETDATA_PUBLISHER "enable Thread Network Data publisher") +if(OT_NETDATA_PUBLISHER) + target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE=1") +endif() + option(OT_PING_SENDER "enable ping sender support" ${OT_APP_CLI}) if(OT_PING_SENDER) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_PING_SENDER_ENABLE=1") diff --git a/examples/Makefile-simulation b/examples/Makefile-simulation index afa89110f..39e61f151 100644 --- a/examples/Makefile-simulation +++ b/examples/Makefile-simulation @@ -60,6 +60,7 @@ LINK_RAW ?= 1 MAC_FILTER ?= 1 MTD_NETDIAG ?= 1 NEIGHBOR_DISCOVERY_AGENT ?= 1 +NETDATA_PUBLISHER ?= 1 PING_SENDER ?= 1 REFERENCE_DEVICE ?= 1 SERVICE ?= 1 diff --git a/examples/README.md b/examples/README.md index 60b2c1751..ea9b0c309 100644 --- a/examples/README.md +++ b/examples/README.md @@ -48,6 +48,7 @@ This page lists the available common switches with description. Unless stated ot | MLR | OT_MLR | Enables Multicast Listener Registration feature for Thread 1.2. | | MTD_NETDIAG | OT_MTD_NETDIAG | Enables the TMF network diagnostics on MTDs. | | MULTIPLE_INSTANCE | OT_MULTIPLE_INSTANCE | Enables multiple OpenThread instances. | +| NETDATA_PUBLISHER | OT_NETDATA_PUBLISHER | Enables support for Thread Network Data publisher. | | PING_SENDER | OT_PING_SENDER | Enables support for ping sender. | | OTNS | OT_OTNS | Enables support for [OpenThread Network Simulator](https://github.com/openthread/ot-ns). Enable this switch if you are building OpenThread for OpenThread Network Simulator. | | PLATFORM_UDP | OT_PLATFORM_UDP | Enables platform UDP support. | diff --git a/examples/common-switches.mk b/examples/common-switches.mk index eadcaaae5..42daeae6c 100644 --- a/examples/common-switches.mk +++ b/examples/common-switches.mk @@ -70,6 +70,7 @@ MLR ?= 0 MTD_NETDIAG ?= 0 MULTIPLE_INSTANCE ?= 0 NEIGHBOR_DISCOVERY_AGENT ?= 0 +NETDATA_PUBLISHER ?= 0 OTNS ?= 0 PING_SENDER ?= 1 PLATFORM_UDP ?= 0 @@ -265,6 +266,10 @@ ifeq ($(NEIGHBOR_DISCOVERY_AGENT),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_NEIGHBOR_DISCOVERY_AGENT_ENABLE=1 endif +ifeq ($(NETDATA_PUBLISHER),1) +COMMONCFLAGS += -DOPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE=1 +endif + ifeq ($(PING_SENDER),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_PING_SENDER_ENABLE=1 endif diff --git a/include/Makefile.am b/include/Makefile.am index b110ca06a..9cf92de93 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -71,6 +71,7 @@ openthread_headers = \ openthread/multi_radio.h \ openthread/ncp.h \ openthread/netdata.h \ + openthread/netdata_publisher.h \ openthread/netdiag.h \ openthread/network_time.h \ openthread/ping_sender.h \ diff --git a/include/openthread/BUILD.gn b/include/openthread/BUILD.gn index 62b6112a1..1bc5d55d0 100644 --- a/include/openthread/BUILD.gn +++ b/include/openthread/BUILD.gn @@ -92,6 +92,7 @@ source_set("openthread") { "multi_radio.h", "ncp.h", "netdata.h", + "netdata_publisher.h", "netdiag.h", "network_time.h", "ping_sender.h", diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 1d83dfc3a..cf496d58f 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 (146) +#define OPENTHREAD_API_VERSION (147) /** * @addtogroup api-instance diff --git a/include/openthread/netdata_publisher.h b/include/openthread/netdata_publisher.h new file mode 100644 index 000000000..d3abc3b0c --- /dev/null +++ b/include/openthread/netdata_publisher.h @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2021, 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. + */ + +/** + * @file + * @brief + * This file defines the OpenThread Network Data Publisher API. + */ + +#ifndef OPENTHREAD_NETDATA_PUBLISHER_H_ +#define OPENTHREAD_NETDATA_PUBLISHER_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup api-thread-general + * + * @{ + * + * The Network Data Publisher provides mechanisms to limit the number of similar Service and/or Prefix (on-mesh prefix + * or external route) entries in the Thread Network Data by monitoring the Network Data and managing if or when to add + * or remove entries. + * + * All the functions in this module require `OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE` to be enabled. + * + */ + +/** + * This enumeration represents the events reported from the Publisher callbacks. + * + */ +typedef enum otNetDataPublisherEvent +{ + OT_NETDATA_PUBLISHER_EVENT_ENTRY_ADDED = 0, ///< Published entry is added to the Thread Network Data. + OT_NETDATA_PUBLISHER_EVENT_ENTRY_REMOVED = 1, ///< Published entry is removed from the Thread Network Data. +} otNetDataPublisherEvent; + +/** + * This function pointer type defines the callback used to notify when a "DNS/SRP Service" entry is added to or removed + * from the Thread Network Data. + * + * On remove the callback is invoked independent of whether the entry is removed by `Publisher` (e.g., when there are + * too many similar entries already present in the Network Data) or through an explicit call to unpublish the entry + * (i.e., a call to `otNetDataUnpublishDnsSrpService()`). + * + * @param[in] aEvent Indicates the event (whether the entry was added or removed). + * @param[in] aContext A pointer to application-specific context. + * + */ +typedef void (*otNetDataDnsSrpServicePublisherCallback)(otNetDataPublisherEvent aEvent, void *aContext); + +/** + * This function pointer type defines the callback used to notify when a prefix (on-mesh or external route) entry is + * added to or removed from the Thread Network Data. + * + * On remove the callback is invoked independent of whether the entry is removed by `Publisher` (e.g., when there are + * too many similar entries already present in the Network Data) or through an explicit call to unpublish the entry. + * + * @param[in] aEvent Indicates the event (whether the entry was added or removed). + * @param[in] aPrefix A pointer to the prefix entry. + * @param[in] aContext A pointer to application-specific context. + * + */ +typedef void (*otNetDataPrefixPublisherCallback)(otNetDataPublisherEvent aEvent, + const otIp6Prefix * aPrefix, + void * aContext); + +/** + * This function requests "DNS/SRP Service Anycast Address" to be published in the Thread Network Data. + * + * This function requires the feature `OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE` to be enabled. + * + * A call to this function will remove and replace any previous "DNS/SRP Service" entry that was being published (from + * earlier call to any of `otNetDataPublishDnsSrpService{Type}()` functions). + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aSequenceNUmber The sequence number of DNS/SRP Anycast Service. + * + */ +void otNetDataPublishDnsSrpServiceAnycast(otInstance *aInstance, uint8_t aSequenceNUmber); + +/** + * This function requests "DNS/SRP Service Unicast Address" to be published in the Thread Network Data. + * + * This function requires the feature `OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE` to be enabled. + * + * A call to this function will remove and replace any previous "DNS/SRP Service" entry that was being published (from + * earlier call to any of `otNetDataPublishDnsSrpService{Type}()` functions). + * + * This function publishes the "DNS/SRP Service Unicast Address" by including the address and port info in the Service + * TLV data. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aAddress The DNS/SRP server address to publish (MUST NOT be NULL). + * @param[in] aPort The SRP server port number to publish. + * + */ +void otNetDataPublishDnsSrpServiceUnicast(otInstance *aInstance, const otIp6Address *aAddress, uint16_t aPort); + +/** + * This function requests "DNS/SRP Service Unicast Address" to be published in the Thread Network Data. + * + * This function requires the feature `OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE` to be enabled. + * + * A call to this function will remove and replace any previous "DNS/SRP Service" entry that was being published (from + * earlier call to any of `otNetDataPublishDnsSrpService{Type}()` functions). + * + * Unlike `otNetDataPublishDnsSrpServiceUnicast()` which requires the published address to be given and includes the + * info in the Service TLV data, this function uses the device's mesh-local EID and includes the info in the Server TLV + * data. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aPort The SRP server port number to publish. + * + */ +void otNetDataPublishDnsSrpServiceUnicastMeshLocalEid(otInstance *aInstance, uint16_t aPort); + +/** + * This function indicates whether or not currently the "DNS/SRP Service" entry is added to the Thread Network Data. + * + * This function requires the feature `OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE` to be enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval TRUE The published DNS/SRP Service entry is added to the Thread Network Data. + * @retval FLASE The entry is not added to Thread Network Data or there is no entry to publish. + * + */ +bool otNetDataIsDnsSrpServiceAdded(otInstance *aInstance); + +/** + * This function sets a callback for notifying when a published "DNS/SRP Service" is actually added to or removed from + * the Thread Network Data. + * + * A subsequent call to this function replaces any previously set callback function. + * + * This function requires the feature `OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE` to be enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aCallback The callback function pointer (can be NULL if not needed). + * @param[in] aContext A pointer to application-specific context (used when @p aCallback is invoked). + * + */ +void otNetDataSetDnsSrpServicePublisherCallback(otInstance * aInstance, + otNetDataDnsSrpServicePublisherCallback aCallback, + void * aContext); + +/** + * This function unpublishes any previously added "DNS/SRP (Anycast or Unicast) Service" entry from the Thread Network + * Data. + * + * This function requires the feature `OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE` to be enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + */ +void otNetDataUnpublishDnsSrpService(otInstance *aInstance); + +/** + * This function requests an on-mesh prefix to be published in the Thread Network Data. + * + * This function requires the feature `OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE` to be enabled. + * + * Only stable entries can be published (i.e.,`aConfig.mStable` MUST be TRUE). + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aConfig The on-mesh prefix config to publish (MUST NOT be NULL). + * + * @retval OT_ERROR_NONE The on-mesh prefix 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_ALREADY An entry with the same prefix is already in the published list. + * @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 otNetDataPublishOnMeshPrefix(otInstance *aInstance, const otBorderRouterConfig *aConfig); + +/** + * This function requests an external route prefix to be published in the Thread Network Data. + * + * This function requires the feature `OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE` to be enabled. + * + * Only stable entries can be published (i.e.,`aConfig.mStable` MUST be TRUE). + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aConfig The external route config to publish (MUST NOT be NULL). + * + * @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_ALREADY An entry with the same prefix is already in the published list. + * @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 otNetDataPublishExternalRoute(otInstance *aInstance, 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. + * + * This function requires the feature `OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE` to be enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aPrefix A pointer to the prefix (MUST NOT be NULL). + * + * @retval TRUE The published prefix entry is added to the Thread Network Data. + * @retval FLASE The entry is not added to Thread Network Data or there is no entry to publish. + * + */ +bool otNetDataIsPrefixAdded(otInstance *aInstance, const otIp6Prefix *aPrefix); + +/** + * This function sets a callback for notifying when a published prefix entry is actually added to or removed from + * the Thread Network Data. + * + * A subsequent call to this function replaces any previously set callback function. + * + * This function requires the feature `OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE` to be enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aCallback The callback function pointer (can be NULL if not needed). + * @param[in] aContext A pointer to application-specific context (used when @p aCallback is invoked). + * + */ +void otNetDataSetPrefixPublisherCallback(otInstance * aInstance, + otNetDataPrefixPublisherCallback aCallback, + void * aContext); + +/** + * This function unpublishes a previously published prefix (on-mesh or external route). + * + * This function requires the feature `OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE` to be enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aPrefix The prefix to unpublish (MUST NOT be NULL). + * + * @retval OT_ERROR_NONE The prefix was unpublished successfully. + * @retval OT_ERROR_NOT_FOUND Could not find the prefix in the published list. + * + */ +otError otNetDataUnpublishPrefix(otInstance *aInstance, const otIp6Prefix *aPrefix); + +/** + * @} + * + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // OPENTHREAD_NETDATA_PUBLISHER_H_ diff --git a/script/check-scan-build b/script/check-scan-build index 0c4bc1263..7f4d161de 100755 --- a/script/check-scan-build +++ b/script/check-scan-build @@ -69,6 +69,7 @@ do_scan_build() "-DOPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE=1" "-DOPENTHREAD_CONFIG_MPL_DYNAMIC_INTERVAL_ENABLE" "-DOPENTHREAD_CONFIG_NEIGHBOR_DISCOVERY_AGENT=1" + "-DOPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE=1" "-DOPENTHREAD_CONFIG_PING_SENDER_ENABLE=1" "-DOPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE=1" "-DOPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE=1" diff --git a/script/check-size b/script/check-size index ded1c1a6d..e24b53815 100755 --- a/script/check-size +++ b/script/check-size @@ -136,6 +136,7 @@ size_nrf52840_version() "-DOT_MAC_FILTER=ON" "-DOT_MESSAGE_USE_HEAP=ON" "-DOT_MTD_NETDIAG=ON" + "-DOT_NETDATA_PUBLISHER=ON" "-DOT_PING_SENDER=ON" "-DOT_SERVICE=ON" "-DOT_SLAAC=ON" diff --git a/script/cmake-build b/script/cmake-build index d53b2cceb..8f7793bec 100755 --- a/script/cmake-build +++ b/script/cmake-build @@ -89,6 +89,7 @@ readonly OT_POSIX_SIM_COMMON_OPTIONS=( "-DOT_MAC_FILTER=ON" "-DOT_MTD_NETDIAG=ON" "-DOT_NEIGHBOR_DISCOVERY_AGENT=ON" + "-DOT_NETDATA_PUBLISHER=ON" "-DOT_PING_SENDER=ON" "-DOT_REFERENCE_DEVICE=ON" "-DOT_SERVICE=ON" diff --git a/script/make-pretty b/script/make-pretty index ce9a148b7..8f4ec67c7 100755 --- a/script/make-pretty +++ b/script/make-pretty @@ -108,6 +108,7 @@ readonly OT_CLANG_TIDY_BUILD_OPTS=( '-DOT_LINK_METRICS_SUBJECT=ON' '-DOT_MAC_FILTER=ON' '-DOT_MTD_NETDIAG=ON' + '-DOT_NETDATA_PUBLISHER=ON' '-DOT_PING_SENDER=ON' '-DOT_REFERENCE_DEVICE=ON' '-DOT_SERVICE=ON' diff --git a/script/test b/script/test index 39d595be1..c0f15bfe8 100755 --- a/script/test +++ b/script/test @@ -55,17 +55,18 @@ build_simulation() { local version="$1" local options=( - "-DOT_MESSAGE_USE_HEAP=ON" - "-DOT_THREAD_VERSION=${version}" "-DBUILD_TESTING=ON" - "-DOT_REFERENCE_DEVICE=ON" - "-DOT_SRP_SERVER=ON" - "-DOT_SRP_CLIENT=ON" - "-DOT_SERVICE=ON" - "-DOT_ECDSA=ON" - "-DOT_PING_SENDER=ON" - "-DOT_DNSSD_SERVER=ON" "-DOT_DNS_CLIENT=ON" + "-DOT_DNSSD_SERVER=ON" + "-DOT_ECDSA=ON" + "-DOT_MESSAGE_USE_HEAP=ON" + "-DOT_NETDATA_PUBLISHER=ON" + "-DOT_PING_SENDER=ON" + "-DOT_REFERENCE_DEVICE=ON" + "-DOT_SERVICE=ON" + "-DOT_SRP_CLIENT=ON" + "-DOT_SRP_SERVER=ON" + "-DOT_THREAD_VERSION=${version}" ) if [[ ${FULL_LOGS} == 1 ]]; then @@ -269,6 +270,7 @@ do_build_otbr_docker() "-DOT_DNS_CLIENT=ON" "-DOT_DUA=ON" "-DOT_MLR=ON" + "-DOT_NETDATA_PUBLISHER=ON" "-DOT_SLAAC=ON" "-DOT_SRP_CLIENT=ON" "-DOT_TREL=OFF" diff --git a/src/cli/README.md b/src/cli/README.md index 526417c5f..60d9e9db4 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -2168,11 +2168,12 @@ Get the external route list in the local Network Data. Done ``` -### route add \ [s][prf] +### route add \ [sn][prf] Add a valid external route to the Network Data. - s: Stable flag +- n: NAT64 flag - prf: Default Router Preference, which may be: 'high', 'med', or 'low'. ```bash diff --git a/src/cli/README_NETDATA.md b/src/cli/README_NETDATA.md index ae6e44e22..5554a9f4b 100644 --- a/src/cli/README_NETDATA.md +++ b/src/cli/README_NETDATA.md @@ -142,9 +142,11 @@ After the device successfully attaches to a Thread network, the device will retr ## Command List - [help](#help) +- [publish](#publish) - [register](#register) - [show](#show) - [steeringdata](#steeringdata-check-eui64discerner) +- [unpublish](#unpublish) ## Command Details @@ -157,9 +159,75 @@ Print netdata help menu. ```bash > netdata help help +publish register show steeringdata +unpublish +Done +``` + +### publish + +The Network Data Publisher provides mechanisms to limit the number of similar Service and/or Prefix (on-mesh prefix or external route) entries in the Thread Network Data by monitoring the Network Data and managing if or when to add or remove entries. + +The Publisher requires `OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE`. + +### publish dnssrp + +Publish DNS/SRP service entry. + +This command requires `OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE`. + +The following formats are available: : + +- `netdata publish dnssrp anycast ` to publish "DNS/SRP Service Anycast Address" with a given sequence number. +- `netdata publish dnssrp unicast
` to publish "DNS/SRP Service Unicast Address" with given address and port number info. The address/port info is included in Service TLV data. +- `netdata publish dnssrp unicast ` to publish "DNS/SRP Service Unicast Address" with given port number and the device's mesh-local EID for the address. The address and port info is included in Server TLV data. + +A new call to `netdata publish dnssrp [anycast|unicast] [...]` command will remove and replace any previous "DNS/SRP Service" entry that was being published (from earlier `netdata publish dnssrp [...]` commands). + +```bash +> netdata publish dnssrp anycast 1 +Done + +> netdata publish dnssrp unicast fd00::1234 51525 +Done + +> netdata publish dnssrp unicast 50152 +Done +``` + +### publish prefix \ [padcrosnD][prf] + +Publish an on-mesh prefix entry. + +- p: Preferred flag +- a: Stateless IPv6 Address Autoconfiguration flag +- d: DHCPv6 IPv6 Address Configuration flag +- c: DHCPv6 Other Configuration flag +- r: Default Route flag +- o: On Mesh flag +- s: Stable flag +- n: Nd Dns flag +- D: Domain Prefix flag (only available for Thread 1.2). +- prf: Preference, which may be 'high', 'med', or 'low'. + +```bash +> netdata publish prefix fd00:1234:5678::/64 paos med +Done +``` + +### publish route \ [sn][prf] + +Publish an external route entry. + +- s: Stable flag +- n: NAT64 flag +- prf: Preference, which may be: 'high', 'med', or 'low'. + +```bash +> netdata publish route fd00:1234:5678::/64 s high Done ``` @@ -212,3 +280,29 @@ Done > netdata steeringdata check 0xdef/12 Error 23: NotFound ``` + +### unpublish + +This command unpublishes a previously published Network Data entry. + +This command requires `OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE`. + +### unpublish dnssrp + +Unpublishes DNS/SRP Service entry (available when `OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE` is enabled): + +- `netdata unpublish dnssrp` to unpublish "DNS/SRP Service" entry (anycast or unciast). + +```bash +> netdata unpublish dnssrp +Done +``` + +### unpublish \ + +Unpublishes a previously published on-mesh prefix or external route entry. + +```bash +> netdata unpublish fd00:1234:5678::/64 +Done +``` diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 51105ecad..ec3ca84b8 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -3302,29 +3302,28 @@ void Interpreter::HandleLinkPcapReceive(const otRadioFrame *aFrame, bool aIsTx) } #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE -otError Interpreter::ProcessPrefixAdd(Arg aArgs[]) +otError Interpreter::ParsePrefix(Arg aArgs[], otBorderRouterConfig &aConfig) { - otError error = OT_ERROR_NONE; - otBorderRouterConfig config; + otError error = OT_ERROR_NONE; - memset(&config, 0, sizeof(otBorderRouterConfig)); + memset(&aConfig, 0, sizeof(otBorderRouterConfig)); - SuccessOrExit(error = aArgs[0].ParseAsIp6Prefix(config.mPrefix)); + SuccessOrExit(error = aArgs[0].ParseAsIp6Prefix(aConfig.mPrefix)); aArgs++; for (; !aArgs->IsEmpty(); aArgs++) { if (*aArgs == "high") { - config.mPreference = OT_ROUTE_PREFERENCE_HIGH; + aConfig.mPreference = OT_ROUTE_PREFERENCE_HIGH; } else if (*aArgs == "med") { - config.mPreference = OT_ROUTE_PREFERENCE_MED; + aConfig.mPreference = OT_ROUTE_PREFERENCE_MED; } else if (*aArgs == "low") { - config.mPreference = OT_ROUTE_PREFERENCE_LOW; + aConfig.mPreference = OT_ROUTE_PREFERENCE_LOW; } else { @@ -3333,40 +3332,40 @@ otError Interpreter::ProcessPrefixAdd(Arg aArgs[]) switch (*arg) { case 'p': - config.mPreferred = true; + aConfig.mPreferred = true; break; case 'a': - config.mSlaac = true; + aConfig.mSlaac = true; break; case 'd': - config.mDhcp = true; + aConfig.mDhcp = true; break; case 'c': - config.mConfigure = true; + aConfig.mConfigure = true; break; case 'r': - config.mDefaultRoute = true; + aConfig.mDefaultRoute = true; break; case 'o': - config.mOnMesh = true; + aConfig.mOnMesh = true; break; case 's': - config.mStable = true; + aConfig.mStable = true; break; case 'n': - config.mNdDns = true; + aConfig.mNdDns = true; break; #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE case 'D': - config.mDp = true; + aConfig.mDp = true; break; #endif default: @@ -3376,6 +3375,16 @@ otError Interpreter::ProcessPrefixAdd(Arg aArgs[]) } } +exit: + return error; +} + +otError Interpreter::ProcessPrefixAdd(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + otBorderRouterConfig config; + + SuccessOrExit(error = ParsePrefix(aArgs, config)); error = otBorderRouterAddOnMeshPrefix(mInstance, &config); exit: @@ -3526,37 +3535,36 @@ otError Interpreter::ProcessRloc16(Arg aArgs[]) } #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE -otError Interpreter::ProcessRouteAdd(Arg aArgs[]) +otError Interpreter::ParseRoute(Arg aArgs[], otExternalRouteConfig &aConfig) { - otError error = OT_ERROR_NONE; - otExternalRouteConfig config; + otError error = OT_ERROR_NONE; - memset(&config, 0, sizeof(otExternalRouteConfig)); + memset(&aConfig, 0, sizeof(otExternalRouteConfig)); - SuccessOrExit(error = aArgs[0].ParseAsIp6Prefix(config.mPrefix)); + SuccessOrExit(error = aArgs[0].ParseAsIp6Prefix(aConfig.mPrefix)); aArgs++; for (; !aArgs->IsEmpty(); aArgs++) { if (*aArgs == "s") { - config.mStable = true; + aConfig.mStable = true; } else if (*aArgs == "n") { - config.mNat64 = true; + aConfig.mNat64 = true; } else if (*aArgs == "high") { - config.mPreference = OT_ROUTE_PREFERENCE_HIGH; + aConfig.mPreference = OT_ROUTE_PREFERENCE_HIGH; } else if (*aArgs == "med") { - config.mPreference = OT_ROUTE_PREFERENCE_MED; + aConfig.mPreference = OT_ROUTE_PREFERENCE_MED; } else if (*aArgs == "low") { - config.mPreference = OT_ROUTE_PREFERENCE_LOW; + aConfig.mPreference = OT_ROUTE_PREFERENCE_LOW; } else { @@ -3564,6 +3572,16 @@ otError Interpreter::ProcessRouteAdd(Arg aArgs[]) } } +exit: + return error; +} + +otError Interpreter::ProcessRouteAdd(Arg aArgs[]) +{ + otError error; + otExternalRouteConfig config; + + SuccessOrExit(error = ParseRoute(aArgs, config)); error = otBorderRouterAddRoute(mInstance, &config); exit: diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 47fe7e006..c74c9e67e 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -412,6 +412,10 @@ private: otError ParsePingInterval(const Arg &aArg, uint32_t &aInterval); #endif static otError ParseJoinerDiscerner(Arg &aArg, otJoinerDiscerner &aDiscerner); +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + static otError ParsePrefix(Arg aArgs[], otBorderRouterConfig &aConfig); + static otError ParseRoute(Arg aArgs[], otExternalRouteConfig &aConfig); +#endif otError ProcessUserCommands(Arg aArgs[]); otError ProcessHelp(Arg aArgs[]); diff --git a/src/cli/cli_network_data.cpp b/src/cli/cli_network_data.cpp index 2aae3c6f1..d6e2eb5a8 100644 --- a/src/cli/cli_network_data.cpp +++ b/src/cli/cli_network_data.cpp @@ -34,6 +34,7 @@ #include "cli_network_data.hpp" #include +#include #include #include "cli/cli.hpp" @@ -215,6 +216,100 @@ otError NetworkData::ProcessHelp(Arg aArgs[]) return OT_ERROR_NONE; } +#if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE +otError NetworkData::ProcessPublish(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + if (aArgs[0] == "dnssrp") + { + if (aArgs[1] == "anycast") + { + uint8_t sequenceNumber; + + SuccessOrExit(error = aArgs[2].ParseAsUint8(sequenceNumber)); + otNetDataPublishDnsSrpServiceAnycast(mInterpreter.mInstance, sequenceNumber); + ExitNow(); + } + + if (aArgs[1] == "unicast") + { + otIp6Address address; + uint16_t port; + + if (aArgs[3].IsEmpty()) + { + SuccessOrExit(error = aArgs[2].ParseAsUint16(port)); + otNetDataPublishDnsSrpServiceUnicastMeshLocalEid(mInterpreter.mInstance, port); + ExitNow(); + } + + SuccessOrExit(error = aArgs[2].ParseAsIp6Address(address)); + SuccessOrExit(error = aArgs[3].ParseAsUint16(port)); + otNetDataPublishDnsSrpServiceUnicast(mInterpreter.mInstance, &address, port); + ExitNow(); + } + } +#endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + if (aArgs[0] == "prefix") + { + otBorderRouterConfig config; + + SuccessOrExit(error = Interpreter::ParsePrefix(aArgs + 1, config)); + error = otNetDataPublishOnMeshPrefix(mInterpreter.mInstance, &config); + ExitNow(); + } + + if (aArgs[0] == "route") + { + otExternalRouteConfig config; + + SuccessOrExit(error = Interpreter::ParseRoute(aArgs + 1, config)); + error = otNetDataPublishExternalRoute(mInterpreter.mInstance, &config); + ExitNow(); + } +#endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + + error = OT_ERROR_INVALID_ARGS; + +exit: + return error; +} + +otError NetworkData::ProcessUnpublish(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + if (aArgs[0] == "dnssrp") + { + otNetDataUnpublishDnsSrpService(mInterpreter.mInstance); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + { + otIp6Prefix prefix; + + if (aArgs[0].ParseAsIp6Prefix(prefix) == OT_ERROR_NONE) + { + error = otNetDataUnpublishPrefix(mInterpreter.mInstance, &prefix); + ExitNow(); + } + } +#endif + + error = OT_ERROR_INVALID_ARGS; + +exit: + return error; +} +#endif // OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE + #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE otError NetworkData::ProcessRegister(Arg aArgs[]) { diff --git a/src/cli/cli_network_data.hpp b/src/cli/cli_network_data.hpp index bb7903103..8c1927d9f 100644 --- a/src/cli/cli_network_data.hpp +++ b/src/cli/cli_network_data.hpp @@ -103,6 +103,10 @@ private: }; otError ProcessHelp(Arg aArgs[]); +#if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE + otError ProcessPublish(Arg aArgs[]); + otError ProcessUnpublish(Arg aArgs[]); +#endif #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE otError ProcessRegister(Arg aArgs[]); #endif @@ -118,11 +122,16 @@ private: static constexpr Command sCommands[] = { {"help", &NetworkData::ProcessHelp}, +#if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE + {"publish", &NetworkData::ProcessPublish}, +#endif #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE {"register", &NetworkData::ProcessRegister}, #endif - {"show", &NetworkData::ProcessShow}, - {"steeringdata", &NetworkData::ProcessSteeringData}, + {"show", &NetworkData::ProcessShow}, {"steeringdata", &NetworkData::ProcessSteeringData}, +#if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE + {"unpublish", &NetworkData::ProcessUnpublish}, +#endif }; static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted"); diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 8341b242c..e860d863f 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -327,6 +327,7 @@ openthread_core_files = [ "api/message_api.cpp", "api/multi_radio_api.cpp", "api/netdata_api.cpp", + "api/netdata_publisher_api.cpp", "api/netdiag_api.cpp", "api/network_time_api.cpp", "api/ping_sender_api.cpp", @@ -597,6 +598,8 @@ openthread_core_files = [ "thread/network_data_local.hpp", "thread/network_data_notifier.cpp", "thread/network_data_notifier.hpp", + "thread/network_data_publisher.cpp", + "thread/network_data_publisher.hpp", "thread/network_data_service.cpp", "thread/network_data_service.hpp", "thread/network_data_tlvs.cpp", @@ -714,6 +717,7 @@ source_set("libopenthread_core_config") { "config/logging.h", "config/mac.h", "config/mle.h", + "config/netdata_publisher.h", "config/openthread-core-config-check.h", "config/openthread-core-default-config.h", "config/parent_search.h", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f0b8440ec..5655f909a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -63,6 +63,7 @@ set(COMMON_SOURCES api/message_api.cpp api/multi_radio_api.cpp api/netdata_api.cpp + api/netdata_publisher_api.cpp api/netdiag_api.cpp api/network_time_api.cpp api/ping_sender_api.cpp @@ -195,6 +196,7 @@ set(COMMON_SOURCES thread/network_data_leader_ftd.cpp thread/network_data_local.cpp thread/network_data_notifier.cpp + thread/network_data_publisher.cpp thread/network_data_service.cpp thread/network_data_tlvs.cpp thread/network_data_types.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 48e757755..2d7a3eac2 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -140,6 +140,7 @@ SOURCES_COMMON = \ api/message_api.cpp \ api/multi_radio_api.cpp \ api/netdata_api.cpp \ + api/netdata_publisher_api.cpp \ api/netdiag_api.cpp \ api/network_time_api.cpp \ api/ping_sender_api.cpp \ @@ -272,6 +273,7 @@ SOURCES_COMMON = \ thread/network_data_leader_ftd.cpp \ thread/network_data_local.cpp \ thread/network_data_notifier.cpp \ + thread/network_data_publisher.cpp \ thread/network_data_service.cpp \ thread/network_data_tlvs.cpp \ thread/network_data_types.cpp \ @@ -435,6 +437,7 @@ HEADERS_COMMON = \ config/logging.h \ config/mac.h \ config/mle.h \ + config/netdata_publisher.h \ config/openthread-core-config-check.h \ config/openthread-core-default-config.h \ config/parent_search.h \ @@ -538,6 +541,7 @@ HEADERS_COMMON = \ thread/network_data_leader_ftd.hpp \ thread/network_data_local.hpp \ thread/network_data_notifier.hpp \ + thread/network_data_publisher.hpp \ thread/network_data_service.hpp \ thread/network_data_tlvs.hpp \ thread/network_data_types.hpp \ diff --git a/src/core/api/netdata_publisher_api.cpp b/src/core/api/netdata_publisher_api.cpp new file mode 100644 index 000000000..53be31f80 --- /dev/null +++ b/src/core/api/netdata_publisher_api.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2021, 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. + */ + +/** + * @file + * This file implements the OpenThread Network Data Publisher API. + */ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE + +#include + +#include "common/instance.hpp" +#include "common/locator_getters.hpp" + +using namespace ot; + +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + +void otNetDataPublishDnsSrpServiceAnycast(otInstance *aInstance, uint8_t aSequenceNumber) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().PublishDnsSrpServiceAnycast(aSequenceNumber); +} + +void otNetDataPublishDnsSrpServiceUnicast(otInstance *aInstance, const otIp6Address *aAddress, uint16_t aPort) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().PublishDnsSrpServiceUnicast(*static_cast(aAddress), + aPort); +} + +void otNetDataPublishDnsSrpServiceUnicastMeshLocalEid(otInstance *aInstance, uint16_t aPort) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().PublishDnsSrpServiceUnicast(aPort); +} + +bool otNetDataIsDnsSrpServiceAdded(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().IsDnsSrpServiceAdded(); +} + +void otNetDataSetDnsSrpServicePublisherCallback(otInstance * aInstance, + otNetDataDnsSrpServicePublisherCallback aCallback, + void * aContext) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().SetDnsSrpServiceCallback(aCallback, aContext); +} + +void otNetDataUnpublishDnsSrpService(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().UnpublishDnsSrpService(); +} + +#endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + +otError otNetDataPublishOnMeshPrefix(otInstance *aInstance, const otBorderRouterConfig *aConfig) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().PublishOnMeshPrefix( + *static_cast(aConfig)); +} + +otError otNetDataPublishExternalRoute(otInstance *aInstance, const otExternalRouteConfig *aConfig) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().PublishExternalRoute( + *static_cast(aConfig)); +} + +bool otNetDataIsPrefixAdded(otInstance *aInstance, const otIp6Prefix *aPrefix) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().IsPrefixAdded(*static_cast(aPrefix)); +} + +void otNetDataSetPrefixPublisherCallback(otInstance * aInstance, + otNetDataPrefixPublisherCallback aCallback, + void * aContext) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().SetPrefixCallback(aCallback, aContext); +} + +otError otNetDataUnpublishPrefix(otInstance *aInstance, const otIp6Prefix *aPrefix) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().UnpublishPrefix(*static_cast(aPrefix)); +} + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + +#endif // OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE diff --git a/src/core/backbone_router/bbr_local.cpp b/src/core/backbone_router/bbr_local.cpp index 9d5a0e90b..b21f7c93b 100644 --- a/src/core/backbone_router/bbr_local.cpp +++ b/src/core/backbone_router/bbr_local.cpp @@ -187,6 +187,8 @@ Error Local::AddService(bool aForce) serverData.SetMlrTimeout(mMlrTimeout); SuccessOrExit(error = Get().Add(serverData)); + Get().HandleServerDataUpdated(); + mIsServiceAdded = true; exit: @@ -199,6 +201,7 @@ void Local::RemoveService(void) Error error; SuccessOrExit(error = Get().Remove()); + Get().HandleServerDataUpdated(); mIsServiceAdded = false; exit: diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index c14323059..e97d5af1f 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -640,6 +640,13 @@ template <> inline NetworkData::Notifier &Instance::Get(void) } #endif +#if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE +template <> inline NetworkData::Publisher &Instance::Get(void) +{ + return mThreadNetif.mNetworkDataPublisher; +} +#endif + template <> inline NetworkData::Service::Manager &Instance::Get(void) { return mThreadNetif.mNetworkDataServiceManager; diff --git a/src/core/common/notifier.cpp b/src/core/common/notifier.cpp index 406283828..f28114537 100644 --- a/src/core/common/notifier.cpp +++ b/src/core/common/notifier.cpp @@ -183,12 +183,15 @@ void Notifier::EmitEvents(void) #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE Get().HandleNotifierEvents(events); #endif -#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE - Get().HandleNotifierEvents(events); -#endif #if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE Get().HandleNotifierEvents(events); #endif +#if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE + // The `NetworkData::Publisher` is notified last (e.g., after SRP + // client) to allow other modules to request changes to what is + // being published (if needed). + Get().HandleNotifierEvents(events); +#endif for (ExternalCallback &callback : mExternalCallbacks) { diff --git a/src/core/config/netdata_publisher.h b/src/core/config/netdata_publisher.h new file mode 100644 index 000000000..7f9fe0708 --- /dev/null +++ b/src/core/config/netdata_publisher.h @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2021, 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. + */ + +/** + * @file + * This file includes compile-time configurations for Network Data Publisher. + * + */ + +#ifndef CONFIG_NETDATA_PUBLISHER_H_ +#define CONFIG_NETDATA_PUBLISHER_H_ + +#include "config/srp_server.h" + +/** + * @def OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE + * + * Define to 1 to enable Network Data Publisher. + * + * Network Data Publisher provides mechanisms to limit the number of similar Service and/or Prefix (on-mesh prefix + * or external route) entries in the Thread Network Data by monitoring the Network Data and managing if or when to add + * or remove entries. + * + */ +#ifndef OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE +#define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE OPENTHREAD_CONFIG_SRP_SERVER_ENABLE +#endif + +/** + * @def OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_DELAY_TO_ADD + * + * Specifies the maximum value (in milliseconds) of the random delay used before adding an entry in the Thread Network + * Data. + * + */ +#ifndef OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_DELAY_TO_ADD +#define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_DELAY_TO_ADD 3500 +#endif + +/** + * @def OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_DELAY_TO_REMOVE + * + * Specifies the maximum value (in milliseconds) of the random wait time used before removing an entry from the Thread + * Network Data. + * + */ +#ifndef OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_DELAY_TO_REMOVE +#define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_DELAY_TO_REMOVE 15000 +#endif + +/** + * @def OPENTHREAD_CONFIG_NETDATA_PUBLISHER_EXTRA_DELAY_TIME_TO_REMOVE_PREFERRED + * + * Specifies the extra wait time (in milliseconds) used when removing a preferred entry. + * + * When removing entries, certain entries are preferred over others. How the preference is determined depends on the + * entry type (e.g., (for DNS/SRP service entries associated with smaller RLOC16 or smaller IPv6 address are preferred). + * If the `Publisher` determines that its own entry is a preferred one, it adds the above extra wait time in addition + * to the random wait selected based on `OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_DELAY_TO_REMOVE` before removing + * its entry. This gives higher chance for a non-preferred entry from another device to be removed before removing a + * preferred entry which helps towards quicker convergence of the process to the desired number of entries. + * + * It is recommended that `OPENTHREAD_CONFIG_NETDATA_PUBLISHER_EXTRA_DELAY_TIME_TO_REMOVE_PREFERRED` to be set to a + * value larger than `OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_DELAY_TO_REMOVE` to ensure that non-preferred entries + * are removed first. + * + */ +#ifndef OPENTHREAD_CONFIG_NETDATA_PUBLISHER_EXTRA_DELAY_TIME_TO_REMOVE_PREFERRED +#define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_EXTRA_DELAY_TIME_TO_REMOVE_PREFERRED 16000 +#endif + +/** + * @def OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_ANYCAST_DNS_SRP_SERVICE_ENTRIES + * + * Specifies the desired number of "DNS/SRP Service Anycast Address" entries in the Thread Network Data. + * + * Publisher attempts to limit the number of such entries to this value. + * + */ +#ifndef OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_ANYCAST_DNS_SRP_SERVICE_ENTRIES +#define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_ANYCAST_DNS_SRP_SERVICE_ENTRIES 8 +#endif + +/** + * @def OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_UNICAST_DNS_SRP_SERVICE_ENTRIES + * + * Specifies the desired number of "DNS/SRP Service Anycast Address" entries in the Thread Network Data. + * + * Publisher attempts to limit the number of such entries to this value. + * + */ +#ifndef OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_UNICAST_DNS_SRP_SERVICE_ENTRIES +#define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_UNICAST_DNS_SRP_SERVICE_ENTRIES 2 +#endif + +/** + * @def OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_ON_MESH_PREFIX_ENTRIES + * + * Specifies the desired number of matching On-mesh Prefix entries in the Thread Network Data. + * + * Publisher attempts to limit the number of matching entries to this value. + * + */ +#ifndef OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_ON_MESH_PREFIX_ENTRIES +#define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_ON_MESH_PREFIX_ENTRIES 3 +#endif + +/** + * @def OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_EXTERNAL_ROUTE_ENTRIES + * + * Specifies the desired number of matching External Route entries in the Thread Network Data. + * + * Publisher attempts to limit the number of matching entries to this value. + * + */ +#ifndef OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_EXTERNAL_ROUTE_ENTRIES +#define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_EXTERNAL_ROUTE_ENTRIES 10 +#endif + +/** + * @def OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_PREFIX_ENTRIES + * + * Specifies maximum number of prefix (on-mesh prefix or external route) entries supported by Publisher. + * + */ +#ifndef OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_PREFIX_ENTRIES +#define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_PREFIX_ENTRIES 3 +#endif + +#endif // CONFIG_NETDATA_PUBLISHER_H_ diff --git a/src/core/net/srp_server.cpp b/src/core/net/srp_server.cpp index 8f7af84c0..b0b1b2007 100644 --- a/src/core/net/srp_server.cpp +++ b/src/core/net/srp_server.cpp @@ -41,7 +41,6 @@ #include "common/new.hpp" #include "common/random.hpp" #include "net/dns_types.hpp" -#include "thread/network_data_service.hpp" #include "thread/thread_netif.hpp" namespace ot { @@ -87,7 +86,8 @@ Server::Server(Instance &aInstance) , mLeaseTimer(aInstance, HandleLeaseTimer) , mOutstandingUpdatesTimer(aInstance, HandleOutstandingUpdatesTimer) , mServiceUpdateId(Random::NonCrypto::GetUint32()) - , mEnabled(false) + , mPort(kUdpPortMin) + , mState(kStateDisabled) , mHasRegisteredAnyService(false) { IgnoreError(SetDomain(kDefaultDomain)); @@ -101,17 +101,27 @@ void Server::SetServiceHandler(otSrpServerServiceUpdateHandler aServiceHandler, void Server::SetEnabled(bool aEnabled) { - VerifyOrExit(mEnabled != aEnabled); - - mEnabled = aEnabled; - - if (!mEnabled) + if (aEnabled) { - Stop(); + VerifyOrExit(mState == kStateDisabled); + mState = kStateStopped; + + // Select a port and then publish "DNS/SRP Unicast Address + // Service" in Thread Network Data using the device's + // mesh-local EID as the address. Then wait for callback + // `HandleNetDataPublisherEntryChange()` from to `Publisher` to + // start server operation when entry is published (i.e., added + // to the Network Data). + + SelectPort(); + Get().PublishDnsSrpServiceUnicast(mPort); } - else if (Get().IsAttached()) + else { - Start(); + VerifyOrExit(mState != kStateDisabled); + Get().UnpublishDnsSrpService(); + Stop(); + mState = kStateDisabled; } exit: @@ -174,7 +184,7 @@ Error Server::SetDomain(const char *aDomain) Error error = kErrorNone; uint16_t length; - VerifyOrExit(!mEnabled, error = kErrorInvalidState); + VerifyOrExit(mState == kStateDisabled, error = kErrorInvalidState); length = StringLength(aDomain, Dns::Name::kMaxNameSize); VerifyOrExit((length > 0) && (length < Dns::Name::kMaxNameSize), error = kErrorInvalidArgs); @@ -407,12 +417,9 @@ exit: } } -void Server::Start(void) +void Server::SelectPort(void) { - Error error = kErrorNone; - uint16_t port = kUdpPortMin; - - VerifyOrExit(!IsRunning()); + mPort = kUdpPortMin; #if OPENTHREAD_CONFIG_SRP_SERVER_PORT_SWITCH_ENABLE { @@ -420,35 +427,44 @@ void Server::Start(void) if (Get().Read(info) == kErrorNone) { - port = info.GetPort() + 1; - if (port < kUdpPortMin || port > kUdpPortMax) + mPort = info.GetPort() + 1; + if (mPort < kUdpPortMin || mPort > kUdpPortMax) { - port = kUdpPortMin; + mPort = kUdpPortMin; } } } #endif - SuccessOrExit(error = mSocket.Open(HandleUdpReceive, this)); - SuccessOrExit(error = mSocket.Bind(port, OT_NETIF_THREAD)); - SuccessOrExit(error = PublishServerData()); + otLogInfoSrp("[server] selected port %u", mPort); +} - otLogInfoSrp("[server] start listening on port %hu", mSocket.GetSockName().mPort); +void Server::Start(void) +{ + Error error = kErrorNone; + + VerifyOrExit(mState == kStateStopped); + + mState = kStateRunning; + + SuccessOrExit(error = mSocket.Open(HandleUdpReceive, this)); + SuccessOrExit(error = mSocket.Bind(mPort, OT_NETIF_THREAD)); + + otLogInfoSrp("[server] start listening on port %u", mPort); exit: if (error != kErrorNone) { otLogCritSrp("[server] failed to start: %s", ErrorToString(error)); - // Cleanup any resources we may have allocated. Stop(); } } void Server::Stop(void) { - VerifyOrExit(IsRunning()); + VerifyOrExit(mState == kStateRunning); - UnpublishServerData(); + mState = kStateStopped; while (!mHosts.IsEmpty()) { @@ -465,7 +481,7 @@ void Server::Stop(void) mLeaseTimer.Stop(); mOutstandingUpdatesTimer.Stop(); - otLogInfoSrp("[server] stop listening on %hu", mSocket.GetSockName().mPort); + otLogInfoSrp("[server] stop listening on %u", mPort); IgnoreError(mSocket.Close()); mHasRegisteredAnyService = false; @@ -473,41 +489,17 @@ exit: return; } -void Server::HandleNotifierEvents(Events aEvents) +void Server::HandleNetDataPublisherEvent(NetworkData::Publisher::Event aEvent) { - VerifyOrExit(mEnabled); - VerifyOrExit(aEvents.Contains(kEventThreadRoleChanged)); - - if (Get().IsAttached()) + switch (aEvent) { + case NetworkData::Publisher::kEventEntryAdded: Start(); - } - else - { + break; + + case NetworkData::Publisher::kEventEntryRemoved: Stop(); - } - -exit: - return; -} - -Error Server::PublishServerData(void) -{ - NetworkData::Service::DnsSrpUnicast::ServerData serverData(Get().GetMeshLocal64(), - mSocket.GetSockName().GetPort()); - - OT_ASSERT(mSocket.IsBound()); - - return Get().Add(serverData); -} - -void Server::UnpublishServerData(void) -{ - Error error = Get().Remove(); - - if (error != kErrorNone) - { - otLogWarnSrp("[server] failed to unpublish SRP service: %s", ErrorToString(error)); + break; } } diff --git a/src/core/net/srp_server.hpp b/src/core/net/srp_server.hpp index 33b2be0a1..c056c92b4 100644 --- a/src/core/net/srp_server.hpp +++ b/src/core/net/srp_server.hpp @@ -42,6 +42,10 @@ #error "OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE is required for OPENTHREAD_CONFIG_SRP_SERVER_ENABLE" #endif +#if !OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE +#error "OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE is required for OPENTHREAD_CONFIG_SRP_SERVER_ENABLE" +#endif + #if !OPENTHREAD_CONFIG_ECDSA_ENABLE #error "OPENTHREAD_CONFIG_ECDSA_ENABLE is required for OPENTHREAD_CONFIG_SRP_SERVER_ENABLE" #endif @@ -61,6 +65,7 @@ #include "net/ip6.hpp" #include "net/ip6_address.hpp" #include "net/udp6.hpp" +#include "thread/network_data_publisher.hpp" namespace ot { namespace Srp { @@ -71,7 +76,7 @@ namespace Srp { */ class Server : public InstanceLocator, private NonCopyable { - friend class ot::Notifier; + friend class NetworkData::Publisher; friend class UpdateMetadata; friend class Service; friend class Host; @@ -593,7 +598,7 @@ public: * @returns A boolean that indicates whether the server is running. * */ - bool IsRunning(void) const { return mSocket.IsBound(); } + bool IsRunning(void) const { return (mState == kStateRunning); } /** * This method enables/disables the SRP server. @@ -655,6 +660,13 @@ private: static constexpr uint32_t kDefaultMaxKeyLease = 3600u * 24 * 14; // 14 days (in seconds). static constexpr uint32_t kDefaultEventsHandlerTimeout = OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_UPDATE_TIMEOUT; + enum State : uint8_t + { + kStateDisabled, + kStateRunning, + kStateStopped, + }; + // This class includes metadata for processing a SRP update (register, deregister) // and sending DNS response to the client. class UpdateMetadata : public InstanceLocator, public LinkedListEntry @@ -688,11 +700,10 @@ private: UpdateMetadata * mNext; }; - void Start(void); - void Stop(void); - void HandleNotifierEvents(Events aEvents); - Error PublishServerData(void); - void UnpublishServerData(void); + void Start(void); + void Stop(void); + void SelectPort(void); + void HandleNetDataPublisherEvent(NetworkData::Publisher::Event aEvent); ServiceUpdateId AllocateId(void) { return mServiceUpdateId++; } @@ -778,7 +789,8 @@ private: LinkedList mOutstandingUpdates; ServiceUpdateId mServiceUpdateId; - bool mEnabled : 1; + uint16_t mPort; + State mState; bool mHasRegisteredAnyService : 1; }; diff --git a/src/core/openthread-core-config.h b/src/core/openthread-core-config.h index fdde86941..423b35a82 100644 --- a/src/core/openthread-core-config.h +++ b/src/core/openthread-core-config.h @@ -78,6 +78,7 @@ #include "config/logging.h" #include "config/mac.h" #include "config/mle.h" +#include "config/netdata_publisher.h" #include "config/parent_search.h" #include "config/ping_sender.h" #include "config/platform.h" diff --git a/src/core/thread/network_data.hpp b/src/core/thread/network_data.hpp index 2234cbcb7..16fc346c8 100644 --- a/src/core/thread/network_data.hpp +++ b/src/core/thread/network_data.hpp @@ -106,6 +106,7 @@ constexpr Iterator kIteratorInit = OT_NETWORK_DATA_ITERATOR_INIT; ///< Initializ class NetworkData : public InstanceLocator { friend class Service::Manager; + friend class Publisher; public: static constexpr uint8_t kMaxSize = 254; ///< Maximum size of Thread Network Data in bytes. diff --git a/src/core/thread/network_data_publisher.cpp b/src/core/thread/network_data_publisher.cpp new file mode 100644 index 000000000..040e9bcca --- /dev/null +++ b/src/core/thread/network_data_publisher.cpp @@ -0,0 +1,1089 @@ +/* + * Copyright (c) 2021, 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. + */ + +/** + * @file + * This file implements the Network Data Publisher. + * + */ + +#include "network_data_publisher.hpp" + +#if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE + +#include "common/code_utils.hpp" +#include "common/instance.hpp" +#include "common/locator_getters.hpp" +#include "common/random.hpp" +#include "thread/network_data_local.hpp" +#include "thread/network_data_service.hpp" + +namespace ot { +namespace NetworkData { + +//--------------------------------------------------------------------------------------------------------------------- +// Publisher + +Publisher::Publisher(Instance &aInstance) + : InstanceLocator(aInstance) +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + , mDnsSrpServiceEntry(aInstance) +#endif +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + , mPrefixCallback(nullptr) + , mPrefixCallbackContext(nullptr) +#endif + , mTimer(aInstance, Publisher::HandleTimer) +{ +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + // Since the `PrefixEntry` type is used in an array, + // we cannot use a constructor with an argument (e.g., + // we cannot use `InstacneLocator`) so we use + // `IntanceLocatorInit` and `Init()` the entries one + // by one. + + for (PrefixEntry &entry : mPrefixEntries) + { + entry.Init(aInstance); + } +#endif +} + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + +void Publisher::SetPrefixCallback(PrefixCallback aCallback, void *aContext) +{ + mPrefixCallback = aCallback; + mPrefixCallbackContext = aContext; +} + +Error Publisher::PublishOnMeshPrefix(const OnMeshPrefixConfig &aConfig) +{ + Error error; + PrefixEntry *entry; + + VerifyOrExit(aConfig.IsValid(GetInstance()), error = kErrorInvalidArgs); + VerifyOrExit(aConfig.mStable, error = kErrorInvalidArgs); + + SuccessOrExit(error = AllocatePrefixEntry(aConfig.GetPrefix(), entry)); + entry->Publish(aConfig); + +exit: + return error; +} + +Error Publisher::PublishExternalRoute(const ExternalRouteConfig &aConfig) +{ + Error error; + PrefixEntry *entry; + + VerifyOrExit(aConfig.IsValid(GetInstance()), error = kErrorInvalidArgs); + VerifyOrExit(aConfig.mStable, error = kErrorInvalidArgs); + + SuccessOrExit(error = AllocatePrefixEntry(aConfig.GetPrefix(), entry)); + entry->Publish(aConfig); + +exit: + return error; +} + +bool Publisher::IsPrefixAdded(const Ip6::Prefix &aPrefix) const +{ + bool isAdded = false; + const PrefixEntry *entry; + + entry = FindMatchingPrefixEntry(aPrefix); + VerifyOrExit(entry != nullptr); + + isAdded = entry->IsAdded(); + +exit: + return isAdded; +} + +Error Publisher::UnpublishPrefix(const Ip6::Prefix &aPrefix) +{ + Error error = kErrorNone; + PrefixEntry *entry; + + entry = FindMatchingPrefixEntry(aPrefix); + VerifyOrExit(entry != nullptr, error = kErrorNotFound); + + entry->Unpublish(); + +exit: + return error; +} + +Error Publisher::AllocatePrefixEntry(const Ip6::Prefix &aPrefix, PrefixEntry *&aEntry) +{ + Error error = kErrorNoBufs; + + VerifyOrExit(FindMatchingPrefixEntry(aPrefix) == nullptr, error = kErrorAlready); + + for (PrefixEntry &entry : mPrefixEntries) + { + if (!entry.IsInUse()) + { + aEntry = &entry; + ExitNow(error = kErrorNone); + } + } + +exit: + return error; +} + +Publisher::PrefixEntry *Publisher::FindMatchingPrefixEntry(const Ip6::Prefix &aPrefix) +{ + return const_cast(const_cast(this)->FindMatchingPrefixEntry(aPrefix)); +} + +const Publisher::PrefixEntry *Publisher::FindMatchingPrefixEntry(const Ip6::Prefix &aPrefix) const +{ + const PrefixEntry *prefixEntry = nullptr; + + for (const PrefixEntry &entry : mPrefixEntries) + { + if (entry.IsInUse() && entry.Matches(aPrefix)) + { + prefixEntry = &entry; + break; + } + } + + return prefixEntry; +} + +bool Publisher::IsAPrefixEntry(const Entry &aEntry) const +{ + return (&mPrefixEntries[0] <= &aEntry) && (&aEntry < OT_ARRAY_END(mPrefixEntries)); +} + +void Publisher::NotifyPrefixEntryChange(Event aEvent, const Ip6::Prefix &aPrefix) const +{ + if (mPrefixCallback != nullptr) + { + mPrefixCallback(static_cast(aEvent), &aPrefix, mPrefixCallbackContext); + } +} + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + +void Publisher::HandleNotifierEvents(Events aEvents) +{ + OT_UNUSED_VARIABLE(aEvents); + + bool registerWithLeader = false; + +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + if (mDnsSrpServiceEntry.HandleNotifierEvents(aEvents)) + { + registerWithLeader = true; + } +#endif + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + for (PrefixEntry &entry : mPrefixEntries) + { + entry.HandleNotifierEvents(aEvents); + } +#endif + + if (registerWithLeader) + { + Get().HandleServerDataUpdated(); + } +} + +void Publisher::HandleTimer(Timer &aTimer) +{ + aTimer.Get().HandleTimer(); +} + +void Publisher::HandleTimer(void) +{ + bool registerWithLeader = false; + +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + if (mDnsSrpServiceEntry.HandleTimer()) + { + registerWithLeader = true; + } +#endif + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + for (PrefixEntry &entry : mPrefixEntries) + { + if (entry.HandleTimer()) + { + registerWithLeader = true; + } + } +#endif + + if (registerWithLeader) + { + Get().HandleServerDataUpdated(); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +// Publisher::Entry + +void Publisher::Entry::SetState(State aState) +{ + VerifyOrExit(mState != aState); + + otLogInfoNetData("Publisher: %s - State: %s -> %s", ToString(/* aIncludeState */ false).AsCString(), + StateToString(mState), StateToString(aState)); + mState = aState; + +exit: + return; +} + +bool Publisher::Entry::IsPreferred(uint16_t aRloc16) const +{ + // Indicates whether or not an entry from `aRloc16` is preferred + // over our entry (based on our RLOC). We prefer an entry from a + // router over an entry from an end-device (e.g., a REED). If both + // are the same type, then the one with smaller RLOC16 is preferred. + + bool isOtherRouter = Mle::Mle::IsActiveRouter(aRloc16); + + return (Get().IsRouterOrLeader() == isOtherRouter) ? (aRloc16 < Get().GetRloc16()) + : isOtherRouter; +} + +void Publisher::Entry::UpdateState(uint8_t aNumEntries, uint8_t aNumPreferredEntries, uint8_t aDesiredNumEntries) +{ + // This method uses the info about number existing entries (total + // and preferred) in Network Data along with the desired number of + // entries we aim to have in the Network Data to decide whether or + // not to take any action (add or remove our entry). + + otLogInfoNetData("Publisher: %s in netdata - total:%d, preferred:%d, desired:%d", ToString().AsCString(), + aNumEntries, aNumPreferredEntries, aDesiredNumEntries); + + switch (GetState()) + { + case kNoEntry: + break; + + case kToAdd: + // Our entry is ready to be added. If there are too few existing + // entries, we start adding our entry (start the timer with a + // random delay before adding the entry). + + if (aNumEntries < aDesiredNumEntries) + { + mUpdateTime = TimerMilli::GetNow() + Random::NonCrypto::GetUint32InRange(1, kMaxDelayToAdd); + SetState(kAdding); + Get().GetTimer().FireAtIfEarlier(mUpdateTime); + LogUpdateTime(); + } + break; + + case kAdding: + // Our entry is being added (waiting time before we add). If we + // now see that there are enough entries, we stop adding the + // entry. + + if (aNumEntries >= aDesiredNumEntries) + { + SetState(kToAdd); + } + break; + + case kAdded: + // Our entry is already added in the Network Data. If there are + // enough entries, do nothing and keep monitoring. If we see now + // that there are too many entries, we start removing our entry + // after a random delay time. If our entry itself is preferred + // over other entries (indicated by `aNumPreferredEntries < + // aDesiredNumEntries`) we add an extra delay before removing + // the entry. This gives higher chance for a non-preferred + // entry from another device to be removed before our entry. + + if (aNumEntries > aDesiredNumEntries) + { + mUpdateTime = TimerMilli::GetNow() + Random::NonCrypto::GetUint32InRange(1, kMaxDelayToRemove); + + if (aNumPreferredEntries < aDesiredNumEntries) + { + mUpdateTime += kExtraDelayToRemovePeferred; + } + + SetState(kRemoving); + Get().GetTimer().FireAtIfEarlier(mUpdateTime); + LogUpdateTime(); + } + break; + + case kRemoving: + // Our entry is being removed (wait time before remove). If we + // now see that there are enough or too few entries, we stop + // removing our entry. + + if (aNumEntries <= aDesiredNumEntries) + { + SetState(kAdded); + } + break; + } +} + +bool Publisher::Entry::HandleTimer(void) +{ + bool registerWithLeader = false; + + // Timer is used to delay adding/removing the entry. If we have + // reached `mUpdateTime` add or remove the entry. Otherwise, + // restart the timer (note that timer can be shared between + // different published entries). This method returns a `bool` + // indicating whether or not anything in local Network Data got + // changed so to notify the leader and register the changes. + + VerifyOrExit((GetState() == kAdding) || (GetState() == kRemoving)); + + if (mUpdateTime <= TimerMilli::GetNow()) + { + registerWithLeader = (GetState() == kAdding) ? Add() : Remove(/* aNextState */ kToAdd); + } + else + { + Get().GetTimer().FireAtIfEarlier(mUpdateTime); + } + +exit: + return registerWithLeader; +} + +bool Publisher::Entry::Add(void) +{ + bool registerWithLeader = false; + +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + if (Get().IsADnsSrpServiceEntry(*this)) + { + registerWithLeader = static_cast(this)->Add(); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + if (Get().IsAPrefixEntry(*this)) + { + registerWithLeader = static_cast(this)->Add(); + ExitNow(); + } +#endif + +exit: + return registerWithLeader; +} + +bool Publisher::Entry::Remove(State aNextState) +{ + bool registerWithLeader = false; + +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + if (Get().IsADnsSrpServiceEntry(*this)) + { + registerWithLeader = static_cast(this)->Remove(aNextState); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + if (Get().IsAPrefixEntry(*this)) + { + registerWithLeader = static_cast(this)->Remove(aNextState); + ExitNow(); + } +#endif + +exit: + return registerWithLeader; +} + +Publisher::Entry::InfoString Publisher::Entry::ToString(bool aIncludeState) const +{ + InfoString string; + +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + if (Get().IsADnsSrpServiceEntry(*this)) + { + string.Append("DNS/SRP service"); + ExitNow(); + } +#endif + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + if (Get().IsAPrefixEntry(*this)) + { + const PrefixEntry &prefixEntry = *static_cast(this); + + switch (prefixEntry.mType) + { + case PrefixEntry::kTypeOnMeshPrefix: + string.Append("OnMeshPrefix "); + break; + + case PrefixEntry::kTypeExternalRoute: + string.Append("ExternalRoute "); + break; + } + + string.Append(prefixEntry.mPrefix.ToString().AsCString()); + ExitNow(); + } +#endif + +exit: + if (aIncludeState) + { + string.Append(" (state:%s)", StateToString(GetState())); + } + + return string; +} + +void Publisher::Entry::LogUpdateTime(void) const +{ + otLogInfoNetData("Publisher: %s - update in %u msec", ToString().AsCString(), mUpdateTime - TimerMilli::GetNow()); +} + +const char *Publisher::Entry::StateToString(State aState) +{ + static const char *const kStateStrings[] = { + "NoEntry", // (0) kNoEntry + "ToAdd", // (1) kToAdd + "Adding", // (2) kAdding + "Added", // (3) kAdded + "Removing", // (4) kRemoving + }; + + static_assert(0 == kNoEntry, "kNoEntry value is not correct"); + static_assert(1 == kToAdd, "kToAdd value is not correct"); + static_assert(2 == kAdding, "kAdding value is not correct"); + static_assert(3 == kAdded, "kAdded value is not correct"); + static_assert(4 == kRemoving, "kRemoving value is not correct"); + + return kStateStrings[aState]; +} + +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + +//--------------------------------------------------------------------------------------------------------------------- +// Publisher::DnsSrpServiceEntry + +Publisher::DnsSrpServiceEntry::DnsSrpServiceEntry(Instance &aInstance) + : mCallback(nullptr) + , mCallbackContext(nullptr) +{ + Init(aInstance); +} + +void Publisher::DnsSrpServiceEntry::SetCallback(DnsSrpServiceCallback aCallback, void *aContext) +{ + mCallback = aCallback; + mCallbackContext = aContext; +} + +void Publisher::DnsSrpServiceEntry::PublishAnycast(uint8_t aSequenceNumber) +{ + otLogInfoNetData("Publisher: Publishing DNS/SRP service anycast (seq-num:%d)", aSequenceNumber); + Publish(Info::InfoAnycast(aSequenceNumber)); +} + +void Publisher::DnsSrpServiceEntry::PublishUnicast(const Ip6::Address &aAddress, uint16_t aPort) +{ + otLogInfoNetData("Publisher: Publishing DNS/SRP service unicast (%s, port:%d)", aAddress.ToString().AsCString(), + aPort); + Publish(Info::InfoUnicast(kTypeUnicast, aAddress, aPort)); +} + +void Publisher::DnsSrpServiceEntry::PublishUnicast(uint16_t aPort) +{ + otLogInfoNetData("Publisher: Publishing DNS/SRP service unicast (ml-eid, port:%d)", aPort); + Publish(Info::InfoUnicast(kTypeUnicastMeshLocalEid, Get().GetMeshLocal64(), aPort)); +} + +void Publisher::DnsSrpServiceEntry::Publish(const Info &aInfo) +{ + bool registerWithLeader = false; + + if (GetState() != kNoEntry) + { + if (aInfo == mInfo) + { + otLogInfoNetData("Publisher: %s is already being published", ToString().AsCString()); + ExitNow(); + } + + registerWithLeader = Remove(/* aNextState */ kNoEntry); + } + + mInfo = aInfo; + SetState(kToAdd); + + Process(); + + if (registerWithLeader) + { + Get().HandleServerDataUpdated(); + } + +exit: + return; +} + +void Publisher::DnsSrpServiceEntry::Unpublish(void) +{ + bool registerWithLeader; + + otLogInfoNetData("Publisher: Unpublishing DNS/SRP service"); + + registerWithLeader = Remove(/* aNextState */ kNoEntry); + + if (registerWithLeader) + { + Get().HandleServerDataUpdated(); + } +} + +bool Publisher::DnsSrpServiceEntry::HandleNotifierEvents(Events aEvents) +{ + bool registerWithLeader = false; + + if ((GetType() == kTypeUnicastMeshLocalEid) && aEvents.Contains(kEventThreadMeshLocalAddrChanged)) + { + mInfo.SetAddress(Get().GetMeshLocal64()); + + if (GetState() == kAdded) + { + // If the entry is already added, we need to update it + // so we remove it and add it back immediately with + // the new mesh-local address. + + Remove(/* aNextState */ kAdding); + Add(); + registerWithLeader = true; + } + } + + if (aEvents.ContainsAny(kEventThreadNetdataChanged | kEventThreadRoleChanged)) + { + Process(); + } + + return registerWithLeader; +} + +bool Publisher::DnsSrpServiceEntry::Add(void) +{ + // Adds the service entry to the network data. + + bool registerWithLeader = false; + + switch (GetType()) + { + case kTypeAnycast: + SuccessOrExit(Get().Add( + Service::DnsSrpAnycast::ServiceData(mInfo.GetSequenceNumber()))); + break; + + case kTypeUnicast: + SuccessOrExit(Get().Add( + Service::DnsSrpUnicast::ServiceData(mInfo.GetAddress(), mInfo.GetPort()))); + break; + + case kTypeUnicastMeshLocalEid: + SuccessOrExit(Get().Add( + Service::DnsSrpUnicast::ServerData(mInfo.GetAddress(), mInfo.GetPort()))); + break; + } + + registerWithLeader = true; + SetState(kAdded); + Notify(kEventEntryAdded); + +exit: + return registerWithLeader; +} + +bool Publisher::DnsSrpServiceEntry::Remove(State aNextState) +{ + // Removes the service entry from network data (if it was added). + + bool registerWithLeader = false; + + VerifyOrExit((GetState() == kAdded) || (GetState() == kRemoving)); + + switch (GetType()) + { + case kTypeAnycast: + SuccessOrExit(Get().Remove( + Service::DnsSrpAnycast::ServiceData(mInfo.GetSequenceNumber()))); + break; + + case kTypeUnicast: + SuccessOrExit(Get().Remove( + Service::DnsSrpUnicast::ServiceData(mInfo.GetAddress(), mInfo.GetPort()))); + break; + + case kTypeUnicastMeshLocalEid: + SuccessOrExit(Get().Remove()); + break; + } + + registerWithLeader = true; + Notify(kEventEntryRemoved); + +exit: + SetState(aNextState); + return registerWithLeader; +} + +void Publisher::DnsSrpServiceEntry::Notify(Event aEvent) const +{ +#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE + Get().HandleNetDataPublisherEvent(aEvent); +#endif + + if (mCallback != nullptr) + { + mCallback(static_cast(aEvent), mCallbackContext); + } +} + +void Publisher::DnsSrpServiceEntry::Process(void) +{ + // This method checks the entries currently present in Network Data + // based on which it then decides whether or not take action + // (add/remove or keep monitoring). + + uint8_t numEntries = 0; + uint8_t numPreferredEntries = 0; + uint8_t desiredNumEntries = 0; + + // Do not make any changes if device is not attached, and wait + // for role change event. + VerifyOrExit(Get().IsAttached()); + + VerifyOrExit(GetState() != kNoEntry); + + switch (GetType()) + { + case kTypeAnycast: + CountAnycastEntries(numEntries, numPreferredEntries); + desiredNumEntries = kDesiredNumAnycast; + break; + + case kTypeUnicast: + case kTypeUnicastMeshLocalEid: + CountUnicastEntries(numEntries, numPreferredEntries); + desiredNumEntries = kDesiredNumUnicast; + break; + } + + UpdateState(numEntries, numPreferredEntries, desiredNumEntries); + +exit: + return; +} + +void Publisher::DnsSrpServiceEntry::CountAnycastEntries(uint8_t &aNumEntries, uint8_t &aNumPreferredEntries) const +{ + // Count the number of matching "DNS/SRP Anycast" service entries + // in the Network Data (the match requires the entry to use same + // "sequence number" value). We prefer the entries associated with + // smaller RLCO16. + + Service::DnsSrpAnycast::ServiceData serviceData(mInfo.GetSequenceNumber()); + const ServiceTlv * serviceTlv = nullptr; + + while ((serviceTlv = Get().FindNextService( + serviceTlv, Service::kThreadEnterpriseNumber, reinterpret_cast(&serviceData), + serviceData.GetLength(), NetworkData::kServicePrefixMatch)) != nullptr) + { + TlvIterator subTlvIterator(*serviceTlv); + const ServerTlv *serverSubTlv; + + while ((serverSubTlv = subTlvIterator.Iterate()) != nullptr) + { + aNumEntries++; + + if (IsPreferred(serverSubTlv->GetServer16())) + { + aNumPreferredEntries++; + } + } + } +} + +void Publisher::DnsSrpServiceEntry::CountUnicastEntries(uint8_t &aNumEntries, uint8_t &aNumPreferredEntries) const +{ + // Count the number of "DNS/SRP Unicast" service entries in + // the Network Data. + + const ServiceTlv *serviceTlv = nullptr; + + while ((serviceTlv = Get().FindNextService( + serviceTlv, Service::kThreadEnterpriseNumber, &Service::DnsSrpUnicast::kServiceData, + sizeof(Service::DnsSrpUnicast::kServiceData), NetworkData::kServicePrefixMatch)) != nullptr) + { + TlvIterator subTlvIterator(*serviceTlv); + const ServerTlv *serverSubTlv; + + while (((serverSubTlv = subTlvIterator.Iterate())) != nullptr) + { + if (serviceTlv->GetServiceDataLength() >= sizeof(Service::DnsSrpUnicast::ServiceData)) + { + aNumEntries++; + + // Generally, we prefer entries where the SRP/DNS server + // address/port info is included in the service TLV data + // over the ones where the info is included in the + // server TLV data (i.e., we prefer infra-provided + // SRP/DNS entry over a BR local one using ML-EID). If + // our entry itself uses the service TLV data, then we + // prefer based on the associated RLOC16. + + if (GetType() == kTypeUnicast) + { + if (IsPreferred(serverSubTlv->GetServer16())) + { + aNumPreferredEntries++; + } + } + else + { + aNumPreferredEntries++; + } + } + + if (serverSubTlv->GetServerDataLength() >= sizeof(Service::DnsSrpUnicast::ServerData)) + { + aNumEntries++; + + // If our entry also uses the server TLV data (with + // ML-EID address), then the we prefer based on the + // associated RLOC16. + + if ((GetType() == kTypeUnicastMeshLocalEid) && IsPreferred(serverSubTlv->GetServer16())) + { + aNumPreferredEntries++; + } + } + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +// Publisher::DnsSrpServiceEntry::Info + +Publisher::DnsSrpServiceEntry::Info::Info(Type aType, uint16_t aPortOrSeqNumber, const Ip6::Address *aAddress) + : mPortOrSeqNumber(aPortOrSeqNumber) + , mType(aType) +{ + // It is important to `Clear()` the object since we compare all + // bytes using overload of operator `==`. + + Clear(); + + mType = aType; + mPortOrSeqNumber = aPortOrSeqNumber; + + if (aAddress != nullptr) + { + mAddress = *aAddress; + } +} + +#endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + +//--------------------------------------------------------------------------------------------------------------------- +// Publisher::PrefixEntry + +void Publisher::PrefixEntry::Publish(const OnMeshPrefixConfig &aConfig) +{ + otLogInfoNetData("Publisher: Publishing OnMeshPrefix %s", aConfig.GetPrefix().ToString().AsCString()); + + mType = kTypeOnMeshPrefix; + mPrefix = aConfig.GetPrefix(); + mFlags = aConfig.ConvertToTlvFlags(); + + SetState(kToAdd); + + Process(); +} + +void Publisher::PrefixEntry::Publish(const ExternalRouteConfig &aConfig) +{ + otLogInfoNetData("Publisher: Publishing ExternalRoute %s", aConfig.GetPrefix().ToString().AsCString()); + + mType = kTypeExternalRoute; + mPrefix = aConfig.GetPrefix(); + mFlags = aConfig.ConvertToTlvFlags(); + + SetState(kToAdd); + + Process(); +} + +void Publisher::PrefixEntry::Unpublish(void) +{ + bool registerWithLeader = false; + + otLogInfoNetData("Publisher: Unpublishing %s", mPrefix.ToString().AsCString()); + + registerWithLeader = Remove(/* aNextState */ kNoEntry); + + if (registerWithLeader) + { + Get().HandleServerDataUpdated(); + } +} + +void Publisher::PrefixEntry::HandleNotifierEvents(Events aEvents) +{ + if (aEvents.ContainsAny(kEventThreadNetdataChanged | kEventThreadRoleChanged)) + { + Process(); + } +} + +bool Publisher::PrefixEntry::Add(void) +{ + // Adds the prefix entry to the network data. + + bool registerWithLeader = false; + + switch (mType) + { + case kTypeOnMeshPrefix: + SuccessOrExit(AddOnMeshPrefix()); + break; + + case kTypeExternalRoute: + SuccessOrExit(AddExternalRoute()); + break; + } + + registerWithLeader = true; + SetState(kAdded); + Get().NotifyPrefixEntryChange(kEventEntryAdded, mPrefix); + +exit: + return registerWithLeader; +} + +Error Publisher::PrefixEntry::AddOnMeshPrefix(void) +{ + OnMeshPrefixConfig config; + + config.mPrefix = mPrefix; + config.mStable = true; + config.SetFromTlvFlags(mFlags); + + return Get().AddOnMeshPrefix(config); +} + +Error Publisher::PrefixEntry::AddExternalRoute(void) +{ + ExternalRouteConfig config; + + config.mPrefix = mPrefix; + config.mStable = true; + config.SetFromTlvFlags(static_cast(mFlags)); + + return Get().AddHasRoutePrefix(config); +} + +bool Publisher::PrefixEntry::Remove(State aNextState) +{ + // Remove the prefix entry from the network data. + + bool registerWithLeader = false; + + VerifyOrExit((GetState() == kAdded) || (GetState() == kRemoving)); + + switch (mType) + { + case kTypeOnMeshPrefix: + IgnoreError(Get().RemoveOnMeshPrefix(mPrefix)); + break; + + case kTypeExternalRoute: + IgnoreError(Get().RemoveHasRoutePrefix(mPrefix)); + break; + } + + registerWithLeader = true; + Get().NotifyPrefixEntryChange(kEventEntryRemoved, mPrefix); + +exit: + SetState(aNextState); + return registerWithLeader; +} + +void Publisher::PrefixEntry::Process(void) +{ + // This method checks the entries currently present in Network Data + // based on which it then decides whether or not take action + // (add/remove or keep monitoring). + + uint8_t numEntries = 0; + uint8_t numPreferredEntries = 0; + uint8_t desiredNumEntries = 0; + + // Do not make any changes if device is not attached, and wait + // for role change event. + VerifyOrExit(Get().IsAttached()); + + VerifyOrExit(GetState() != kNoEntry); + + switch (mType) + { + case kTypeOnMeshPrefix: + CountOnMeshPrefixEntries(numEntries, numPreferredEntries); + desiredNumEntries = kDesiredNumOnMeshPrefix; + break; + case kTypeExternalRoute: + CountExternalRouteEntries(numEntries, numPreferredEntries); + desiredNumEntries = kDesiredNumExternalRoute; + break; + } + + UpdateState(numEntries, numPreferredEntries, desiredNumEntries); + +exit: + return; +} + +void Publisher::PrefixEntry::CountOnMeshPrefixEntries(uint8_t &aNumEntries, uint8_t &aNumPreferredEntries) const +{ + const PrefixTlv * prefixTlv; + const BorderRouterTlv *brSubTlv; + int8_t preference = BorderRouterEntry::PreferenceFromFlags(mFlags); + uint16_t flagsWithoutPreference = BorderRouterEntry::FlagsWithoutPreference(mFlags); + + prefixTlv = Get().FindPrefix(mPrefix); + VerifyOrExit(prefixTlv != nullptr); + + brSubTlv = prefixTlv->FindSubTlv(/* aStable */ true); + VerifyOrExit(brSubTlv != nullptr); + + for (const BorderRouterEntry *entry = brSubTlv->GetFirstEntry(); entry <= brSubTlv->GetLastEntry(); + entry = entry->GetNext()) + { + uint16_t entryFlags = entry->GetFlags(); + int8_t entryPreference = BorderRouterEntry::PreferenceFromFlags(entryFlags); + + // Count an existing entry in the network data if its flags + // match ours and and its preference is same or higher than our + // preference. We do not count matching entries at a lower + // preference than ours. This ensures that a device with higher + // preference entry publishes its entry even when there are many + // lower preference similar entries in the network data + // (potentially causing a lower preference entry to be removed). + + if ((BorderRouterEntry::FlagsWithoutPreference(entryFlags) == flagsWithoutPreference) && + (entryPreference >= preference)) + { + aNumEntries++; + + // We prefer an entry if it has strictly higher preference + // than ours or if it has same preference we use the associated + // RLOC16. + + if ((entryPreference > preference) || IsPreferred(entry->GetRloc())) + { + aNumPreferredEntries++; + } + } + } + +exit: + return; +} + +void Publisher::PrefixEntry::CountExternalRouteEntries(uint8_t &aNumEntries, uint8_t &aNumPreferredEntries) const +{ + const PrefixTlv * prefixTlv; + const HasRouteTlv *hrSubTlv; + int8_t preference = HasRouteEntry::PreferenceFromFlags(static_cast(mFlags)); + uint8_t flagsWithoutPreference = HasRouteEntry::FlagsWithoutPreference(static_cast(mFlags)); + + prefixTlv = Get().FindPrefix(mPrefix); + VerifyOrExit(prefixTlv != nullptr); + + hrSubTlv = prefixTlv->FindSubTlv(/* aStable */ true); + VerifyOrExit(hrSubTlv != nullptr); + + for (const HasRouteEntry *entry = hrSubTlv->GetFirstEntry(); entry <= hrSubTlv->GetLastEntry(); + entry = entry->GetNext()) + { + uint8_t entryFlags = entry->GetFlags(); + int8_t entryPreference = HasRouteEntry::PreferenceFromFlags(entryFlags); + + // Count an existing entry in the network data if its flags + // match ours and and its preference is same or higher than our + // preference. We do not count matching entries at a lower + // preference than ours. This ensures that a device with higher + // preference entry publishes its entry even when there are many + // lower preference similar entries in the network data + // (potentially causing a lower preference entry to be removed). + + if ((HasRouteEntry::FlagsWithoutPreference(entryFlags) == flagsWithoutPreference) && + (entryPreference >= preference)) + { + aNumEntries++; + + // We prefer an entry if it has strictly higher preference + // than ours or if it has same preference with a smaller + // RLOC16. + + if ((entryPreference > preference) || IsPreferred(entry->GetRloc())) + { + aNumPreferredEntries++; + } + } + } + +exit: + return; +} + +} // namespace NetworkData +} // namespace ot + +#endif // OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE diff --git a/src/core/thread/network_data_publisher.hpp b/src/core/thread/network_data_publisher.hpp new file mode 100644 index 000000000..26cc9193a --- /dev/null +++ b/src/core/thread/network_data_publisher.hpp @@ -0,0 +1,464 @@ +/* + * Copyright (c) 2021, 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. + */ + +/** + * @file + * This file includes definition of Network Data Publisher. + */ + +#ifndef NETWORK_DATA_PUBLISHER_HPP_ +#define NETWORK_DATA_PUBLISHER_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE + +#if !OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE && !OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE +#error "OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE requires either OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE"\ + "or OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE" +#endif + +#include + +#include "common/clearable.hpp" +#include "common/equatable.hpp" +#include "common/error.hpp" +#include "common/locator.hpp" +#include "common/non_copyable.hpp" +#include "common/notifier.hpp" +#include "common/string.hpp" +#include "common/timer.hpp" +#include "net/ip6_address.hpp" +#include "thread/network_data_types.hpp" + +namespace ot { +namespace NetworkData { + +/** + * This class implements the Network Data Publisher. + * + * It provides mechanisms to limit the number of similar Service and/or Prefix (on-mesh prefix or external route) + * entries in the Thread Network Data by monitoring the Network Data and managing if or when to add or remove entries. + * + */ +class Publisher : public InstanceLocator, private NonCopyable +{ + friend class ot::Notifier; + +public: + /** + * This enumeration represents the events reported from the Publisher callbacks. + * + */ + enum Event : uint8_t + { + kEventEntryAdded = OT_NETDATA_PUBLISHER_EVENT_ENTRY_ADDED, ///< Entry is added to Network Data. + kEventEntryRemoved = OT_NETDATA_PUBLISHER_EVENT_ENTRY_REMOVED, ///< Entry is removed from Network Data. + }; + + /** + * This constructor initializes `Publisher` object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit Publisher(Instance &aInstance); + +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + + /** + * This type represents the callback function pointer used to notify when a "DNS/SRP Service" entry is added to or + * removed from the Thread Network Data. + * + * On remove the callback is invoked independent of whether the entry is removed by `Publisher` (e.g., when there + * are too many similar entries already present in the Network Data) or through an explicit call to unpublish the + * entry (i.e., a call to `UnpublishDnsSrpService()`). + * + */ + typedef otNetDataDnsSrpServicePublisherCallback DnsSrpServiceCallback; + + /** + * This method sets a callback for notifying when a published "DNS/SRP Service" is actually added to or removed + * from the Thread Network Data. + * + * A subsequent call to this method replaces any previously set callback function. + * + * @param[in] aCallback The callback function pointer (can be NULL if not needed). + * @param[in] aContext A pointer to application-specific context (used when @p aCallback is invoked). + * + */ + void SetDnsSrpServiceCallback(DnsSrpServiceCallback aCallback, void *aContext) + { + mDnsSrpServiceEntry.SetCallback(aCallback, aContext); + } + + /** + * This method requests "DNS/SRP Service Anycast Address" to be published in the Thread Network Data. + * + * A call to this method will remove and replace any previous "DNS/SRP Service" entry that was being published + * (from earlier call to any of `PublishDnsSrpService{Type}()` methods). + * + * @param[in] aSequenceNumber The sequence number of DNS/SRP Anycast Service. + * + */ + void PublishDnsSrpServiceAnycast(uint8_t aSequenceNumber) { mDnsSrpServiceEntry.PublishAnycast(aSequenceNumber); } + + /** + * This method requests "DNS/SRP Service Unicast Address" to be published in the Thread Network Data. + * + * A call to this method will remove and replace any previous "DNS/SRP Service" entry that was being published + * (from earlier call to any of `PublishDnsSrpService{Type}()` methods). + * + * This method publishes the "DNS/SRP Service Unicast Address" by including the address and port info in the + * Service TLV data. + * + * @param[in] aAddress The DNS/SRP server address to publish. + * @param[in] aPort The SRP server port number to publish. + * + */ + void PublishDnsSrpServiceUnicast(const Ip6::Address &aAddress, uint16_t aPort) + { + mDnsSrpServiceEntry.PublishUnicast(aAddress, aPort); + } + + /** + * This method requests "DNS/SRP Service Unicast Address" to be published in the Thread Network Data. + * + * A call to this method will remove and replace any previous "DNS/SRP Service" entry that was being published + * (from earlier call to any of `PublishDnsSrpService{Type}()` methods). + * + * Unlike the `PublishDnsSrpServiceUnicast(aAddress, aPort)` which requires the published address to be given and + * includes the info in the Service TLV data, this method uses the device's mesh-local EID and includes the info + * in the Server TLV data. + * + * @param[in] aPort The SRP server port number to publish. + * + */ + void PublishDnsSrpServiceUnicast(uint16_t aPort) { mDnsSrpServiceEntry.PublishUnicast(aPort); } + + /** + * This method indicates whether or not currently the "DNS/SRP Service" entry is added to the Thread Network Data. + * + * @retval TRUE The published DNS/SRP Service entry is added to the Thread Network Data. + * @retval FLASE The entry is not added to Thread Network Data or there is no entry to publish. + * + */ + bool IsDnsSrpServiceAdded(void) const { return mDnsSrpServiceEntry.IsAdded(); } + + /** + * This method unpublishes any previously added "DNS/SRP (Anycast or Unicast) Service" entry from the Thread + * Network Data. + * + */ + void UnpublishDnsSrpService(void) { mDnsSrpServiceEntry.Unpublish(); } + +#endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + /** + * This type represents the callback function pointer used to notify when a prefix (on-mesh or external route) + * entry is added to or removed from the Thread Network Data. + * + * On remove the callback is invoked independent of whether the entry is removed by `Publisher` (e.g., when there + * are too many similar entries already present in the Network Data) or through an explicit call to unpublish the + * entry. + * + */ + typedef otNetDataPrefixPublisherCallback PrefixCallback; + + /** + * This method sets a callback for notifying when a published prefix entry is actually added to or removed from + * the Thread Network Data. + * + * A subsequent call to this method replaces any previously set callback function. + * + * @param[in] aCallback The callback function pointer (can be NULL if not needed). + * @param[in] aContext A pointer to application-specific context (used when @p aCallback is invoked). + * + */ + void SetPrefixCallback(PrefixCallback aCallback, void *aContext); + + /** + * This method requests an on-mesh prefix to be published in the Thread Network Data. + * + * Only stable entries can be published (i.e.,`aConfig.mStable` MUST be `true`). + * + * @param[in] aConfig The on-mesh prefix config to publish. + * + * @retval kErrorNone The on-mesh prefix is published successfully. + * @retval kErrorInvalidArgs The @p aConfig is not valid (bad prefix, invalid flag combinations, or not stable). + * @retval kErrorAlready An entry with the same prefix is already in the published list. + * @retval kErrorNoBufs 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`. + * + * + */ + Error PublishOnMeshPrefix(const OnMeshPrefixConfig &aConfig); + + /** + * This method requests an external route prefix to be published in the Thread Network Data. + * + * Only stable entries can be published (i.e.,`aConfig.mStable` MUST be `true`). + * + * @param[in] aConfig The external route config to publish. + * + * @retval kErrorNone The external route is published successfully. + * @retval kErrorInvalidArgs The @p aConfig is not valid (bad prefix, invalid flag combinations, or not stable). + * @retval kErrorAlready An entry with the same prefix is already in the published list. + * @retval kErrorNoBufs 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`. + * + * + */ + Error PublishExternalRoute(const ExternalRouteConfig &aConfig); + + /** + * This method indicates whether or not currently a published prefix entry (on-mesh or external route) is added to + * the Thread Network Data. + * + * @param[in] aPrefix The prefix to check. + * + * @retval TRUE The published prefix entry is added to the Thread Network Data. + * @retval FLASE The entry is not added to Thread Network Data or there is no matching entry to publish. + * + */ + bool IsPrefixAdded(const Ip6::Prefix &aPrefix) const; + + /** + * This method unpublishes a previously published prefix (on-mesh or external route). + * + * @param[in] aPrefix The prefix to unpublish. + * + * @retval kErrorNone The prefix was unpublished successfully. + * @retval kErrorNotFound Could not find the prefix in the published list. + * + */ + Error UnpublishPrefix(const Ip6::Prefix &aPrefix); +#endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + +private: + class Entry : public InstanceLocatorInit + { + protected: + enum State : uint8_t + { + kNoEntry, // Entry is unused (there is no entry). + kToAdd, // Entry is ready to be added, monitoring network data to decide if/when to add it. + kAdding, // Entry is being added in network data (random wait interval before add). + kAdded, // Entry is added in network data, monitoring to determine if/when to remove. + kRemoving, // Entry is being removed from network data (random wait interval before remove). + }; + + // All intervals are in milliseconds. + static constexpr uint32_t kMaxDelayToAdd = OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_DELAY_TO_ADD; + static constexpr uint32_t kMaxDelayToRemove = OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_DELAY_TO_REMOVE; + static constexpr uint32_t kExtraDelayToRemovePeferred = + OPENTHREAD_CONFIG_NETDATA_PUBLISHER_EXTRA_DELAY_TIME_TO_REMOVE_PREFERRED; + + static constexpr uint16_t kInfoStringSize = 50; + + typedef String InfoString; + + Entry(void) + : mState(kNoEntry) + { + } + + void Init(Instance &aInstance) { InstanceLocatorInit::Init(aInstance); } + State GetState(void) const { return mState; } + void SetState(State aState); + const TimeMilli &GetUpdateTime(void) const { return mUpdateTime; } + bool IsPreferred(uint16_t aRloc16) const; + void UpdateState(uint8_t aNumEntries, uint8_t aNumPreferredEntries, uint8_t aDesiredNumEntries); + bool HandleTimer(void); + InfoString ToString(bool aIncludeState = true) const; + + public: + bool IsAdded(void) const { return (mState == kAdded); } + + private: + bool Add(void); + bool Remove(State aNextState); + void LogUpdateTime(void) const; + static const char *StateToString(State aState); + + TimeMilli mUpdateTime; + State mState; + }; + +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + class DnsSrpServiceEntry : public Entry, private NonCopyable + { + friend class Entry; + + public: + explicit DnsSrpServiceEntry(Instance &aInstance); + void SetCallback(DnsSrpServiceCallback aCallback, void *aContext); + void PublishAnycast(uint8_t aSequenceNumber); + void PublishUnicast(const Ip6::Address &aAddress, uint16_t aPort); + void PublishUnicast(uint16_t aPort); + void Unpublish(void); + bool HandleTimer(void) { return Entry::HandleTimer(); } + bool HandleNotifierEvents(Events aEvents); + + private: + static constexpr uint8_t kDesiredNumAnycast = + OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_ANYCAST_DNS_SRP_SERVICE_ENTRIES; + + static constexpr uint8_t kDesiredNumUnicast = + OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_UNICAST_DNS_SRP_SERVICE_ENTRIES; + + enum Type : uint8_t + { + kTypeAnycast, + kTypeUnicast, + kTypeUnicastMeshLocalEid, + }; + + class Info : public Clearable, public Equatable + { + public: + Info(void) { Clear(); } + Type GetType(void) const { return mType; } + uint8_t GetSequenceNumber(void) const { return static_cast(mPortOrSeqNumber); } + uint16_t GetPort(void) const { return mPortOrSeqNumber; } + const Ip6::Address &GetAddress(void) const { return mAddress; } + void SetAddress(const Ip6::Address &aAddress) { mAddress = aAddress; } + + static Info InfoAnycast(uint8_t aSequenceNumber) { return Info(kTypeAnycast, aSequenceNumber); } + static Info InfoUnicast(Type aType, const Ip6::Address &aAddress, uint16_t aPort) + { + return Info(aType, aPort, &aAddress); + } + + private: + Info(Type aType, uint16_t aPortOrSeqNumber, const Ip6::Address *aAddress = nullptr); + + Ip6::Address mAddress; + uint16_t mPortOrSeqNumber; + Type mType; + }; + + Type GetType(void) const { return mInfo.GetType(); } + void Publish(const Info &aInfo); + bool Add(void); + bool Remove(State aNextState); + void Notify(Event aEvent) const; + void Process(void); + void CountAnycastEntries(uint8_t &aNumEntries, uint8_t &aNumPreferredEntries) const; + void CountUnicastEntries(uint8_t &aNumEntries, uint8_t &aNumPreferredEntries) const; + + Info mInfo; + DnsSrpServiceCallback mCallback; + void * mCallbackContext; + }; +#endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + // Max number of prefix (on-mesh or external route) entries. + static constexpr uint16_t kMaxPrefixEntries = OPENTHREAD_CONFIG_NETDATA_PUBLISHER_MAX_PREFIX_ENTRIES; + + class PrefixEntry : public Entry, private NonCopyable + { + friend class Entry; + + public: + void Init(Instance &aInstance) { Entry::Init(aInstance); } + bool IsInUse(void) const { return GetState() != kNoEntry; } + bool Matches(const Ip6::Prefix &aPrefix) const { return mPrefix == aPrefix; } + void Publish(const OnMeshPrefixConfig &aConfig); + void Publish(const ExternalRouteConfig &aConfig); + void Unpublish(void); + bool HandleTimer(void) { return Entry::HandleTimer(); } + void HandleNotifierEvents(Events aEvents); + + private: + static constexpr uint8_t kDesiredNumOnMeshPrefix = + OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_ON_MESH_PREFIX_ENTRIES; + + static constexpr uint8_t kDesiredNumExternalRoute = + OPENTHREAD_CONFIG_NETDATA_PUBLISHER_DESIRED_NUM_EXTERNAL_ROUTE_ENTRIES; + + enum Type : uint8_t + { + kTypeOnMeshPrefix, + kTypeExternalRoute, + }; + + bool Add(void); + Error AddOnMeshPrefix(void); + Error AddExternalRoute(void); + bool Remove(State aNextState); + void Process(void); + void CountOnMeshPrefixEntries(uint8_t &aNumEntries, uint8_t &aNumPreferredEntries) const; + void CountExternalRouteEntries(uint8_t &aNumEntries, uint8_t &aNumPreferredEntries) const; + + Type mType; + Ip6::Prefix mPrefix; + uint16_t mFlags; + }; +#endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + bool IsADnsSrpServiceEntry(const Entry &aEntry) const { return (&aEntry == &mDnsSrpServiceEntry); } +#endif + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + Error AllocatePrefixEntry(const Ip6::Prefix &aPrefix, PrefixEntry *&aEntry); + PrefixEntry * FindMatchingPrefixEntry(const Ip6::Prefix &aPrefix); + const PrefixEntry *FindMatchingPrefixEntry(const Ip6::Prefix &aPrefix) const; + bool IsAPrefixEntry(const Entry &aEntry) const; + void NotifyPrefixEntryChange(Event aEvent, const Ip6::Prefix &aPrefix) const; +#endif + + TimerMilli &GetTimer(void) { return mTimer; } + void HandleNotifierEvents(Events aEvents); + static void HandleTimer(Timer &aTimer); + void HandleTimer(void); + +#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE + DnsSrpServiceEntry mDnsSrpServiceEntry; +#endif + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE + PrefixEntry mPrefixEntries[kMaxPrefixEntries]; + PrefixCallback mPrefixCallback; + void * mPrefixCallbackContext; +#endif + + TimerMilli mTimer; +}; + +} // namespace NetworkData +} // namespace ot + +#endif // OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE + +#endif // NETWORK_DATA_PUBLISHER_HPP_ diff --git a/src/core/thread/network_data_service.cpp b/src/core/thread/network_data_service.cpp index f431e8b43..77405000f 100644 --- a/src/core/thread/network_data_service.cpp +++ b/src/core/thread/network_data_service.cpp @@ -59,28 +59,15 @@ Error Manager::AddService(const void *aServiceData, const void *aServerData, uint8_t aServerDataLength) { - Error error; - - SuccessOrExit(error = Get().AddService( - kThreadEnterpriseNumber, reinterpret_cast(aServiceData), aServiceDataLength, - aServerStable, reinterpret_cast(aServerData), aServerDataLength)); - - Get().HandleServerDataUpdated(); - -exit: - return error; + return Get().AddService(kThreadEnterpriseNumber, reinterpret_cast(aServiceData), + aServiceDataLength, aServerStable, reinterpret_cast(aServerData), + aServerDataLength); } Error Manager::RemoveService(const void *aServiceData, uint8_t aServiceDataLength) { - Error error; - - SuccessOrExit(error = Get().RemoveService( - kThreadEnterpriseNumber, reinterpret_cast(aServiceData), aServiceDataLength)); - Get().HandleServerDataUpdated(); - -exit: - return error; + return Get().RemoveService(kThreadEnterpriseNumber, reinterpret_cast(aServiceData), + aServiceDataLength); } #endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE diff --git a/src/core/thread/network_data_service.hpp b/src/core/thread/network_data_service.hpp index 534a03e7d..c99287c7b 100644 --- a/src/core/thread/network_data_service.hpp +++ b/src/core/thread/network_data_service.hpp @@ -421,9 +421,6 @@ public: /** * This method adds a Thread Service entry to the local Thread Network Data. * - * When successfully added, this method also invokes `Notifier::HandleServerDataUpdated()` to register the changes - * in local Network Data with leader. - * * This version of `Add()` is intended for use with a `ServiceType` that has a constant service data * format with a non-empty and potentially non-const server data format (provided as input parameter). * @@ -451,9 +448,6 @@ public: /** * This method adds a Thread Service entry to the local Thread Network Data. * - * When successfully added, this method also invokes `Notifier::HandleServerDataUpdated()` to register the changes - * in local Network Data with leader. - * * This version of `Add()` is intended for use with a `ServiceType` that has a non-const service data * format (provided as input parameter) with an empty server data. * @@ -479,9 +473,6 @@ public: /** * This method removes a Thread Service entry from the local Thread Network Data. * - * When successfully removed, this method also invokes `Notifier::HandleServerDataUpdated()` to register the - * changes in local Network Data with leader. - * * This version of `Remove()` is intended for use with a `ServiceType` that has a constant service data * format. * @@ -502,9 +493,6 @@ public: /** * This method removes a Thread Service entry from the local Thread Network Data. * - * When successfully removed, this method also invokes `Notifier::HandleServerDataUpdated()` to register the - * changes in local Network Data with leader. - * * This version of `Remove()` is intended for use with a `ServiceType` that has a non-const service data * format (provided as input parameter). * @@ -514,6 +502,8 @@ public: * * @tparam ServiceType The service type to be removed. * + * @param[in] aServiceData The service data. + * * @retval kErrorNone Successfully removed the Service entry. * @retval kErrorNotFound Could not find the Service entry. * diff --git a/src/core/thread/network_data_tlvs.hpp b/src/core/thread/network_data_tlvs.hpp index db10c773c..bf62f04e9 100644 --- a/src/core/thread/network_data_tlvs.hpp +++ b/src/core/thread/network_data_tlvs.hpp @@ -391,7 +391,7 @@ public: * @returns The preference value. * */ - int8_t GetPreference(void) const { return static_cast(mFlags) >> kPreferenceOffset; } + int8_t GetPreference(void) const { return PreferenceFromFlags(GetFlags()); } /** * This method gets the Flags value. @@ -434,6 +434,30 @@ public: */ const HasRouteEntry *GetNext(void) const { return (this + 1); } + /** + * This static method returns an updated flags bitmask by removing the preference bits (sets them to zero) from a + * given flags bitmask. + * + * @param[in] aFlags The flags bitmask. + * + * @returns An updated version @p aFlags with preference bits cleared. + * + */ + static uint8_t FlagsWithoutPreference(uint8_t aFlags) { return (aFlags & ~kPreferenceMask); } + + /** + * This static method gets the preference field from a flags bitmask. + * + * @param[in] aFlags The flags. + * + * @returns The preference field from the @p aFlags. + * + */ + static int8_t PreferenceFromFlags(uint8_t aFlags) + { + return static_cast(static_cast(aFlags) >> kPreferenceOffset); + } + private: static constexpr uint8_t kPreferenceOffset = 6; static constexpr uint8_t kPreferenceMask = 3 << kPreferenceOffset; @@ -890,10 +914,7 @@ public: * @returns the Preference value. * */ - int8_t GetPreference(void) const - { - return static_cast(static_cast(HostSwap16(mFlags)) >> kPreferenceOffset); - } + int8_t GetPreference(void) const { return PreferenceFromFlags(GetFlags()); } /** * This method indicates whether or not the Preferred flag is set. @@ -983,6 +1004,30 @@ public: */ const BorderRouterEntry *GetNext(void) const { return (this + 1); } + /** + * This static method returns an updated flags bitmask by removing the preference bits (sets them to zero) from a + * given flags bitmask. + * + * @param[in] aFlags The flags bitmask. + * + * @returns An updated version @p aFlags with preference bits cleared. + * + */ + static uint16_t FlagsWithoutPreference(uint16_t aFlags) { return (aFlags & ~kPreferenceMask); } + + /** + * This static method gets the preference field from a flags bitmask. + * + * @param[in] aFlags The flags. + * + * @returns The preference field from the @p aFlags. + * + */ + static int8_t PreferenceFromFlags(uint16_t aFlags) + { + return static_cast(static_cast(aFlags) >> kPreferenceOffset); + } + private: static constexpr uint8_t kPreferenceOffset = 14; static constexpr uint16_t kPreferenceMask = 3 << kPreferenceOffset; diff --git a/src/core/thread/network_data_types.hpp b/src/core/thread/network_data_types.hpp index df2c52060..4d8308d80 100644 --- a/src/core/thread/network_data_types.hpp +++ b/src/core/thread/network_data_types.hpp @@ -56,6 +56,7 @@ namespace NetworkData { // Forward declarations class NetworkData; class Local; +class Publisher; class PrefixTlv; class BorderRouterTlv; class BorderRouterEntry; @@ -74,6 +75,7 @@ class OnMeshPrefixConfig : public otBorderRouterConfig, { friend class NetworkData; friend class Local; + friend class Publisher; public: /** @@ -125,6 +127,7 @@ class ExternalRouteConfig : public otExternalRouteConfig, { friend class NetworkData; friend class Local; + friend class Publisher; public: /** diff --git a/src/core/thread/thread_netif.cpp b/src/core/thread/thread_netif.cpp index 5a07e88d6..8d4768aa8 100644 --- a/src/core/thread/thread_netif.cpp +++ b/src/core/thread/thread_netif.cpp @@ -95,6 +95,9 @@ ThreadNetif::ThreadNetif(Instance &aInstance) , mNetworkDataLeader(aInstance) #if OPENTHREAD_FTD || OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE , mNetworkDataNotifier(aInstance) +#endif +#if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE + , mNetworkDataPublisher(aInstance) #endif , mNetworkDataServiceManager(aInstance) #if OPENTHREAD_FTD || OPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE diff --git a/src/core/thread/thread_netif.hpp b/src/core/thread/thread_netif.hpp index 14b0647c7..df6ae6fe0 100644 --- a/src/core/thread/thread_netif.hpp +++ b/src/core/thread/thread_netif.hpp @@ -97,6 +97,7 @@ #include "thread/mle_router.hpp" #include "thread/network_data_local.hpp" #include "thread/network_data_notifier.hpp" +#include "thread/network_data_publisher.hpp" #include "thread/network_data_service.hpp" #include "thread/network_diagnostic.hpp" #include "thread/panid_query_server.hpp" @@ -239,6 +240,9 @@ private: NetworkData::Leader mNetworkDataLeader; #if OPENTHREAD_FTD || OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE NetworkData::Notifier mNetworkDataNotifier; +#endif +#if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE + NetworkData::Publisher mNetworkDataPublisher; #endif NetworkData::Service::Manager mNetworkDataServiceManager; #if OPENTHREAD_FTD || OPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE diff --git a/src/posix/Makefile-posix b/src/posix/Makefile-posix index 57d7c5cff..9d37ea140 100644 --- a/src/posix/Makefile-posix +++ b/src/posix/Makefile-posix @@ -63,6 +63,7 @@ MAC_FILTER ?= 1 MAX_POWER_TABLE ?= 1 MTD_NETDIAG ?= 1 NEIGHBOR_DISCOVERY_AGENT ?= 1 +NETDATA_PUBLISHER ?= 1 PING_SENDER ?= 1 READLINE ?= readline REFERENCE_DEVICE ?= 1 diff --git a/tests/fuzz/oss-fuzz-build b/tests/fuzz/oss-fuzz-build index cbfba51ca..37b4291a5 100755 --- a/tests/fuzz/oss-fuzz-build +++ b/tests/fuzz/oss-fuzz-build @@ -61,6 +61,7 @@ -DOT_LOG_OUTPUT=APP \ -DOT_MAC_FILTER=ON \ -DOT_MTD_NETDIAG=ON \ + -DOT_NETDATA_PUBLISHER=ON \ -DOT_PING_SENDER=ON \ -DOT_SERVICE=ON \ -DOT_SLAAC=ON \ diff --git a/tests/scripts/thread-cert/Makefile.am b/tests/scripts/thread-cert/Makefile.am index d4ff8b87d..f873f331f 100644 --- a/tests/scripts/thread-cert/Makefile.am +++ b/tests/scripts/thread-cert/Makefile.am @@ -168,6 +168,7 @@ EXTRA_DIST = \ test_lowpan.py \ test_mac802154.py \ test_mle.py \ + test_netdata_publisher.py \ test_network_data.py \ test_network_layer.py \ test_on_mesh_prefix.py \ @@ -233,6 +234,7 @@ check_SCRIPTS = \ test_lowpan.py \ test_mac802154.py \ test_mle.py \ + test_netdata_publisher.py \ test_network_data.py \ test_network_layer.py \ test_on_mesh_prefix.py \ diff --git a/tests/scripts/thread-cert/border_router/test_advertising_proxy.py b/tests/scripts/thread-cert/border_router/test_advertising_proxy.py index 8d6d052ed..6dfdf2810 100755 --- a/tests/scripts/thread-cert/border_router/test_advertising_proxy.py +++ b/tests/scripts/thread-cert/border_router/test_advertising_proxy.py @@ -157,7 +157,7 @@ class SingleHostAndService(thread_cert.TestCase): # server.srp_server_set_enabled(False) - self.simulator.go(2) + self.simulator.go(5) self.assertEqual(len(server.srp_server_get_hosts()), 0) self.assertEqual(len(server.srp_server_get_services()), 0) diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index fdf0e6ff0..63c51a5e7 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -1715,6 +1715,34 @@ class NodeImpl: self.send_command('netdata register') self._expect_done() + def netdata_publish_dnssrp_anycast(self, seqnum): + self.send_command(f'netdata publish dnssrp anycast {seqnum}') + self._expect_done() + + def netdata_publish_dnssrp_unicast(self, address, port): + self.send_command(f'netdata publish dnssrp unicast {address} {port}') + self._expect_done() + + def netdata_publish_dnssrp_unicast_mleid(self, port): + self.send_command(f'netdata publish dnssrp unicast {port}') + self._expect_done() + + def netdata_unpublish_dnssrp(self): + self.send_command('netdata unpublish dnssrp') + self._expect_done() + + def netdata_publish_prefix(self, prefix, flags='paosr', prf='med'): + self.send_command(f'netdata publish prefix {prefix} {flags} {prf}') + self._expect_done() + + def netdata_publish_route(self, prefix, flags='s', prf='med'): + self.send_command(f'netdata publish route {prefix} {flags} {prf}') + self._expect_done() + + def netdata_unpublish_prefix(self, prefix): + self.send_command(f'netdata unpublish {prefix}') + self._expect_done() + def send_network_diag_get(self, addr, tlv_types): self.send_command('networkdiagnostic get %s %s' % (addr, ' '.join([str(t.value) for t in tlv_types]))) diff --git a/tests/scripts/thread-cert/test_netdata_publisher.py b/tests/scripts/thread-cert/test_netdata_publisher.py new file mode 100755 index 000000000..b3d6b23c0 --- /dev/null +++ b/tests/scripts/thread-cert/test_netdata_publisher.py @@ -0,0 +1,452 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2021, 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. +# + +import ipaddress +import unittest + +import command +import thread_cert + +# Test description: +# This test verifies network data publisher behavior with DNS/SRP service entries and on-mesh prefix and external +# route entries. +# +# Topology: +# +# 1 leader, 5 routers and 5 end-devices all connected. +# + +LEADER = 1 +ROUTER1 = 2 +ROUTER2 = 3 +ROUTER3 = 4 +ROUTER4 = 5 +ROUTER5 = 6 +END_DEV1 = 7 +END_DEV2 = 8 +END_DEV3 = 9 +END_DEV4 = 10 +END_DEV5 = 11 + +WAIT_TIME = 55 + +ON_MESH_PREFIX = 'fd00:1234::/64' +ON_MESH_FLAGS = 'paso' + +EXTERNAL_ROUTE = 'fd00:abce::/64' +EXTERNAL_FLAGS = 's' + +ANYCAST_SEQ_NUM = 4 + +DNSSRP_ADDRESS = 'fd00::cdef' +DNSSRP_PORT = 49152 + +# The desired number of entries (based on related config). +DESIRED_NUM_DNSSRP_ANYCAST = 8 +DESIRED_NUM_DNSSRP_UNCIAST = 2 +DESIRED_NUM_ON_MESH_PREFIX = 3 +DESIRED_NUM_EXTERNAL_ROUTE = 10 + +THREAD_ENTERPRISE_NUMBER = 44970 +ANYCAST_SERVICE_NUM = 0x5c +UNICAST_SERVICE_NUM = 0x5d + + +class NetDataPublisher(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + SUPPORT_NCP = False + + TOPOLOGY = { + LEADER: { + 'name': 'LEADER', + 'mode': 'rdn', + }, + ROUTER1: { + 'name': 'ROUTER1', + 'mode': 'rdn', + }, + ROUTER2: { + 'name': 'ROUTER2', + 'mode': 'rdn', + }, + ROUTER3: { + 'name': 'ROUTER3', + 'mode': 'rdn', + }, + ROUTER4: { + 'name': 'ROUTER4', + 'mode': 'rdn', + }, + ROUTER5: { + 'name': 'ROUTER5', + 'mode': 'rdn', + }, + END_DEV1: { + 'name': 'END_DEV1', + 'mode': 'rn', + }, + END_DEV2: { + 'name': 'END_DEV2', + 'mode': 'rn', + }, + END_DEV3: { + 'name': 'END_DEV3', + 'mode': 'rn', + }, + END_DEV4: { + 'name': 'END_DEV4', + 'mode': 'rn', + }, + END_DEV5: { + 'name': 'END_DEV5', + 'mode': 'rn', + }, + } + + def verify_anycast_service(self, service): + # Verify the data in a single anycast `service` from `get_services()` + # Example of `service`: ['44970', '5c04', '', 's', 'bc00'] + self.assertEqual(int(service[0]), THREAD_ENTERPRISE_NUMBER) + # Check service data + service_data = bytes.fromhex(service[1]) + self.assertTrue(len(service_data) >= 2) + self.assertEqual(service_data[0], ANYCAST_SERVICE_NUM) + self.assertEqual(service_data[1], int(ANYCAST_SEQ_NUM)) + # Verify that it stable + self.assertEqual(service[3], 's') + + def verify_anycast_services(self, services): + # Verify a list of anycast `services` from `get_services()` + for service in services: + self.verify_anycast_service(service) + + def verify_unicast_service(self, service): + # Verify the data in a single unicast `service` from `get_services()` + # Example of `service`: ['44970', '5d', 'fd000db800000000c6b0e5ee81f940e8223d', 's', '7000'] + self.assertEqual(int(service[0]), THREAD_ENTERPRISE_NUMBER) + # Check service data + service_data = bytes.fromhex(service[1]) + self.assertTrue(len(service_data) >= 1) + self.assertEqual(service_data[0], UNICAST_SERVICE_NUM) + # Verify that it stable + self.assertEqual(service[3], 's') + + def verify_unicast_services(self, services): + # Verify a list of unicast `services` from `get_services()` + for service in services: + self.verify_unicast_service(service) + + def check_num_of_prefixes(self, prefixes, num_low, num_med, num_high): + # Check and validate the prefix entries in network data (from + # `prefixes`) based on number of published prefix entries at + # different preference levels given by `num_low`, `num_med`, + # `num_high`. Prefixes is a list of the format + # 'fd00:1234:0:0::/64 paos low a802'. + self.assertEqual(len(prefixes), min(num_high + num_med + num_low, DESIRED_NUM_ON_MESH_PREFIX)) + prfs = [prefix.split(' ')[2] for prefix in prefixes] + self.assertEqual(prfs.count('high'), min(num_high, DESIRED_NUM_ON_MESH_PREFIX)) + self.assertEqual(prfs.count('med'), min(num_med, max(0, DESIRED_NUM_ON_MESH_PREFIX - num_high))) + self.assertEqual(prfs.count('low'), min(num_low, max(0, DESIRED_NUM_ON_MESH_PREFIX - num_high - num_med))) + + def check_num_of_routes(self, routes, num_low, num_med, num_high): + # Check and validate the prefix entries in network data (from + # `routes`) based on number of published prefix entries at + # different preference levels given by `num_low`, `num_med`, + # `num_high`. Prefixes is a list of the format + # 'fd00:abce:0:0::/64 s med 6c01'. + self.assertEqual(len(routes), min(num_high + num_med + num_low, DESIRED_NUM_EXTERNAL_ROUTE)) + prfs = [route.split(' ')[2] for route in routes] + self.assertEqual(prfs.count('high'), min(num_high, DESIRED_NUM_EXTERNAL_ROUTE)) + self.assertEqual(prfs.count('med'), min(num_med, max(0, DESIRED_NUM_EXTERNAL_ROUTE - num_high))) + self.assertEqual(prfs.count('low'), min(num_low, max(0, DESIRED_NUM_EXTERNAL_ROUTE - num_high - num_med))) + + def test(self): + leader = self.nodes[LEADER] + router1 = self.nodes[ROUTER1] + router2 = self.nodes[ROUTER2] + router3 = self.nodes[ROUTER3] + router4 = self.nodes[ROUTER4] + router5 = self.nodes[ROUTER5] + end_dev1 = self.nodes[END_DEV1] + end_dev2 = self.nodes[END_DEV2] + end_dev3 = self.nodes[END_DEV3] + end_dev4 = self.nodes[END_DEV4] + end_dev5 = self.nodes[END_DEV5] + + nodes = self.nodes.values() + routers = [router1, router2, router3, router4, router5] + end_devs = [end_dev1, end_dev2, end_dev3, end_dev4, end_dev5] + + # Start the nodes + + leader.start() + self.simulator.go(5) + self.assertEqual(leader.get_state(), 'leader') + + for router in routers: + router.start() + self.simulator.go(5) + self.assertEqual(router.get_state(), 'router') + + for end_dev in end_devs: + end_dev.start() + self.simulator.go(5) + self.assertEqual(end_dev.get_state(), 'child') + + #--------------------------------------------------------------------------------- + # DNS/SRP anycast entries + + # Publish DNS/SRP anycast on leader and all routers (6 nodes). + + leader.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) + for node in routers: + node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) + self.simulator.go(WAIT_TIME) + + # Check all entries are present in the network data + + services = leader.get_services() + self.assertEqual(len(services), min(1 + len(routers), DESIRED_NUM_DNSSRP_ANYCAST)) + self.verify_anycast_services(services) + + # Publish same entry on all end-devices (5 nodes). + + for node in end_devs: + node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) + print(node.name) + self.simulator.go(WAIT_TIME) + + # Check number of entries in the network data is limited to + # the desired number (8 entries). + + services = leader.get_services() + self.assertEqual(len(leader.get_services()), min(len(nodes), DESIRED_NUM_DNSSRP_ANYCAST)) + self.verify_anycast_services(services) + + # Unpublish the entry from nodes one by one starting from leader + # and check that number of entries is correct in each step. + + num = len(nodes) + for node in nodes: + node.netdata_unpublish_dnssrp() + self.simulator.go(WAIT_TIME) + num -= 1 + services = leader.get_services() + self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_ANYCAST)) + self.verify_anycast_services(services) + + #--------------------------------------------------------------------------------- + # DNS/SRP unicast entries + + # Publish DNS/SRP unicast address on all routers, first using + # MLE-EID address, then change to use specific address. Verify + # that number of entries in network data is correct in each step + # and that entries are switched correctly. + + num = 0 + for node in routers: + node.netdata_publish_dnssrp_unicast_mleid(DNSSRP_PORT) + self.simulator.go(WAIT_TIME) + num += 1 + services = leader.get_services() + self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNCIAST)) + self.verify_unicast_services(services) + + for node in routers: + node.netdata_publish_dnssrp_unicast(DNSSRP_ADDRESS, DNSSRP_PORT) + self.simulator.go(WAIT_TIME) + services = leader.get_services() + self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNCIAST)) + self.verify_unicast_services(services) + + for node in routers: + node.netdata_unpublish_dnssrp() + self.simulator.go(WAIT_TIME) + num -= 1 + services = leader.get_services() + self.assertEqual(len(services), min(num, DESIRED_NUM_DNSSRP_UNCIAST)) + self.verify_unicast_services(services) + + #--------------------------------------------------------------------------------- + # DNS/SRP entries: Verify publisher preference when removing + # entries. + # + # Publish DNS/SRP anycast on 8 nodes: leader, router1, + # router2, and all 5 end-devices. Afterwards, manually add + # the same service entry in Network Data on router3, router4, + # and router5 and at each step check that entry from one of + # the end-devices is removed (publisher prefers + # entries from routers over the ones from end-devices). + + num = 0 + test_routers = [leader, router1, router2] + for node in test_routers + end_devs: + node.netdata_publish_dnssrp_anycast(ANYCAST_SEQ_NUM) + self.simulator.go(WAIT_TIME) + num += 1 + services = leader.get_services() + self.assertEqual(len(services), num) + self.verify_anycast_services(services) + + self.assertEqual(num, DESIRED_NUM_DNSSRP_ANYCAST) + + service_data = '%02x%02x' % (ANYCAST_SERVICE_NUM, int(ANYCAST_SEQ_NUM)) + for node in [router3, router4, router5]: + node.add_service(str(THREAD_ENTERPRISE_NUMBER), service_data, '00') + node.register_netdata() + self.simulator.go(WAIT_TIME) + + services = leader.get_services() + self.assertEqual(len(services), num) + self.verify_anycast_services(services) + + service_rlocs = [int(service[4], 16) for service in services] + test_routers.append(node) + + for router in test_routers: + self.assertIn(router.get_addr16(), service_rlocs) + + #--------------------------------------------------------------------------------- + # On-mesh prefix + + # Publish the same on-mesh prefix on different nodes (low + # preference on end-devices, medium preference on routers, and + # high on leader) one by one and then unpublish them one by one. + # Verify that at each step the entries in the network data are + # correct. Particularly verify that that higher preference + # entries replace lower preference ones even when there are + # already desired number in network data. + + num_low = 0 + num_med = 0 + num_high = 0 + + for node in end_devs: + node.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'low') + self.simulator.go(WAIT_TIME) + num_low += 1 + prefixes = leader.get_prefixes() + self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) + + # Now add the entry as 'med' on routers and check that we see those in the list. + for node in routers: + node.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'med') + self.simulator.go(WAIT_TIME) + num_med += 1 + prefixes = leader.get_prefixes() + self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) + + leader.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'high') + self.simulator.go(WAIT_TIME) + num_high += 1 + prefixes = leader.get_prefixes() + self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) + + for node in routers: + node.netdata_unpublish_prefix(ON_MESH_PREFIX) + self.simulator.go(WAIT_TIME) + num_med -= 1 + prefixes = leader.get_prefixes() + self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) + + leader.netdata_unpublish_prefix(ON_MESH_PREFIX) + self.simulator.go(WAIT_TIME) + num_high -= 1 + prefixes = leader.get_prefixes() + self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) + + for node in end_devs: + node.netdata_unpublish_prefix(ON_MESH_PREFIX) + self.simulator.go(WAIT_TIME) + num_low -= 1 + prefixes = leader.get_prefixes() + self.check_num_of_prefixes(prefixes, num_low, num_med, num_high) + + #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # Verify that when removing extra entries, non-preferred entries + # are removed first over preferred ones. Entries from routers are + # preferred over similar entries from end-devices. + + # Publish prefix entry on `end_dev1` and verify that it is added. + + end_dev1.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'med') + self.simulator.go(WAIT_TIME) + prefixes = leader.get_prefixes() + self.check_num_of_prefixes(prefixes, 0, 1, 0) + + # Publish same prefix on all routers (again as `med` preference). + # Verify that we reach the desired number of prefix entries in network + # data and that the entry from `end_dev1` is present in network data. + + for node in routers: + node.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'med') + self.simulator.go(WAIT_TIME) + prefixes = leader.get_prefixes() + self.check_num_of_prefixes(prefixes, 0, 1 + len(routers), 0) + self.assertTrue(1 + len(routers) >= DESIRED_NUM_ON_MESH_PREFIX) + # `prefixes` is a list of format 'fd00:1234:0:0::/64 paos low a802' + rlocs = [int(prefix.split(' ')[3], 16) for prefix in prefixes] + self.assertTrue(rlocs.count(end_dev1.get_addr16()) == 1) + + # Publish same prefix now with `high` preference on leader. + # Since it is `high` preference, it is added to network data + # which leads to total number of entries to go above the desired + # number temporarily and trigger other nodes to try to remove + # their entry. The entries from routers should be preferred over + # the one from `end_dev1` so that is the one we expect to be + # removed. We check that this is the case (i.e., the entry from + # `end_dev1` is no longer present in network data). + + leader.netdata_publish_prefix(ON_MESH_PREFIX, ON_MESH_FLAGS, 'high') + self.simulator.go(WAIT_TIME) + prefixes = leader.get_prefixes() + self.check_num_of_prefixes(prefixes, 0, 1 + len(routers), 1) + rlocs = [int(prefix.split(' ')[3], 16) for prefix in prefixes] + self.assertTrue(rlocs.count(end_dev1.get_addr16()) == 0) + + #--------------------------------------------------------------------------------- + # External route + + num = 0 + for node in nodes: + node.netdata_publish_route(EXTERNAL_ROUTE, EXTERNAL_FLAGS, 'low') + self.simulator.go(WAIT_TIME) + num += 1 + routes = leader.get_routes() + self.check_num_of_routes(routes, num, 0, 0) + + leader.netdata_unpublish_prefix(EXTERNAL_ROUTE) + leader.netdata_publish_route(EXTERNAL_ROUTE, EXTERNAL_FLAGS, 'high') + self.simulator.go(WAIT_TIME) + routes = leader.get_routes() + self.check_num_of_routes(routes, num - 1, 0, 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/test_srp_auto_start_mode.py b/tests/scripts/thread-cert/test_srp_auto_start_mode.py index 1d210dffb..2b18264e5 100755 --- a/tests/scripts/thread-cert/test_srp_auto_start_mode.py +++ b/tests/scripts/thread-cert/test_srp_auto_start_mode.py @@ -128,7 +128,7 @@ class SrpAutoStartMode(thread_cert.TestCase): # server1.srp_server_set_enabled(True) - self.simulator.go(2) + self.simulator.go(5) self.assertEqual(client.srp_client_get_state(), 'Enabled') self.assertEqual(client.srp_client_get_server_address(), server2_address) diff --git a/tests/scripts/thread-cert/test_srp_client_save_server_info.py b/tests/scripts/thread-cert/test_srp_client_save_server_info.py index 474ff12c9..2f3bf2f57 100755 --- a/tests/scripts/thread-cert/test_srp_client_save_server_info.py +++ b/tests/scripts/thread-cert/test_srp_client_save_server_info.py @@ -98,6 +98,7 @@ class SrpAutoStartMode(thread_cert.TestCase): # Enable auto start mode on client and check that server1 is used. server1.srp_server_set_enabled(True) + self.simulator.go(WAIT_TIME) client.srp_client_set_host_name('host') client.srp_client_set_host_address('2001::1') diff --git a/tests/scripts/thread-cert/test_srp_sub_type.py b/tests/scripts/thread-cert/test_srp_sub_type.py index 4563262cf..817ff40c8 100755 --- a/tests/scripts/thread-cert/test_srp_sub_type.py +++ b/tests/scripts/thread-cert/test_srp_sub_type.py @@ -79,6 +79,7 @@ class SrpSubType(thread_cert.TestCase): server.srp_server_set_enabled(True) client.srp_client_enable_auto_start_mode() + self.simulator.go(5) # Register a single service with 3 subtypes and verify that it worked. diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h index 79cf6d3a6..9f1a4ced2 100644 --- a/tests/toranj/openthread-core-toranj-config.h +++ b/tests/toranj/openthread-core-toranj-config.h @@ -103,6 +103,14 @@ */ #define OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE 1 +/** + * @def OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE + * + * Define to 1 to enable Network Data Publisher. + * + */ +#define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE 1 + /** * @def OPENTHREAD_CONFIG_LEGACY_ENABLE *