[coap-secure] fix and test to validate max connection attempt limit (#9803)

This commit contains the following
- It fixes `SecureTransport` to ensure we do the max connection
  attempt check when transitioning from `kStateCloseNotify`.
- It adds new public `otCoapSecure` APIs to start the agent with a
  given max connection attempts, and to check the agent's state,
  whether it is connected, connecting or closed.
- It adds CLI commands under `coaps` for the new APIs.
- It adds `test-026-coaps-conn-limit.py` to validate the max
  connection attempt limit of CoAP secure agent.
This commit is contained in:
Abtin Keshavarzian
2024-01-26 15:59:49 -08:00
committed by GitHub
parent dacdc7d36f
commit f718ad7a09
12 changed files with 269 additions and 14 deletions
+40
View File
@@ -76,6 +76,15 @@ extern "C" {
*/
typedef void (*otHandleCoapSecureClientConnect)(bool aConnected, void *aContext);
/**
* Callback function pointer to notify when the CoAP secure agent is automatically stopped due to reaching the maximum
* number of connection attempts.
*
* @param[in] aContext A pointer to arbitrary context information.
*
*/
typedef void (*otCoapSecureAutoStopCallback)(void *aContext);
/**
* Starts the CoAP Secure service.
*
@@ -87,6 +96,26 @@ typedef void (*otHandleCoapSecureClientConnect)(bool aConnected, void *aContext)
*/
otError otCoapSecureStart(otInstance *aInstance, uint16_t aPort);
/**
* Starts the CoAP secure service and sets the maximum number of allowed connection attempts before stopping the
* agent automatically.
*
* @param[in] aInstance A pointer to an OpenThread instance.
* @param[in] aPort The local UDP port to bind to.
* @param[in] aMaxAttempts Maximum number of allowed connection request attempts. Zero indicates no limit.
* @param[in] aCallback Callback to notify if max number of attempts has reached and agent is stopped.
* @param[in] aContext A pointer to arbitrary context to use with @p aCallback.
*
* @retval OT_ERROR_NONE Successfully started the CoAP agent.
* @retval OT_ERROR_ALREADY Already started.
*
*/
otError otCoapSecureStartWithMaxConnAttempts(otInstance *aInstance,
uint16_t aPort,
uint16_t aMaxAttempts,
otCoapSecureAutoStopCallback aCallback,
void *aContext);
/**
* Stops the CoAP Secure server.
*
@@ -230,6 +259,17 @@ bool otCoapSecureIsConnected(otInstance *aInstance);
*/
bool otCoapSecureIsConnectionActive(otInstance *aInstance);
/**
* Indicates whether or not the DTLS session is closed.
*
* @param[in] aInstance A pointer to an OpenThread instance.
*
* @retval TRUE The DTLS session is closed.
* @retval FALSE The DTLS session is not closed.
*
*/
bool otCoapSecureIsClosed(otInstance *aInstance);
/**
* Sends a CoAP request block-wise over secure DTLS connection.
*
+1 -1
View File
@@ -53,7 +53,7 @@ extern "C" {
* @note This number versions both OpenThread platform and user APIs.
*
*/
#define OPENTHREAD_API_VERSION (391)
#define OPENTHREAD_API_VERSION (392)
/**
* @addtogroup api-instance
+36 -1
View File
@@ -87,6 +87,9 @@ coaps response sent
- [delete](#delete-uri-path-type-payload)
- [disconnect](#disconnect)
- [get](#get-uri-path-type)
- [isclosed](#isclosed)
- [isconnactive](#isconnactive)
- [isconnected](#isconnected)
- [post](#post-uri-path-type-payload)
- [psk](#psk-psk-pskid)
- [put](#put-uri-path-type-payload)
@@ -102,11 +105,13 @@ coaps response sent
```bash
> coaps help
help
connect
delete
disconnect
get
isclosed
isconnactive
isconnected
post
psk
put
@@ -251,6 +256,36 @@ Stops the application coaps service.
Done
```
### isconnected
Indicates whether or not the CoAP secure service is connected.
```bash
> coaps isconnected
yes
Done
```
### isconnactive
Indicates whether or not the CoAP secure service connection is active (already connected or establishing a connection).
```bash
> coaps isconnactive
yes
Done
```
### isclosed
Indicates whether or not the CoAP secure service is closed.
```bash
> coaps isclosed
no
Done
```
### x509
Set DTLS ciphersuite to `TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8`.
+40 -6
View File
@@ -149,8 +149,9 @@ exit:
template <> otError CoapSecure::Process<Cmd("start")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
bool verifyPeerCert = true;
otError error = OT_ERROR_NONE;
bool verifyPeerCert = true;
uint16_t maxConnAttempts = 0;
if (!aArgs[0].IsEmpty())
{
@@ -158,9 +159,13 @@ template <> otError CoapSecure::Process<Cmd("start")>(Arg aArgs[])
{
verifyPeerCert = false;
}
else if (aArgs[0] == "true")
{
verifyPeerCert = true;
}
else
{
VerifyOrExit(aArgs[0] == "true", error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = aArgs[0].ParseAsUint16(maxConnAttempts));
}
}
@@ -171,7 +176,8 @@ template <> otError CoapSecure::Process<Cmd("start")>(Arg aArgs[])
otCoapSecureSetDefaultHandler(GetInstancePtr(), &CoapSecure::DefaultHandler, this);
#endif
error = otCoapSecureStart(GetInstancePtr(), OT_DEFAULT_COAP_SECURE_PORT);
error = otCoapSecureStartWithMaxConnAttempts(GetInstancePtr(), OT_DEFAULT_COAP_SECURE_PORT, maxConnAttempts,
nullptr, nullptr);
exit:
return error;
@@ -200,6 +206,32 @@ template <> otError CoapSecure::Process<Cmd("stop")>(Arg aArgs[])
return OT_ERROR_NONE;
}
template <> otError CoapSecure::Process<Cmd("isclosed")>(Arg aArgs[])
{
return ProcessIsRequest(aArgs, otCoapSecureIsClosed);
}
template <> otError CoapSecure::Process<Cmd("isconnected")>(Arg aArgs[])
{
return ProcessIsRequest(aArgs, otCoapSecureIsConnected);
}
template <> otError CoapSecure::Process<Cmd("isconnactive")>(Arg aArgs[])
{
return ProcessIsRequest(aArgs, otCoapSecureIsConnectionActive);
}
otError CoapSecure::ProcessIsRequest(Arg aArgs[], bool (*IsChecker)(otInstance *))
{
otError error = OT_ERROR_NONE;
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
OutputLine("%s", IsChecker(GetInstancePtr()) ? "yes" : "no");
exit:
return error;
}
template <> otError CoapSecure::Process<Cmd("get")>(Arg aArgs[]) { return ProcessRequest(aArgs, OT_COAP_CODE_GET); }
template <> otError CoapSecure::Process<Cmd("post")>(Arg aArgs[]) { return ProcessRequest(aArgs, OT_COAP_CODE_POST); }
@@ -442,11 +474,13 @@ otError CoapSecure::Process(Arg aArgs[])
}
static constexpr Command kCommands[] = {
CmdEntry("connect"), CmdEntry("delete"), CmdEntry("disconnect"), CmdEntry("get"), CmdEntry("post"),
CmdEntry("connect"), CmdEntry("delete"), CmdEntry("disconnect"), CmdEntry("get"),
CmdEntry("isclosed"), CmdEntry("isconnactive"), CmdEntry("isconnected"), CmdEntry("post"),
#ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED
CmdEntry("psk"),
#endif
CmdEntry("put"), CmdEntry("resource"), CmdEntry("set"), CmdEntry("start"), CmdEntry("stop"),
CmdEntry("put"), CmdEntry("resource"), CmdEntry("set"), CmdEntry("start"),
CmdEntry("stop"),
#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
CmdEntry("x509"),
#endif
+1
View File
@@ -103,6 +103,7 @@ private:
template <CommandId kCommandId> otError Process(Arg aArgs[]);
otError ProcessRequest(Arg aArgs[], otCoapCode aCoapCode);
otError ProcessIsRequest(Arg aArgs[], bool (*IsChecker)(otInstance *));
void Stop(void);
+11
View File
@@ -50,6 +50,15 @@ otError otCoapSecureStart(otInstance *aInstance, uint16_t aPort)
return AsCoreType(aInstance).GetApplicationCoapSecure().Start(aPort);
}
otError otCoapSecureStartWithMaxConnAttempts(otInstance *aInstance,
uint16_t aPort,
uint16_t aMaxAttempts,
otCoapSecureAutoStopCallback aCallback,
void *aContext)
{
return AsCoreType(aInstance).GetApplicationCoapSecure().Start(aPort, aMaxAttempts, aCallback, aContext);
}
#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
void otCoapSecureSetCertificate(otInstance *aInstance,
const uint8_t *aX509Cert,
@@ -122,6 +131,8 @@ bool otCoapSecureIsConnectionActive(otInstance *aInstance)
return AsCoreType(aInstance).GetApplicationCoapSecure().IsConnectionActive();
}
bool otCoapSecureIsClosed(otInstance *aInstance) { return AsCoreType(aInstance).GetApplicationCoapSecure().IsClosed(); }
void otCoapSecureStop(otInstance *aInstance) { AsCoreType(aInstance).GetApplicationCoapSecure().Stop(); }
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+10 -3
View File
@@ -65,10 +65,8 @@ public:
* Callback to notify when the agent is automatically stopped due to reaching the maximum number of connection
* attempts.
*
* @param[in] aContext A pointer to arbitrary context information.
*
*/
typedef void (*AutoStopCallback)(void *aContext);
typedef otCoapSecureAutoStopCallback AutoStopCallback;
/**
* Initializes the object.
@@ -165,6 +163,15 @@ public:
*/
bool IsConnected(void) const { return mDtls.IsConnected(); }
/**
* Indicates whether or not the DTLS session is closed.
*
* @retval TRUE The DTLS session is closed
* @retval FALSE The DTLS session is not closed.
*
*/
bool IsClosed(void) const { return mDtls.IsClosed(); }
/**
* Stops the DTLS connection.
*
+12 -3
View File
@@ -1053,9 +1053,18 @@ void SecureTransport::HandleTimer(void)
}
else if (IsStateCloseNotify())
{
SetState(kStateOpen);
mTimer.Stop();
mConnectedCallback.InvokeIfSet(false);
if ((mMaxConnectionAttempts > 0) && (mRemainingConnectionAttempts == 0))
{
Close();
mConnectedCallback.InvokeIfSet(false);
mAutoCloseCallback.InvokeIfSet();
}
else
{
SetState(kStateOpen);
mTimer.Stop();
mConnectedCallback.InvokeIfSet(false);
}
}
}
+9
View File
@@ -234,6 +234,15 @@ public:
*/
bool IsConnected(void) const { return mState == kStateConnected; }
/**
* Indicates whether or not the session is closed.
*
* @retval TRUE The session is closed.
* @retval FALSE The session is not closed.
*
*/
bool IsClosed(void) const { return mState == kStateClosed; }
/**
* Disconnects the session.
*
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
#
# 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.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description:
#
# Validate CoaP secure agent related to the maximum number of allowed connection attempts before the socket is a
# automatically closed and the agent is stopped.
#
#
# Network topology
#
# r1 ---- r2
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 40
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.form('coaps-conn-lmt')
r2.join(r1)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
r1.cli('coaps psk RIGHT 1234')
# Set max connection attempts to 3
r1.cli('coaps start 3')
r1_mleid = r1.get_mleid_ip_addr()
r2.cli('coaps psk WRONG 1234')
r2.cli('coaps start')
# First connection attempt
r2.cli('coaps connect', r1_mleid)
time.sleep(0.1)
verify(r1.cli('coaps isconnactive')[-1] == 'no')
verify(r1.cli('coaps isclosed')[-1] == 'no')
# Second connection attempt
r2.cli('coaps connect', r1_mleid)
time.sleep(0.1)
verify(r1.cli('coaps isconnactive')[-1] == 'no')
verify(r1.cli('coaps isclosed')[-1] == 'no')
# Third connection attempt
r2.cli('coaps connect', r1_mleid)
time.sleep(0.1)
verify(r1.cli('coaps isconnactive')[-1] == 'no')
verify(r1.cli('coaps isclosed')[-1] == 'yes')
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
@@ -45,6 +45,10 @@
#endif
#define OPENTHREAD_CONFIG_COAP_API_ENABLE 1
#define OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE 1
#define OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE 0
#define OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE 1
+1
View File
@@ -190,6 +190,7 @@ if [ "$TORANJ_CLI" = 1 ]; then
run cli/test-023-mesh-diag.py
run cli/test-024-mle-adv-imax-change.py
run cli/test-025-mesh-local-prefix-change.py
run cli/test-026-coaps-conn-limit.py
run cli/test-400-srp-client-server.py
run cli/test-601-channel-manager-channel-change.py
# Skip the "channel-select" test on a TREL only radio link, since it