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