mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-23 01:23:00 +02:00
Merge pull request #1254 from kernelkit/ospf-fixes
Improvements to OSPF and BFD operational data and CLI commands
This commit is contained in:
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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"):
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user