1 /*
2  *  Copyright (c) 2023, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /**
30  * @file
31  *   This file implements CLI for Border Router.
32  */
33 
34 #include "cli_br.hpp"
35 
36 #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
37 
38 #include <string.h>
39 
40 #include "cli/cli.hpp"
41 
42 namespace ot {
43 namespace Cli {
44 
45 /**
46  * @cli br init
47  * @code
48  * br init 2 1
49  * Done
50  * @endcode
51  * @cparam br init @ca{infrastructure-network-index} @ca{is-running}
52  * @par
53  * Initializes the Border Routing Manager.
54  * @sa otBorderRoutingInit
55  */
Process(Arg aArgs[])56 template <> otError Br::Process<Cmd("init")>(Arg aArgs[])
57 {
58     otError  error = OT_ERROR_NONE;
59     uint32_t ifIndex;
60     bool     isRunning;
61 
62     SuccessOrExit(error = aArgs[0].ParseAsUint32(ifIndex));
63     SuccessOrExit(error = aArgs[1].ParseAsBool(isRunning));
64     VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
65     error = otBorderRoutingInit(GetInstancePtr(), ifIndex, isRunning);
66 
67 exit:
68     return error;
69 }
70 
71 /**
72  * @cli br enable
73  * @code
74  * br enable
75  * Done
76  * @endcode
77  * @par
78  * Enables the Border Routing Manager.
79  * @sa otBorderRoutingSetEnabled
80  */
Process(Arg aArgs[])81 template <> otError Br::Process<Cmd("enable")>(Arg aArgs[])
82 {
83     otError error = OT_ERROR_NONE;
84 
85     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
86     error = otBorderRoutingSetEnabled(GetInstancePtr(), true);
87 
88 exit:
89     return error;
90 }
91 
92 /**
93  * @cli br disable
94  * @code
95  * br disable
96  * Done
97  * @endcode
98  * @par
99  * Disables the Border Routing Manager.
100  * @sa otBorderRoutingSetEnabled
101  */
Process(Arg aArgs[])102 template <> otError Br::Process<Cmd("disable")>(Arg aArgs[])
103 {
104     otError error = OT_ERROR_NONE;
105 
106     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
107     error = otBorderRoutingSetEnabled(GetInstancePtr(), false);
108 
109 exit:
110     return error;
111 }
112 
113 /**
114  * @cli br state
115  * @code
116  * br state
117  * running
118  * @endcode
119  * @par api_copy
120  * #otBorderRoutingGetState
121  */
Process(Arg aArgs[])122 template <> otError Br::Process<Cmd("state")>(Arg aArgs[])
123 {
124     static const char *const kStateStrings[] = {
125 
126         "uninitialized", // (0) OT_BORDER_ROUTING_STATE_UNINITIALIZED
127         "disabled",      // (1) OT_BORDER_ROUTING_STATE_DISABLED
128         "stopped",       // (2) OT_BORDER_ROUTING_STATE_STOPPED
129         "running",       // (3) OT_BORDER_ROUTING_STATE_RUNNING
130     };
131 
132     otError error = OT_ERROR_NONE;
133 
134     static_assert(0 == OT_BORDER_ROUTING_STATE_UNINITIALIZED, "STATE_UNINITIALIZED value is incorrect");
135     static_assert(1 == OT_BORDER_ROUTING_STATE_DISABLED, "STATE_DISABLED value is incorrect");
136     static_assert(2 == OT_BORDER_ROUTING_STATE_STOPPED, "STATE_STOPPED value is incorrect");
137     static_assert(3 == OT_BORDER_ROUTING_STATE_RUNNING, "STATE_RUNNING value is incorrect");
138 
139     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
140     OutputLine("%s", Stringify(otBorderRoutingGetState(GetInstancePtr()), kStateStrings));
141 
142 exit:
143     return error;
144 }
145 
ParsePrefixTypeArgs(Arg aArgs[],PrefixType & aFlags)146 otError Br::ParsePrefixTypeArgs(Arg aArgs[], PrefixType &aFlags)
147 {
148     otError error = OT_ERROR_NONE;
149 
150     aFlags = 0;
151 
152     if (aArgs[0].IsEmpty())
153     {
154         aFlags = kPrefixTypeFavored | kPrefixTypeLocal;
155         ExitNow();
156     }
157 
158     if (aArgs[0] == "local")
159     {
160         aFlags = kPrefixTypeLocal;
161     }
162     else if (aArgs[0] == "favored")
163     {
164         aFlags = kPrefixTypeFavored;
165     }
166     else
167     {
168         ExitNow(error = OT_ERROR_INVALID_ARGS);
169     }
170 
171     VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
172 
173 exit:
174     return error;
175 }
176 
177 /**
178  * @cli br omrprefix
179  * @code
180  * br omrprefix
181  * Local: fdfc:1ff5:1512:5622::/64
182  * Favored: fdfc:1ff5:1512:5622::/64 prf:low
183  * Done
184  * @endcode
185  * @par
186  * Outputs both local and favored OMR prefix.
187  * @sa otBorderRoutingGetOmrPrefix
188  * @sa otBorderRoutingGetFavoredOmrPrefix
189  */
Process(Arg aArgs[])190 template <> otError Br::Process<Cmd("omrprefix")>(Arg aArgs[])
191 {
192     otError    error = OT_ERROR_NONE;
193     PrefixType outputPrefixTypes;
194 
195     SuccessOrExit(error = ParsePrefixTypeArgs(aArgs, outputPrefixTypes));
196 
197     /**
198      * @cli br omrprefix local
199      * @code
200      * br omrprefix local
201      * fdfc:1ff5:1512:5622::/64
202      * Done
203      * @endcode
204      * @par api_copy
205      * #otBorderRoutingGetOmrPrefix
206      */
207     if (outputPrefixTypes & kPrefixTypeLocal)
208     {
209         otIp6Prefix local;
210 
211         SuccessOrExit(error = otBorderRoutingGetOmrPrefix(GetInstancePtr(), &local));
212 
213         OutputFormat("%s", outputPrefixTypes == kPrefixTypeLocal ? "" : "Local: ");
214         OutputIp6PrefixLine(local);
215     }
216 
217     /**
218      * @cli br omrprefix favored
219      * @code
220      * br omrprefix favored
221      * fdfc:1ff5:1512:5622::/64 prf:low
222      * Done
223      * @endcode
224      * @par api_copy
225      * #otBorderRoutingGetFavoredOmrPrefix
226      */
227     if (outputPrefixTypes & kPrefixTypeFavored)
228     {
229         otIp6Prefix       favored;
230         otRoutePreference preference;
231 
232         SuccessOrExit(error = otBorderRoutingGetFavoredOmrPrefix(GetInstancePtr(), &favored, &preference));
233 
234         OutputFormat("%s", outputPrefixTypes == kPrefixTypeFavored ? "" : "Favored: ");
235         OutputIp6Prefix(favored);
236         OutputLine(" prf:%s", PreferenceToString(preference));
237     }
238 
239 exit:
240     return error;
241 }
242 
243 /**
244  * @cli br onlinkprefix
245  * @code
246  * br onlinkprefix
247  * Local: fd41:2650:a6f5:0::/64
248  * Favored: 2600::0:1234:da12::/64
249  * Done
250  * @endcode
251  * @par
252  * Outputs both local and favored on-link prefixes.
253  * @sa otBorderRoutingGetOnLinkPrefix
254  * @sa otBorderRoutingGetFavoredOnLinkPrefix
255  */
Process(Arg aArgs[])256 template <> otError Br::Process<Cmd("onlinkprefix")>(Arg aArgs[])
257 {
258     otError    error = OT_ERROR_NONE;
259     PrefixType outputPrefixTypes;
260 
261 #if OPENTHREAD_CONFIG_BORDER_ROUTING_TESTING_API_ENABLE
262     if (aArgs[0] == "test")
263     {
264         otIp6Prefix prefix;
265 
266         SuccessOrExit(error = aArgs[1].ParseAsIp6Prefix(prefix));
267         otBorderRoutingSetOnLinkPrefix(GetInstancePtr(), &prefix);
268         ExitNow();
269     }
270 #endif
271 
272     error = ParsePrefixTypeArgs(aArgs, outputPrefixTypes);
273 
274     SuccessOrExit(error);
275 
276     /**
277      * @cli br onlinkprefix local
278      * @code
279      * br onlinkprefix local
280      * fd41:2650:a6f5:0::/64
281      * Done
282      * @endcode
283      * @par api_copy
284      * #otBorderRoutingGetOnLinkPrefix
285      */
286     if (outputPrefixTypes & kPrefixTypeLocal)
287     {
288         otIp6Prefix local;
289 
290         SuccessOrExit(error = otBorderRoutingGetOnLinkPrefix(GetInstancePtr(), &local));
291 
292         OutputFormat("%s", outputPrefixTypes == kPrefixTypeLocal ? "" : "Local: ");
293         OutputIp6PrefixLine(local);
294     }
295 
296     /**
297      * @cli br onlinkprefix favored
298      * @code
299      * br onlinkprefix favored
300      * 2600::0:1234:da12::/64
301      * Done
302      * @endcode
303      * @par api_copy
304      * #otBorderRoutingGetFavoredOnLinkPrefix
305      */
306     if (outputPrefixTypes & kPrefixTypeFavored)
307     {
308         otIp6Prefix favored;
309 
310         SuccessOrExit(error = otBorderRoutingGetFavoredOnLinkPrefix(GetInstancePtr(), &favored));
311 
312         OutputFormat("%s", outputPrefixTypes == kPrefixTypeFavored ? "" : "Favored: ");
313         OutputIp6PrefixLine(favored);
314     }
315 
316 exit:
317     return error;
318 }
319 
320 #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE
321 
322 /**
323  * @cli br nat64prefix
324  * @code
325  * br nat64prefix
326  * Local: fd14:1078:b3d5:b0b0:0:0::/96
327  * Favored: fd14:1078:b3d5:b0b0:0:0::/96 prf:low
328  * Done
329  * @endcode
330  * @par
331  * Outputs both local and favored NAT64 prefixes.
332  * @sa otBorderRoutingGetNat64Prefix
333  * @sa otBorderRoutingGetFavoredNat64Prefix
334  */
Process(Arg aArgs[])335 template <> otError Br::Process<Cmd("nat64prefix")>(Arg aArgs[])
336 {
337     otError    error = OT_ERROR_NONE;
338     PrefixType outputPrefixTypes;
339 
340     SuccessOrExit(error = ParsePrefixTypeArgs(aArgs, outputPrefixTypes));
341 
342     /**
343      * @cli br nat64prefix local
344      * @code
345      * br nat64prefix local
346      * fd14:1078:b3d5:b0b0:0:0::/96
347      * Done
348      * @endcode
349      * @par api_copy
350      * #otBorderRoutingGetNat64Prefix
351      */
352     if (outputPrefixTypes & kPrefixTypeLocal)
353     {
354         otIp6Prefix local;
355 
356         SuccessOrExit(error = otBorderRoutingGetNat64Prefix(GetInstancePtr(), &local));
357 
358         OutputFormat("%s", outputPrefixTypes == kPrefixTypeLocal ? "" : "Local: ");
359         OutputIp6PrefixLine(local);
360     }
361 
362     /**
363      * @cli br nat64prefix favored
364      * @code
365      * br nat64prefix favored
366      * fd14:1078:b3d5:b0b0:0:0::/96 prf:low
367      * Done
368      * @endcode
369      * @par api_copy
370      * #otBorderRoutingGetFavoredNat64Prefix
371      */
372     if (outputPrefixTypes & kPrefixTypeFavored)
373     {
374         otIp6Prefix       favored;
375         otRoutePreference preference;
376 
377         SuccessOrExit(error = otBorderRoutingGetFavoredNat64Prefix(GetInstancePtr(), &favored, &preference));
378 
379         OutputFormat("%s", outputPrefixTypes == kPrefixTypeFavored ? "" : "Favored: ");
380         OutputIp6Prefix(favored);
381         OutputLine(" prf:%s", PreferenceToString(preference));
382     }
383 
384 exit:
385     return error;
386 }
387 
388 #endif // OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE
389 
390 #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE
391 
Process(Arg aArgs[])392 template <> otError Br::Process<Cmd("peers")>(Arg aArgs[])
393 {
394     otError error = OT_ERROR_NONE;
395 
396     /**
397      * @cli br peers
398      * @code
399      * br peers
400      * rloc16:0x5c00 age:00:00:49
401      * rloc16:0xf800 age:00:01:51
402      * Done
403      * @endcode
404      * @par
405      * Get the list of peer BRs found in Network Data entries.
406      * `OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE` is required.
407      * Peer BRs are other devices within the Thread mesh that provide external IP connectivity. A device is considered
408      * to provide external IP connectivity if at least one of the following conditions is met regarding its Network
409      * Data entries:
410      * - It has added at least one external route entry.
411      * - It has added at least one prefix entry with both the default-route and on-mesh flags set.
412      * - It has added at least one domain prefix (with both the domain and on-mesh flags set).
413      * The list of peer BRs specifically excludes the current device, even if its is itself acting as a BR.
414      * Info per BR entry:
415      * - RLOC16 of the BR
416      * - Age as the duration interval since this BR appeared in Network Data. It is formatted as `{hh}:{mm}:{ss}` for
417      *   hours, minutes, seconds, if the duration is less than 24 hours. If the duration is 24 hours or more, the
418      *   format is `{dd}d.{hh}:{mm}:{ss}` for days, hours, minutes, seconds.
419      * @sa otBorderRoutingGetNextPrefixTableEntry
420      */
421     if (aArgs[0].IsEmpty())
422     {
423         otBorderRoutingPrefixTableIterator   iterator;
424         otBorderRoutingPeerBorderRouterEntry peerBrEntry;
425         char                                 ageString[OT_DURATION_STRING_SIZE];
426 
427         otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator);
428 
429         while (otBorderRoutingGetNextPeerBrEntry(GetInstancePtr(), &iterator, &peerBrEntry) == OT_ERROR_NONE)
430         {
431             otConvertDurationInSecondsToString(peerBrEntry.mAge, ageString, sizeof(ageString));
432             OutputLine("rloc16:0x%04x age:%s", peerBrEntry.mRloc16, ageString);
433         }
434     }
435     /**
436      * @cli br peers count
437      * @code
438      * br peers count
439      * 2 min-age:00:00:47
440      * Done
441      * @endcode
442      * @par api_copy
443      * #otBorderRoutingCountPeerBrs
444      */
445     else if (aArgs[0] == "count")
446     {
447         uint32_t minAge;
448         uint16_t count;
449         char     ageString[OT_DURATION_STRING_SIZE];
450 
451         VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
452 
453         count = otBorderRoutingCountPeerBrs(GetInstancePtr(), &minAge);
454         otConvertDurationInSecondsToString(minAge, ageString, sizeof(ageString));
455         OutputLine("%u min-age:%s", count, ageString);
456     }
457     else
458     {
459         error = OT_ERROR_INVALID_ARGS;
460     }
461 
462 exit:
463     return error;
464 }
465 
466 #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE
467 
468 /**
469  * @cli br prefixtable
470  * @code
471  * br prefixtable
472  * prefix:fd00:1234:5678:0::/64, on-link:no, ms-since-rx:29526, lifetime:1800, route-prf:med,
473  * router:ff02:0:0:0:0:0:0:1 (M:0 O:0 Stub:1)
474  * prefix:1200:abba:baba:0::/64, on-link:yes, ms-since-rx:29527, lifetime:1800, preferred:1800,
475  * router:ff02:0:0:0:0:0:0:1 (M:0 O:0 Stub:1)
476  * Done
477  * @endcode
478  * @par
479  * Get the discovered prefixes by Border Routing Manager on the infrastructure link.
480  * Info per prefix entry:
481  * - The prefix
482  * - Whether the prefix is on-link or route
483  * - Milliseconds since last received Router Advertisement containing this prefix
484  * - Prefix lifetime in seconds
485  * - Preferred lifetime in seconds only if prefix is on-link
486  * - Route preference (low, med, high) only if prefix is route (not on-link)
487  * - The router IPv6 address which advertising this prefix
488  * - Flags in received Router Advertisement header:
489  *   - M: Managed Address Config flag
490  *   - O: Other Config flag
491  *   - Stub: Stub Router flag (indicates whether the router is a stub router)
492  * @sa otBorderRoutingGetNextPrefixTableEntry
493  */
Process(Arg aArgs[])494 template <> otError Br::Process<Cmd("prefixtable")>(Arg aArgs[])
495 {
496     otError                            error = OT_ERROR_NONE;
497     otBorderRoutingPrefixTableIterator iterator;
498     otBorderRoutingPrefixTableEntry    entry;
499 
500     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
501 
502     otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator);
503 
504     while (otBorderRoutingGetNextPrefixTableEntry(GetInstancePtr(), &iterator, &entry) == OT_ERROR_NONE)
505     {
506         char string[OT_IP6_PREFIX_STRING_SIZE];
507 
508         otIp6PrefixToString(&entry.mPrefix, string, sizeof(string));
509         OutputFormat("prefix:%s, on-link:%s, ms-since-rx:%lu, lifetime:%lu, ", string, entry.mIsOnLink ? "yes" : "no",
510                      ToUlong(entry.mMsecSinceLastUpdate), ToUlong(entry.mValidLifetime));
511 
512         if (entry.mIsOnLink)
513         {
514             OutputFormat("preferred:%lu, ", ToUlong(entry.mPreferredLifetime));
515         }
516         else
517         {
518             OutputFormat("route-prf:%s, ", PreferenceToString(entry.mRoutePreference));
519         }
520 
521         OutputFormat("router:");
522         OutputRouterInfo(entry.mRouter, kShortVersion);
523     }
524 
525 exit:
526     return error;
527 }
528 
529 #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
Process(Arg aArgs[])530 template <> otError Br::Process<Cmd("pd")>(Arg aArgs[])
531 {
532     otError error = OT_ERROR_NONE;
533 
534     /**
535      * @cli br pd (enable,disable)
536      * @code
537      * br pd enable
538      * Done
539      * @endcode
540      * @code
541      * br pd disable
542      * Done
543      * @endcode
544      * @cparam br pd @ca{enable|disable}
545      * @par api_copy
546      * #otBorderRoutingDhcp6PdSetEnabled
547      *
548      */
549     if (ProcessEnableDisable(aArgs, otBorderRoutingDhcp6PdSetEnabled) == OT_ERROR_NONE)
550     {
551     }
552     /**
553      * @cli br pd state
554      * @code
555      * br pd state
556      * running
557      * Done
558      * @endcode
559      * @par api_copy
560      * #otBorderRoutingDhcp6PdGetState
561      */
562     else if (aArgs[0] == "state")
563     {
564         static const char *const kDhcpv6PdStateStrings[] = {
565             "disabled", // (0) OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED
566             "stopped",  // (1) OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED
567             "running",  // (2) OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING
568         };
569 
570         static_assert(0 == OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED,
571                       "OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED value is not expected!");
572         static_assert(1 == OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED,
573                       "OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED value is not expected!");
574         static_assert(2 == OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING,
575                       "OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING value is not expected!");
576 
577         OutputLine("%s", Stringify(otBorderRoutingDhcp6PdGetState(GetInstancePtr()), kDhcpv6PdStateStrings));
578     }
579     /**
580      * @cli br pd omrprefix
581      * @code
582      * br pd omrprefix
583      * 2001:db8:cafe:0:0/64 lifetime:1800 preferred:1800
584      * Done
585      * @endcode
586      * @par api_copy
587      * #otBorderRoutingGetPdOmrPrefix
588      */
589     else if (aArgs[0] == "omrprefix")
590     {
591         otBorderRoutingPrefixTableEntry entry;
592 
593         SuccessOrExit(error = otBorderRoutingGetPdOmrPrefix(GetInstancePtr(), &entry));
594 
595         OutputIp6Prefix(entry.mPrefix);
596         OutputLine(" lifetime:%lu preferred:%lu", ToUlong(entry.mValidLifetime), ToUlong(entry.mPreferredLifetime));
597     }
598     else
599     {
600         ExitNow(error = OT_ERROR_INVALID_COMMAND);
601     }
602 
603 exit:
604     return error;
605 }
606 #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
607 
608 /**
609  * @cli br routers
610  * @code
611  * br routers
612  * ff02:0:0:0:0:0:0:1 (M:0 O:0 Stub:1) ms-since-rx:1505 reachable:yes age:00:18:13
613  * Done
614  * @endcode
615  * @par
616  * Get the list of discovered routers by Border Routing Manager on the infrastructure link.
617  * Info per router:
618  * - The router IPv6 address
619  * - Flags in received Router Advertisement header:
620  *   - M: Managed Address Config flag
621  *   - O: Other Config flag
622  *   - Stub: Stub Router flag (indicates whether the router is a stub router)
623  * - Milliseconds since last received message from this router
624  * - Reachability flag: A router is marked as unreachable if it fails to respond to multiple Neighbor Solicitation
625  *   probes.
626  * - Age: Duration interval since this router was first discovered. It is formatted as `{hh}:{mm}:{ss}` for  hours,
627  *   minutes, seconds, if the duration is less than 24 hours. If the duration is 24 hours or more, the format is
628  *   `{dd}d.{hh}:{mm}:{ss}` for days, hours, minutes, seconds.
629  * - `(this BR)` is appended when the router is the local device itself.
630  * - `(peer BR)` is appended when the router is likely a peer BR connected to the same Thread mesh. This requires
631  *   `OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE`.
632  * @sa otBorderRoutingGetNextRouterEntry
633  */
Process(Arg aArgs[])634 template <> otError Br::Process<Cmd("routers")>(Arg aArgs[])
635 {
636     otError                            error = OT_ERROR_NONE;
637     otBorderRoutingPrefixTableIterator iterator;
638     otBorderRoutingRouterEntry         entry;
639 
640     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
641 
642     otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator);
643 
644     while (otBorderRoutingGetNextRouterEntry(GetInstancePtr(), &iterator, &entry) == OT_ERROR_NONE)
645     {
646         OutputRouterInfo(entry, kLongVersion);
647     }
648 
649 exit:
650     return error;
651 }
652 
OutputRouterInfo(const otBorderRoutingRouterEntry & aEntry,RouterOutputMode aMode)653 void Br::OutputRouterInfo(const otBorderRoutingRouterEntry &aEntry, RouterOutputMode aMode)
654 {
655     OutputIp6Address(aEntry.mAddress);
656     OutputFormat(" (M:%u O:%u Stub:%u)", aEntry.mManagedAddressConfigFlag, aEntry.mOtherConfigFlag,
657                  aEntry.mStubRouterFlag);
658 
659     if (aMode == kLongVersion)
660     {
661         char ageString[OT_DURATION_STRING_SIZE];
662 
663         otConvertDurationInSecondsToString(aEntry.mAge, ageString, sizeof(ageString));
664 
665         OutputFormat(" ms-since-rx:%lu reachable:%s age:%s", ToUlong(aEntry.mMsecSinceLastUpdate),
666                      aEntry.mIsReachable ? "yes" : "no", ageString);
667 
668         if (aEntry.mIsLocalDevice)
669         {
670             OutputFormat(" (this BR)");
671         }
672 
673 #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE
674         if (aEntry.mIsPeerBr)
675         {
676             OutputFormat(" (peer BR)");
677         }
678 #endif
679     }
680 
681     OutputNewLine();
682 }
683 
Process(Arg aArgs[])684 template <> otError Br::Process<Cmd("raoptions")>(Arg aArgs[])
685 {
686     static constexpr uint16_t kMaxExtraOptions = 800;
687 
688     otError  error = OT_ERROR_NONE;
689     uint8_t  options[kMaxExtraOptions];
690     uint16_t length;
691 
692     /**
693      * @cli br raoptions (set,clear)
694      * @code
695      * br raoptions 0400ff00020001
696      * Done
697      * @endcode
698      * @code
699      * br raoptions clear
700      * Done
701      * @endcode
702      * @cparam br raoptions @ca{options|clear}
703      * `br raoptions clear` passes a `nullptr` to #otBorderRoutingSetExtraRouterAdvertOptions.
704      * Otherwise, you can pass the `options` byte as hex data.
705      * @par api_copy
706      * #otBorderRoutingSetExtraRouterAdvertOptions
707      */
708     if (aArgs[0] == "clear")
709     {
710         length = 0;
711     }
712     else
713     {
714         length = sizeof(options);
715         SuccessOrExit(error = aArgs[0].ParseAsHexString(length, options));
716     }
717 
718     error = otBorderRoutingSetExtraRouterAdvertOptions(GetInstancePtr(), length > 0 ? options : nullptr, length);
719 
720 exit:
721     return error;
722 }
723 
Process(Arg aArgs[])724 template <> otError Br::Process<Cmd("rioprf")>(Arg aArgs[])
725 {
726     otError error = OT_ERROR_NONE;
727 
728     /**
729      * @cli br rioprf
730      * @code
731      * br rioprf
732      * med
733      * Done
734      * @endcode
735      * @par api_copy
736      * #otBorderRoutingGetRouteInfoOptionPreference
737      */
738     if (aArgs[0].IsEmpty())
739     {
740         OutputLine("%s", PreferenceToString(otBorderRoutingGetRouteInfoOptionPreference(GetInstancePtr())));
741     }
742     /**
743      * @cli br rioprf clear
744      * @code
745      * br rioprf clear
746      * Done
747      * @endcode
748      * @par api_copy
749      * #otBorderRoutingClearRouteInfoOptionPreference
750      */
751     else if (aArgs[0] == "clear")
752     {
753         otBorderRoutingClearRouteInfoOptionPreference(GetInstancePtr());
754     }
755     /**
756      * @cli br rioprf (high,med,low)
757      * @code
758      * br rioprf low
759      * Done
760      * @endcode
761      * @cparam br rioprf [@ca{high}|@ca{med}|@ca{low}]
762      * @par api_copy
763      * #otBorderRoutingSetRouteInfoOptionPreference
764      */
765     else
766     {
767         otRoutePreference preference;
768 
769         SuccessOrExit(error = Interpreter::ParsePreference(aArgs[0], preference));
770         otBorderRoutingSetRouteInfoOptionPreference(GetInstancePtr(), preference);
771     }
772 
773 exit:
774     return error;
775 }
776 
Process(Arg aArgs[])777 template <> otError Br::Process<Cmd("routeprf")>(Arg aArgs[])
778 {
779     otError error = OT_ERROR_NONE;
780 
781     /**
782      * @cli br routeprf
783      * @code
784      * br routeprf
785      * med
786      * Done
787      * @endcode
788      * @par api_copy
789      * #otBorderRoutingGetRoutePreference
790      */
791     if (aArgs[0].IsEmpty())
792     {
793         OutputLine("%s", PreferenceToString(otBorderRoutingGetRoutePreference(GetInstancePtr())));
794     }
795     /**
796      * @cli br routeprf clear
797      * @code
798      * br routeprf clear
799      * Done
800      * @endcode
801      * @par api_copy
802      * #otBorderRoutingClearRoutePreference
803      */
804     else if (aArgs[0] == "clear")
805     {
806         otBorderRoutingClearRoutePreference(GetInstancePtr());
807     }
808     /**
809      * @cli br routeprf (high,med,low)
810      * @code
811      * br routeprf low
812      * Done
813      * @endcode
814      * @cparam br routeprf [@ca{high}|@ca{med}|@ca{low}]
815      * @par api_copy
816      * #otBorderRoutingSetRoutePreference
817      */
818     else
819     {
820         otRoutePreference preference;
821 
822         SuccessOrExit(error = Interpreter::ParsePreference(aArgs[0], preference));
823         otBorderRoutingSetRoutePreference(GetInstancePtr(), preference);
824     }
825 
826 exit:
827     return error;
828 }
829 
830 #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE
831 
832 /**
833  * @cli br counters
834  * @code
835  * br counters
836  * Inbound Unicast: Packets 4 Bytes 320
837  * Inbound Multicast: Packets 0 Bytes 0
838  * Outbound Unicast: Packets 2 Bytes 160
839  * Outbound Multicast: Packets 0 Bytes 0
840  * RA Rx: 4
841  * RA TxSuccess: 2
842  * RA TxFailed: 0
843  * RS Rx: 0
844  * RS TxSuccess: 2
845  * RS TxFailed: 0
846  * Done
847  * @endcode
848  * @par api_copy
849  * #otIp6GetBorderRoutingCounters
850  */
Process(Arg aArgs[])851 template <> otError Br::Process<Cmd("counters")>(Arg aArgs[])
852 {
853     otError error = OT_ERROR_NONE;
854 
855     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
856     Interpreter::GetInterpreter().OutputBorderRouterCounters();
857 
858 exit:
859     return error;
860 }
861 
862 #endif // OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE
863 
Process(Arg aArgs[])864 otError Br::Process(Arg aArgs[])
865 {
866 #define CmdEntry(aCommandString)                          \
867     {                                                     \
868         aCommandString, &Br::Process<Cmd(aCommandString)> \
869     }
870 
871     static constexpr Command kCommands[] = {
872 #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE
873         CmdEntry("counters"),
874 #endif
875         CmdEntry("disable"),
876         CmdEntry("enable"),
877         CmdEntry("init"),
878 #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE
879         CmdEntry("nat64prefix"),
880 #endif
881         CmdEntry("omrprefix"),
882         CmdEntry("onlinkprefix"),
883 #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
884         CmdEntry("pd"),
885 #endif
886 #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE
887         CmdEntry("peers"),
888 #endif
889         CmdEntry("prefixtable"),
890         CmdEntry("raoptions"),
891         CmdEntry("rioprf"),
892         CmdEntry("routeprf"),
893         CmdEntry("routers"),
894         CmdEntry("state"),
895     };
896 
897 #undef CmdEntry
898 
899     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
900 
901     otError        error = OT_ERROR_INVALID_COMMAND;
902     const Command *command;
903 
904     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
905     {
906         OutputCommandTable(kCommands);
907         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
908     }
909 
910     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
911     VerifyOrExit(command != nullptr);
912 
913     error = (this->*command->mHandler)(aArgs + 1);
914 
915 exit:
916     return error;
917 }
918 
919 } // namespace Cli
920 } // namespace ot
921 
922 #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
923