From c73320ff3ea5baf4d4c1944f31455fce4850a751 Mon Sep 17 00:00:00 2001 From: Simon Lin Date: Fri, 21 Aug 2020 23:40:58 +0800 Subject: [PATCH] [mlr] add MulticastListenersTable on BBR (#5292) This commit adds the Multicast Listener Table for BBR: - Implements Multicast Listener Table on BBR - Adds registered MAs to MulticastListenerTable - Reject when the table is full or the multicast address is invalid and return failed status - Respond with unsuccessful status along with failed addresses - Expire Listeners from MulticastListenerTable per second (using MinHeap for better performance) - MLR enhancement - Re-register only failed multicast addresses (used to register all registering multicast addresses) - Reference Device only APIs - Some APIs are defined for reference devices and tests. - Add tests - Add unit test: test_multicast_listener_table.cpp (runs for 1.2-bbr only) - Add functional tests --- Android.mk | 1 + include/openthread/backbone_router_ftd.h | 108 ++++ include/openthread/instance.h | 2 +- script/test | 24 +- src/cli/README.md | 43 ++ src/cli/cli.cpp | 70 +- src/cli/cli.hpp | 4 + src/core/BUILD.gn | 2 + src/core/CMakeLists.txt | 1 + src/core/Makefile.am | 610 +++++++++--------- src/core/api/backbone_router_ftd_api.cpp | 51 +- src/core/backbone_router/bbr_leader.cpp | 18 + src/core/backbone_router/bbr_local.cpp | 3 +- src/core/backbone_router/bbr_manager.cpp | 87 ++- src/core/backbone_router/bbr_manager.hpp | 25 +- .../multicast_listeners_table.cpp | 318 +++++++++ .../multicast_listeners_table.hpp | 241 +++++++ src/core/common/instance.hpp | 5 +- src/core/config/backbone_router.h | 15 + src/core/thread/mle_types.hpp | 24 +- src/core/thread/mlr_manager.cpp | 212 ++++-- src/core/thread/mlr_manager.hpp | 30 +- src/core/thread/thread_tlvs.hpp | 4 +- tests/scripts/thread-cert/node.py | 31 + ..._2_test_multicast_listener_registration.py | 228 ++++++- tests/unit/CMakeLists.txt | 25 +- tests/unit/Makefile.am | 4 + tests/unit/test_multicast_listeners_table.cpp | 213 ++++++ 28 files changed, 2002 insertions(+), 397 deletions(-) create mode 100644 src/core/backbone_router/multicast_listeners_table.cpp create mode 100644 src/core/backbone_router/multicast_listeners_table.hpp create mode 100644 tests/unit/test_multicast_listeners_table.cpp diff --git a/Android.mk b/Android.mk index 8a7ce8519..928cf4dfb 100644 --- a/Android.mk +++ b/Android.mk @@ -179,6 +179,7 @@ LOCAL_SRC_FILES := \ src/core/backbone_router/bbr_leader.cpp \ src/core/backbone_router/bbr_local.cpp \ src/core/backbone_router/bbr_manager.cpp \ + src/core/backbone_router/multicast_listeners_table.cpp \ src/core/coap/coap.cpp \ src/core/coap/coap_message.cpp \ src/core/coap/coap_secure.cpp \ diff --git a/include/openthread/backbone_router_ftd.h b/include/openthread/backbone_router_ftd.h index a4890d996..dda75b007 100644 --- a/include/openthread/backbone_router_ftd.h +++ b/include/openthread/backbone_router_ftd.h @@ -193,6 +193,114 @@ void otBackboneRouterConfigNextDuaRegistrationResponse(otInstance * const otIp6InterfaceIdentifier *aMlIid, uint8_t aStatus); +/** + * Represents the Multicast Listener events. + * + */ +typedef enum +{ + OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ADDED = 0, ///< Multicast Listener was added. + OT_BACKBONE_ROUTER_MULTICAST_LISTENER_REMOVED = 1, ///< Multicast Listener was removed or expired. +} otBackboneRouterMulticastListenerEvent; + +/** + * This function pointer is called whenever the Multicast Listeners change. + * + * @param[in] aContext The user context pointer. + * @param[in] aEvent The Multicast Listener event. + * @param[in] aAddress The Ip6 multicast address of the Multicast Listener. + * + */ +typedef void (*otBackboneRouterMulticastListenerCallback)(void * aContext, + otBackboneRouterMulticastListenerEvent aEvent, + const otIp6Address * aAddress); + +/** + * This method sets the Backbone Router Multicast Listener callback. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aCallback A pointer to the Multicast Listener callback. + * @param[in] aContext A user context pointer. + * + */ +void otBackboneRouterSetMulticastListenerCallback(otInstance * aInstance, + otBackboneRouterMulticastListenerCallback aCallback, + void * aContext); + +/** + * This method clears the Multicast Listeners. + * + * Note: available only when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled. + * Only used for test and certification. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @sa otBackboneRouterMulticastListenerAdd + * @sa otBackboneRouterMulticastListenerGetNext + * + */ +void otBackboneRouterMulticastListenerClear(otInstance *aInstance); + +/** + * This method adds a Multicast Listener. + * + * Note: available only when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled. + * Only used for test and certification. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aAddress The Multicast Listener address. + * @param[in] aTimeout The timeout (in seconds) of the Multicast Listener, or 0 to use the default MLR timeout. + * + * @retval OT_ERROR_NONE If the Multicast Listener was successfully added. + * @retval OT_ERROR_INVALID_ARGS If the Multicast Listener address was invalid. + * @retval OT_ERROR_NO_BUFS No space available to save the Multicast Listener. + * + * @sa otBackboneRouterMulticastListenerClear + * @sa otBackboneRouterMulticastListenerGetNext + * + */ +otError otBackboneRouterMulticastListenerAdd(otInstance *aInstance, const otIp6Address *aAddress, uint32_t aTimeout); + +#define OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT \ + 0 ///< Initializer for otBackboneRouterMulticastListenerIterator + +typedef uint16_t otBackboneRouterMulticastListenerIterator; ///< Used to iterate through Multicast Listeners. + +/** + * This structure represents a Backbone Router Multicast Listener info. + * + */ +typedef struct otBackboneRouterMulticastListenerInfo +{ + otIp6Address mAddress; // Multicast Listener address. + uint32_t mTimeout; // Timeout (in seconds). +} otBackboneRouterMulticastListenerInfo; + +/** + * This function gets the next Multicast Listener info (using an iterator). + * + * Note: available only when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled. + * Only used for test and certification. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[inout] aIterator A pointer to the iterator. On success the iterator will be updated to point to next + * Multicast Listener. To get the first entry the iterator should be set to + * OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT. + * @param[out] aListenerInfo A pointer to an `otBackboneRouterMulticastListenerInfo` where information of next + * Multicast Listener is placed (on success). + * + * @retval OT_ERROR_NONE Successfully found the next Multicast Listener info (@p aListenerInfo was successfully + * updated). + * @retval OT_ERROR_NOT_FOUND No subsequent Multicast Listener info was found. + * + * @sa otBackboneRouterMulticastListenerClear + * @sa otBackboneRouterMulticastListenerAdd + * + */ +otError otBackboneRouterMulticastListenerGetNext(otInstance * aInstance, + otBackboneRouterMulticastListenerIterator *aIterator, + otBackboneRouterMulticastListenerInfo * aListenerInfo); + /** * @} * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index da60c784c..b6e77899d 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 (24) +#define OPENTHREAD_API_VERSION (25) /** * @addtogroup api-instance diff --git a/script/test b/script/test index b08f46a2c..3505fb844 100755 --- a/script/test +++ b/script/test @@ -138,16 +138,30 @@ do_clean() rm -rfv "${OT_BUILDDIR}" || sudo rm -rfv "${OT_BUILDDIR}" } -do_unit() +do_unit_version() { - local builddir="${OT_BUILDDIR}/cmake/openthread-simulation-${THREAD_VERSION}" + local version=$1 + local builddir="${OT_BUILDDIR}/cmake/openthread-simulation-${version}" + if [[ ! -d ${builddir} ]]; then - echo "Cannot find build directory!" + echo "Cannot find build directory: ${builddir}" exit 1 fi - cd "${builddir}" - ninja test + ( + cd "${builddir}" + ninja test + ) +} + +do_unit() +{ + do_unit_version "${THREAD_VERSION}" + + if [[ ${THREAD_VERSION} == "1.2" ]]; then + do_unit_version "1.2-bbr" + do_unit_version "1.1" + fi } do_cert() diff --git a/src/cli/README.md b/src/cli/README.md index 794ba44fd..5de993fbe 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -142,6 +142,49 @@ known status value: Done ``` +### bbr mgmt mlr listener + +Show the Multicast Listeners. + +Only for testing/reference Backbone Router device. + +```bash +> bbr mgmt mlr listener +ff04:0:0:0:0:0:0:abcd 3534000 +ff04:0:0:0:0:0:0:eeee 3537610 +Done +``` + +### bbr mgmt mlr listener add \ \[\\] + +Add a Multicast Listener with a given Ip6 multicast address and timeout (in seconds). + +Only for testing/reference Backbone Router device. + +```bash +> bbr mgmt mlr listener add ff04::1 +Done +> bbr mgmt mlr listener add ff04::2 300 +Done +> bbr mgmt mlr listener +ff04:0:0:0:0:0:0:2 261 +ff04:0:0:0:0:0:0:1 3522 +Done +``` + +### bbr mgmt mlr listener clear + +Removes all the Multicast Listeners. + +Only for testing/reference Backbone Router device. + +```bash +> bbr mgmt mlr listener clear +Done +> bbr mgmt mlr listener +Done +``` + ### bbr state Show local Backbone state ([`Disabled`,`Primary`, `Secondary`]) for Thread 1.2 FTD. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 02dc375e8..dffab51f8 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -527,13 +527,15 @@ void Interpreter::ProcessBackboneRouter(uint8_t aArgsLength, char *aArgs[]) { unsigned long value; - VerifyOrExit((aArgsLength == 3 || aArgsLength == 4), error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(aArgsLength >= 2, error = OT_ERROR_INVALID_COMMAND); if (strcmp(aArgs[1], "dua") == 0) { otIp6InterfaceIdentifier *mlIid = nullptr; otIp6InterfaceIdentifier iid; + VerifyOrExit((aArgsLength == 3 || aArgsLength == 4), error = OT_ERROR_INVALID_ARGS); + SuccessOrExit(error = ParseUnsignedLong(aArgs[2], value)); if (aArgsLength == 4) @@ -546,6 +548,11 @@ void Interpreter::ProcessBackboneRouter(uint8_t aArgsLength, char *aArgs[]) otBackboneRouterConfigNextDuaRegistrationResponse(mInstance, mlIid, static_cast(value)); ExitNow(); } + else if (strcmp(aArgs[1], "mlr") == 0) + { + error = ProcessBackboneRouterMgmtMlr(aArgsLength - 2, aArgs + 2); + ExitNow(); + } } #endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE SuccessOrExit(error = ProcessBackboneRouterLocal(aArgsLength, aArgs)); @@ -553,11 +560,70 @@ void Interpreter::ProcessBackboneRouter(uint8_t aArgsLength, char *aArgs[]) exit: #endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE - AppendResult(error); } #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE +otError Interpreter::ProcessBackboneRouterMgmtMlr(uint8_t aArgsLength, char **aArgs) +{ + OT_UNUSED_VARIABLE(aArgsLength); + OT_UNUSED_VARIABLE(aArgs); + + otError error = OT_ERROR_INVALID_COMMAND; + + VerifyOrExit(aArgsLength >= 1, OT_NOOP); + + if (!strcmp(aArgs[0], "listener")) + { + if (aArgsLength == 1) + { + PrintMulticastListenersTable(); + error = OT_ERROR_NONE; + } + else if (!strcmp(aArgs[1], "clear")) + { + otBackboneRouterMulticastListenerClear(mInstance); + error = OT_ERROR_NONE; + } + else if (!strcmp(aArgs[1], "add")) + { + struct otIp6Address address; + unsigned long value; + uint32_t timeout = 0; + + VerifyOrExit(aArgsLength == 3 || aArgsLength == 4, error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit(error = otIp6AddressFromString(aArgs[2], &address)); + if (aArgsLength == 4) + { + SuccessOrExit(error = ParseUnsignedLong(aArgs[3], value)); + timeout = static_cast(value); + } + + error = otBackboneRouterMulticastListenerAdd(mInstance, &address, timeout); + } + } + +exit: + return error; +} + +void Interpreter::PrintMulticastListenersTable(void) +{ + otBackboneRouterMulticastListenerIterator iter = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT; + otBackboneRouterMulticastListenerInfo listenerInfo; + + while (otBackboneRouterMulticastListenerGetNext(mInstance, &iter, &listenerInfo) == OT_ERROR_NONE) + { + OutputIp6Address(listenerInfo.mAddress); + OutputFormat(" %u\r\n", listenerInfo.mTimeout); + } +} + +#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + otError Interpreter::ProcessBackboneRouterLocal(uint8_t aArgsLength, char *aArgs[]) { otError error = OT_ERROR_NONE; diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index d635c419e..087749dc5 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -275,6 +275,10 @@ private: #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE otError ProcessBackboneRouterLocal(uint8_t aArgsLength, char *aArgs[]); +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + otError ProcessBackboneRouterMgmtMlr(uint8_t aArgsLength, char **aArgs); + void PrintMulticastListenersTable(void); +#endif #endif void ProcessDomainName(uint8_t aArgsLength, char *aArgs[]); diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 866722c26..f7be24008 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -325,6 +325,8 @@ openthread_core_files = [ "backbone_router/bbr_local.hpp", "backbone_router/bbr_manager.cpp", "backbone_router/bbr_manager.hpp", + "backbone_router/multicast_listeners_table.cpp", + "backbone_router/multicast_listeners_table.hpp", "coap/coap.cpp", "coap/coap.hpp", "coap/coap_message.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 09b4f6ba9..156ceefff 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -105,6 +105,7 @@ set(COMMON_SOURCES backbone_router/bbr_leader.cpp backbone_router/bbr_local.cpp backbone_router/bbr_manager.cpp + backbone_router/multicast_listeners_table.cpp coap/coap.cpp coap/coap_message.cpp coap/coap_secure.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 41cfccbeb..8404a3cc2 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -107,152 +107,153 @@ libopenthread_mtd_a_CPPFLAGS = \ # unit" # -SOURCES_COMMON = \ - api/backbone_router_api.cpp \ - api/backbone_router_ftd_api.cpp \ - api/border_router_api.cpp \ - api/channel_manager_api.cpp \ - api/channel_monitor_api.cpp \ - api/child_supervision_api.cpp \ - api/coap_api.cpp \ - api/coap_secure_api.cpp \ - api/commissioner_api.cpp \ - api/crypto_api.cpp \ - api/dataset_api.cpp \ - api/dataset_ftd_api.cpp \ - api/diags_api.cpp \ - api/dns_api.cpp \ - api/entropy_api.cpp \ - api/heap_api.cpp \ - api/icmp6_api.cpp \ - api/instance_api.cpp \ - api/ip6_api.cpp \ - api/jam_detection_api.cpp \ - api/joiner_api.cpp \ - api/link_api.cpp \ - api/link_raw_api.cpp \ - api/logging_api.cpp \ - api/message_api.cpp \ - api/netdata_api.cpp \ - api/netdiag_api.cpp \ - api/network_time_api.cpp \ - api/random_crypto_api.cpp \ - api/random_noncrypto_api.cpp \ - api/server_api.cpp \ - api/sntp_api.cpp \ - api/tasklet_api.cpp \ - api/thread_api.cpp \ - api/thread_ftd_api.cpp \ - api/udp_api.cpp \ - backbone_router/bbr_leader.cpp \ - backbone_router/bbr_local.cpp \ - backbone_router/bbr_manager.cpp \ - coap/coap.cpp \ - coap/coap_message.cpp \ - coap/coap_secure.cpp \ - common/crc16.cpp \ - common/instance.cpp \ - common/logging.cpp \ - common/message.cpp \ - common/notifier.cpp \ - common/random_manager.cpp \ - common/settings.cpp \ - common/string.cpp \ - common/tasklet.cpp \ - common/time_ticker.cpp \ - common/timer.cpp \ - common/tlvs.cpp \ - common/trickle_timer.cpp \ - crypto/aes_ccm.cpp \ - crypto/aes_ecb.cpp \ - crypto/ecdsa.cpp \ - crypto/hmac_sha256.cpp \ - crypto/mbedtls.cpp \ - crypto/pbkdf2_cmac.cpp \ - crypto/sha256.cpp \ - diags/factory_diags.cpp \ - mac/channel_mask.cpp \ - mac/data_poll_handler.cpp \ - mac/data_poll_sender.cpp \ - mac/link_raw.cpp \ - mac/mac.cpp \ - mac/mac_filter.cpp \ - mac/mac_frame.cpp \ - mac/mac_types.cpp \ - mac/sub_mac.cpp \ - mac/sub_mac_callbacks.cpp \ - meshcop/announce_begin_client.cpp \ - meshcop/border_agent.cpp \ - meshcop/commissioner.cpp \ - meshcop/dataset.cpp \ - meshcop/dataset_local.cpp \ - meshcop/dataset_manager.cpp \ - meshcop/dataset_manager_ftd.cpp \ - meshcop/dtls.cpp \ - meshcop/energy_scan_client.cpp \ - meshcop/joiner.cpp \ - meshcop/joiner_router.cpp \ - meshcop/meshcop.cpp \ - meshcop/meshcop_leader.cpp \ - meshcop/meshcop_tlvs.cpp \ - meshcop/panid_query_client.cpp \ - meshcop/timestamp.cpp \ - net/dhcp6_client.cpp \ - net/dhcp6_server.cpp \ - net/dns_client.cpp \ - net/icmp6.cpp \ - net/ip6.cpp \ - net/ip6_address.cpp \ - net/ip6_filter.cpp \ - net/ip6_headers.cpp \ - net/ip6_mpl.cpp \ - net/netif.cpp \ - net/sntp_client.cpp \ - net/udp6.cpp \ - radio/radio.cpp \ - radio/radio_callbacks.cpp \ - radio/radio_platform.cpp \ - thread/address_resolver.cpp \ - thread/announce_begin_server.cpp \ - thread/announce_sender.cpp \ - thread/child_table.cpp \ - thread/csl_tx_scheduler.cpp \ - thread/discover_scanner.cpp \ - thread/dua_manager.cpp \ - thread/energy_scan_server.cpp \ - thread/indirect_sender.cpp \ - thread/key_manager.cpp \ - thread/link_quality.cpp \ - thread/lowpan.cpp \ - thread/mesh_forwarder.cpp \ - thread/mesh_forwarder_ftd.cpp \ - thread/mesh_forwarder_mtd.cpp \ - thread/mle.cpp \ - thread/mle_router.cpp \ - thread/mle_types.cpp \ - thread/mlr_manager.cpp \ - thread/neighbor_table.cpp \ - thread/network_data.cpp \ - thread/network_data_leader.cpp \ - thread/network_data_leader_ftd.cpp \ - thread/network_data_local.cpp \ - thread/network_data_notifier.cpp \ - thread/network_diagnostic.cpp \ - thread/panid_query_server.cpp \ - thread/router_table.cpp \ - thread/src_match_controller.cpp \ - thread/thread_netif.cpp \ - thread/time_sync_service.cpp \ - thread/topology.cpp \ - utils/channel_manager.cpp \ - utils/channel_monitor.cpp \ - utils/child_supervision.cpp \ - utils/flash.cpp \ - utils/heap.cpp \ - utils/jam_detector.cpp \ - utils/otns.cpp \ - utils/parse_cmdline.cpp \ - utils/slaac_address.cpp \ +SOURCES_COMMON = \ + api/backbone_router_api.cpp \ + api/backbone_router_ftd_api.cpp \ + api/border_router_api.cpp \ + api/channel_manager_api.cpp \ + api/channel_monitor_api.cpp \ + api/child_supervision_api.cpp \ + api/coap_api.cpp \ + api/coap_secure_api.cpp \ + api/commissioner_api.cpp \ + api/crypto_api.cpp \ + api/dataset_api.cpp \ + api/dataset_ftd_api.cpp \ + api/diags_api.cpp \ + api/dns_api.cpp \ + api/entropy_api.cpp \ + api/heap_api.cpp \ + api/icmp6_api.cpp \ + api/instance_api.cpp \ + api/ip6_api.cpp \ + api/jam_detection_api.cpp \ + api/joiner_api.cpp \ + api/link_api.cpp \ + api/link_raw_api.cpp \ + api/logging_api.cpp \ + api/message_api.cpp \ + api/netdata_api.cpp \ + api/netdiag_api.cpp \ + api/network_time_api.cpp \ + api/random_crypto_api.cpp \ + api/random_noncrypto_api.cpp \ + api/server_api.cpp \ + api/sntp_api.cpp \ + api/tasklet_api.cpp \ + api/thread_api.cpp \ + api/thread_ftd_api.cpp \ + api/udp_api.cpp \ + backbone_router/bbr_leader.cpp \ + backbone_router/bbr_local.cpp \ + backbone_router/bbr_manager.cpp \ + backbone_router/multicast_listeners_table.cpp \ + coap/coap.cpp \ + coap/coap_message.cpp \ + coap/coap_secure.cpp \ + common/crc16.cpp \ + common/instance.cpp \ + common/logging.cpp \ + common/message.cpp \ + common/notifier.cpp \ + common/random_manager.cpp \ + common/settings.cpp \ + common/string.cpp \ + common/tasklet.cpp \ + common/time_ticker.cpp \ + common/timer.cpp \ + common/tlvs.cpp \ + common/trickle_timer.cpp \ + crypto/aes_ccm.cpp \ + crypto/aes_ecb.cpp \ + crypto/ecdsa.cpp \ + crypto/hmac_sha256.cpp \ + crypto/mbedtls.cpp \ + crypto/pbkdf2_cmac.cpp \ + crypto/sha256.cpp \ + diags/factory_diags.cpp \ + mac/channel_mask.cpp \ + mac/data_poll_handler.cpp \ + mac/data_poll_sender.cpp \ + mac/link_raw.cpp \ + mac/mac.cpp \ + mac/mac_filter.cpp \ + mac/mac_frame.cpp \ + mac/mac_types.cpp \ + mac/sub_mac.cpp \ + mac/sub_mac_callbacks.cpp \ + meshcop/announce_begin_client.cpp \ + meshcop/border_agent.cpp \ + meshcop/commissioner.cpp \ + meshcop/dataset.cpp \ + meshcop/dataset_local.cpp \ + meshcop/dataset_manager.cpp \ + meshcop/dataset_manager_ftd.cpp \ + meshcop/dtls.cpp \ + meshcop/energy_scan_client.cpp \ + meshcop/joiner.cpp \ + meshcop/joiner_router.cpp \ + meshcop/meshcop.cpp \ + meshcop/meshcop_leader.cpp \ + meshcop/meshcop_tlvs.cpp \ + meshcop/panid_query_client.cpp \ + meshcop/timestamp.cpp \ + net/dhcp6_client.cpp \ + net/dhcp6_server.cpp \ + net/dns_client.cpp \ + net/icmp6.cpp \ + net/ip6.cpp \ + net/ip6_address.cpp \ + net/ip6_filter.cpp \ + net/ip6_headers.cpp \ + net/ip6_mpl.cpp \ + net/netif.cpp \ + net/sntp_client.cpp \ + net/udp6.cpp \ + radio/radio.cpp \ + radio/radio_callbacks.cpp \ + radio/radio_platform.cpp \ + thread/address_resolver.cpp \ + thread/announce_begin_server.cpp \ + thread/announce_sender.cpp \ + thread/child_table.cpp \ + thread/csl_tx_scheduler.cpp \ + thread/discover_scanner.cpp \ + thread/dua_manager.cpp \ + thread/energy_scan_server.cpp \ + thread/indirect_sender.cpp \ + thread/key_manager.cpp \ + thread/link_quality.cpp \ + thread/lowpan.cpp \ + thread/mesh_forwarder.cpp \ + thread/mesh_forwarder_ftd.cpp \ + thread/mesh_forwarder_mtd.cpp \ + thread/mle.cpp \ + thread/mle_router.cpp \ + thread/mle_types.cpp \ + thread/mlr_manager.cpp \ + thread/neighbor_table.cpp \ + thread/network_data.cpp \ + thread/network_data_leader.cpp \ + thread/network_data_leader_ftd.cpp \ + thread/network_data_local.cpp \ + thread/network_data_notifier.cpp \ + thread/network_diagnostic.cpp \ + thread/panid_query_server.cpp \ + thread/router_table.cpp \ + thread/src_match_controller.cpp \ + thread/thread_netif.cpp \ + thread/time_sync_service.cpp \ + thread/topology.cpp \ + utils/channel_manager.cpp \ + utils/channel_monitor.cpp \ + utils/child_supervision.cpp \ + utils/flash.cpp \ + utils/heap.cpp \ + utils/jam_detector.cpp \ + utils/otns.cpp \ + utils/parse_cmdline.cpp \ + utils/slaac_address.cpp \ $(NULL) EXTRA_DIST = \ @@ -318,164 +319,165 @@ nodist_libopenthread_radio_a_SOURCES = \ endif # OPENTHREAD_ENABLE_VENDOR_EXTENSION -HEADERS_COMMON = \ - openthread-core-config.h \ - backbone_router/bbr_leader.hpp \ - backbone_router/bbr_local.hpp \ - backbone_router/bbr_manager.hpp \ - coap/coap.hpp \ - coap/coap_message.hpp \ - coap/coap_secure.hpp \ - common/bit_vector.hpp \ - common/clearable.hpp \ - common/code_utils.hpp \ - common/crc16.hpp \ - common/debug.hpp \ - common/encoding.hpp \ - common/equatable.hpp \ - common/extension.hpp \ - common/instance.hpp \ - common/linked_list.hpp \ - common/locator.hpp \ - common/locator-getters.hpp \ - common/logging.hpp \ - common/message.hpp \ - common/new.hpp \ - common/non_copyable.hpp \ - common/notifier.hpp \ - common/pool.hpp \ - common/random.hpp \ - common/random_manager.hpp \ - common/settings.hpp \ - common/string.hpp \ - common/tasklet.hpp \ - common/time.hpp \ - common/time_ticker.hpp \ - common/timer.hpp \ - common/tlvs.hpp \ - common/trickle_timer.hpp \ - config/announce_sender.h \ - config/backbone_router.h \ - config/border_router.h \ - config/channel_manager.h \ - config/channel_monitor.h \ - config/child_supervision.h \ - config/coap.h \ - config/commissioner.h \ - config/dhcp6_client.h \ - config/dhcp6_server.h \ - config/diag.h \ - config/dns_client.h \ - config/ip6.h \ - config/joiner.h \ - config/link_quality.h \ - config/link_raw.h \ - config/logging.h \ - config/mac.h \ - config/mle.h \ - config/openthread-core-config-check.h \ - config/openthread-core-default-config.h \ - config/parent_search.h \ - config/platform.h \ - config/sntp_client.h \ - config/time_sync.h \ - config/tmf.h \ - crypto/aes_ccm.hpp \ - crypto/aes_ecb.hpp \ - crypto/ecdsa.hpp \ - crypto/hmac_sha256.hpp \ - crypto/mbedtls.hpp \ - crypto/pbkdf2_cmac.h \ - crypto/sha256.hpp \ - diags/factory_diags.hpp \ - mac/channel_mask.hpp \ - mac/data_poll_handler.hpp \ - mac/data_poll_sender.hpp \ - mac/link_raw.hpp \ - mac/mac.hpp \ - mac/mac_filter.hpp \ - mac/mac_frame.hpp \ - mac/mac_types.hpp \ - mac/sub_mac.hpp \ - meshcop/announce_begin_client.hpp \ - meshcop/border_agent.hpp \ - meshcop/commissioner.hpp \ - meshcop/dataset.hpp \ - meshcop/dataset_local.hpp \ - meshcop/dataset_manager.hpp \ - meshcop/dtls.hpp \ - meshcop/energy_scan_client.hpp \ - meshcop/joiner.hpp \ - meshcop/joiner_router.hpp \ - meshcop/meshcop.hpp \ - meshcop/meshcop_leader.hpp \ - meshcop/meshcop_tlvs.hpp \ - meshcop/panid_query_client.hpp \ - meshcop/timestamp.hpp \ - net/dhcp6.hpp \ - net/dhcp6_client.hpp \ - net/dhcp6_server.hpp \ - net/dns_client.hpp \ - net/dns_headers.hpp \ - net/icmp6.hpp \ - net/ip6.hpp \ - net/ip6_address.hpp \ - net/ip6_filter.hpp \ - net/ip6_headers.hpp \ - net/ip6_mpl.hpp \ - net/netif.hpp \ - net/sntp_client.hpp \ - net/socket.hpp \ - net/tcp.hpp \ - net/udp6.hpp \ - radio/radio.hpp \ - thread/address_resolver.hpp \ - thread/announce_begin_server.hpp \ - thread/announce_sender.hpp \ - thread/child_mask.hpp \ - thread/child_table.hpp \ - thread/csl_tx_scheduler.hpp \ - thread/discover_scanner.hpp \ - thread/dua_manager.hpp \ - thread/energy_scan_server.hpp \ - thread/indirect_sender.hpp \ - thread/indirect_sender_frame_context.hpp \ - thread/key_manager.hpp \ - thread/link_quality.hpp \ - thread/lowpan.hpp \ - thread/mesh_forwarder.hpp \ - thread/mle.hpp \ - thread/mle_router.hpp \ - thread/mle_tlvs.hpp \ - thread/mle_types.hpp \ - thread/mlr_manager.hpp \ - thread/mlr_types.hpp \ - thread/neighbor_table.hpp \ - thread/network_data.hpp \ - thread/network_data_leader.hpp \ - thread/network_data_leader_ftd.hpp \ - thread/network_data_local.hpp \ - thread/network_data_notifier.hpp \ - thread/network_data_tlvs.hpp \ - thread/network_diagnostic.hpp \ - thread/network_diagnostic_tlvs.hpp \ - thread/panid_query_server.hpp \ - thread/router_table.hpp \ - thread/src_match_controller.hpp \ - thread/thread_netif.hpp \ - thread/thread_tlvs.hpp \ - thread/thread_uri_paths.hpp \ - thread/time_sync_service.hpp \ - thread/topology.hpp \ - utils/channel_manager.hpp \ - utils/channel_monitor.hpp \ - utils/child_supervision.hpp \ - utils/flash.hpp \ - utils/heap.hpp \ - utils/jam_detector.hpp \ - utils/otns.hpp \ - utils/parse_cmdline.hpp \ - utils/slaac_address.hpp \ +HEADERS_COMMON = \ + openthread-core-config.h \ + backbone_router/bbr_leader.hpp \ + backbone_router/bbr_local.hpp \ + backbone_router/bbr_manager.hpp \ + backbone_router/multicast_listeners_table.hpp \ + coap/coap.hpp \ + coap/coap_message.hpp \ + coap/coap_secure.hpp \ + common/bit_vector.hpp \ + common/clearable.hpp \ + common/code_utils.hpp \ + common/crc16.hpp \ + common/debug.hpp \ + common/encoding.hpp \ + common/equatable.hpp \ + common/extension.hpp \ + common/instance.hpp \ + common/linked_list.hpp \ + common/locator.hpp \ + common/locator-getters.hpp \ + common/logging.hpp \ + common/message.hpp \ + common/new.hpp \ + common/non_copyable.hpp \ + common/notifier.hpp \ + common/pool.hpp \ + common/random.hpp \ + common/random_manager.hpp \ + common/settings.hpp \ + common/string.hpp \ + common/tasklet.hpp \ + common/time.hpp \ + common/time_ticker.hpp \ + common/timer.hpp \ + common/tlvs.hpp \ + common/trickle_timer.hpp \ + config/announce_sender.h \ + config/backbone_router.h \ + config/border_router.h \ + config/channel_manager.h \ + config/channel_monitor.h \ + config/child_supervision.h \ + config/coap.h \ + config/commissioner.h \ + config/dhcp6_client.h \ + config/dhcp6_server.h \ + config/diag.h \ + config/dns_client.h \ + config/ip6.h \ + config/joiner.h \ + config/link_quality.h \ + config/link_raw.h \ + config/logging.h \ + config/mac.h \ + config/mle.h \ + config/openthread-core-config-check.h \ + config/openthread-core-default-config.h \ + config/parent_search.h \ + config/platform.h \ + config/sntp_client.h \ + config/time_sync.h \ + config/tmf.h \ + crypto/aes_ccm.hpp \ + crypto/aes_ecb.hpp \ + crypto/ecdsa.hpp \ + crypto/hmac_sha256.hpp \ + crypto/mbedtls.hpp \ + crypto/pbkdf2_cmac.h \ + crypto/sha256.hpp \ + diags/factory_diags.hpp \ + mac/channel_mask.hpp \ + mac/data_poll_handler.hpp \ + mac/data_poll_sender.hpp \ + mac/link_raw.hpp \ + mac/mac.hpp \ + mac/mac_filter.hpp \ + mac/mac_frame.hpp \ + mac/mac_types.hpp \ + mac/sub_mac.hpp \ + meshcop/announce_begin_client.hpp \ + meshcop/border_agent.hpp \ + meshcop/commissioner.hpp \ + meshcop/dataset.hpp \ + meshcop/dataset_local.hpp \ + meshcop/dataset_manager.hpp \ + meshcop/dtls.hpp \ + meshcop/energy_scan_client.hpp \ + meshcop/joiner.hpp \ + meshcop/joiner_router.hpp \ + meshcop/meshcop.hpp \ + meshcop/meshcop_leader.hpp \ + meshcop/meshcop_tlvs.hpp \ + meshcop/panid_query_client.hpp \ + meshcop/timestamp.hpp \ + net/dhcp6.hpp \ + net/dhcp6_client.hpp \ + net/dhcp6_server.hpp \ + net/dns_client.hpp \ + net/dns_headers.hpp \ + net/icmp6.hpp \ + net/ip6.hpp \ + net/ip6_address.hpp \ + net/ip6_filter.hpp \ + net/ip6_headers.hpp \ + net/ip6_mpl.hpp \ + net/netif.hpp \ + net/sntp_client.hpp \ + net/socket.hpp \ + net/tcp.hpp \ + net/udp6.hpp \ + radio/radio.hpp \ + thread/address_resolver.hpp \ + thread/announce_begin_server.hpp \ + thread/announce_sender.hpp \ + thread/child_mask.hpp \ + thread/child_table.hpp \ + thread/csl_tx_scheduler.hpp \ + thread/discover_scanner.hpp \ + thread/dua_manager.hpp \ + thread/energy_scan_server.hpp \ + thread/indirect_sender.hpp \ + thread/indirect_sender_frame_context.hpp \ + thread/key_manager.hpp \ + thread/link_quality.hpp \ + thread/lowpan.hpp \ + thread/mesh_forwarder.hpp \ + thread/mle.hpp \ + thread/mle_router.hpp \ + thread/mle_tlvs.hpp \ + thread/mle_types.hpp \ + thread/mlr_manager.hpp \ + thread/mlr_types.hpp \ + thread/neighbor_table.hpp \ + thread/network_data.hpp \ + thread/network_data_leader.hpp \ + thread/network_data_leader_ftd.hpp \ + thread/network_data_local.hpp \ + thread/network_data_notifier.hpp \ + thread/network_data_tlvs.hpp \ + thread/network_diagnostic.hpp \ + thread/network_diagnostic_tlvs.hpp \ + thread/panid_query_server.hpp \ + thread/router_table.hpp \ + thread/src_match_controller.hpp \ + thread/thread_netif.hpp \ + thread/thread_tlvs.hpp \ + thread/thread_uri_paths.hpp \ + thread/time_sync_service.hpp \ + thread/topology.hpp \ + utils/channel_manager.hpp \ + utils/channel_monitor.hpp \ + utils/child_supervision.hpp \ + utils/flash.hpp \ + utils/heap.hpp \ + utils/jam_detector.hpp \ + utils/otns.hpp \ + utils/parse_cmdline.hpp \ + utils/slaac_address.hpp \ $(NULL) noinst_HEADERS = \ diff --git a/src/core/api/backbone_router_ftd_api.cpp b/src/core/api/backbone_router_ftd_api.cpp index cf8d02f86..bffec15bb 100644 --- a/src/core/api/backbone_router_ftd_api.cpp +++ b/src/core/api/backbone_router_ftd_api.cpp @@ -111,6 +111,15 @@ otError otBackboneRouterGetDomainPrefix(otInstance *aInstance, otBorderRouterCon *static_cast(aConfig)); } +void otBackboneRouterSetMulticastListenerCallback(otInstance * aInstance, + otBackboneRouterMulticastListenerCallback aCallback, + void * aContext) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().SetCallback(aCallback, aContext); +} + #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE void otBackboneRouterConfigNextDuaRegistrationResponse(otInstance * aInstance, const otIp6InterfaceIdentifier *aMlIid, @@ -121,6 +130,46 @@ void otBackboneRouterConfigNextDuaRegistrationResponse(otInstance * instance.Get().ConfigNextDuaRegistrationResponse( static_cast(aMlIid), aStatus); } -#endif + +void otBackboneRouterMulticastListenerClear(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().Clear(); +} + +otError otBackboneRouterMulticastListenerAdd(otInstance *aInstance, const otIp6Address *aAddress, uint32_t aTimeout) +{ + Instance &instance = *static_cast(aInstance); + + OT_ASSERT(aAddress != nullptr); + + if (aTimeout == 0) + { + BackboneRouter::BackboneRouterConfig config; + instance.Get().GetConfig(config); + aTimeout = config.mMlrTimeout; + } + + aTimeout = + aTimeout > static_cast(Mle::kMlrTimeoutMax) ? static_cast(Mle::kMlrTimeoutMax) : aTimeout; + aTimeout = Time::SecToMsec(aTimeout); + + return instance.Get().Add(static_cast(*aAddress), + TimerMilli::GetNow() + aTimeout); +} + +otError otBackboneRouterMulticastListenerGetNext(otInstance * aInstance, + otChildIp6AddressIterator * aIterator, + otBackboneRouterMulticastListenerInfo *aListenerInfo) +{ + Instance &instance = *static_cast(aInstance); + + OT_ASSERT(aIterator != nullptr); + OT_ASSERT(aListenerInfo != nullptr); + + return instance.Get().GetNext(*aIterator, *aListenerInfo); +} +#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE #endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE diff --git a/src/core/backbone_router/bbr_leader.cpp b/src/core/backbone_router/bbr_leader.cpp index da1cf2968..4d2b5fc31 100644 --- a/src/core/backbone_router/bbr_leader.cpp +++ b/src/core/backbone_router/bbr_leader.cpp @@ -180,6 +180,7 @@ void Leader::UpdateBackboneRouterPrimary(void) { BackboneRouterConfig config; State state; + uint32_t origMlrTimeout; IgnoreError(Get().GetBackboneRouterPrimary(config)); @@ -217,6 +218,23 @@ void Leader::UpdateBackboneRouterPrimary(void) state = kStateUnchanged; } + // Restrain the range of MLR timeout to be always valid + if (config.mServer16 != Mac::kShortAddrInvalid) + { + origMlrTimeout = config.mMlrTimeout; + config.mMlrTimeout = config.mMlrTimeout < static_cast(Mle::kMlrTimeoutMin) + ? static_cast(Mle::kMlrTimeoutMin) + : config.mMlrTimeout; + config.mMlrTimeout = config.mMlrTimeout > static_cast(Mle::kMlrTimeoutMax) + ? static_cast(Mle::kMlrTimeoutMax) + : config.mMlrTimeout; + + if (config.mMlrTimeout != origMlrTimeout) + { + otLogNoteBbr("Leader MLR Timeout is normalized from %u to %u", origMlrTimeout, config.mMlrTimeout); + } + } + mConfig = config; LogBackboneRouterPrimary(state, mConfig); diff --git a/src/core/backbone_router/bbr_local.cpp b/src/core/backbone_router/bbr_local.cpp index 968bc5a27..ec254f4cb 100644 --- a/src/core/backbone_router/bbr_local.cpp +++ b/src/core/backbone_router/bbr_local.cpp @@ -135,7 +135,8 @@ otError Local::SetConfig(const BackboneRouterConfig &aConfig) otError error = OT_ERROR_NONE; bool update = false; - VerifyOrExit(aConfig.mMlrTimeout >= Mle::kMlrTimeoutMin, error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(aConfig.mMlrTimeout >= Mle::kMlrTimeoutMin && aConfig.mMlrTimeout <= Mle::kMlrTimeoutMax, + error = OT_ERROR_INVALID_ARGS); // Validate configuration according to Thread 1.2.1 Specification 5.21.3.3: // "The Reregistration Delay in seconds MUST be lower than (0.5 * MLR Timeout). It MUST be at least 1." VerifyOrExit(aConfig.mReregistrationDelay >= 1, error = OT_ERROR_INVALID_ARGS); diff --git a/src/core/backbone_router/bbr_manager.cpp b/src/core/backbone_router/bbr_manager.cpp index 27a995830..24f00c673 100644 --- a/src/core/backbone_router/bbr_manager.cpp +++ b/src/core/backbone_router/bbr_manager.cpp @@ -53,6 +53,8 @@ Manager::Manager(Instance &aInstance) : InstanceLocator(aInstance) , mMulticastListenerRegistration(OT_URI_PATH_MLR, Manager::HandleMulticastListenerRegistration, this) , mDuaRegistration(OT_URI_PATH_DUA_REGISTRATION_REQUEST, Manager::HandleDuaRegistration, this) + , mMulticastListenersTable(aInstance) + , mTimer(aInstance, Manager::HandleTimer, this) #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE , mDuaResponseStatus(ThreadStatusTlv::kDuaSuccess) , mDuaResponseIsSpecified(false) @@ -68,20 +70,39 @@ void Manager::HandleNotifierEvents(Events aEvents) { Get().RemoveResource(mMulticastListenerRegistration); Get().RemoveResource(mDuaRegistration); + mTimer.Stop(); + mMulticastListenersTable.Clear(); } else { Get().AddResource(mMulticastListenerRegistration); Get().AddResource(mDuaRegistration); + if (!mTimer.IsRunning()) + { + mTimer.Start(kTimerInterval); + } } } } +void Manager::HandleTimer(void) +{ + mMulticastListenersTable.Expire(); + + mTimer.Start(kTimerInterval); +} + void Manager::HandleMulticastListenerRegistration(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { otError error = OT_ERROR_NONE; bool isPrimary = Get().IsPrimary(); ThreadStatusTlv::MlrStatus status = ThreadStatusTlv::kMlrSuccess; + uint16_t addressesOffset, addressesLength; + Ip6::Address address; + Ip6::Address failedAddresses[kIPv6AddressesNumMax]; + uint8_t failedAddressNum = 0; + TimeMilli expireTime; + BackboneRouterConfig config; VerifyOrExit(aMessage.IsConfirmable() && aMessage.GetCode() == OT_COAP_CODE_POST, error = OT_ERROR_PARSE); @@ -90,18 +111,63 @@ void Manager::HandleMulticastListenerRegistration(const Coap::Message &aMessage, // TODO: (MLR) send configured MLR response for Reference Device // TODO: (MLR) handle Commissioner Session TLV // TODO: (MLR) handle Timeout TLV - // TODO: (MLR) handle addresses, updated listener groups, send BMLR.req + + VerifyOrExit(ThreadTlv::FindTlvValueOffset(aMessage, IPv6AddressesTlv::kIPv6Addresses, addressesOffset, + addressesLength) == OT_ERROR_NONE, + error = OT_ERROR_PARSE); + VerifyOrExit(addressesLength % sizeof(Ip6::Address) == 0, status = ThreadStatusTlv::kMlrGeneralFailure); + VerifyOrExit(addressesLength / sizeof(Ip6::Address) <= kIPv6AddressesNumMax, + status = ThreadStatusTlv::kMlrGeneralFailure); + + IgnoreError(Get().GetConfig(config)); + expireTime = TimerMilli::GetNow() + TimeMilli::SecToMsec(config.mMlrTimeout); + + for (uint16_t offset = 0; offset < addressesLength; offset += sizeof(Ip6::Address)) + { + bool failed = true; + + IgnoreReturnValue(aMessage.Read(addressesOffset + offset, sizeof(Ip6::Address), &address)); + + switch (mMulticastListenersTable.Add(address, expireTime)) + { + case OT_ERROR_NONE: + failed = false; + break; + case OT_ERROR_INVALID_ARGS: + if (status == ThreadStatusTlv::kMlrSuccess) + { + status = ThreadStatusTlv::kMlrInvalid; + } + break; + case OT_ERROR_NO_BUFS: + if (status == ThreadStatusTlv::kMlrSuccess) + { + status = ThreadStatusTlv::kMlrNoResources; + } + break; + default: + OT_ASSERT(false); + } + + if (failed) + { + failedAddresses[failedAddressNum++] = address; + } + } exit: if (error == OT_ERROR_NONE) { - SendMulticastListenerRegistrationResponse(aMessage, aMessageInfo, status); + SendMulticastListenerRegistrationResponse(aMessage, aMessageInfo, status, failedAddresses, failedAddressNum); + // TODO: (MLR) send BMLR.req } } void Manager::SendMulticastListenerRegistrationResponse(const Coap::Message & aMessage, const Ip6::MessageInfo & aMessageInfo, - ThreadStatusTlv::MlrStatus aStatus) + ThreadStatusTlv::MlrStatus aStatus, + Ip6::Address * aFailedAddresses, + uint8_t aFailedAddressNum) { otError error = OT_ERROR_NONE; Coap::Message *message = nullptr; @@ -113,7 +179,20 @@ void Manager::SendMulticastListenerRegistrationResponse(const Coap::Message & SuccessOrExit(Tlv::AppendUint8Tlv(*message, ThreadTlv::kStatus, aStatus)); - // TODO: (MLR) append the failed addresses + if (aFailedAddressNum > 0) + { + IPv6AddressesTlv addressesTlv; + + addressesTlv.Init(); + addressesTlv.SetLength(sizeof(Ip6::Address) * aFailedAddressNum); + SuccessOrExit(error = message->Append(&addressesTlv, sizeof(addressesTlv))); + + for (uint8_t i = 0; i < aFailedAddressNum; i++) + { + SuccessOrExit(error = message->Append(aFailedAddresses + i, sizeof(Ip6::Address))); + } + } + SuccessOrExit(error = Get().SendMessage(*message, aMessageInfo)); exit: diff --git a/src/core/backbone_router/bbr_manager.hpp b/src/core/backbone_router/bbr_manager.hpp index bd68a110b..c63e1f3f8 100644 --- a/src/core/backbone_router/bbr_manager.hpp +++ b/src/core/backbone_router/bbr_manager.hpp @@ -41,6 +41,7 @@ #include #include "backbone_router/bbr_leader.hpp" +#include "backbone_router/multicast_listeners_table.hpp" #include "common/locator.hpp" #include "net/netif.hpp" #include "thread/network_data.hpp" @@ -81,7 +82,20 @@ public: void ConfigNextDuaRegistrationResponse(const Ip6::InterfaceIdentifier *aMlIid, uint8_t aStatus); #endif + /** + * This method gets the Multicast Listeners Table. + * + * @returns The Multicast Listeners Table. + * + */ + MulticastListenersTable &GetMulticastListenersTable(void) { return mMulticastListenersTable; } + private: + enum + { + kTimerInterval = 1000, + }; + static void HandleMulticastListenerRegistration(void * aContext, otMessage * aMessage, const otMessageInfo *aMessageInfo) @@ -92,7 +106,9 @@ private: void HandleMulticastListenerRegistration(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); void SendMulticastListenerRegistrationResponse(const Coap::Message & aMessage, const Ip6::MessageInfo & aMessageInfo, - ThreadStatusTlv::MlrStatus aStatus); + ThreadStatusTlv::MlrStatus aStatus, + Ip6::Address * aFailedAddresses, + uint8_t aFailedAddressNum); static void HandleDuaRegistration(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) { @@ -104,11 +120,18 @@ private: const Ip6::MessageInfo & aMessageInfo, const Ip6::Address & aTarget, ThreadStatusTlv::DuaStatus aStatus); + void HandleNotifierEvents(Events aEvents); + static void HandleTimer(Timer &aTimer) { aTimer.GetOwner().HandleTimer(); } + void HandleTimer(void); + Coap::Resource mMulticastListenerRegistration; Coap::Resource mDuaRegistration; + MulticastListenersTable mMulticastListenersTable; + TimerMilli mTimer; + #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE Ip6::InterfaceIdentifier mDuaResponseTargetMlIid; ThreadStatusTlv::DuaStatus mDuaResponseStatus; diff --git a/src/core/backbone_router/multicast_listeners_table.cpp b/src/core/backbone_router/multicast_listeners_table.cpp new file mode 100644 index 000000000..bcb58760b --- /dev/null +++ b/src/core/backbone_router/multicast_listeners_table.cpp @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements the Multicast Listeners Table. + */ + +#include "multicast_listeners_table.hpp" + +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + +#include "common/code_utils.hpp" +#include "common/instance.hpp" +#include "common/locator-getters.hpp" +#include "common/logging.hpp" +#include "common/random.hpp" +#include "thread/mle_types.hpp" +#include "thread/thread_netif.hpp" +#include "thread/thread_uri_paths.hpp" + +namespace ot { + +namespace BackboneRouter { + +otError MulticastListenersTable::Add(const Ip6::Address &aAddress, Time aExpireTime) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(aAddress.IsMulticastLargerThanRealmLocal(), error = OT_ERROR_INVALID_ARGS); + + for (uint16_t i = 0; i < mNumValidListeners; i++) + { + Listener &listener = mListeners[i]; + + if (listener.GetAddress() == aAddress) + { + listener.SetExpireTime(aExpireTime); + FixHeap(i); + ExitNow(); + } + } + + VerifyOrExit(mNumValidListeners < OT_ARRAY_LENGTH(mListeners), error = OT_ERROR_NO_BUFS); + + mListeners[mNumValidListeners].SetAddress(aAddress); + mListeners[mNumValidListeners].SetExpireTime(aExpireTime); + mNumValidListeners++; + + FixHeap(mNumValidListeners - 1); + + if (mCallback != nullptr) + { + mCallback(mCallbackContext, OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ADDED, &aAddress); + } + +exit: + LogMulticastListenersTable("Add", aAddress, aExpireTime, error); + CheckInvariants(); + return error; +} + +void MulticastListenersTable::Remove(const Ip6::Address &aAddress) +{ + otError error = OT_ERROR_NOT_FOUND; + + for (uint16_t i = 0; i < mNumValidListeners; i++) + { + Listener &listener = mListeners[i]; + + if (listener.GetAddress() == aAddress) + { + mNumValidListeners--; + + if (i != mNumValidListeners) + { + listener = mListeners[mNumValidListeners]; + FixHeap(i); + } + + if (mCallback != nullptr) + { + mCallback(mCallbackContext, OT_BACKBONE_ROUTER_MULTICAST_LISTENER_REMOVED, &aAddress); + } + + ExitNow(error = OT_ERROR_NONE); + } + } + +exit: + LogMulticastListenersTable("Remove", aAddress, TimeMilli(0), error); + CheckInvariants(); +} + +void MulticastListenersTable::Expire(void) +{ + TimeMilli now = TimerMilli::GetNow(); + Ip6::Address address; + + while (mNumValidListeners > 0 && now >= mListeners[0].GetExpireTime()) + { + LogMulticastListenersTable("Expire", mListeners[0].GetAddress(), mListeners[0].GetExpireTime(), OT_ERROR_NONE); + address = mListeners[0].GetAddress(); + + mNumValidListeners--; + + if (mNumValidListeners > 0) + { + mListeners[0] = mListeners[mNumValidListeners]; + FixHeap(0); + } + + if (mCallback != nullptr) + { + mCallback(mCallbackContext, OT_BACKBONE_ROUTER_MULTICAST_LISTENER_REMOVED, &address); + } + } + + CheckInvariants(); +} + +void MulticastListenersTable::LogMulticastListenersTable(const char * aAction, + const Ip6::Address &aAddress, + TimeMilli aExpireTime, + otError aError) +{ + OT_UNUSED_VARIABLE(aAction); + OT_UNUSED_VARIABLE(aAddress); + OT_UNUSED_VARIABLE(aExpireTime); + OT_UNUSED_VARIABLE(aError); + + otLogDebgBbr("MulticastListenersTable: %s %s expire %u: %s", aAction, aAddress.ToString().AsCString(), + aExpireTime.GetValue(), otThreadErrorToString(aError)); +} + +void MulticastListenersTable::FixHeap(uint16_t aIndex) +{ + if (!SiftHeapElemDown(aIndex)) + { + SiftHeapElemUp(aIndex); + } +} + +void MulticastListenersTable::CheckInvariants(void) const +{ +#if OPENTHREAD_EXAMPLES_SIMULATION && OPENTHREAD_CONFIG_ASSERT_ENABLE + for (uint16_t child = 1; child < mNumValidListeners; ++child) + { + uint16_t parent = (child - 1) / 2; + + OT_ASSERT(!(mListeners[child] < mListeners[parent])); + } +#endif +} + +bool MulticastListenersTable::SiftHeapElemDown(uint16_t aIndex) +{ + uint16_t index = aIndex; + Listener saveElem; + + OT_ASSERT(aIndex < mNumValidListeners); + + saveElem = mListeners[aIndex]; + + for (;;) + { + uint16_t child = 2 * index + 1; + + if (child >= mNumValidListeners || child <= index) // child <= index after int overflow + { + break; + } + + if (child + 1 < mNumValidListeners && mListeners[child + 1] < mListeners[child]) + { + child++; + } + + if (!(mListeners[child] < saveElem)) + { + break; + } + + mListeners[index] = mListeners[child]; + + index = child; + } + + if (index > aIndex) + { + mListeners[index] = saveElem; + } + + return index > aIndex; +} + +void MulticastListenersTable::SiftHeapElemUp(uint16_t aIndex) +{ + uint16_t index = aIndex; + Listener saveElem; + + OT_ASSERT(aIndex < mNumValidListeners); + + saveElem = mListeners[aIndex]; + + for (;;) + { + uint16_t parent = (index - 1) / 2; + + if (index == 0 || !(saveElem < mListeners[parent])) + { + break; + } + + mListeners[index] = mListeners[parent]; + + index = parent; + } + + if (index < aIndex) + { + mListeners[index] = saveElem; + } +} + +MulticastListenersTable::Listener *MulticastListenersTable::IteratorBuilder::begin(void) +{ + return &Get().mListeners[0]; +} + +MulticastListenersTable::Listener *MulticastListenersTable::IteratorBuilder::end(void) +{ + return &Get().mListeners[Get().mNumValidListeners]; +} + +void MulticastListenersTable::Clear(void) +{ + if (mCallback != nullptr) + { + for (uint16_t i = 0; i < mNumValidListeners; i++) + { + mCallback(mCallbackContext, OT_BACKBONE_ROUTER_MULTICAST_LISTENER_REMOVED, &mListeners[i].GetAddress()); + } + } + + mNumValidListeners = 0; + + CheckInvariants(); +} + +void MulticastListenersTable::SetCallback(otBackboneRouterMulticastListenerCallback aCallback, void *aContext) +{ + mCallback = aCallback; + mCallbackContext = aContext; + + if (mCallback != nullptr) + { + for (uint16_t i = 0; i < mNumValidListeners; i++) + { + mCallback(mCallbackContext, OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ADDED, &mListeners[i].GetAddress()); + } + } +} + +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + +otError MulticastListenersTable::GetNext(otBackboneRouterMulticastListenerIterator &aIterator, + otBackboneRouterMulticastListenerInfo & aListenerInfo) +{ + otError error = OT_ERROR_NONE; + TimeMilli now; + + VerifyOrExit(aIterator < mNumValidListeners, error = OT_ERROR_NOT_FOUND); + + now = TimerMilli::GetNow(); + + aListenerInfo.mAddress = mListeners[aIterator].mAddress; + aListenerInfo.mTimeout = + Time::MsecToSec(mListeners[aIterator].mExpireTime > now ? mListeners[aIterator].mExpireTime - now : 0); + + aIterator++; + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + +} // namespace BackboneRouter + +} // namespace ot + +#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE diff --git a/src/core/backbone_router/multicast_listeners_table.hpp b/src/core/backbone_router/multicast_listeners_table.hpp new file mode 100644 index 000000000..2eb9f035e --- /dev/null +++ b/src/core/backbone_router/multicast_listeners_table.hpp @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for Multicast Listeners Table. + */ + +#ifndef MULTICAST_LISTENERS_TABLE_HPP +#define MULTICAST_LISTENERS_TABLE_HPP + +#include "openthread-core-config.h" + +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + +#include + +#include "common/notifier.hpp" +#include "common/time.hpp" +#include "net/ip6_address.hpp" + +namespace ot { + +namespace BackboneRouter { + +/** + * This class implements the definitions for Multicast Listeners Table. + * + */ +class MulticastListenersTable : public InstanceLocator, private NonCopyable +{ + class IteratorBuilder; + +public: + /** + * This class represents a Multicast Listener entry. + * + */ + class Listener : public Clearable + { + friend class MulticastListenersTable; + + public: + /** + * This constructor initializes the `Listener` object. + * + */ + Listener(void) { Clear(); } + + /** + * This method returns the Multicast Listener address. + * + * @returns The Multicast Listener address. + * + */ + const Ip6::Address &GetAddress(void) const { return mAddress; } + + /** + * This method returns the expire time of the Multicast Listener. + * + * @returns The Multicast Listener expire time. + * + */ + const TimeMilli GetExpireTime(void) const { return mExpireTime; } + + private: + void SetAddress(const Ip6::Address &aAddress) { mAddress = aAddress; } + void SetExpireTime(TimeMilli aExpireTime) { mExpireTime = aExpireTime; } + + bool operator<(const Listener &aOther) const { return GetExpireTime() < aOther.GetExpireTime(); } + + Ip6::Address mAddress; + TimeMilli mExpireTime; + }; + + /** + * This constructor initializes the Multicast Listeners Table. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit MulticastListenersTable(Instance &aInstance) + : InstanceLocator(aInstance) + , mNumValidListeners(0) + , mCallback(nullptr) + , mCallbackContext(nullptr) + { + } + + /** + * This method adds a Multicast Listener with given address and expire time. + * + * @param[in] aAddress The Multicast Listener address. + * @param[in] aExpireTime The Multicast Listener expire time. + * + * @retval OT_ERROR_NONE If the Multicast Listener was successfully added. + * @retval OT_ERROR_INVALID_ARGS If the Multicast Listener address was invalid. + * @retval OT_ERROR_NO_BUFS No space available to save the Multicast Listener. + * + */ + otError Add(const Ip6::Address &aAddress, TimeMilli aExpireTime); + + /** + * This method removes a given Multicast Listener. + * + * @param[in] aAddress The Multicast Listener address. + * + */ + void Remove(const Ip6::Address &aAddress); + + /** + * This method removes expired Multicast Listeners. + * + */ + void Expire(void); + + /** + * This method counts the number of valid Multicast Listeners. + * + * @returns The number of valid Multicast Listeners. + * + */ + uint16_t Count(void) const { return mNumValidListeners; } + + /** + * This method enables range-based `for` loop iteration over all Multicast Listeners. + * + * This method should be used as follows: + * + * for (MulticastListenersTable::Listener &listener : Get().Iterate()) + * + * @returns An IteratorBuilder instance. + * + */ + IteratorBuilder Iterate(void) { return IteratorBuilder(GetInstance()); } + + /** + * This method removes all the Multicast Listeners. + * + */ + void Clear(void); + + /** + * This method sets the Multicast Listener callback. + * + * @param[in] aCallback The callback function. + * @param[in] aContext A user context pointer. + * + */ + void SetCallback(otBackboneRouterMulticastListenerCallback aCallback, void *aContext); + +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + /** + * This method gets the next Multicast Listener. + * + * @param[in] aIterator A pointer to the Multicast Listener Iterator. + * @param[out] aListenerInfo A pointer to where the Multicast Listener info is placed. + * + * @retval OT_ERROR_NONE Successfully found the next Multicast Listener info. + * @retval OT_ERROR_NOT_FOUND No subsequent Multicast Listener was found. + * + */ + otError GetNext(otBackboneRouterMulticastListenerIterator &aIterator, + otBackboneRouterMulticastListenerInfo & aListenerInfo); +#endif + +private: + enum + { + kMulticastListenersTableSize = OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS, + }; + + static_assert( + kMulticastListenersTableSize >= 75, + "Thread 1.2 Conformance requires the Multicast Listener Table size to be larger than or equal to 75."); + + class IteratorBuilder : InstanceLocator + { + public: + IteratorBuilder(Instance &aInstance) + : InstanceLocator(aInstance) + { + } + + Listener *begin(void); + Listener *end(void); + }; + + void LogMulticastListenersTable(const char * aAction, + const Ip6::Address &aAddress, + TimeMilli aExpireTime, + otError aError); + + void FixHeap(uint16_t aIndex); + bool SiftHeapElemDown(uint16_t aIndex); + void SiftHeapElemUp(uint16_t aIndex); + void CheckInvariants(void) const; + + Listener mListeners[kMulticastListenersTableSize]; + uint16_t mNumValidListeners; + + otBackboneRouterMulticastListenerCallback mCallback; + void * mCallbackContext; +}; + +} // namespace BackboneRouter + +/** + * @} + */ + +} // namespace ot + +#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + +#endif // MULTICAST_LISTENERS_TABLE_HPP diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index 0be241a1c..b8e6ef3e5 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -759,7 +759,10 @@ template <> inline BackboneRouter::Manager &Instance::Get(void) { return mThreadNetif.mBackboneRouterManager; } - +template <> inline BackboneRouter::MulticastListenersTable &Instance::Get(void) +{ + return mThreadNetif.mBackboneRouterManager.GetMulticastListenersTable(); +} #endif #if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE diff --git a/src/core/config/backbone_router.h b/src/core/config/backbone_router.h index 204e3f46c..0a2989b44 100644 --- a/src/core/config/backbone_router.h +++ b/src/core/config/backbone_router.h @@ -45,4 +45,19 @@ #define OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS + * + * This setting configures the maximum number of Multicast Listeners on a Backbone Router. + * + * Note: According to Thread Conformance v1.2.0, a Thread Border Router MUST be able to hold a Multicast Listeners Table + * in memory with at least seventy five (75) entries. + * + * @sa MulticastListenersTable + * + */ +#ifndef OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS +#define OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS 75 +#endif + #endif // CONFIG_BACKBONE_ROUTER_H_ diff --git a/src/core/thread/mle_types.hpp b/src/core/thread/mle_types.hpp index e12c67be6..d7f69a211 100644 --- a/src/core/thread/mle_types.hpp +++ b/src/core/thread/mle_types.hpp @@ -257,22 +257,24 @@ enum */ enum { - kRegistrationDelayDefault = 1200, ///< In seconds. - kMlrTimeoutDefault = 3600, ///< In seconds. - kMlrTimeoutMin = 300, ///< In seconds. - kBackboneRouterRegistrationJitter = 5, ///< In seconds. - kParentAggregateDelay = 5, ///< In seconds. - kNoBufDelay = 5, ///< In seconds. - kImmediateReRegisterDelay = 1, ///< In seconds. - KResponseTimeoutDelay = 30, ///< In seconds. - kDuaDadPeriod = 100, ///< In seconds. Time period after which the address - ///< becomes "Preferred" if no duplicate address error. + kRegistrationDelayDefault = 1200, ///< In seconds. + kMlrTimeoutDefault = 3600, ///< In seconds. + kMlrTimeoutMin = 300, ///< In seconds. + kMlrTimeoutMax = 0x7fffffff / 1000, ///< In seconds (about 24 days). + kBackboneRouterRegistrationJitter = 5, ///< In seconds. + kParentAggregateDelay = 5, ///< In seconds. + kNoBufDelay = 5, ///< In seconds. + kImmediateReRegisterDelay = 1, ///< In seconds. + KResponseTimeoutDelay = 30, ///< In seconds. + kDuaDadPeriod = 100, ///< In seconds. Time period after which the address + ///< becomes "Preferred" if no duplicate address error. }; -static_assert(kMlrTimeoutDefault >= kMlrTimeoutMin, +static_assert(kMlrTimeoutDefault >= kMlrTimeoutMin && kMlrTimeoutDefault <= kMlrTimeoutMax, "kMlrTimeoutDefault must be larger than or equal to kMlrTimeoutMin"); static_assert(Mle::kParentAggregateDelay > 1, "kParentAggregateDelay should be larger than 1 second"); +static_assert(kMlrTimeoutMax * 1000 > kMlrTimeoutMax, "SecToMsec(kMlrTimeoutMax) will overflow"); /** * State change of Child's DUA diff --git a/src/core/thread/mlr_manager.cpp b/src/core/thread/mlr_manager.cpp index 43e20b3cc..1b0e42d2b 100644 --- a/src/core/thread/mlr_manager.cpp +++ b/src/core/thread/mlr_manager.cpp @@ -96,6 +96,7 @@ void MlrManager::UpdateLocalSubscriptions(void) } #endif + CheckInvariants(); ScheduleSend(0); } @@ -117,17 +118,6 @@ exit: return ret; } -void MlrManager::SetNetifMulticastAddressMlrState(MlrState aFromState, MlrState aToState) -{ - for (Ip6::ExternalNetifMulticastAddress &addr : - Get().IterateExternalMulticastAddresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal)) - { - if (addr.GetMlrState() == aFromState) - { - addr.SetMlrState(aToState); - } - } -} #endif // OPENTHREAD_CONFIG_MLR_ENABLE #if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE @@ -183,6 +173,7 @@ void MlrManager::UpdateProxiedSubscriptions(Child & aChild, exit: LogMulticastAddresses(); + CheckInvariants(); if (aChild.HasAnyMlrToRegisterAddress()) { @@ -190,19 +181,6 @@ exit: } } -void MlrManager::SetChildMulticastAddressMlrState(MlrState aFromState, MlrState aToState) -{ - for (Child &child : Get().Iterate(Child::kInStateValid)) - { - for (const Ip6::Address &address : child.IterateIp6Addresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal)) - { - if (child.GetAddressMlrState(address) == aFromState) - { - child.SetAddressMlrState(address, aToState); - } - } - } -} #endif // OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE void MlrManager::ScheduleSend(uint16_t aDelay) @@ -360,6 +338,7 @@ exit: otLogInfoMlr("Send MLR.req: %s", otThreadErrorToString(error)); LogMulticastAddresses(); + CheckInvariants(); } void MlrManager::HandleMulticastListenerRegistrationResponse(Coap::Message * aMessage, @@ -368,25 +347,40 @@ void MlrManager::HandleMulticastListenerRegistrationResponse(Coap::Message * { OT_UNUSED_VARIABLE(aMessageInfo); - uint8_t status = ThreadStatusTlv::MlrStatus::kMlrGeneralFailure; - otError error; - - mMlrPending = false; + uint8_t status = ThreadStatusTlv::MlrStatus::kMlrGeneralFailure; + otError error = OT_ERROR_NONE; + uint16_t addressesOffset, addressesLength; + Ip6::Address failedAddresses[kIPv6AddressesNumMax]; + uint8_t failedAddressNum = 0; VerifyOrExit(aResult == OT_ERROR_NONE && aMessage != nullptr, error = OT_ERROR_PARSE); VerifyOrExit(aMessage->GetCode() == OT_COAP_CODE_CHANGED, error = OT_ERROR_PARSE); SuccessOrExit(error = Tlv::FindUint8Tlv(*aMessage, ThreadTlv::kStatus, status)); -exit: - SetMulticastAddressMlrState(kMlrStateRegistering, status == ThreadStatusTlv::MlrStatus::kMlrSuccess - ? kMlrStateRegistered - : kMlrStateToRegister); - otLogInfoBbr("Receive MLR.rsp, result=%s, status=%d, error=%s", otThreadErrorToString(aResult), status, - otThreadErrorToString(error)); - LogMulticastAddresses(); + if (ThreadTlv::FindTlvValueOffset(*aMessage, IPv6AddressesTlv::kIPv6Addresses, addressesOffset, addressesLength) == + OT_ERROR_NONE) + { + VerifyOrExit(addressesLength % sizeof(Ip6::Address) == 0, error = OT_ERROR_PARSE); + VerifyOrExit(addressesLength / sizeof(Ip6::Address) <= kIPv6AddressesNumMax, error = OT_ERROR_PARSE); - if (status == ThreadStatusTlv::MlrStatus::kMlrSuccess) + for (uint16_t offset = 0; offset < addressesLength; offset += sizeof(Ip6::Address)) + { + IgnoreReturnValue( + aMessage->Read(addressesOffset + offset, sizeof(Ip6::Address), &failedAddresses[failedAddressNum])); + failedAddressNum++; + } + } + + VerifyOrExit(failedAddressNum == 0 || status != ThreadStatusTlv::MlrStatus::kMlrSuccess, error = OT_ERROR_PARSE); + +exit: + LogMlrResponse(aResult, error, status, failedAddresses, failedAddressNum); + + FinishMulticastListenerRegistration(error == OT_ERROR_NONE && status == ThreadStatusTlv::MlrStatus::kMlrSuccess, + failedAddresses, failedAddressNum); + + if (error == OT_ERROR_NONE && status == ThreadStatusTlv::MlrStatus::kMlrSuccess) { // keep sending until all multicast addresses are registered. ScheduleSend(0); @@ -399,7 +393,6 @@ exit: // The Device has just attempted a Multicast Listener Registration which failed, and it retries the same // registration with a random time delay chosen in the interval [0, Reregistration Delay]. // This is required by Thread 1.2 Specification 5.24.2.3 - // TODO (MLR): reregister only failed addresses. if (Get().GetConfig(config) == OT_ERROR_NONE) { reregDelay = config.mReregistrationDelay > 1 @@ -410,6 +403,71 @@ exit: } } +void MlrManager::SetMulticastAddressMlrState(MlrState aFromState, MlrState aToState) +{ +#if OPENTHREAD_CONFIG_MLR_ENABLE + for (Ip6::ExternalNetifMulticastAddress &addr : + Get().IterateExternalMulticastAddresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal)) + { + if (addr.GetMlrState() == aFromState) + { + addr.SetMlrState(aToState); + } + } +#endif +#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE + for (Child &child : Get().Iterate(Child::kInStateValid)) + { + for (const Ip6::Address &address : child.IterateIp6Addresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal)) + { + if (child.GetAddressMlrState(address) == aFromState) + { + child.SetAddressMlrState(address, aToState); + } + } + } +#endif +} + +void MlrManager::FinishMulticastListenerRegistration(bool aSuccess, + const Ip6::Address *aFailedAddresses, + uint8_t aFailedAddressNum) +{ + OT_ASSERT(mMlrPending); + + mMlrPending = false; + +#if OPENTHREAD_CONFIG_MLR_ENABLE + for (Ip6::ExternalNetifMulticastAddress &addr : + Get().IterateExternalMulticastAddresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal)) + { + if (addr.GetMlrState() == kMlrStateRegistering) + { + bool success = aSuccess || !AddressListContains(aFailedAddresses, aFailedAddressNum, addr.GetAddress()); + + addr.SetMlrState(success ? kMlrStateRegistered : kMlrStateToRegister); + } + } +#endif +#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE + for (Child &child : Get().Iterate(Child::kInStateValid)) + { + for (const Ip6::Address &address : child.IterateIp6Addresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal)) + { + if (child.GetAddressMlrState(address) == kMlrStateRegistering) + { + bool success = aSuccess || !AddressListContains(aFailedAddresses, aFailedAddressNum, address); + + child.SetAddressMlrState(address, success ? kMlrStateRegistered : kMlrStateToRegister); + } + } + } +#endif + + LogMulticastAddresses(); + CheckInvariants(); +} + void MlrManager::HandleTimeTick(void) { if (mSendDelay > 0 && --mSendDelay == 0) @@ -428,6 +486,7 @@ void MlrManager::HandleTimeTick(void) void MlrManager::Reregister(void) { SetMulticastAddressMlrState(kMlrStateRegistered, kMlrStateToRegister); + CheckInvariants(); ScheduleSend(0); @@ -529,6 +588,85 @@ exit: return; } +bool MlrManager::AddressListContains(const Ip6::Address *aAddressList, + uint8_t aAddressListSize, + const Ip6::Address &aAddress) +{ + bool contains = false; + + // An empty address list is treated as if it contains all failed addresses. + VerifyOrExit(aAddressListSize > 0, contains = true); + + for (uint8_t i = 0; i < aAddressListSize; i++) + { + if (aAddressList[i] == aAddress) + { + ExitNow(contains = true); + } + } + +exit: + return contains; +} + +void MlrManager::LogMlrResponse(otError aResult, + otError aError, + uint8_t aStatus, + const Ip6::Address *aFailedAddresses, + uint8_t aFailedAddressNum) +{ + OT_UNUSED_VARIABLE(aResult); + OT_UNUSED_VARIABLE(aError); + OT_UNUSED_VARIABLE(aStatus); + OT_UNUSED_VARIABLE(aFailedAddresses); + OT_UNUSED_VARIABLE(aFailedAddressNum); + +#if OPENTHREAD_CONFIG_LOG_BBR + if (aResult == OT_ERROR_NONE && aError == OT_ERROR_NONE && aStatus == ThreadStatusTlv::MlrStatus::kMlrSuccess) + { + otLogInfoBbr("Receive MLR.rsp OK", otThreadErrorToString(aResult)); + } + else + { + otLogWarnBbr("Receive MLR.rsp: result=%s, error=%s, status=%d, failedAddressNum=%d", + otThreadErrorToString(aResult), otThreadErrorToString(aError), aStatus, aFailedAddressNum); + + for (uint8_t i = 0; i < aFailedAddressNum; i++) + { + otLogWarnBbr("MLR failed: %s", aFailedAddresses[i].ToString().AsCString()); + } + } +#endif +} + +void MlrManager::CheckInvariants(void) const +{ +#if OPENTHREAD_EXAMPLES_SIMULATION + uint16_t registeringNum = 0; + + OT_ASSERT(!mMlrPending || mSendDelay == 0); + +#if OPENTHREAD_CONFIG_MLR_ENABLE + for (Ip6::ExternalNetifMulticastAddress &addr : + Get().IterateExternalMulticastAddresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal)) + { + registeringNum += (addr.GetMlrState() == kMlrStateRegistering); + } +#endif +#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE + for (Child &child : Get().Iterate(Child::kInStateValid)) + { + for (const Ip6::Address &address : child.IterateIp6Addresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal)) + { + registeringNum += (child.GetAddressMlrState(address) == kMlrStateRegistering); + } + } +#endif + + OT_ASSERT(registeringNum == 0 || mMlrPending); +#endif // OPENTHREAD_EXAMPLES_SIMULATION +} + } // namespace ot #endif // OPENTHREAD_CONFIG_MLR_ENABLE diff --git a/src/core/thread/mlr_manager.hpp b/src/core/thread/mlr_manager.hpp index 05c42ab78..12b2b93ae 100644 --- a/src/core/thread/mlr_manager.hpp +++ b/src/core/thread/mlr_manager.hpp @@ -127,12 +127,10 @@ private: #if OPENTHREAD_CONFIG_MLR_ENABLE void UpdateLocalSubscriptions(void); - void SetNetifMulticastAddressMlrState(MlrState aFromState, MlrState aToState); bool IsAddressMlrRegisteredByNetif(const Ip6::Address &aAddress) const; #endif #if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE - void SetChildMulticastAddressMlrState(MlrState aFromState, MlrState aToState); bool IsAddressMlrRegisteredByAnyChild(const Ip6::Address &aAddress) const { return IsAddressMlrRegisteredByAnyChildExcept(aAddress, nullptr); @@ -140,19 +138,17 @@ private: bool IsAddressMlrRegisteredByAnyChildExcept(const Ip6::Address &aAddress, const Child *aExceptChild) const; #endif - void SetMulticastAddressMlrState(MlrState aFromState, MlrState aToState) - { -#if OPENTHREAD_CONFIG_MLR_ENABLE - SetNetifMulticastAddressMlrState(aFromState, aToState); -#endif -#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE - SetChildMulticastAddressMlrState(aFromState, aToState); -#endif - } + void SetMulticastAddressMlrState(MlrState aFromState, MlrState aToState); + void FinishMulticastListenerRegistration(bool aSuccess, + const Ip6::Address *aFailedAddresses, + uint8_t aFailedAddressNum); - void AppendToUniqueAddressList(Ip6::Address (&aAddresses)[kIPv6AddressesNumMax], - uint8_t & aAddressNum, - const Ip6::Address &aAddress); + void AppendToUniqueAddressList(Ip6::Address (&aAddresses)[kIPv6AddressesNumMax], + uint8_t & aAddressNum, + const Ip6::Address &aAddress); + static bool AddressListContains(const Ip6::Address *aAddressList, + uint8_t aAddressListSize, + const Ip6::Address &aAddress); void ScheduleSend(uint16_t aDelay); void UpdateTimeTickerRegistration(void); @@ -161,6 +157,12 @@ private: void HandleTimeTick(void); void LogMulticastAddresses(void); + void CheckInvariants(void) const; + void LogMlrResponse(otError aResult, + otError aError, + uint8_t aStatus, + const Ip6::Address *aFailedAddresses, + uint8_t aFailedAddressNum); uint32_t mReregistrationDelay; uint16_t mSendDelay; diff --git a/src/core/thread/thread_tlvs.hpp b/src/core/thread/thread_tlvs.hpp index 1b69f27b9..8b5480791 100644 --- a/src/core/thread/thread_tlvs.hpp +++ b/src/core/thread/thread_tlvs.hpp @@ -259,7 +259,7 @@ private: uint8_t mTlvs[kMaxSize]; } OT_TOOL_PACKED_END; -#if OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 /** * This class implements IPv6 Addresses TLV generation and parsing. @@ -302,7 +302,7 @@ public: } } OT_TOOL_PACKED_END; -#endif // OPENTHREAD_CONFIG_MLR_ENABLE || OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE +#endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 } // namespace ot diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 62f7b9bfc..5c57fe1c8 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -40,6 +40,8 @@ import time import unittest import binascii +from typing import Union + class Node: @@ -528,6 +530,35 @@ class Node: self.send_command(cmd) self._expect('Done') + def multicast_listener_list(self): + cmd = 'bbr mgmt mlr listener' + self.send_command(cmd) + + table = {} + for line in self._expect_results("\S+ \d+"): + line = line.split() + assert len(line) == 2, line + ip = ipaddress.IPv6Address(line[0]) + timeout = int(line[1]) + assert ip not in table + + table[ip] = timeout + + return table + + def multicast_listener_clear(self): + cmd = f'bbr mgmt mlr listener clear' + self.send_command(cmd) + self._expect("Done") + + def multicast_listener_add(self, ip: Union[ipaddress.IPv6Address, str], timeout: int = 0): + if not isinstance(ip, ipaddress.IPv6Address): + ip = ipaddress.IPv6Address(ip) + + cmd = f'bbr mgmt mlr listener add {ip.compressed} {timeout}' + self.send_command(cmd) + self._expect(r"(Done|Error .*)") + def set_link_quality(self, addr, lqi): cmd = 'macfilter rss add-lqi %s %s' % (addr, lqi) self.send_command(cmd) diff --git a/tests/scripts/thread-cert/v1_2_test_multicast_listener_registration.py b/tests/scripts/thread-cert/v1_2_test_multicast_listener_registration.py index 7b1c836d0..9d939747c 100755 --- a/tests/scripts/thread-cert/v1_2_test_multicast_listener_registration.py +++ b/tests/scripts/thread-cert/v1_2_test_multicast_listener_registration.py @@ -28,6 +28,8 @@ # import ipaddress +import logging +import time import unittest from typing import Union, List @@ -35,6 +37,8 @@ import config import network_layer import thread_cert +logging.basicConfig(level=logging.DEBUG) + _, BBR_1, BBR_2, ROUTER_1_2, ROUTER_1_1, SED_1, MED_1, MED_2, FED_1 = range(9) WAIT_ATTACH = 5 @@ -124,6 +128,7 @@ class TestMulticastListenerRegistration(thread_cert.TestCase): assert (turn_on_bbr_2 or not turn_on_router_1_1) # ROUTER_1_1 needs BBR_2 # starting context id + t0 = time.time() context_id = 1 # Bring up BBR_1, BBR_1 becomes Leader and Primary Backbone Router @@ -142,7 +147,8 @@ class TestMulticastListenerRegistration(thread_cert.TestCase): self.simulator.go(WAIT_TIME) self.assertEqual(self.nodes[BBR_1].get_backbone_router_state(), 'Primary') - self.pbbr_seq = 1 + self.pbbr_seq = 1 + self.pbbr_id = BBR_1 if turn_on_bbr_2: # Bring up BBR_2, BBR_2 becomes Router and Secondary Backbone Router @@ -197,6 +203,8 @@ class TestMulticastListenerRegistration(thread_cert.TestCase): self.simulator.go(WAIT_ATTACH) self.assertEqual(self.nodes[SED_1].get_state(), 'child') + logging.info("bootstrap takes %f seconds", time.time() - t0) + def test(self): self._bootstrap() @@ -257,11 +265,171 @@ class TestMulticastListenerRegistration(thread_cert.TestCase): # Expect MLR.req sent by ROUTER_1_2 and FED_1 self.__check_send_mlr_req(ROUTER_1_2, router_reg, should_send=True, expect_mlr_rsp=True) + def testMulticastListenersTableAdd(self): + self._bootstrap() + self.__test_multicast_listeners_table_add() + + def testMulticastListenersTableExpire(self): + self._bootstrap() + self.__test_multicast_listeners_table_expire() + + def testMulticastListenersTableFull(self): + self._bootstrap() + self.__test_multicast_listeners_table_full() + + def testMulticastListenersTableTwoFreeSlot(self): + self._bootstrap() + self.__test_multicast_listeners_table_two_free_slots() + + def testMulticastListenerTableAPI(self): + self._bootstrap() + self.__test_multicast_listeners_table_api() + + def __test_multicast_listeners_table_api(self): + self.assertTrue(self.nodes[BBR_1].multicast_listener_list() == {}) + + self.nodes[BBR_1].multicast_listener_add("ff04::1") + self.assertEqual(1, len(self.nodes[BBR_1].multicast_listener_list())) + self.nodes[BBR_1].multicast_listener_add("ff04::2", 300) + self.assertEqual(2, len(self.nodes[BBR_1].multicast_listener_list())) + self.nodes[BBR_1].multicast_listener_clear() + self.assertTrue(self.nodes[BBR_1].multicast_listener_list() == {}) + + def __test_multicast_listeners_table_add(self): + self.assertTrue(self.nodes[BBR_1].multicast_listener_list() == {}) + + all_mas = set() + + CHECK_LIST = [(ROUTER_1_2, "ff04::1"), (FED_1, "ff04::2"), (MED_1, "ff04::3"), (SED_1, "ff04::4")] + + for id, ip in CHECK_LIST: + self.nodes[id].add_ipmaddr(ip) + self.simulator.go(PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE) + all_mas.add(ipaddress.IPv6Address(ip)) + self.assertEqual(all_mas, set(self.nodes[BBR_1].multicast_listener_list().keys())) + + # restore + for id, ip in CHECK_LIST: + self.nodes[id].del_ipmaddr(ip) + + self.simulator.go(WAIT_REDUNDANCE) + + def __test_multicast_listeners_table_expire(self): + self.assertEqual({}, self.nodes[BBR_1].multicast_listener_list()) + + all_mas = set() + + CHECK_LIST = [(ROUTER_1_2, "ff04::1"), (FED_1, "ff04::2"), (MED_1, "ff04::3"), (SED_1, "ff04::4")] + + for id, ip in CHECK_LIST: + self.nodes[id].add_ipmaddr(ip) + self.simulator.go(PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE) + all_mas.add(ipaddress.IPv6Address(ip)) + self.assertEqual(all_mas, set(self.nodes[BBR_1].multicast_listener_list().keys())) + + # remove MAs from nodes, and wait for Multicast Listeners to expire on BBR_1 + for id, ip in CHECK_LIST: + self.nodes[id].del_ipmaddr(ip) + + # Wait for MLR_TIMEOUT/3, and expect Multicast Listeners not to expire. + self.simulator.go(MLR_TIMEOUT / 3) + self.assertEqual(all_mas, set(self.nodes[BBR_1].multicast_listener_list().keys())) + + # Wait for MLR_TIMEOUT*2/3, and expect all Multicast Listeners to expire. + self.simulator.go(MLR_TIMEOUT * 2 / 3 + WAIT_REDUNDANCE) + self.assertEqual({}, self.nodes[BBR_1].multicast_listener_list()) + + def __test_multicast_listeners_table_full(self): + self.assertTrue(self.nodes[BBR_1].multicast_listener_list() == {}) + + table = set() + + # Add to Multicast Listeners Table until it's full + for i in range(1, 76): + self.nodes[BBR_1].multicast_listener_add(f"ff04::{i}") + table.add(ipaddress.IPv6Address(f"ff04::{i}")) + self.assertEqual(table, set(self.nodes[BBR_1].multicast_listener_list().keys())) + + # Add when Multicast Listeners Table is full should not succeed + self.nodes[BBR_1].multicast_listener_add(f"ff05::1") + self.assertEqual(table, set(self.nodes[BBR_1].multicast_listener_list().keys())) + + self.flush_all() + + # Expect PBBR to respond with MLR_NO_RESOURCES Multicast Listeners Table when it's full + self.nodes[ROUTER_1_2].add_ipmaddr("ff06::1") + self.simulator.go(WAIT_REDUNDANCE) + + self.__check_send_mlr_req(ROUTER_1_2, + "ff06::1", + should_send=True, + expect_mlr_rsp=True, + expect_mlr_rsp_status=4) + + self.assertEqual(table, set(self.nodes[BBR_1].multicast_listener_list().keys())) + + self.nodes[MED_1].add_ipmaddr("ff06::2") + self.simulator.go(PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE) + + self.__check_send_mlr_req(ROUTER_1_2, + "ff06::2", + should_send=True, + expect_mlr_rsp=True, + expect_mlr_rsp_status=4) + + self.assertEqual(table, set(self.nodes[BBR_1].multicast_listener_list().keys())) + + # the ROUTER_1_2 should be resending both ff06::1 and ff06::2 + for i in range(3): + self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE) + self.__check_send_mlr_req(ROUTER_1_2, ['ff06::1', 'ff06::2'], + should_send=True, + expect_mlr_rsp=True, + expect_mlr_rsp_status=4) + + # Restore + self.nodes[ROUTER_1_2].del_ipmaddr("ff06::1") + self.nodes[MED_1].del_ipmaddr("ff06::2") + self.simulator.go(WAIT_REDUNDANCE) + + def __test_multicast_listeners_table_two_free_slots(self): + + # Add to Multicast Listeners Table until there is only two free slots + for i in range(1, 74): + self.nodes[BBR_1].multicast_listener_add(f"ff04::{i}") + + self.assertEqual(73, len(self.nodes[BBR_1].multicast_listener_list())) + + self.nodes[MED_1].add_ipmaddr("ff05::1") + self.nodes[MED_1].add_ipmaddr("ff05::2") + self.nodes[MED_2].add_ipmaddr("ff05::3") + self.nodes[MED_2].add_ipmaddr("ff05::4") + self.nodes[SED_1].add_ipmaddr("ff05::5") + self.nodes[SED_1].add_ipmaddr("ff05::6") + + self.simulator.go(PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE) + self.__check_send_mlr_req(ROUTER_1_2, ["ff05::1", "ff05::2", "ff05::3", "ff05::4", "ff05::5", "ff05::6"]) + + self.simulator.go(PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE) + self.flush_all() + # two addresses should be registered, others can not + for i in range(3): + self.simulator.go(PARENT_AGGREGATE_DELAY + WAIT_REDUNDANCE) + self.assertEqual(4, len(set(self.__get_registered_MAs(ROUTER_1_2)))) + + # Restore + self.nodes[MED_1].del_ipmaddr("ff05::1") + self.nodes[MED_1].del_ipmaddr("ff05::2") + self.nodes[MED_2].del_ipmaddr("ff05::3") + self.nodes[MED_2].del_ipmaddr("ff05::4") + self.nodes[SED_1].del_ipmaddr("ff05::5") + self.nodes[SED_1].del_ipmaddr("ff05::6") + def __check_mlr_ok(self, id, is_ftd, is_parent_1p1=False): """Check if MLR works for the node""" # Add MA1 and send MLR.req - print("======== checking MLR: Node%d (%s), Parent=%s ========" % - (id, 'FTD' if is_ftd else 'MTD', '1.1' if is_parent_1p1 else '1.2')) + logging.info("======== checking MLR: Node%d (%s), Parent=%s ========" % + (id, 'FTD' if is_ftd else 'MTD', '1.1' if is_parent_1p1 else '1.2')) expect_mlr_req = is_ftd or is_parent_1p1 if id == ROUTER_1_2: @@ -278,11 +446,11 @@ class TestMulticastListenerRegistration(thread_cert.TestCase): for addr in [MA5, MA6]: self.__check_ipmaddr_add(id, parent_id, addr, expect_mlr_req=False, expect_mlr_req_proxied=False) - print('=' * 120) + logging.info('=' * 120) def __check_ipmaddr_add(self, id, parent_id, addr, expect_mlr_req=True, expect_mlr_req_proxied=False): """Check MLR works for the added multicast address""" - print("Node %d: ipmaddr %s" % (id, addr)) + logging.info("Node %d: ipmaddr %s" % (id, addr)) self.flush_all() self.nodes[id].add_ipmaddr(addr) self.assertTrue(self.nodes[id].has_ipmaddr(addr)) @@ -315,12 +483,14 @@ class TestMulticastListenerRegistration(thread_cert.TestCase): addrs: Union[List[str], str], should_send=True, expect_mlr_rsp=False, + expect_mlr_rsp_status=0, expect_mlr_req_num=None, expect_unique_reg=False): if isinstance(addrs, str): addrs = [addrs] - reg_mas = self.__get_registered_MAs(id, expect_mlr_req_num=expect_mlr_req_num) + message_ids = [] + reg_mas = self.__get_registered_MAs(id, expect_mlr_req_num=expect_mlr_req_num, message_ids=message_ids) if should_send: for addr in addrs: self.assertIn(ipaddress.IPv6Address(addr), reg_mas) @@ -329,13 +499,37 @@ class TestMulticastListenerRegistration(thread_cert.TestCase): # BBR should send MLR.rsp ACK if expect_mlr_rsp: - messages = self.simulator.get_messages_sent_by(BBR_1) - messages.next_coap_message('2.04') + message_id = message_ids[0] + rsp = self.__expect_MLR_rsp(message_id) + + logging.info('MLR.rsp %s uri_path=%s, payload=%s', rsp, rsp.coap.uri_path, rsp.coap.payload) + + statusTlv = None + for tlv in rsp.coap.payload: + if isinstance(tlv, network_layer.Status): + statusTlv = tlv + break + + self.assertIsNotNone(statusTlv) + self.assertEqual(expect_mlr_rsp_status, statusTlv.status) + else: for addr in addrs: self.assertNotIn(ipaddress.IPv6Address(addr), reg_mas) - def __get_registered_MAs(self, id, expect_mlr_req_num=None): + def __expect_MLR_rsp(self, message_id): + logging.info("Expecting MLR.rsp with message ID = %s", message_id) + messages = self.simulator.get_messages_sent_by(self.pbbr_id) + logging.info("PBBR %d messages: %s", self.pbbr_id, messages) + + while True: + msg = messages.next_coap_message('2.04') + logging.info('Check ACK for %s: %s, %s, %s, %s', message_id, msg, msg.coap.message_id, msg.coap.uri_path, + msg.coap.payload) + if msg.coap.message_id == message_id: + return msg + + def __get_registered_MAs(self, id, expect_mlr_req_num=None, message_ids=None): """Get MAs registered via MLR.req by the node""" messages = self.simulator.get_messages_sent_by(id) reg_mas = [] @@ -343,10 +537,14 @@ class TestMulticastListenerRegistration(thread_cert.TestCase): msg = messages.next_coap_message('0.02', '/n/mr', assert_enabled=False) if not msg: break + + logging.info("MLR.req: %s %s" % (msg.coap.message_id, msg.coap.payload)) addrs = msg.get_coap_message_tlv(network_layer.IPv6Addresses) reg_mas.append(addrs) + if message_ids is not None: + message_ids.append(msg.coap.message_id) - print('Node %d registered MAs: %s' % (id, reg_mas)) + logging.info('Node %d registered MAs: %s' % (id, reg_mas)) if expect_mlr_req_num is not None: self.assertEqual(len(reg_mas), expect_mlr_req_num) @@ -358,6 +556,7 @@ class TestMulticastListenerRegistration(thread_cert.TestCase): def __check_renewing(self, id, parent_id, addr, expect_mlr_req=True, expect_mlr_req_proxied=False): """Check if MLR works that a node can renew it's registered MAs""" + assert self.pbbr_id == BBR_1 self.flush_all() self.simulator.go(MLR_TIMEOUT + WAIT_REDUNDANCE) @@ -401,10 +600,14 @@ class TestMulticastListenerRegistration(thread_cert.TestCase): def __check_rereg_pbbr_change(self, id, parent_id, addr, expect_mlr_req=True, expect_mlr_req_proxied=False): """Check if MLR works that a node can do MLR reregistration when PBBR changes""" # Make BBR_2 to be Primary and expect MLR.req within REREG_DELAY + assert self.pbbr_id == BBR_1 + self.flush_all() self.nodes[BBR_1].disable_backbone_router() self.simulator.go(BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE) self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(), 'Primary') + self.pbbr_id = BBR_2 + self.simulator.go(REREG_DELAY + WAIT_REDUNDANCE) self.__check_send_mlr_req(id, addr, should_send=expect_mlr_req, expect_mlr_rsp=expect_mlr_req) @@ -423,11 +626,12 @@ class TestMulticastListenerRegistration(thread_cert.TestCase): self.nodes[BBR_2].enable_backbone_router() self.simulator.go(BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE) self.assertEqual(self.nodes[BBR_2].get_backbone_router_state(), 'Secondary') + self.pbbr_id = BBR_1 def __switch_to_1_1_parent(self): """Check if MLR works when nodes are switching to a 1.1 parent""" # Add MA1 to EDs to prepare for parent switching - print("=" * 10, "switching to 1.1 parent", '=' * 10) + logging.info("=" * 10 + " switching to 1.1 parent " + '=' * 10) self.flush_all() @@ -468,7 +672,7 @@ class TestMulticastListenerRegistration(thread_cert.TestCase): def __switch_to_1_2_parent(self): """Check if MLR works when nodes are switching to a 1.2 parent""" # Add MA1 to EDs to prepare for parent switching - print("=" * 10, "switching to 1.2 parent", '=' * 10) + logging.info("=" * 10, "switching to 1.2 parent", '=' * 10) self.flush_all() diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index ebaf1ca11..49ced260d 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -336,6 +336,29 @@ target_link_libraries(test-message-queue add_test(NAME test-message-queue COMMAND test-message-queue) +add_executable(test-multicast-listeners-table + ${COMMON_SOURCES} + test_multicast_listeners_table.cpp + ) + +target_include_directories(test-multicast-listeners-table + PRIVATE + ${COMMON_INCLUDES} + ) + +target_compile_options(test-multicast-listeners-table + PRIVATE + ${COMMON_COMPILE_OPTIONS} + ) + +target_link_libraries(test-multicast-listeners-table + PRIVATE + ${COMMON_LIBS} + ) + +add_test(NAME test-multicast-listeners-table COMMAND test-multicast-listeners-table) + + add_executable(test-netif ${COMMON_SOURCES} test_netif.cpp @@ -513,7 +536,7 @@ target_link_libraries(test-timer add_test(NAME test-timer COMMAND test-timer) set_target_properties( - test-aes test-child test-child-table test-flash test-heap test-hmac-sha256 test-ip6-address test-link-quality test-linked-list test-lowpan test-mac-frame test-message test-message-queue test-netif test-network-data test-pool test-priority-queue test-pskc test-steering-data test-string test-timer + test-aes test-child test-child-table test-flash test-heap test-hmac-sha256 test-ip6-address test-link-quality test-linked-list test-lowpan test-mac-frame test-message test-message-queue test-multicast-listeners-table test-netif test-network-data test-pool test-priority-queue test-pskc test-steering-data test-string test-timer PROPERTIES C_STANDARD 99 CXX_STANDARD 11 diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 7aac4bfb5..5d3d29197 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -119,6 +119,7 @@ check_PROGRAMS += \ test-mac-frame \ test-message \ test-message-queue \ + test-multicast-listeners-table \ test-netif \ test-network-data \ test-pool \ @@ -209,6 +210,9 @@ test_message_SOURCES = $(COMMON_SOURCES) test_message.cpp test_message_queue_LDADD = $(COMMON_LDADD) test_message_queue_SOURCES = $(COMMON_SOURCES) test_message_queue.cpp +test_multicast_listeners_table_LDADD = $(COMMON_LDADD) +test_multicast_listeners_table_SOURCES = $(COMMON_SOURCES) test_multicast_listeners_table.cpp + test_spinel_buffer_LDADD = $(COMMON_LDADD) test_spinel_buffer_SOURCES = $(COMMON_SOURCES) test_spinel_buffer.cpp diff --git a/tests/unit/test_multicast_listeners_table.cpp b/tests/unit/test_multicast_listeners_table.cpp new file mode 100644 index 000000000..7c66aa097 --- /dev/null +++ b/tests/unit/test_multicast_listeners_table.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "openthread-core-config.h" + +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + +#include "test_platform.h" + +#include +#include + +#include "test_util.h" +#include "backbone_router/multicast_listeners_table.hpp" +#include "common/code_utils.hpp" +#include "common/instance.hpp" + +namespace ot { + +static ot::Instance *sInstance; + +using namespace ot::BackboneRouter; + +static const otIp6Address MA201 = { + {{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}}; +static const otIp6Address MA301 = { + {{0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}}; +static const otIp6Address MA401 = { + {{0xff, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}}; +static const otIp6Address MA501 = { + {{0xff, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}}; + +uint32_t sNow; + +uint32_t testTimerAlarmGetNow(void) +{ + return sNow; +} + +void InitTestTimer(void) +{ + g_testPlatAlarmGetNow = testTimerAlarmGetNow; +} + +void testMulticastListenersTableAPIs(Instance *aInstance); + +void TestMulticastListenersTable(void) +{ + InitTestTimer(); + + sInstance = testInitInstance(); + VerifyOrQuit(sInstance != nullptr, "Null OpenThread instance"); + + MulticastListenersTable &table = sInstance->Get(); + + for (MulticastListenersTable::Listener &listener : table.Iterate()) + { + VerifyOrQuit(false, "MulticastListenersTable should be empty when created"); + } + + // Removing from empty table should be OK + table.Remove(static_cast(MA401)); + + sNow = 1; + // Add valid MAs should succeed + SuccessOrQuit(table.Add(static_cast(MA401), TimerMilli::GetNow()), "Add failed"); + SuccessOrQuit(table.Add(static_cast(MA501), TimerMilli::GetNow()), "Add failed"); + VerifyOrQuit(table.Count() == 2, "Table count is wrong"); + + // Add invalid MAs should fail with OT_ERROR_INVALID_ARGS + VerifyOrQuit(table.Add(static_cast(MA201), TimerMilli::GetNow()) == OT_ERROR_INVALID_ARGS, + "Add should fail"); + VerifyOrQuit(table.Add(static_cast(MA301), TimerMilli::GetNow()) == OT_ERROR_INVALID_ARGS, + "Add should fail"); + + // Expire should expire outdated Listeners + sNow = 2; + table.Expire(); + VerifyOrQuit(table.Count() == 0, "Table count is wrong"); + + // Add different addresses until the table is full + sNow = 10; + for (uint16_t i = 0; i < OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS; i++) + { + Ip6::Address address; + + address = static_cast(MA401); + address.mFields.m16[7] = HostSwap16(i); + + SuccessOrQuit(table.Add(address, TimerMilli::GetNow() + i), "Add failed"); + VerifyOrQuit(table.Count() == i + 1, "Table count is wrong"); + } + + // Now the table is full, we can't add more addresses + VerifyOrQuit(table.Add(static_cast(MA501), TimerMilli::GetNow()) == OT_ERROR_NO_BUFS, + "Add should fail"); + + // Expire one Listener at a time + for (uint16_t i = 0; i < OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS; i++) + { + table.Expire(); + VerifyOrQuit(table.Count() == OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS - i - 1, "Table count is wrong"); + + sNow += 1; + } + + // Now the table should be empty + VerifyOrQuit(table.Count() == 0, "Table count is wrong"); + + // Now test the APIs + testMulticastListenersTableAPIs(sInstance); + + // Do some fuzzy test + for (uint16_t i = 0; i < 10000; i++) + { + Ip6::Address address; + sNow += 10; + + table.Expire(); + + for (MulticastListenersTable::Listener &listener : table.Iterate()) + { + OT_ASSERT(listener.GetAddress().IsMulticastLargerThanRealmLocal()); + OT_ASSERT(listener.GetExpireTime() > TimerMilli::GetNow()); + } + + address = static_cast(MA401); + + address.mFields.m16[7] = Random::NonCrypto::GetUint16InRange(1, 1000); + IgnoreError(table.Add(address, TimerMilli::GetNow() + Random::NonCrypto::GetUint32InRange(1, 100))); + + address.mFields.m16[7] = Random::NonCrypto::GetUint16InRange(1, 1000); + if (Random::NonCrypto::GetUint16InRange(0, 2) == 0) + { + table.Remove(address); + } + } +} + +void testMulticastListenersTableAPIs(Instance *aInstance) +{ +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + otBackboneRouterMulticastListenerIterator iter = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT; + otBackboneRouterMulticastListenerInfo info; + size_t table_size = 0; + + while (otBackboneRouterMulticastListenerGetNext(aInstance, &iter, &info) == OT_ERROR_NONE) + { + VerifyOrQuit(false, "Table should be empty"); + } + + SuccessOrQuit(otBackboneRouterMulticastListenerAdd(aInstance, &MA401, 30), "Add failed"); + + table_size = 0, iter = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT; + while (otBackboneRouterMulticastListenerGetNext(aInstance, &iter, &info) == OT_ERROR_NONE) + { + table_size++; + + VerifyOrQuit(memcmp(&info.mAddress, &MA401, sizeof(otIp6Address)) == 0, "bad address"); + VerifyOrQuit(info.mTimeout == 30, "bad timeout"); + } + VerifyOrQuit(table_size == 1, "Table size is wrong"); + + otBackboneRouterMulticastListenerClear(aInstance); + + iter = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT; + while (otBackboneRouterMulticastListenerGetNext(aInstance, &iter, &info) == OT_ERROR_NONE) + { + VerifyOrQuit(false, "Table should be empty"); + } +#endif +} + +} // namespace ot + +int main(void) +{ + ot::TestMulticastListenersTable(); + printf("\nAll tests passed.\n"); + return 0; +} + +#else +int main(void) +{ + return 0; +} +#endif