From 293b41c8081b7a7a75114b0f494219e4368980a0 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Thu, 8 May 2025 20:39:54 -0700 Subject: [PATCH] [border-agent] manage mDNS MeshCoP service registrations (#11455) This commit updates `BorderAgent` modules to directly manage the registration of mDNS MeshCoP services. Previously, this was the responsibility of the platform or higher-level code. This behavior is enabled using `OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE` configuration option. When enabled, the `BorderAgent` module itself will register the `_meshcop._udp` service name with properly formatted TXT data. As the state changes, the service registration is updated accordingly. If the ephemeral key feature is enabled and used, the `BorderAgent` will also manage the registration of the `_meshcop-e._udp` service. The implementation allows the service instance name to be configured in different ways. The Thread specification recommends using a user-friendly name, such as " ". The name can be set using a newly added configuration option, or alternatively, using a newly added public API for projects where the name needs to be set at run-time after device initialization. This commit also updates `test_border_agent`, validating all the newly added behaviors related to MeshCoP service registrations. --- etc/cmake/options.cmake | 2 + include/openthread/border_agent.h | 36 ++++ include/openthread/instance.h | 2 +- src/cli/README.md | 15 ++ src/cli/cli.cpp | 16 ++ src/core/api/border_agent_api.cpp | 9 + src/core/config/border_agent.h | 32 +++ src/core/meshcop/border_agent.cpp | 179 +++++++++++++++- src/core/meshcop/border_agent.hpp | 60 +++++- src/core/net/dnssd.cpp | 4 + tests/nexus/openthread-core-nexus-config.h | 6 +- tests/nexus/platform/nexus_misc.cpp | 30 +++ tests/nexus/test_border_agent.cpp | 238 +++++++++++++++++++++ tests/unit/test_mdns.cpp | 8 + tests/unit/test_srp_adv_proxy.cpp | 7 + 15 files changed, 634 insertions(+), 10 deletions(-) diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 90ce3e27a..bffda1fa4 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -176,6 +176,7 @@ ot_option(OT_BLE_TCAT OPENTHREAD_CONFIG_BLE_TCAT_ENABLE "Ble based thread commis ot_option(OT_BORDER_AGENT OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE "border agent") ot_option(OT_BORDER_AGENT_EPSKC OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE "border agent ephemeral PSKc") ot_option(OT_BORDER_AGENT_ID OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE "create and save border agent ID") +ot_option(OT_BORDER_AGENT_MESHCOP_SERVICE OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE "border agent meshcop service") ot_option(OT_BORDER_ROUTER OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE "border router") ot_option(OT_BORDER_ROUTING OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE "border routing") ot_option(OT_BORDER_ROUTING_DHCP6_PD OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE "dhcpv6 pd support in border routing") @@ -332,6 +333,7 @@ ot_multi_option(OT_LOG_OUTPUT OT_LOG_OUTPUT_VALUES OPENTHREAD_CONFIG_LOG_OUTPUT ot_string_option(OT_VENDOR_NAME OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "set the vendor name config") ot_string_option(OT_VENDOR_MODEL OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL "set the vendor model config") ot_string_option(OT_VENDOR_SW_VERSION OPENTHREAD_CONFIG_NET_DIAG_VENDOR_SW_VERSION "set the vendor sw version config") +ot_string_option(OT_BORDER_AGENT_SERVICE_NAME OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME "set the border agent service base name") set(OT_POWER_SUPPLY_VALUES "BATTERY" "EXTERNAL" "EXTERNAL_STABLE" "EXTERNAL_UNSTABLE") ot_multi_option(OT_POWER_SUPPLY OT_POWER_SUPPLY_VALUES OPENTHREAD_CONFIG_DEVICE_POWER_SUPPLY OT_POWER_SUPPLY_ "set the device power supply config") diff --git a/include/openthread/border_agent.h b/include/openthread/border_agent.h index 3a5b3810c..4ba8bc9a3 100644 --- a/include/openthread/border_agent.h +++ b/include/openthread/border_agent.h @@ -35,6 +35,7 @@ #ifndef OPENTHREAD_BORDER_AGENT_H_ #define OPENTHREAD_BORDER_AGENT_H_ +#include #include #include @@ -242,6 +243,41 @@ void otBorderAgentSetMeshCoPServiceChangedCallback(otInstance */ otError otBorderAgentGetMeshCoPServiceTxtData(otInstance *aInstance, otBorderAgentMeshCoPServiceTxtData *aTxtData); +/** + * Maximum string length of base name used in `otBorderAgentSetMeshCoPServiceBaseName()`. + * + * The full DNS label is constructed by appending the Extended Address of the device (as 16-character hex digits) to + * the given base name. + */ +#define OT_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME_MAX_LENGTH (OT_DNS_MAX_LABEL_SIZE - 17) + +/** + * Sets the base name to construct the service instance name used when advertising the mDNS `_meshcop._udp` service by + * the Border Agent. + * + * Requires the `OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE` feature. + * + * The name can also be configured using the `OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME` configuration + * option (which is the recommended way to specify this name). This API is provided for projects where the name needs + * to be set after device initialization and at run-time. + * + * Per the Thread specification, the service instance should be a user-friendly name identifying the device model or + * product. A recommended format is " ". + * + * To construct the full name and ensure name uniqueness, the OpenThread Border Agent module will append the Extended + * Address of the device (as 16-character hex digits) to the given base name. + * + * Note that the same name will be used for the ephemeral key service `_meshcop-e._udp` when the ephemeral key feature + * is enabled and used. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aBaseName The base name to use (MUST not be NULL). + * + * @retval OT_ERROR_NONE The name was set successfully. + * @retval OT_ERROR_INVALID_ARGS The name is too long or invalid. + */ +otError otBorderAgentSetMeshCoPServiceBaseName(otInstance *aInstance, const char *aBaseName); + /** * Gets the randomly generated Border Agent ID. * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 29f30be69..334de3c98 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (505) +#define OPENTHREAD_API_VERSION (506) /** * @addtogroup api-instance diff --git a/src/cli/README.md b/src/cli/README.md index c7fe94831..29766cf20 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -410,6 +410,21 @@ Active Done ``` +### ba servicebasename \ + +Sets the base name to construct the service instance name used when advertising the mDNS `_meshcop._udp` service by the Border Agent. + +Requires the `OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE` feature. + +The name can also be configured using the `OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME` configuration option (which is the recommended way to specify this name). This CLI command (and its corresponding API) is provided for projects where the name needs to be set after device initialization and at run-time. + +Per the Thread specification, the service instance should be a user-friendly name identifying the device model or product. A recommended format is " ". To construct the full name and ensure name uniqueness, the OpenThread Border Agent module will append the Extended Address of the device (as 16-character hex digits) to the given base name. Note that the same name will be used for the ephemeral key service `_meshcop-e._udp` when the ephemeral key feature is enabled and used. + +```bash +ba servicebasename OpenThreadBorderAgent +Done +``` + ### ba sessions Prints the list of Border Agent's sessions. Information per session: diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index e90f3ff97..42dc21785 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -502,6 +502,22 @@ template <> otError Interpreter::Process(Arg aArgs[]) OutputLine("%s", otBorderAgentIsActive(GetInstancePtr()) ? "Active" : "Inactive"); } } +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + /** + * @cli ba servicebasename + * @code + * ba servicebasename OpenThreadBorderAgent + * Done + * @endcode + * @par api_copy + * #otBorderAgentSetMeshCoPServiceBaseName + */ + else if (aArgs[0] == "servicebasename") + { + VerifyOrExit(!aArgs[1].IsEmpty() && aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + error = otBorderAgentSetMeshCoPServiceBaseName(GetInstancePtr(), aArgs[1].GetCString()); + } +#endif /** * @cli ba sessions * @code diff --git a/src/core/api/border_agent_api.cpp b/src/core/api/border_agent_api.cpp index 88eef3433..5ab314d5e 100644 --- a/src/core/api/border_agent_api.cpp +++ b/src/core/api/border_agent_api.cpp @@ -57,6 +57,15 @@ bool otBorderAgentIsActive(otInstance *aInstance) return AsCoreType(aInstance).Get().IsRunning(); } +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE +otError otBorderAgentSetMeshCoPServiceBaseName(otInstance *aInstance, const char *aBaseName) +{ + AssertPointerIsNotNull(aBaseName); + + return AsCoreType(aInstance).Get().SetServiceBaseName(aBaseName); +} +#endif + #if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE otError otBorderAgentGetId(otInstance *aInstance, otBorderAgentId *aId) { diff --git a/src/core/config/border_agent.h b/src/core/config/border_agent.h index edb99d380..e743ed827 100644 --- a/src/core/config/border_agent.h +++ b/src/core/config/border_agent.h @@ -89,6 +89,38 @@ OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE #endif +/** + * @def OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + * + * Define to 1 to enable Border Agent to manage registering/updating of the mDNS MeshCoP service(s) on the + * infrastructure link + * + * This includes the ephemeral key service when the `OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE` is enabled. + */ +#ifndef OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE +#define OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE \ + (OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE || OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE) +#endif + +/** + * @def OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME + * + * Specifies the base name to construct the service instance name used when advertising the mDNS `_meshcop._udp` + * service by the Border Agent. + * + * Applicable when the `OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE` feature is enabled. + * + * The name can also be configured using the `otBorderAgentSetMeshCoPServiceBaseName()` API at run-time. + * + * Per the Thread specification, the service instance should be a user-friendly name identifying the device model or + * product. A recommended format is " ". + * + * The name MUST have a length less than or equal to `OT_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME_MAX_LENGTH` (47 chars). + */ +#ifndef OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME +#define OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME "OpenThread BR (unspecified vendor) " +#endif + /** * @} */ diff --git a/src/core/meshcop/border_agent.cpp b/src/core/meshcop/border_agent.cpp index b118a4eee..0a8a179e2 100644 --- a/src/core/meshcop/border_agent.cpp +++ b/src/core/meshcop/border_agent.cpp @@ -45,6 +45,11 @@ RegisterLogModule("BorderAgent"); //---------------------------------------------------------------------------------------------------------------------- // `BorderAgent` +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE +const char BorderAgent::kServiceType[] = "_meshcop._udp"; +const char BorderAgent::kDefaultBaseServiceName[] = OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME; +#endif + BorderAgent::BorderAgent(Instance &aInstance) : InstanceLocator(aInstance) , mEnabled(true) @@ -59,6 +64,14 @@ BorderAgent::BorderAgent(Instance &aInstance) #endif { ClearAllBytes(mCounters); + +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + ClearAllBytes(mServiceName); + PostServiceTask(); + + static_assert(sizeof(kDefaultBaseServiceName) - 1 <= kBaseServiceNameMaxLen, + "OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME is too long"); +#endif } #if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE @@ -111,6 +124,13 @@ void BorderAgent::SetEnabled(bool aEnabled) LogInfo("%sabling Border Agent", mEnabled ? "En" : "Dis"); UpdateState(); +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + if (!mEnabled) + { + UnregisterService(); + } +#endif + exit: return; } @@ -365,24 +385,113 @@ exit: FreeMessageOnError(message, error); } +void BorderAgent::PostServiceTask(void) +{ + VerifyOrExit(mEnabled); + +#if !OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + VerifyOrExit(mServiceChangedCallback.IsSet()); +#endif + + mServiceTask.Post(); + +exit: + return; +} + void BorderAgent::HandleServiceTask(void) { VerifyOrExit(mEnabled); +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + RegisterService(); +#endif mServiceChangedCallback.InvokeIfSet(); exit: return; } -void BorderAgent::PostServiceTask(void) +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + +Error BorderAgent::SetServiceBaseName(const char *aBaseName) { - if (mEnabled && mServiceChangedCallback.IsSet()) - { - mServiceTask.Post(); - } + Error error = kErrorNone; + Dns::Name::LabelBuffer newName; + + VerifyOrExit(StringLength(aBaseName, kBaseServiceNameMaxLen + 1) <= kBaseServiceNameMaxLen, + error = kErrorInvalidArgs); + + ConstrcutServiceName(aBaseName, newName); + + VerifyOrExit(!StringMatch(newName, mServiceName)); + + UnregisterService(); + IgnoreError(StringCopy(mServiceName, newName)); + RegisterService(); + +exit: + return error; } +const char *BorderAgent::GetServiceName(void) +{ + if (IsServiceNameEmpty()) + { + ConstrcutServiceName(kDefaultBaseServiceName, mServiceName); + } + + return mServiceName; +} + +void BorderAgent::ConstrcutServiceName(const char *aBaseName, Dns::Name::LabelBuffer &aNameBuffer) +{ + StringWriter writer(aNameBuffer, sizeof(Dns::Name::LabelBuffer)); + + writer.Append("%.*s%s", kBaseServiceNameMaxLen, aBaseName, Get().GetExtAddress().ToString().AsCString()); +} + +void BorderAgent::RegisterService(void) +{ + ServiceTxtData txtData; + Dnssd::Service service; + + VerifyOrExit(Get().IsReady()); + + SuccessOrAssert(PrepareServiceTxtData(txtData)); + + service.Clear(); + service.mServiceInstance = GetServiceName(); + service.mServiceType = kServiceType; + service.mPort = IsRunning() ? GetUdpPort() : kDummyUdpPort; + service.mTxtData = txtData.mData; + service.mTxtDataLength = txtData.mLength; + + Get().RegisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr); + +exit: + return; +} + +void BorderAgent::UnregisterService(void) +{ + Dnssd::Service service; + + VerifyOrExit(Get().IsReady()); + VerifyOrExit(!IsServiceNameEmpty()); + + service.Clear(); + service.mServiceInstance = GetServiceName(); + service.mServiceType = kServiceType; + + Get().UnregisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr); + +exit: + return; +} + +#endif // OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + Error BorderAgent::PrepareServiceTxtData(ServiceTxtData &aTxtData) { Error error = kErrorNone; @@ -529,6 +638,10 @@ exit: #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE +const char BorderAgent::EphemeralKeyManager::kServiceType[] = "_meshcop-e._udp"; +#endif + BorderAgent::EphemeralKeyManager::EphemeralKeyManager(Instance &aInstance) : InstanceLocator(aInstance) #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_FEATURE_ENABLED_BY_DEFAULT @@ -659,11 +772,20 @@ exit: void BorderAgent::EphemeralKeyManager::SetState(State aState) { +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + bool isServiceRegistered = ShouldRegisterService(); +#endif + VerifyOrExit(mState != aState); LogInfo("Ephemeral key - state: %s -> %s", StateToString(mState), StateToString(aState)); mState = aState; mCallbackTask.Post(); +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + VerifyOrExit(isServiceRegistered != ShouldRegisterService()); + RegisterOrUnregisterService(); +#endif + exit: return; } @@ -761,6 +883,53 @@ void BorderAgent::EphemeralKeyManager::HandleTransportClosed(void) ; } +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + +bool BorderAgent::EphemeralKeyManager::ShouldRegisterService(void) const +{ + bool shouldRegister = false; + + switch (mState) + { + case kStateDisabled: + case kStateStopped: + break; + case kStateStarted: + case kStateConnected: + case kStateAccepted: + shouldRegister = true; + break; + } + + return shouldRegister; +} + +void BorderAgent::EphemeralKeyManager::RegisterOrUnregisterService(void) +{ + Dnssd::Service service; + + VerifyOrExit(Get().IsReady()); + + service.Clear(); + service.mServiceInstance = Get().GetServiceName(); + service.mServiceType = kServiceType; + service.mPort = GetUdpPort(); + + if (ShouldRegisterService()) + { + Get().RegisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr); + } + else + { + Get().UnregisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr); + } + +exit: + return; +} + +#endif // OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + const char *BorderAgent::EphemeralKeyManager::StateToString(State aState) { static const char *const kStateStrings[] = { diff --git a/src/core/meshcop/border_agent.hpp b/src/core/meshcop/border_agent.hpp index 9c792aa9d..721c64c1d 100644 --- a/src/core/meshcop/border_agent.hpp +++ b/src/core/meshcop/border_agent.hpp @@ -49,11 +49,11 @@ #include "common/non_copyable.hpp" #include "common/notifier.hpp" #include "common/owned_ptr.hpp" -#include "common/random.hpp" #include "common/tasklet.hpp" #include "meshcop/dataset.hpp" #include "meshcop/secure_transport.hpp" #include "net/dns_types.hpp" +#include "net/dnssd.hpp" #include "net/socket.hpp" #include "net/udp6.hpp" #include "thread/tmf.hpp" @@ -67,10 +67,25 @@ namespace MeshCoP { #error "Border Agent feature requires `OPENTHREAD_CONFIG_SECURE_TRANSPORT_ENABLE`" #endif +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + +#if !(OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE || OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE) +#error "OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE requires either the native mDNS or platform DNS-SD APIs" +#endif + +#if !OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE +#error "OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE requires OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE" +#endif + +#endif + class BorderAgent : public InstanceLocator, private NonCopyable { #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE friend class ot::BorderRouter::RoutingManager; +#endif +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + friend ot::Dnssd; #endif friend class ot::Notifier; friend class Tmf::Agent; @@ -229,6 +244,19 @@ public: */ Error PrepareServiceTxtData(ServiceTxtData &aTxtData); +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + /** + * Sets the base name to construct the service instance name used when advertising the mDNS `_meshcop._udp` service + * by the Border Agent. + * + * @param[in] aBaseName The base name to use (MUST not be NULL). + * + * @retval kErrorNone The name was set successfully. + * @retval kErrorInvalidArgs The name is too long or invalid. + */ + Error SetServiceBaseName(const char *aBaseName); +#endif + #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE /** * Manages the ephemeral key use by Border Agent. @@ -359,6 +387,10 @@ public: void HandleSessionConnected(void); void HandleSessionDisconnected(SecureSession::ConnectEvent aEvent); void HandleCommissionerPetitionAccepted(void); +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + bool ShouldRegisterService(void) const; + void RegisterOrUnregisterService(void); +#endif // Session or Transport callbacks static SecureSession *HandleAcceptSession(void *aContext, const Ip6::MessageInfo &aMessageInfo); @@ -372,6 +404,10 @@ public: static const char *StopReasonToString(StopReason aReason); #endif +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + static const char kServiceType[]; +#endif + using TimeoutTimer = TimerMilliIn; using CallbackTask = TaskletIn; @@ -403,6 +439,11 @@ private: static constexpr uint16_t kUdpPort = OPENTHREAD_CONFIG_BORDER_AGENT_UDP_PORT; static constexpr uint32_t kKeepAliveTimeout = 50 * 1000; // Timeout to reject a commissioner (in msec) +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + static constexpr uint16_t kDummyUdpPort = 49152; + static constexpr uint8_t kBaseServiceNameMaxLen = OT_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME_MAX_LENGTH; +#endif + class CoapDtlsSession : public Coap::SecureSession, public Heap::Allocatable { friend Heap::Allocatable; @@ -544,8 +585,22 @@ private: void HandleFavoredOmrPrefixChanged(void) { PostServiceTask(); } #endif +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + const char *GetServiceName(void); + bool IsServiceNameEmpty(void) const { return mServiceName[0] == kNullChar; } + void ConstrcutServiceName(const char *aBaseName, Dns::Name::LabelBuffer &aNameBuffer); + void RegisterService(void); + void UnregisterService(void); + void HandleDnssdPlatformStateChange(void) { PostServiceTask(); } +#endif + using ServiceTask = TaskletIn; +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + static const char kServiceType[]; + static const char kDefaultBaseServiceName[]; +#endif + bool mEnabled; bool mIsRunning; Dtls::Transport mDtlsTransport; @@ -557,6 +612,9 @@ private: ServiceTask mServiceTask; #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE EphemeralKeyManager mEphemeralKeyManager; +#endif +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + Dns::Name::LabelBuffer mServiceName; #endif Counters mCounters; }; diff --git a/src/core/net/dnssd.cpp b/src/core/net/dnssd.cpp index 086b5a555..27f193088 100644 --- a/src/core/net/dnssd.cpp +++ b/src/core/net/dnssd.cpp @@ -526,6 +526,10 @@ void Dnssd::HandleStateChange(void) #if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE && OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE Get().HandleDnssdPlatformStateChange(); #endif + +#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + Get().HandleDnssdPlatformStateChange(); +#endif } #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE diff --git a/tests/nexus/openthread-core-nexus-config.h b/tests/nexus/openthread-core-nexus-config.h index 88d519088..d18a2cae4 100644 --- a/tests/nexus/openthread-core-nexus-config.h +++ b/tests/nexus/openthread-core-nexus-config.h @@ -94,9 +94,9 @@ #define OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE 0 #define OPENTHREAD_CONFIG_MLE_MAX_CHILDREN 128 #define OPENTHREAD_CONFIG_MULTICAST_DNS_AUTO_ENABLE_ON_INFRA_IF 0 -#define OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE 0 -#define OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE 0 -#define OPENTHREAD_CONFIG_MULTICAST_DNS_PUBLIC_API_ENABLE 0 +#define OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE 1 +#define OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE 1 +#define OPENTHREAD_CONFIG_MULTICAST_DNS_PUBLIC_API_ENABLE 1 #define OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE 0 #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE OPENTHREAD_FTD #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL "Nexus Simulation" diff --git a/tests/nexus/platform/nexus_misc.cpp b/tests/nexus/platform/nexus_misc.cpp index 09919940f..dba1f55b5 100644 --- a/tests/nexus/platform/nexus_misc.cpp +++ b/tests/nexus/platform/nexus_misc.cpp @@ -104,6 +104,36 @@ exit: return error; } +//--------------------------------------------------------------------------------------------------------------------- +// otPlatMdns + +OT_TOOL_WEAK otError otPlatMdnsSetListeningEnabled(otInstance *aInstance, bool aEnable, uint32_t aInfraIfIndex) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aEnable); + OT_UNUSED_VARIABLE(aInfraIfIndex); + + return kErrorNone; +} + +OT_TOOL_WEAK void otPlatMdnsSendMulticast(otInstance *aInstance, otMessage *aMessage, uint32_t aInfraIfIndex) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aInfraIfIndex); + + AsCoreType(aMessage).Free(); +} + +OT_TOOL_WEAK void otPlatMdnsSendUnicast(otInstance *aInstance, + otMessage *aMessage, + const otPlatMdnsAddressInfo *aAddress) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aAddress); + + AsCoreType(aMessage).Free(); +} + //--------------------------------------------------------------------------------------------------------------------- // Misc diff --git a/tests/nexus/test_border_agent.cpp b/tests/nexus/test_border_agent.cpp index cb95616ec..662c58538 100644 --- a/tests/nexus/test_border_agent.cpp +++ b/tests/nexus/test_border_agent.cpp @@ -987,6 +987,243 @@ void TestBorderAgentTxtDataCallback(void) VerifyOrQuit(!(stateBitmap & kFlagEpskcSupported)); } +//---------------------------------------------------------------------------------------------------------------------- + +void TestBorderAgentServiceRegisteration(void) +{ + static const char kDefaultServiceBaseName[] = OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME; + + static const char kEphemeralKey[] = "nexus1234"; + + static constexpr uint32_t kUdpPort = 49155; + static constexpr uint32_t kInfraIfIndex = 1; + + Core nexus; + Node &node0 = nexus.CreateNode(); + Dns::Multicast::Core::Iterator *iterator; + Dns::Multicast::Core::Service service; + Dns::Multicast::Core::EntryState entryState; + + Log("------------------------------------------------------------------------------------------------------"); + Log("TestBorderAgentServiceRegisteration"); + + nexus.AdvanceTime(0); + + node0.Form(); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Enable mDNS + SuccessOrQuit(node0.Get().SetEnabled(true, kInfraIfIndex)); + VerifyOrQuit(node0.Get().IsEnabled()); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + nexus.AdvanceTime(50 * Time::kOneSecondInMsec); + VerifyOrQuit(node0.Get().IsLeader()); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + VerifyOrQuit(node0.Get().IsEnabled()); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Validate the registered mDNS MeshCop service by Border Agent"); + + iterator = node0.Get().AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + SuccessOrQuit(node0.Get().GetNextService(*iterator, service, entryState)); + + Log(" HostName: %s", service.mHostName); + Log(" ServiceInstance: %s", service.mServiceInstance); + Log(" ServiceType: %s", service.mServiceType); + Log(" Port: %u", service.mPort); + Log(" TTL: %lu", ToUlong(service.mTtl)); + + VerifyOrQuit(StringMatch(service.mServiceType, "_meshcop._udp")); + VerifyOrQuit(StringStartsWith(service.mServiceInstance, kDefaultServiceBaseName)); + VerifyOrQuit(StringStartsWith(service.mHostName, "ot")); + VerifyOrQuit(service.mPort == node0.Get().GetUdpPort()); + VerifyOrQuit(service.mSubTypeLabelsLength == 0); + VerifyOrQuit(service.mTxtDataLength > 1); + VerifyOrQuit(service.mTtl > 0); + VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex); + VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED); + + // Check that there is no more registered mDNS service + VerifyOrQuit(node0.Get().GetNextService(*iterator, service, entryState) == kErrorNotFound); + + node0.Get().FreeIterator(*iterator); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Enable ans start ephemeral key"); + + node0.Get().SetEnabled(true); + VerifyOrQuit(node0.Get().GetState() == EphemeralKeyManager::kStateStopped); + node0.Get().SetCallback(HandleEphemeralKeyChange, &node0); + + SuccessOrQuit(node0.Get().Start(kEphemeralKey, /* aTimeout */ 0, kUdpPort)); + + nexus.AdvanceTime(10 * Time::kOneSecondInMsec); + + VerifyOrQuit(node0.Get().GetState() == EphemeralKeyManager::kStateStarted); + VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check the registered services"); + + iterator = node0.Get().AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + for (uint8_t num = 2; num > 0; num--) + { + SuccessOrQuit(node0.Get().GetNextService(*iterator, service, entryState)); + Log("- - - - - - - - - - - - - - - - -"); + Log(" HostName: %s", service.mHostName); + Log(" ServiceInstance: %s", service.mServiceInstance); + Log(" ServiceType: %s", service.mServiceType); + Log(" Port: %u", service.mPort); + Log(" TTL: %lu", ToUlong(service.mTtl)); + + VerifyOrQuit(StringStartsWith(service.mServiceInstance, kDefaultServiceBaseName)); + VerifyOrQuit(StringStartsWith(service.mHostName, "ot")); + VerifyOrQuit(service.mSubTypeLabelsLength == 0); + VerifyOrQuit(service.mTtl > 0); + VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex); + VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED); + + if (StringMatch(service.mServiceType, "_meshcop._udp")) + { + VerifyOrQuit(service.mTxtDataLength > 1); + VerifyOrQuit(service.mPort == node0.Get().GetUdpPort()); + } + else if (StringMatch(service.mServiceType, "_meshcop-e._udp")) + { + VerifyOrQuit(service.mPort == kUdpPort); + VerifyOrQuit(service.mTxtDataLength == 1); + VerifyOrQuit(service.mTxtData[0] == 0); + } + else + { + // Unexpected service type + VerifyOrQuit(false); + } + } + + // Check that there is no more registered mDNS service + VerifyOrQuit(node0.Get().GetNextService(*iterator, service, entryState) == kErrorNotFound); + + node0.Get().FreeIterator(*iterator); + + Log("Wait for the ephemeral key to expire and validate the registered service is removed"); + + nexus.AdvanceTime(5 * Time::kOneMinuteInMsec); + + iterator = node0.Get().AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + SuccessOrQuit(node0.Get().GetNextService(*iterator, service, entryState)); + Log(" HostName: %s", service.mHostName); + Log(" ServiceInstance: %s", service.mServiceInstance); + Log(" ServiceType: %s", service.mServiceType); + Log(" Port: %u", service.mPort); + Log(" TTL: %lu", ToUlong(service.mTtl)); + + VerifyOrQuit(StringMatch(service.mServiceType, "_meshcop._udp")); + VerifyOrQuit(StringStartsWith(service.mServiceInstance, kDefaultServiceBaseName)); + VerifyOrQuit(StringStartsWith(service.mHostName, "ot")); + VerifyOrQuit(service.mSubTypeLabelsLength == 0); + VerifyOrQuit(service.mTxtDataLength > 1); + VerifyOrQuit(service.mPort == node0.Get().GetUdpPort()); + VerifyOrQuit(service.mTtl > 0); + VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex); + VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED); + + // Check that there is no more registered mDNS service + VerifyOrQuit(node0.Get().GetNextService(*iterator, service, entryState) == kErrorNotFound); + + node0.Get().FreeIterator(*iterator); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Change the base service name and validate the new service"); + + SuccessOrQuit(node0.Get().SetServiceBaseName("OpenThreadAgent")); + + nexus.AdvanceTime(30 * Time::kOneSecondInMsec); + + iterator = node0.Get().AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + SuccessOrQuit(node0.Get().GetNextService(*iterator, service, entryState)); + Log(" HostName: %s", service.mHostName); + Log(" ServiceInstance: %s", service.mServiceInstance); + Log(" ServiceType: %s", service.mServiceType); + Log(" Port: %u", service.mPort); + Log(" TTL: %lu", ToUlong(service.mTtl)); + + VerifyOrQuit(StringMatch(service.mServiceType, "_meshcop._udp")); + VerifyOrQuit(StringStartsWith(service.mServiceInstance, "OpenThreadAgent")); + VerifyOrQuit(StringStartsWith(service.mHostName, "ot")); + VerifyOrQuit(service.mSubTypeLabelsLength == 0); + VerifyOrQuit(service.mTxtDataLength > 1); + VerifyOrQuit(service.mPort == node0.Get().GetUdpPort()); + VerifyOrQuit(service.mTtl > 0); + VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex); + VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED); + + // Check that there is no more registered mDNS service + VerifyOrQuit(node0.Get().GetNextService(*iterator, service, entryState) == kErrorNotFound); + + node0.Get().FreeIterator(*iterator); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Disable Border Agent and validate that registered service is removed"); + + node0.Get().SetEnabled(false); + VerifyOrQuit(!node0.Get().IsEnabled()); + + nexus.AdvanceTime(30 * Time::kOneSecondInMsec); + + iterator = node0.Get().AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + VerifyOrQuit(node0.Get().GetNextService(*iterator, service, entryState) == kErrorNotFound); + + node0.Get().FreeIterator(*iterator); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Re-enable Border Agent and validate that service is registered again"); + + node0.Get().SetEnabled(true); + VerifyOrQuit(node0.Get().IsEnabled()); + + nexus.AdvanceTime(30 * Time::kOneSecondInMsec); + + iterator = node0.Get().AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + SuccessOrQuit(node0.Get().GetNextService(*iterator, service, entryState)); + Log(" HostName: %s", service.mHostName); + Log(" ServiceInstance: %s", service.mServiceInstance); + Log(" ServiceType: %s", service.mServiceType); + Log(" Port: %u", service.mPort); + Log(" TTL: %lu", ToUlong(service.mTtl)); + + VerifyOrQuit(StringMatch(service.mServiceType, "_meshcop._udp")); + VerifyOrQuit(StringStartsWith(service.mServiceInstance, "OpenThreadAgent")); + VerifyOrQuit(StringStartsWith(service.mHostName, "ot")); + VerifyOrQuit(service.mSubTypeLabelsLength == 0); + VerifyOrQuit(service.mTxtDataLength > 1); + VerifyOrQuit(service.mPort == node0.Get().GetUdpPort()); + VerifyOrQuit(service.mTtl > 0); + VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex); + VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED); + + // Check that there is no more registered mDNS service + VerifyOrQuit(node0.Get().GetNextService(*iterator, service, entryState) == kErrorNotFound); + + node0.Get().FreeIterator(*iterator); +} + } // namespace Nexus } // namespace ot @@ -995,6 +1232,7 @@ int main(void) ot::Nexus::TestBorderAgent(); ot::Nexus::TestBorderAgentEphemeralKey(); ot::Nexus::TestBorderAgentTxtDataCallback(); + ot::Nexus::TestBorderAgentServiceRegisteration(); printf("All tests passed\n"); return 0; } diff --git a/tests/unit/test_mdns.cpp b/tests/unit/test_mdns.cpp index 9b2fbf9dc..a21205270 100644 --- a/tests/unit/test_mdns.cpp +++ b/tests/unit/test_mdns.cpp @@ -1849,6 +1849,14 @@ Core *InitTest(void) VerifyOrQuit(sInstance != nullptr); +#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE + // Disable the Border Agent to prevent its attempt to + // register the `_meshcop._udp` service from + // interfering with this test. + + sInstance->Get().SetEnabled(false); +#endif + return &sInstance->Get(); } diff --git a/tests/unit/test_srp_adv_proxy.cpp b/tests/unit/test_srp_adv_proxy.cpp index dec83df65..cbd03cfe5 100644 --- a/tests/unit/test_srp_adv_proxy.cpp +++ b/tests/unit/test_srp_adv_proxy.cpp @@ -543,6 +543,13 @@ void InitTest(void) SuccessOrQuit(otIp6SetEnabled(sInstance, true)); SuccessOrQuit(otThreadSetEnabled(sInstance, true)); +#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE + // Disable the Border Agent to prevent its attempt to + // register the `_meshcop._udp` service from + // interfering with this test. + sInstance->Get().SetEnabled(false); +#endif + // Configure the `Dnssd` module to use `otPlatDnssd` APIs. sInstance->Get().SetUseNativeMdns(false);