[network-data] enhance route lookup for ALOC destination (#10509)

This commit updates `NetworkData::Leader` to enhance route lookup for
ALOC destinations. It contains the following changes:

- Adds `LookupRouteForServiceAloc()` to look up the route for a
  service ALOC. This method uses the common `CompareRouteEntries()`,
  which applies the same rules as in external route lookup.
- Adds `LookupRouteForAgentAloc()` to look up the route for DHCPv6 or
  ND agent ALOC.
- Adds `FindPrefixTlvForContextId()` to find a Prefix TLV matching a
  given Context ID (used in `GetContext()` and
  `LookupRouteForAgentAloc()`)
- Adds `LookupRouteIn()` to perform route lookup among all entries of
  a given `PrefixTlv` that match a given `EntryChecker` function
  (used for `DefaultRouteLookup()` or `LookupRouteForAgentAloc()`)
- Adds `test-031-service-aloc-route-lookup.py`.
This commit is contained in:
Abtin Keshavarzian
2024-07-16 13:06:14 -07:00
committed by GitHub
parent cf5637f3ca
commit aba7aede14
6 changed files with 306 additions and 136 deletions
+60 -27
View File
@@ -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<PrefixTlv>()) != nullptr)
{
const ContextTlv *contextTlv = prefixTlv->FindSubTlv<ContextTlv>();
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 @@ exit:
return error;
}
template <typename EntryType> 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<BorderRouterTlv>()) != 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,
+21 -25
View File
@@ -423,15 +423,6 @@ public:
*/
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 @@ public:
private:
using FilterIndexes = MeshCoP::SteeringData::HashBitIndexes;
const PrefixTlv *FindNextMatchingPrefixTlv(const Ip6::Address &aAddress, const PrefixTlv *aPrevTlv) const;
typedef bool (&EntryChecker)(const BorderRouterEntry &aEntry);
template <typename EntryType> int CompareRouteEntries(const EntryType &aFirst, const EntryType &aSecond) const;
int CompareRouteEntries(int8_t aFirstPreference,
uint16_t aFirstRloc,
int8_t aSecondPreference,
uint16_t aSecondRloc) const;
const PrefixTlv *FindNextMatchingPrefixTlv(const Ip6::Address &aAddress, const PrefixTlv *aPrevTlv) const;
const PrefixTlv *FindPrefixTlvForContextId(uint8_t aContextId, const ContextTlv *&aContextTlv) 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 @@ private:
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 @@ private:
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 @@ private:
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);
+53 -84
View File
@@ -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<Mle::Mle>().GetLeaderRloc16();
@@ -127,13 +129,11 @@ Error Leader::AnycastLookup(uint16_t aAloc16, uint16_t &aRloc16) const
{
uint8_t contextId = static_cast<uint8_t>(aAloc16 - Mle::kAloc16DhcpAgentStart + 1);
error = AnycastLookup(contextId, kAnycastDhcp6Agent, aRloc16);
error = LookupRouteForAgentAloc(contextId, IsEntryForDhcp6Agent, aRloc16);
}
else if (aAloc16 <= Mle::kAloc16ServiceEnd)
{
uint8_t serviceId = static_cast<uint8_t>(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<uint8_t>(aAloc16 - Mle::kAloc16NeighborDiscoveryAgentStart + 1);
error = AnycastLookup(contextId, kAnycastNdAgent, aRloc16);
error = LookupRouteForAgentAloc(contextId, IsEntryForNdAgent, aRloc16);
}
else
{
ExitNow(error = kErrorDrop);
error = kErrorDrop;
}
SuccessOrExit(error);
VerifyOrExit(aRloc16 != Mle::kInvalidRloc16, error = kErrorNoRoute);
if (Mle::IsChildRloc16(aRloc16))
{
// If the selected destination is a child, we use its parent
// as the destination unless the device itself is the
// parent of the `aRloc16`.
uint16_t parentRloc16 = Mle::ParentRloc16ForRloc16(aRloc16);
if (!Get<Mle::Mle>().HasRloc16(parentRloc16))
{
aRloc16 = parentRloc16;
}
}
exit:
return error;
}
Error Leader::AnycastLookup(uint8_t aServiceId, AnycastType aType, uint16_t &aRloc16) const
Error Leader::LookupRouteForServiceAloc(uint16_t aAloc16, uint16_t &aRloc16) const
{
Iterator iterator = kIteratorInit;
uint8_t bestCost = Mle::kMaxRouteCost;
uint16_t bestDest = Mle::kInvalidRloc16;
Error error = kErrorNoRoute;
const ServiceTlv *serviceTlv = FindServiceById(Mle::ServiceIdFromAloc(aAloc16));
switch (aType)
if (serviceTlv != nullptr)
{
case kAnycastDhcp6Agent:
case kAnycastNdAgent:
{
OnMeshPrefixConfig config;
Lowpan::Context context;
TlvIterator subTlvIterator(*serviceTlv);
const ServerTlv *bestServerTlv = nullptr;
const ServerTlv *serverTlv;
SuccessOrExit(GetContext(aServiceId, context));
while (GetNextOnMeshPrefix(iterator, config) == kErrorNone)
while ((serverTlv = subTlvIterator.Iterate<ServerTlv>()) != nullptr)
{
if (config.GetPrefix() != context.mPrefix)
if ((bestServerTlv == nullptr) || CompareRouteEntries(*serverTlv, *bestServerTlv) > 0)
{
continue;
bestServerTlv = serverTlv;
}
switch (aType)
{
case kAnycastDhcp6Agent:
if (!(config.mDhcp || config.mConfigure))
{
continue;
}
break;
case kAnycastNdAgent:
if (!config.mNdDns)
{
continue;
}
break;
default:
OT_ASSERT(false);
break;
}
EvaluateRoutingCost(config.mRloc16, bestCost, bestDest);
}
break;
}
case kAnycastService:
{
ServiceConfig config;
while (GetNextService(iterator, config) == kErrorNone)
if (bestServerTlv != nullptr)
{
if (config.mServiceId != aServiceId)
{
continue;
}
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<Mle::Mle>().HasRloc16(bestDestParent))
{
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
{
uint8_t cost = Get<RouterTable>().GetPathCost(aDest);
bool Leader::IsEntryForDhcp6Agent(const BorderRouterEntry &aEntry) { return aEntry.IsDhcp() || aEntry.IsConfigure(); }
if ((aBestDest == Mle::kInvalidRloc16) || (cost < aBestCost))
{
aBestDest = aDest;
aBestCost = cost;
}
bool Leader::IsEntryForNdAgent(const BorderRouterEntry &aEntry) { return aEntry.IsNdDns(); }
Error Leader::LookupRouteForAgentAloc(uint8_t aContextId, EntryChecker aEntryChecker, uint16_t &aRloc16) const
{
Error error = kErrorNoRoute;
const PrefixTlv *prefixTlv;
const ContextTlv *contextTlv;
prefixTlv = FindPrefixTlvForContextId(aContextId, contextTlv);
VerifyOrExit(prefixTlv != nullptr);
error = LookupRouteIn(*prefixTlv, aEntryChecker, aRloc16);
exit:
return error;
}
void Leader::RemoveBorderRouter(uint16_t aRloc16, MatchMode aMatchMode)
+3
View File
@@ -469,6 +469,9 @@ class Node(object):
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:
+168
View File
@@ -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))
+1
View File
@@ -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