[mle] enhance MTD child IPv6 address registration (#10743)

This commit improves how MTD children register their IPv6 addresses
with their parent. The `Slaac` class now tracks the Lowpan Context
ID (from Network Data) for each SLAAC address. If the Context ID
associated with an existing SLAAC address changes (due to Network
Data updates), the `Slaac` module notifies the `Mle` to schedule
a "Child Update Request" transmission (if the device is an MTD
child). This ensures that the MTD child re-registers its addresses,
resolving any previous registration failures caused by incorrect or
outdated context ID compression.

This commit also adds `test-035-context-id-change-addr-reg.py`
to validate the newly added behavior.
This commit is contained in:
Abtin Keshavarzian
2024-09-25 10:28:53 -07:00
committed by GitHub
parent 3a57996016
commit 3913e0de61
7 changed files with 284 additions and 13 deletions
+20 -13
View File
@@ -138,8 +138,11 @@ exit:
void Mle::ScheduleChildUpdateRequest(void)
{
mChildUpdateRequestState = kChildUpdateRequestPending;
ScheduleMessageTransmissionTimer();
if (mChildUpdateRequestState != kChildUpdateRequestPending)
{
mChildUpdateRequestState = kChildUpdateRequestPending;
ScheduleMessageTransmissionTimer();
}
}
Error Mle::Disable(void)
@@ -1055,13 +1058,21 @@ void Mle::InitNeighbor(Neighbor &aNeighbor, const RxInfo &aRxInfo)
aNeighbor.SetLastHeard(TimerMilli::GetNow());
}
void Mle::ScheduleChildUpdateRequestIfMtdChild(void)
{
if (IsChild() && !IsFullThreadDevice())
{
ScheduleChildUpdateRequest();
}
}
void Mle::HandleNotifierEvents(Events aEvents)
{
VerifyOrExit(!IsDisabled());
if (aEvents.Contains(kEventThreadRoleChanged))
{
if (IsChild() && !IsFullThreadDevice() && mAddressRegistrationMode == kAppendMeshLocalOnly)
if (mAddressRegistrationMode == kAppendMeshLocalOnly)
{
// If only mesh-local address was registered in the "Child
// ID Request" message, after device is attached, trigger a
@@ -1069,7 +1080,7 @@ void Mle::HandleNotifierEvents(Events aEvents)
// addresses.
mAddressRegistrationMode = kAppendAllAddresses;
ScheduleChildUpdateRequest();
ScheduleChildUpdateRequestIfMtdChild();
}
}
@@ -1083,10 +1094,7 @@ void Mle::HandleNotifierEvents(Events aEvents)
Get<Notifier>().Signal(kEventThreadMeshLocalAddrChanged);
}
if (IsChild() && !IsFullThreadDevice())
{
ScheduleChildUpdateRequest();
}
ScheduleChildUpdateRequestIfMtdChild();
}
if (aEvents.ContainsAny(kEventIp6MulticastSubscribed | kEventIp6MulticastUnsubscribed))
@@ -1097,15 +1105,14 @@ void Mle::HandleNotifierEvents(Events aEvents)
// parent of 1.2 or higher version as it could depend on its
// parent to perform Multicast Listener Report.
if (IsChild() && !IsFullThreadDevice() &&
(!IsRxOnWhenIdle()
if (!IsRxOnWhenIdle()
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
|| !GetParent().IsThreadVersion1p1()
|| !GetParent().IsThreadVersion1p1()
#endif
))
)
{
ScheduleChildUpdateRequest();
ScheduleChildUpdateRequestIfMtdChild();
}
}
+9
View File
@@ -667,6 +667,15 @@ public:
return (&aAddress == &mLinkLocalAllThreadNodes) || (&aAddress == &mRealmLocalAllThreadNodes);
}
/**
* Schedules a "Child Update Request" transmission if the device is an MTD child.
*
* For example, the `Slaac` class, which manages SLAAC addresses, calls this method to notify `Mle` that an
* existing SLAAC address's Context ID has changed. This can occur due to Network Data updates where the same
* on-mesh prefix receives a new Context ID.
*/
void ScheduleChildUpdateRequestIfMtdChild(void);
#if OPENTHREAD_CONFIG_DYNAMIC_STORE_FRAME_AHEAD_COUNTER_ENABLE
/**
* Sets the store frame counter ahead.
+30
View File
@@ -196,6 +196,15 @@ void Slaac::RemoveOrDeprecateAddresses(void)
{
RemoveAddress(slaacAddr);
}
if (UpdateContextIdFor(slaacAddr))
{
// If the Context ID of an existing address changes,
// notify MLE so an MTD child can re-register its
// addresses with the parent.
Get<Mle::Mle>().ScheduleChildUpdateRequestIfMtdChild();
}
}
else if (!slaacAddr.IsDeprecating())
{
@@ -341,6 +350,9 @@ void Slaac::AddAddressFor(const NetworkData::OnMeshPrefixConfig &aConfig)
IgnoreError(GenerateIid(*newAddress, dadCounter));
newAddress->SetContextId(SlaacAddress::kInvalidContextId);
UpdateContextIdFor(*newAddress);
LogAddress(kAdding, *newAddress);
Get<ThreadNetif>().AddUnicastAddress(*newAddress);
@@ -349,6 +361,24 @@ exit:
return;
}
bool Slaac::UpdateContextIdFor(SlaacAddress &aSlaacAddress)
{
bool didChange = false;
Lowpan::Context context;
if (Get<NetworkData::Leader>().GetContext(aSlaacAddress.GetAddress(), context) != kErrorNone)
{
context.mContextId = SlaacAddress::kInvalidContextId;
}
VerifyOrExit(context.mContextId != aSlaacAddress.GetContextId());
aSlaacAddress.SetContextId(context.mContextId);
didChange = true;
exit:
return didChange;
}
void Slaac::HandleTimer(void)
{
NextFireTime nextTime;
+6
View File
@@ -168,8 +168,12 @@ private:
class SlaacAddress : public Ip6::Netif::UnicastAddress
{
public:
static constexpr uint8_t kInvalidContextId = 0;
bool IsInUse(void) const { return mValid; }
void MarkAsNotInUse(void) { mValid = false; }
uint8_t GetContextId(void) const { return mContextId; }
void SetContextId(uint8_t aContextId) { mContextId = aContextId; }
uint8_t GetDomainId(void) const { return mDomainId; }
void SetDomainId(uint8_t aDomainId) { mDomainId = aDomainId; }
bool IsDeprecating(void) const { return (mExpirationTime.GetValue() != kNotDeprecated); };
@@ -188,6 +192,7 @@ private:
private:
static constexpr uint32_t kNotDeprecated = 0; // Special `mExpirationTime` value to indicate not deprecated.
uint8_t mContextId;
uint8_t mDomainId;
TimeMilli mExpirationTime;
};
@@ -200,6 +205,7 @@ private:
void DeprecateAddress(SlaacAddress &aAddress);
void RemoveAddress(SlaacAddress &aAddress);
void AddAddressFor(const NetworkData::OnMeshPrefixConfig &aConfig);
bool UpdateContextIdFor(SlaacAddress &aSlaacAddress);
void HandleTimer(void);
void GetIidSecretKey(IidSecretKey &aKey) const;
void HandleNotifierEvents(Events aEvents);
+9
View File
@@ -351,6 +351,12 @@ class Node(object):
def set_pollperiod(self, period):
self._cli_no_output('pollperiod', period)
def get_child_timeout(self):
return self._cli_single_output('childtimeout')
def set_child_timeout(self, timeout):
self._cli_no_output('childtimeout', timeout)
def get_partition_id(self):
return self._cli_single_output('partitionid')
@@ -368,6 +374,9 @@ class Node(object):
def get_child_table(self):
return Node.parse_table(self.cli('child table'))
def get_child_ip(self):
return self.cli('childip')
def get_neighbor_table(self):
return Node.parse_table(self.cli('neighbor table'))
+209
View File
@@ -0,0 +1,209 @@
#!/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:
#
# This test validates the behavior of MTD children regarding the
# registration of their IPv6 addresses with their parent.
# Specifically, it covers the scenario where SLAAC-based addresses
# remain unchanged, but their assigned LoWPAN Context ID for the
# corresponding on-mesh prefix in the Network Data changes. In this
# case, the MTD child should schedule an MLE Child Update Request
# exchange with its parent to re-register the addresses. This ensures
# that any earlier registration failures due to incorrect context ID
# compression are resolved.
#
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()
sed = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
leader.form('cid-chng')
verify(leader.get_state() == 'leader')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
# Set the "context ID reuse delay" to a short interval of 3 seconds.
leader.set_context_reuse_delay(3)
verify(int(leader.get_context_reuse_delay()) == 3)
# Add two on-link prefixes on `leader`.
leader.add_prefix('fd00:1::/64', 'poas')
leader.add_prefix('fd00:2::/64', 'poas')
leader.register_netdata()
# Check that context ID 2 is assigned to prefix `fd00:2::/64`.
time.sleep(0.5 / speedup)
netdata = leader.get_netdata()
contexts = netdata['contexts']
verify(len(contexts) == 2)
verify(any([context.startswith('fd00:1:0:0::/64 1 c') for context in contexts]))
verify(any([context.startswith('fd00:2:0:0::/64 2 c') for context in contexts]))
# Remove the first prefix.
leader.remove_prefix('fd00:1::/64')
leader.register_netdata()
# Wait longer than Context ID reuse delay to ensure that context ID
# associated with the removed prefix is aged and removed.
# Wait longer than the Context ID reuse delay to ensure the removed
# prefix's associated context ID is aged out and removed.
time.sleep(3.5 / speedup)
netdata = leader.get_netdata()
contexts = netdata['contexts']
verify(len(contexts) == 1)
verify(any([context.startswith('fd00:2:0:0::/64 2 c') for context in contexts]))
# Have `sed` attach as a child of `leader`.
sed.set_child_timeout(10)
sed.set_pollperiod(1000)
sed.join(leader, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
verify(sed.get_state() == 'child')
verify(int(sed.get_child_timeout()) == 10)
# Check the Network Data on `sed`.
netdata = sed.get_netdata()
contexts = netdata['contexts']
verify(len(contexts) == 1)
verify(any([context.startswith('fd00:2:0:0::/64 2 c') for context in contexts]))
# Find the `sed` address associated with on-mesh prefix `fd00:2::`.
sed_ip_addrs = sed.get_ip_addrs()
for ip_addr in sed_ip_addrs:
if ip_addr.startswith('fd00:2:0:0:'):
sed_addr = ip_addr
break
else:
verify(False)
# Check the parent's child table, and that `sed` child has registered its
# IPv6 addresses with the parent.
verify(len(leader.get_child_table()) == 1)
ip_addrs = leader.get_child_ip()
verify(len(ip_addrs) == 2)
# Stop MLE operation on `sed`
sed.thread_stop()
verify(sed.get_state() == 'disabled')
# Remove the `fd00::2` prefix and wait for the child entry
# on parent to expire. Child timeout is set to 10 seconds.
leader.remove_prefix('fd00:2::/64')
leader.register_netdata()
time.sleep(10.5 / speedup)
# Validate that the Context ID associated with `fd00::2` is expired
# and removed.
netdata = leader.get_netdata()
contexts = netdata['contexts']
verify(len(contexts) == 0)
# Re-add the `fd00::2` prefix and check that it gets a different
# Context ID.
leader.add_prefix('fd00:2::/64', 'poas')
leader.register_netdata()
time.sleep(0.5 / speedup)
netdata = leader.get_netdata()
contexts = netdata['contexts']
verify(len(contexts) == 1)
verify(any([context.startswith('fd00:2:0:0::/64 1 c') for context in contexts]))
# Make sure that child is timed out and removed on parent.
verify(leader.get_child_table() == [])
# Re-enable MLE operation on `sed` for it to attach back.
sed.thread_start()
time.sleep(5.0 / speedup)
verify(sed.get_state() == 'child')
# Check the `sed` IPv6 addresses and that `sed` still has the same
# SLAAC address based on the `fd00:2::` prefix
sed_ip_addrs = sed.get_ip_addrs()
verify(any([ip_addr == sed_addr for ip_addr in sed_ip_addrs]))
# Validate that all `sed` IPv6 addresses are successfully
# registered on parent.
verify(len(leader.get_child_table()) == 1)
ip_addrs = leader.get_child_ip()
verify(len(ip_addrs) == 2)
verify(any([ip_addr.endswith(sed_addr) for ip_addr in ip_addrs]))
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
+1
View File
@@ -197,6 +197,7 @@ if [ "$TORANJ_CLI" = 1 ]; then
run cli/test-030-anycast-forwarding.py
run cli/test-031-service-aloc-route-lookup.py
run cli/test-032-leader-take-over.py
run cli/test-035-context-id-change-addr-reg.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