[border-agent] directly respond to MGMT_GET from non-active commissioner (#10524)

This commit updates the `BorderAgent` to directly respond to
`MGMT_ACTIVE_GET` and `MGMT_PENDING_GET` requests from a non-active
commissioner. Requests from an active commissioner are still
forwarded to the leader. This aligns the implementation with Thread
1.4 requirements (ephemeral PSKc use case).

To achieve this, the following changes are made:

- New `State` values are added to distinguish between when a
  commissioner candidate is connected and when its petition to become
  the active commissioner is accepted. This determines whether the
  `MGMT_GET` request should be handled directly or forwarded to the
  leader.
- This state is tracked locally by `BorderAgent` instead of monitoring
  Network Data to determine whether an active commissioner exists.
  This ensures correct behavior even when Network Data updates are
  delayed.
- The `DatasetManager` is updated to provide `ProcessGetRequest()`
  to process an `MGMT_GET` request and prepare the response. This is
  then used by `DatasetManager` itself and `BorderAgent`.
This commit is contained in:
Abtin Keshavarzian
2024-07-25 11:04:37 -07:00
committed by GitHub
parent af18582b4e
commit 2cc0798a97
5 changed files with 138 additions and 48 deletions
+16 -1
View File
@@ -56,7 +56,22 @@ otError otBorderAgentSetId(otInstance *aInstance, const otBorderAgentId *aId)
otBorderAgentState otBorderAgentGetState(otInstance *aInstance)
{
return MapEnum(AsCoreType(aInstance).Get<MeshCoP::BorderAgent>().GetState());
otBorderAgentState state = OT_BORDER_AGENT_STATE_STOPPED;
switch (AsCoreType(aInstance).Get<MeshCoP::BorderAgent>().GetState())
{
case MeshCoP::BorderAgent::kStateStopped:
break;
case MeshCoP::BorderAgent::kStateStarted:
state = OT_BORDER_AGENT_STATE_STARTED;
break;
case MeshCoP::BorderAgent::kStateConnected:
case MeshCoP::BorderAgent::kStateAccepted:
state = OT_BORDER_AGENT_STATE_ACTIVE;
break;
}
return state;
}
uint16_t otBorderAgentGetUdpPort(otInstance *aInstance)
+45 -4
View File
@@ -203,6 +203,7 @@ void BorderAgent::HandleCoapResponse(const ForwardContext &aForwardContext,
Get<Mle::Mle>().GetCommissionerAloc(sessionId, mCommissionerAloc.GetAddress());
Get<ThreadNetif>().AddUnicastAddress(mCommissionerAloc);
IgnoreError(Get<Ip6::Udp>().AddReceiver(mUdpReceiver));
mState = kStateAccepted;
LogInfo("Commissioner accepted - SessionId:%u ALOC:%s", sessionId,
mCommissionerAloc.GetAddress().ToString().AsCString());
@@ -462,7 +463,7 @@ void BorderAgent::HandleTmf<kUriCommissionerSet>(Coap::Message &aMessage, const
template <> void BorderAgent::HandleTmf<kUriActiveGet>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
IgnoreError(ForwardToLeader(aMessage, aMessageInfo, kUriActiveGet));
HandleTmfDatasetGet(aMessage, aMessageInfo, Dataset::kActive);
}
template <> void BorderAgent::HandleTmf<kUriActiveSet>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
@@ -472,7 +473,7 @@ template <> void BorderAgent::HandleTmf<kUriActiveSet>(Coap::Message &aMessage,
template <> void BorderAgent::HandleTmf<kUriPendingGet>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
IgnoreError(ForwardToLeader(aMessage, aMessageInfo, kUriPendingGet));
HandleTmfDatasetGet(aMessage, aMessageInfo, Dataset::kPending);
}
template <> void BorderAgent::HandleTmf<kUriPendingSet>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
@@ -591,6 +592,45 @@ exit:
return error;
}
void BorderAgent::HandleTmfDatasetGet(Coap::Message &aMessage,
const Ip6::MessageInfo &aMessageInfo,
Dataset::Type aType)
{
Error error = kErrorNone;
Coap::Message *response = nullptr;
if (mState == kStateAccepted)
{
Uri uri = (aType == Dataset::kActive) ? kUriActiveGet : kUriPendingGet;
IgnoreError(ForwardToLeader(aMessage, aMessageInfo, uri));
ExitNow();
}
// When processing `MGMT_GET` request directly on Border Agent,
// the Security Policy flags (O-bit) should be ignore to allow
// the commissioner candidate to get the full Operational Dataset.
if (aType == Dataset::kActive)
{
response = Get<ActiveDatasetManager>().ProcessGetRequest(aMessage, DatasetManager::kIgnoreSecurityPolicyFlags);
}
else
{
response = Get<PendingDatasetManager>().ProcessGetRequest(aMessage, DatasetManager::kIgnoreSecurityPolicyFlags);
}
VerifyOrExit(response != nullptr, error = kErrorParse);
SuccessOrExit(error = Get<Tmf::SecureAgent>().SendMessage(*response, aMessageInfo));
LogInfo("Sent %sGet response to non-active commissioner", Dataset::TypeToString(aType));
exit:
LogWarnOnError(error, "send Active/PendingGet response");
FreeMessageOnError(response, error);
}
void BorderAgent::HandleConnected(bool aConnected, void *aContext)
{
static_cast<BorderAgent *>(aContext)->HandleConnected(aConnected);
@@ -601,7 +641,7 @@ void BorderAgent::HandleConnected(bool aConnected)
if (aConnected)
{
LogInfo("Commissioner connected");
mState = kStateActive;
mState = kStateConnected;
mTimer.Start(kKeepAliveTimeout);
}
else
@@ -766,7 +806,8 @@ void BorderAgent::ClearEphemeralKey(void)
break;
case kStateStopped:
case kStateActive:
case kStateConnected:
case kStateAccepted:
// If there is an active commissioner connection, we wait till
// it gets disconnected before removing ephemeral key and
// restarting the agent.
+6 -4
View File
@@ -46,6 +46,7 @@
#include "common/non_copyable.hpp"
#include "common/notifier.hpp"
#include "common/tasklet.hpp"
#include "meshcop/dataset.hpp"
#include "meshcop/secure_transport.hpp"
#include "net/udp6.hpp"
#include "thread/tmf.hpp"
@@ -94,9 +95,10 @@ public:
*/
enum State : uint8_t
{
kStateStopped = OT_BORDER_AGENT_STATE_STOPPED, ///< Border agent is stopped/disabled.
kStateStarted = OT_BORDER_AGENT_STATE_STARTED, ///< Border agent is started.
kStateActive = OT_BORDER_AGENT_STATE_ACTIVE, ///< Border agent is connected with external commissioner.
kStateStopped, ///< Stopped/disabled.
kStateStarted, ///< Started and listening for connections.
kStateConnected, ///< Connected to an external commissioner candidate, petition pending.
kStateAccepted, ///< Connected to and accepted an external commissioner.
};
/**
@@ -288,6 +290,7 @@ private:
template <Uri kUri> void HandleTmf(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
void HandleTmfDatasetGet(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Dataset::Type aType);
void HandleTimeout(void);
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
@@ -346,7 +349,6 @@ DeclareTmfHandler(BorderAgent, kUriProxyTx);
} // namespace MeshCoP
DefineMapEnum(otBorderAgentState, MeshCoP::BorderAgent::State);
DefineCoreType(otBorderAgentId, MeshCoP::BorderAgent::Id);
} // namespace ot
+48 -36
View File
@@ -531,71 +531,83 @@ exit:
void DatasetManager::HandleGet(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) const
{
TlvList tlvList;
uint8_t tlvType;
OffsetRange offsetRange;
Error error = kErrorNone;
Coap::Message *response = ProcessGetRequest(aMessage, kCheckSecurityPolicyFlags);
SuccessOrExit(Tlv::FindTlvValueOffsetRange(aMessage, Tlv::kGet, offsetRange));
VerifyOrExit(response != nullptr);
SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*response, aMessageInfo));
while (!offsetRange.IsEmpty())
{
IgnoreError(aMessage.Read(offsetRange, tlvType));
tlvList.Add(tlvType);
offsetRange.AdvanceOffset(sizeof(uint8_t));
}
// MGMT_PENDING_GET.rsp must include Delay Timer TLV (Thread 1.1.1
// Section 8.7.5.4).
if (!tlvList.IsEmpty() && IsPendingDataset())
{
tlvList.Add(Tlv::kDelayTimer);
}
LogInfo("sent %s dataset get response to %s", IsActiveDataset() ? "active" : "pending",
aMessageInfo.GetPeerAddr().ToString().AsCString());
exit:
SendGetResponse(aMessage, aMessageInfo, tlvList);
FreeMessageOnError(response, error);
}
void DatasetManager::SendGetResponse(const Coap::Message &aRequest,
const Ip6::MessageInfo &aMessageInfo,
const TlvList &aTlvList) const
Coap::Message *DatasetManager::ProcessGetRequest(const Coap::Message &aRequest,
SecurityPolicyCheckMode aCheckMode) const
{
Error error = kErrorNone;
Coap::Message *message;
// Processes a MGMT_ACTIVE_GET or MGMT_PENDING_GET request
// and prepares the response.
Error error = kErrorNone;
Coap::Message *response = nullptr;
Dataset dataset;
TlvList tlvList;
OffsetRange offsetRange;
if (Tlv::FindTlvValueOffsetRange(aRequest, Tlv::kGet, offsetRange) == kErrorNone)
{
while (!offsetRange.IsEmpty())
{
uint8_t tlvType;
IgnoreError(aRequest.Read(offsetRange, tlvType));
tlvList.Add(tlvType);
offsetRange.AdvanceOffset(sizeof(uint8_t));
}
// MGMT_PENDING_GET.rsp must include Delay Timer TLV (Thread 1.1.1
// Section 8.7.5.4).
if (!tlvList.IsEmpty() && IsPendingDataset())
{
tlvList.Add(Tlv::kDelayTimer);
}
}
// Ignore `Read()` error, since even if no Dataset is saved, we should
// respond with an empty one.
IgnoreError(Read(dataset));
message = Get<Tmf::Agent>().NewPriorityResponseMessage(aRequest);
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
response = Get<Tmf::Agent>().NewPriorityResponseMessage(aRequest);
VerifyOrExit(response != nullptr, error = kErrorNoBufs);
for (const Tlv *tlv = dataset.GetTlvsStart(); tlv < dataset.GetTlvsEnd(); tlv = tlv->GetNext())
{
bool shouldAppend = true;
if (!aTlvList.IsEmpty())
if (!tlvList.IsEmpty())
{
shouldAppend = aTlvList.Contains(tlv->GetType());
shouldAppend = tlvList.Contains(tlv->GetType());
}
if ((tlv->GetType() == Tlv::kNetworkKey) && !Get<KeyManager>().GetSecurityPolicy().mObtainNetworkKeyEnabled)
if ((aCheckMode == kCheckSecurityPolicyFlags) && (tlv->GetType() == Tlv::kNetworkKey) &&
!Get<KeyManager>().GetSecurityPolicy().mObtainNetworkKeyEnabled)
{
shouldAppend = false;
}
if (shouldAppend)
{
SuccessOrExit(error = tlv->AppendTo(*message));
SuccessOrExit(error = tlv->AppendTo(*response));
}
}
SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*message, aMessageInfo));
LogInfo("sent %s dataset get response to %s", IsActiveDataset() ? "active" : "pending",
aMessageInfo.GetPeerAddr().ToString().AsCString());
exit:
FreeMessageOnError(message, error);
FreeAndNullMessageOnError(response, error);
return response;
}
Error DatasetManager::SendSetRequest(const Dataset::Info &aDatasetInfo,
+23 -3
View File
@@ -65,6 +65,18 @@ public:
*/
typedef otDatasetMgmtSetCallback MgmtSetCallback;
/**
* Indicates whether to check or ignore Security Policy flag when processing an MGMT_GET request message.
*
* This is used as input in `ProcessGetRequest().
*
*/
enum SecurityPolicyCheckMode : uint8_t
{
kCheckSecurityPolicyFlags, ///< Check Security Policy flags.
kIgnoreSecurityPolicyFlags, ///< Ignore Security Policy flags.
};
/**
* Returns the network Timestamp.
*
@@ -219,6 +231,17 @@ public:
uint8_t aLength,
const otIp6Address *aAddress) const;
/**
* Processes a MGMT_GET request message and prepares the response.
*
* @param[in] aRequest The MGMT_GET request message.
* @param[in] aCheckMode Indicates whether to check or ignore the Security Policy flags.
*
* @returns The prepared response, or `nullptr` if fails to parse the request or cannot allocate message.
*
*/
Coap::Message *ProcessGetRequest(const Coap::Message &aRequest, SecurityPolicyCheckMode aCheckMode) const;
private:
static constexpr uint8_t kMaxGetTypes = 64; // Max number of types in MGMT_GET.req
static constexpr uint32_t kSendSetDelay = 5000; // in msec.
@@ -279,9 +302,6 @@ private:
void SignalDatasetChange(void) const;
void SyncLocalWithLeader(const Dataset &aDataset);
Error SendSetRequest(const Dataset &aDataset);
void SendGetResponse(const Coap::Message &aRequest,
const Ip6::MessageInfo &aMessageInfo,
const TlvList &aTlvList) const;
void HandleMgmtSetResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, Error aError);
static void HandleMgmtSetResponse(void *aContext,