diff --git a/src/core/thread/network_data_leader.cpp b/src/core/thread/network_data_leader.cpp index f032f4c0b1e..d6742a92399 100644 --- a/src/core/thread/network_data_leader.cpp +++ b/src/core/thread/network_data_leader.cpp @@ -187,34 +187,46 @@ Error Leader::GetContext(const Ip6::Address &aAddress, Lowpan::Context &aContext return (aContext.mPrefix.GetLength() > 0) ? kErrorNone : kErrorNotFound; } -Error Leader::GetContext(uint8_t aContextId, Lowpan::Context &aContext) const +const PrefixTlv *Leader::FindPrefixTlvForContextId(uint8_t aContextId, const ContextTlv *&aContextTlv) const { - Error error = kErrorNotFound; TlvIterator tlvIterator(GetTlvsStart(), GetTlvsEnd()); const PrefixTlv *prefixTlv; - if (aContextId == Mle::kMeshLocalPrefixContextId) - { - GetContextForMeshLocalPrefix(aContext); - ExitNow(error = kErrorNone); - } - while ((prefixTlv = tlvIterator.Iterate()) != nullptr) { const ContextTlv *contextTlv = prefixTlv->FindSubTlv(); - if ((contextTlv == nullptr) || (contextTlv->GetContextId() != aContextId)) + if ((contextTlv != nullptr) && (contextTlv->GetContextId() == aContextId)) { - continue; + aContextTlv = contextTlv; + break; } + } - prefixTlv->CopyPrefixTo(aContext.mPrefix); - aContext.mContextId = contextTlv->GetContextId(); - aContext.mCompressFlag = contextTlv->IsCompress(); - aContext.mIsValid = true; - ExitNow(error = kErrorNone); + return prefixTlv; +} + +Error Leader::GetContext(uint8_t aContextId, Lowpan::Context &aContext) const +{ + Error error = kErrorNone; + TlvIterator tlvIterator(GetTlvsStart(), GetTlvsEnd()); + const PrefixTlv *prefixTlv; + const ContextTlv *contextTlv; + + if (aContextId == Mle::kMeshLocalPrefixContextId) + { + GetContextForMeshLocalPrefix(aContext); + ExitNow(); } + prefixTlv = FindPrefixTlvForContextId(aContextId, contextTlv); + VerifyOrExit(prefixTlv != nullptr, error = kErrorNotFound); + + prefixTlv->CopyPrefixTo(aContext.mPrefix); + aContext.mContextId = contextTlv->GetContextId(); + aContext.mCompressFlag = contextTlv->IsCompress(); + aContext.mIsValid = true; + exit: return error; } @@ -299,13 +311,22 @@ Error Leader::RouteLookup(const Ip6::Address &aSource, const Ip6::Address &aDest return error; } -template int Leader::CompareRouteEntries(const EntryType &aFirst, const EntryType &aSecond) const +int Leader::CompareRouteEntries(const BorderRouterEntry &aFirst, const BorderRouterEntry &aSecond) const { - // `EntryType` can be `HasRouteEntry` or `BorderRouterEntry`. + return CompareRouteEntries(aFirst.GetPreference(), aFirst.GetRloc(), aSecond.GetPreference(), aSecond.GetRloc()); +} +int Leader::CompareRouteEntries(const HasRouteEntry &aFirst, const HasRouteEntry &aSecond) const +{ return CompareRouteEntries(aFirst.GetPreference(), aFirst.GetRloc(), aSecond.GetPreference(), aSecond.GetRloc()); } +int Leader::CompareRouteEntries(const ServerTlv &aFirst, const ServerTlv &aSecond) const +{ + return CompareRouteEntries(/* aFirstPreference */ 0, aFirst.GetServer16(), /* aSecondPreference */ 0, + aSecond.GetServer16()); +} + int Leader::CompareRouteEntries(int8_t aFirstPreference, uint16_t aFirstRloc, int8_t aSecondPreference, @@ -393,39 +414,51 @@ Error Leader::ExternalRouteLookup(uint8_t aDomainId, const Ip6::Address &aDestin return error; } -Error Leader::DefaultRouteLookup(const PrefixTlv &aPrefix, uint16_t &aRloc16) const +Error Leader::LookupRouteIn(const PrefixTlv &aPrefixTlv, EntryChecker aEntryChecker, uint16_t &aRloc16) const { + // Iterates over all `BorderRouterEntry` associated with + // `aPrefixTlv` which also match `aEntryChecker` and determine the + // best route. For example, this is used from `DefaultRouteLookup()` + // to look up best default route. + Error error = kErrorNoRoute; - TlvIterator subTlvIterator(aPrefix); + TlvIterator subTlvIterator(aPrefixTlv); const BorderRouterTlv *brTlv; - const BorderRouterEntry *route = nullptr; + const BorderRouterEntry *bestEntry = nullptr; while ((brTlv = subTlvIterator.Iterate()) != nullptr) { for (const BorderRouterEntry *entry = brTlv->GetFirstEntry(); entry <= brTlv->GetLastEntry(); entry = entry->GetNext()) { - if (!entry->IsDefaultRoute()) + if (!aEntryChecker(*entry)) { continue; } - if (route == nullptr || CompareRouteEntries(*entry, *route) > 0) + if ((bestEntry == nullptr) || CompareRouteEntries(*entry, *bestEntry) > 0) { - route = entry; + bestEntry = entry; } } } - if (route != nullptr) + if (bestEntry != nullptr) { - aRloc16 = route->GetRloc(); + aRloc16 = bestEntry->GetRloc(); error = kErrorNone; } return error; } +bool Leader::IsEntryDefaultRoute(const BorderRouterEntry &aEntry) { return aEntry.IsDefaultRoute(); } + +Error Leader::DefaultRouteLookup(const PrefixTlv &aPrefix, uint16_t &aRloc16) const +{ + return LookupRouteIn(aPrefix, IsEntryDefaultRoute, aRloc16); +} + Error Leader::SetNetworkData(uint8_t aVersion, uint8_t aStableVersion, Type aType, diff --git a/src/core/thread/network_data_leader.hpp b/src/core/thread/network_data_leader.hpp index f7ba0c13877..22566d531fb 100644 --- a/src/core/thread/network_data_leader.hpp +++ b/src/core/thread/network_data_leader.hpp @@ -423,15 +423,6 @@ class Leader : public MutableNetworkData, private NonCopyable */ void HandleNetworkDataRestoredAfterReset(void); - /** - * Scans network data for given Service ID and returns pointer to the respective TLV, if present. - * - * @param aServiceId Service ID to look for. - * @return Pointer to the Service TLV for given Service ID, or nullptr if not present. - * - */ - const ServiceTlv *FindServiceById(uint8_t aServiceId) const; - #endif // OPENTHREAD_FTD #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE @@ -453,16 +444,24 @@ class Leader : public MutableNetworkData, private NonCopyable private: using FilterIndexes = MeshCoP::SteeringData::HashBitIndexes; + typedef bool (&EntryChecker)(const BorderRouterEntry &aEntry); + const PrefixTlv *FindNextMatchingPrefixTlv(const Ip6::Address &aAddress, const PrefixTlv *aPrevTlv) const; + const PrefixTlv *FindPrefixTlvForContextId(uint8_t aContextId, const ContextTlv *&aContextTlv) const; - template int CompareRouteEntries(const EntryType &aFirst, const EntryType &aSecond) const; - int CompareRouteEntries(int8_t aFirstPreference, - uint16_t aFirstRloc, - int8_t aSecondPreference, - uint16_t aSecondRloc) const; + int CompareRouteEntries(const BorderRouterEntry &aFirst, const BorderRouterEntry &aSecond) const; + int CompareRouteEntries(const HasRouteEntry &aFirst, const HasRouteEntry &aSecond) const; + int CompareRouteEntries(const ServerTlv &aFirst, const ServerTlv &aSecond) const; + int CompareRouteEntries(int8_t aFirstPreference, + uint16_t aFirstRloc, + int8_t aSecondPreference, + uint16_t aSecondRloc) const; + + static bool IsEntryDefaultRoute(const BorderRouterEntry &aEntry); Error ExternalRouteLookup(uint8_t aDomainId, const Ip6::Address &aDestination, uint16_t &aRloc16) const; Error DefaultRouteLookup(const PrefixTlv &aPrefix, uint16_t &aRloc16) const; + Error LookupRouteIn(const PrefixTlv &aPrefixTlv, EntryChecker aEntryChecker, uint16_t &aRloc16) const; Error SteeringDataCheck(const FilterIndexes &aFilterIndexes) const; void GetContextForMeshLocalPrefix(Lowpan::Context &aContext) const; Error ReadCommissioningDataUint16SubTlv(MeshCoP::Tlv::Type aType, uint16_t &aValue) const; @@ -480,13 +479,6 @@ class Leader : public MutableNetworkData, private NonCopyable static constexpr uint8_t kMinServiceId = 0x00; static constexpr uint8_t kMaxServiceId = 0x0f; - enum AnycastType : uint8_t - { - kAnycastDhcp6Agent, - kAnycastNdAgent, - kAnycastService, - }; - class ChangedFlags { public: @@ -571,8 +563,11 @@ class Leader : public MutableNetworkData, private NonCopyable void HandleTimer(void); - Error AnycastLookup(uint8_t aServiceId, AnycastType aType, uint16_t &aRloc16) const; - void EvaluateRoutingCost(uint16_t aDest, uint8_t &aBestCost, uint16_t &aBestDest) const; + static bool IsEntryForDhcp6Agent(const BorderRouterEntry &aEntry); + static bool IsEntryForNdAgent(const BorderRouterEntry &aEntry); + + Error LookupRouteForServiceAloc(uint16_t aAloc16, uint16_t &aRloc16) const; + Error LookupRouteForAgentAloc(uint8_t aContextId, EntryChecker aEntryChecker, uint16_t &aRloc16) const; void RegisterNetworkData(uint16_t aRloc16, const NetworkData &aNetworkData); @@ -582,7 +577,8 @@ class Leader : public MutableNetworkData, private NonCopyable Error AddService(const ServiceTlv &aService, ChangedFlags &aChangedFlags); Error AddServer(const ServerTlv &aServer, ServiceTlv &aDstService, ChangedFlags &aChangedFlags); - Error AllocateServiceId(uint8_t &aServiceId) const; + Error AllocateServiceId(uint8_t &aServiceId) const; + const ServiceTlv *FindServiceById(uint8_t aServiceId) const; void RemoveContext(uint8_t aContextId); void RemoveContext(PrefixTlv &aPrefix, uint8_t aContextId); diff --git a/src/core/thread/network_data_leader_ftd.cpp b/src/core/thread/network_data_leader_ftd.cpp index 684d907cb7b..3768d385d97 100644 --- a/src/core/thread/network_data_leader_ftd.cpp +++ b/src/core/thread/network_data_leader_ftd.cpp @@ -119,6 +119,8 @@ Error Leader::AnycastLookup(uint16_t aAloc16, uint16_t &aRloc16) const { Error error = kErrorNone; + aRloc16 = Mle::kInvalidRloc16; + if (aAloc16 == Mle::kAloc16Leader) { aRloc16 = Get().GetLeaderRloc16(); @@ -127,13 +129,11 @@ Error Leader::AnycastLookup(uint16_t aAloc16, uint16_t &aRloc16) const { uint8_t contextId = static_cast(aAloc16 - Mle::kAloc16DhcpAgentStart + 1); - error = AnycastLookup(contextId, kAnycastDhcp6Agent, aRloc16); + error = LookupRouteForAgentAloc(contextId, IsEntryForDhcp6Agent, aRloc16); } else if (aAloc16 <= Mle::kAloc16ServiceEnd) { - uint8_t serviceId = static_cast(aAloc16 - Mle::kAloc16ServiceStart); - - error = AnycastLookup(serviceId, kAnycastService, aRloc16); + error = LookupRouteForServiceAloc(aAloc16, aRloc16); } else if (aAloc16 <= Mle::kAloc16CommissionerEnd) { @@ -150,111 +150,80 @@ Error Leader::AnycastLookup(uint16_t aAloc16, uint16_t &aRloc16) const { uint8_t contextId = static_cast(aAloc16 - Mle::kAloc16NeighborDiscoveryAgentStart + 1); - error = AnycastLookup(contextId, kAnycastNdAgent, aRloc16); + error = LookupRouteForAgentAloc(contextId, IsEntryForNdAgent, aRloc16); } else { - ExitNow(error = kErrorDrop); + error = kErrorDrop; } -exit: - return error; -} - -Error Leader::AnycastLookup(uint8_t aServiceId, AnycastType aType, uint16_t &aRloc16) const -{ - Iterator iterator = kIteratorInit; - uint8_t bestCost = Mle::kMaxRouteCost; - uint16_t bestDest = Mle::kInvalidRloc16; + SuccessOrExit(error); + VerifyOrExit(aRloc16 != Mle::kInvalidRloc16, error = kErrorNoRoute); - switch (aType) + if (Mle::IsChildRloc16(aRloc16)) { - case kAnycastDhcp6Agent: - case kAnycastNdAgent: - { - OnMeshPrefixConfig config; - Lowpan::Context context; + // If the selected destination is a child, we use its parent + // as the destination unless the device itself is the + // parent of the `aRloc16`. - SuccessOrExit(GetContext(aServiceId, context)); + uint16_t parentRloc16 = Mle::ParentRloc16ForRloc16(aRloc16); - while (GetNextOnMeshPrefix(iterator, config) == kErrorNone) + if (!Get().HasRloc16(parentRloc16)) { - if (config.GetPrefix() != context.mPrefix) - { - continue; - } + aRloc16 = parentRloc16; + } + } - switch (aType) - { - case kAnycastDhcp6Agent: - if (!(config.mDhcp || config.mConfigure)) - { - continue; - } - break; - case kAnycastNdAgent: - if (!config.mNdDns) - { - continue; - } - break; - default: - OT_ASSERT(false); - break; - } +exit: + return error; +} - EvaluateRoutingCost(config.mRloc16, bestCost, bestDest); - } +Error Leader::LookupRouteForServiceAloc(uint16_t aAloc16, uint16_t &aRloc16) const +{ + Error error = kErrorNoRoute; + const ServiceTlv *serviceTlv = FindServiceById(Mle::ServiceIdFromAloc(aAloc16)); - break; - } - case kAnycastService: + if (serviceTlv != nullptr) { - ServiceConfig config; + TlvIterator subTlvIterator(*serviceTlv); + const ServerTlv *bestServerTlv = nullptr; + const ServerTlv *serverTlv; - while (GetNextService(iterator, config) == kErrorNone) + while ((serverTlv = subTlvIterator.Iterate()) != nullptr) { - if (config.mServiceId != aServiceId) + if ((bestServerTlv == nullptr) || CompareRouteEntries(*serverTlv, *bestServerTlv) > 0) { - continue; + bestServerTlv = serverTlv; } - - EvaluateRoutingCost(config.mServerConfig.mRloc16, bestCost, bestDest); } - break; - } - } - - if (Mle::IsChildRloc16(bestDest)) - { - // If the selected destination is a child, we use its parent - // as the destination unless the device itself is the - // parent of the `bestDest`. - - uint16_t bestDestParent = Mle::ParentRloc16ForRloc16(bestDest); - - if (!Get().HasRloc16(bestDestParent)) + if (bestServerTlv != nullptr) { - bestDest = bestDestParent; + aRloc16 = bestServerTlv->GetServer16(); + error = kErrorNone; } } - aRloc16 = bestDest; - -exit: - return (bestDest != Mle::kInvalidRloc16) ? kErrorNone : kErrorNoRoute; + return error; } -void Leader::EvaluateRoutingCost(uint16_t aDest, uint8_t &aBestCost, uint16_t &aBestDest) const +bool Leader::IsEntryForDhcp6Agent(const BorderRouterEntry &aEntry) { return aEntry.IsDhcp() || aEntry.IsConfigure(); } + +bool Leader::IsEntryForNdAgent(const BorderRouterEntry &aEntry) { return aEntry.IsNdDns(); } + +Error Leader::LookupRouteForAgentAloc(uint8_t aContextId, EntryChecker aEntryChecker, uint16_t &aRloc16) const { - uint8_t cost = Get().GetPathCost(aDest); + Error error = kErrorNoRoute; + const PrefixTlv *prefixTlv; + const ContextTlv *contextTlv; - if ((aBestDest == Mle::kInvalidRloc16) || (cost < aBestCost)) - { - aBestDest = aDest; - aBestCost = cost; - } + prefixTlv = FindPrefixTlvForContextId(aContextId, contextTlv); + VerifyOrExit(prefixTlv != nullptr); + + error = LookupRouteIn(*prefixTlv, aEntryChecker, aRloc16); + +exit: + return error; } void Leader::RemoveBorderRouter(uint16_t aRloc16, MatchMode aMatchMode) diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index 8e02b65b3a0..308a39867ae 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -469,6 +469,9 @@ def ping(self, address, size=0, count=1, verify_success=True): def get_mle_counter(self): return self.cli('counters mle') + def get_ip_counters(self): + return Node.parse_list(self.cli('counters ip')) + def get_br_counter_unicast_outbound_packets(self): outputs = self.cli('counters br') for line in outputs: diff --git a/tests/toranj/cli/test-031-service-aloc-route-lookup.py b/tests/toranj/cli/test-031-service-aloc-route-lookup.py new file mode 100755 index 00000000000..dcc6f5e77b9 --- /dev/null +++ b/tests/toranj/cli/test-031-service-aloc-route-lookup.py @@ -0,0 +1,168 @@ +#!/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: Service ALOC destination route lookup +# +# Test topology: +# +# r1---- r2 ---- r3 ---- r4 +# | +# | +# fed1 +# +# The same service is added on `r4` and `fed1`. + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `cli.Nodes` instances + +speedup = 40 +cli.Node.set_time_speedup_factor(speedup) + +r1 = cli.Node() +r2 = cli.Node() +r3 = cli.Node() +r4 = cli.Node() +fed1 = cli.Node() + +nodes = [r1, r2, r3, r4, fed1] + +# ----------------------------------------------------------------------------------------------------------------------- +# Form topology + +r1.allowlist_node(r2) +r1.allowlist_node(fed1) + +r2.allowlist_node(r1) +r2.allowlist_node(r3) + +r3.allowlist_node(r2) +r3.allowlist_node(r4) + +r4.allowlist_node(r3) + +fed1.allowlist_node(r1) + +r1.form("srv-aloc") +r2.join(r1) +r3.join(r2) +r4.join(r3) +fed1.join(r1, cli.JOIN_TYPE_REED) + +verify(r1.get_state() == 'leader') +verify(r2.get_state() == 'router') +verify(r3.get_state() == 'router') +verify(r4.get_state() == 'router') +verify(fed1.get_state() == 'child') + +# ----------------------------------------------------------------------------------------------------------------------- +# Test Implementation + +# Wait till first router has either established a link or +# has a valid "next hop" towards all other routers. + +r1_rloc16 = int(r1.get_rloc16(), 16) + + +def check_r1_router_table(): + table = r1.get_router_table() + verify(len(table) == 4) + for entry in table: + verify(int(entry['RLOC16'], 0) == r1_rloc16 or int(entry['Link']) == 1 or int(entry['Next Hop']) != 63) + + +verify_within(check_r1_router_table, 120) + +# Add the same service on `r4` and `fed1` + +r4.cli('service add 44970 11 00') +r4.register_netdata() + +fed1.cli('service add 44970 11 00') +fed1.register_netdata() + + +def check_netdata_services(expected_num_services): + # Check that all nodes see the `expected_num_services` service + # entries in network data. + for node in nodes: + verify(len(node.get_netdata_services()) == expected_num_services) + + +verify_within(check_netdata_services, 100, 2) + +# Determine the ALOC address of the added service. + +aloc = r4.get_mesh_local_prefix().split('/')[0] + 'ff:fe00:fc10' + +# Ping ALOC address from `r3` and verify that `r4` responds. +# `r4` should be chosen due to its shorter path cost from `r3`. + +old_counters = r4.get_ip_counters() +r3.ping(aloc) +new_counters = r4.get_ip_counters() + +verify(int(new_counters['RxSuccess']) > int(old_counters['RxSuccess'])) +verify(int(new_counters['TxSuccess']) > int(old_counters['TxSuccess'])) + +# Ping ALOC address from `r1` and verify that `fed1` responds. +# `fed1` should be chosen due to its shorter path cost from `r1`. + +old_counters = fed1.get_ip_counters() +r1.ping(aloc) +new_counters = fed1.get_ip_counters() + +verify(int(new_counters['RxSuccess']) > int(old_counters['RxSuccess'])) +verify(int(new_counters['TxSuccess']) > int(old_counters['TxSuccess'])) + +# Ping ALOC address from `r2` and verify that `r4` responds. +# Both `r4` and `fed1` have the same path cost, but `r4` is +# preferred because it is acting as a router. + +old_counters = r4.get_ip_counters() +r2.ping(aloc) +new_counters = r4.get_ip_counters() + +verify(int(new_counters['RxSuccess']) > int(old_counters['RxSuccess'])) +verify(int(new_counters['TxSuccess']) > int(old_counters['TxSuccess'])) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test finished + +cli.Node.finalize_all_nodes() + +print('\'{}\' passed.'.format(test_name)) diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index ef471b00eab..495af398cea 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -195,6 +195,7 @@ if [ "$TORANJ_CLI" = 1 ]; then run cli/test-028-border-agent-ephemeral-key.py run cli/test-029-pending-dataset-key-change.py run cli/test-030-anycast-forwarding.py + run cli/./test-031-service-aloc-route-lookup.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