[border-admitter] enhance forwarding of joiner relay to enrollers (#12829)

This commit enhances the `Admitter` logic for forwarding `RelayRx`
messages to connected enrollers.

If an enroller has explicitly accepted a specific joiner IID, the
`Admitter` will now always forward that joiner's relay traffic to the
owning enroller, regardless of its current `kForwardJoinerRelayRx`
flag in its registered enroller mode. The flag now strictly controls
whether the enroller receives general "multicast" forwarding for
joiners that have not yet been accepted by any enroller.

This enhancement adds a new capability to the interactions between the
`Admitter` and enrollers. Previously, if an enroller accepted a joiner
but also cleared its `kForwardJoinerRelayRx` flag, it could be
considered a misbehavior by the enroller, as it would effectively
block that joiner's traffic from reaching any other enrollers without
receiving the traffic itself. This scenario is no longer possible, and
this configuration now supports a new behavior: allowing an enroller
to accept certain joiners and receive relay traffic only from those it
has explicitly accepted.

The `test_border_admitter` is updated to validate this behavior in
detail.
This commit is contained in:
Abtin Keshavarzian
2026-04-03 23:18:34 -07:00
committed by GitHub
parent 9f28df1e30
commit 5889e428ce
3 changed files with 228 additions and 7 deletions
+1 -1
View File
@@ -341,7 +341,7 @@ private:
void SendEnrollerResponse(Uri aUri, StateTlv::State aResponseState, const Coap::Message &aRequest);
void SendEnrollerReportState(uint8_t aAdmitterState);
Error AppendAdmitterTlvs(Coap::Message &aMessage, uint8_t aAdmitterState);
void ForwardUdpRelayToEnroller(const Coap::Message &aMessage);
void ForwardUdpRelayToEnroller(const Coap::Message &aMessage, bool aCheckEnrollerMode);
void ForwardUdpProxyToEnroller(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
static Error ReadSteeringDataTlv(const Message &aMessage, SteeringData &aSteeringData);
+10 -4
View File
@@ -236,14 +236,16 @@ void Admitter::ForwardJoinerRelayToEnrollers(const Coap::Msg &aMsg)
if (joiner != nullptr)
{
joiner->UpdateExpirationTime();
iter.GetSessionAs<Manager::CoapDtlsSession>()->ForwardUdpRelayToEnroller(aMsg.mMessage);
iter.GetSessionAs<Manager::CoapDtlsSession>()->ForwardUdpRelayToEnroller(aMsg.mMessage,
/* aCheckEnrollerMode */ false);
ExitNow();
}
}
for (EnrollerIterator iter(GetInstance()); !iter.IsDone(); iter.Advance())
{
iter.GetSessionAs<Manager::CoapDtlsSession>()->ForwardUdpRelayToEnroller(aMsg.mMessage);
iter.GetSessionAs<Manager::CoapDtlsSession>()->ForwardUdpRelayToEnroller(aMsg.mMessage,
/* aCheckEnrollerMode */ true);
}
exit:
@@ -1425,12 +1427,16 @@ exit:
return error;
}
void Manager::CoapDtlsSession::ForwardUdpRelayToEnroller(const Coap::Message &aMessage)
void Manager::CoapDtlsSession::ForwardUdpRelayToEnroller(const Coap::Message &aMessage, bool aCheckEnrollerMode)
{
Error error = kErrorNone;
VerifyOrExit(IsEnroller());
VerifyOrExit(mEnroller->ShouldForwardJoinerRelay());
if (aCheckEnrollerMode)
{
VerifyOrExit(mEnroller->ShouldForwardJoinerRelay());
}
SuccessOrExit(error = ForwardUdpRelay(aMessage));
LogInfo("Forward %s to enroller - session %u", UriToString<kUriRelayRx>(), mIndex);
+217 -2
View File
@@ -1326,8 +1326,10 @@ template <uint8_t kNumEnrollers> bool DidFindAllEnrollers(const BitSet<kNumEnrol
void LogEnroller(const Admitter::EnrollerInfo &aInfo)
{
Log(" Enroller - id:%s steeringData:%s mode:0x%02x", aInfo.mId,
AsCoreType(&aInfo.mSteeringData).ToString().AsCString(), aInfo.mMode);
Log(" Enroller - id:%s steeringData:%s mode:0x%02x[%c%c]", aInfo.mId,
AsCoreType(&aInfo.mSteeringData).ToString().AsCString(), aInfo.mMode,
aInfo.mMode & MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx ? 'J' : '-',
aInfo.mMode & MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx ? 'U' : '-');
}
void LogJoiner(const Admitter::JoinerInfo &aInfo)
@@ -2040,6 +2042,219 @@ void TestBorderAdmitterJoinerEnrollerInteraction(void)
joiners[1]->Get<Joiner>().Stop();
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send `EnrollerKeepAlive` message from `enrollers[0]` change its mode to disallow `kForwardJoinerRelayRx`");
modes[0] = MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx;
message =
enrollers[0]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kAccept));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, modes[0]));
responseContexts[0].Clear();
SuccessOrQuit(enrollers[0]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[0]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[0].mReceived);
VerifyOrQuit(responseContexts[0].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[0].mHasAdmitterState);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the `enrollers[0]` mode got changed on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 0)
{
SuccessOrQuit(iter.GetNextJoinerInfo(joinerInfo));
VerifyOrQuit(AsCoreType(&joinerInfo.mIid) == joinerIids[0]);
LogJoiner(joinerInfo);
}
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
nexus.AdvanceTime(Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Start `joiners[0]` again and validate that its `RelayRx` is still only forwarded to `enrollers[0]`");
joiners[0]->Get<ThreadNetif>().Up();
SuccessOrQuit(joiners[0]->Get<Joiner>().Start(kPskd,
/* aProvisioningUrl */ nullptr,
/* aVendorName */ nullptr,
/* aVendorModel */ nullptr,
/* aVendorSwVersion */ nullptr,
/* aVendorData */ nullptr,
/* aCallback */ nullptr,
/* aContext */ nullptr));
nexus.AdvanceTime(8 * Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Ip6::InterfaceIdentifier readIid;
uint16_t joinerRouterRloc;
message = AsCoapMessagePtr(recvContext[i].mRelayRxMsgs.GetHead());
if (i != 0)
{
VerifyOrQuit(message == nullptr);
continue;
}
VerifyOrQuit(message != nullptr);
VerifyOrQuit(message->ReadType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodePost);
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerIidTlv>(*message, readIid));
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerRouterLocatorTlv>(*message, joinerRouterRloc));
VerifyOrQuit(readIid == joinerIids[0]);
VerifyOrQuit(joinerRouterRloc == admitter.Get<Mle::Mle>().GetRloc16());
}
joiners[0]->Get<Joiner>().Stop();
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Start `joiners[1]` and validate that its `RelayRx` is not longer forwarded to `enrollers[0]`");
joiners[1]->Get<ThreadNetif>().Up();
SuccessOrQuit(joiners[1]->Get<Joiner>().Start(kPskd,
/* aProvisioningUrl */ nullptr,
/* aVendorName */ nullptr,
/* aVendorModel */ nullptr,
/* aVendorSwVersion */ nullptr,
/* aVendorData */ nullptr,
/* aCallback */ nullptr,
/* aContext */ nullptr));
joinerIids[1].SetFromExtAddress(joiners[1]->Get<Joiner>().GetId());
nexus.AdvanceTime(8 * Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Ip6::InterfaceIdentifier readIid;
uint16_t joinerRouterRloc;
message = AsCoapMessagePtr(recvContext[i].mRelayRxMsgs.GetHead());
if ((modes[i] & MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx) == 0)
{
VerifyOrQuit(message == nullptr);
continue;
}
VerifyOrQuit(message != nullptr);
VerifyOrQuit(message->ReadType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodePost);
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerIidTlv>(*message, readIid));
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerRouterLocatorTlv>(*message, joinerRouterRloc));
VerifyOrQuit(readIid == joinerIids[1]);
VerifyOrQuit(joinerRouterRloc == admitter.Get<Mle::Mle>().GetRloc16());
}
joiners[1]->Get<Joiner>().Stop();
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send `EnrollerKeepAlive` message from `enrollers[0]` to revert its mode back to allowing both Joiner and UDP");
modes[0] = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx | MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx;
message =
enrollers[0]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kAccept));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, modes[0]));
responseContexts[0].Clear();
SuccessOrQuit(enrollers[0]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[0]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[0].mReceived);
VerifyOrQuit(responseContexts[0].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[0].mHasAdmitterState);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the `enrollers[0]` mode got changed on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 0)
{
SuccessOrQuit(iter.GetNextJoinerInfo(joinerInfo));
VerifyOrQuit(AsCoreType(&joinerInfo.mIid) == joinerIids[0]);
LogJoiner(joinerInfo);
}
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
nexus.AdvanceTime(Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send `EnrollerKeepAlive` message from all `enrollers` to maintain the connection");