From 90e90bba71555f543af4561271334dcfbd81911f Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 6 Jan 2025 15:54:08 -0800 Subject: [PATCH] [test] add `test_border_agent` to nexus test cases (#11110) This commit adds `test_border_agent`, using the nexus framework, to cover the functionality of `BorderAgent`. The test cases include: - Border Agent initial state. - Establishing a secure session to the Border Agent and disconnecting. - Handling "Commissioner Petition" to accept a full commissioner. - Handling "Commissioner Keep Alive" and timeouts. - Ephemeral key use and its initial state. - Establishing a connection with an ephemeral key and disconnecting. - Ephemeral key timeout mechanism (with or without a session). - Ephemeral key use stopping after the maximum number of failed connection attempts. --- tests/nexus/CMakeLists.txt | 1 + tests/nexus/platform/nexus_core.cpp | 2 + tests/nexus/platform/nexus_core.hpp | 1 + tests/nexus/test_border_agent.cpp | 363 ++++++++++++++++++++++++++++ 4 files changed, 367 insertions(+) create mode 100644 tests/nexus/test_border_agent.cpp diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 8f248fa9a..d331d073a 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -108,6 +108,7 @@ endmacro() #---------------------------------------------------------------------------------------------------------------------- +ot_nexus_test(border_agent) ot_nexus_test(dtls) ot_nexus_test(form_join) ot_nexus_test(large_network) diff --git a/tests/nexus/platform/nexus_core.cpp b/tests/nexus/platform/nexus_core.cpp index 37b7eef98..f417ab643 100644 --- a/tests/nexus/platform/nexus_core.cpp +++ b/tests/nexus/platform/nexus_core.cpp @@ -45,6 +45,8 @@ Core::Core(void) mNextAlarmTime = mNow.GetDistantFuture(); } +Core::~Core(void) { sCore = nullptr; } + Node &Core::CreateNode(void) { Node *node; diff --git a/tests/nexus/platform/nexus_core.hpp b/tests/nexus/platform/nexus_core.hpp index f164e5bc3..84313f836 100644 --- a/tests/nexus/platform/nexus_core.hpp +++ b/tests/nexus/platform/nexus_core.hpp @@ -45,6 +45,7 @@ class Core { public: Core(void); + ~Core(void); static Core &Get(void) { return *sCore; } diff --git a/tests/nexus/test_border_agent.cpp b/tests/nexus/test_border_agent.cpp new file mode 100644 index 000000000..37fcec6a8 --- /dev/null +++ b/tests/nexus/test_border_agent.cpp @@ -0,0 +1,363 @@ +/* + * 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. + */ + +#include +#include +#include + +#include "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" + +namespace ot { +namespace Nexus { + +void TestBorderAgent(void) +{ + Core nexus; + Node &node0 = nexus.CreateNode(); + Node &node1 = nexus.CreateNode(); + Ip6::SockAddr sockAddr; + Pskc pskc; + Coap::Message *message; + + Log("------------------------------------------------------------------------------------------------------"); + Log("TestBorderAgent"); + + nexus.AdvanceTime(0); + + // Form the topology: + // - node0 leader acting as Border Agent, + // - node1 staying disconnected (acting as candidate) + + node0.Form(); + nexus.AdvanceTime(50 * Time::kOneSecondInMsec); + VerifyOrQuit(node0.Get().IsLeader()); + + SuccessOrQuit(node1.Get().SetPanChannel(node0.Get().GetPanChannel())); + node1.Get().SetPanId(node0.Get().GetPanId()); + node1.Get().Up(); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check Border Agent initial state"); + + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); + + SuccessOrQuit(node0.Get().AddUnsecurePort(node0.Get().GetUdpPort())); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Establish a DTLS connection to Border Agent"); + + sockAddr.SetAddress(node0.Get().GetLinkLocalAddress()); + sockAddr.SetPort(node0.Get().GetUdpPort()); + + node0.Get().GetPskc(pskc); + SuccessOrQuit(node1.Get().SetPsk(pskc.m8, Pskc::kSize)); + + SuccessOrQuit(node1.Get().Open()); + SuccessOrQuit(node1.Get().Connect(sockAddr)); + + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + VerifyOrQuit(node1.Get().IsConnected()); + + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateConnected); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Disconnect from candidate side"); + + node1.Get().Close(); + + nexus.AdvanceTime(3 * Time::kOneSecondInMsec); + + VerifyOrQuit(!node1.Get().IsConnected()); + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Establish a secure connection again"); + + SuccessOrQuit(node1.Get().Open()); + SuccessOrQuit(node1.Get().Connect(sockAddr)); + + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + VerifyOrQuit(node1.Get().IsConnected()); + + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateConnected); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Send `Commissioner Petition` TMF command to become full commissioner"); + + message = node1.Get().NewPriorityConfirmablePostMessage(kUriCommissionerPetition); + VerifyOrQuit(message != nullptr); + SuccessOrQuit(Tlv::Append(*message, "node1")); + SuccessOrQuit(node1.Get().SendMessage(*message)); + + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateAccepted); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Send `Commissioner Keep Alive` and check timeout behavior"); + + nexus.AdvanceTime(30 * Time::kOneSecondInMsec); + + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateAccepted); + VerifyOrQuit(node1.Get().IsConnected()); + + message = node1.Get().NewPriorityConfirmablePostMessage(kUriCommissionerKeepAlive); + VerifyOrQuit(message != nullptr); + SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kAccept)); + SuccessOrQuit(Tlv::Append(*message, "node1")); + SuccessOrQuit(node1.Get().SendMessage(*message)); + + Log(" Wait for 49 seconds (TIMEOUT_LEAD_PET is 50 seconds) and check BA state"); + + nexus.AdvanceTime(49 * Time::kOneSecondInMsec); + + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateAccepted); + VerifyOrQuit(node1.Get().IsConnected()); + + Log(" Wait for additional 5 seconds (ensuring TIMEOUT_LEAD_PET and session disconnect guard time expires)"); + + nexus.AdvanceTime(5 * Time::kOneSecondInMsec); + + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); + VerifyOrQuit(!node1.Get().IsConnected()); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Establish a secure session again and petition to become commissioner"); + + SuccessOrQuit(node1.Get().Connect(sockAddr)); + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + VerifyOrQuit(node1.Get().IsConnected()); + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateConnected); + + message = node1.Get().NewPriorityConfirmablePostMessage(kUriCommissionerPetition); + VerifyOrQuit(message != nullptr); + SuccessOrQuit(Tlv::Append(*message, "node1")); + SuccessOrQuit(node1.Get().SendMessage(*message)); + + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateAccepted); +} + +static bool sEphemeralKeyCallbackCalled = false; + +void HandleEphemeralKeyChange(void *aContext) +{ + Node *node; + + VerifyOrQuit(aContext != nullptr); + node = reinterpret_cast(aContext); + + Log(" EphemeralKeyCallback() active:%u connected:%u", node->Get().IsEphemeralKeyActive(), + node->Get().GetState() == MeshCoP::BorderAgent::kStateConnected); + sEphemeralKeyCallbackCalled = true; +} + +void TestBorderAgentEphemeralKey(void) +{ + static const char kEphemeralKey[] = "nexus1234"; + static constexpr uint16_t kEphemeralKeySize = sizeof(kEphemeralKey) - 1; + static constexpr uint16_t kUdpPort = 49155; + + Core nexus; + Node &node0 = nexus.CreateNode(); + Node &node1 = nexus.CreateNode(); + Ip6::SockAddr sockAddr; + Coap::Message *message; + + Log("------------------------------------------------------------------------------------------------------"); + Log("TestBorderAgentEphemeralKey"); + + nexus.AdvanceTime(0); + + // Form the topology: + // - node0 leader acting as Border Agent, + // - node1 staying disconnected (acting as candidate) + + node0.Form(); + nexus.AdvanceTime(50 * Time::kOneSecondInMsec); + VerifyOrQuit(node0.Get().IsLeader()); + + SuccessOrQuit(node1.Get().SetPanChannel(node0.Get().GetPanChannel())); + node1.Get().SetPanId(node0.Get().GetPanId()); + node1.Get().Up(); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check Border Agent ephemeral key initial state"); + + sEphemeralKeyCallbackCalled = false; + VerifyOrQuit(!node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(!sEphemeralKeyCallbackCalled); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Set and start ephemeral key on Border Agent"); + + node0.Get().SetEphemeralKeyCallback(HandleEphemeralKeyChange, &node0); + + SuccessOrQuit(node0.Get().SetEphemeralKey(kEphemeralKey, /* aTimeout */ 0, kUdpPort)); + + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); + VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); + + SuccessOrQuit(node0.Get().AddUnsecurePort(kUdpPort)); + + nexus.AdvanceTime(0); + VerifyOrQuit(sEphemeralKeyCallbackCalled); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Establish a secure connection with BA using the ephemeral key"); + + sEphemeralKeyCallbackCalled = false; + sockAddr.SetAddress(node0.Get().GetLinkLocalAddress()); + sockAddr.SetPort(kUdpPort); + + SuccessOrQuit( + node1.Get().SetPsk(reinterpret_cast(kEphemeralKey), kEphemeralKeySize)); + + SuccessOrQuit(node1.Get().Open()); + SuccessOrQuit(node1.Get().Connect(sockAddr)); + + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + VerifyOrQuit(node1.Get().IsConnected()); + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateConnected); + VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(sEphemeralKeyCallbackCalled); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Disconnect from candidate side, check that ephemeral key use is stopped"); + + sEphemeralKeyCallbackCalled = false; + + node1.Get().Close(); + + nexus.AdvanceTime(3 * Time::kOneSecondInMsec); + + VerifyOrQuit(!node1.Get().IsConnected()); + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); + + VerifyOrQuit(!node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(sEphemeralKeyCallbackCalled); + + nexus.AdvanceTime(10 * Time::kOneSecondInMsec); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Start using ephemeral key again with short timeout (20 seconds) and establish a connection"); + + SuccessOrQuit( + node0.Get().SetEphemeralKey(kEphemeralKey, 20 * Time::kOneSecondInMsec, kUdpPort)); + + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); + VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); + + SuccessOrQuit(node1.Get().Open()); + SuccessOrQuit(node1.Get().Connect(sockAddr)); + + nexus.AdvanceTime(2 * Time::kOneSecondInMsec); + + VerifyOrQuit(node1.Get().IsConnected()); + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateConnected); + VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); + + Log(" Check the ephemeral key timeout behavior"); + + sEphemeralKeyCallbackCalled = false; + nexus.AdvanceTime(25 * Time::kOneSecondInMsec); + + VerifyOrQuit(!node1.Get().IsConnected()); + VerifyOrQuit(!node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(sEphemeralKeyCallbackCalled); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Start using ephemeral key without any candidate connecting, check timeout"); + + SuccessOrQuit(node0.Get().SetEphemeralKey(kEphemeralKey, /* aTimeout */ 0, kUdpPort)); + + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); + VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); + + Log(" Wait more than 120 seconds (default ephemeral key timeout)"); + sEphemeralKeyCallbackCalled = false; + nexus.AdvanceTime(122 * Time::kOneSecondInMsec); + + VerifyOrQuit(!node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(sEphemeralKeyCallbackCalled); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check that ephemeral key use is stopped after max failed connection attempts"); + + SuccessOrQuit(node0.Get().SetEphemeralKey(kEphemeralKey, /* aTimeout */ 0, kUdpPort)); + + VerifyOrQuit(node0.Get().GetState() == MeshCoP::BorderAgent::kStateStarted); + VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(node0.Get().GetUdpPort() == kUdpPort); + + SuccessOrQuit( + node1.Get().SetPsk(reinterpret_cast(kEphemeralKey), kEphemeralKeySize - 2)); + + for (uint8_t numAttempts = 1; numAttempts < 10; numAttempts++) + { + Log(" Attempt %u to connect with the wrong key", numAttempts); + + SuccessOrQuit(node1.Get().Connect(sockAddr)); + + nexus.AdvanceTime(3 * Time::kOneSecondInMsec); + + VerifyOrQuit(!node1.Get().IsConnected()); + VerifyOrQuit(node0.Get().GetState() != MeshCoP::BorderAgent::kStateConnected); + VerifyOrQuit(node0.Get().IsEphemeralKeyActive()); + } + + Log(" Attempt 10 (final attempt) to connect with the wrong key, check that ephemeral key use is stopped"); + + sEphemeralKeyCallbackCalled = false; + SuccessOrQuit(node1.Get().Connect(sockAddr)); + nexus.AdvanceTime(3 * Time::kOneSecondInMsec); + + VerifyOrQuit(!node1.Get().IsConnected()); + VerifyOrQuit(!node0.Get().IsEphemeralKeyActive()); + VerifyOrQuit(sEphemeralKeyCallbackCalled); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestBorderAgent(); + ot::Nexus::TestBorderAgentEphemeralKey(); + printf("All tests passed\n"); + return 0; +}