mirror of
https://github.com/espressif/openthread.git
synced 2026-08-21 01:49:52 +00:00
[mle] allow processing of "Child Update Request" while restoring child role (#12007)
This change modifies `HandleChildUpdateRequest()` to allow a detached child that is restoring its previous role to process a "Child Update Request" from its former parent. When in this state, the device will respond to the request but will not save any of the content (TLVs) from the message, as the child has not yet established trust with any device (including its former parent) and therefore cannot authenticate the freshness of the received request. This change handles the scenario where a child and its parent may be reset simultaneously. It allows the parent to first restore its link with the child through a "Child Update" exchange, which can then be followed by the child sending its own "Child Update Request" to re-establish the link. Without this change, a communication impasse could occur where the parent rejects the child's request (as the child is not yet valid), and the child ignores the parent's request (as it is not yet attached). This change prevents devices from resorting to a full re-attachment, thereby improving network resilience and recovery time. A new test is added to emulate this scenario and verify that the child restores its role correctly without performing a full attach.
This commit is contained in:
+31
-19
@@ -2181,31 +2181,40 @@ exit:
|
||||
|
||||
void Mle::HandleChildUpdateRequest(RxInfo &aRxInfo)
|
||||
{
|
||||
VerifyOrExit(IsAttached());
|
||||
|
||||
#if OPENTHREAD_FTD
|
||||
if (IsRouterOrLeader())
|
||||
{
|
||||
HandleChildUpdateRequestOnParent(aRxInfo);
|
||||
ExitNow();
|
||||
}
|
||||
else
|
||||
#endif
|
||||
|
||||
HandleChildUpdateRequestOnChild(aRxInfo);
|
||||
|
||||
exit:
|
||||
return;
|
||||
{
|
||||
HandleChildUpdateRequestOnChild(aRxInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void Mle::HandleChildUpdateRequestOnChild(RxInfo &aRxInfo)
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
Error error = kErrorNone;
|
||||
bool canTrustMessage = aRxInfo.IsNeighborStateValid();
|
||||
uint16_t sourceAddress;
|
||||
RxChallenge challenge;
|
||||
TlvList requestedTlvList;
|
||||
TlvList tlvList;
|
||||
uint8_t linkMarginOut;
|
||||
|
||||
if (!IsAttached())
|
||||
{
|
||||
// If detached and trying to restore our role as a child, we
|
||||
// allow processing of a received "Child Update Request"
|
||||
// and send a response. But since we have not yet established
|
||||
// trust with any device (including our former parent),
|
||||
// we will not save any of the content (TLVs) from the
|
||||
// message (`canTrustMessage` will be `false`).
|
||||
|
||||
VerifyOrExit(mPrevRoleRestorer.IsRestoringChildRole());
|
||||
}
|
||||
|
||||
SuccessOrExit(error = Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress));
|
||||
|
||||
Log(kMessageReceive, kTypeChildUpdateRequestAsChild, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress);
|
||||
@@ -2245,17 +2254,20 @@ void Mle::HandleChildUpdateRequestOnChild(RxInfo &aRxInfo)
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
SuccessOrExit(error = HandleLeaderData(aRxInfo));
|
||||
|
||||
switch (Tlv::Find<LinkMarginTlv>(aRxInfo.mMessage, linkMarginOut))
|
||||
if (canTrustMessage)
|
||||
{
|
||||
case kErrorNone:
|
||||
mParent.SetLinkQualityOut(LinkQualityForLinkMargin(linkMarginOut));
|
||||
break;
|
||||
case kErrorNotFound:
|
||||
break;
|
||||
default:
|
||||
ExitNow(error = kErrorParse);
|
||||
SuccessOrExit(error = HandleLeaderData(aRxInfo));
|
||||
|
||||
switch (Tlv::Find<LinkMarginTlv>(aRxInfo.mMessage, linkMarginOut))
|
||||
{
|
||||
case kErrorNone:
|
||||
mParent.SetLinkQualityOut(LinkQualityForLinkMargin(linkMarginOut));
|
||||
break;
|
||||
case kErrorNotFound:
|
||||
break;
|
||||
default:
|
||||
ExitNow(error = kErrorParse);
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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.
|
||||
|
||||
from cli import verify
|
||||
from cli import verify_within
|
||||
import cli
|
||||
import time
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Test description:
|
||||
# This test covers reset of parent, router, leader and restoring children after reset
|
||||
#
|
||||
|
||||
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
|
||||
print('-' * 120)
|
||||
print('Starting \'{}\''.format(test_name))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Creating `cli.Nodes` instances
|
||||
|
||||
speedup = 25
|
||||
cli.Node.set_time_speedup_factor(speedup)
|
||||
|
||||
leader = cli.Node()
|
||||
router = cli.Node()
|
||||
child = cli.Node()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Form topology
|
||||
#
|
||||
# leader --- router
|
||||
# |
|
||||
# child
|
||||
#
|
||||
|
||||
leader.allowlist_node(router)
|
||||
router.allowlist_node(leader)
|
||||
|
||||
child.allowlist_node(router)
|
||||
router.allowlist_node(child)
|
||||
|
||||
leader.form('reset')
|
||||
router.join(leader)
|
||||
child.join(leader, cli.JOIN_TYPE_END_DEVICE)
|
||||
|
||||
verify(leader.get_state() == 'leader')
|
||||
verify(router.get_state() == 'router')
|
||||
verify(child.get_state() == 'child')
|
||||
|
||||
# Make sure that `child` is attached to `router` as its parent.
|
||||
|
||||
router_rloc = int(router.get_rloc16(), 16)
|
||||
verify(int(child.get_parent_info()['Rloc'], 16) == router_rloc)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Test Implementation
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
del router
|
||||
router = cli.Node(index=2)
|
||||
router.interface_up()
|
||||
router.thread_start()
|
||||
|
||||
del child
|
||||
child = cli.Node(index=3)
|
||||
child.set_router_eligible('disable')
|
||||
child.interface_up()
|
||||
child.thread_start()
|
||||
|
||||
# Ensure both child and router restore their previous role.
|
||||
|
||||
|
||||
def check_router_and_child_states():
|
||||
verify(router.get_state() == 'router')
|
||||
verify(child.get_state() == 'child')
|
||||
|
||||
|
||||
verify_within(check_router_and_child_states, 10)
|
||||
|
||||
verify(int(router.get_rloc16(), 16) == router_rloc)
|
||||
verify(int(child.get_parent_info()['Rloc'], 16) == router_rloc)
|
||||
|
||||
# Validate that the child was restored using a "Child Update" exchange and not
|
||||
# through a "Parent Request/Child ID Request" sequence. We validate this by
|
||||
# checking that the "Attach Attempts" in the MLE counters remains zero.
|
||||
|
||||
mle_counter = child.get_mle_counter()
|
||||
|
||||
verify(int(cli.Node.parse_list(mle_counter)['Role Detached']) == 1)
|
||||
verify(int(cli.Node.parse_list(mle_counter)['Attach Attempts']) == 0)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
cli.Node.finalize_all_nodes()
|
||||
|
||||
print('\'{}\' passed.'.format(test_name))
|
||||
@@ -203,6 +203,7 @@ if [ "$TORANJ_CLI" = 1 ]; then
|
||||
run cli/test-035-context-id-change-addr-reg.py
|
||||
run cli/test-036-dhcp-prefix-netdata.py
|
||||
run cli/test-037-mtd-annc-join-older-timestamp.py
|
||||
run cli/test-038-simultaneous-parent-and-child-reset.py
|
||||
run cli/test-400-srp-client-server.py
|
||||
run cli/test-401-srp-server-address-cache-snoop.py
|
||||
run cli/test-500-two-brs-two-networks.py
|
||||
|
||||
Reference in New Issue
Block a user