diff --git a/include/openthread/instance.h b/include/openthread/instance.h index e7696c029..fc36d1aa1 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -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 diff --git a/include/openthread/thread_ftd.h b/include/openthread/thread_ftd.h index 9a1c500d6..c51acdd01 100644 --- a/include/openthread/thread_ftd.h +++ b/include/openthread/thread_ftd.h @@ -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); + /** * @} * diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index ce8114f39..1623e6b46 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -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(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(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"), diff --git a/src/core/api/thread_ftd_api.cpp b/src/core/api/thread_ftd_api.cpp index c422169e9..5876e6126 100644 --- a/src/core/api/thread_ftd_api.cpp +++ b/src/core/api/thread_ftd_api.cpp @@ -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().GetNextHopAndPathCost( + aDestRloc16, (aNextHopRloc16 != nullptr) ? *aNextHopRloc16 : nextHopRloc16, + (aPathCost != nullptr) ? *aPathCost : pathcost); +} + #endif // OPENTHREAD_FTD diff --git a/src/core/thread/router_table.cpp b/src/core/thread/router_table.cpp index a8a14937f..306a154ba 100644 --- a/src/core/thread/router_table.cpp +++ b/src/core/thread/router_table.cpp @@ -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().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().IsAttached()); if (aDestRloc16 == Get().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().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().FindChild(aDestRloc16, Child::kInStateValid); + const Child *child = Get().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(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().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().IsChild()) - { - const Router &parent = Get().GetParent(); + GetNextHopAndPathCost(aDestRloc16, nextHopRloc16, pathCost); - VerifyOrExit(parent.IsStateValid()); - nextHopRloc16 = parent.GetRloc16(); - ExitNow(); - } - - if (destRouterId == Mle::RouterIdFromRloc16(Get().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().ResetAdvertiseInterval(); exit: - return; + if (changed) + { + LogRouteTable(); + } } void RouterTable::UpdateRoutes(const Mle::RouteTlv &aRouteTlv, uint8_t aNeighborId) diff --git a/src/core/thread/router_table.hpp b/src/core/thread/router_table.hpp index eecb824fd..d84ad9164 100644 --- a/src/core/thread/router_table.hpp +++ b/src/core/thread/router_table.hpp @@ -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. * diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index c005f2dbd..e47eb648b 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -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 diff --git a/tests/toranj/cli/test-018-next-hop-and-path-cost.py b/tests/toranj/cli/test-018-next-hop-and-path-cost.py new file mode 100755 index 000000000..b31b2f878 --- /dev/null +++ b/tests/toranj/cli/test-018-next-hop-and-path-cost.py @@ -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)) diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index 6c24d336b..9e4f325b0 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -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