diff --git a/Android.mk b/Android.mk index bad1ea5c1..a00dc6db8 100644 --- a/Android.mk +++ b/Android.mk @@ -304,6 +304,7 @@ LOCAL_SRC_FILES := \ src/core/thread/address_resolver.cpp \ src/core/thread/announce_begin_server.cpp \ src/core/thread/announce_sender.cpp \ + src/core/thread/anycast_locator.cpp \ src/core/thread/child_table.cpp \ src/core/thread/csl_tx_scheduler.cpp \ src/core/thread/discover_scanner.cpp \ diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 268594d92..9d31ecac0 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -34,6 +34,11 @@ option(OT_FTD "enable FTD" ON) option(OT_MTD "enable MTD" ON) option(OT_RCP "enable RCP" ON) +option(OT_ANYCAST_LOCATOR "enable anycast locator support") +if(OT_ANYCAST_LOCATOR) + target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE=1") +endif() + option(OT_ASSERT "enable assert function OT_ASSERT()" ON) if(OT_ASSERT) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_ASSERT_ENABLE=1") diff --git a/etc/gn/openthread.gni b/etc/gn/openthread.gni index 1bd07dd39..1a8f285ee 100644 --- a/etc/gn/openthread.gni +++ b/etc/gn/openthread.gni @@ -78,6 +78,9 @@ if (openthread_enable_core_config_args) { # Enable assertions. openthread_config_assert_enable = true + # Enable anycast locator functionality + openthread_config_anycast_locator_enable = false + # Enable backbone router functionality openthread_config_backbone_router_enable = false diff --git a/examples/Makefile-simulation b/examples/Makefile-simulation index 8bdcb3fe3..0df78dc68 100644 --- a/examples/Makefile-simulation +++ b/examples/Makefile-simulation @@ -35,6 +35,7 @@ DEBUG ?= 0 # Enable most features by default to cover most code +ANYCAST_LOCATOR ?= 1 BORDER_AGENT ?= 1 BORDER_ROUTER ?= 1 COAP ?= 1 diff --git a/examples/README.md b/examples/README.md index cff5d4c5d..9a1861dcf 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,6 +6,7 @@ This page lists the available common switches with description. Unless stated ot | Makefile switch | CMake switch | Description | | --- | --- | --- | +| ANYCAST_LOCATOR | OT_ANYCAST_LOCATOR | Enables anycast locator functionality. | | BACKBONE_ROUTER | OT_BACKBONE_ROUTER | Enables Backbone Router functionality for Thread 1.2. | | BIG_ENDIAN | OT_BIG_ENDIAN | Allows the host platform to use big-endian byte order. | | BORDER_AGENT | OT_BORDER_AGENT | Enables support for border agent. In most cases, enable this switch if you are building On-mesh Commissioner or Border Router with External Commissioning support. | diff --git a/examples/common-switches.mk b/examples/common-switches.mk index 6fbb4f8e0..33687fecc 100644 --- a/examples/common-switches.mk +++ b/examples/common-switches.mk @@ -28,6 +28,7 @@ # OpenThread Features (Makefile default configuration). +ANYCAST_LOCATOR ?= 0 BACKBONE_ROUTER ?= 0 BIG_ENDIAN ?= 0 BORDER_AGENT ?= 0 @@ -90,6 +91,9 @@ UDP_FORWARD ?= 0 UPTIME ?= 0 RCP_RESTORATION_MAX_COUNT ?= 0 +ifeq ($(ANYCAST_LOCATOR),1) +COMMONCFLAGS += -DOPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE=1 +endif ifeq ($(BACKBONE_ROUTER),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE=1 diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 2d9318bad..ab22b89d2 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 (164) +#define OPENTHREAD_API_VERSION (165) /** * @addtogroup api-instance diff --git a/include/openthread/thread.h b/include/openthread/thread.h index b2cff9c6f..cbac610ec 100644 --- a/include/openthread/thread.h +++ b/include/openthread/thread.h @@ -925,6 +925,58 @@ void otThreadSetDiscoveryRequestCallback(otInstance * aInsta otThreadDiscoveryRequestCallback aCallback, void * aContext); +/** + * This function pointer type defines the callback to notify the outcome of a `otThreadLocateAnycastDestination()` + * request. + * + * @param[in] aContext A pointer to an arbitrary context (provided when callback is registered). + * @param[in] aError The error when handling the request. OT_ERROR_NONE indicates success. + * OT_ERROR_RESPONSE_TIMEOUT indicates a destination could not be found. + * OT_ERROR_ABORT indicates the request was aborted. + * @param[in] aMeshLocalAddress A pointer to the mesh-local EID of the closest destination of the anycast address + * when @p aError is OT_ERROR_NONE, NULL otherwise. + * @param[in] aRloc16 The RLOC16 of the destination if found, otherwise invalid RLOC16 (0xfffe). + * + */ +typedef void (*otThreadAnycastLocatorCallback)(void * aContext, + otError aError, + const otIp6Address *aMeshLocalAddress, + uint16_t aRloc16); + +/** + * This function requests the closest destination of a given anycast address to be located. + * + * This function is only available when `OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE` is enabled. + * + * If a previous request is ongoing, a subsequent call to this function will cancel and replace the earlier request. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aAnycastAddress The anycast address to locate. MUST NOT be NULL. + * @param[in] aCallback The callback function to report the result. + * @param[in] aContext An arbitrary context used with @p aCallback. + * + * @retval OT_ERROR_NONE The request started successfully. @p aCallback will be invoked to report the result. + * @retval OT_ERROR_INVALID_ARGS The @p aAnycastAddress is not a valid anycast address or @p aCallback is NULL. + * @retval OT_ERROR_NO_BUFS Out of buffer to prepare and send the request message. + * + */ +otError otThreadLocateAnycastDestination(otInstance * aInstance, + const otIp6Address * aAnycastAddress, + otThreadAnycastLocatorCallback aCallback, + void * aContext); + +/** + * This function indicates whether an anycast locate request is currently in progress. + * + * This function is only available when `OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE` is enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns TRUE if an anycast locate request is currently in progress, FALSE otherwise. + * + */ +bool otThreadIsAnycastLocateInProgress(otInstance *aInstance); + /** * This function sends a Proactive Address Notification (ADDR_NTF.ntf) message. * diff --git a/script/check-scan-build b/script/check-scan-build index 57681f6ef..7ae01d8c6 100755 --- a/script/check-scan-build +++ b/script/check-scan-build @@ -32,6 +32,7 @@ set -euxo pipefail readonly OT_SRCDIR="$(pwd)" readonly OT_BUILD_OPTIONS=( + "-DOT_ANYCAST_LOCATOR=ON" "-DOT_BUILD_EXECUTABLES=OFF" "-DOT_BORDER_AGENT=ON" "-DOT_BORDER_ROUTER=ON" diff --git a/script/check-simulation-build-autotools b/script/check-simulation-build-autotools index 4b17fe667..5f86d22fe 100755 --- a/script/check-simulation-build-autotools +++ b/script/check-simulation-build-autotools @@ -40,6 +40,7 @@ build_all_features() { local options=( "-DOPENTHREAD_CONFIG_ANNOUNCE_SENDER_ENABLE=1" + "-DOPENTHREAD_CONFIG_ANYCAST_LOCATOR=1" "-DOPENTHREAD_CONFIG_BORDER_AGENT_ENABLE=1" "-DOPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE=1" "-DOPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE=1" diff --git a/script/check-size b/script/check-size index 1eb1bda44..2163d0576 100755 --- a/script/check-size +++ b/script/check-size @@ -114,6 +114,7 @@ nm_size() size_nrf52840_version() { local options=( + "-DOT_ANYCAST_LOCATOR=ON" "-DOT_BORDER_AGENT=ON" "-DOT_BORDER_ROUTER=ON" "-DOT_CHANNEL_MANAGER=ON" diff --git a/script/cmake-build b/script/cmake-build index 1f3b65d45..d3ccf0d41 100755 --- a/script/cmake-build +++ b/script/cmake-build @@ -66,6 +66,7 @@ OT_CMAKE_NINJA_TARGET=${OT_CMAKE_NINJA_TARGET:-} readonly OT_SRCDIR="$(pwd)" readonly OT_PLATFORMS=(cc2538 simulation posix) readonly OT_POSIX_SIM_COMMON_OPTIONS=( + "-DOT_ANYCAST_LOCATOR=ON" "-DOT_BORDER_AGENT=ON" "-DOT_BORDER_ROUTER=ON" "-DOT_COAP=ON" diff --git a/script/make-pretty b/script/make-pretty index ad3a7b1b2..06477d875 100755 --- a/script/make-pretty +++ b/script/make-pretty @@ -74,6 +74,7 @@ readonly OT_PYTHON_SOURCES=('*.py') readonly OT_CLANG_TIDY_FIX_DIRS=('examples' 'include' 'src' 'tests') readonly OT_CLANG_TIDY_BUILD_OPTS=( '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON' + '-DOT_ANYCAST_LOCATOR=ON' '-DOT_APP_RCP=OFF' '-DOT_MTD=OFF' '-DOT_RCP=OFF' diff --git a/script/test b/script/test index c076b31e4..bae466ae4 100755 --- a/script/test +++ b/script/test @@ -56,6 +56,7 @@ build_simulation() local version="$1" local options=( "-DBUILD_TESTING=ON" + "-DOT_ANYCAST_LOCATOR=ON" "-DOT_DNS_CLIENT=ON" "-DOT_DNSSD_SERVER=ON" "-DOT_ECDSA=ON" @@ -268,6 +269,7 @@ do_build_otbr_docker() local otdir local otbrdir local otbr_options=( + "-DOT_ANYCAST_LOCATOR=ON" "-DOT_COVERAGE=ON" "-DOT_DNS_CLIENT=ON" "-DOT_DUA=ON" diff --git a/src/cli/README.md b/src/cli/README.md index 5661411c3..707cf74ec 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -63,6 +63,7 @@ Done - [leaderweight](#leaderweight) - [linkmetrics](#linkmetrics-mgmt-ipaddr-enhanced-ack-clear) - [linkquality](#linkquality-extaddr) +- [locate](#locate) - [log](#log-filename-filename) - [mac](#mac-retries-direct) - [macfilter](#macfilter) @@ -1614,6 +1615,60 @@ Set the link quality on the link to a given extended address. Done ``` +### locate + +Gets the current state (`In Progress` or `Idle`) of anycast locator. + +`OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE` is required. + +```bash +> locate +Idle +Done + +> locate fdde:ad00:beef:0:0:ff:fe00:fc10 + +> locate +In Progress +Done +``` + +### locate \ + +Locate the closest destination of an anycast address (i.e., find the destination's mesh local EID and RLOC16). + +`OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE` is required. + +The closest destination is determined based on the the current routing table and path costs within the Thread mesh. + +Locate the leader using its anycast address: + +```bash +> locate fdde:ad00:beef:0:0:ff:fe00:fc00 +fdde:ad00:beef:0:d9d3:9000:16b:d03b 0xc800 +Done +``` + +Locate the closest destination of a service anycast address: + +```bash + +> srp server enable +Done + +> netdata show +Prefixes: +Routes: +Services: +44970 5d c002 s c800 +44970 5d c002 s cc00 +Done + +> locate fdde:ad00:beef:0:0:ff:fe00:fc10 +fdde:ad00:beef:0:a477:dc98:a4e4:71ea 0xcc00 +done +``` + ### log filename \ - Note: Simulation Only, ie: `OPENTHREAD_EXAMPLES_SIMULATION` diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 3a545ad76..6efff36da 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -140,6 +140,9 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi , mOutputLength(0) , mIsLogging(false) #endif +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + , mLocateInProgress(false) +#endif { #if OPENTHREAD_FTD otThreadSetDiscoveryRequestCallback(mInstance, &Interpreter::HandleDiscoveryRequest, this); @@ -2554,6 +2557,57 @@ exit: } #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + +otError Interpreter::ProcessLocate(Arg aArgs[]) +{ + otError error = OT_ERROR_INVALID_ARGS; + otIp6Address anycastAddress; + + if (aArgs[0].IsEmpty()) + { + OutputLine(otThreadIsAnycastLocateInProgress(mInstance) ? "In Progress" : "Idle"); + ExitNow(error = OT_ERROR_NONE); + } + + SuccessOrExit(error = aArgs[0].ParseAsIp6Address(anycastAddress)); + SuccessOrExit(error = otThreadLocateAnycastDestination(mInstance, &anycastAddress, HandleLocateResult, this)); + SetCommandTimeout(kLocateTimeoutMsecs); + mLocateInProgress = true; + error = OT_ERROR_PENDING; + +exit: + return error; +} + +void Interpreter::HandleLocateResult(void * aContext, + otError aError, + const otIp6Address *aMeshLocalAddress, + uint16_t aRloc16) +{ + static_cast(aContext)->HandleLocateResult(aError, aMeshLocalAddress, aRloc16); +} + +void Interpreter::HandleLocateResult(otError aError, const otIp6Address *aMeshLocalAddress, uint16_t aRloc16) +{ + VerifyOrExit(mLocateInProgress); + + mLocateInProgress = false; + + if (aError == OT_ERROR_NONE) + { + OutputIp6Address(*aMeshLocalAddress); + OutputLine(" 0x%04x", aRloc16); + } + + OutputResult(aError); + +exit: + return; +} + +#endif // OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + #if OPENTHREAD_FTD otError Interpreter::ProcessPskc(Arg aArgs[]) { @@ -5191,7 +5245,17 @@ void Interpreter::HandleTimer(Timer &aTimer) void Interpreter::HandleTimer(void) { - OutputResult(kErrorNone); +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + if (mLocateInProgress) + { + mLocateInProgress = false; + OutputResult(OT_ERROR_RESPONSE_TIMEOUT); + } + else +#endif + { + OutputResult(OT_ERROR_NONE); + } } void Interpreter::SetCommandTimeout(uint32_t aTimeoutMilli) diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index c8000bc04..3ad0ca585 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -337,6 +337,7 @@ private: }; static constexpr uint32_t kNetworkDiagnosticTimeoutMsecs = 5000; + static constexpr uint32_t kLocateTimeoutMsecs = 2500; struct Command { @@ -555,6 +556,14 @@ private: otError ProcessLinkMetricsProbe(Arg aArgs[]); otError ParseLinkMetricsFlags(otLinkMetrics &aLinkMetrics, const Arg &aFlags); #endif +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + otError ProcessLocate(Arg aArgs[]); + static void HandleLocateResult(void * aContext, + otError aError, + const otIp6Address *aMeshLocalAddress, + uint16_t aRloc16); + void HandleLocateResult(otError aError, const otIp6Address *aMeshLocalAddress, uint16_t aRloc16); +#endif #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE otError ProcessMlr(Arg aArgs[]); @@ -867,6 +876,9 @@ private: #endif #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE {"linkmetrics", &Interpreter::ProcessLinkMetrics}, +#endif +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + {"locate", &Interpreter::ProcessLocate}, #endif {"log", &Interpreter::ProcessLog}, {"mac", &Interpreter::ProcessMac}, @@ -1025,7 +1037,10 @@ private: bool mIsLogging; #endif #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE - bool mPingIsAsync; + bool mPingIsAsync : 1; +#endif +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + bool mLocateInProgress : 1; #endif }; diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 222d99701..d7f95e74e 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -62,6 +62,10 @@ if (openthread_enable_core_config_args) { defines += [ "OPENTHREAD_CONFIG_ASSERT_ENABLE=0" ] } + if (openthread_config_anycast_locator_enable) { + defines += [ "OPENTHREAD_CONFIG_ANYCAST_LOCATOR_ENABLE=1" ] + } + if (openthread_config_backbone_router_enable) { defines += [ "OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE=1" ] } @@ -558,6 +562,8 @@ openthread_core_files = [ "thread/announce_begin_server.hpp", "thread/announce_sender.cpp", "thread/announce_sender.hpp", + "thread/anycast_locator.cpp", + "thread/anycast_locator.hpp", "thread/child_mask.hpp", "thread/child_table.cpp", "thread/child_table.hpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e18408560..6bdca8fea 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -177,6 +177,7 @@ set(COMMON_SOURCES thread/address_resolver.cpp thread/announce_begin_server.cpp thread/announce_sender.cpp + thread/anycast_locator.cpp thread/child_table.cpp thread/csl_tx_scheduler.cpp thread/discover_scanner.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 0ab57f578..245845ee8 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -254,6 +254,7 @@ SOURCES_COMMON = \ thread/address_resolver.cpp \ thread/announce_begin_server.cpp \ thread/announce_sender.cpp \ + thread/anycast_locator.cpp \ thread/child_table.cpp \ thread/csl_tx_scheduler.cpp \ thread/discover_scanner.cpp \ @@ -529,6 +530,7 @@ HEADERS_COMMON = \ thread/address_resolver.hpp \ thread/announce_begin_server.hpp \ thread/announce_sender.hpp \ + thread/anycast_locator.hpp \ thread/child_mask.hpp \ thread/child_table.hpp \ thread/csl_tx_scheduler.hpp \ diff --git a/src/core/api/thread_api.cpp b/src/core/api/thread_api.cpp index 0d4f71b0a..9ebafa9d7 100644 --- a/src/core/api/thread_api.cpp +++ b/src/core/api/thread_api.cpp @@ -486,4 +486,19 @@ void otThreadRegisterParentResponseCallback(otInstance * aInst AsCoreType(aInstance).Get().RegisterParentResponseStatsCallback(aCallback, aContext); } +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE +otError otThreadLocateAnycastDestination(otInstance * aInstance, + const otIp6Address * aAnycastAddress, + otThreadAnycastLocatorCallback aCallback, + void * aContext) +{ + return AsCoreType(aInstance).Get().Locate(AsCoreType(aAnycastAddress), aCallback, aContext); +} + +bool otThreadIsAnycastLocateInProgress(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().IsInProgress(); +} +#endif + #endif // OPENTHREAD_FTD || OPENTHREAD_MTD diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index 5ead9914e..7b6456a62 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -639,6 +639,13 @@ template <> inline PanIdQueryServer &Instance::Get(void) return mThreadNetif.mPanIdQuery; } +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE +template <> inline AnycastLocator &Instance::Get(void) +{ + return mThreadNetif.mAnycastLocator; +} +#endif + #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE template <> inline NetworkData::Local &Instance::Get(void) { diff --git a/src/core/config/tmf.h b/src/core/config/tmf.h index 2bff1426c..cf3c06216 100644 --- a/src/core/config/tmf.h +++ b/src/core/config/tmf.h @@ -172,6 +172,31 @@ #define OPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + * + * Define to 1 to enable TMF anycast locator functionality. + * + * This feature allows a device to determine the mesh local EID and RLOC16 of the closest destination of an anycast + * address (if any) through sending `TMF_ANYCAST_LOCATE` requests. + * + */ +#ifndef OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE +#define OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE 0 +#endif + +/** + * @def OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_SEND_RESPONSE + * + * Define to 1 to require the device to listen and respond to `TMF_ANYCAST_LOCATE` requests. + * + * This config is used only when `OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE` is enabled. It is enabled by default. + * + */ +#ifndef OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_SEND_RESPONSE +#define OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_SEND_RESPONSE 1 +#endif + /** * @def OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE * diff --git a/src/core/thread/anycast_locator.cpp b/src/core/thread/anycast_locator.cpp new file mode 100644 index 000000000..342c9c19c --- /dev/null +++ b/src/core/thread/anycast_locator.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2021, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements Anycast Locator functionality. + */ + +#include "anycast_locator.hpp" + +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + +#include "common/code_utils.hpp" +#include "common/instance.hpp" +#include "common/locator_getters.hpp" +#include "thread/thread_tlvs.hpp" +#include "thread/uri_paths.hpp" + +namespace ot { + +AnycastLocator::AnycastLocator(Instance &aInstance) + : InstanceLocator(aInstance) + , mCallback(nullptr) + , mContext(nullptr) +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_SEND_RESPONSE + , mAnycastLocate(UriPath::kAnycastLocate, HandleAnycastLocate, this) +#endif + +{ +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_SEND_RESPONSE + Get().AddResource(mAnycastLocate); +#endif +} + +Error AnycastLocator::Locate(const Ip6::Address &aAnycastAddress, Callback aCallback, void *aContext) +{ + Error error = kErrorNone; + Coap::Message * message = nullptr; + Ip6::MessageInfo messageInfo; + + VerifyOrExit((aCallback != nullptr) && Get().IsAnycastLocator(aAnycastAddress), + error = kErrorInvalidArgs); + + message = Get().NewMessage(); + VerifyOrExit(message != nullptr, error = kErrorNoBufs); + + SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kAnycastLocate)); + SuccessOrExit(error = message->SetPayloadMarker()); + + if (mCallback != nullptr) + { + IgnoreError(Get().AbortTransaction(HandleResponse, this)); + } + + messageInfo.SetSockAddr(Get().GetMeshLocal16()); + messageInfo.SetPeerAddr(aAnycastAddress); + messageInfo.SetPeerPort(Tmf::kUdpPort); + + SuccessOrExit(error = Get().SendMessage(*message, messageInfo, HandleResponse, this)); + + mCallback = aCallback; + mContext = aContext; + +exit: + FreeMessageOnError(message, error); + return error; +} + +void AnycastLocator::HandleResponse(void * aContext, + otMessage * aMessage, + const otMessageInfo *aMessageInfo, + Error aError) +{ + static_cast(aContext)->HandleResponse( + static_cast(aMessage), static_cast(aMessageInfo), aError); +} + +void AnycastLocator::HandleResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, Error aError) +{ + OT_UNUSED_VARIABLE(aMessageInfo); + + uint16_t rloc16 = Mac::kShortAddrInvalid; + const Ip6::Address *address = nullptr; + Ip6::Address meshLocalAddress; + + SuccessOrExit(aError); + OT_ASSERT(aMessage != nullptr); + + meshLocalAddress.SetPrefix(Get().GetMeshLocalPrefix()); + SuccessOrExit(Tlv::Find(*aMessage, meshLocalAddress.GetIid())); + SuccessOrExit(Tlv::Find(*aMessage, rloc16)); + +#if OPENTHREAD_FTD + Get().UpdateSnoopedCacheEntry(meshLocalAddress, rloc16, Get().GetShortAddress()); +#endif + + address = &meshLocalAddress; + +exit: + if (mCallback != nullptr) + { + Callback callback = mCallback; + + mCallback = nullptr; + callback(mContext, aError, address, rloc16); + } +} + +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_SEND_RESPONSE + +void AnycastLocator::HandleAnycastLocate(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) +{ + static_cast(aContext)->HandleAnycastLocate(*static_cast(aMessage), + *static_cast(aMessageInfo)); +} + +void AnycastLocator::HandleAnycastLocate(const Coap::Message &aRequest, const Ip6::MessageInfo &aMessageInfo) +{ + Coap::Message *message = nullptr; + + VerifyOrExit(aRequest.IsConfirmablePostRequest()); + + message = Get().NewMessage(); + VerifyOrExit(message != nullptr); + + SuccessOrExit(message->SetDefaultResponseHeader(aRequest)); + SuccessOrExit(message->SetPayloadMarker()); + + SuccessOrExit(Tlv::Append(*message, Get().GetMeshLocal64().GetIid())); + SuccessOrExit(Tlv::Append(*message, Get().GetRloc16())); + + SuccessOrExit(Get().SendMessage(*message, aMessageInfo)); + message = nullptr; + +exit: + FreeMessage(message); +} + +#endif // OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_SEND_RESPONSE + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE diff --git a/src/core/thread/anycast_locator.hpp b/src/core/thread/anycast_locator.hpp new file mode 100644 index 000000000..5ae937f45 --- /dev/null +++ b/src/core/thread/anycast_locator.hpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2021, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for Anycast Locator functionality. + */ + +#ifndef ANYCAST_LOCATOR_HPP_ +#define ANYCAST_LOCATOR_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + +#include "coap/coap.hpp" +#include "common/locator.hpp" +#include "common/non_copyable.hpp" +#include "net/ip6_address.hpp" + +namespace ot { + +/** + * This class implements Anycast Locator functionality which allows caller to determine the mesh local EID and RLOC16 + * of the closest destination of an anycast address (if any). + * + * The closest destination is determined based on the current routing table and path costs within the Thread mesh. + * + */ +class AnycastLocator : public InstanceLocator, private NonCopyable +{ +public: + /** + * This function pointer type defines the callback to notify the outcome of a request. + * + */ + typedef otThreadAnycastLocatorCallback Callback; + + /** + * This constructor initializes the `AnycastLocator` object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit AnycastLocator(Instance &aInstance); + + /** + * This method requests the closest destination of a given anycast address to be located. + * + * If a previous `Locate()` request is ongoing, a subsequent call to this method will cancel and replace the + * earlier request. + * + * @param[in] aAnycastAddress The anycast address to locate the closest destination of. + * @param[in] aCallback The callback handler to report the result. + * @param[in] aContext An arbitrary context used with @p aCallback. + * + * @retval kErrorNone The request started successfully. @p aCallback will be invoked to report the result. + * @retval kErrorNoBufs Out of buffers to prepare and send the request message. + * @retval kErrorInvalidArgs The @p aAnycastAddress is not a valid anycast address or @p aCallback is `nullptr`. + * + */ + Error Locate(const Ip6::Address &aAnycastAddress, Callback aCallback, void *aContext); + + /** + * This method indicates whether an earlier request is in progress. + * + * @returns TRUE if an earlier request is in progress, FALSE otherwise. + * + */ + bool IsInProgress(void) const { return (mCallback != nullptr); } + +private: + static void HandleResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, Error aError); + + void HandleResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, Error aError); + +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_SEND_RESPONSE + static void HandleAnycastLocate(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); + void HandleAnycastLocate(const Coap::Message &aRequest, const Ip6::MessageInfo &aMessageInfo); +#endif + + Callback mCallback; + void * mContext; +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_SEND_RESPONSE + Coap::Resource mAnycastLocate; +#endif +}; + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + +#endif // ANYCAST_LOCATOR_HPP_ diff --git a/src/core/thread/thread_netif.cpp b/src/core/thread/thread_netif.cpp index 8d4768aa8..b698e34c2 100644 --- a/src/core/thread/thread_netif.cpp +++ b/src/core/thread/thread_netif.cpp @@ -151,6 +151,9 @@ ThreadNetif::ThreadNetif(Instance &aInstance) , mAnnounceBegin(aInstance) , mPanIdQuery(aInstance) , mEnergyScan(aInstance) +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + , mAnycastLocator(aInstance) +#endif #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE , mTimeSync(aInstance) #endif diff --git a/src/core/thread/thread_netif.hpp b/src/core/thread/thread_netif.hpp index df6ae6fe0..15e9d7684 100644 --- a/src/core/thread/thread_netif.hpp +++ b/src/core/thread/thread_netif.hpp @@ -88,6 +88,7 @@ #include "net/srp_client.hpp" #include "thread/address_resolver.hpp" #include "thread/announce_begin_server.hpp" +#include "thread/anycast_locator.hpp" #include "thread/discover_scanner.hpp" #include "thread/energy_scan_server.hpp" #include "thread/key_manager.hpp" @@ -301,6 +302,9 @@ private: AnnounceBeginServer mAnnounceBegin; PanIdQueryServer mPanIdQuery; EnergyScanServer mEnergyScan; +#if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + AnycastLocator mAnycastLocator; +#endif #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE TimeSync mTimeSync; diff --git a/src/core/thread/tmf.hpp b/src/core/thread/tmf.hpp index dd5d4998a..738f665e7 100644 --- a/src/core/thread/tmf.hpp +++ b/src/core/thread/tmf.hpp @@ -34,6 +34,8 @@ #ifndef OT_CORE_THREAD_TMF_HPP_ #define OT_CORE_THREAD_TMF_HPP_ +#include "openthread-core-config.h" + #include "coap/coap.hpp" namespace ot { diff --git a/src/core/thread/uri_paths.cpp b/src/core/thread/uri_paths.cpp index f5a794da5..9f72a6aef 100644 --- a/src/core/thread/uri_paths.cpp +++ b/src/core/thread/uri_paths.cpp @@ -40,6 +40,7 @@ const char UriPath::kAddressNotify[] = "a/an"; const char UriPath::kAddressError[] = "a/ae"; const char UriPath::kAddressRelease[] = "a/ar"; const char UriPath::kAddressSolicit[] = "a/as"; +const char UriPath::kAnycastLocate[] = "a/yl"; const char UriPath::kActiveGet[] = "c/ag"; const char UriPath::kActiveSet[] = "c/as"; const char UriPath::kDatasetChanged[] = "c/dc"; diff --git a/src/core/thread/uri_paths.hpp b/src/core/thread/uri_paths.hpp index ef5bb193c..30c7999e2 100644 --- a/src/core/thread/uri_paths.hpp +++ b/src/core/thread/uri_paths.hpp @@ -50,6 +50,7 @@ struct UriPath static const char kAddressError[]; ///< The URI Path for Address Error ("a/ae"). static const char kAddressRelease[]; ///< The URI Path for Address Release ("a/ar"). static const char kAddressSolicit[]; ///< The URI Path for Address Solicit ("a/as"). + static const char kAnycastLocate[]; ///< The URI Path for Anycast Locate ("a/yl") static const char kActiveGet[]; ///< The URI Path for MGMT_ACTIVE_GE ("c/ag")T static const char kActiveSet[]; ///< The URI Path for MGMT_ACTIVE_SET ("c/as"). static const char kDatasetChanged[]; ///< The URI Path for MGMT_DATASET_CHANGED ("c/dc"). diff --git a/src/posix/Makefile-posix b/src/posix/Makefile-posix index 9740197eb..24c7a83fd 100644 --- a/src/posix/Makefile-posix +++ b/src/posix/Makefile-posix @@ -35,6 +35,7 @@ DEBUG ?= 0 # Enable most features by default to cover most code +ANYCAST_LOCATOR ?= 1 BORDER_AGENT ?= 1 BORDER_ROUTER ?= 1 COAP ?= 1 diff --git a/tests/scripts/thread-cert/Makefile.am b/tests/scripts/thread-cert/Makefile.am index 07b3908ac..43356a1eb 100644 --- a/tests/scripts/thread-cert/Makefile.am +++ b/tests/scripts/thread-cert/Makefile.am @@ -152,6 +152,7 @@ EXTRA_DIST = \ sniffer.py \ sniffer_transport.py \ test_anycast.py \ + test_anycast_locator.py \ test_coap.py \ test_coap_block.py \ test_coap_observe.py \ @@ -222,6 +223,7 @@ check_PROGRAMS = \ check_SCRIPTS = \ test_anycast.py \ + test_anycast_locator.py \ test_coap.py \ test_coap_block.py \ test_coap_observe.py \ diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 03c0b4d26..4c5811c4c 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -1082,6 +1082,22 @@ class NodeImpl: values = [key_value[1].strip('"') for key_value in key_values] return dict(zip(keys, values)) + def locate(self, anycast_addr): + cmd = 'locate ' + anycast_addr + self.send_command(cmd) + self.simulator.go(5) + return self._parse_locate_result(self._expect_command_output(cmd)[0]) + + def _parse_locate_result(self, line: str): + """Parse anycast locate result as list of ml-eid and rloc16. + + Example output for input + 'fd00:db8:0:0:acf9:9d0:7f3c:b06e 0xa800' + + [ 'fd00:db8:0:0:acf9:9d0:7f3c:b06e', '0xa800' ] + """ + return line.split(' ') + def enable_backbone_router(self): cmd = 'bbr enable' self.send_command(cmd) diff --git a/tests/scripts/thread-cert/test_anycast_locator.py b/tests/scripts/thread-cert/test_anycast_locator.py new file mode 100755 index 000000000..c45da666b --- /dev/null +++ b/tests/scripts/thread-cert/test_anycast_locator.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2021, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +import config +import ipaddress +import unittest + +import command +import thread_cert + +# Test description: +# This test verifies Anycast Locator functionality +# +# Topology: +# +# LEADER -- ROUTER1 -- ROUTER2 -- ROUTER3 -- ROUTER4 +# + +LEADER = 1 +ROUTER1 = 2 +ROUTER2 = 3 +ROUTER3 = 4 +ROUTER4 = 5 + +SRV_ENT_NUMBER = '44970' +SRV_SERVICE_DATA = '571234' +SRV_SERVER_DATA = '00' + + +class AnycastLocator(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + SUPPORT_NCP = False + + TOPOLOGY = { + LEADER: { + 'name': 'LEADER', + 'mode': 'rdn', + 'allowlist': [ROUTER1] + }, + ROUTER1: { + 'name': 'ROUTER1', + 'mode': 'rdn', + 'allowlist': [LEADER, ROUTER2] + }, + ROUTER2: { + 'name': 'ROUTER2', + 'mode': 'rdn', + 'allowlist': [ROUTER1, ROUTER3] + }, + ROUTER3: { + 'name': 'ROUTER3', + 'mode': 'rdn', + 'allowlist': [ROUTER2, ROUTER4] + }, + ROUTER4: { + 'name': 'ROUTER4', + 'mode': 'rdn', + 'allowlist': [ROUTER3] + }, + } + + def test(self): + leader = self.nodes[LEADER] + router1 = self.nodes[ROUTER1] + router2 = self.nodes[ROUTER2] + router3 = self.nodes[ROUTER3] + router4 = self.nodes[ROUTER4] + nodes = [leader, router1, router2, router3, router4] + + # + # 0. Start the leader and routers + # + + leader.start() + self.simulator.go(5) + self.assertEqual(leader.get_state(), 'leader') + + for node in nodes[1:]: + node.start() + self.simulator.go(5) + self.assertEqual(node.get_state(), 'router') + + # + # 1. Locate leader ALOC from all nodes + # + + leader_aloc = leader.get_addr_leader_aloc() + + for node in nodes: + result = node.locate(leader_aloc) + self.assertEqual(result[0], leader.get_mleid()) + self.assertEqual(int(result[1], 0), leader.get_addr16()) + + # + # 2. Add a service on router4 and locate its ALOC + # + + router4.add_service(SRV_ENT_NUMBER, SRV_SERVICE_DATA, SRV_SERVER_DATA) + router4.register_netdata() + self.simulator.go(5) + services = leader.get_services() + self.assertEqual(len(services), 1) + + service_aloc = router4.get_ip6_address(config.ADDRESS_TYPE.ALOC)[0] + + for node in nodes: + result = node.locate(service_aloc) + self.assertEqual(result[0], router4.get_mleid()) + self.assertEqual(int(result[1], 0), router4.get_addr16()) + + # + # 3. Add same service on leader and ensure we locate the closest ALOC destination + # + + leader.add_service(SRV_ENT_NUMBER, SRV_SERVICE_DATA, SRV_SERVER_DATA) + leader.register_netdata() + self.simulator.go(5) + services = leader.get_services() + self.assertEqual(len(services), 2) + + # leader and router1 should locate leader as closest service ALOC, + # router3 and router4 should locate router4. router2 is in middle + # and can locate either one. + for node in [leader, router1]: + result = node.locate(service_aloc) + self.assertEqual(result[0], leader.get_mleid()) + self.assertEqual(int(result[1], 0), leader.get_addr16()) + + for node in [router3, router4]: + result = node.locate(service_aloc) + self.assertEqual(result[0], router4.get_mleid()) + self.assertEqual(int(result[1], 0), router4.get_addr16()) + + result = router2.locate(service_aloc) + self.assertTrue(result[0] in [leader.get_mleid(), router4.get_mleid()]) + self.assertTrue(int(result[1], 0) in [leader.get_addr16(), router4.get_addr16()]) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h index daf58df6c..d0b718aca 100644 --- a/tests/toranj/openthread-core-toranj-config.h +++ b/tests/toranj/openthread-core-toranj-config.h @@ -119,6 +119,14 @@ */ #define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE 1 +/** + * @def OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE + * + * Define to 1 to enable TMF anycast locator functionality. + * + */ +#define OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE 1 + /** * @def OPENTHREAD_CONFIG_LEGACY_ENABLE *