mirror of
https://github.com/espressif/openthread.git
synced 2026-09-29 20:37:40 +00:00
[router-table] new API to get next hop and cost (#8653)
This commit updates `RouterTable` to provide a common method to determine both the next hop and the path cost towards a given destination. This helps us combine and reuse similar code. This commit also adds `otThreadGetNextHopAndPathCost()` as a new public OT API to get the next hop and path cost. It also adds a related CLI command `nexthop`. A new test `test-018-next-hop-and-path-cost.py` is added which validates the next hop and path cost calculation on different network topologies and from different nodes (FEDs, routers).
This commit is contained in:
@@ -53,7 +53,7 @@ extern "C" {
|
||||
* @note This number versions both OpenThread platform and user APIs.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_API_VERSION (275)
|
||||
#define OPENTHREAD_API_VERSION (276)
|
||||
|
||||
/**
|
||||
* @addtogroup api-instance
|
||||
|
||||
@@ -787,6 +787,24 @@ void otThreadGetRouterIdRange(otInstance *aInstance, uint8_t *aMinRouterId, uint
|
||||
*
|
||||
*/
|
||||
otError otThreadSetRouterIdRange(otInstance *aInstance, uint8_t aMinRouterId, uint8_t aMaxRouterId);
|
||||
|
||||
/**
|
||||
* This function gets the next hop and path cost towards a given RLOC16 destination.
|
||||
*
|
||||
* This function can be used with either @p aNextHopRloc16 or @p aPathCost being NULL indicating caller does not want
|
||||
* to get the value.
|
||||
*
|
||||
* @param[in] aInstance A pointer to an OpenThread instance.
|
||||
* @param[in] aDesRloct16 The RLOC16 of destination.
|
||||
* @param[out] aNextHopRloc16 A pointer to return RLOC16 of next hop, 0xfffe if no next hop.
|
||||
* @param[out] aPathCost A pointer to return path cost towards destination.
|
||||
*
|
||||
*/
|
||||
void otThreadGetNextHopAndPathCost(otInstance *aInstance,
|
||||
uint16_t aDestRloc16,
|
||||
uint16_t *aNextHopRloc16,
|
||||
uint8_t *aPathCost);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
|
||||
@@ -5301,6 +5301,37 @@ exit:
|
||||
}
|
||||
#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
||||
|
||||
#if OPENTHREAD_FTD
|
||||
/**
|
||||
* @cli nexthop
|
||||
* @code
|
||||
* nexthop 0xc000
|
||||
* 0xc000 cost:0
|
||||
* Done
|
||||
* nexthop 0x8001
|
||||
* 0x2000 cost:3
|
||||
* Done
|
||||
* @endcode
|
||||
* @cparam nexthop @ca{rloc16}
|
||||
* @par api_copy
|
||||
* #otThreadGetNextHopAndPathCost
|
||||
*/
|
||||
template <> otError Interpreter::Process<Cmd("nexthop")>(Arg aArgs[])
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
uint16_t destRloc16;
|
||||
uint16_t nextHopRloc16;
|
||||
uint8_t pathCost;
|
||||
|
||||
SuccessOrExit(error = aArgs[0].ParseAsUint16(destRloc16));
|
||||
otThreadGetNextHopAndPathCost(GetInstancePtr(), destRloc16, &nextHopRloc16, &pathCost);
|
||||
OutputLine("0x%04x cost:%u", nextHopRloc16, pathCost);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
#endif // OPENTHREAD_FTD
|
||||
|
||||
template <> otError Interpreter::Process<Cmd("panid")>(Arg aArgs[])
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
@@ -7401,6 +7432,9 @@ otError Interpreter::ProcessCommand(Arg aArgs[])
|
||||
CmdEntry("networkname"),
|
||||
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
||||
CmdEntry("networktime"),
|
||||
#endif
|
||||
#if OPENTHREAD_FTD
|
||||
CmdEntry("nexthop"),
|
||||
#endif
|
||||
CmdEntry("panid"),
|
||||
CmdEntry("parent"),
|
||||
|
||||
@@ -381,4 +381,17 @@ otError otThreadSetRouterIdRange(otInstance *aInstance, uint8_t aMinRouterId, ui
|
||||
}
|
||||
#endif
|
||||
|
||||
void otThreadGetNextHopAndPathCost(otInstance *aInstance,
|
||||
uint16_t aDestRloc16,
|
||||
uint16_t *aNextHopRloc16,
|
||||
uint8_t *aPathCost)
|
||||
{
|
||||
uint8_t pathcost;
|
||||
uint16_t nextHopRloc16;
|
||||
|
||||
AsCoreType(aInstance).Get<RouterTable>().GetNextHopAndPathCost(
|
||||
aDestRloc16, (aNextHopRloc16 != nullptr) ? *aNextHopRloc16 : nextHopRloc16,
|
||||
(aPathCost != nullptr) ? *aPathCost : pathcost);
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_FTD
|
||||
|
||||
@@ -374,18 +374,36 @@ exit:
|
||||
|
||||
uint8_t RouterTable::GetPathCost(uint16_t aDestRloc16) const
|
||||
{
|
||||
uint8_t cost = Mle::kMaxRouteCost;
|
||||
uint8_t pathCost;
|
||||
uint16_t nextHopRloc16;
|
||||
|
||||
GetNextHopAndPathCost(aDestRloc16, nextHopRloc16, pathCost);
|
||||
|
||||
return pathCost;
|
||||
}
|
||||
|
||||
uint8_t RouterTable::GetPathCostToLeader(void) const
|
||||
{
|
||||
return GetPathCost(Mle::Rloc16FromRouterId(Get<Mle::Mle>().GetLeaderId()));
|
||||
}
|
||||
|
||||
void RouterTable::GetNextHopAndPathCost(uint16_t aDestRloc16, uint16_t &aNextHopRloc16, uint8_t &aPathCost) const
|
||||
{
|
||||
uint8_t destRouterId;
|
||||
const Router *router;
|
||||
const Router *nextHop;
|
||||
|
||||
aPathCost = Mle::kMaxRouteCost;
|
||||
aNextHopRloc16 = Mle::kInvalidRloc16;
|
||||
|
||||
VerifyOrExit(Get<Mle::Mle>().IsAttached());
|
||||
|
||||
if (aDestRloc16 == Get<Mle::Mle>().GetRloc16())
|
||||
{
|
||||
// Destination is this device, return cost as zero.
|
||||
// This is also valid when device is a child.
|
||||
ExitNow(cost = 0);
|
||||
aPathCost = 0;
|
||||
aNextHopRloc16 = aDestRloc16;
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
destRouterId = Mle::RouterIdFromRloc16(aDestRloc16);
|
||||
@@ -397,6 +415,11 @@ uint8_t RouterTable::GetPathCost(uint16_t aDestRloc16) const
|
||||
{
|
||||
const Router &parent = Get<Mle::Mle>().GetParent();
|
||||
|
||||
if (parent.IsStateValid())
|
||||
{
|
||||
aNextHopRloc16 = parent.GetRloc16();
|
||||
}
|
||||
|
||||
// If destination is our parent or another child of our
|
||||
// parent, we use the link cost to our parent. Otherwise we
|
||||
// check if we have a next hop towards the destination and
|
||||
@@ -404,11 +427,11 @@ uint8_t RouterTable::GetPathCost(uint16_t aDestRloc16) const
|
||||
|
||||
VerifyOrExit((destRouterId == parent.GetRouterId()) || (nextHop != nullptr));
|
||||
|
||||
cost = CostForLinkQuality(parent.GetLinkQualityIn());
|
||||
aPathCost = CostForLinkQuality(parent.GetLinkQualityIn());
|
||||
|
||||
if (destRouterId != parent.GetRouterId())
|
||||
{
|
||||
cost += router->GetCost();
|
||||
aPathCost += router->GetCost();
|
||||
}
|
||||
|
||||
// The case where destination itself is a child is handled at
|
||||
@@ -420,21 +443,35 @@ uint8_t RouterTable::GetPathCost(uint16_t aDestRloc16) const
|
||||
{
|
||||
// Destination is a one of our children.
|
||||
|
||||
const Child *child = Get<ChildTable>().FindChild(aDestRloc16, Child::kInStateValid);
|
||||
const Child *child = Get<ChildTable>().FindChild(aDestRloc16, Child::kInStateAnyExceptInvalid);
|
||||
|
||||
VerifyOrExit(child != nullptr);
|
||||
ExitNow(cost = CostForLinkQuality(child->GetLinkQualityIn()));
|
||||
aNextHopRloc16 = aDestRloc16;
|
||||
aPathCost = CostForLinkQuality(child->GetLinkQualityIn());
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
VerifyOrExit(router != nullptr);
|
||||
|
||||
cost = GetLinkCost(*router);
|
||||
aPathCost = GetLinkCost(*router);
|
||||
|
||||
if (aPathCost < Mle::kMaxRouteCost)
|
||||
{
|
||||
aNextHopRloc16 = router->GetRloc16();
|
||||
}
|
||||
|
||||
if (nextHop != nullptr)
|
||||
{
|
||||
// Determine whether direct link or forwarding hop link
|
||||
// has a lower cost.
|
||||
cost = Min(cost, static_cast<uint8_t>(router->GetCost() + GetLinkCost(*nextHop)));
|
||||
// through `nextHop` has a lower path cost.
|
||||
|
||||
uint8_t nextHopPathCost = router->GetCost() + GetLinkCost(*nextHop);
|
||||
|
||||
if (nextHopPathCost < aPathCost)
|
||||
{
|
||||
aPathCost = nextHopPathCost;
|
||||
aNextHopRloc16 = nextHop->GetRloc16();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,71 +480,27 @@ uint8_t RouterTable::GetPathCost(uint16_t aDestRloc16) const
|
||||
// Destination is a child. we assume best link quality
|
||||
// between destination and its parent router.
|
||||
|
||||
cost += kCostForLinkQuality3;
|
||||
aPathCost += kCostForLinkQuality3;
|
||||
}
|
||||
|
||||
exit:
|
||||
return cost;
|
||||
}
|
||||
|
||||
uint8_t RouterTable::GetPathCostToLeader(void) const
|
||||
{
|
||||
return GetPathCost(Mle::Rloc16FromRouterId(Get<Mle::Mle>().GetLeaderId()));
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t RouterTable::GetNextHop(uint16_t aDestRloc16) const
|
||||
{
|
||||
uint16_t nextHopRloc16 = Mle::kInvalidRloc16;
|
||||
uint8_t destRouterId = Mle::RouterIdFromRloc16(aDestRloc16);
|
||||
uint8_t linkCost;
|
||||
const Router *router;
|
||||
const Router *nextHop;
|
||||
uint8_t pathCost;
|
||||
uint16_t nextHopRloc16;
|
||||
|
||||
if (Get<Mle::Mle>().IsChild())
|
||||
{
|
||||
const Router &parent = Get<Mle::Mle>().GetParent();
|
||||
GetNextHopAndPathCost(aDestRloc16, nextHopRloc16, pathCost);
|
||||
|
||||
VerifyOrExit(parent.IsStateValid());
|
||||
nextHopRloc16 = parent.GetRloc16();
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
if (destRouterId == Mle::RouterIdFromRloc16(Get<Mle::Mle>().GetRloc16()))
|
||||
{
|
||||
// Destination is device itself or one of its
|
||||
// children.
|
||||
ExitNow(nextHopRloc16 = aDestRloc16);
|
||||
}
|
||||
|
||||
router = FindRouterById(destRouterId);
|
||||
VerifyOrExit(router != nullptr);
|
||||
|
||||
linkCost = GetLinkCost(*router);
|
||||
|
||||
if (linkCost < Mle::kMaxRouteCost)
|
||||
{
|
||||
nextHopRloc16 = Mle::Rloc16FromRouterId(destRouterId);
|
||||
}
|
||||
|
||||
// Check if we have a forwarding route path towards the
|
||||
// destination and whether direct link or forwarding path has a
|
||||
// lower cost.
|
||||
|
||||
nextHop = FindNextHopOf(*router);
|
||||
VerifyOrExit(nextHop != nullptr);
|
||||
|
||||
if (router->GetCost() + GetLinkCost(*nextHop) < linkCost)
|
||||
{
|
||||
nextHopRloc16 = nextHop->GetRloc16();
|
||||
}
|
||||
|
||||
exit:
|
||||
return nextHopRloc16;
|
||||
}
|
||||
|
||||
void RouterTable::UpdateRouterIdSet(uint8_t aRouterIdSequence, const Mle::RouterIdSet &aRouterIdSet)
|
||||
{
|
||||
bool shouldAdd = false;
|
||||
bool changed = false;
|
||||
|
||||
mRouterIdSequence = aRouterIdSequence;
|
||||
mRouterIdSequenceLastUpdated = TimerMilli::GetNow();
|
||||
@@ -530,6 +523,7 @@ void RouterTable::UpdateRouterIdSet(uint8_t aRouterIdSequence, const Mle::Router
|
||||
router->SetNextHopToInvalid();
|
||||
RemoveRouterLink(*router);
|
||||
RemoveRouter(*router);
|
||||
changed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -546,13 +540,17 @@ void RouterTable::UpdateRouterIdSet(uint8_t aRouterIdSequence, const Mle::Router
|
||||
if (!IsAllocated(routerId) && aRouterIdSet.Contains(routerId))
|
||||
{
|
||||
AddRouter(routerId);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
Get<Mle::MleRouter>().ResetAdvertiseInterval();
|
||||
|
||||
exit:
|
||||
return;
|
||||
if (changed)
|
||||
{
|
||||
LogRouteTable();
|
||||
}
|
||||
}
|
||||
|
||||
void RouterTable::UpdateRoutes(const Mle::RouteTlv &aRouteTlv, uint8_t aNeighborId)
|
||||
|
||||
@@ -190,6 +190,16 @@ public:
|
||||
*/
|
||||
uint16_t GetNextHop(uint16_t aDestRloc16) const;
|
||||
|
||||
/**
|
||||
* This method determines the next hop and the path cost towards an RLOC16 destination.
|
||||
*
|
||||
* @param[in] aDestRloc16 The RLOC16 of the destination.
|
||||
* @param[out] aNextHopRloc16 A reference to return the RLOC16 of next hop if known, or `Mle::kInvalidRloc16`.
|
||||
* @param[out] aPathCost A reference to return the path cost.
|
||||
*
|
||||
*/
|
||||
void GetNextHopAndPathCost(uint16_t aDestRloc16, uint16_t &aNextHopRloc16, uint8_t &aPathCost) const;
|
||||
|
||||
/**
|
||||
* This method finds the router for a given Router ID.
|
||||
*
|
||||
|
||||
+13
-7
@@ -195,8 +195,8 @@ class Node(object):
|
||||
outputs = self.cli(cmd, *args)
|
||||
verify(len(outputs) == 0)
|
||||
|
||||
def _cli_single_output(self, cmd, expected_outputs=None):
|
||||
outputs = self.cli(cmd)
|
||||
def _cli_single_output(self, cmd, *args, expected_outputs=None):
|
||||
outputs = self.cli(cmd, *args)
|
||||
verify(len(outputs) == 1)
|
||||
verify((expected_outputs is None) or (outputs[0] in expected_outputs))
|
||||
return outputs[0]
|
||||
@@ -212,7 +212,7 @@ class Node(object):
|
||||
# cli commands
|
||||
|
||||
def get_state(self):
|
||||
return self._cli_single_output('state', ['detached', 'child', 'router', 'leader', 'disabled'])
|
||||
return self._cli_single_output('state', expected_outputs=['detached', 'child', 'router', 'leader', 'disabled'])
|
||||
|
||||
def get_channel(self):
|
||||
return self._cli_single_output('channel')
|
||||
@@ -328,6 +328,9 @@ class Node(object):
|
||||
def get_partition_id(self):
|
||||
return self._cli_single_output('partitionid')
|
||||
|
||||
def get_nexthop(self, rloc16):
|
||||
return self._cli_single_output('nexthop', rloc16)
|
||||
|
||||
def get_parent_info(self):
|
||||
outputs = self.cli('parent')
|
||||
result = {}
|
||||
@@ -446,10 +449,10 @@ class Node(object):
|
||||
self._cli_no_output('srp client stop')
|
||||
|
||||
def srp_client_get_state(self):
|
||||
return self._cli_single_output('srp client state', ['Enabled', 'Disabled'])
|
||||
return self._cli_single_output('srp client state', expected_outputs=['Enabled', 'Disabled'])
|
||||
|
||||
def srp_client_get_auto_start_mode(self):
|
||||
return self._cli_single_output('srp client autostart', ['Enabled', 'Disabled'])
|
||||
return self._cli_single_output('srp client autostart', expected_outputs=['Enabled', 'Disabled'])
|
||||
|
||||
def srp_client_enable_auto_start_mode(self):
|
||||
self._cli_no_output('srp client autostart enable')
|
||||
@@ -545,10 +548,10 @@ class Node(object):
|
||||
# SRP server
|
||||
|
||||
def srp_server_get_state(self):
|
||||
return self._cli_single_output('srp server state', ['disabled', 'running', 'stopped'])
|
||||
return self._cli_single_output('srp server state', expected_outputs=['disabled', 'running', 'stopped'])
|
||||
|
||||
def srp_server_get_addr_mode(self):
|
||||
return self._cli_single_output('srp server addrmode', ['unicast', 'anycast'])
|
||||
return self._cli_single_output('srp server addrmode', expected_outputs=['unicast', 'anycast'])
|
||||
|
||||
def srp_server_set_addr_mode(self, mode):
|
||||
self._cli_no_output('srp server addrmode', mode)
|
||||
@@ -717,6 +720,9 @@ class Node(object):
|
||||
"""Removes a given node (of node `Node) from the allowlist"""
|
||||
self._cli_no_output('macfilter addr remove', node.get_ext_addr())
|
||||
|
||||
def set_macfilter_lqi_to_node(self, node, lqi):
|
||||
self._cli_no_output('macfilter rss add-lqi', node.get_ext_addr(), lqi)
|
||||
|
||||
# ------------------------------------------------------------------------------------------------------------------
|
||||
# Parsing helpers
|
||||
|
||||
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2023, 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: Next hop and path cost calculation.
|
||||
#
|
||||
# Network topology
|
||||
#
|
||||
# r1 ---- r2...2...r3
|
||||
# / | \ / \
|
||||
# / | \ / \
|
||||
# fed1 fed2 r4...1...r5 ---- fed3
|
||||
#
|
||||
# Link r2 --> r3 is configured to be at link quality of 2.
|
||||
# Link r5 --> r4 is configured to be at link quality of 1.
|
||||
# Link r1 --> fed2 is configured to be at link quality 1.
|
||||
# Other links are at link quality 3 (best possible link quality).
|
||||
#
|
||||
|
||||
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
|
||||
print('-' * 120)
|
||||
print('Starting \'{}\''.format(test_name))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Creating `cli.Node` instances
|
||||
|
||||
speedup = 40
|
||||
cli.Node.set_time_speedup_factor(speedup)
|
||||
|
||||
r1 = cli.Node()
|
||||
r2 = cli.Node()
|
||||
r3 = cli.Node()
|
||||
r4 = cli.Node()
|
||||
r5 = cli.Node()
|
||||
fed1 = cli.Node()
|
||||
fed2 = cli.Node()
|
||||
fed3 = cli.Node()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Form topology
|
||||
|
||||
r1.allowlist_node(r2)
|
||||
r1.allowlist_node(fed1)
|
||||
r1.allowlist_node(fed2)
|
||||
|
||||
r2.allowlist_node(r1)
|
||||
r2.allowlist_node(r3)
|
||||
r2.allowlist_node(r4)
|
||||
|
||||
r3.allowlist_node(r2)
|
||||
r3.allowlist_node(r4)
|
||||
r3.allowlist_node(r5)
|
||||
r3.set_macfilter_lqi_to_node(r2, 2)
|
||||
|
||||
r4.allowlist_node(r2)
|
||||
r4.allowlist_node(r3)
|
||||
r4.allowlist_node(r5)
|
||||
r4.set_macfilter_lqi_to_node(r5, 1)
|
||||
|
||||
r5.allowlist_node(r4)
|
||||
r5.allowlist_node(r3)
|
||||
r5.allowlist_node(fed3)
|
||||
|
||||
fed1.allowlist_node(r1)
|
||||
fed2.allowlist_node(r1)
|
||||
fed3.allowlist_node(r5)
|
||||
fed2.set_macfilter_lqi_to_node(r1, 1)
|
||||
|
||||
r1.form('hop-cost')
|
||||
r2.join(r1)
|
||||
r3.join(r1)
|
||||
r4.join(r1)
|
||||
r5.join(r1)
|
||||
fed1.join(r1, cli.JOIN_TYPE_REED)
|
||||
fed2.join(r1, cli.JOIN_TYPE_REED)
|
||||
fed3.join(r1, cli.JOIN_TYPE_REED)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Test Implementation
|
||||
|
||||
r1_rloc = int(r1.get_rloc16(), 16)
|
||||
r2_rloc = int(r2.get_rloc16(), 16)
|
||||
r3_rloc = int(r3.get_rloc16(), 16)
|
||||
r4_rloc = int(r4.get_rloc16(), 16)
|
||||
r5_rloc = int(r5.get_rloc16(), 16)
|
||||
|
||||
fed1_rloc = int(fed1.get_rloc16(), 16)
|
||||
fed2_rloc = int(fed2.get_rloc16(), 16)
|
||||
fed3_rloc = int(fed3.get_rloc16(), 16)
|
||||
|
||||
|
||||
def parse_nexthop(line):
|
||||
# Exmaple: "0x5000 cost:3" -> (0x5000, 3).
|
||||
items = line.strip().split(' ', 2)
|
||||
return (int(items[0], 16), int(items[1].split(':')[1]))
|
||||
|
||||
|
||||
def check_nexthops_and_costs():
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# `r1` next hops and costs
|
||||
verify(parse_nexthop(r1.get_nexthop(r1_rloc)) == (r1_rloc, 0))
|
||||
verify(parse_nexthop(r1.get_nexthop(r2_rloc)) == (r2_rloc, 1))
|
||||
verify(parse_nexthop(r1.get_nexthop(r3_rloc)) == (r2_rloc, 3))
|
||||
verify(parse_nexthop(r1.get_nexthop(r4_rloc)) == (r2_rloc, 2))
|
||||
verify(parse_nexthop(r1.get_nexthop(r5_rloc)) == (r2_rloc, 4))
|
||||
verify(parse_nexthop(r1.get_nexthop(fed3_rloc)) == (r2_rloc, 5))
|
||||
# On `r1` its children can be reached directly.
|
||||
verify(parse_nexthop(r1.get_nexthop(fed1_rloc)) == (fed1_rloc, 1))
|
||||
verify(parse_nexthop(r1.get_nexthop(fed2_rloc)) == (fed2_rloc, 1))
|
||||
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# `r2` next hops and costs
|
||||
verify(parse_nexthop(r2.get_nexthop(r1_rloc)) == (r1_rloc, 1))
|
||||
verify(parse_nexthop(r2.get_nexthop(r2_rloc)) == (r2_rloc, 0))
|
||||
# On `r2` the direct link to `r3` and the path through `r4` both
|
||||
# have the same cost, but the direct link should be preferred.
|
||||
verify(parse_nexthop(r2.get_nexthop(r3_rloc)) == (r3_rloc, 2))
|
||||
verify(parse_nexthop(r2.get_nexthop(r4_rloc)) == (r4_rloc, 1))
|
||||
# On 'r2' the path to `r5` can go through `r3` or `r4`
|
||||
# as both have the same cost.
|
||||
(nexthop, cost) = parse_nexthop(r2.get_nexthop(r5_rloc))
|
||||
verify(cost == 3)
|
||||
verify(nexthop in [r3_rloc, r4_rloc])
|
||||
verify(parse_nexthop(r2.get_nexthop(fed1_rloc)) == (r1_rloc, 2))
|
||||
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# `r3` next hops and costs
|
||||
verify(parse_nexthop(r3.get_nexthop(r3_rloc)) == (r3_rloc, 0))
|
||||
verify(parse_nexthop(r3.get_nexthop(r5_rloc)) == (r5_rloc, 1))
|
||||
verify(parse_nexthop(r3.get_nexthop(r4_rloc)) == (r4_rloc, 1))
|
||||
verify(parse_nexthop(r3.get_nexthop(r2_rloc)) == (r2_rloc, 2))
|
||||
# On `r3` the path to `r1` can go through `r2` or `r4`
|
||||
# as both have the same cost.
|
||||
(nexthop, cost) = parse_nexthop(r3.get_nexthop(r1_rloc))
|
||||
verify(cost == 3)
|
||||
verify(nexthop in [r2_rloc, r4_rloc])
|
||||
# On `r3` the path to fed1 should use the same next hop as `r1`
|
||||
verify(parse_nexthop(r3.get_nexthop(fed2_rloc)) == (nexthop, 4))
|
||||
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# `r4` next hops and costs
|
||||
verify(parse_nexthop(r4.get_nexthop(fed1_rloc)) == (r2_rloc, 3))
|
||||
verify(parse_nexthop(r4.get_nexthop(r1_rloc)) == (r2_rloc, 2))
|
||||
verify(parse_nexthop(r4.get_nexthop(r2_rloc)) == (r2_rloc, 1))
|
||||
verify(parse_nexthop(r4.get_nexthop(r3_rloc)) == (r3_rloc, 1))
|
||||
verify(parse_nexthop(r4.get_nexthop(r4_rloc)) == (r4_rloc, 0))
|
||||
# On `r4` even though we have a direct link to `r5`
|
||||
# the path cost through `r3` has a smaller cost over
|
||||
# the direct link cost.
|
||||
verify(parse_nexthop(r4.get_nexthop(r5_rloc)) == (r3_rloc, 2))
|
||||
verify(parse_nexthop(r4.get_nexthop(fed3_rloc)) == (r3_rloc, 3))
|
||||
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# `r5` next hops and costs
|
||||
verify(parse_nexthop(r5.get_nexthop(fed3_rloc)) == (fed3_rloc, 1))
|
||||
verify(parse_nexthop(r5.get_nexthop(r5_rloc)) == (r5_rloc, 0))
|
||||
verify(parse_nexthop(r5.get_nexthop(r3_rloc)) == (r3_rloc, 1))
|
||||
verify(parse_nexthop(r5.get_nexthop(r4_rloc)) == (r3_rloc, 2))
|
||||
verify(parse_nexthop(r5.get_nexthop(r2_rloc)) == (r3_rloc, 3))
|
||||
verify(parse_nexthop(r5.get_nexthop(r1_rloc)) == (r3_rloc, 4))
|
||||
verify(parse_nexthop(r5.get_nexthop(fed1_rloc)) == (r3_rloc, 5))
|
||||
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# `fed1` next hops and costs
|
||||
verify(parse_nexthop(fed1.get_nexthop(fed1_rloc)) == (fed1_rloc, 0))
|
||||
verify(parse_nexthop(fed1.get_nexthop(r1_rloc)) == (r1_rloc, 1))
|
||||
verify(parse_nexthop(fed1.get_nexthop(r2_rloc)) == (r1_rloc, 2))
|
||||
verify(parse_nexthop(fed1.get_nexthop(r3_rloc)) == (r1_rloc, 4))
|
||||
verify(parse_nexthop(fed1.get_nexthop(r4_rloc)) == (r1_rloc, 3))
|
||||
verify(parse_nexthop(fed1.get_nexthop(r5_rloc)) == (r1_rloc, 5))
|
||||
verify(parse_nexthop(fed1.get_nexthop(fed3_rloc)) == (r1_rloc, 6))
|
||||
# On `fed1`, path to `fed2` should go through our parent.
|
||||
verify(parse_nexthop(fed1.get_nexthop(fed2_rloc)) == (r1_rloc, 2))
|
||||
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# `fed2` next hops and costs
|
||||
verify(parse_nexthop(fed2.get_nexthop(fed2_rloc)) == (fed2_rloc, 0))
|
||||
verify(parse_nexthop(fed2.get_nexthop(r1_rloc)) == (r1_rloc, 4))
|
||||
verify(parse_nexthop(fed2.get_nexthop(r2_rloc)) == (r1_rloc, 5))
|
||||
verify(parse_nexthop(fed2.get_nexthop(r3_rloc)) == (r1_rloc, 7))
|
||||
verify(parse_nexthop(fed2.get_nexthop(r4_rloc)) == (r1_rloc, 6))
|
||||
verify(parse_nexthop(fed2.get_nexthop(r5_rloc)) == (r1_rloc, 8))
|
||||
verify(parse_nexthop(fed2.get_nexthop(fed3_rloc)) == (r1_rloc, 9))
|
||||
verify(parse_nexthop(fed2.get_nexthop(fed1_rloc)) == (r1_rloc, 5))
|
||||
|
||||
|
||||
verify_within(check_nexthops_and_costs, 5)
|
||||
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Disable `r4` and check nexthop and cost on it and on other
|
||||
# nodes.
|
||||
#
|
||||
#
|
||||
# r1 ---- r2...2...r3
|
||||
# / | \
|
||||
# / | \
|
||||
# fed1 fed2 r4 r5 ---- fed3
|
||||
|
||||
r4.thread_stop()
|
||||
r4.interface_down()
|
||||
|
||||
verify(parse_nexthop(r4.get_nexthop(r2_rloc)) == (0xfffe, 16))
|
||||
verify(parse_nexthop(r4.get_nexthop(r4_rloc)) == (0xfffe, 16))
|
||||
|
||||
|
||||
def check_nexthops_and_costs_after_r4_detach():
|
||||
# Make sure we have no next hop towards `r4`.
|
||||
verify(parse_nexthop(r1.get_nexthop(r4_rloc)) == (0xfffe, 16))
|
||||
verify(parse_nexthop(r2.get_nexthop(r4_rloc)) == (0xfffe, 16))
|
||||
verify(parse_nexthop(r3.get_nexthop(r4_rloc)) == (0xfffe, 16))
|
||||
verify(parse_nexthop(r5.get_nexthop(r4_rloc)) == (0xfffe, 16))
|
||||
verify(parse_nexthop(fed3.get_nexthop(r4_rloc)) == (r5_rloc, 16))
|
||||
# Check cost and next hop on other nodes
|
||||
verify(parse_nexthop(r1.get_nexthop(r5_rloc)) == (r2_rloc, 4))
|
||||
verify(parse_nexthop(r2.get_nexthop(r3_rloc)) == (r3_rloc, 2))
|
||||
verify(parse_nexthop(r3.get_nexthop(r2_rloc)) == (r2_rloc, 2))
|
||||
verify(parse_nexthop(fed3.get_nexthop(fed1_rloc)) == (r5_rloc, 6))
|
||||
|
||||
|
||||
verify_within(check_nexthops_and_costs_after_r4_detach, 45)
|
||||
verify(r1.get_state() == 'leader')
|
||||
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Disable `r1` (which was previous leader) and check
|
||||
# routes on other nodes
|
||||
#
|
||||
#
|
||||
# r1 r2...2...r3
|
||||
# / | \
|
||||
# / | \
|
||||
# fed1 fed2 r4 r5 ---- fed3
|
||||
|
||||
r1.thread_stop()
|
||||
r1.interface_down()
|
||||
fed1.thread_stop()
|
||||
fed1.interface_down()
|
||||
fed2.thread_stop()
|
||||
fed2.interface_down()
|
||||
|
||||
verify(parse_nexthop(r1.get_nexthop(r2_rloc)) == (0xfffe, 16))
|
||||
verify(parse_nexthop(r1.get_nexthop(r1_rloc)) == (0xfffe, 16))
|
||||
verify(parse_nexthop(r1.get_nexthop(fed1_rloc)) == (0xfffe, 16))
|
||||
|
||||
|
||||
def check_nexthops_and_costs_after_r1_detach():
|
||||
verify(parse_nexthop(r2.get_nexthop(r1_rloc)) == (0xfffe, 16))
|
||||
verify(parse_nexthop(r3.get_nexthop(r1_rloc)) == (0xfffe, 16))
|
||||
verify(parse_nexthop(r5.get_nexthop(r1_rloc)) == (0xfffe, 16))
|
||||
verify(parse_nexthop(fed3.get_nexthop(r1_rloc)) == (r5_rloc, 16))
|
||||
verify(parse_nexthop(r2.get_nexthop(r5_rloc)) == (r3_rloc, 3))
|
||||
verify(parse_nexthop(fed3.get_nexthop(r2_rloc)) == (r5_rloc, 4))
|
||||
|
||||
|
||||
verify_within(check_nexthops_and_costs_after_r1_detach, 30)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
cli.Node.finalize_all_nodes()
|
||||
|
||||
print('\'{}\' passed.'.format(test_name))
|
||||
@@ -138,6 +138,7 @@ if [ "$TORANJ_CLI" = 1 ]; then
|
||||
run cli/test-015-clear-addresss-cache-for-sed.py
|
||||
run cli/test-016-child-mode-change.py
|
||||
run cli/test-017-network-data-versions.py
|
||||
run cli/test-018-next-hop-and-path-cost.py
|
||||
run cli/test-400-srp-client-server.py
|
||||
run cli/test-601-channel-manager-channel-change.py
|
||||
# Skip the "channel-select" test on a TREL only radio link, since it
|
||||
|
||||
Reference in New Issue
Block a user