statd: extend ospf operational data to replace vtysh commands in cli

Collect more data from FRR to the OSPF and BFD operational datastore.
Refactor CLI commands to use the YANG datastore instead of vtysh.

Additionally, the BFD command(s) have been moved to the top level of
admin-exec, which is where vtysh has them.

Finally, preparing for future OSPFv3 support, all commands have been
given a new context under 'show ip ospf'.  Deprecation warnings have
been added to the existing 'show ospf' commands.

Also, for consistency, all commands names in plural have been changed to
their singular form: routes -> route, interfaes -> interface, etc.  This
is what vtysh, Cisco, and others do as well.

Fixes #1190

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2025-11-12 22:28:18 +01:00
parent b1f83e5ce7
commit f79c0ee903
12 changed files with 1484 additions and 46 deletions
+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@2025-11-10.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;
}
}
}
+106 -21
View File
@@ -15,14 +15,33 @@ 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";
reference "RFC 2328, RFC 3101";
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";
@@ -125,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;
@@ -385,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"
{
@@ -411,10 +428,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:priority"
{
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:fast-reroute"
{
deviate not-supported;
@@ -474,14 +487,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" {
@@ -529,4 +534,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.";
}
}
}
+78 -21
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>
@@ -459,6 +487,35 @@
show-legacy -p ip route |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>
+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))