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