From 6bbb258023e231338fe304be67e2e471fb2998f9 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Thu, 5 Dec 2024 18:47:48 -0800 Subject: [PATCH] [test] add `test_dtls` to nexus test cases (#10996) This commit adds `test_dtls` using the nexus framework, which covers basic functionality of `Dtls` (`SecureTransport). The test covers: - Devices acting as DTLS server or client - Establishing a DTLS session and data transfer - `ConnectedHandler` events when the peer or local side disconnects - Failed connection attempts (e.g., using an incorrect PSK) - Setting max allowed attempts and auto-close behavior --- tests/nexus/CMakeLists.txt | 1 + tests/nexus/platform/nexus_node.hpp | 2 + tests/nexus/test_dtls.cpp | 371 ++++++++++++++++++++++++++++ 3 files changed, 374 insertions(+) create mode 100644 tests/nexus/test_dtls.cpp diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 4421d9a44..8f248fa9a 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -108,5 +108,6 @@ endmacro() #---------------------------------------------------------------------------------------------------------------------- +ot_nexus_test(dtls) ot_nexus_test(form_join) ot_nexus_test(large_network) diff --git a/tests/nexus/platform/nexus_node.hpp b/tests/nexus/platform/nexus_node.hpp index 770332a3f..81f214558 100644 --- a/tests/nexus/platform/nexus_node.hpp +++ b/tests/nexus/platform/nexus_node.hpp @@ -72,6 +72,8 @@ public: return *this; } + uint32_t GetId(void) { return GetInstance().GetId(); } + static Node &From(otInstance *aInstance) { return static_cast(*aInstance); } Node *mNext; diff --git a/tests/nexus/test_dtls.cpp b/tests/nexus/test_dtls.cpp new file mode 100644 index 000000000..f9f5f24ae --- /dev/null +++ b/tests/nexus/test_dtls.cpp @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2024, 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 { + +typedef MeshCoP::Dtls Dtls; + +static constexpr uint16_t kMaxNodes = 3; +static constexpr uint16_t kUdpPort = 1234; +static constexpr uint16_t kMessageSize = 100; +static constexpr uint16_t kMaxAttempts = 3; + +static const uint8_t kPsk[] = {0x10, 0x20, 0x03, 0x15, 0x10, 0x00, 0x60, 0x16}; + +static Dtls::ConnectEvent sDtlsEvent[kMaxNodes]; +static Array sDtlsLastReceive[kMaxNodes]; +static bool sDtlsAutoClosed[kMaxNodes]; + +const char *ConnectEventToString(Dtls::ConnectEvent aEvent) +{ + const char *str = ""; + + switch (aEvent) + { + case Dtls::kConnected: + str = "kConnected"; + break; + case Dtls::kDisconnectedPeerClosed: + str = "kDisconnectedPeerClosed"; + break; + case Dtls::kDisconnectedLocalClosed: + str = "kDisconnectedLocalClosed"; + break; + case Dtls::kDisconnectedMaxAttempts: + str = "kDisconnectedMaxAttempts"; + break; + case Dtls::kDisconnectedError: + str = "kDisconnectedError"; + break; + } + + return str; +} + +void HandleReceive(void *aContext, uint8_t *aBuf, uint16_t aLength) +{ + Node *node = static_cast(aContext); + + VerifyOrQuit(node != nullptr); + VerifyOrQuit(node->GetId() < kMaxNodes); + + Log(" node%u: HandleReceive(aLength:%u)", node->GetId(), aLength); + + sDtlsLastReceive[node->GetId()].Clear(); + + for (; aLength > 0; aLength--, aBuf++) + { + SuccessOrQuit(sDtlsLastReceive[node->GetId()].PushBack(*aBuf)); + } +} + +void HandleConnectEvent(Dtls::ConnectEvent aEvent, void *aContext) +{ + Node *node = static_cast(aContext); + + VerifyOrQuit(node != nullptr); + VerifyOrQuit(node->GetId() < kMaxNodes); + sDtlsEvent[node->GetId()] = aEvent; + + Log(" node%u: HandleConnectEvent(%s)", node->GetId(), ConnectEventToString(aEvent)); +} + +void HandleAutoClose(void *aContext) +{ + Node *node = static_cast(aContext); + + VerifyOrQuit(node != nullptr); + VerifyOrQuit(node->GetId() < kMaxNodes); + sDtlsAutoClosed[node->GetId()] = true; + + Log(" node%u: HandleAutoClose()", node->GetId()); +} + +OwnedPtr PrepareMessage(Node &aNode) +{ + Message *message = aNode.Get().Allocate(Message::kTypeOther); + uint16_t length; + + VerifyOrQuit(message != nullptr); + + length = Random::NonCrypto::GetUint16InRange(1, kMessageSize); + + for (uint16_t i = 0; i < length; i++) + { + SuccessOrQuit(message->Append(Random::NonCrypto::GetUint8())); + } + + return OwnedPtr(message); +} + +void TestDtls(void) +{ + Core nexus; + Node &node0 = nexus.CreateNode(); + Node &node1 = nexus.CreateNode(); + Node &node2 = nexus.CreateNode(); + + nexus.AdvanceTime(0); + + // Form the topology: node0 leader, with node1 & node2 as its FTD children + + node0.Form(); + nexus.AdvanceTime(50 * Time::kOneSecondInMsec); + VerifyOrQuit(node0.Get().IsLeader()); + + SuccessOrQuit(node1.Get().SetRouterEligible(false)); + node1.Join(node0); + nexus.AdvanceTime(20 * Time::kOneSecondInMsec); + VerifyOrQuit(node1.Get().IsChild()); + + SuccessOrQuit(node2.Get().SetRouterEligible(false)); + node2.Join(node0); + nexus.AdvanceTime(20 * Time::kOneSecondInMsec); + VerifyOrQuit(node2.Get().IsChild()); + + Log("------------------------------------------------------------------------------------------------------"); + + { + Dtls dtls0(node0.GetInstance(), kWithLinkSecurity); + Dtls dtls1(node1.GetInstance(), kWithLinkSecurity); + Dtls dtsl2(node2.GetInstance(), kWithLinkSecurity); + Ip6::SockAddr sockAddr; + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Start DTLS (server) on node0 bound to port %u", kUdpPort); + + SuccessOrQuit(dtls0.SetPsk(kPsk, sizeof(kPsk))); + SuccessOrQuit(dtls0.Open(HandleReceive, HandleConnectEvent, &node0)); + SuccessOrQuit(dtls0.Bind(kUdpPort)); + + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + VerifyOrQuit(dtls0.GetUdpPort() == kUdpPort); + VerifyOrQuit(!dtls0.IsConnectionActive()); + + sockAddr.SetAddress(node0.Get().GetMeshLocalRloc()); + sockAddr.SetPort(kUdpPort); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Try to establish a DTLS connection from node 1 using a wrong PSK multiple times"); + + SuccessOrQuit(dtls1.SetPsk(kPsk, sizeof(kPsk) - 1)); + SuccessOrQuit(dtls1.Open(HandleReceive, HandleConnectEvent, &node1)); + + for (uint16_t iter = 0; iter <= kMaxAttempts + 1; iter++) + { + memset(sDtlsEvent, Dtls::kConnected, sizeof(sDtlsEvent)); + + SuccessOrQuit(dtls1.Connect(sockAddr)); + nexus.AdvanceTime(3 * Time::kOneSecondInMsec); + + VerifyOrQuit(!dtls0.IsConnected()); + VerifyOrQuit(!dtls1.IsConnected()); + + VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::kDisconnectedError); + VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::kDisconnectedError); + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Establish a DTLS connection from node1 with node0 using the correct PSK"); + + dtls1.Close(); + + SuccessOrQuit(dtls1.SetPsk(kPsk, sizeof(kPsk))); + SuccessOrQuit(dtls1.Open(HandleReceive, HandleConnectEvent, &node1)); + SuccessOrQuit(dtls1.Connect(sockAddr)); + + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + VerifyOrQuit(dtls0.IsConnected()); + VerifyOrQuit(dtls1.IsConnected()); + + VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::kConnected); + VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::kConnected); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Send message (random data and length) over DTLS session from node0 to node1"); + + for (uint16_t iter = 0; iter < 20; iter++) + { + OwnedPtr msg(PrepareMessage(node0)); + + SuccessOrQuit(dtls0.Send(*msg->Clone(), msg->GetLength())); + nexus.AdvanceTime(100); + + VerifyOrQuit(sDtlsLastReceive[node1.GetId()].GetLength() == msg->GetLength()); + VerifyOrQuit(msg->CompareBytes(0, sDtlsLastReceive[node1.GetId()].GetArrayBuffer(), msg->GetLength())); + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Now send from node1 to node0"); + + for (uint16_t iter = 0; iter < 20; iter++) + { + OwnedPtr msg(PrepareMessage(node1)); + + SuccessOrQuit(dtls1.Send(*msg->Clone(), msg->GetLength())); + nexus.AdvanceTime(100); + + VerifyOrQuit(sDtlsLastReceive[node0.GetId()].GetLength() == msg->GetLength()); + VerifyOrQuit(msg->CompareBytes(0, sDtlsLastReceive[node0.GetId()].GetArrayBuffer(), msg->GetLength())); + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Disconnect from node1 - validate the disconnect events (local/peer)"); + + dtls1.Disconnect(); + + nexus.AdvanceTime(3 * Time::kOneSecondInMsec); + + VerifyOrQuit(!dtls0.IsConnected()); + VerifyOrQuit(!dtls1.IsConnected()); + + VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::kDisconnectedPeerClosed); + VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::kDisconnectedLocalClosed); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Establish a DTLS connection again"); + + SuccessOrQuit(dtls1.Connect(sockAddr)); + + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + VerifyOrQuit(dtls0.IsConnected()); + VerifyOrQuit(dtls1.IsConnected()); + + VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::kConnected); + VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::kConnected); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Try to connect from node2 - validate that it fails to connect since already connected"); + + SuccessOrQuit(dtsl2.SetPsk(kPsk, sizeof(kPsk))); + SuccessOrQuit(dtsl2.Open(HandleReceive, HandleConnectEvent, &node2)); + SuccessOrQuit(dtsl2.Connect(sockAddr)); + + nexus.AdvanceTime(20 * Time::kOneSecondInMsec); + + VerifyOrQuit(dtls0.IsConnected()); + VerifyOrQuit(dtls1.IsConnected()); + VerifyOrQuit(!dtsl2.IsConnected()); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Disconnect from node0 - validate the disconnect events"); + + dtls0.Disconnect(); + + nexus.AdvanceTime(3 * Time::kOneSecondInMsec); + + VerifyOrQuit(!dtls0.IsConnected()); + VerifyOrQuit(!dtls1.IsConnected()); + VerifyOrQuit(!dtsl2.IsConnected()); + + VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::kDisconnectedLocalClosed); + VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::kDisconnectedPeerClosed); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + dtls0.Close(); + dtls1.Close(); + dtsl2.Close(); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + Log("Start DTLS (server) on node0 bound to port %u with auto-close max attempt %u", kUdpPort, kMaxAttempts); + + memset(sDtlsAutoClosed, false, sizeof(sDtlsAutoClosed)); + + SuccessOrQuit(dtls0.SetMaxConnectionAttempts(kMaxAttempts, HandleAutoClose, &node0)); + SuccessOrQuit(dtls0.SetPsk(kPsk, sizeof(kPsk))); + SuccessOrQuit(dtls0.Open(HandleReceive, HandleConnectEvent, &node0)); + SuccessOrQuit(dtls0.Bind(kUdpPort)); + + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + VerifyOrQuit(dtls0.GetUdpPort() == kUdpPort); + VerifyOrQuit(!dtls0.IsConnectionActive()); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Using wrong PSK try to establish DTLS connection with node0 %u times", kMaxAttempts - 1); + + SuccessOrQuit(dtls1.SetPsk(kPsk, sizeof(kPsk) - 1)); + SuccessOrQuit(dtls1.Open(HandleReceive, HandleConnectEvent, &node1)); + + for (uint16_t iter = 0; iter < kMaxAttempts - 1; iter++) + { + memset(sDtlsEvent, Dtls::kConnected, sizeof(sDtlsEvent)); + + SuccessOrQuit(dtls1.Connect(sockAddr)); + nexus.AdvanceTime(3 * Time::kOneSecondInMsec); + + VerifyOrQuit(!dtls0.IsConnected()); + VerifyOrQuit(!dtls1.IsConnected()); + + VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::kDisconnectedError); + VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::kDisconnectedError); + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Using wrong PSK try one last time, validate the auto-close behavior"); + + memset(sDtlsEvent, Dtls::kConnected, sizeof(sDtlsEvent)); + + SuccessOrQuit(dtls1.Connect(sockAddr)); + nexus.AdvanceTime(3 * Time::kOneSecondInMsec); + + VerifyOrQuit(sDtlsEvent[node0.GetId()] == Dtls::kDisconnectedMaxAttempts); + VerifyOrQuit(sDtlsEvent[node1.GetId()] == Dtls::kDisconnectedError); + + VerifyOrQuit(sDtlsAutoClosed[node0.GetId()]); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + dtls0.Close(); + dtls1.Close(); + dtsl2.Close(); + } +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestDtls(); + printf("All tests passed\n"); + return 0; +}