Skip to content

Commit

Permalink
[border-agent] directly respond to MGMT_GET from non-active commissio…
Browse files Browse the repository at this point in the history
…ner (openthread#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`.
  • Loading branch information
abtink authored Jul 25, 2024
1 parent af18582 commit 2cc0798
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 48 deletions.
17 changes: 16 additions & 1 deletion src/core/api/border_agent_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
49 changes: 45 additions & 4 deletions src/core/meshcop/border_agent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -591,6 +592,45 @@ Error BorderAgent::ForwardToLeader(const Coap::Message &aMessage, const Ip6::Mes
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);
Expand All @@ -601,7 +641,7 @@ void BorderAgent::HandleConnected(bool aConnected)
if (aConnected)
{
LogInfo("Commissioner connected");
mState = kStateActive;
mState = kStateConnected;
mTimer.Start(kKeepAliveTimeout);
}
else
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 6 additions & 4 deletions src/core/meshcop/border_agent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -94,9 +95,10 @@ class BorderAgent : public InstanceLocator, private NonCopyable
*/
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.
};

/**
Expand Down Expand Up @@ -288,6 +290,7 @@ class BorderAgent : public InstanceLocator, private NonCopyable

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
Expand Down Expand Up @@ -346,7 +349,6 @@ DeclareTmfHandler(BorderAgent, kUriProxyTx);

} // namespace MeshCoP

DefineMapEnum(otBorderAgentState, MeshCoP::BorderAgent::State);
DefineCoreType(otBorderAgentId, MeshCoP::BorderAgent::Id);

} // namespace ot
Expand Down
84 changes: 48 additions & 36 deletions src/core/meshcop/dataset_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -531,71 +531,83 @@ void DatasetManager::HandleMgmtSetResponse(Coap::Message *aMessage, const Ip6::M

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,
Expand Down
26 changes: 23 additions & 3 deletions src/core/meshcop/dataset_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ class DatasetManager : public InstanceLocator
*/
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.
*
Expand Down Expand Up @@ -219,6 +231,17 @@ class DatasetManager : public InstanceLocator
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.
Expand Down Expand Up @@ -279,9 +302,6 @@ class DatasetManager : public InstanceLocator
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,
Expand Down

0 comments on commit 2cc0798

Please sign in to comment.