[border-agent] improve DTLS session resource management (#13078)

Every DTLS ClientHello from an unseen port previously allocated a
dynamic CoapDtlsSession on the heap before DTLS cookie verification.
This allowed multiple connection attempts to leave allocated sessions
active indefinitely, leading to high memory utilization.

To resolve this:
- Enforce a 15-second handshake timeout on newly allocated sessions.
  Connecting sessions that do not successfully finish the handshake
  within 15 seconds are cleanly disconnected and freed.
- Enforce a session limit cap of 16 concurrent secure sessions on the
  Border Agent. Reaching this limit immediately rejects new session
  connection requests before triggering heap allocation.
- Implement Nexus test case TestBorderAgentSessionsLimit to robustly
  verify both session limit rejection and handshake timeout behavior.
This commit is contained in:
Jonathan Hui
2026-05-07 09:06:30 -07:00
committed by GitHub
parent 87fdaa6946
commit ef6fabd758
3 changed files with 155 additions and 3 deletions
+132
View File
@@ -2574,6 +2574,137 @@ void TestBorderAgentServiceRegistrationRename(void)
node1.Get<Dns::Multicast::Core>().FreeIterator(*iterator);
}
void TestBorderAgentSessionsLimit(void)
{
Core nexus;
Node &node0 = nexus.CreateNode();
Node *nodes[20];
Ip6::SockAddr sockAddr;
Pskc pskc;
Manager::SessionIterator iter;
Manager::SessionInfo sessionInfo;
uint8_t sessionCount;
Log("------------------------------------------------------------------------------------------------------");
Log("TestBorderAgentSessionsLimit");
nexus.AdvanceTime(0);
SuccessOrQuit(node0.SetLogLevel(kLogLevelInfo));
node0.Form();
nexus.AdvanceTime(50 * Time::kOneSecondInMsec);
VerifyOrQuit(node0.Get<Mle::Mle>().IsLeader());
for (uint8_t i = 0; i < 20; i++)
{
nodes[i] = &nexus.CreateNode();
SuccessOrQuit(nodes[i]->Get<Mac::Mac>().SetPanChannel(node0.Get<Mac::Mac>().GetPanChannel()));
nodes[i]->Get<Mac::Mac>().SetPanId(node0.Get<Mac::Mac>().GetPanId());
nodes[i]->Get<ThreadNetif>().Up();
}
VerifyOrQuit(node0.Get<Manager>().IsEnabled());
VerifyOrQuit(node0.Get<Manager>().IsRunning());
SuccessOrQuit(node0.Get<Ip6::Filter>().AddUnsecurePort(node0.Get<Manager>().GetUdpPort()));
sockAddr.SetAddress(node0.Get<Mle::Mle>().GetLinkLocalAddress());
sockAddr.SetPort(node0.Get<Manager>().GetUdpPort());
node0.Get<KeyManager>().GetPskc(pskc);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Establish 16 concurrent secure sessions (the limit)");
for (uint8_t i = 0; i < 16; i++)
{
SuccessOrQuit(nodes[i]->Get<Tmf::SecureAgent>().SetPsk(pskc.m8, Pskc::kSize));
SuccessOrQuit(nodes[i]->Get<Tmf::SecureAgent>().Open(0));
SuccessOrQuit(nodes[i]->Get<Tmf::SecureAgent>().Connect(sockAddr));
}
nexus.AdvanceTime(2 * Time::kOneSecondInMsec);
for (uint8_t i = 0; i < 16; i++)
{
VerifyOrQuit(nodes[i]->Get<Tmf::SecureAgent>().IsConnected());
}
VerifyOrQuit(node0.Get<Manager>().GetCounters().mPskcSecureSessionSuccesses == 16);
// Verify exactly 16 sessions are connected
sessionCount = 0;
iter.Init(node0.GetInstance());
while (iter.GetNextSessionInfo(sessionInfo) == kErrorNone)
{
VerifyOrQuit(sessionInfo.mIsConnected);
sessionCount++;
}
VerifyOrQuit(sessionCount == 16);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Try to establish a 17th secure session, should be rejected");
SuccessOrQuit(nodes[16]->Get<Tmf::SecureAgent>().SetPsk(pskc.m8, Pskc::kSize));
SuccessOrQuit(nodes[16]->Get<Tmf::SecureAgent>().Open(0));
SuccessOrQuit(nodes[16]->Get<Tmf::SecureAgent>().Connect(sockAddr));
nexus.AdvanceTime(2 * Time::kOneSecondInMsec);
VerifyOrQuit(!nodes[16]->Get<Tmf::SecureAgent>().IsConnected());
VerifyOrQuit(node0.Get<Manager>().GetCounters().mPskcSecureSessionSuccesses == 16);
// Verify count remains 16
sessionCount = 0;
iter.Init(node0.GetInstance());
while (iter.GetNextSessionInfo(sessionInfo) == kErrorNone)
{
sessionCount++;
}
VerifyOrQuit(sessionCount == 16);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Close all sessions to clean up");
for (uint8_t i = 0; i < 17; i++)
{
nodes[i]->Get<Tmf::SecureAgent>().Close();
}
nexus.AdvanceTime(5 * Time::kOneSecondInMsec);
iter.Init(node0.GetInstance());
VerifyOrQuit(iter.GetNextSessionInfo(sessionInfo) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Verify Handshake Timeout: start a session but block the client right after first packet");
SuccessOrQuit(nodes[0]->Get<Tmf::SecureAgent>().SetPsk(pskc.m8, Pskc::kSize));
SuccessOrQuit(nodes[0]->Get<Tmf::SecureAgent>().Open(0));
SuccessOrQuit(nodes[0]->Get<Tmf::SecureAgent>().Connect(sockAddr));
// Drop client's interface immediately so it never receives HelloVerifyRequest
nodes[0]->Get<ThreadNetif>().Down();
// Wait for 14 seconds (handshake timeout is 15 seconds)
nexus.AdvanceTime(14 * Time::kOneSecondInMsec);
// The session should still be in connecting state on node0
iter.Init(node0.GetInstance());
SuccessOrQuit(iter.GetNextSessionInfo(sessionInfo));
VerifyOrQuit(!sessionInfo.mIsConnected);
VerifyOrQuit(iter.GetNextSessionInfo(sessionInfo) == kErrorNotFound);
// Wait for additional 4 seconds (total 18 seconds, past the 15-second timeout + 2-second guard time)
nexus.AdvanceTime(4 * Time::kOneSecondInMsec);
// The session should have timed out and been removed
iter.Init(node0.GetInstance());
VerifyOrQuit(iter.GetNextSessionInfo(sessionInfo) == kErrorNotFound);
Log("TestBorderAgentSessionsLimit passed successfully!");
}
} // namespace Nexus
} // namespace ot
@@ -2586,6 +2717,7 @@ int main(void)
ot::Nexus::TestBorderAgentTxtDataCallback();
ot::Nexus::TestBorderAgentServiceRegistration();
ot::Nexus::TestBorderAgentServiceRegistrationRename();
ot::Nexus::TestBorderAgentSessionsLimit();
printf("All tests passed\n");
return 0;
}