• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 S: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 S: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  *   - S: SNAC Router flag
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     if (ProcessEnableDisable(aArgs, otBorderRoutingDhcp6PdSetEnabled) == OT_ERROR_NONE)
549     {
550     }
551     /**
552      * @cli br pd state
553      * @code
554      * br pd state
555      * running
556      * Done
557      * @endcode
558      * @par api_copy
559      * #otBorderRoutingDhcp6PdGetState
560      */
561     else if (aArgs[0] == "state")
562     {
563         static const char *const kDhcpv6PdStateStrings[] = {
564             "disabled", // (0) OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED
565             "stopped",  // (1) OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED
566             "running",  // (2) OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING
567             "idle",     // (3) OT_BORDER_ROUTING_DHCP6_PD_STATE_IDLE
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         static_assert(3 == OT_BORDER_ROUTING_DHCP6_PD_STATE_IDLE,
577                       "OT_BORDER_ROUTING_DHCP6_PD_STATE_IDLE value is not expected!");
578 
579         OutputLine("%s", Stringify(otBorderRoutingDhcp6PdGetState(GetInstancePtr()), kDhcpv6PdStateStrings));
580     }
581     /**
582      * @cli br pd omrprefix
583      * @code
584      * br pd omrprefix
585      * 2001:db8:cafe:0:0/64 lifetime:1800 preferred:1800
586      * Done
587      * @endcode
588      * @par api_copy
589      * #otBorderRoutingGetPdOmrPrefix
590      */
591     else if (aArgs[0] == "omrprefix")
592     {
593         otBorderRoutingPrefixTableEntry entry;
594 
595         SuccessOrExit(error = otBorderRoutingGetPdOmrPrefix(GetInstancePtr(), &entry));
596 
597         OutputIp6Prefix(entry.mPrefix);
598         OutputLine(" lifetime:%lu preferred:%lu", ToUlong(entry.mValidLifetime), ToUlong(entry.mPreferredLifetime));
599     }
600     else
601     {
602         ExitNow(error = OT_ERROR_INVALID_COMMAND);
603     }
604 
605 exit:
606     return error;
607 }
608 #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
609 
610 /**
611  * @cli br routers
612  * @code
613  * br routers
614  * ff02:0:0:0:0:0:0:1 (M:0 O:0 S:1) ms-since-rx:1505 reachable:yes age:00:18:13
615  * Done
616  * @endcode
617  * @par
618  * Get the list of discovered routers by Border Routing Manager on the infrastructure link.
619  * Info per router:
620  * - The router IPv6 address
621  * - Flags in received Router Advertisement header:
622  *   - M: Managed Address Config flag
623  *   - O: Other Config flag
624  *   - S: SNAC Router flag (indicates whether the router is a stub router)
625  * - Milliseconds since last received message from this router
626  * - Reachability flag: A router is marked as unreachable if it fails to respond to multiple Neighbor Solicitation
627  *   probes.
628  * - Age: Duration interval since this router was first discovered. It is formatted as `{hh}:{mm}:{ss}` for  hours,
629  *   minutes, seconds, if the duration is less than 24 hours. If the duration is 24 hours or more, the format is
630  *   `{dd}d.{hh}:{mm}:{ss}` for days, hours, minutes, seconds.
631  * - `(this BR)` is appended when the router is the local device itself.
632  * - `(peer BR)` is appended when the router is likely a peer BR connected to the same Thread mesh. This requires
633  *   `OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE`.
634  * @sa otBorderRoutingGetNextRouterEntry
635  */
Process(Arg aArgs[])636 template <> otError Br::Process<Cmd("routers")>(Arg aArgs[])
637 {
638     otError                            error = OT_ERROR_NONE;
639     otBorderRoutingPrefixTableIterator iterator;
640     otBorderRoutingRouterEntry         entry;
641 
642     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
643 
644     otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator);
645 
646     while (otBorderRoutingGetNextRouterEntry(GetInstancePtr(), &iterator, &entry) == OT_ERROR_NONE)
647     {
648         OutputRouterInfo(entry, kLongVersion);
649     }
650 
651 exit:
652     return error;
653 }
654 
OutputRouterInfo(const otBorderRoutingRouterEntry & aEntry,RouterOutputMode aMode)655 void Br::OutputRouterInfo(const otBorderRoutingRouterEntry &aEntry, RouterOutputMode aMode)
656 {
657     OutputIp6Address(aEntry.mAddress);
658     OutputFormat(" (M:%u O:%u S:%u)", aEntry.mManagedAddressConfigFlag, aEntry.mOtherConfigFlag,
659                  aEntry.mSnacRouterFlag);
660 
661     if (aMode == kLongVersion)
662     {
663         char ageString[OT_DURATION_STRING_SIZE];
664 
665         otConvertDurationInSecondsToString(aEntry.mAge, ageString, sizeof(ageString));
666 
667         OutputFormat(" ms-since-rx:%lu reachable:%s age:%s", ToUlong(aEntry.mMsecSinceLastUpdate),
668                      aEntry.mIsReachable ? "yes" : "no", ageString);
669 
670         if (aEntry.mIsLocalDevice)
671         {
672             OutputFormat(" (this BR)");
673         }
674 
675 #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE
676         if (aEntry.mIsPeerBr)
677         {
678             OutputFormat(" (peer BR)");
679         }
680 #endif
681     }
682 
683     OutputNewLine();
684 }
685 
Process(Arg aArgs[])686 template <> otError Br::Process<Cmd("raoptions")>(Arg aArgs[])
687 {
688     static constexpr uint16_t kMaxExtraOptions = 800;
689 
690     otError  error = OT_ERROR_NONE;
691     uint8_t  options[kMaxExtraOptions];
692     uint16_t length;
693 
694     /**
695      * @cli br raoptions (set,clear)
696      * @code
697      * br raoptions 0400ff00020001
698      * Done
699      * @endcode
700      * @code
701      * br raoptions clear
702      * Done
703      * @endcode
704      * @cparam br raoptions @ca{options|clear}
705      * `br raoptions clear` passes a `nullptr` to #otBorderRoutingSetExtraRouterAdvertOptions.
706      * Otherwise, you can pass the `options` byte as hex data.
707      * @par api_copy
708      * #otBorderRoutingSetExtraRouterAdvertOptions
709      */
710     if (aArgs[0] == "clear")
711     {
712         length = 0;
713     }
714     else
715     {
716         length = sizeof(options);
717         SuccessOrExit(error = aArgs[0].ParseAsHexString(length, options));
718     }
719 
720     error = otBorderRoutingSetExtraRouterAdvertOptions(GetInstancePtr(), length > 0 ? options : nullptr, length);
721 
722 exit:
723     return error;
724 }
725 
Process(Arg aArgs[])726 template <> otError Br::Process<Cmd("rioprf")>(Arg aArgs[])
727 {
728     otError error = OT_ERROR_NONE;
729 
730     /**
731      * @cli br rioprf
732      * @code
733      * br rioprf
734      * med
735      * Done
736      * @endcode
737      * @par api_copy
738      * #otBorderRoutingGetRouteInfoOptionPreference
739      */
740     if (aArgs[0].IsEmpty())
741     {
742         OutputLine("%s", PreferenceToString(otBorderRoutingGetRouteInfoOptionPreference(GetInstancePtr())));
743     }
744     /**
745      * @cli br rioprf clear
746      * @code
747      * br rioprf clear
748      * Done
749      * @endcode
750      * @par api_copy
751      * #otBorderRoutingClearRouteInfoOptionPreference
752      */
753     else if (aArgs[0] == "clear")
754     {
755         otBorderRoutingClearRouteInfoOptionPreference(GetInstancePtr());
756     }
757     /**
758      * @cli br rioprf (high,med,low)
759      * @code
760      * br rioprf low
761      * Done
762      * @endcode
763      * @cparam br rioprf [@ca{high}|@ca{med}|@ca{low}]
764      * @par api_copy
765      * #otBorderRoutingSetRouteInfoOptionPreference
766      */
767     else
768     {
769         otRoutePreference preference;
770 
771         SuccessOrExit(error = Interpreter::ParsePreference(aArgs[0], preference));
772         otBorderRoutingSetRouteInfoOptionPreference(GetInstancePtr(), preference);
773     }
774 
775 exit:
776     return error;
777 }
778 
Process(Arg aArgs[])779 template <> otError Br::Process<Cmd("routeprf")>(Arg aArgs[])
780 {
781     otError error = OT_ERROR_NONE;
782 
783     /**
784      * @cli br routeprf
785      * @code
786      * br routeprf
787      * med
788      * Done
789      * @endcode
790      * @par api_copy
791      * #otBorderRoutingGetRoutePreference
792      */
793     if (aArgs[0].IsEmpty())
794     {
795         OutputLine("%s", PreferenceToString(otBorderRoutingGetRoutePreference(GetInstancePtr())));
796     }
797     /**
798      * @cli br routeprf clear
799      * @code
800      * br routeprf clear
801      * Done
802      * @endcode
803      * @par api_copy
804      * #otBorderRoutingClearRoutePreference
805      */
806     else if (aArgs[0] == "clear")
807     {
808         otBorderRoutingClearRoutePreference(GetInstancePtr());
809     }
810     /**
811      * @cli br routeprf (high,med,low)
812      * @code
813      * br routeprf low
814      * Done
815      * @endcode
816      * @cparam br routeprf [@ca{high}|@ca{med}|@ca{low}]
817      * @par api_copy
818      * #otBorderRoutingSetRoutePreference
819      */
820     else
821     {
822         otRoutePreference preference;
823 
824         SuccessOrExit(error = Interpreter::ParsePreference(aArgs[0], preference));
825         otBorderRoutingSetRoutePreference(GetInstancePtr(), preference);
826     }
827 
828 exit:
829     return error;
830 }
831 
832 #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE
833 
834 /**
835  * @cli br counters
836  * @code
837  * br counters
838  * Inbound Unicast: Packets 4 Bytes 320
839  * Inbound Multicast: Packets 0 Bytes 0
840  * Outbound Unicast: Packets 2 Bytes 160
841  * Outbound Multicast: Packets 0 Bytes 0
842  * RA Rx: 4
843  * RA TxSuccess: 2
844  * RA TxFailed: 0
845  * RS Rx: 0
846  * RS TxSuccess: 2
847  * RS TxFailed: 0
848  * Done
849  * @endcode
850  * @par api_copy
851  * #otIp6GetBorderRoutingCounters
852  */
Process(Arg aArgs[])853 template <> otError Br::Process<Cmd("counters")>(Arg aArgs[])
854 {
855     otError error = OT_ERROR_NONE;
856 
857     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
858     Interpreter::GetInterpreter().OutputBorderRouterCounters();
859 
860 exit:
861     return error;
862 }
863 
864 #endif // OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE
865 
Process(Arg aArgs[])866 otError Br::Process(Arg aArgs[])
867 {
868 #define CmdEntry(aCommandString)                          \
869     {                                                     \
870         aCommandString, &Br::Process<Cmd(aCommandString)> \
871     }
872 
873     static constexpr Command kCommands[] = {
874 #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE
875         CmdEntry("counters"),
876 #endif
877         CmdEntry("disable"),
878         CmdEntry("enable"),
879         CmdEntry("init"),
880 #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE
881         CmdEntry("nat64prefix"),
882 #endif
883         CmdEntry("omrprefix"),
884         CmdEntry("onlinkprefix"),
885 #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
886         CmdEntry("pd"),
887 #endif
888 #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE
889         CmdEntry("peers"),
890 #endif
891         CmdEntry("prefixtable"),
892         CmdEntry("raoptions"),
893         CmdEntry("rioprf"),
894         CmdEntry("routeprf"),
895         CmdEntry("routers"),
896         CmdEntry("state"),
897     };
898 
899 #undef CmdEntry
900 
901     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
902 
903     otError        error = OT_ERROR_INVALID_COMMAND;
904     const Command *command;
905 
906     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
907     {
908         OutputCommandTable(kCommands);
909         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
910     }
911 
912     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
913     VerifyOrExit(command != nullptr);
914 
915     error = (this->*command->mHandler)(aArgs + 1);
916 
917 exit:
918     return error;
919 }
920 
921 } // namespace Cli
922 } // namespace ot
923 
924 #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
925