[meshcop] add BorderAgentTracker to discover Border Agents (#11985)

Introduces a new `BorderAgentTracker` module to discover and track
Border Agents on the infrastructure link.

The tracker browses for the `_meshcop._udp` mDNS service and maintains
a list of discovered Border Agents. For each discovered service, it
resolves the port, host name, TXT record, and host addresses.

This change also adds new public otBorderAgentTracker APIs,
corresponding `batracker` CLI commands, and a new Nexus test case to
validate the behavior.
This commit is contained in:
Abtin Keshavarzian
2025-10-06 21:10:36 -07:00
committed by GitHub
parent 2596c9486b
commit 2af369e844
23 changed files with 1624 additions and 4 deletions
+1
View File
@@ -104,6 +104,7 @@
*
* @defgroup api-backbone-router Backbone Router
* @defgroup api-border-agent Border Agent
* @defgroup api-border-agent-tracker Border Agent Tracker
* @defgroup api-border-router Border Router
* @defgroup api-border-routing Border Routing Manager
* @defgroup api-commissioner Commissioner
+1
View File
@@ -177,6 +177,7 @@ 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_AGENT_TRACKER OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE "border agent tracker")
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")
+1
View File
@@ -43,6 +43,7 @@ source_set("openthread") {
"backbone_router_ftd.h",
"ble_secure.h",
"border_agent.h",
"border_agent_tracker.h",
"border_router.h",
"border_routing.h",
"channel_manager.h",
+162
View File
@@ -0,0 +1,162 @@
/*
* Copyright (c) 2025, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* @brief
* This file defines the OpenThread Border Agent Tracker APIs.
*/
#ifndef OPENTHREAD_BORDER_AGENT_TRACKER_H_
#define OPENTHREAD_BORDER_AGENT_TRACKER_H_
#include <openthread/error.h>
#include <openthread/instance.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @addtogroup api-border-agent-tracker
*
* @brief
* This module includes APIs for the Border Agent Tracker.
*
* The Border Agent Tracker discovers and tracks Border Agents on the infrastructure link by browsing for the
* `_meshcop._udp` mDNS service.
*
* @{
*
*/
/**
* Represents an iterator to iterate through the discovered Border Agents.
*
* The fields in this struct are for OpenThread internal use only and MUST NOT be accessed or modified by the caller.
*
* An iterator MUST be initialized using `otBorderAgentTrackerInitIterator()` before it is used.
*/
typedef struct otBorderAgentTrackerIterator
{
const void *mPtr;
uint64_t mData;
} otBorderAgentTrackerIterator;
/**
* Represents information about a discovered Border Agent.
*
* To ensure consistent `mMsecSinceDiscovered` and `mMsecSinceLastChange` time calculations, the iterator's
* initialization time is stored within the iterator when `otBorderAgentTrackerInitIterator()` is called. The time
* values in this struct are calculated relative to the iterator's initialization time.
*/
typedef struct otBorderAgentTrackerAgentInfo
{
const char *mServiceName; ///< The service name.
const char *mHostName; ///< The host name. May be NULL if not known yet.
uint16_t mPort; ///< The port number. Can be zero if not known yet.
const uint8_t *mTxtData; ///< The TXT data. May be NULL if not known yet.
uint16_t mTxtDataLength; ///< The TXT data length.
const otIp6Address *mAddresses; ///< Array of IPv6 addresses of the host. May be NULL if not known yet.
uint16_t mNumAddresses; ///< Number of addresses in the `mAddresses` array.
uint64_t mMsecSinceDiscovered; ///< Milliseconds since the service was discovered.
uint64_t mMsecSinceLastChange; ///< Milliseconds since the last change (port, TXT, or addresses).
} otBorderAgentTrackerAgentInfo;
/**
* Enables or disables the Border Agent Tracker.
*
* Requires `OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE`.
*
* When enabled, the tracker browses for the `_meshcop._udp` mDNS service to discover and track Border Agents on
* the infra-if network.
*
* @param[in] aInstance A pointer to an OpenThread instance.
* @param[in] aEnable TRUE to enable the Border Agent Tracker, FALSE to disable it.
*/
void otBorderAgentTrackerSetEnabled(otInstance *aInstance, bool aEnable);
/**
* Indicates whether the Border Agent Tracker is running.
*
* Requires `OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE`.
*
* The tracker can be enabled by the user (via `otBorderAgentTrackerSetEnabled()`) or by the OpenThread stack
* itself. The tracker is considered running if it is enabled by either entity AND the underlying DNS-SD (mDNS)
* is ready. This means that `otBorderAgentTrackerIsRunning()` may not return `TRUE` immediately after a call
* to `otBorderAgentTrackerSetEnabled(true)`.
*
* @param[in] aInstance A pointer to an OpenThread instance.
*
* @retval TRUE If the tracker is running.
* @retval FALSE If the tracker is not running.
*/
bool otBorderAgentTrackerIsRunning(otInstance *aInstance);
/**
* Initializes a Border Agent Tracker iterator.
*
* Requires `OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE`.
*
* An iterator MUST be initialized before being used.
*
* @param[in] aInstance A pointer to an OpenThread instance.
* @param[in] aIterator A pointer to the iterator to initialize.
*/
void otBorderAgentTrackerInitIterator(otInstance *aInstance, otBorderAgentTrackerIterator *aIterator);
/**
* Gets the information for the next discovered Border Agent.
*
* Requires `OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE`.
*
* The iterator initialization time is used to determine the `mMsecSinceDiscovered` and `mMsecSinceLastChange` in the
* `otBorderAgentTrackerAgentInfo`.
*
* @param[in] aInstance A pointer to an OpenThread instance.
* @param[in,out] aIterator A pointer to the iterator. An iterator MUST be initialized using
* `otBorderAgentTrackerInitIterator()` before it is used.
* @param[out] aAgentInfo A pointer to an `otBorderAgentTrackerAgentInfo` struct to populate.
*
* @retval OT_ERROR_NONE Successfully retrieved the information for the next agent.
* @retval OT_ERROR_NOT_FOUND No more agents were found.
*/
otError otBorderAgentTrackerGetNextAgent(otInstance *aInstance,
otBorderAgentTrackerIterator *aIterator,
otBorderAgentTrackerAgentInfo *aAgentInfo);
/**
* @}
*
*/
#ifdef __cplusplus
} // extern "C"
#endif
#endif // OPENTHREAD_BORDER_AGENT_TRACKER_H_
+1 -1
View File
@@ -52,7 +52,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (539)
#define OPENTHREAD_API_VERSION (540)
/**
* @addtogroup api-instance
+76
View File
@@ -23,6 +23,7 @@ Done
- [attachtime](#attachtime)
- [ba](#ba)
- [batracker](#batracker-enable)
- [bbr](#bbr)
- [br](README_BR.md)
- [bufferinfo](#bufferinfo)
@@ -609,6 +610,81 @@ mgmtPendingGet: 0
Done
```
### batracker enable
Enables Border Agent Tracker.
Requires `OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE`.
When enabled, the tracker browses for the `_meshcop._udp` mDNS service to discover and track Border Agents on the infra-if network.
```bash
> batracker enable
Done
```
### batracker disable
Disables Border Agent Tracker.
Requires `OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE`.
```
> batracker disable
Done
```
### batracker state
Requires `OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE`.
Shows the state of Border Agent Tracker, `running` or `inactive`.
The tracker can be enabled by the user (e.g., via `batracker enable`) or by the OpenThread stack itself. The tracker is considered running if it is enabled by either entity and the underlying DNS-SD (mDNS) is ready.
```bash
> batracker state
running
Done
```
### batracker agents
Requires `OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE`.
Outputs the list of discovered Border Agents. Information per Agent:
- Service name
- Port number
- Host name
- TXT data (key/value pairs per line)
- Host addresses
- Milliseconds since agent was first discovered
- Milliseconds since the last change to agent info (port, addresses, TXT data)
```bash
> batracker agents
ServiceName: OTBR-by-Google-be345eefb12f7f9c
Port: 49152
Host: otbe345eefb12f7f9c
TxtData:
id=4b21d3f4a431725048380698f3073a4b
rv=31
nn=4f70656e546872656164
xp=dead00beef00cafe
tv=312e342e30
xa=be345eefb12f7f9c
sb=00000820
dn=44656661756c74446f6d61696e
Address(es):
fe80:0:0:0:108f:3188:ff96:8e9f
fd7c:af54:fada:564d:7:fd6e:744c:e300
fd7c:af54:fada:564d:d9:899d:1217:9e2
MilliSecondsSinceDiscovered: 5237
MilliSecondsSinceLastChange: 5237
Done
```
### bufferinfo
Show the current message buffer information.
+137
View File
@@ -41,6 +41,7 @@
#include <openthread/backbone_router.h>
#include <openthread/backbone_router_ftd.h>
#include <openthread/border_agent_tracker.h>
#include <openthread/border_router.h>
#include <openthread/channel_manager.h>
#include <openthread/channel_monitor.h>
@@ -807,6 +808,139 @@ void Interpreter::HandleBorderAgentEphemeralKeyStateChange(void)
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
#if OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
template <> otError Interpreter::Process<Cmd("batracker")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
/**
* @cli batracker (enable, disable)
* @code
* batracker enable
* Done
* @endcode
* @code
* batracker disable
* Done
* @endcode
* @cparam batracker @ca{enable|disable}
* @par api_copy
* #otBorderAgentTrackerSetEnabled
*/
if (ProcessEnableDisable(aArgs, otBorderAgentTrackerSetEnabled) == OT_ERROR_NONE)
{
}
/**
* @cli batracker state
* @code
* batracker state
* running
* Done
* @endcode
* @par
* Shows the state of Border Agent Tracker, `running` or `inactive`.
*
* The tracker can be enabled by the user (e.g., via `batracker enable`) or by the OpenThread stack itself. The
* tracker is considered running if it is enabled by either entity and the underlying DNS-SD (mDNS) is ready.
*/
else if (aArgs[0] == "state")
{
OutputLine("%s", otBorderAgentTrackerIsRunning(GetInstancePtr()) ? "running" : "inactive");
}
/**
* @cli batracker agents
* @code
* batracker agents
* ServiceName: OTBR-by-Google-be345eefb12f7f9c
* Port: 49152
* Host: otbe345eefb12f7f9c
* TxtData:
* id=4b21d3f4a431725048380698f3073a4b
* rv=31
* nn=4f70656e546872656164
* xp=dead00beef00cafe
* tv=312e342e30
* xa=be345eefb12f7f9c
* sb=00000820
* dn=44656661756c74446f6d61696e
* Address(es):
* fe80:0:0:0:108f:3188:ff96:8e9f
* fd7c:af54:fada:564d:7:fd6e:744c:e300
* fd7c:af54:fada:564d:d9:899d:1217:9e2
* MilliSecondsSinceDiscovered: 5237
* MilliSecondsSinceLastChange: 5237
* Done
* @endcode
* @par
* Outputs the list of discovered border agents. Information per agent:
* - Service name
* - Port number
* - Host name
* - TXT data (key/value pairs per line)
* - Host addresses
* - Milliseconds since agent was first discovered
* - Milliseconds since the last change to agent info (port, addresses, TXT data)
*/
else if (aArgs[0] == "agents")
{
otBorderAgentTrackerIterator iterator;
otBorderAgentTrackerAgentInfo agent;
otBorderAgentTrackerInitIterator(GetInstancePtr(), &iterator);
while (otBorderAgentTrackerGetNextAgent(GetInstancePtr(), &iterator, &agent) == OT_ERROR_NONE)
{
OutputLine("ServiceName: %s", agent.mServiceName);
OutputLine(kIndentSize, "Port: %u", agent.mPort);
OutputLine(kIndentSize, "Host: %s", agent.mHostName != nullptr ? agent.mHostName : "(null)");
OutputFormat(kIndentSize, "TxtData:");
if (agent.mTxtData != nullptr)
{
OutputNewLine();
OutputDnsTxtData(kIndentSize * 2, agent.mTxtData, agent.mTxtDataLength);
}
else
{
OutputLine(" (null)");
}
OutputFormat(kIndentSize, "Address(es):");
if (agent.mAddresses != nullptr)
{
OutputNewLine();
for (uint16_t i = 0; i < agent.mNumAddresses; i++)
{
OutputSpaces(kIndentSize * 2);
OutputIp6AddressLine(agent.mAddresses[i]);
}
}
else
{
OutputLine(" (null)");
}
OutputFormat(kIndentSize, "MilliSecondsSinceDiscovered: ");
OutputUint64Line(agent.mMsecSinceDiscovered);
OutputFormat(kIndentSize, "MilliSecondsSinceLastChange: ");
OutputUint64Line(agent.mMsecSinceLastChange);
}
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
return error;
}
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
template <> otError Interpreter::Process<Cmd("br")>(Arg aArgs[]) { return mBr.Process(aArgs); }
#endif
@@ -8433,6 +8567,9 @@ otError Interpreter::ProcessCommand(Arg aArgs[])
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
CmdEntry("ba"),
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
CmdEntry("batracker"),
#endif
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
CmdEntry("bbr"),
#endif
+31 -3
View File
@@ -218,6 +218,19 @@ void Utils::OutputSockAddrLine(const otSockAddr &aSockAddr)
}
void Utils::OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength)
{
OutputDnsTxtData(/* aKeyValuePerLine */ false, 0, aTxtData, aTxtDataLength);
}
void Utils::OutputDnsTxtData(uint8_t aIndentSize, const uint8_t *aTxtData, uint16_t aTxtDataLength)
{
OutputDnsTxtData(/* aKeyValuePerLine */ true, aIndentSize, aTxtData, aTxtDataLength);
}
void Utils::OutputDnsTxtData(bool aKeyValuePerLine,
uint8_t aIndentSize,
const uint8_t *aTxtData,
uint16_t aTxtDataLength)
{
otDnsTxtEntry entry;
otDnsTxtEntryIterator iterator;
@@ -225,11 +238,18 @@ void Utils::OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength)
otDnsInitTxtEntryIterator(&iterator, aTxtData, aTxtDataLength);
OutputFormat("[");
if (!aKeyValuePerLine)
{
OutputFormat("[");
}
while (otDnsGetNextTxtEntry(&iterator, &entry) == OT_ERROR_NONE)
{
if (!isFirst)
if (aKeyValuePerLine)
{
OutputSpaces(aIndentSize);
}
else if (!isFirst)
{
OutputFormat(", ");
}
@@ -257,9 +277,17 @@ void Utils::OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength)
}
isFirst = false;
if (aKeyValuePerLine)
{
OutputNewLine();
}
}
OutputFormat("]");
if (!aKeyValuePerLine)
{
OutputFormat("]");
}
}
const char *Utils::PercentageToString(uint16_t aValue, PercentageStringBuffer &aBuffer)
+16
View File
@@ -403,11 +403,24 @@ public:
/**
* Outputs DNS TXT data to the CLI console.
*
* All key-value pairs are output on a single line in the format: "[key1=value1, key2=value2, ...]".
*
* @param[in] aTxtData A pointer to a buffer containing the DNS TXT data.
* @param[in] aTxtDataLength The length of @p aTxtData (in bytes).
*/
void OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength);
/**
* Outputs DNS TXT data to the CLI console, one key per line and applying an indentation.
*
* Each key-value pair is output on its own line, preceded by the specified indentation.
*
* @param[in] aIndentSize The number of space characters to prepend as indentation to each output line.
* @param[in] aTxtData A pointer to a buffer containing the DNS TXT data.
* @param[in] aTxtDataLength The length of @p aTxtData (in bytes).
*/
void OutputDnsTxtData(uint8_t aIndentSize, const uint8_t *aTxtData, uint16_t aTxtDataLength);
/**
* Represents a buffer which is used when converting an encoded rate value to percentage string.
*/
@@ -719,6 +732,9 @@ private:
void OutputTableHeader(uint8_t aNumColumns, const char *const aTitles[], const uint8_t aWidths[]);
void OutputTableSeparator(uint8_t aNumColumns, const uint8_t aWidths[]);
#if OPENTHREAD_FTD || OPENTHREAD_MTD
void OutputDnsTxtData(bool aKeyValuePerLine, uint8_t aIndentSize, const uint8_t *aTxtData, uint16_t aTxtDataLength);
#endif
otInstance *mInstance;
OutputImplementer &mImplementer;
+3
View File
@@ -309,6 +309,7 @@ openthread_core_files = [
"api/backbone_router_ftd_api.cpp",
"api/ble_secure_api.cpp",
"api/border_agent_api.cpp",
"api/border_agent_tracker_api.cpp",
"api/border_router_api.cpp",
"api/border_routing_api.cpp",
"api/channel_manager_api.cpp",
@@ -523,6 +524,8 @@ openthread_core_files = [
"meshcop/announce_begin_client.hpp",
"meshcop/border_agent.cpp",
"meshcop/border_agent.hpp",
"meshcop/border_agent_tracker.cpp",
"meshcop/border_agent_tracker.hpp",
"meshcop/commissioner.cpp",
"meshcop/commissioner.hpp",
"meshcop/dataset.cpp",
+2
View File
@@ -35,6 +35,7 @@ set(COMMON_SOURCES
api/backbone_router_ftd_api.cpp
api/ble_secure_api.cpp
api/border_agent_api.cpp
api/border_agent_tracker_api.cpp
api/border_router_api.cpp
api/border_routing_api.cpp
api/channel_manager_api.cpp
@@ -156,6 +157,7 @@ set(COMMON_SOURCES
mac/wakeup_tx_scheduler.cpp
meshcop/announce_begin_client.cpp
meshcop/border_agent.cpp
meshcop/border_agent_tracker.cpp
meshcop/commissioner.cpp
meshcop/dataset.cpp
meshcop/dataset_manager.cpp
+66
View File
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2025, 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_CONFIG_BORDER_AGENT_TRACKER_ENABLE
#include <openthread/border_agent_tracker.h>
#include "common/as_core_type.hpp"
#include "instance/instance.hpp"
using namespace ot;
void otBorderAgentTrackerSetEnabled(otInstance *aInstance, bool aEnable)
{
AsCoreType(aInstance).Get<MeshCoP::BorderAgentTracker>().SetEnabled(aEnable,
MeshCoP::BorderAgentTracker::kRequesterUser);
}
bool otBorderAgentTrackerIsRunning(otInstance *aInstance)
{
return AsCoreType(aInstance).Get<MeshCoP::BorderAgentTracker>().IsRunning();
}
void otBorderAgentTrackerInitIterator(otInstance *aInstance, otBorderAgentTrackerIterator *aIterator)
{
AsCoreType(aIterator).Init(AsCoreType(aInstance));
}
otError otBorderAgentTrackerGetNextAgent(otInstance *aInstance,
otBorderAgentTrackerIterator *aIterator,
otBorderAgentTrackerAgentInfo *aAgentInfo)
{
OT_UNUSED_VARIABLE(aInstance);
AssertPointerIsNotNull(aAgentInfo);
return AsCoreType(aIterator).GetNextAgentInfo(*aAgentInfo);
}
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
+10
View File
@@ -172,6 +172,16 @@ public:
*/
bool operator==(const String &aString) const { return (*this == aString.AsCString()); }
/**
* Overloads operator `!=` to evaluate whether or not two `String` are not equal.
*
* @param[in] aString The other string to compare with.
*
* @retval TRUE If the two strings are not equal.
* @retval FALSE If the two strings are equal.
*/
bool operator!=(const String &aString) const { return (*this != aString.AsCString()); }
String(const String &) = delete;
String &operator=(const String &) = delete;
+12
View File
@@ -122,6 +122,18 @@
#define OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME "OpenThread BR (unspecified vendor) "
#endif
/**
* @def OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
*
* Define to 1 to enable the Border Agent Tracker feature.
*
* The Border Agent Tracker feature discovers and tracks Border Agents on the infrastructure network. This feature
* requires either `OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE` or `OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE` to be enabled.
*/
#ifndef OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
#define OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE 0
#endif
/**
* @}
*/
+3
View File
@@ -174,6 +174,9 @@ Instance::Instance(void)
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
, mBorderAgent(*this)
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
, mBorderAgentTracker(*this)
#endif
#if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD
, mCommissioner(*this)
#endif
+9
View File
@@ -84,6 +84,7 @@
#include "mac/mac.hpp"
#include "mac/wakeup_tx_scheduler.hpp"
#include "meshcop/border_agent.hpp"
#include "meshcop/border_agent_tracker.hpp"
#include "meshcop/commissioner.hpp"
#include "meshcop/dataset_manager.hpp"
#include "meshcop/dataset_updater.hpp"
@@ -589,6 +590,10 @@ private:
MeshCoP::BorderAgent mBorderAgent;
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
MeshCoP::BorderAgentTracker mBorderAgentTracker;
#endif
#if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD
MeshCoP::Commissioner mCommissioner;
#endif
@@ -1024,6 +1029,10 @@ template <> inline MeshCoP::DatasetUpdater &Instance::Get(void) { return mDatase
template <> inline MeshCoP::BorderAgent &Instance::Get(void) { return mBorderAgent; }
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
template <> inline MeshCoP::BorderAgentTracker &Instance::Get(void) { return mBorderAgentTracker; }
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
template <> inline MeshCoP::BorderAgent::EphemeralKeyManager &Instance::Get(void)
{
+569
View File
@@ -0,0 +1,569 @@
/*
* Copyright (c) 2025, 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 Border Agent Tracker.
*/
#include "border_agent_tracker.hpp"
#if OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
#include "instance/instance.hpp"
namespace ot {
namespace MeshCoP {
RegisterLogModule("BaTracker");
//---------------------------------------------------------------------------------------------------------------------
// BorderAgentTracker
const char BorderAgentTracker::kServiceType[] = "_meshcop._udp";
BorderAgentTracker::BorderAgentTracker(Instance &aInstance)
: InstanceLocator(aInstance)
, mState(kStateStopped)
, mUserEnabled(false)
, mStackEnabled(false)
{
}
void BorderAgentTracker::SetEnabled(bool aEnable, Requester aRequester)
{
switch (aRequester)
{
case kRequesterUser:
mUserEnabled = aEnable;
break;
case kRequesterStack:
mStackEnabled = aEnable;
break;
}
UpdateState();
}
void BorderAgentTracker::HandleDnssdPlatformStateChange(void) { UpdateState(); }
void BorderAgentTracker::UpdateState(void)
{
State newState;
if (mUserEnabled || mStackEnabled)
{
newState = Get<Dnssd>().IsReady() ? kStateRunning : kStatePendingDnssd;
}
else
{
newState = kStateStopped;
}
VerifyOrExit(newState != mState);
if (mState == kStateRunning)
{
Get<Dnssd>().StopBrowser(Browser());
mAgents.Free();
}
LogInfo("State: %s -> %s", StateToString(mState), StateToString(newState));
mState = newState;
// It is important to start browser after `mState` is updated.
// This ensures that if the `HandleBrowseResult()` callback is
// invoked immediately from within the `StartBrowser()` method
// call, the state is valid.
if (newState == kStateRunning)
{
Get<Dnssd>().StartBrowser(Browser());
}
exit:
return;
}
void BorderAgentTracker::HandleBrowseResult(otInstance *aInstance, const otPlatDnssdBrowseResult *aResult)
{
AsCoreType(aInstance).Get<BorderAgentTracker>().HandleBrowseResult(*aResult);
}
void BorderAgentTracker::HandleBrowseResult(const Dnssd::BrowseResult &aResult)
{
Error error = kErrorNone;
Agent *newAgent;
VerifyOrExit(IsRunning());
VerifyOrExit(aResult.mServiceInstance != nullptr);
if (aResult.mTtl == 0)
{
mAgents.RemoveMatching(kMatchServiceName, aResult.mServiceInstance);
ExitNow();
}
VerifyOrExit(!mAgents.ContainsMatching(kMatchServiceName, aResult.mServiceInstance));
LogInfo("Discovered agent %s", aResult.mServiceInstance);
newAgent = Agent::Allocate(GetInstance());
VerifyOrExit(newAgent != nullptr, error = kErrorNoBufs);
// We add the new agent to the list first before setting service
// name and starting the SRV and TXT resolvers. This ensures that
// if the `HandleSrvResult()` or `HandleTxtResult()` callbacks
// are invoked immediately from within the method call that start
// the resolvers, the agent entry can be correctly found in the
// list.
mAgents.Push(*newAgent);
if (newAgent->SetServiceNameAndStartSrvTxtResolvers(aResult.mServiceInstance) != kErrorNone)
{
// `Pop()` returns a temporary `OwnedPtr<Agent>` which is
// immediately destroyed, freeing the allocated agent.
mAgents.Pop();
error = kErrorNoBufs;
}
exit:
LogOnError(error, "add new agent", aResult.mServiceInstance);
}
void BorderAgentTracker::HandleSrvResult(otInstance *aInstance, const otPlatDnssdSrvResult *aResult)
{
AsCoreType(aInstance).Get<BorderAgentTracker>().HandleSrvResult(*aResult);
}
void BorderAgentTracker::HandleSrvResult(const Dnssd::SrvResult &aResult)
{
Agent *agent;
VerifyOrExit(IsRunning());
agent = mAgents.FindMatching(kMatchServiceName, aResult.mServiceInstance);
VerifyOrExit(agent != nullptr);
if (aResult.mTtl == 0)
{
agent->SetPort(0);
agent->ClearHost();
ExitNow();
}
agent->SetPort(aResult.mPort);
VerifyOrExit(aResult.mHostName != nullptr);
agent->SetHost(aResult.mHostName);
exit:
return;
}
void BorderAgentTracker::HandleTxtResult(otInstance *aInstance, const otPlatDnssdTxtResult *aResult)
{
AsCoreType(aInstance).Get<BorderAgentTracker>().HandleTxtResult(*aResult);
}
void BorderAgentTracker::HandleTxtResult(const Dnssd::TxtResult &aResult)
{
Agent *agent;
VerifyOrExit(IsRunning());
agent = mAgents.FindMatching(kMatchServiceName, aResult.mServiceInstance);
VerifyOrExit(agent != nullptr);
if ((aResult.mTtl == 0) || (aResult.mTxtData == nullptr))
{
agent->ClearTxtData();
}
else
{
agent->SetTxtData(aResult.mTxtData, aResult.mTxtDataLength);
}
exit:
return;
}
void BorderAgentTracker::HandleAddressResult(otInstance *aInstance, const otPlatDnssdAddressResult *aResult)
{
AsCoreType(aInstance).Get<BorderAgentTracker>().HandleAddressResult(*aResult);
}
void BorderAgentTracker::HandleAddressResult(const Dnssd::AddressResult &aResult)
{
Agent *agent;
VerifyOrExit(IsRunning());
agent = mAgents.FindMatching(kMatchHostName, aResult.mHostName);
VerifyOrExit(agent != nullptr);
agent->mHost->SetAddresses(aResult);
exit:
return;
}
bool BorderAgentTracker::NameMatch(const Heap::String &aHeapString, const char *aName)
{
return !aHeapString.IsNull() && StringMatch(aHeapString.AsCString(), aName, kStringCaseInsensitiveMatch);
}
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_WARN)
void BorderAgentTracker::LogOnError(Error aError, const char *aText, const char *aName)
{
if (aError != kErrorNone)
{
LogWarn("Error %s - Failed to %s - %s", ErrorToString(aError), aText, (aName != nullptr) ? aName : "");
}
}
#else
void BorderAgentTracker::LogOnError(Error, const char *, const char *) {}
#endif
const char *BorderAgentTracker::StateToString(State aState)
{
static const char *const kStateStrings[] = {
"Stopped",
"PendingDnssd",
"Running",
};
struct EnumCheck
{
InitEnumValidatorCounter();
ValidateNextEnum(kStateStopped);
ValidateNextEnum(kStatePendingDnssd);
ValidateNextEnum(kStateRunning);
};
return kStateStrings[aState];
}
//---------------------------------------------------------------------------------------------------------------------
// BorderAgentTracker::Iterator
void BorderAgentTracker::Iterator::Init(Instance &aInstance)
{
SetAgentEntry(aInstance.Get<BorderAgentTracker>().mAgents.GetHead());
SetInitUptime(aInstance.Get<Uptime>().GetUptime());
}
Error BorderAgentTracker::Iterator::GetNextAgentInfo(AgentInfo &aInfo)
{
Error error = kErrorNone;
const Agent *agent = GetAgentEntry();
VerifyOrExit(agent != nullptr, error = kErrorNotFound);
agent->CopyInfoTo(aInfo, GetInitUptime());
SetAgentEntry(agent->GetNext());
exit:
return error;
}
//---------------------------------------------------------------------------------------------------------------------
// BorderAgentTracker::Host
BorderAgentTracker::Host::Host(Instance &aInstance)
: InstanceLocator(aInstance)
{
}
BorderAgentTracker::Host::~Host(void)
{
VerifyOrExit(mName != nullptr);
Get<Dnssd>().StopIp6AddressResolver(AddressResolver(mName.AsCString()));
exit:
return;
}
Error BorderAgentTracker::Host::SetNameAndStartAddrResolver(const char *aHostName)
{
Error error;
SuccessOrExit(error = mName.Set(aHostName));
Get<Dnssd>().StartIp6AddressResolver(AddressResolver(mName.AsCString()));
exit:
LogOnError(error, "set host name", aHostName);
return error;
}
void BorderAgentTracker::Host::SetAddresses(const Dnssd::AddressResult &aResult)
{
Error error = kErrorNone;
uint16_t length;
const Dnssd::AddressAndTtl *addrAndTtl;
mAddresses.Free();
length = aResult.mAddressesLength;
SuccessOrExit(error = mAddresses.ReserveCapacity(length));
for (addrAndTtl = aResult.mAddresses; length > 0; length--, addrAndTtl++)
{
const Ip6::Address &addr = AsCoreType(&addrAndTtl->mAddress);
if (addrAndTtl->mTtl == 0)
{
continue;
}
if (!mAddresses.Contains(addr))
{
SuccessOrAssert(mAddresses.PushBack(addr));
}
}
exit:
LogOnError(error, "set host addresses", mName.AsCString());
}
//---------------------------------------------------------------------------------------------------------------------
// BorderAgentTracker::Agent
BorderAgentTracker::Agent::Agent(Instance &aInstance)
: InstanceLocator(aInstance)
, mNext(nullptr)
, mDiscoverUptime(aInstance.Get<Uptime>().GetUptime())
, mLastUpdateUptime(mDiscoverUptime)
, mPort(0)
{
}
BorderAgentTracker::Agent::~Agent(void)
{
VerifyOrExit(mServiceName != nullptr);
Get<Dnssd>().StopSrvResolver(SrvResolver(mServiceName.AsCString()));
Get<Dnssd>().StopTxtResolver(TxtResolver(mServiceName.AsCString()));
exit:
return;
}
Error BorderAgentTracker::Agent::SetServiceNameAndStartSrvTxtResolvers(const char *aServiceName)
{
Error error;
SuccessOrExit(error = mServiceName.Set(aServiceName));
SetUpdateTimeToNow();
Get<Dnssd>().StartSrvResolver(SrvResolver(mServiceName.AsCString()));
Get<Dnssd>().StartTxtResolver(TxtResolver(mServiceName.AsCString()));
exit:
return error;
}
void BorderAgentTracker::Agent::SetHost(const char *aHostName)
{
Agent *matchingHostAgent;
if (mHost != nullptr)
{
VerifyOrExit(!NameMatch(mHost->mName, aHostName));
}
SetUpdateTimeToNow();
// We handle the case where multiple meshcop services are
// advertised from the same host. While this is unlikely in
// actual deployments, it can be useful for testing. To minimize
// resource usage (memory and mDNS queries), we check if another
// `Agent` is already tracking the same host. If so, we share its
// `Host` entry. Otherwise, we allocate a new one. Note that
// `mHost` is defined as `RetainPtr` which does ref-counting.
matchingHostAgent = Get<BorderAgentTracker>().mAgents.FindMatching(kMatchHostName, aHostName);
if (matchingHostAgent != nullptr)
{
mHost = matchingHostAgent->mHost;
}
else
{
mHost.Reset(Host::Allocate(GetInstance()));
VerifyOrExit(mHost != nullptr);
if (mHost->SetNameAndStartAddrResolver(aHostName) != kErrorNone)
{
ClearHost();
}
}
exit:
return;
}
void BorderAgentTracker::Agent::ClearHost(void)
{
VerifyOrExit(mHost != nullptr);
mHost.Reset();
SetUpdateTimeToNow();
exit:
return;
}
void BorderAgentTracker::Agent::SetPort(uint16_t aPort)
{
VerifyOrExit(mPort != aPort);
SetUpdateTimeToNow();
mPort = aPort;
exit:
return;
}
void BorderAgentTracker::Agent::SetTxtData(const uint8_t *aData, uint16_t aDataLength)
{
Error error = kErrorNone;
VerifyOrExit(!mTxtData.Matches(aData, aDataLength));
SuccessOrExit(error = mTxtData.SetFrom(aData, aDataLength));
SetUpdateTimeToNow();
exit:
LogOnError(error, "set TXT data", mServiceName.AsCString());
}
void BorderAgentTracker::Agent::ClearTxtData(void)
{
VerifyOrExit(!mTxtData.IsNull());
SetUpdateTimeToNow();
mTxtData.Free();
exit:
return;
}
void BorderAgentTracker::Agent::SetUpdateTimeToNow(void) { mLastUpdateUptime = Get<Uptime>().GetUptime(); }
bool BorderAgentTracker::Agent::Matches(MatchType aType, const char *aName) const
{
bool matches = false;
switch (aType)
{
case kMatchServiceName:
matches = NameMatch(mServiceName, aName);
break;
case kMatchHostName:
VerifyOrExit(mHost != nullptr);
matches = NameMatch(mHost->mName, aName);
break;
}
exit:
return matches;
}
void BorderAgentTracker::Agent::CopyInfoTo(AgentInfo &aInfo, uint64_t aUptimeNow) const
{
ClearAllBytes(aInfo);
aInfo.mServiceName = mServiceName.AsCString();
aInfo.mPort = mPort;
aInfo.mTxtData = mTxtData.GetBytes();
aInfo.mTxtDataLength = mTxtData.GetLength();
aInfo.mMsecSinceDiscovered = aUptimeNow - mDiscoverUptime;
aInfo.mMsecSinceLastChange = aUptimeNow - mLastUpdateUptime;
if (mHost != nullptr)
{
aInfo.mHostName = mHost->mName.AsCString();
aInfo.mAddresses = mHost->mAddresses.AsCArray();
aInfo.mNumAddresses = mHost->mAddresses.GetLength();
}
}
//----------------------------------------------------------------------------------------------------------------------
// BorderAgentTracker::Browser
BorderAgentTracker::Browser::Browser(void)
{
Clear();
mServiceType = kServiceType;
mCallback = BorderAgentTracker::HandleBrowseResult;
}
//----------------------------------------------------------------------------------------------------------------------
// BorderAgentTracker::SrvResolver
BorderAgentTracker::SrvResolver::SrvResolver(const char *aServiceName)
{
Clear();
mServiceInstance = aServiceName;
mServiceType = kServiceType;
mCallback = BorderAgentTracker::HandleSrvResult;
}
//----------------------------------------------------------------------------------------------------------------------
// BorderAgentTracker::TxtResolver
BorderAgentTracker::TxtResolver::TxtResolver(const char *aServiceName)
{
Clear();
mServiceInstance = aServiceName;
mServiceType = kServiceType;
mCallback = BorderAgentTracker::HandleTxtResult;
}
//----------------------------------------------------------------------------------------------------------------------
// BorderAgentTracker::AddressResolver
BorderAgentTracker::AddressResolver::AddressResolver(const char *aHostName)
{
Clear();
mHostName = aHostName;
mCallback = BorderAgentTracker::HandleAddressResult;
}
} // namespace MeshCoP
} // namespace ot
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
+245
View File
@@ -0,0 +1,245 @@
/*
* Copyright (c) 2025, 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 Border Agent Tracker.
*/
#ifndef BORDER_AGENT_TRACKER_HPP_
#define BORDER_AGENT_TRACKER_HPP_
#include "openthread-core-config.h"
#if OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
#if !OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE && !OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE
#error "BORDER_AGENT_TRACKER_ENABLE requires either the native mDNS or platform DNS-SD APIs"
#endif
#include <openthread/border_agent_tracker.h>
#include "common/as_core_type.hpp"
#include "common/heap_allocatable.hpp"
#include "common/heap_array.hpp"
#include "common/heap_data.hpp"
#include "common/heap_string.hpp"
#include "common/locator.hpp"
#include "common/owning_list.hpp"
#include "common/retain_ptr.hpp"
#include "net/dnssd.hpp"
#include "net/ip6_address.hpp"
namespace ot {
namespace MeshCoP {
/**
* Implements the Border Agent Tracker.
*/
class BorderAgentTracker : public InstanceLocator
{
friend class ot::Dnssd;
struct Agent;
public:
typedef otBorderAgentTrackerAgentInfo AgentInfo; ///< Information about a discovered Border Agent.
/**
* Represents an entity requesting to start or stop.
*/
enum Requester : uint8_t
{
kRequesterUser, ///< Requested by user (public OT API).
kRequesterStack, ///< Requested by stack itself (other OT modules).
};
class Iterator : public otBorderAgentTrackerIterator
{
public:
/**
* Initializes the iterator.
*
* An iterator MUST be initialized before being used.
*
* @param[in] aInstance The OpenThread instance.
*/
void Init(Instance &aInstance);
/**
* Gets the information for the next discovered Border Agent.
*
* @param[out] aInfo A reference to an `AgentInfo` to populate with the agent's information.
*
* @retval kErrorNone Successfully retrieved the information for the next agent.
* @retval kErrorNotFound No more agents were found.
*/
Error GetNextAgentInfo(AgentInfo &aInfo);
private:
const Agent *GetAgentEntry(void) const { return static_cast<const Agent *>(mPtr); }
void SetAgentEntry(const Agent *aEntry) { mPtr = aEntry; }
uint64_t GetInitUptime(void) const { return mData; }
void SetInitUptime(uint64_t aUptime) { mData = aUptime; }
};
/**
* Initializes the Border Agent Tracker.
*
* @param[in] aInstance The OpenThread instance.
*/
explicit BorderAgentTracker(Instance &aInstance);
/**
* Enables or disables the Border Agent Tracker.
*
* When enabled, the tracker browses for the `_meshcop._udp` mDNS service to discover and track Border Agents on
* the infra-if network.
*
* The Border Agent Tracker can be enabled by multiple requesters (see `Requester`). It remains enabled as long as
* at least one requester has it enabled. It is disabled only when all requesters have disabled it.
*
* @param[in] aEnable A boolean to enable/disable the Border Agent Tracker.
* @param[in] aRequester The entity requesting to enable/disable.
*/
void SetEnabled(bool aEnable, Requester aRequester);
/**
* Indicates whether the tracker is running.
*
* The tracker is running if at least one requester (see `Requester`) has enabled it and the underlying DNS-SD
* platform is ready.
*
* @retval TRUE If the tracker is running.
* @retval FALSE If the tracker is not running.
*/
bool IsRunning(void) const { return mState == kStateRunning; }
private:
enum State : uint8_t
{
kStateStopped,
kStatePendingDnssd,
kStateRunning,
};
enum MatchType : uint8_t
{
kMatchServiceName,
kMatchHostName,
};
struct Host : public InstanceLocator, public RetainCountable, public Heap::Allocatable<Host>
{
explicit Host(Instance &aInstance);
~Host(void);
Error SetNameAndStartAddrResolver(const char *aHostName);
void SetAddresses(const Dnssd::AddressResult &aResult);
Heap::String mName;
Heap::Array<Ip6::Address> mAddresses;
};
struct Agent : public InstanceLocator, public Heap::Allocatable<Agent>, public LinkedListEntry<Agent>
{
explicit Agent(Instance &aInstance);
~Agent(void);
Error SetServiceNameAndStartSrvTxtResolvers(const char *aServiceName);
void SetHost(const char *aHostName);
void ClearHost(void);
void SetPort(uint16_t aPort);
void SetTxtData(const uint8_t *aData, uint16_t aDataLength);
void ClearTxtData(void);
void SetUpdateTimeToNow(void);
bool Matches(MatchType aType, const char *aName) const;
void CopyInfoTo(AgentInfo &aInfo, uint64_t aUptimeNow) const;
Agent *mNext;
Heap::String mServiceName;
RetainPtr<Host> mHost;
Heap::Data mTxtData;
uint64_t mDiscoverUptime;
uint64_t mLastUpdateUptime;
uint16_t mPort;
};
struct Browser : public Dnssd::Browser
{
Browser(void);
};
struct SrvResolver : public Dnssd::SrvResolver
{
explicit SrvResolver(const char *aServiceName);
};
struct TxtResolver : public Dnssd::TxtResolver
{
explicit TxtResolver(const char *aServiceName);
};
struct AddressResolver : public Dnssd::AddressResolver
{
explicit AddressResolver(const char *aHostName);
};
void UpdateState(void);
void HandleDnssdPlatformStateChange(void);
void HandleBrowseResult(const Dnssd::BrowseResult &aResult);
void HandleSrvResult(const Dnssd::SrvResult &aResult);
void HandleTxtResult(const Dnssd::TxtResult &aResult);
void HandleAddressResult(const Dnssd::AddressResult &aResult);
static void HandleBrowseResult(otInstance *aInstance, const otPlatDnssdBrowseResult *aResult);
static void HandleSrvResult(otInstance *aInstance, const otPlatDnssdSrvResult *aResult);
static void HandleTxtResult(otInstance *aInstance, const otPlatDnssdTxtResult *aResult);
static void HandleAddressResult(otInstance *aInstance, const otPlatDnssdAddressResult *aResult);
static bool NameMatch(const Heap::String &aHeapString, const char *aName);
static void LogOnError(Error aError, const char *aText, const char *aName);
static const char *StateToString(State aState);
static const char kServiceType[];
State mState;
bool mUserEnabled : 1;
bool mStackEnabled : 1;
OwningList<Agent> mAgents;
};
} // namespace MeshCoP
DefineCoreType(otBorderAgentTrackerIterator, MeshCoP::BorderAgentTracker::Iterator);
} // namespace ot
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
#endif // BORDER_AGENT_TRACKER_HPP_
+4
View File
@@ -531,6 +531,10 @@ void Dnssd::HandleStateChange(void)
Get<MeshCoP::BorderAgent>().HandleDnssdPlatformStateChange();
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
Get<MeshCoP::BorderAgentTracker>().HandleDnssdPlatformStateChange();
#endif
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE && OPENTHREAD_CONFIG_TREL_MANAGE_DNSSD_ENABLE
Get<Trel::PeerDiscoverer>().HandleDnssdPlatformStateChange();
#endif
+1
View File
@@ -112,6 +112,7 @@ endmacro()
#----------------------------------------------------------------------------------------------------------------------
ot_nexus_test(border_agent)
ot_nexus_test(border_agent_tracker)
ot_nexus_test(dtls)
ot_nexus_test(form_join)
ot_nexus_test(full_network_reset)
@@ -46,6 +46,7 @@
#define OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_CLIENT_ENABLE 0
+271
View File
@@ -0,0 +1,271 @@
/*
* Copyright (c) 2025, 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 <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "platform/nexus_core.hpp"
#include "platform/nexus_node.hpp"
namespace ot {
namespace Nexus {
void TestBorderAgentTracker(void)
{
static constexpr uint32_t kInfraIfIndex = 1;
Core nexus;
Node &node0 = nexus.CreateNode();
Node &node1 = nexus.CreateNode();
Node &node2 = nexus.CreateNode();
Node &node3 = nexus.CreateNode();
MeshCoP::BorderAgentTracker::Iterator iterator;
MeshCoP::BorderAgentTracker::AgentInfo agent;
Dns::Multicast::Core::Service service;
uint16_t count;
bool found;
Log("------------------------------------------------------------------------------------------------------");
Log("TestBorderAgentTracker");
SuccessOrQuit(node0.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
SuccessOrQuit(node1.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
SuccessOrQuit(node2.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
SuccessOrQuit(node3.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
node0.Form();
nexus.AdvanceTime(13 * 1000);
VerifyOrQuit(node0.Get<Mle::Mle>().IsLeader());
node1.Join(node0);
node2.Join(node0);
node3.Form();
nexus.AdvanceTime(600 * 1000);
VerifyOrQuit(node1.Get<Mle::Mle>().IsRouter());
VerifyOrQuit(node2.Get<Mle::Mle>().IsRouter());
VerifyOrQuit(node3.Get<Mle::Mle>().IsLeader());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check Border Agent Tracker's initial state");
VerifyOrQuit(!node0.Get<MeshCoP::BorderAgentTracker>().IsRunning());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable Border Agent Tracker");
node0.Get<MeshCoP::BorderAgentTracker>().SetEnabled(true, MeshCoP::BorderAgentTracker::kRequesterUser);
nexus.AdvanceTime(10);
VerifyOrQuit(node0.Get<MeshCoP::BorderAgentTracker>().IsRunning());
nexus.AdvanceTime(5000);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check the tracked agents");
iterator.Init(node0.GetInstance());
count = 0;
while (iterator.GetNextAgentInfo(agent) == kErrorNone)
{
count++;
Log("- %u) \"%s\", host:\"%s\", port:%u", count, agent.mServiceName, agent.mHostName, agent.mPort);
VerifyOrQuit(agent.mHostName != nullptr);
VerifyOrQuit(agent.mPort != 0);
VerifyOrQuit(agent.mTxtData != nullptr);
VerifyOrQuit(agent.mAddresses != nullptr);
}
VerifyOrQuit(count == 4);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Disable BA function on node0, ensure that it is removed from the `BorderAgentTracker` list");
node0.Get<MeshCoP::BorderAgent>().SetEnabled(false);
nexus.AdvanceTime(5000);
iterator.Init(node0.GetInstance());
count = 0;
while (iterator.GetNextAgentInfo(agent) == kErrorNone)
{
count++;
Log("- %u) \"%s\", host:\"%s\", port:%u", count, agent.mServiceName, agent.mHostName, agent.mPort);
VerifyOrQuit(agent.mHostName != nullptr);
VerifyOrQuit(agent.mPort != 0);
VerifyOrQuit(agent.mTxtData != nullptr);
VerifyOrQuit(agent.mAddresses != nullptr);
}
VerifyOrQuit(count == 3);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Re-enable BA function on node0, ensure that it is added again in the `BorderAgentTracker` list");
node0.Get<MeshCoP::BorderAgent>().SetEnabled(true);
nexus.AdvanceTime(5000);
iterator.Init(node0.GetInstance());
count = 0;
while (iterator.GetNextAgentInfo(agent) == kErrorNone)
{
count++;
Log("- %u) \"%s\", host:\"%s\", port:%u", count, agent.mServiceName, agent.mHostName, agent.mPort);
VerifyOrQuit(agent.mHostName != nullptr);
VerifyOrQuit(agent.mPort != 0);
VerifyOrQuit(agent.mTxtData != nullptr);
VerifyOrQuit(agent.mAddresses != nullptr);
}
VerifyOrQuit(count == 4);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Disable Border Agent Tracker");
node0.Get<MeshCoP::BorderAgentTracker>().SetEnabled(false, MeshCoP::BorderAgentTracker::kRequesterUser);
nexus.AdvanceTime(10);
VerifyOrQuit(!node0.Get<MeshCoP::BorderAgentTracker>().IsRunning());
iterator.Init(node0.GetInstance());
VerifyOrQuit(iterator.GetNextAgentInfo(agent) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Re-enable BA tracker and ensure all agents are discovered again");
node0.Get<MeshCoP::BorderAgentTracker>().SetEnabled(true, MeshCoP::BorderAgentTracker::kRequesterUser);
nexus.AdvanceTime(10);
VerifyOrQuit(node0.Get<MeshCoP::BorderAgentTracker>().IsRunning());
nexus.AdvanceTime(5000);
iterator.Init(node0.GetInstance());
count = 0;
while (iterator.GetNextAgentInfo(agent) == kErrorNone)
{
count++;
Log("- %u) \"%s\", host:\"%s\", port:%u", count, agent.mServiceName, agent.mHostName, agent.mPort);
VerifyOrQuit(agent.mHostName != nullptr);
VerifyOrQuit(agent.mPort != 0);
VerifyOrQuit(agent.mTxtData != nullptr);
VerifyOrQuit(agent.mAddresses != nullptr);
}
VerifyOrQuit(count == 4);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Manually register a second `_meshcop._udp` service on node3");
ClearAllBytes(service);
service.mServiceInstance = "extra";
service.mServiceType = "_meshcop._udp";
service.mPort = 1234;
SuccessOrQuit(node3.Get<Dns::Multicast::Core>().RegisterService(service, /* aRequestId */ 0, nullptr));
nexus.AdvanceTime(5 * 1000);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that both agent services from node3 are discovered and tracked correctly");
iterator.Init(node0.GetInstance());
count = 0;
found = false;
while (iterator.GetNextAgentInfo(agent) == kErrorNone)
{
count++;
Log("- %u) \"%s\", host:\"%s\", port:%u", count, agent.mServiceName, agent.mHostName, agent.mPort);
VerifyOrQuit(agent.mHostName != nullptr);
VerifyOrQuit(agent.mPort != 0);
VerifyOrQuit(agent.mTxtData != nullptr);
VerifyOrQuit(agent.mAddresses != nullptr);
if (StringMatch(agent.mServiceName, service.mServiceInstance, kStringCaseInsensitiveMatch))
{
VerifyOrQuit(agent.mPort == service.mPort);
found = true;
}
}
VerifyOrQuit(count == 5);
VerifyOrQuit(found);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Unregister the manually added second `_meshcop._udp` service");
SuccessOrQuit(node3.Get<Dns::Multicast::Core>().UnregisterService(service));
nexus.AdvanceTime(5 * 1000);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that tracked agents is updated");
iterator.Init(node0.GetInstance());
count = 0;
found = false;
while (iterator.GetNextAgentInfo(agent) == kErrorNone)
{
count++;
Log("- %u) \"%s\", host:\"%s\", port:%u", count, agent.mServiceName, agent.mHostName, agent.mPort);
VerifyOrQuit(agent.mHostName != nullptr);
VerifyOrQuit(agent.mPort != 0);
VerifyOrQuit(agent.mTxtData != nullptr);
VerifyOrQuit(agent.mAddresses != nullptr);
if (StringMatch(agent.mServiceName, service.mServiceInstance, kStringCaseInsensitiveMatch))
{
found = true;
}
}
VerifyOrQuit(count == 4);
VerifyOrQuit(!found);
}
} // namespace Nexus
} // namespace ot
int main(void)
{
ot::Nexus::TestBorderAgentTracker();
printf("All tests passed\n");
return 0;
}
@@ -82,6 +82,8 @@
#define OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE 1
#define OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE 1
#define OPENTHREAD_CONFIG_DIAG_ENABLE 1
#define OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE 1