[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:
Abtin Keshavarzian
2023-01-17 13:31:44 -08:00
committed by GitHub
parent c766c8735e
commit 57733ea668
9 changed files with 440 additions and 69 deletions
+1 -1
View File
@@ -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
+18
View File
@@ -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);
/**
* @}
*
+34
View File
@@ -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"),
+13
View File
@@ -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
+59 -61
View File
@@ -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)
+10
View File
@@ -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
View File
@@ -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
View File
@@ -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))
+1
View File
@@ -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