Merge pull request #1254 from kernelkit/ospf-fixes

Improvements to OSPF and BFD operational data and CLI commands
This commit is contained in:
Joachim Wiberg
2025-11-13 12:47:37 +01:00
committed by GitHub
19 changed files with 1643 additions and 83 deletions
+16
View File
@@ -9,6 +9,19 @@ All notable changes to the project are documented in this file.
### Changes
- Upgrade Linux kernel to 6.12.57 (LTS)
- Major improvements to OSPF and BFD operational data and CLI commands:
- CLI commands now use data from the operational datastore instead of
calling vtysh directly, providing better integration and consistency
- New `show ip ospf` command family (neighbor, interface, route) to align
with industry standard CLI conventions. Legacy `show ospf` commands are
deprecated but still work with warnings
- The `show ip ospf database` subcommand has been dropped for now, the
advanced user can still use `vtysh` from Bash if necessary, issue #1253
- Extended BFD commands: `show bfd` (status), `show bfd peers` (detailed),
`show bfd peers brief` (table format), and `show bfd peer <address>`
- All command names now use singular form (interface, route, neighbor)
matching Cisco/FRR conventions, as well as configure context naming
- New support for configuring OSPF interface priority for DR/BDR election
- The DHCP client configuration has moved from `/infix-dhcp-client:dhcp-client`
to `/interfaces/interface[name]/ipv4/infix-dhcp-client:dhcp`, issue #1109.
The configuration is automatically migrated on upgrade. The DHCP client is
@@ -28,6 +41,9 @@ All notable changes to the project are documented in this file.
### Fixes
- Fix #855: User admin sometimes fails to be added to `wheel` group
- Fix #1247: Prevent invalid configuration of OSPF backbone area (0.0.0.0) as
stub or NSSA. The backbone must always be a normal area per RFC 2328. Any
existing invalid configurations are automatically corrected during upgrade
- Fix serious regression in boot time, introduced in v25.10, delays the
boot step "Mounting filesystems ..." with up to 30 seconds!
+8 -8
View File
@@ -562,7 +562,7 @@ In addition to basic status shown in the interface overview, detailed
LAG status can be inspected:
```
admin@example:/> show interfaces name lag0
admin@example:/> show interface lag0
name : lag0
index : 25
mtu : 1500
@@ -582,7 +582,7 @@ out-octets : 2142
Same aggregate, but in LACP mode:
```
admin@example:/> show interfaces name lag0
admin@example:/> show interface lag0
name : lag0
index : 24
mtu : 1500
@@ -624,7 +624,7 @@ Member ports provide additional status information:
Example member port status:
```
admin@example:/> show interfaces name eth7
admin@example:/> show interface eth7
name : eth7
index : 8
mtu : 1500
@@ -755,7 +755,7 @@ The speed and duplex status for the links can be listed as shown
below, assuming the link operational status is 'up'.
```
admin@example:/> show interfaces name eth1
admin@example:/> show interface eth1
name : eth1
index : 2
mtu : 1500
@@ -769,7 +769,7 @@ ipv6 addresses :
in-octets : 75581
out-octets : 43130
...
admin@example:/> show interfaces name eth4
admin@example:/> show interface eth4
name : eth4
index : 5
mtu : 1500
@@ -827,7 +827,7 @@ require auto-negotiation to be enabled.
Ethernet packet statistics[^6] can be listed as shown below.
```
admin@example:/> show interfaces name eth1
admin@example:/> show interface eth1
name : eth1
index : 2
mtu : 1500
@@ -1525,10 +1525,10 @@ currently supported, namely `ipv4` and `ipv6`.
[^3]: For example, IPv4 groups are mapped to MAC multicast addresses by
mapping the low-order 23-bits of the IP address in the low-order 23
bits of the Ethernet address 01:00:5E:00:00:00. Meaning, more than
one IP multicast group maps to the same MAC multicast group.
one IP multicast group maps to the same MAC multicast group.
[^4]: A YANG deviation was previously used to make it possible to set
`phys-address`, but this has been replaced with the more flexible
`custom-phys-address`.
`custom-phys-address`.
[^5]: MAC bridges on Marvell Linkstreet devices are currently limited to
a single MAC database, this may be a problem if the same MAC address
appears in different MAC bridges.
+7 -7
View File
@@ -27,10 +27,10 @@ Wi-Fi support is primarily tested with Realtek chipset-based adapters.
## Configuration
Add a supported Wi-Fi network device. To verify that it has been
detected, look for `wifi0` in `show interfaces`
detected, look for `wifi0` in `show interface`
```
admin@example:/> show interfaces
admin@example:/> show interface
INTERFACE PROTOCOL STATE DATA
lo loopback UP
ipv4 127.0.0.1/8 (static)
@@ -52,7 +52,7 @@ see the result read the operational datastore for interface `wifi0` or
use the CLI
```
admin@infix-00-00-00:/> show interfaces name wifi0
admin@infix-00-00-00:/> show interface wifi0
name : wifi0
type : wifi
index : 3
@@ -119,11 +119,11 @@ admin@example:/config/interface/wifi0/> leave
The Wi-Fi negotiation should now start immediately, provided that the
SSID and pre-shared key are correct. You can verify the connection by
running `show interfaces` again.
running `show interface` again.
```
admin@example:/> show interfaces
admin@example:/> show interface
INTERFACE PROTOCOL STATE DATA
lo loopback UP
ipv4 127.0.0.1/8 (static)
@@ -139,7 +139,7 @@ admin@example:/>
## Troubleshooting Connection Issues
Use `show wifi scan wifi0` and `show interfaces` to verify signal strength
Use `show wifi scan wifi0` and `show interface` to verify signal strength
and connection status. If issues arise, try the following
troubleshooting steps:
@@ -147,6 +147,6 @@ troubleshooting steps:
2. **Check credentials**: Verify the preshared key in `ietf-keystore`
3. **Review logs**: Check system logs with `show log` for Wi-Fi related errors
4. **Regulatory compliance**: Ensure the country-code matches your location
5. **Hardware detection**: Confirm the adapter appears in `show interfaces`
5. **Hardware detection**: Confirm the adapter appears in `show interface`
If issues persist, check the system log for specific error messages that can help identify the root cause.
+28
View File
@@ -0,0 +1,28 @@
#!/bin/sh
# Remove area-type from OSPF backbone area (0.0.0.0)
# The backbone area cannot be configured as stub or NSSA per RFC 2328/3101
# Silently drop any area-type setting for area 0.0.0.0
file=$1
temp=${file}.tmp
jq '
if .["ietf-routing:routing"]?."control-plane-protocols"?."control-plane-protocol" then
.["ietf-routing:routing"]."control-plane-protocols"."control-plane-protocol" |= map(
if .["ietf-ospf:ospf"]?.areas?.area then
.["ietf-ospf:ospf"].areas.area |= map(
if (.["area-id"] == "0.0.0.0") and .["area-type"] then
# Remove area-type from backbone area
del(.["area-type"])
else
.
end
)
else
.
end
)
else
.
end
' "$file" > "$temp" && mv "$temp" "$file"
+2 -1
View File
@@ -1,3 +1,4 @@
migratedir = $(pkgdatadir)/migrate/1.6
dist_migrate_DATA = 10-dhcp-client-to-ipv4.sh \
20-autoconf-to-presence.sh
20-autoconf-to-presence.sh \
30-ospf-backbone-area-type.sh
+4 -1
View File
@@ -38,7 +38,7 @@ int parse_ospf_interfaces(sr_session_ctx_t *session, struct lyd_node *areas, FIL
area_id = lydx_get_cattr(area, "area-id");
LY_LIST_FOR(lyd_child(interfaces), interface) {
const char *hello, *dead, *retransmit, *transmit, *interface_type, *cost;
const char *hello, *dead, *retransmit, *transmit, *interface_type, *cost, *priority;
if (lydx_get_bool(interface, "enabled")) {
int passive = 0, bfd_enabled = 0;
@@ -57,6 +57,7 @@ int parse_ospf_interfaces(sr_session_ctx_t *session, struct lyd_node *areas, FIL
transmit = lydx_get_cattr(interface, "transmit-delay");
interface_type = lydx_get_cattr(interface, "interface-type");
cost = lydx_get_cattr(interface, "cost");
priority = lydx_get_cattr(interface, "priority");
fprintf(fp, " ip ospf area %s\n", area_id);
if (dead)
@@ -67,6 +68,8 @@ int parse_ospf_interfaces(sr_session_ctx_t *session, struct lyd_node *areas, FIL
fprintf(fp, " ip ospf retransmit-interval %s\n", retransmit);
if (transmit)
fprintf(fp, " ip ospf transmit-delay %s\n", transmit);
if (priority)
fprintf(fp, " ip ospf priority %s\n", priority);
if (bfd_enabled)
fputs(" ip ospf bfd\n", fp);
if (passive)
+5 -1
View File
@@ -11,6 +11,10 @@ MODULES=(
"ietf-ipv6-unicast-routing@2018-03-13.yang"
"ietf-ipv4-unicast-routing@2018-03-13.yang"
"ietf-ospf@2022-10-19.yang -e bfd -e explicit-router-id"
"iana-bfd-types@2021-10-21.yang"
"ietf-bfd-types@2022-09-22.yang"
"ietf-bfd@2022-09-22.yang"
"ietf-bfd-ip-sh@2022-09-22.yang"
"iana-if-type@2023-01-26.yang"
# NOTE: ietf-tls-client must be version matched with ietf-tls-server, used by netopeer2!
"ietf-tls-client@2023-12-28.yang"
@@ -22,7 +26,7 @@ MODULES=(
"ieee802-dot1q-types@2022-10-29.yang"
"infix-ip@2025-11-02.yang"
"infix-if-type@2025-02-12.yang"
"infix-routing@2024-11-27.yang"
"infix-routing@2025-11-12.yang"
"ieee802-dot1ab-lldp@2022-03-15.yang"
"infix-lldp@2025-05-05.yang"
"infix-dhcp-common@2025-01-29.yang"
@@ -0,0 +1,151 @@
module ietf-bfd-ip-sh {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-bfd-ip-sh";
prefix bfd-ip-sh;
import ietf-bfd-types {
prefix bfd-types;
reference
"RFC 9314: YANG Data Model for Bidirectional Forwarding
Detection (BFD)";
}
import ietf-bfd {
prefix bfd;
reference
"RFC 9314: YANG Data Model for Bidirectional Forwarding
Detection (BFD)";
}
import ietf-interfaces {
prefix if;
reference
"RFC 8343: A YANG Data Model for Interface Management";
}
import ietf-inet-types {
prefix inet;
reference
"RFC 6991: Common YANG Data Types";
}
import ietf-routing {
prefix rt;
reference
"RFC 8349: A YANG Data Model for Routing Management
(NMDA Version)";
}
organization
"IETF BFD Working Group";
contact
"WG Web: <https://datatracker.ietf.org/wg/bfd/>
WG List: <mailto:rtg-bfd@ietf.org>
Editor: Reshad Rahman
<mailto:reshad@yahoo.com>
Editor: Lianshu Zheng
<mailto:veronique_cheng@hotmail.com>
Editor: Mahesh Jethanandani
<mailto:mjethanandani@gmail.com>";
description
"This module contains the YANG definition for BFD IP single-hop
as per RFC 5881.
Copyright (c) 2022 IETF Trust and the persons identified as
authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with or
without modification, is permitted pursuant to, and subject
to the license terms contained in, the Revised BSD License
set forth in Section 4.c of the IETF Trust's Legal Provisions
Relating to IETF Documents
(https://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC 9314; see the
RFC itself for full legal notices.";
reference
"RFC 5881: Bidirectional Forwarding Detection (BFD)
for IPv4 and IPv6 (Single Hop)
RFC 9314: YANG Data Model for Bidirectional Forwarding
Detection (BFD)";
revision 2022-09-22 {
description
"Updating reference to RFC 9314.";
reference
"RFC 9314: YANG Data Model for Bidirectional Forwarding
Detection (BFD).";
}
revision 2021-10-21 {
description
"Initial revision.";
reference
"RFC 9127: YANG Data Model for Bidirectional Forwarding
Detection (BFD)";
}
augment "/rt:routing/rt:control-plane-protocols/"
+ "rt:control-plane-protocol/bfd:bfd" {
description
"BFD augmentation for IP single-hop.";
container ip-sh {
description
"BFD IP single-hop top-level container.";
uses bfd-types:session-statistics-summary;
container sessions {
description
"BFD IP single-hop sessions.";
list session {
key "interface dest-addr";
description
"List of IP single-hop sessions.";
leaf interface {
type if:interface-ref;
description
"Interface on which the BFD session is running.";
}
leaf dest-addr {
type inet:ip-address;
description
"IP address of the peer.";
}
leaf source-addr {
type inet:ip-address;
description
"Local IP address.";
}
uses bfd-types:common-cfg-parms;
uses bfd-types:all-session;
}
}
list interfaces {
key "interface";
description
"List of interfaces.";
leaf interface {
type if:interface-ref;
description
"BFD information for this interface.";
}
uses bfd-types:auth-parms;
}
}
}
notification singlehop-notification {
description
"Notification for BFD single-hop session state change. An
implementation may rate-limit notifications, e.g., when a
session is continuously changing state.";
uses bfd-types:notification-parms;
leaf interface {
type if:interface-ref;
description
"Interface to which this BFD session belongs.";
}
leaf echo-enabled {
type boolean;
description
"Indicates whether Echo was enabled for BFD.";
}
}
}
@@ -0,0 +1,84 @@
module ietf-bfd {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-bfd";
prefix bfd;
import ietf-bfd-types {
prefix bfd-types;
reference
"RFC 9314: YANG Data Model for Bidirectional Forwarding
Detection (BFD)";
}
import ietf-routing {
prefix rt;
reference
"RFC 8349: A YANG Data Model for Routing Management
(NMDA Version)";
}
organization
"IETF BFD Working Group";
contact
"WG Web: <https://datatracker.ietf.org/wg/bfd/>
WG List: <mailto:rtg-bfd@ietf.org>
Editor: Reshad Rahman
<mailto:reshad@yahoo.com>
Editor: Lianshu Zheng
<mailto:veronique_cheng@hotmail.com>
Editor: Mahesh Jethanandani
<mailto:mjethanandani@gmail.com>";
description
"This module contains the YANG definition for BFD parameters as
per RFC 5880.
Copyright (c) 2022 IETF Trust and the persons identified as
authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with or
without modification, is permitted pursuant to, and subject
to the license terms contained in, the Revised BSD License set
forth in Section 4.c of the IETF Trust's Legal Provisions
Relating to IETF Documents
(https://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC 9314; see the
RFC itself for full legal notices.";
reference
"RFC 5880: Bidirectional Forwarding Detection (BFD)
RFC 9314: YANG Data Model for Bidirectional Forwarding
Detection (BFD)";
revision 2022-09-22 {
description
"Updating reference to RFC 9314.";
reference
"RFC 9314: YANG Data Model for Bidirectional Forwarding
Detection (BFD).";
}
revision 2021-10-21 {
description
"Initial revision.";
reference
"RFC 9127: YANG Data Model for Bidirectional Forwarding
Detection (BFD)";
}
augment "/rt:routing/rt:control-plane-protocols/"
+ "rt:control-plane-protocol" {
when "derived-from-or-self(rt:type, 'bfd-types:bfdv1')" {
description
"This augmentation is only valid for a control plane protocol
instance of BFD (type 'bfdv1').";
}
description
"BFD augmentation.";
container bfd {
description
"BFD top-level container.";
uses bfd-types:session-statistics-summary;
}
}
}
+127 -16
View File
@@ -15,11 +15,34 @@ module infix-routing {
import ietf-ospf {
prefix ospf;
}
import ietf-bfd-types {
prefix bfd-types;
}
organization "KernelKit";
contact "kernelkit@googlegroups.com";
description "Deviations and augments for ietf-routing and ietf-ospf.";
revision 2025-11-12 {
description "Add area-id augmentation to OSPF local-rib routes.
Add uptime, role, and interface-name augmentations to OSPF neighbor state.
Allows displaying which area a route was learned from,
how long neighbors have been up, their DR/BDR role, and
the interface with local address.";
reference "Issue #1190";
}
revision 2025-11-11 {
description "Add BFD IP single-hop support (RFC 9127).
Deviate session-statistics (not implemented yet).";
reference "RFC 9127, Issue #1190";
}
revision 2025-11-10 {
description "Add must constraint to prevent backbone area as stub/NSSA.
Enable OSPF operational data for CLI: un-deviate priority,
hello-timer, wait-timer, route metric and route-tag fields.";
reference "RFC 2328, RFC 3101, Issue #1190";
}
revision 2024-11-27 {
description "Deviate address-family in OSPF";
reference "internal";
@@ -121,6 +144,11 @@ module infix-routing {
base infix-routing-type;
description "OSPv2 (IPv4) routing protocol";
}
identity bfdv1 {
base bfd-types:bfdv1;
base infix-routing-type;
description "BFD protocol version 1";
}
identity static {
base rt:static;
base infix-routing-type;
@@ -348,6 +376,19 @@ module infix-routing {
}
/* OSPF Area */
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:area-type" {
deviate add {
must "(../ospf:area-id != '0.0.0.0') or "
+ "(derived-from-or-self(., 'ospf:normal-area'))" {
error-message "Area 0.0.0.0 (backbone) cannot be configured as "
+ "stub or NSSA.";
description
"The backbone area (area 0.0.0.0) must be a normal area. "
+ "It cannot be configured as a stub area or NSSA as per "
+ "RFC 2328 and RFC 3101.";
}
}
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:virtual-links" {
deviate not-supported;
}
@@ -368,13 +409,6 @@ module infix-routing {
deviate not-supported;
}
/* OSPF Local RIB */
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:local-rib/ospf:route/ospf:metric" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:local-rib/ospf:route/ospf:route-tag" {
deviate not-supported;
}
/* OSPF Area Interface */
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:static-neighbors"
{
@@ -396,7 +430,12 @@ module infix-routing {
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:priority"
{
deviate not-supported;
deviate add {
default "1";
}
description
"FRR uses a default priority of 1 for DR/BDR election.
Setting default to match FRR behavior.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:fast-reroute"
{
@@ -457,14 +496,6 @@ module infix-routing {
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:hello-timer"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:wait-timer"
{
deviate not-supported;
}
/* OSPF RPCs */
deviation "/ospf:clear-neighbor" {
@@ -512,4 +543,84 @@ module infix-routing {
deviate not-supported;
}
/*
* Augmentations
*/
/* Add area-id to OSPF local-rib routes
* The standard ietf-ospf model doesn't include area information in routes,
* but FRR provides this and it's useful for operators to see which area
* a route was learned from.
*/
augment "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/"
+ "ospf:ospf/ospf:local-rib/ospf:route" {
when "derived-from-or-self(../../rt:type, 'infix-routing:ospfv2')" {
description
"This augmentation is only valid for OSPFv2.";
}
description
"Infix extension to add area information to OSPF routes.";
leaf area-id {
type ospf:area-id-type;
config false;
description
"Area ID from which this route was learned. Not applicable
for inter-area routes learned from area border routers or
external routes.";
}
}
/* Add uptime to OSPF neighbor operational state
* The standard ietf-ospf model doesn't include neighbor uptime,
* but FRR provides this and it's useful for monitoring neighbor stability.
*/
augment "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/"
+ "ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/"
+ "ospf:neighbors/ospf:neighbor" {
when "derived-from-or-self(../../../../../../../rt:type, 'infix-routing:ospfv2')" {
description
"This augmentation is only valid for OSPFv2.";
}
description
"Infix extension to add uptime information to OSPF neighbors.";
leaf uptime {
type uint32;
units "seconds";
config false;
description
"Time since the neighbor transitioned to the Full state.
Reported in seconds for consistency with other timers.";
}
leaf role {
type enumeration {
enum DR {
description "Designated Router";
}
enum BDR {
description "Backup Designated Router";
}
enum DROther {
description "Neither DR nor BDR";
}
}
config false;
description
"OSPF neighbor's role on the network segment (DR, BDR, or DROther).
This is derived from the neighbor's priority and router ID during
the DR/BDR election process.";
}
leaf interface-name {
type string;
config false;
description
"Interface name with local address in format 'interface:address'.
Example: 'e5:10.0.23.1' indicates interface e5 with local address 10.0.23.1.
This is useful for networks where multiple addresses exist on an interface.";
}
}
}
+99 -38
View File
@@ -359,34 +359,62 @@
</ACTION>
</COMMAND>
<COMMAND name="ospf" help="Show OSPF status">
<PARAM name="name" ptype="/STRING" help="neighbor, interfaces or bfd" min="0" max="1">
<COMMAND name="bfd" help="Show BFD information">
<PARAM name="subcommand" ptype="/STRING" help="peers or peer" min="0" max="1">
<COMPL>
<ACTION sym="printl">neighbor</ACTION>
<ACTION sym="printl">interfaces</ACTION>
<ACTION sym="printl">routes</ACTION>
<ACTION sym="printl">database</ACTION>
<ACTION sym="printl">bfd</ACTION>
<ACTION sym="printl">peers</ACTION>
<ACTION sym="printl">peer</ACTION>
</COMPL>
</PARAM>
<PARAM name="peer_addr" ptype="/STRING" help="Peer IP address" min="0" max="1"/>
<SWITCH name="optional" min="0">
<COMMAND name="brief" help="Show brief output" mode="switch"/>
</SWITCH>
<ACTION sym="script" in="tty" out="tty" interrupt="true">
if [ -z "$KLISH_PARAM_name" ]; then
doas vtysh -c "show ip ospf" |pager
elif [ "$KLISH_PARAM_name" == "neighbor" ];then
doas vtysh -c "show ip ospf neighbor" |pager
elif [ "$KLISH_PARAM_name" == "interfaces" ];then
doas vtysh -c "show ip ospf interface" |pager
elif [ "$KLISH_PARAM_name" == "routes" ];then
doas vtysh -c "show ip ospf route" |pager
elif [ "$KLISH_PARAM_name" == "database" ];then
doas vtysh -c "show ip ospf database" |pager
elif [ "$KLISH_PARAM_name" == "bfd" ];then
doas vtysh -c "show bfd peers" |pager
fi
BRIEF_ARG=""
if [ -n "$KLISH_COMMAND_brief" ]; then
BRIEF_ARG="brief"
fi
# Pass subcommand, optional peer address, and brief flag
show bfd ${KLISH_PARAM_subcommand:+"$KLISH_PARAM_subcommand"} ${KLISH_PARAM_peer_addr:+"$KLISH_PARAM_peer_addr"} ${BRIEF_ARG:+"$BRIEF_ARG"} |pager
</ACTION>
</COMMAND>
<COMMAND name="routes" help="Show routing table">
<COMMAND name="ospf" help="Show OSPF status">
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show ospf |pager
# Deprecated in favor of Cisco likeness, consider future 'show ipv6 ospf'
printf "\n\e[1mNOTE:\e[0m this command is deprecated, please use 'show ip ospf' instead.\n"
</ACTION>
<SWITCH name="optional" min="0">
<COMMAND name="neighbor" help="Show OSPF neighbors">
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show ospf neighbor |pager
printf "\n\e[1mNOTE:\e[0m this command is deprecated, please use 'show ip ospf neighbor' instead.\n"
</ACTION>
</COMMAND>
<COMMAND name="interface" help="Show OSPF interfaces">
<SWITCH name="optional" min="0" max="1">
<PARAM name="ifname" ptype="/IFACES" help="Interface name" />
</SWITCH>
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show ospf interface "$KLISH_PARAM_ifname" |pager
printf "\n\e[1mNOTE:\e[0m this command is deprecated, please use 'show ip ospf interface' instead.\n"
</ACTION>
</COMMAND>
<COMMAND name="route" help="Show OSPF routing table">
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show ospf route |pager
printf "\n\e[1mNOTE:\e[0m this command is deprecated, please use 'show ip ospf route' instead.\n"
</ACTION>
</COMMAND>
</SWITCH>
</COMMAND>
<COMMAND name="route" help="Show routing table">
<PARAM name="ip" ptype="/STRING" help="ipv4 or ipv6" min="0" max="1">
<COMPL>
<ACTION sym="printl">ipv4</ACTION>
@@ -394,15 +422,9 @@
</COMPL>
</PARAM>
<ACTION sym="script" in="tty" out="tty" interrupt="true">
case $KLISH_PARAM_ip in
ipv4)
KLISH_PARAM_ip=ip
;;
ipv6)
KLISH_PARAM_ip=ipv6
;;
esac
show-legacy -p $KLISH_PARAM_ip route |pager
show routes $KLISH_PARAM_ip route |pager
# Deprecated in favor of Cisco likeness
printf "\n\e[1mNOTE:\e[0m this command is deprecated, please use 'show ip route' instead.\n"
</ACTION>
</COMMAND>
@@ -419,14 +441,12 @@
</COMMAND>
<!-- https://www.cisco.com/c/en/us/td/docs/wireless/access_point/mob_exp/83/cmd-ref/me_cr_book/me_ports_and_interfaces_cli.html -->
<COMMAND name="interfaces" help="Show interface info">
<COMMAND name="interface" help="Show interface info, default an overview of all">
<SWITCH name="optional" min="0" max="1">
<COMMAND name="name" help="Show detailed info about NAME">
<PARAM name="name" ptype="/IFACES" help="Interface." />
</COMMAND>
<PARAM name="ifname" ptype="/IFACES" help="Interface name" />
</SWITCH>
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show interface "$KLISH_PARAM_name" |pager
show interface "$KLISH_PARAM_ifname" |pager
</ACTION>
</COMMAND>
@@ -454,11 +474,40 @@
</ACTION>
</COMMAND>
<COMMAND name="route" help="Show IPv4 routing table" mode="switch">
<COMMAND name="route" help="Show routing table">
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show-legacy -p ip route |pager
show routes ipv4 |pager
</ACTION>
</COMMAND>
<COMMAND name="ospf" help="Show OSPF status">
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show ospf |pager
</ACTION>
<SWITCH name="optional" min="0">
<COMMAND name="neighbor" help="Show OSPF neighbors">
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show ospf neighbor |pager
</ACTION>
</COMMAND>
<COMMAND name="interface" help="Show OSPF interfaces">
<SWITCH name="optional" min="0" max="1">
<PARAM name="ifname" ptype="/IFACES" help="Interface name" />
</SWITCH>
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show ospf interface "$KLISH_PARAM_ifname" |pager
</ACTION>
</COMMAND>
<COMMAND name="route" help="Show OSPF routing table">
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show ospf route |pager
</ACTION>
</COMMAND>
</SWITCH>
</COMMAND>
</SWITCH>
</COMMAND>
@@ -468,9 +517,21 @@
</ACTION>
<SWITCH name="optional" min="0">
<COMMAND name="brief" help="Simplified (human-readable) output" mode="switch">
<ACTION sym="script" in="tty" out="tty" interrupt="true">
ip -color -6 -br addr | sort -V | sed 's/@dsa[0-9]/ /' |pager
</ACTION>
</COMMAND>
<COMMAND name="detailed" help="Detailed (full) output" mode="switch">
<ACTION sym="script" in="tty" out="tty" interrupt="true">
ip -color -6 -d addr |pager
</ACTION>
</COMMAND>
<COMMAND name="route" help="Show IPv6 routing table" mode="switch">
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show-legacy -p ipv6 route |pager
show routes ipv6 |pager
</ACTION>
</COMMAND>
</SWITCH>
+132 -2
View File
@@ -154,6 +154,134 @@ def services(args: List[str]) -> None:
cli_pretty(data, f"show-services")
def bfd(args: List[str]) -> None:
"""Handle show bfd [subcommand] [peer] [brief]
Subcommands:
(none) - Show BFD status summary (default)
peers - Show BFD peer sessions
peers brief - Show BFD peers in brief format
peer <addr> - Show specific BFD peer details
"""
# Fetch operational data from sysrepocfg
data = run_sysrepocfg("/ietf-routing:routing/control-plane-protocols/control-plane-protocol")
if not data:
print("No BFD data retrieved.")
return
if RAW_OUTPUT:
print(json.dumps(data, indent=2))
return
# Parse arguments: [subcommand] [peer_addr] [brief]
subcommand = ""
peer_addr = None
brief_flag = False
for arg in args:
if arg == "brief":
brief_flag = True
elif arg in ["peers", "peer"]:
subcommand = arg
elif arg: # Must be peer address
peer_addr = arg
# Default to empty string for general status
if not subcommand and not peer_addr:
subcommand = ""
# Add metadata to data
if peer_addr:
data['_peer_addr'] = peer_addr
if brief_flag:
data['_brief'] = True
# For brief view, fetch interface data to show interface:ip format
if brief_flag:
ifaces_data = run_sysrepocfg("/ietf-interfaces:interfaces")
if ifaces_data:
data['_interfaces'] = ifaces_data
# Route to appropriate formatter
if subcommand == "":
cli_pretty(data, "show-bfd-status")
elif subcommand == "peers":
if brief_flag:
cli_pretty(data, "show-bfd-peers-brief")
else:
cli_pretty(data, "show-bfd-peers")
elif subcommand == "peer":
cli_pretty(data, "show-bfd-peer")
else:
print(f"Unknown BFD subcommand: {subcommand}")
def ospf(args: List[str]) -> None:
"""Handle show ospf [subcommand] [ifname] [detail]
Subcommands:
(none) - General OSPF instance information
neighbor - OSPF neighbor table
interfaces - OSPF interface details (optionally for specific interface)
routes - OSPF routing table (local-rib)
Optional:
ifname - Interface name (for interfaces subcommand)
detail - Show detailed information (Cisco-style)
"""
# Fetch operational data from sysrepocfg
data = run_sysrepocfg("/ietf-routing:routing/control-plane-protocols/control-plane-protocol")
if not data:
print("No OSPF data retrieved.")
return
if RAW_OUTPUT:
print(json.dumps(data, indent=2))
return
# Parse arguments: subcommand, optional interface name, optional detail flag
subcommand = args[0] if len(args) > 0 and args[0] else ""
# Determine if second arg is interface name or detail flag
ifname = None
detail_flag = False
if len(args) > 1:
if args[1] == "detail":
detail_flag = True
else:
ifname = args[1]
# Check if third arg is detail
if len(args) > 2 and args[2] == "detail":
detail_flag = True
# Add metadata to data for formatters
if detail_flag:
data['_detail'] = True
if ifname:
data['_ifname'] = ifname
# For detailed interface view, fetch additional data (interfaces and BFD)
if subcommand == "interface" or subcommand == "interfaces":
ifaces_data = run_sysrepocfg("/ietf-interfaces:interfaces")
if ifaces_data:
data['_interfaces'] = ifaces_data
# Fetch BFD data for per-interface status
routing_data = run_sysrepocfg("/ietf-routing:routing/control-plane-protocols/control-plane-protocol")
if routing_data:
data['_routing'] = routing_data
# Route to appropriate formatter
if subcommand == "":
cli_pretty(data, "show-ospf")
elif subcommand == "neighbor":
cli_pretty(data, "show-ospf-neighbor")
elif subcommand == "interface" or subcommand == "interfaces":
cli_pretty(data, "show-ospf-interfaces")
elif subcommand == "route" or subcommand == "routes":
cli_pretty(data, "show-ospf-routes")
else:
print(f"Unknown OSPF subcommand: {subcommand}")
def routes(args: List[str]):
ip_version = args[0] if args and args[0] in ["ipv4", "ipv6"] else "ipv4"
@@ -296,12 +424,14 @@ def system(args: List[str]) -> None:
def execute_command(command: str, args: List[str]):
command_mapping = {
'bfd': bfd,
'dhcp': dhcp,
'hardware': hardware,
'interface': interface,
'ntp': ntp,
'routes': routes,
'lldp': lldp,
'ntp': ntp,
'ospf': ospf,
'routes': routes,
'services' : services,
'software' : software,
'stp': stp,
+693
View File
@@ -2819,6 +2819,670 @@ def show_firewall_service(json, name=None):
f"{ports:<{PadFirewall.service_ports}}")
def show_ospf(json_data):
"""Show OSPF general instance information"""
routing = json_data.get('ietf-routing:routing', {})
protocols = routing.get('control-plane-protocols', {}).get('control-plane-protocol', [])
ospf_instance = None
for protocol in protocols:
if 'ietf-ospf:ospf' in protocol:
ospf_instance = protocol
break
if not ospf_instance:
print("OSPF is not configured or running")
return
ospf = ospf_instance.get('ietf-ospf:ospf', {})
router_id = ospf.get('router-id', '0.0.0.0')
areas = ospf.get('areas', {}).get('area', [])
# OSPF Process header
print(f" OSPF Routing Process, Router ID: {router_id}")
print(f" Number of areas attached to this router: {len(areas)}")
print()
if not areas:
print("No areas configured")
return
# Display detailed area information
for area in areas:
area_id = area.get('area-id', '0.0.0.0')
area_type = area.get('area-type', 'ietf-ospf:normal-area')
# Determine area type display
if area_id == "0.0.0.0":
area_label = f" Area ID: {area_id} (Backbone)"
else:
if 'nssa' in area_type.lower():
area_label = f" Area ID: {area_id} (NSSA)"
elif 'stub' in area_type.lower():
area_label = f" Area ID: {area_id} (Stub)"
else:
area_label = f" Area ID: {area_id}"
print(area_label)
interfaces = area.get('interfaces', {}).get('interface', [])
interface_count = len(interfaces)
# Count active interfaces and neighbors
active_count = 0
neighbor_count = 0
fully_adjacent = 0
for iface in interfaces:
# Count active interfaces (enabled and up)
if iface.get('enabled', False):
state = iface.get('state', 'down')
if state != 'down':
active_count += 1
# Count neighbors
neighbors = iface.get('neighbors', {}).get('neighbor', [])
neighbor_count += len(neighbors)
# Count fully adjacent neighbors (full state)
for neighbor in neighbors:
if neighbor.get('state', '') == 'full':
fully_adjacent += 1
print(f" Number of interfaces in this area: Total: {interface_count}, Active: {active_count}")
print(f" Number of fully adjacent neighbors in this area: {fully_adjacent}")
print()
def show_ospf_interfaces(json_data):
"""Show OSPF interface information"""
routing = json_data.get('ietf-routing:routing', {})
protocols = routing.get('control-plane-protocols', {}).get('control-plane-protocol', [])
ospf_instance = None
for protocol in protocols:
if 'ietf-ospf:ospf' in protocol:
ospf_instance = protocol
break
if not ospf_instance:
print("OSPF is not configured or running")
return
ospf = ospf_instance.get('ietf-ospf:ospf', {})
router_id = ospf.get('router-id', '0.0.0.0')
areas = ospf.get('areas', {}).get('area', [])
if not areas:
print("No OSPF interfaces configured")
return
# Check if specific interface requested
requested_ifname = json_data.get('_ifname')
# Collect all interfaces from all areas
all_interfaces = []
for area in areas:
area_id = area.get('area-id', '0.0.0.0')
interfaces = area.get('interfaces', {}).get('interface', [])
for iface in interfaces:
iface['_area_id'] = area_id # Add area context
all_interfaces.append(iface)
if not all_interfaces:
print("No OSPF interfaces found")
return
# If specific interface requested, show detailed view
if requested_ifname:
target_iface = None
for iface in all_interfaces:
if iface.get('name') == requested_ifname:
target_iface = iface
break
if not target_iface:
print(f"Interface {requested_ifname} not found in OSPF")
return
# Display detailed interface information (vtysh-style)
name = target_iface.get('name', 'unknown')
area_id = target_iface.get('_area_id', '0.0.0.0')
state = target_iface.get('state', 'down')
cost = target_iface.get('cost', 0)
priority = target_iface.get('priority', 1)
iface_type = target_iface.get('interface-type', 'unknown')
hello_interval = target_iface.get('hello-interval', 10)
dead_interval = target_iface.get('dead-interval', 40)
retransmit_interval = target_iface.get('retransmit-interval', 5)
transmit_delay = target_iface.get('transmit-delay', 1)
dr_id = target_iface.get('dr-router-id', '-')
dr_addr = target_iface.get('dr-ip-addr', '-')
bdr_id = target_iface.get('bdr-router-id', '-')
bdr_addr = target_iface.get('bdr-ip-addr', '-')
neighbors = target_iface.get('neighbors', {}).get('neighbor', [])
# Get interface IP address from interfaces data
ip_address = None
broadcast = None
if json_data.get('_interfaces'):
interfaces = json_data['_interfaces'].get('ietf-interfaces:interfaces', {}).get('interface', [])
for iface in interfaces:
if iface.get('name') == name:
# Get IPv4 address
ipv4 = iface.get('ietf-ip:ipv4', {})
addresses = ipv4.get('address', [])
if addresses:
addr = addresses[0]
ip = addr.get('ip', '')
prefix_len = addr.get('prefix-length', 0)
if ip and prefix_len:
ip_address = f"{ip}/{prefix_len}"
# Calculate broadcast (simple approximation for /30 networks)
import ipaddress
try:
net = ipaddress.IPv4Network(ip_address, strict=False)
broadcast = str(net.broadcast_address)
except:
broadcast = None
break
# Get BFD information from routing data
bfd_detect_mult = None
bfd_rx_interval = None
bfd_tx_interval = None
bfd_state = None
if json_data.get('_routing'):
routing = json_data['_routing'].get('ietf-routing:routing', {})
protocols = routing.get('control-plane-protocols', {}).get('control-plane-protocol', [])
for proto in protocols:
if proto.get('type') == 'infix-routing:bfdv1':
bfd = proto.get('ietf-bfd:bfd', {})
ip_sh = bfd.get('ietf-bfd-ip-sh:ip-sh', {})
sessions_container = ip_sh.get('sessions', {})
sessions = sessions_container.get('session', [])
# Find BFD session for this interface
for session in sessions:
if session.get('interface') == name:
session_running = session.get('session-running', {})
# Get session state
bfd_state = session_running.get('local-state', 'down')
# Get detection multiplier and intervals
rx_interval = session_running.get('negotiated-rx-interval', 0)
tx_interval = session_running.get('negotiated-tx-interval', 0)
detection_time = session_running.get('detection-time', 0)
if rx_interval and detection_time:
bfd_detect_mult = detection_time // rx_interval
bfd_rx_interval = rx_interval // 1000 if rx_interval else 0 # Convert μs to ms
bfd_tx_interval = tx_interval // 1000 if tx_interval else 0
break
break
# State display
state_display = state.upper() if state in ['dr', 'bdr'] else state.capitalize()
if state == 'dr-other':
state_display = 'DROther'
# Network type display (match FRR/Cisco format)
network_type_map = {
'point-to-point': 'POINTOPOINT',
'broadcast': 'BROADCAST',
'non-broadcast': 'NBMA'
}
network_type = network_type_map.get(iface_type, iface_type.upper())
print(f"{name} is up")
if ip_address:
broadcast_str = f", Broadcast {broadcast}" if broadcast else ""
print(f" Internet Address {ip_address}{broadcast_str}, Area {area_id}")
else:
print(f" Internet Address (not available), Area {area_id}")
print(f" Router ID {router_id}, Network Type {network_type}, Cost: {cost}")
print(f" Transmit Delay is {transmit_delay} sec, State {state_display}, Priority {priority}")
if dr_id != '-':
print(f" Designated Router (ID) {dr_id}, Interface Address {dr_addr}")
if bdr_id != '-':
print(f" Backup Designated Router (ID) {bdr_id}, Interface Address {bdr_addr}")
print(f" Timer intervals configured, Hello {hello_interval}s, Dead {dead_interval}s, Retransmit {retransmit_interval}")
# Count adjacent neighbors
adjacent_count = sum(1 for n in neighbors if n.get('state') == 'full')
print(f" Neighbor Count is {len(neighbors)}, Adjacent neighbor count is {adjacent_count}")
# Show BFD status if available
if bfd_detect_mult is not None and bfd_rx_interval is not None and bfd_tx_interval is not None:
# Format state for display
state_display_map = {
'up': 'Up (two-way connection established)',
'down': 'Down (no connection)',
'init': 'Init (initializing, no two-way yet)',
'adminDown': 'AdminDown (administratively disabled)'
}
bfd_state_display = state_display_map.get(bfd_state, bfd_state.capitalize() if bfd_state else 'Unknown')
print(f" BFD: Status: {bfd_state_display}")
print(f" Detect Multiplier: {bfd_detect_mult}, Min Rx interval: {bfd_rx_interval}, Min Tx interval: {bfd_tx_interval}")
else:
print(f" BFD: Disabled")
return
# Display table view (no specific interface)
hdr = f"{'INTERFACE':<12} {'AREA':<12} {'STATE':<10} {'COST':<6} {'PRI':<4} {'DR':<15} {'BDR':<15} {'NBRS':<5}"
print(Decore.invert(hdr))
for iface in all_interfaces:
name = iface.get('name', 'unknown')
area_id = iface.get('_area_id', '0.0.0.0')
state = iface.get('state', 'down')
cost = iface.get('cost', 0)
priority = iface.get('priority', 1)
dr_id = iface.get('dr-router-id', '-')
bdr_id = iface.get('bdr-router-id', '-')
neighbors = iface.get('neighbors', {}).get('neighbor', [])
nbr_count = len(neighbors)
# Capitalize state nicely
state_display = state.upper() if state in ['dr', 'bdr'] else state.capitalize()
if state == 'dr-other':
state_display = 'DROther'
# Shorten router IDs for display
dr_display = dr_id if dr_id != '-' else '-'
bdr_display = bdr_id if bdr_id != '-' else '-'
print(f"{name:<12} {area_id:<12} {state_display:<10} {cost:<6} {priority:<4} {dr_display:<15} {bdr_display:<15} {nbr_count:<5}")
def show_ospf_neighbor(json_data):
"""Show OSPF neighbor information"""
routing = json_data.get('ietf-routing:routing', {})
protocols = routing.get('control-plane-protocols', {}).get('control-plane-protocol', [])
ospf_instance = None
for protocol in protocols:
if 'ietf-ospf:ospf' in protocol:
ospf_instance = protocol
break
if not ospf_instance:
print("OSPF is not configured or running")
return
ospf = ospf_instance.get('ietf-ospf:ospf', {})
areas = ospf.get('areas', {}).get('area', [])
if not areas:
print("No OSPF areas configured")
return
# Collect all neighbors from all interfaces in all areas
all_neighbors = []
for area in areas:
area_id = area.get('area-id', '0.0.0.0')
interfaces = area.get('interfaces', {}).get('interface', [])
for iface in interfaces:
iface_name = iface.get('name', 'unknown')
neighbors = iface.get('neighbors', {}).get('neighbor', [])
for neighbor in neighbors:
neighbor['_interface'] = iface_name
neighbor['_area_id'] = area_id
all_neighbors.append(neighbor)
if not all_neighbors:
print("No OSPF neighbors found")
return
# Display table header with PRI and UPTIME columns
hdr = f"{'NEIGHBOR ID':<16} {'PRI':<4} {'STATE':<12} {'UPTIME':<10} {'DEAD TIME':<10} {'ADDRESS':<16} {'INTERFACE':<18} {'AREA':<12}"
print(Decore.invert(hdr))
for neighbor in all_neighbors:
neighbor_id = neighbor.get('neighbor-router-id', '0.0.0.0')
address = neighbor.get('address', '0.0.0.0')
state = neighbor.get('state', 'down')
priority = neighbor.get('priority', 1) # Default priority is 1
uptime = neighbor.get('infix-routing:uptime', 0)
dead_timer = neighbor.get('dead-timer', 0)
# Use interface-name (e.g., "e5:10.0.23.1") if available, fallback to interface name
interface = neighbor.get('infix-routing:interface-name', neighbor.get('_interface', 'unknown'))
area_id = neighbor.get('_area_id', '0.0.0.0')
role = neighbor.get('infix-routing:role', '')
# Capitalize state and add role if present
state_base = state.capitalize() if state != '2-way' else '2-Way'
if role and state_base == 'Full':
state_display = f"{state_base}/{role}"
else:
state_display = state_base
# Format uptime (convert seconds to human-readable format)
if uptime > 0:
days = uptime // 86400
hours = (uptime % 86400) // 3600
minutes = (uptime % 3600) // 60
seconds = uptime % 60
if days > 0:
uptime_display = f"{days}d{hours:02d}h{minutes:02d}m"
elif hours > 0:
uptime_display = f"{hours}h{minutes:02d}m{seconds:02d}s"
elif minutes > 0:
uptime_display = f"{minutes}m{seconds:02d}s"
else:
uptime_display = f"{seconds}s"
else:
uptime_display = "-"
# Format dead timer
dead_display = f"{dead_timer}s" if dead_timer > 0 else "-"
print(f"{neighbor_id:<16} {priority:<4} {state_display:<12} {uptime_display:<10} {dead_display:<10} {address:<16} {interface:<18} {area_id:<12}")
def show_ospf_routes(json_data):
"""Show OSPF routing table (local-rib)"""
routing = json_data.get('ietf-routing:routing', {})
protocols = routing.get('control-plane-protocols', {}).get('control-plane-protocol', [])
ospf_instance = None
for protocol in protocols:
if 'ietf-ospf:ospf' in protocol:
ospf_instance = protocol
break
if not ospf_instance:
print("OSPF is not configured or running")
return
ospf = ospf_instance.get('ietf-ospf:ospf', {})
local_rib = ospf.get('local-rib', {})
routes = local_rib.get('route', [])
if not routes:
print("No OSPF routes in local RIB")
return
# Display table header (AREA column from infix-routing augmentation)
hdr = f"{'DESTINATION':<20} {'TYPE':<10} {'AREA':<12} {'METRIC':<8} {'NEXT HOP':<16} {'INTERFACE':<12}"
print(Decore.invert(hdr))
for route in routes:
prefix = route.get('prefix', '0.0.0.0/0')
route_type = route.get('route-type', 'unknown')
# Check for area-id with infix-routing prefix (augmented field)
area_id = route.get('infix-routing:area-id', route.get('area-id', '-'))
metric = route.get('metric', 0)
# Simplify route type display
type_map = {
'intra-area': 'Intra',
'inter-area': 'Inter',
'external-1': 'Ext-1',
'external-2': 'Ext-2',
'nssa-1': 'NSSA-1',
'nssa-2': 'NSSA-2'
}
type_display = type_map.get(route_type, route_type)
# Get next hops
next_hops_data = route.get('next-hops', {})
next_hops = next_hops_data.get('next-hop', [])
if not next_hops:
print(f"{prefix:<20} {type_display:<10} {area_id:<12} {metric:<8} {'-':<16} {'-':<12}")
else:
# Display first next hop on same line as route
first_hop = next_hops[0]
next_hop_addr = first_hop.get('next-hop', '-')
outgoing_iface = first_hop.get('outgoing-interface', '-')
print(f"{prefix:<20} {type_display:<10} {area_id:<12} {metric:<8} {next_hop_addr:<16} {outgoing_iface:<12}")
# Display additional next hops indented
for hop in next_hops[1:]:
next_hop_addr = hop.get('next-hop', '-')
outgoing_iface = hop.get('outgoing-interface', '-')
print(f"{'':<52} {next_hop_addr:<16} {outgoing_iface:<12}")
def show_bfd_status(json_data):
"""Show BFD status summary"""
routing = json_data.get('ietf-routing:routing', {})
protocols = routing.get('control-plane-protocols', {})
protocol_list = protocols.get('control-plane-protocol', [])
bfd_protocol = None
for proto in protocol_list:
if proto.get('type') == 'infix-routing:bfdv1':
bfd_protocol = proto
break
if not bfd_protocol:
print("BFD is not enabled")
return
print("Is enabled, single-hop (ospf)")
def show_bfd_peers(json_data):
"""Show BFD peer sessions in detailed format"""
routing = json_data.get('ietf-routing:routing', {})
protocols = routing.get('control-plane-protocols', {})
protocol_list = protocols.get('control-plane-protocol', [])
bfd_protocol = None
for proto in protocol_list:
if proto.get('type') == 'infix-routing:bfdv1':
bfd_protocol = proto
break
if not bfd_protocol:
print("BFD is not enabled")
return
bfd = bfd_protocol.get('ietf-bfd:bfd', {})
ip_sh = bfd.get('ietf-bfd-ip-sh:ip-sh', {})
sessions_container = ip_sh.get('sessions', {})
sessions = sessions_container.get('session', [])
if not sessions:
print("No BFD sessions found")
return
print("BFD Peers:")
for session in sessions:
peer = session.get('dest-addr', 'unknown')
interface = session.get('interface', 'unknown')
local_disc = session.get('local-discriminator', 0)
remote_disc = session.get('remote-discriminator', 0)
# Get session running state
session_running = session.get('session-running', {})
local_state = session_running.get('local-state', 'unknown')
remote_state = session_running.get('remote-state', 'unknown')
local_diag = session_running.get('local-diagnostic', 'unknown')
detection_mode = session_running.get('detection-mode', 'unknown')
detection_time = session_running.get('detection-time', 0)
rx_interval = session_running.get('negotiated-rx-interval', 0)
tx_interval = session_running.get('negotiated-tx-interval', 0)
# Convert microseconds to milliseconds for display
rx_ms = rx_interval // 1000 if rx_interval else 0
tx_ms = tx_interval // 1000 if tx_interval else 0
detect_mult = detection_time // rx_interval if rx_interval and detection_time else 0
print(f" peer {peer}")
print(f" ID: {local_disc}")
print(f" Remote ID: {remote_disc}")
print(f" Status: {local_state}")
print(f" Diagnostics: {local_diag}")
print(f" Remote diagnostics: {local_diag}")
print(f" Peer Type: dynamic")
print(f" Local timers:")
print(f" Detect-multiplier: {detect_mult}")
print(f" Receive interval: {rx_ms}ms")
print(f" Transmission interval: {tx_ms}ms")
print(f" Echo transmission interval: disabled")
print(f" Remote timers:")
print(f" Detect-multiplier: {detect_mult}")
print(f" Receive interval: {rx_ms}ms")
print(f" Transmission interval: {tx_ms}ms")
print(f" Echo transmission interval: disabled")
print()
def show_bfd_peers_brief(json_data):
"""Show BFD peers in brief table format"""
routing = json_data.get('ietf-routing:routing', {})
protocols = routing.get('control-plane-protocols', {})
protocol_list = protocols.get('control-plane-protocol', [])
# Find BFD protocol instance
bfd_protocol = None
for proto in protocol_list:
if proto.get('type') == 'infix-routing:bfdv1':
bfd_protocol = proto
break
if not bfd_protocol:
print("BFD is not enabled")
return
bfd = bfd_protocol.get('ietf-bfd:bfd', {})
ip_sh = bfd.get('ietf-bfd-ip-sh:ip-sh', {})
sessions_container = ip_sh.get('sessions', {})
sessions = sessions_container.get('session', [])
if not sessions:
print("No BFD sessions found")
return
# Build interface to IP mapping by looking up from peer address
# We need to extract the local IP from the same subnet as the peer
ifaces_data = json_data.get('_interfaces', {})
if_ip_map = {}
if ifaces_data:
interfaces = ifaces_data.get('ietf-interfaces:interfaces', {}).get('interface', [])
for iface in interfaces:
ifname = iface.get('name', '')
ipv4 = iface.get('ietf-ip:ipv4', {})
addresses = ipv4.get('address', [])
for addr_entry in addresses:
ip = addr_entry.get('ip', '')
prefix_len = addr_entry.get('prefix-length', 0)
if ip and ifname:
if_ip_map[ifname] = ip
print(f"Session count: {len(sessions)}")
hdr = f"{'SESSION ID':<16} {'LOCAL':<22} {'PEER':<22} {'STATE':<10}"
print(Decore.invert(hdr))
for session in sessions:
local_disc = session.get('local-discriminator', 0)
interface = session.get('interface', '-')
peer = session.get('dest-addr', '-')
# Get session running state
session_running = session.get('session-running', {})
local_state = session_running.get('local-state', 'unknown')
# Format local address as interface:ip
local_ip = if_ip_map.get(interface, '')
if local_ip:
local_addr = f"{interface}:{local_ip}"
else:
local_addr = interface
print(f"{local_disc:<16} {local_addr:<22} {peer:<22} {local_state:<10}")
def show_bfd_peer(json_data):
"""Show specific BFD peer details"""
peer_addr = json_data.get('_peer_addr')
if not peer_addr:
print("Error: No peer address specified")
return
routing = json_data.get('ietf-routing:routing', {})
protocols = routing.get('control-plane-protocols', {})
protocol_list = protocols.get('control-plane-protocol', [])
# Find BFD protocol instance
bfd_protocol = None
for proto in protocol_list:
if proto.get('type') == 'infix-routing:bfdv1':
bfd_protocol = proto
break
if not bfd_protocol:
print("BFD is not enabled")
return
bfd = bfd_protocol.get('ietf-bfd:bfd', {})
ip_sh = bfd.get('ietf-bfd-ip-sh:ip-sh', {})
sessions_container = ip_sh.get('sessions', {})
sessions = sessions_container.get('session', [])
# Find the specific peer
target_session = None
for session in sessions:
if session.get('dest-addr') == peer_addr:
target_session = session
break
if not target_session:
print(f"BFD peer {peer_addr} not found")
return
# Display single peer details
peer = target_session.get('dest-addr', 'unknown')
interface = target_session.get('interface', 'unknown')
local_disc = target_session.get('local-discriminator', 0)
remote_disc = target_session.get('remote-discriminator', 0)
# Get session running state
session_running = target_session.get('session-running', {})
local_state = session_running.get('local-state', 'unknown')
remote_state = session_running.get('remote-state', 'unknown')
local_diag = session_running.get('local-diagnostic', 'unknown')
detection_mode = session_running.get('detection-mode', 'unknown')
detection_time = session_running.get('detection-time', 0)
rx_interval = session_running.get('negotiated-rx-interval', 0)
tx_interval = session_running.get('negotiated-tx-interval', 0)
# Convert microseconds to milliseconds for display
rx_ms = rx_interval // 1000 if rx_interval else 0
tx_ms = tx_interval // 1000 if tx_interval else 0
detect_mult = detection_time // rx_interval if rx_interval and detection_time else 0
print("BFD Peer:")
print(f" peer {peer}")
print(f" ID: {local_disc}")
print(f" Remote ID: {remote_disc}")
print(f" Status: {local_state}")
print(f" Diagnostics: {local_diag}")
print(f" Remote diagnostics: {local_diag}")
print(f" Peer Type: dynamic")
print(f" Local timers:")
print(f" Detect-multiplier: {detect_mult}")
print(f" Receive interval: {rx_ms}ms")
print(f" Transmission interval: {tx_ms}ms")
print(f" Echo transmission interval: disabled")
print(f" Remote timers:")
print(f" Detect-multiplier: {detect_mult}")
print(f" Receive interval: {rx_ms}ms")
print(f" Transmission interval: {tx_ms}ms")
print(f" Echo transmission interval: disabled")
def show_bfd(json_data):
"""Legacy function - redirect to show_bfd_peers_brief for backward compatibility"""
show_bfd_peers_brief(json_data)
def main():
global UNIT_TEST
@@ -2861,6 +3525,17 @@ def main():
subparsers.add_parser('show-ntp', help='Show NTP sources')
subparsers.add_parser('show-bfd', help='Show BFD sessions')
subparsers.add_parser('show-bfd-status', help='Show BFD status')
subparsers.add_parser('show-bfd-peers', help='Show BFD peers')
subparsers.add_parser('show-bfd-peers-brief', help='Show BFD peers brief')
subparsers.add_parser('show-bfd-peer', help='Show BFD peer')
subparsers.add_parser('show-ospf', help='Show OSPF instance information')
subparsers.add_parser('show-ospf-interfaces', help='Show OSPF interfaces')
subparsers.add_parser('show-ospf-neighbor', help='Show OSPF neighbors')
subparsers.add_parser('show-ospf-routes', help='Show OSPF routing table')
subparsers.add_parser('show-routing-table', help='Show the routing table') \
.add_argument('-i', '--ip', required=True, help='IPv4 or IPv6 address')
@@ -2896,6 +3571,24 @@ def main():
show_firewall_service(json_data, args.name)
elif args.command == "show-ntp":
show_ntp(json_data)
elif args.command == "show-bfd":
show_bfd(json_data)
elif args.command == "show-bfd-status":
show_bfd_status(json_data)
elif args.command == "show-bfd-peers":
show_bfd_peers(json_data)
elif args.command == "show-bfd-peers-brief":
show_bfd_peers_brief(json_data)
elif args.command == "show-bfd-peer":
show_bfd_peer(json_data)
elif args.command == "show-ospf":
show_ospf(json_data)
elif args.command == "show-ospf-interfaces":
show_ospf_interfaces(json_data)
elif args.command == "show-ospf-neighbor":
show_ospf_neighbor(json_data)
elif args.command == "show-ospf-routes":
show_ospf_routes(json_data)
elif args.command == "show-routing-table":
show_routing_table(json_data, args.ip)
elif args.command == "show-software":
+3
View File
@@ -81,6 +81,9 @@ def main():
elif args.model == 'infix-firewall':
from . import infix_firewall
yang_data = infix_firewall.operational()
elif args.model == 'ietf-bfd-ip-sh':
from . import ietf_bfd_ip_sh
yang_data = ietf_bfd_ip_sh.operational()
else:
common.LOG.warning("Unsupported model %s", args.model)
sys.exit(1)
+96
View File
@@ -0,0 +1,96 @@
from .common import insert
from .host import HOST
def frr_to_ietf_state(state):
"""Convert FRR BFD state to IETF BFD state"""
state_map = {
"up": "up",
"down": "down",
"init": "init",
"adminDown": "adminDown"
}
return state_map.get(state, "down")
def add_sessions(control_protocols):
"""Fetch BFD session data from FRR"""
cmd = ['vtysh', '-c', 'show bfd peers json']
data = HOST.run_json(cmd, default=[])
if not data:
return # No BFD sessions available
control_protocol = {}
control_protocol["type"] = "infix-routing:bfdv1"
control_protocol["name"] = "bfd"
control_protocol["ietf-bfd:bfd"] = {}
control_protocol["ietf-bfd:bfd"]["ietf-bfd-ip-sh:ip-sh"] = {}
control_protocol["ietf-bfd:bfd"]["ietf-bfd-ip-sh:ip-sh"]["sessions"] = {}
sessions = []
# FRR returns a list of BFD peers
for peer in data:
# Only process single-hop sessions (multihop == false)
if peer.get("multihop", False):
continue
session = {}
# Key fields: interface and dest-addr
session["interface"] = peer.get("interface", "unknown")
session["dest-addr"] = peer.get("peer", "0.0.0.0")
# Operational state fields (config false)
# Local and remote discriminators
if peer.get("id") is not None:
session["local-discriminator"] = peer["id"]
if peer.get("remote-id") is not None:
session["remote-discriminator"] = peer["remote-id"]
# Session running state
session["session-running"] = {}
# Local and remote state
state = peer.get("status", "down")
session["session-running"]["local-state"] = frr_to_ietf_state(state)
# Remote state not directly available in FRR output, infer from status
session["session-running"]["remote-state"] = frr_to_ietf_state(state)
# Local diagnostic - not directly available, use "none"
session["session-running"]["local-diagnostic"] = "none"
# Detection mode - FRR uses async mode for OSPF-created sessions
session["session-running"]["detection-mode"] = "async-without-echo"
# Timing intervals (convert milliseconds to microseconds for YANG)
if peer.get("receive-interval") is not None:
session["session-running"]["negotiated-rx-interval"] = peer["receive-interval"] * 1000
if peer.get("transmit-interval") is not None:
session["session-running"]["negotiated-tx-interval"] = peer["transmit-interval"] * 1000
# Detection time (in microseconds)
if peer.get("detect-multiplier") is not None and peer.get("receive-interval") is not None:
detection_time_ms = peer["detect-multiplier"] * peer["receive-interval"]
session["session-running"]["detection-time"] = detection_time_ms * 1000
# Path type
session["path-type"] = "ietf-bfd-types:path-ip-sh"
session["ip-encapsulation"] = True
sessions.append(session)
if sessions:
control_protocol["ietf-bfd:bfd"]["ietf-bfd-ip-sh:ip-sh"]["sessions"]["session"] = sessions
insert(control_protocols, "control-plane-protocol", [control_protocol])
def operational():
out = {
"ietf-routing:routing": {
"control-plane-protocols": {}
}
}
add_sessions(out['ietf-routing:routing']['control-plane-protocols'])
return out
+97 -1
View File
@@ -10,6 +10,14 @@ def frr_to_ietf_neighbor_state(state):
return state.lower()
def frr_to_ietf_neighbor_role(role):
"""Translate FRR neighbor role to YANG enumeration values"""
if role == "Backup":
return "BDR"
# DR and DROther are already correct
return role
def add_routes(ospf):
"""Fetch OSPF routes from Frr"""
cmd = ['vtysh', '-c', "show ip ospf route json"]
@@ -38,6 +46,22 @@ def add_routes(ospf):
elif routetype[0] == "N":
route["route-type"] = "intra-area"
# Add area information if available
# Note: augmented by infix-routing.yang since standard ietf-ospf doesn't include it
# Must use the augmenting module's prefix
if info.get("area") is not None:
route["infix-routing:area-id"] = info["area"]
# Add metric (cost) if available
if info.get("cost") is not None:
route["metric"] = info["cost"]
elif info.get("metric") is not None:
route["metric"] = info["metric"]
# Add route-tag for external routes
if info.get("tag") is not None:
route["route-tag"] = info["tag"]
for hop in info["nexthops"]:
nexthop = {}
if hop["ip"] != " ":
@@ -119,13 +143,85 @@ def add_areas(control_protocols):
val = xlate.get(iface["state"], "unknown")
interface["state"] = val
# Interface priority (for DR/BDR election)
if iface.get("priority") is not None:
interface["priority"] = iface["priority"]
# Interface cost
if iface.get("cost") is not None:
interface["cost"] = iface["cost"]
# Configuration timers (in seconds)
if iface.get("timerDeadSecs") is not None:
interface["dead-interval"] = iface["timerDeadSecs"]
if iface.get("timerRetransmitSecs") is not None:
interface["retransmit-interval"] = iface["timerRetransmitSecs"]
if iface.get("transmitDelaySecs") is not None:
interface["transmit-delay"] = iface["transmitDelaySecs"]
# Hello interval - convert from milliseconds to seconds
if iface.get("timerMsecs") is not None:
hello_sec = iface["timerMsecs"] // 1000
# timer-value-seconds16 requires range 1..65535, use max(1, value)
if hello_sec >= 1:
interface["hello-interval"] = hello_sec
# Operational state timers (config false)
# Hello timer - time remaining until next Hello (convert ms to seconds)
if iface.get("timerHelloInMsecs") is not None:
hello_timer_sec = iface["timerHelloInMsecs"] // 1000
# timer-value-seconds16 requires range 1..65535, use max(1, value)
if hello_timer_sec >= 1:
interface["hello-timer"] = hello_timer_sec
# Wait timer - time until interface exits Waiting state
if iface.get("timerWaitSecs") is not None:
wait_sec = iface["timerWaitSecs"]
# timer-value-seconds16 requires range 1..65535
if wait_sec >= 1:
interface["wait-timer"] = wait_sec
neighbors = []
for neigh in iface["neighbors"]:
neighbor = {}
neighbor["neighbor-router-id"] = neigh["neighborIp"]
neighbor["address"] = neigh["ifaceAddress"]
neighbor["dead-timer"] = neigh["routerDeadIntervalTimerDueMsec"]
# Priority - use existing YANG leaf for operational data
if neigh.get("nbrPriority") is not None:
neighbor["priority"] = neigh["nbrPriority"]
# Uptime - convert from milliseconds to seconds
# Note: augmented by infix-routing.yang
# Use lastPrgrsvChangeMsec from detail output (time since last progressive state change)
if neigh.get("lastPrgrsvChangeMsec") is not None:
uptime_sec = neigh["lastPrgrsvChangeMsec"] // 1000
neighbor["infix-routing:uptime"] = uptime_sec
# Dead timer - convert from milliseconds to seconds
# timer-value-seconds16 requires range 1..65535
if neigh.get("routerDeadIntervalTimerDueMsec") is not None:
dead_timer_sec = neigh["routerDeadIntervalTimerDueMsec"] // 1000
if dead_timer_sec >= 1:
neighbor["dead-timer"] = dead_timer_sec
neighbor["state"] = frr_to_ietf_neighbor_state(neigh["nbrState"])
# Store role (DR/BDR/DROther) for display
# Note: augmented by infix-routing.yang
if neigh.get("role"):
neighbor["infix-routing:role"] = frr_to_ietf_neighbor_role(neigh["role"])
# Store interface name with local address (e.g., "e5:10.0.23.1")
# Note: augmented by infix-routing.yang
# Compose from ifaceName and localIfaceAddress
if neigh.get("ifaceName") and neigh.get("localIfaceAddress"):
neighbor["infix-routing:interface-name"] = f"{neigh['ifaceName']}:{neigh['localIfaceAddress']}"
elif neigh.get("ifaceName"):
neighbor["infix-routing:interface-name"] = neigh["ifaceName"]
if neigh.get("routerDesignatedId"):
neighbor["dr-router-id"] = neigh["routerDesignatedId"]
if neigh.get("routerDesignatedBackupId"):
+39
View File
@@ -40,6 +40,7 @@
#define XPATH_HARDWARE_BASE "/ietf-hardware:hardware"
#define XPATH_SYSTEM_BASE "/ietf-system"
#define XPATH_ROUTING_OSPF XPATH_ROUTING_BASE "/ospf"
#define XPATH_ROUTING_BFD XPATH_ROUTING_BASE "/bfd"
#define XPATH_CONTAIN_BASE "/infix-containers:containers"
#define XPATH_DHCP_SERVER_BASE "/infix-dhcp-server:dhcp-server"
#define XPATH_LLDP_BASE "/ieee802-dot1ab-lldp:lldp"
@@ -259,6 +260,42 @@ static int sr_ospf_cb(sr_session_ctx_t *session, uint32_t, const char *,
return err;
}
static int sr_bfd_cb(sr_session_ctx_t *session, uint32_t, const char *,
const char *, const char *xpath, uint32_t,
struct lyd_node **parent, __attribute__((unused)) void *priv)
{
char *yanger_args[5] = {
YANGER_BINPATH,
"ietf-bfd-ip-sh",
NULL
};
const struct ly_ctx *ctx;
sr_conn_ctx_t *con;
sr_error_t err;
DEBUG("Incoming BFD query for xpath: %s", xpath);
con = sr_session_get_connection(session);
if (!con) {
ERROR("Error, getting sr connection");
return SR_ERR_INTERNAL;
}
ctx = sr_acquire_context(con);
if (!ctx) {
ERROR("Error, acquiring context");
return SR_ERR_INTERNAL;
}
err = ly_add_yanger_data(ctx, parent, yanger_args);
if (err)
ERROR("Error adding yanger data");
sr_release_context(con);
return err;
}
static void sigint_cb(struct ev_loop *loop, struct ev_signal *, int)
{
@@ -343,6 +380,8 @@ static int subscribe_to_all(struct statd *statd)
return SR_ERR_INTERNAL;
if (subscribe(statd, "ietf-routing", XPATH_ROUTING_OSPF, sr_ospf_cb))
return SR_ERR_INTERNAL;
if (subscribe(statd, "ietf-routing", XPATH_ROUTING_BFD, sr_bfd_cb))
return SR_ERR_INTERNAL;
if (subscribe(statd, "ietf-hardware", XPATH_HARDWARE_BASE, sr_generic_cb))
return SR_ERR_INTERNAL;
if (subscribe(statd, "ietf-system", XPATH_SYSTEM_BASE":system", sr_generic_cb))
+52 -8
View File
@@ -18,8 +18,12 @@
{
"neighbor-router-id": "10.1.2.1",
"address": "10.1.2.1",
"dead-timer": 22506,
"state": "full"
"priority": 1,
"infix-routing:uptime": 57,
"dead-timer": 22,
"state": "full",
"infix-routing:role": "DROther",
"infix-routing:interface-name": "e3.8:10.1.1.1"
}
]
},
@@ -27,7 +31,15 @@
"passive": false,
"enabled": true,
"interface-type": "point-to-point",
"state": "point-to-point"
"state": "point-to-point",
"priority": 1,
"cost": 1,
"dead-interval": 40,
"retransmit-interval": 5,
"transmit-delay": 1,
"hello-interval": 10,
"hello-timer": 2,
"wait-timer": 40
},
{
"ietf-ospf:neighbors": {
@@ -35,8 +47,12 @@
{
"neighbor-router-id": "10.1.3.1",
"address": "10.1.3.1",
"dead-timer": 22334,
"state": "full"
"priority": 1,
"infix-routing:uptime": 57,
"dead-timer": 22,
"state": "full",
"infix-routing:role": "DROther",
"infix-routing:interface-name": "e4.8:10.1.1.1"
}
]
},
@@ -44,7 +60,15 @@
"passive": false,
"enabled": true,
"interface-type": "point-to-point",
"state": "point-to-point"
"state": "point-to-point",
"priority": 1,
"cost": 1,
"dead-interval": 40,
"retransmit-interval": 5,
"transmit-delay": 1,
"hello-interval": 10,
"hello-timer": 2,
"wait-timer": 40
},
{
"ietf-ospf:neighbors": {
@@ -52,8 +76,12 @@
{
"neighbor-router-id": "192.168.100.1",
"address": "10.1.1.100",
"dead-timer": 31385,
"priority": 1,
"infix-routing:uptime": 23,
"dead-timer": 31,
"state": "full",
"infix-routing:role": "DR",
"infix-routing:interface-name": "e6:10.1.1.101",
"dr-router-id": "192.168.100.1",
"bdr-router-id": "10.1.1.1"
}
@@ -67,7 +95,15 @@
"passive": false,
"enabled": true,
"interface-type": "broadcast",
"state": "bdr"
"state": "bdr",
"priority": 1,
"cost": 1,
"dead-interval": 40,
"retransmit-interval": 5,
"transmit-delay": 1,
"hello-interval": 10,
"hello-timer": 2,
"wait-timer": 40
}
]
},
@@ -82,6 +118,8 @@
{
"prefix": "0.0.0.0/0",
"route-type": "inter-area",
"infix-routing:area-id": "0.0.0.1",
"metric": 2,
"next-hops": {
"next-hop": [
{
@@ -93,6 +131,8 @@
{
"prefix": "10.1.1.0/24",
"route-type": "intra-area",
"infix-routing:area-id": "0.0.0.1",
"metric": 1,
"next-hops": {
"next-hop": [
{
@@ -104,6 +144,8 @@
{
"prefix": "10.1.2.0/24",
"route-type": "intra-area",
"infix-routing:area-id": "0.0.0.1",
"metric": 2,
"next-hops": {
"next-hop": [
{
@@ -118,6 +160,8 @@
{
"prefix": "10.1.3.0/24",
"route-type": "intra-area",
"infix-routing:area-id": "0.0.0.1",
"metric": 2,
"next-hops": {
"next-hop": [
{