Files
openthread/src/cli/cli_br.cpp
T
Abtin Keshavarzian 7f3ab64dce [cli] add ToYesNo() helper to convert boolean to string (#12424)
This commit introduces a new helper method `Utils::ToYesNo()` in the
CLI module to convert boolean values into "yes" or "no" strings.

Previously, this conversion was performed using inline ternary
operators (e.g., `val ? "yes" : "no"`) scattered throughout the CLI
implementation. This change replaces these instances with the new
helper method, improving code readability and consistency.
2026-02-12 16:55:36 -06:00

1264 lines
36 KiB
C++

/*
* 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.
*/
/**
* @file
* This file implements CLI for Border Router.
*/
#include "cli_br.hpp"
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
#include <string.h>
#include "cli/cli.hpp"
namespace ot {
namespace Cli {
/**
* @cli br init
* @code
* br init 2 1
* Done
* @endcode
* @cparam br init @ca{infrastructure-network-index} @ca{is-running}
* @par
* Initializes the Border Routing Manager.
* @sa otBorderRoutingInit
*/
template <> otError Br::Process<Cmd("init")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
uint32_t ifIndex;
bool isRunning;
SuccessOrExit(error = aArgs[0].ParseAsUint32(ifIndex));
SuccessOrExit(error = aArgs[1].ParseAsBool(isRunning));
VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
error = otBorderRoutingInit(GetInstancePtr(), ifIndex, isRunning);
exit:
return error;
}
/**
* @cli br infraif
* @code
* br infraif
* if-index:2, is-running:yes
* Done
* @endcode
* @par
* Gets the interface index and running state of the configured infrastructure interface.
*/
template <> otError Br::Process<Cmd("infraif")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
uint32_t ifIndex;
bool isRunning;
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = otBorderRoutingGetInfraIfInfo(GetInstancePtr(), &ifIndex, &isRunning));
OutputLine("if-index:%lu, is-running:%s", ToUlong(ifIndex), ToYesNo(isRunning));
exit:
return error;
}
/**
* @cli br enable
* @code
* br enable
* Done
* @endcode
* @par
* Enables the Border Routing Manager.
* @sa otBorderRoutingSetEnabled
*/
template <> otError Br::Process<Cmd("enable")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
error = otBorderRoutingSetEnabled(GetInstancePtr(), true);
exit:
return error;
}
/**
* @cli br disable
* @code
* br disable
* Done
* @endcode
* @par
* Disables the Border Routing Manager.
* @sa otBorderRoutingSetEnabled
*/
template <> otError Br::Process<Cmd("disable")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
error = otBorderRoutingSetEnabled(GetInstancePtr(), false);
exit:
return error;
}
/**
* @cli br state
* @code
* br state
* running
* @endcode
* @par api_copy
* #otBorderRoutingGetState
*/
template <> otError Br::Process<Cmd("state")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
OutputLine("%s", BorderRoutingStateToString(otBorderRoutingGetState(GetInstancePtr())));
exit:
return error;
}
#if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE
template <> otError Br::Process<Cmd("multiail")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
/**
* @cli br multiail
* @code
* br multiail
* not detected
* @endcode
* @par api_copy
* #otBorderRoutingIsMultiAilDetected
*/
if (aArgs[0].IsEmpty())
{
OutputLine("%sdetected", otBorderRoutingIsMultiAilDetected(GetInstancePtr()) ? "" : "not ");
}
/**
* @cli br multiail callback
* @code
* br multiail callback enable
* Done
* @endcode
* @cparam br multiail callback @ca{enable|disable}
* @par api_copy
* #otBorderRoutingSetMultiAilCallback
*/
else if (aArgs[0] == "callback")
{
bool enable;
otBorderRoutingMultiAilCallback callback = nullptr;
SuccessOrExit(error = ParseEnableOrDisable(aArgs[1], enable));
if (enable)
{
callback = &HandleMultiAilDetected;
}
otBorderRoutingSetMultiAilCallback(GetInstancePtr(), callback, this);
}
/**
* @cli br multiail state
* @code
* br multiail state
* Enabled: yes
* Running: yes
* Detected: no
* Done
* @endcode
* @par
* Outputs full state of multi-AIL detector:
* - Whether the detector is enabled.
* - Whether the detector is running (when it is enabled and the infra-if interface is also active).
* - Whether multi-AIL was detected.
*/
else if (aArgs[0] == "state")
{
OutputLine("Enabled: %s", ToYesNo(otBorderRoutingIsMultiAilDetectionEnabled(GetInstancePtr())));
OutputLine("Running: %s", ToYesNo(otBorderRoutingIsMultiAilDetectionRunning(GetInstancePtr())));
OutputLine("Detected: %s", ToYesNo(otBorderRoutingIsMultiAilDetected(GetInstancePtr())));
}
/**
* @cli br multiail (enable, disable)
* @code
* br multiail enable
* Done
* @endcode
* @cparam br multiail @ca{enable|disable}
* @par api_copy
* #otBorderRoutingSetMultiAilDetectionEnabled
*/
else if (ProcessEnableDisable(aArgs, otBorderRoutingSetMultiAilDetectionEnabled) == OT_ERROR_NONE)
{
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
exit:
return error;
}
void Br::HandleMultiAilDetected(bool aDetected, void *aContext)
{
static_cast<Br *>(aContext)->HandleMultiAilDetected(aDetected);
}
void Br::HandleMultiAilDetected(bool aDetected)
{
OutputLine("BR multi AIL callback: %s", aDetected ? "detected" : "cleared");
}
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE
otError Br::ParsePrefixTypeArgs(Arg aArgs[], PrefixType &aFlags)
{
otError error = OT_ERROR_NONE;
aFlags = 0;
if (aArgs[0].IsEmpty())
{
aFlags = kPrefixTypeFavored | kPrefixTypeLocal;
ExitNow();
}
if (aArgs[0] == "local")
{
aFlags = kPrefixTypeLocal;
}
else if (aArgs[0] == "favored")
{
aFlags = kPrefixTypeFavored;
}
else
{
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
exit:
return error;
}
template <> otError Br::Process<Cmd("omrconfig")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otIp6Prefix customPrefix;
otRoutePreference preference;
otBorderRoutingOmrConfig omrConfig;
/**
* @cli br omrconfig
* @code
* br omrconfig
* auto
* Done
* @endcode
* @code
* br omrconfig
* custom (fd00:0:0:0::/64, prf:med)
* Done
* @endcode
* @par
* Outputs current OMR prefix configuration mode.
* @sa otBorderRoutingGetOmrConfig
*/
if (aArgs[0].IsEmpty())
{
omrConfig = otBorderRoutingGetOmrConfig(GetInstancePtr(), &customPrefix, &preference);
switch (omrConfig)
{
case OT_BORDER_ROUTING_OMR_CONFIG_AUTO:
OutputLine("auto");
break;
case OT_BORDER_ROUTING_OMR_CONFIG_CUSTOM:
OutputFormat("custom (");
OutputIp6Prefix(customPrefix);
OutputLine(", prf:%s)", PreferenceToString(preference));
break;
case OT_BORDER_ROUTING_OMR_CONFIG_DISABLED:
OutputLine("disabled");
break;
}
}
else
{
ClearAllBytes(customPrefix);
preference = OT_ROUTE_PREFERENCE_MED;
/**
* @cli br omrconfig auto
* @code
* br omrconfig auto
* Done
* @endcode
* @par
* Sets OMR prefix configuration mode to `auto` In this mode, the Border Routing Manager automatically
* selects and manages the OMR prefix.
*/
if (aArgs[0] == "auto")
{
omrConfig = OT_BORDER_ROUTING_OMR_CONFIG_AUTO;
VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
}
/**
* @cli br omrconfig custom
* @code
* br omrconfig custom fd00::/64 med
* Done
* @endcode
* @cparam br omrconfig custom @ca{prefix} [@ca{high}|@ca{med}|@ca{low}]
* @par
* Sets OMR prefix configuration mode to `custom`. In this mode, a custom OMR prefix and its associated
* preference are used.
*/
else if (aArgs[0] == "custom")
{
omrConfig = OT_BORDER_ROUTING_OMR_CONFIG_CUSTOM;
SuccessOrExit(error = aArgs[1].ParseAsIp6Prefix(customPrefix));
SuccessOrExit(error = Interpreter::ParsePreference(aArgs[2], preference));
VerifyOrExit(aArgs[3].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
}
/**
* @cli br omrconfig disable
* @code
* br omrconfig disable
* Done
* @endcode
* @cparam br omrconfig disable
* @par
* Sets OMR prefix configuration mode to `disable` which prevents the Border Routing Manager from adding any
* local or DHCPv6 PD OMR prefixes to the Network Data.
*/
else if (aArgs[0] == "disable")
{
omrConfig = OT_BORDER_ROUTING_OMR_CONFIG_DISABLED;
}
else
{
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
error = otBorderRoutingSetOmrConfig(GetInstancePtr(), omrConfig, &customPrefix, preference);
}
exit:
return error;
}
/**
* @cli br omrprefix
* @code
* br omrprefix
* Local: fdfc:1ff5:1512:5622::/64
* Favored: fdfc:1ff5:1512:5622::/64 prf:low
* Done
* @endcode
* @par
* Outputs both local and favored OMR prefix.
* @sa otBorderRoutingGetOmrPrefix
* @sa otBorderRoutingGetFavoredOmrPrefix
*/
template <> otError Br::Process<Cmd("omrprefix")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
PrefixType outputPrefixTypes;
SuccessOrExit(error = ParsePrefixTypeArgs(aArgs, outputPrefixTypes));
/**
* @cli br omrprefix local
* @code
* br omrprefix local
* fdfc:1ff5:1512:5622::/64
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetOmrPrefix
*/
if (outputPrefixTypes & kPrefixTypeLocal)
{
otIp6Prefix local;
SuccessOrExit(error = otBorderRoutingGetOmrPrefix(GetInstancePtr(), &local));
OutputFormat("%s", outputPrefixTypes == kPrefixTypeLocal ? "" : "Local: ");
OutputIp6PrefixLine(local);
}
/**
* @cli br omrprefix favored
* @code
* br omrprefix favored
* fdfc:1ff5:1512:5622::/64 prf:low
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetFavoredOmrPrefix
*/
if (outputPrefixTypes & kPrefixTypeFavored)
{
otIp6Prefix favored;
otRoutePreference preference;
SuccessOrExit(error = otBorderRoutingGetFavoredOmrPrefix(GetInstancePtr(), &favored, &preference));
OutputFormat("%s", outputPrefixTypes == kPrefixTypeFavored ? "" : "Favored: ");
OutputIp6Prefix(favored);
OutputLine(" prf:%s", PreferenceToString(preference));
}
exit:
return error;
}
/**
* @cli br onlinkprefix
* @code
* br onlinkprefix
* Local: fd41:2650:a6f5:0::/64
* Favored: 2600::0:1234:da12::/64
* Done
* @endcode
* @par
* Outputs both local and favored on-link prefixes.
* @sa otBorderRoutingGetOnLinkPrefix
* @sa otBorderRoutingGetFavoredOnLinkPrefix
*/
template <> otError Br::Process<Cmd("onlinkprefix")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
PrefixType outputPrefixTypes;
#if OPENTHREAD_CONFIG_BORDER_ROUTING_TESTING_API_ENABLE
if (aArgs[0] == "test")
{
otIp6Prefix prefix;
SuccessOrExit(error = aArgs[1].ParseAsIp6Prefix(prefix));
otBorderRoutingSetOnLinkPrefix(GetInstancePtr(), &prefix);
ExitNow();
}
#endif
error = ParsePrefixTypeArgs(aArgs, outputPrefixTypes);
SuccessOrExit(error);
/**
* @cli br onlinkprefix local
* @code
* br onlinkprefix local
* fd41:2650:a6f5:0::/64
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetOnLinkPrefix
*/
if (outputPrefixTypes & kPrefixTypeLocal)
{
otIp6Prefix local;
SuccessOrExit(error = otBorderRoutingGetOnLinkPrefix(GetInstancePtr(), &local));
OutputFormat("%s", outputPrefixTypes == kPrefixTypeLocal ? "" : "Local: ");
OutputIp6PrefixLine(local);
}
/**
* @cli br onlinkprefix favored
* @code
* br onlinkprefix favored
* 2600::0:1234:da12::/64
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetFavoredOnLinkPrefix
*/
if (outputPrefixTypes & kPrefixTypeFavored)
{
otIp6Prefix favored;
SuccessOrExit(error = otBorderRoutingGetFavoredOnLinkPrefix(GetInstancePtr(), &favored));
OutputFormat("%s", outputPrefixTypes == kPrefixTypeFavored ? "" : "Favored: ");
OutputIp6PrefixLine(favored);
}
exit:
return error;
}
#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE
/**
* @cli br nat64prefix
* @code
* br nat64prefix
* Local: fd14:1078:b3d5:b0b0:0:0::/96
* Favored: fd14:1078:b3d5:b0b0:0:0::/96 prf:low
* Done
* @endcode
* @par
* Outputs both local and favored NAT64 prefixes.
* @sa otBorderRoutingGetNat64Prefix
* @sa otBorderRoutingGetFavoredNat64Prefix
*/
template <> otError Br::Process<Cmd("nat64prefix")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
PrefixType outputPrefixTypes;
SuccessOrExit(error = ParsePrefixTypeArgs(aArgs, outputPrefixTypes));
/**
* @cli br nat64prefix local
* @code
* br nat64prefix local
* fd14:1078:b3d5:b0b0:0:0::/96
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetNat64Prefix
*/
if (outputPrefixTypes & kPrefixTypeLocal)
{
otIp6Prefix local;
SuccessOrExit(error = otBorderRoutingGetNat64Prefix(GetInstancePtr(), &local));
OutputFormat("%s", outputPrefixTypes == kPrefixTypeLocal ? "" : "Local: ");
OutputIp6PrefixLine(local);
}
/**
* @cli br nat64prefix favored
* @code
* br nat64prefix favored
* fd14:1078:b3d5:b0b0:0:0::/96 prf:low
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetFavoredNat64Prefix
*/
if (outputPrefixTypes & kPrefixTypeFavored)
{
otIp6Prefix favored;
otRoutePreference preference;
SuccessOrExit(error = otBorderRoutingGetFavoredNat64Prefix(GetInstancePtr(), &favored, &preference));
OutputFormat("%s", outputPrefixTypes == kPrefixTypeFavored ? "" : "Favored: ");
OutputIp6Prefix(favored);
OutputLine(" prf:%s", PreferenceToString(preference));
}
exit:
return error;
}
/**
* @cli br nat64prefixtable
* @code
* br nat64prefixtable
* prefix:fd00:1234:5678:0:0:0::/96, ms-since-rx:29526, lifetime:1800, router:fe80:0:0:0:0:0:0:1 (M:0 O:0 S:1)
* prefix:fd11:2233:4455:0:0:0::/96, ms-since-rx:29527, lifetime:1800, router:fe80:0:0:0:0:0:0:1 (M:0 O:0 S:1)
* Done
* @endcode
* @par
* Get the RA-discovered NAT64 prefixes by Border Routing Manager on the infrastructure link.
* Info per prefix entry:
* - The prefix
* - Milliseconds since last received Router Advertisement containing this prefix
* - Prefix lifetime in seconds
* - The router IPv6 address which advertises this prefix
* - Flags in received Router Advertisement header:
* - M: Managed Address Config flag
* - O: Other Config flag
* - S: SNAC Router flag
* @sa otBorderRoutingGetNextNat64PrefixEntry
*/
template <> otError Br::Process<Cmd("nat64prefixtable")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otBorderRoutingPrefixTableIterator iterator;
otBorderRoutingNat64PrefixEntry entry;
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator);
while (otBorderRoutingGetNextNat64PrefixEntry(GetInstancePtr(), &iterator, &entry) == OT_ERROR_NONE)
{
char string[OT_IP6_PREFIX_STRING_SIZE];
otIp6PrefixToString(&entry.mPrefix, string, sizeof(string));
OutputFormat("prefix:%s, ms-since-rx:%lu, lifetime:%lu, router:", string, ToUlong(entry.mMsecSinceLastUpdate),
ToUlong(entry.mLifetime));
OutputRouterInfo(entry.mRouter, kShortVersion);
}
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE
#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE
template <> otError Br::Process<Cmd("peers")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
/**
* @cli br peers
* @code
* br peers
* rloc16:0x5c00 age:00:00:49
* rloc16:0xf800 age:00:01:51
* Done
* @endcode
* @par
* Get the list of peer BRs found in Network Data entries.
* `OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE` is required.
* Peer BRs are other devices within the Thread mesh that provide external IP connectivity. A device is considered
* to provide external IP connectivity if at least one of the following conditions is met regarding its Network
* Data entries:
* - It has added at least one external route entry.
* - It has added at least one prefix entry with both the default-route and on-mesh flags set.
* - It has added at least one domain prefix (with both the domain and on-mesh flags set).
* The list of peer BRs specifically excludes the current device, even if its is itself acting as a BR.
* Info per BR entry:
* - RLOC16 of the BR
* - Age as the duration interval since this BR appeared in Network Data. It is formatted as `{hh}:{mm}:{ss}` for
* hours, minutes, seconds, if the duration is less than 24 hours. If the duration is 24 hours or more, the
* format is `{dd}d.{hh}:{mm}:{ss}` for days, hours, minutes, seconds.
* @sa otBorderRoutingGetNextPrefixTableEntry
*/
if (aArgs[0].IsEmpty())
{
otBorderRoutingPrefixTableIterator iterator;
otBorderRoutingPeerBorderRouterEntry peerBrEntry;
char ageString[OT_DURATION_STRING_SIZE];
otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator);
while (otBorderRoutingGetNextPeerBrEntry(GetInstancePtr(), &iterator, &peerBrEntry) == OT_ERROR_NONE)
{
otConvertDurationInSecondsToString(peerBrEntry.mAge, ageString, sizeof(ageString));
OutputLine("rloc16:0x%04x age:%s", peerBrEntry.mRloc16, ageString);
}
}
/**
* @cli br peers count
* @code
* br peers count
* 2 min-age:00:00:47
* Done
* @endcode
* @par api_copy
* #otBorderRoutingCountPeerBrs
*/
else if (aArgs[0] == "count")
{
uint32_t minAge;
uint16_t count;
char ageString[OT_DURATION_STRING_SIZE];
VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
count = otBorderRoutingCountPeerBrs(GetInstancePtr(), &minAge);
otConvertDurationInSecondsToString(minAge, ageString, sizeof(ageString));
OutputLine("%u min-age:%s", count, ageString);
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE
/**
* @cli br prefixtable
* @code
* br prefixtable
* prefix:fd00:1234:5678:0::/64, on-link:no, ms-since-rx:29526, lifetime:1800, route-prf:med,
* router:ff02:0:0:0:0:0:0:1 (M:0 O:0 S:1)
* prefix:1200:abba:baba:0::/64, on-link:yes, ms-since-rx:29527, lifetime:1800, preferred:1800,
* router:ff02:0:0:0:0:0:0:1 (M:0 O:0 S:1)
* Done
* @endcode
* @par
* Get the discovered prefixes by Border Routing Manager on the infrastructure link.
* Info per prefix entry:
* - The prefix
* - Whether the prefix is on-link or route
* - Milliseconds since last received Router Advertisement containing this prefix
* - Prefix lifetime in seconds
* - Preferred lifetime in seconds only if prefix is on-link
* - Route preference (low, med, high) only if prefix is route (not on-link)
* - The router IPv6 address which advertises this prefix
* - Flags in received Router Advertisement header:
* - M: Managed Address Config flag
* - O: Other Config flag
* - S: SNAC Router flag
* @sa otBorderRoutingGetNextPrefixTableEntry
*/
template <> otError Br::Process<Cmd("prefixtable")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otBorderRoutingPrefixTableIterator iterator;
otBorderRoutingPrefixTableEntry entry;
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator);
while (otBorderRoutingGetNextPrefixTableEntry(GetInstancePtr(), &iterator, &entry) == OT_ERROR_NONE)
{
char string[OT_IP6_PREFIX_STRING_SIZE];
otIp6PrefixToString(&entry.mPrefix, string, sizeof(string));
OutputFormat("prefix:%s, on-link:%s, ms-since-rx:%lu, lifetime:%lu, ", string, ToYesNo(entry.mIsOnLink),
ToUlong(entry.mMsecSinceLastUpdate), ToUlong(entry.mValidLifetime));
if (entry.mIsOnLink)
{
OutputFormat("preferred:%lu, ", ToUlong(entry.mPreferredLifetime));
}
else
{
OutputFormat("route-prf:%s, ", PreferenceToString(entry.mRoutePreference));
}
OutputFormat("router:");
OutputRouterInfo(entry.mRouter, kShortVersion);
}
exit:
return error;
}
/**
* @cli br rdnsstable
* @code
* br rdnsstable
* fd00:1234:5678::1, lifetime:500, ms-since-rx:29526, router:ff02:0:0:0:0:0:0:1 (M:0 O:0 S:1)
* fd00:aaaa::2, lifetime:500, ms-since-rx:107, router:ff02:0:0:0:0:0:0:1 (M:0 O:0 S:1)
* Done
* @endcode
* @par
* Get the discovered Recursive DNS Server (RDNSS) address table by Border Routing Manager on the infrastructure link.
* Info per entry:
* - IPv6 address
* - Lifetime in seconds
* - Milliseconds since last received Router Advertisement containing this address
* - The router IPv6 address which advertised this prefix
* - Flags in received Router Advertisement header:
* - M: Managed Address Config flag
* - O: Other Config flag
* - S: SNAC Router flag
* @sa otBorderRoutingGetNextRdnssAddrEntry
*/
template <> otError Br::Process<Cmd("rdnsstable")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otBorderRoutingPrefixTableIterator iterator;
otBorderRoutingRdnssAddrEntry entry;
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator);
while (otBorderRoutingGetNextRdnssAddrEntry(GetInstancePtr(), &iterator, &entry) == OT_ERROR_NONE)
{
char string[OT_IP6_ADDRESS_STRING_SIZE];
otIp6AddressToString(&entry.mAddress, string, sizeof(string));
OutputFormat("%s, lifetime:%lu, ms-since-rx:%lu, router:", string, ToUlong(entry.mLifetime),
ToUlong(entry.mMsecSinceLastUpdate));
OutputRouterInfo(entry.mRouter, kShortVersion);
}
exit:
return error;
}
/**
* @cli br ifaddrs
* @code
* br ifaddrs
* fe80::896:228b:4ae0:8609, sec-since-use:15
* Done
* @endcode
* @par
* Get the infrastructure interface addresses. These are addresses used by the BR itself, for example, when sending
* Router Advertisements.
* Info per entry:
* - IPv6 address
* - Seconds since the last RA was sent from this BR using this address.
* @sa otBorderRoutingGetNextIfAddrEntry
*/
template <> otError Br::Process<Cmd("ifaddrs")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otBorderRoutingPrefixTableIterator iterator;
otBorderRoutingIfAddrEntry entry;
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator);
while (otBorderRoutingGetNextIfAddrEntry(GetInstancePtr(), &iterator, &entry) == OT_ERROR_NONE)
{
char string[OT_IP6_ADDRESS_STRING_SIZE];
otIp6AddressToString(&entry.mAddress, string, sizeof(string));
OutputLine("%s, sec-since-use:%lu", string, ToUlong(entry.mSecSinceLastUse));
}
exit:
return error;
}
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
const char *Br::Dhcp6PdStateToString(otBorderRoutingDhcp6PdState aState)
{
static const char *const kDhcpv6PdStateStrings[] = {
"disabled", // (0) OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED
"stopped", // (1) OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED
"running", // (2) OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING
"idle", // (3) OT_BORDER_ROUTING_DHCP6_PD_STATE_IDLE
};
static_assert(0 == OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED, "DHCP6_PD_STATE_DISABLED value is incorrect");
static_assert(1 == OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED, "DHCP6_PD_STATE_STOPPED value is incorrect");
static_assert(2 == OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING, "DHCP6_PD_STATE_RUNNING value is incorrect");
static_assert(3 == OT_BORDER_ROUTING_DHCP6_PD_STATE_IDLE, "DHCP6_PD_STATE_IDLE value is incorrect");
return Stringify(aState, kDhcpv6PdStateStrings);
}
template <> otError Br::Process<Cmd("pd")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
/**
* @cli br pd (enable,disable)
* @code
* br pd enable
* Done
* @endcode
* @code
* br pd disable
* Done
* @endcode
* @cparam br pd @ca{enable|disable}
* @par api_copy
* #otBorderRoutingDhcp6PdSetEnabled
*/
if (ProcessEnableDisable(aArgs, otBorderRoutingDhcp6PdSetEnabled) == OT_ERROR_NONE)
{
}
/**
* @cli br pd state
* @code
* br pd state
* running
* Done
* @endcode
* @par api_copy
* #otBorderRoutingDhcp6PdGetState
*/
else if (aArgs[0] == "state")
{
OutputLine("%s", Dhcp6PdStateToString(otBorderRoutingDhcp6PdGetState(GetInstancePtr())));
}
/**
* @cli br pd omrprefix
* @code
* br pd omrprefix
* 2001:db8:cafe:0:0/64 lifetime:1800 preferred:1800
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetPdOmrPrefix
*/
else if (aArgs[0] == "omrprefix")
{
otBorderRoutingPrefixTableEntry entry;
SuccessOrExit(error = otBorderRoutingGetPdOmrPrefix(GetInstancePtr(), &entry));
OutputIp6Prefix(entry.mPrefix);
OutputLine(" lifetime:%lu preferred:%lu", ToUlong(entry.mValidLifetime), ToUlong(entry.mPreferredLifetime));
}
else
{
ExitNow(error = OT_ERROR_INVALID_COMMAND);
}
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
/**
* @cli br routers
* @code
* br routers
* ff02:0:0:0:0:0:0:1 (M:0 O:0 S:1) ms-since-rx:1505 reachable:yes age:00:18:13
* Done
* @endcode
* @par
* Get the list of discovered routers by Border Routing Manager on the infrastructure link.
* Info per router:
* - The router IPv6 address
* - Flags in received Router Advertisement header:
* - M: Managed Address Config flag
* - O: Other Config flag
* - S: SNAC Router flag (indicates whether the router is a stub router)
* - Milliseconds since last received message from this router
* - Reachability flag: A router is marked as unreachable if it fails to respond to multiple Neighbor Solicitation
* probes.
* - Age: Duration interval since this router was first discovered. It is formatted as `{hh}:{mm}:{ss}` for hours,
* minutes, seconds, if the duration is less than 24 hours. If the duration is 24 hours or more, the format is
* `{dd}d.{hh}:{mm}:{ss}` for days, hours, minutes, seconds.
* - `(this BR)` is appended when the router is the local device itself.
* - `(peer BR)` is appended when the router is likely a peer BR connected to the same Thread mesh. This requires
* `OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE`.
* @sa otBorderRoutingGetNextRouterEntry
*/
template <> otError Br::Process<Cmd("routers")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
otBorderRoutingPrefixTableIterator iterator;
otBorderRoutingRouterEntry entry;
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator);
while (otBorderRoutingGetNextRouterEntry(GetInstancePtr(), &iterator, &entry) == OT_ERROR_NONE)
{
OutputRouterInfo(entry, kLongVersion);
}
exit:
return error;
}
void Br::OutputRouterInfo(const otBorderRoutingRouterEntry &aEntry, RouterOutputMode aMode)
{
OutputIp6Address(aEntry.mAddress);
OutputFormat(" (M:%u O:%u S:%u)", aEntry.mManagedAddressConfigFlag, aEntry.mOtherConfigFlag,
aEntry.mSnacRouterFlag);
if (aMode == kLongVersion)
{
char ageString[OT_DURATION_STRING_SIZE];
otConvertDurationInSecondsToString(aEntry.mAge, ageString, sizeof(ageString));
OutputFormat(" ms-since-rx:%lu reachable:%s age:%s", ToUlong(aEntry.mMsecSinceLastUpdate),
ToYesNo(aEntry.mIsReachable), ageString);
if (aEntry.mIsLocalDevice)
{
OutputFormat(" (this BR)");
}
#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE
if (aEntry.mIsPeerBr)
{
OutputFormat(" (peer BR)");
}
#endif
}
OutputNewLine();
}
template <> otError Br::Process<Cmd("raoptions")>(Arg aArgs[])
{
static constexpr uint16_t kMaxExtraOptions = 800;
otError error = OT_ERROR_NONE;
uint8_t options[kMaxExtraOptions];
uint16_t length;
/**
* @cli br raoptions (set,clear)
* @code
* br raoptions 0400ff00020001
* Done
* @endcode
* @code
* br raoptions clear
* Done
* @endcode
* @cparam br raoptions @ca{options|clear}
* `br raoptions clear` passes a `nullptr` to #otBorderRoutingSetExtraRouterAdvertOptions.
* Otherwise, you can pass the `options` byte as hex data.
* @par api_copy
* #otBorderRoutingSetExtraRouterAdvertOptions
*/
if (aArgs[0] == "clear")
{
length = 0;
}
else
{
length = sizeof(options);
SuccessOrExit(error = aArgs[0].ParseAsHexString(length, options));
}
error = otBorderRoutingSetExtraRouterAdvertOptions(GetInstancePtr(), length > 0 ? options : nullptr, length);
exit:
return error;
}
template <> otError Br::Process<Cmd("rioprf")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
/**
* @cli br rioprf
* @code
* br rioprf
* med
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetRouteInfoOptionPreference
*/
if (aArgs[0].IsEmpty())
{
OutputLine("%s", PreferenceToString(otBorderRoutingGetRouteInfoOptionPreference(GetInstancePtr())));
}
/**
* @cli br rioprf clear
* @code
* br rioprf clear
* Done
* @endcode
* @par api_copy
* #otBorderRoutingClearRouteInfoOptionPreference
*/
else if (aArgs[0] == "clear")
{
otBorderRoutingClearRouteInfoOptionPreference(GetInstancePtr());
}
/**
* @cli br rioprf (high,med,low)
* @code
* br rioprf low
* Done
* @endcode
* @cparam br rioprf [@ca{high}|@ca{med}|@ca{low}]
* @par api_copy
* #otBorderRoutingSetRouteInfoOptionPreference
*/
else
{
otRoutePreference preference;
SuccessOrExit(error = Interpreter::ParsePreference(aArgs[0], preference));
otBorderRoutingSetRouteInfoOptionPreference(GetInstancePtr(), preference);
}
exit:
return error;
}
template <> otError Br::Process<Cmd("routeprf")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
/**
* @cli br routeprf
* @code
* br routeprf
* med
* Done
* @endcode
* @par api_copy
* #otBorderRoutingGetRoutePreference
*/
if (aArgs[0].IsEmpty())
{
OutputLine("%s", PreferenceToString(otBorderRoutingGetRoutePreference(GetInstancePtr())));
}
/**
* @cli br routeprf clear
* @code
* br routeprf clear
* Done
* @endcode
* @par api_copy
* #otBorderRoutingClearRoutePreference
*/
else if (aArgs[0] == "clear")
{
otBorderRoutingClearRoutePreference(GetInstancePtr());
}
/**
* @cli br routeprf (high,med,low)
* @code
* br routeprf low
* Done
* @endcode
* @cparam br routeprf [@ca{high}|@ca{med}|@ca{low}]
* @par api_copy
* #otBorderRoutingSetRoutePreference
*/
else
{
otRoutePreference preference;
SuccessOrExit(error = Interpreter::ParsePreference(aArgs[0], preference));
otBorderRoutingSetRoutePreference(GetInstancePtr(), preference);
}
exit:
return error;
}
#if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE
/**
* @cli br counters
* @code
* br counters
* Inbound Unicast: Packets 4 Bytes 320
* Inbound Multicast: Packets 0 Bytes 0
* Outbound Unicast: Packets 2 Bytes 160
* Outbound Multicast: Packets 0 Bytes 0
* RA Rx: 4
* RA TxSuccess: 2
* RA TxFailed: 0
* RS Rx: 0
* RS TxSuccess: 2
* RS TxFailed: 0
* Done
* @endcode
* @par api_copy
* #otIp6GetBorderRoutingCounters
*/
template <> otError Br::Process<Cmd("counters")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
Interpreter::GetInterpreter().OutputBorderRouterCounters();
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE
otError Br::Process(Arg aArgs[])
{
#define CmdEntry(aCommandString) {aCommandString, &Br::Process<Cmd(aCommandString)>}
static constexpr Command kCommands[] = {
#if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE
CmdEntry("counters"),
#endif
CmdEntry("disable"), CmdEntry("enable"), CmdEntry("ifaddrs"), CmdEntry("infraif"),
CmdEntry("init"),
#if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE
CmdEntry("multiail"),
#endif
#if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE
CmdEntry("nat64prefix"), CmdEntry("nat64prefixtable"),
#endif
CmdEntry("omrconfig"), CmdEntry("omrprefix"), CmdEntry("onlinkprefix"),
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
CmdEntry("pd"),
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE
CmdEntry("peers"),
#endif
CmdEntry("prefixtable"), CmdEntry("raoptions"), CmdEntry("rdnsstable"), CmdEntry("rioprf"),
CmdEntry("routeprf"), CmdEntry("routers"), CmdEntry("state"),
};
#undef CmdEntry
static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
otError error = OT_ERROR_INVALID_COMMAND;
const Command *command;
if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
{
OutputCommandTable(kCommands);
ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
}
command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
VerifyOrExit(command != nullptr);
error = (this->*command->mHandler)(aArgs + 1);
exit:
return error;
}
} // namespace Cli
} // namespace ot
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE