mirror of
https://github.com/espressif/openthread.git
synced 2026-08-13 06:07:48 +00:00
[border-agent] add commissioner eviction API (#12174)
This change adds `EvictActiveCommissioner()` to the Border Agent, which sends a `LeaderKeepAlive` TMF message with a `StateTlv` of `kReject` to the Leader, causing the current active commissioner to be evicted. The feature is exposed through: - A new public C API `otBorderAgentEvictActiveCommissioner()`. - A new CLI command `ba evictcommissioner`. The entire feature is guarded by a new configuration flag, `OPENTHREAD_CONFIG_BORDER_AGENT_COMMISSIONER_EVICTION_API_ENABLE`, which is disabled by default. This provides an administrator-level tool to remove a stale or misbehaving commissioner, which is particularly useful when the commissioner is connected through a different border agent and cannot be managed locally. A new test is also added to verify the eviction behavior.
This commit is contained in:
@@ -383,6 +383,23 @@ otError otBorderAgentGetNextSessionInfo(otBorderAgentSessionIterator *aIterator,
|
||||
*/
|
||||
const otBorderAgentCounters *otBorderAgentGetCounters(otInstance *aInstance);
|
||||
|
||||
/**
|
||||
* Forcefully evicts the current active Thread Commissioner.
|
||||
*
|
||||
* Requires `OPENTHREAD_CONFIG_BORDER_AGENT_COMMISSIONER_EVICTION_API_ENABLE`.
|
||||
*
|
||||
* This is intended as an administrator tool to address a misbehaving or stale commissioner session that may be
|
||||
* connected through a different Border Agent. It provides a mechanism to clear the single Active Commissioner role
|
||||
* within the Thread network, allowing a new candidate to be selected as the Active commissioner.
|
||||
*
|
||||
* @param[in] aInstance A pointer to an OpenThread instance.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully sent the eviction request to the Leader.
|
||||
* @retval OT_ERROR_NOT_FOUND There is no active commissioner session to evict.
|
||||
* @retval OT_ERROR_NO_BUFS Could not allocate a message buffer to send the request.
|
||||
*/
|
||||
otError otBorderAgentEvictActiveCommissioner(otInstance *aInstance);
|
||||
|
||||
/*--------------------------------------------------------------------------------------------------------------------
|
||||
* Border Agent Ephemeral Key feature */
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ extern "C" {
|
||||
*
|
||||
* @note This number versions both OpenThread platform and user APIs.
|
||||
*/
|
||||
#define OPENTHREAD_API_VERSION (556)
|
||||
#define OPENTHREAD_API_VERSION (557)
|
||||
|
||||
/**
|
||||
* @addtogroup api-instance
|
||||
|
||||
@@ -443,6 +443,19 @@ ba sessions
|
||||
Done
|
||||
```
|
||||
|
||||
### ba evictcommissioner
|
||||
|
||||
Forcefully evicts the current active Thread Commissioner.
|
||||
|
||||
Requires `OPENTHREAD_CONFIG_BORDER_AGENT_COMMISSIONER_EVICTION_API_ENABLE`.
|
||||
|
||||
This command is intended as an administrator tool to address a misbehaving or stale commissioner session that may be connected through a different Border Agent. It provides a mechanism to clear the single Active Commissioner role within the Thread network, allowing a new candidate to be selected as the Active commissioner.
|
||||
|
||||
```bash
|
||||
> ba evictcommissioner
|
||||
Done
|
||||
```
|
||||
|
||||
### ba ephemeralkey
|
||||
|
||||
Print the Border Agent's Ephemeral Key Manager state.
|
||||
|
||||
@@ -588,6 +588,22 @@ template <> otError Interpreter::Process<Cmd("ba")>(Arg aArgs[])
|
||||
}
|
||||
}
|
||||
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_COMMISSIONER_EVICTION_API_ENABLE
|
||||
/**
|
||||
* @cli ba evictcommissioner
|
||||
* @code
|
||||
* ba evictcommissioner
|
||||
* Done
|
||||
* @endcode
|
||||
* @par api_copy
|
||||
* #otBorderAgentEvictActiveCommissioner
|
||||
*/
|
||||
else if (aArgs[0] == "evictcommissioner")
|
||||
{
|
||||
VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
|
||||
error = otBorderAgentEvictActiveCommissioner(GetInstancePtr());
|
||||
}
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
|
||||
else if (aArgs[0] == "ephemeralkey")
|
||||
{
|
||||
|
||||
@@ -119,6 +119,13 @@ const otBorderAgentCounters *otBorderAgentGetCounters(otInstance *aInstance)
|
||||
return &AsCoreType(aInstance).Get<MeshCoP::BorderAgent::Manager>().GetCounters();
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_COMMISSIONER_EVICTION_API_ENABLE
|
||||
otError otBorderAgentEvictActiveCommissioner(otInstance *aInstance)
|
||||
{
|
||||
return AsCoreType(aInstance).Get<MeshCoP::BorderAgent::Manager>().EvictActiveCommissioner();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
|
||||
|
||||
otBorderAgentEphemeralKeyState otBorderAgentEphemeralKeyGetState(otInstance *aInstance)
|
||||
|
||||
@@ -122,6 +122,18 @@
|
||||
#define OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME "OpenThread BR (unspecified vendor) "
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_BORDER_AGENT_COMMISSIONER_EVICTION_API_ENABLE
|
||||
*
|
||||
* Define to 1 to enable the `otBorderAgentEvictActiveCommissioner()` API.
|
||||
*
|
||||
* This API provides a mechanism to evict the active Thread Commissioner from the network. This is primarily intended
|
||||
* for administrative use to handle misbehaving or stale commissioner sessions.
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_BORDER_AGENT_COMMISSIONER_EVICTION_API_ENABLE
|
||||
#define OPENTHREAD_CONFIG_BORDER_AGENT_COMMISSIONER_EVICTION_API_ENABLE 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE
|
||||
*
|
||||
|
||||
@@ -475,6 +475,37 @@ exit:
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_COMMISSIONER_EVICTION_API_ENABLE
|
||||
|
||||
Error Manager::EvictActiveCommissioner(void)
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
uint16_t sessionId;
|
||||
uint16_t baRloc16;
|
||||
Tmf::MessageInfo messageInfo(GetInstance());
|
||||
OwnedPtr<Coap::Message> message;
|
||||
|
||||
SuccessOrExit(error = Get<NetworkData::Leader>().FindBorderAgentRloc(baRloc16));
|
||||
SuccessOrExit(error = Get<NetworkData::Leader>().FindCommissioningSessionId(sessionId));
|
||||
|
||||
message.Reset(Get<Tmf::Agent>().NewPriorityConfirmablePostMessage(kUriLeaderKeepAlive));
|
||||
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
|
||||
|
||||
SuccessOrExit(error = Tlv::Append<StateTlv>(*message, StateTlv::kReject));
|
||||
SuccessOrExit(error = Tlv::Append<CommissionerSessionIdTlv>(*message, sessionId));
|
||||
|
||||
messageInfo.SetSockAddrToRlocPeerAddrToLeaderAloc();
|
||||
messageInfo.SetSockPortToTmf();
|
||||
|
||||
SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*message, messageInfo));
|
||||
message.Release();
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_COMMISSIONER_EVICTION_API_ENABLE
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
// Manager::SessionIterator
|
||||
|
||||
|
||||
@@ -235,6 +235,21 @@ public:
|
||||
Error SetServiceBaseName(const char *aBaseName);
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_COMMISSIONER_EVICTION_API_ENABLE
|
||||
/**
|
||||
* Forcefully evicts the current active Thread Commissioner.
|
||||
*
|
||||
* This is intended as an administrator tool to address a misbehaving or stale commissioner session that may be
|
||||
* connected through a different Border Agent. It provides a mechanism to clear the single Active Commissioner
|
||||
* role within the Thread network, allowing a new candidate to be selected as the Active commissioner.
|
||||
*
|
||||
* @retval kErrorNone Successfully sent the eviction request to the Leader.
|
||||
* @retval kErrorNotFound There is no active commissioner session to evict.
|
||||
* @retval kErrorNoBufs Could not allocate a message buffer to send the request.
|
||||
*/
|
||||
Error EvictActiveCommissioner(void);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Gets the set of border agent counters.
|
||||
*
|
||||
|
||||
@@ -456,6 +456,9 @@ class Node(object):
|
||||
def get_netdata_contexts(self):
|
||||
return self.get_netdata()['contexts']
|
||||
|
||||
def get_netdata_commissioning(self):
|
||||
return self.get_netdata()['commissioning']
|
||||
|
||||
def get_netdata_versions(self):
|
||||
leaderdata = Node.parse_list(self.cli('leaderdata'))
|
||||
return (int(leaderdata['Data Version']), int(leaderdata['Stable Data Version']))
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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.
|
||||
|
||||
from cli import verify
|
||||
from cli import verify_within
|
||||
import cli
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Test description:
|
||||
# This test covers the behavior of `BorderAgent::EvictActiveCommissioner()`
|
||||
#
|
||||
|
||||
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
|
||||
print('-' * 120)
|
||||
print('Starting \'{}\''.format(test_name))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Creating `cli.Nodes` instances
|
||||
|
||||
speedup = 25
|
||||
cli.Node.set_time_speedup_factor(speedup)
|
||||
|
||||
leader = cli.Node()
|
||||
commissioner = cli.Node()
|
||||
agent = cli.Node()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Form topology
|
||||
#
|
||||
|
||||
leader.form('evictcmmr')
|
||||
commissioner.join(leader)
|
||||
agent.join(leader)
|
||||
|
||||
verify(leader.get_state() == 'leader')
|
||||
verify(commissioner.get_state() == 'router')
|
||||
verify(agent.get_state() == 'router')
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Test Implementation
|
||||
|
||||
commissioner.cli('commissioner start')
|
||||
|
||||
|
||||
def check_commissioner_state_is_active():
|
||||
verify(commissioner.cli('commissioner state')[0] == 'active')
|
||||
|
||||
|
||||
verify_within(check_commissioner_state_is_active, 10)
|
||||
|
||||
# Verify Commissioning Info in Network Data and that `commissioner` is accepted and active.
|
||||
|
||||
data = leader.get_netdata_commissioning()
|
||||
rloc16 = int(data[0].strip().split()[1], 16)
|
||||
verify(rloc16 == int(commissioner.get_rloc16(), 16))
|
||||
|
||||
# Evict the current active commissioner.
|
||||
|
||||
agent.cli('ba evictcommissioner')
|
||||
|
||||
# Check that the Network Data Commissioning Info is cleared after eviction.
|
||||
|
||||
|
||||
def check_netdata_commissioning_info():
|
||||
# check there is no active commissioner
|
||||
data = leader.get_netdata_commissioning()
|
||||
verify(data[0].strip().split()[1] == '-')
|
||||
|
||||
|
||||
verify_within(check_netdata_commissioning_info, 10)
|
||||
|
||||
# Check that the original commissioner's state becomes disabled
|
||||
|
||||
|
||||
def check_commissioner_state_is_disabled():
|
||||
verify(commissioner.cli('commissioner state')[0] == 'disabled')
|
||||
|
||||
|
||||
verify_within(check_commissioner_state_is_disabled, 60 / speedup)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
cli.Node.finalize_all_nodes()
|
||||
|
||||
print('\'{}\' passed.'.format(test_name))
|
||||
@@ -84,6 +84,8 @@
|
||||
|
||||
#define OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE 1
|
||||
|
||||
#define OPENTHREAD_CONFIG_BORDER_AGENT_COMMISSIONER_EVICTION_API_ENABLE 1
|
||||
|
||||
#define OPENTHREAD_CONFIG_BORDER_AGENT_TRACKER_ENABLE 1
|
||||
|
||||
#define OPENTHREAD_CONFIG_BORDER_AGENT_TXT_DATA_PARSER_ENABLE 1
|
||||
|
||||
@@ -204,6 +204,7 @@ if [ "$TORANJ_CLI" = 1 ]; then
|
||||
run cli/test-036-dhcp-prefix-netdata.py
|
||||
run cli/test-037-mtd-annc-join-older-timestamp.py
|
||||
run cli/test-038-simultaneous-parent-and-child-reset.py
|
||||
run cli/test-039-border-agent-evict-active-commissioner.py
|
||||
run cli/test-400-srp-client-server.py
|
||||
run cli/test-401-srp-server-address-cache-snoop.py
|
||||
run cli/test-500-two-brs-two-networks.py
|
||||
|
||||
Reference in New Issue
Block a user