mirror of
https://github.com/espressif/openthread.git
synced 2026-08-06 18:57:47 +00:00
[mle] allow detached MTD to attach via stale announce (#11928)
This commit updates the logic in MLE `HandleAnnounce()` for processing a received Announce message with an older (stale) timestamp. Previously, only an Announce with a newer timestamp would be considered for processing and announce attach (trying to attach using the channel and/or PANID from the received announce). Also, any device receiving a stale Announce with an older timestamp would send its own Announce back to inform the sender. This change updates the behavior regarding the processing of stale Announce messages: - A router-eligible FTD still sends an Announce back to help inform the other device. - A detached MTD will now process the Announce (wait for a short delay before trying to attach to the older Dataset). This is useful when an MTD child device has a newer Dataset but the routers it can hear are still on a previous, older Dataset. - An attached MTD now ignores the stale Announce. Since an MTD cannot become a router to help the device with the older Dataset join the new Dataset, sending an Announce back from the MTD would be pointless. This commit also introduces a test to verify this exact scenario.
This commit is contained in:
+59
-13
@@ -5775,7 +5775,16 @@ void Mle::AnnounceHandler::Stop(void)
|
||||
|
||||
void Mle::AnnounceHandler::HandleAnnounce(RxInfo &aRxInfo)
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
enum Action : uint8_t
|
||||
{
|
||||
kIgnore,
|
||||
kSendAnnouceBack,
|
||||
kAnnounceAttachAfterDelay,
|
||||
kSignalAnnounceSender,
|
||||
};
|
||||
|
||||
Error error = kErrorNone;
|
||||
Action action = kIgnore;
|
||||
ChannelTlvValue channelTlvValue;
|
||||
MeshCoP::Timestamp timestamp;
|
||||
MeshCoP::Timestamp pendingActiveTimestamp;
|
||||
@@ -5799,21 +5808,59 @@ void Mle::AnnounceHandler::HandleAnnounce(RxInfo &aRxInfo)
|
||||
timestampCompare = MeshCoP::Timestamp::Compare(timestamp, Get<MeshCoP::ActiveDatasetManager>().GetTimestamp());
|
||||
channelAndPanIdMatch = (channel == Get<Mac::Mac>().GetPanChannel()) && (panId == Get<Mac::Mac>().GetPanId());
|
||||
|
||||
if (isFromOrphan || (timestampCompare < 0))
|
||||
// Determine the action to perform.
|
||||
|
||||
if (isFromOrphan)
|
||||
{
|
||||
if (isFromOrphan)
|
||||
VerifyOrExit(!channelAndPanIdMatch);
|
||||
action = kSendAnnouceBack;
|
||||
}
|
||||
else if (timestampCompare < 0)
|
||||
{
|
||||
// On an FTD which can become a router, we send an Announce
|
||||
// back to help the sender learn and migrate to the newer
|
||||
// Dataset. On a detached MTD, we process the Announce (wait
|
||||
// for a short delay before trying to attach to the older
|
||||
// Dataset). This is useful when an MTD child device has a
|
||||
// newer Dataset but the routers it can hear are still on a
|
||||
// previous, older Dataset. On an attached MTD, we ignore the
|
||||
// stale Announce, since we cannot become a router to help the
|
||||
// older device join the new Dataset, so sending an Announce
|
||||
// back would be pointless.
|
||||
|
||||
#if OPENTHREAD_FTD
|
||||
if (Get<Mle>().IsFullThreadDevice() && Get<Mle>().IsRouterEligible())
|
||||
{
|
||||
VerifyOrExit(!channelAndPanIdMatch);
|
||||
action = kSendAnnouceBack;
|
||||
}
|
||||
|
||||
Get<Mle>().SendAnnounce(channel);
|
||||
|
||||
#if OPENTHREAD_CONFIG_MLE_SEND_UNICAST_ANNOUNCE_RESPONSE
|
||||
Get<Mle>().SendAnnounce(channel, aRxInfo.mMessageInfo.GetPeerAddr());
|
||||
else
|
||||
#endif
|
||||
{
|
||||
action = Get<Mle>().IsDetached() ? kAnnounceAttachAfterDelay : kIgnore;
|
||||
}
|
||||
}
|
||||
else if (timestampCompare > 0)
|
||||
{
|
||||
action = kAnnounceAttachAfterDelay;
|
||||
}
|
||||
else // timestampCompare is zero
|
||||
{
|
||||
action = kSignalAnnounceSender;
|
||||
}
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case kIgnore:
|
||||
break;
|
||||
|
||||
case kSendAnnouceBack:
|
||||
Get<Mle>().SendAnnounce(channel);
|
||||
#if OPENTHREAD_CONFIG_MLE_SEND_UNICAST_ANNOUNCE_RESPONSE
|
||||
Get<Mle>().SendAnnounce(channel, aRxInfo.mMessageInfo.GetPeerAddr());
|
||||
#endif
|
||||
break;
|
||||
|
||||
case kAnnounceAttachAfterDelay:
|
||||
// No action is required if device is detached, and current
|
||||
// channel and pan-id match the values from the received MLE
|
||||
// Announce message.
|
||||
@@ -5853,17 +5900,16 @@ void Mle::AnnounceHandler::HandleAnnounce(RxInfo &aRxInfo)
|
||||
mTimer.Start(kAnnounceProcessTimeout);
|
||||
|
||||
LogNote("Delay processing Announce - channel %d, panid 0x%02x", channel, panId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Timestamps are equal.
|
||||
break;
|
||||
|
||||
case kSignalAnnounceSender:
|
||||
#if OPENTHREAD_CONFIG_ANNOUNCE_SENDER_ENABLE
|
||||
// Notify `AnnounceSender` of the received Announce
|
||||
// message so it can update its state to determine
|
||||
// whether to send Announce or not.
|
||||
Get<AnnounceSender>().UpdateOnReceivedAnnounce();
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
exit:
|
||||
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
#!/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 validates that a MTD can attach to a router/parent that
|
||||
# has a previous Dataset with an older active timestamp (when it
|
||||
# received Announce message from the parent/router).
|
||||
#
|
||||
|
||||
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)
|
||||
|
||||
parent = cli.Node()
|
||||
child = cli.Node()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Form topology
|
||||
|
||||
parent.form('mtd-old-tmstmp')
|
||||
child.join(parent, cli.JOIN_TYPE_END_DEVICE)
|
||||
|
||||
verify(parent.get_state() == 'leader')
|
||||
verify(child.get_state() == 'child')
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Test Implementation
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Disable MLE on the child.
|
||||
|
||||
child.thread_stop()
|
||||
verify(child.get_state() == 'disabled')
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Update the Active Dataset on the child with a new channel and a
|
||||
# higher active timestamp.
|
||||
|
||||
child.cli('dataset init active')
|
||||
|
||||
channel = int(child.get_channel())
|
||||
channel = 11 if channel != 11 else 12
|
||||
child.cli('dataset channel', channel)
|
||||
|
||||
timestamp = int(child.cli('dataset activetimestamp')[0])
|
||||
timestamp = timestamp + 100
|
||||
child.cli('dataset activetimestamp', timestamp)
|
||||
|
||||
child.cli('dataset commit active')
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Restart MLE on the child again now with the new Dataset.
|
||||
|
||||
child.thread_start()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Verify that the child can successfully attach to the parent, despite
|
||||
# the parent being on an older Dataset.
|
||||
|
||||
|
||||
def check_child_state():
|
||||
verify(child.get_state() == 'child')
|
||||
|
||||
|
||||
verify_within(check_child_state, 10)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Verify that the parent also adopts the new Dataset from the child.
|
||||
|
||||
|
||||
def check_parent_adopts_the_new_dataset():
|
||||
verify(child.get_state() == 'child')
|
||||
verify(parent.get_state() == 'leader')
|
||||
parent.cli('dataset init active')
|
||||
verify(int(parent.cli('dataset activetimestamp')[0]) == timestamp)
|
||||
verify(int(parent.get_channel()) == channel)
|
||||
|
||||
|
||||
verify_within(check_parent_adopts_the_new_dataset, 10)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
cli.Node.finalize_all_nodes()
|
||||
|
||||
print('\'{}\' passed.'.format(test_name))
|
||||
@@ -202,6 +202,7 @@ if [ "$TORANJ_CLI" = 1 ]; then
|
||||
run cli/test-034-fed-parent-search.py
|
||||
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-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