Merge pull request #1306 from kernelkit/rip

This commit is contained in:
Joachim Wiberg
2025-12-06 20:44:58 +01:00
committed by GitHub
56 changed files with 4135 additions and 115 deletions
+7
View File
@@ -8,10 +8,17 @@ All notable changes to the project are documented in this file.
### Changes
- Add RIPv2 routing support, issue #582
- Add support for configurable OSPF debug logging, issue #1281. Debug options
can now be enabled per category (bfd, packet, ism, nsm, default-information,
nssa). All debug options are disabled by default to prevent log flooding in
production environments. See the documentation for usage examples
- Add support for "routing interfaces", issue #647. Lists interfaces with IP
forwarding. Inspect from CLI using `show interface`, look for `⇅` flag
### Fixes
N/A
[v25.11.0][] - 2025-12-02
-------------------------
+116
View File
@@ -1339,6 +1339,7 @@ Currently supported YANG models:
| ietf-ipv4-unicast-routing | Static IPv4 unicast routing |
| ietf-ipv6-unicast-routing | Static IPv6 unicast routing |
| ietf-ospf | OSPF routing |
| ietf-rip | RIP routing |
| infix-routing | Infix deviations and extensions |
The base model, ietf-routing, is where all the other models hook in. It
@@ -1545,6 +1546,121 @@ all options back to `false`:
admin@example:/>
### RIP Routing
The system supports RIP dynamic routing for IPv4, i.e., RIPv2. To enable
RIP and set active interfaces:
admin@example:/config/> edit routing control-plane-protocol ripv2 name default rip
admin@example:/config/routing/…/rip/> set interfaces interface e0
admin@example:/config/routing/…/rip/> set interfaces interface e1
admin@example:/config/routing/…/rip/> leave
admin@example:/>
> [!TIP]
> Remember to enable [IPv4 forwarding](#ipv4-forwarding) for all the
> interfaces you want to route between.
#### RIP interface settings
By default, interfaces send and receive RIPv2 packets. To control the
RIP version per interface:
admin@example:/config/routing/…/rip/> edit interfaces interface e0
admin@example:/config/routing/…/rip/interfaces/interface/e0/> set send-version 1
admin@example:/config/routing/…/rip/interfaces/interface/e0/> set receive-version 1-2
admin@example:/config/routing/…/rip/interfaces/interface/e0/> leave
admin@example:/>
Valid version values are `1`, `2`, or `1-2` (both versions).
To configure a passive interface (advertise network but don't send/receive
RIP updates):
admin@example:/config/routing/…/rip/> edit interfaces interface e0
admin@example:/config/routing/…/rip/interfaces/interface/e0/> set passive
admin@example:/config/routing/…/rip/interfaces/interface/e0/> leave
admin@example:/>
#### RIP global settings
RIP supports redistribution of connected and static routes:
admin@example:/config/routing/…/rip/> set redistribute connected
admin@example:/config/routing/…/rip/> set redistribute static
admin@example:/config/routing/…/rip/> leave
admin@example:/>
#### Debug RIPv2
The CLI provides various RIP status commands:
admin@example:/> show ip rip
Default version control: send version 2, receive version 2
Interface Send Recv Key-chain
e0 2 2
e1 2 2
Routing for Networks:
e0
e1
Routing Information Sources:
Gateway BadPackets BadRoutes Distance Last Update
10.0.1.2 0 0 120 00:00:16
Distance: (default is 120)
admin@example:/> show ip rip neighbor
ADDRESS BAD-PACKETS BAD-ROUTES
10.0.1.2 0 0
admin@example:/>
For more detailed troubleshooting, RIP debug logging can be enabled to
capture specific protocol events. Debug messages are written to the
routing log file (`/var/log/routing`).
> [!CAUTION]
> Debug logging significantly increases log output and may impact
> performance. Only enable debug categories needed for troubleshooting,
> and disable them when done.
To enable specific RIP debug categories:
admin@example:/> configure
admin@example:/config/> edit routing control-plane-protocol ripv2 name default rip debug
admin@example:/config/routing/…/rip/debug/> set events true
admin@example:/config/routing/…/rip/debug/> set packet true
admin@example:/config/routing/…/rip/debug/> leave
admin@example:/>
Available debug categories include:
- `events`: RIP events (sending/receiving packets, timers, interface changes)
- `packet`: Detailed packet debugging (packet dumps with origin and port)
- `kernel`: Kernel routing table updates (route add/delete, interface updates)
All debug options are disabled by default. Refer to the `infix-routing`
YANG model for the complete list of available debug options.
To view current debug settings:
admin@example:/> show running-config routing control-plane-protocol
To disable all debug logging, simply delete the debug settings or set
all options back to `false`:
admin@example:/> configure
admin@example:/config/> delete routing control-plane-protocol ripv2 name default rip debug
admin@example:/config/> leave
admin@example:/>
### View routing table
The routing table can be inspected from the operational datastore, XPath
@@ -0,0 +1,2 @@
# --log-level debug
RIPD_ARGS="-A 127.0.0.1 -u frr -g frr -f /etc/frr/ripd.conf --log syslog"
@@ -1 +1,2 @@
service [2345] log:null <pid/zebra> ripd -A 127.0.0.1 -u frr -g frr -- RIP daemon
service <pid/zebra> env:-/etc/default/ripd \
[2345] ripd $RIPD_ARGS -- RIP daemon
+247 -2
View File
@@ -14,6 +14,9 @@
#define OSPFD_CONF "/etc/frr/ospfd.conf"
#define OSPFD_CONF_NEXT OSPFD_CONF "+"
#define OSPFD_CONF_PREV OSPFD_CONF "-"
#define RIPD_CONF "/etc/frr/ripd.conf"
#define RIPD_CONF_NEXT RIPD_CONF "+"
#define RIPD_CONF_PREV RIPD_CONF "-"
#define BFDD_CONF "/etc/frr/bfd_enabled" /* Just signal that bfd should be enabled*/
#define BFDD_CONF_NEXT BFDD_CONF "+"
@@ -26,6 +29,219 @@ no log unique-id\n\
log syslog informational\n\
log facility local2\n"
int parse_rip_redistribute(sr_session_ctx_t *session, struct lyd_node *redistributes, FILE *fp)
{
struct lyd_node *tmp;
LY_LIST_FOR(lyd_child(redistributes), tmp) {
const char *protocol = lydx_get_cattr(tmp, "protocol");
fprintf(fp, " redistribute %s\n", protocol);
}
return 0;
}
int parse_rip_interfaces(sr_session_ctx_t *session, struct lyd_node *interfaces, FILE *fp)
{
struct lyd_node *interface, *neighbors_node, *neighbor;
int num_interfaces = 0;
/* First pass: network and passive-interface statements (inside router rip block) */
LY_LIST_FOR(lyd_child(interfaces), interface) {
const char *name;
int passive;
name = lydx_get_cattr(interface, "interface");
if (!name)
continue;
passive = lydx_get_bool(interface, "passive");
/* Enable RIP on the interface by adding it to the network statement */
fprintf(fp, " network %s\n", name);
if (passive)
fprintf(fp, " passive-interface %s\n", name);
num_interfaces++;
}
/* Handle explicit neighbors (inside router rip block) */
LY_LIST_FOR(lyd_child(interfaces), interface) {
neighbors_node = lydx_get_child(interface, "neighbors");
if (neighbors_node) {
LY_LIST_FOR(lyd_child(neighbors_node), neighbor) {
const char *address = lydx_get_cattr(neighbor, "address");
if (address)
fprintf(fp, " neighbor %s\n", address);
}
}
}
/* Second pass: interface-specific settings (outside router rip block) */
LY_LIST_FOR(lyd_child(interfaces), interface) {
const char *name, *split_horizon, *cost, *send_version, *recv_version;
name = lydx_get_cattr(interface, "interface");
if (!name)
continue;
split_horizon = lydx_get_cattr(interface, "split-horizon");
cost = lydx_get_cattr(interface, "cost");
send_version = lydx_get_cattr(interface, "send-version");
recv_version = lydx_get_cattr(interface, "receive-version");
/* Only create interface block if there are per-interface settings */
if (split_horizon || cost || send_version || recv_version) {
fprintf(fp, "interface %s\n", name);
if (split_horizon) {
if (!strcmp(split_horizon, "poison-reverse"))
fputs(" ip rip split-horizon poisoned-reverse\n", fp);
else if (!strcmp(split_horizon, "disabled"))
fputs(" no ip rip split-horizon\n", fp);
/* "simple" is default, no need to configure */
}
/* Configure send version */
if (send_version) {
if (!strcmp(send_version, "1"))
fputs(" ip rip send version 1\n", fp);
else if (!strcmp(send_version, "1-2"))
fputs(" ip rip send version 1 2\n", fp);
/* "2" is default in augmentation, explicit config if needed */
else if (!strcmp(send_version, "2"))
fputs(" ip rip send version 2\n", fp);
}
/* Configure receive version */
if (recv_version) {
if (!strcmp(recv_version, "1"))
fputs(" ip rip receive version 1\n", fp);
else if (!strcmp(recv_version, "1-2"))
fputs(" ip rip receive version 1 2\n", fp);
/* "2" is default in augmentation, explicit config if needed */
else if (!strcmp(recv_version, "2"))
fputs(" ip rip receive version 2\n", fp);
}
if (cost) {
/* FRR uses offset-list for per-interface cost adjustment */
/* Note: offset-list is configured globally, not per-interface */
/* This is just a placeholder - actual implementation would need
access lists and global offset-list configuration */
}
}
}
return num_interfaces;
}
int parse_rip_timers(sr_session_ctx_t *session, struct lyd_node *timers, FILE *fp)
{
const char *update, *invalid, *holddown, *flush;
if (!timers)
return 0;
update = lydx_get_cattr(timers, "update-interval");
invalid = lydx_get_cattr(timers, "invalid-interval");
holddown = lydx_get_cattr(timers, "holddown-interval");
flush = lydx_get_cattr(timers, "flush-interval");
/* FRR timers basic: UPDATE TIMEOUT GARBAGE
* TIMEOUT = invalid-interval (when route becomes invalid)
* GARBAGE = flush-interval (when route is flushed)
* Note: holddown-interval is used between invalid and flush
*/
DEBUG("Ignoring 'holddown interval %s' for now", holddown);
if (update || invalid || flush) {
fprintf(fp, " timers basic %s %s %s\n",
update ? update : "30",
invalid ? invalid : "180",
flush ? flush : "240");
}
return 0;
}
int parse_rip(sr_session_ctx_t *session, struct lyd_node *rip)
{
struct lyd_node *interfaces, *timers, *default_route, *debug;
const char *default_metric, *distance;
int num_interfaces = 0;
FILE *fp;
fp = fopen(RIPD_CONF_NEXT, "w");
if (!fp) {
ERROR("Failed to open %s", RIPD_CONF_NEXT);
return SR_ERR_INTERNAL;
}
fputs(FRR_STATIC_CONFIG, fp);
/* Handle RIP debug configuration */
debug = lydx_get_child(rip, "debug");
if (debug) {
int any_debug = 0;
if (lydx_get_bool(debug, "events")) {
fputs("debug rip events\n", fp);
any_debug = 1;
}
if (lydx_get_bool(debug, "packet")) {
fputs("debug rip packet\n", fp);
any_debug = 1;
}
if (lydx_get_bool(debug, "kernel")) {
fputs("debug rip zebra\n", fp);
any_debug = 1;
}
if (any_debug)
fputs("!\n", fp);
}
fputs("router rip\n", fp);
fputs(" version 2\n", fp);
/* Global RIP parameters */
default_metric = lydx_get_cattr(rip, "default-metric");
if (default_metric)
fprintf(fp, " default-metric %s\n", default_metric);
distance = lydx_get_cattr(rip, "distance");
if (distance)
fprintf(fp, " distance %s\n", distance);
/* Timers */
timers = lydx_get_child(rip, "timers");
parse_rip_timers(session, timers, fp);
/* Default route origination */
default_route = lydx_get_child(rip, "originate-default-route");
if (default_route && lydx_get_bool(default_route, "enabled"))
fputs(" default-information originate\n", fp);
/* Redistribution */
parse_rip_redistribute(session, lydx_get_child(rip, "redistribute"), fp);
/* Interfaces - must be done after router rip block and before interface blocks */
interfaces = lydx_get_child(rip, "interfaces");
if (interfaces)
num_interfaces = parse_rip_interfaces(session, interfaces, fp);
fclose(fp);
if (!num_interfaces) {
(void)remove(RIPD_CONF_NEXT);
return 0;
}
return 0;
}
int parse_ospf_interfaces(sr_session_ctx_t *session, struct lyd_node *areas, FILE *fp)
{
struct lyd_node *interface, *interfaces, *area;
@@ -274,9 +490,9 @@ static int parse_static_routes(sr_session_ctx_t *session, struct lyd_node *paren
int routing_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd)
{
int staticd_enabled = 0, ospfd_enabled = 0, bfdd_enabled = 0;
int staticd_enabled = 0, ospfd_enabled = 0, ripd_enabled = 0, bfdd_enabled = 0;
struct lyd_node *cplane, *cplanes;
bool ospfd_running, bfdd_running;
bool ospfd_running, ripd_running, bfdd_running;
bool restart_zebra = false;
int rc = SR_ERR_OK;
FILE *fp;
@@ -303,8 +519,10 @@ int routing_change(sr_session_ctx_t *session, struct lyd_node *config, struct ly
/* Check if passed validation in previous event */
staticd_enabled = fexist(STATICD_CONF_NEXT);
ospfd_enabled = fexist(OSPFD_CONF_NEXT);
ripd_enabled = fexist(RIPD_CONF_NEXT);
bfdd_enabled = fexist(BFDD_CONF_NEXT);
ospfd_running = !systemf("initctl -bfq status ospfd");
ripd_running = !systemf("initctl -bfq status ripd");
bfdd_running = !systemf("initctl -bfq status bfdd");
if (bfdd_running && !bfdd_enabled) {
@@ -327,6 +545,16 @@ int routing_change(sr_session_ctx_t *session, struct lyd_node *config, struct ly
(void)remove(OSPFD_CONF);
}
if (ripd_running && !ripd_enabled) {
if (systemf("initctl -bfq disable ripd")) {
ERROR("Failed to disable RIP routing daemon");
rc = SR_ERR_INTERNAL;
goto err_abandon;
}
/* Remove all generated files */
(void)remove(RIPD_CONF);
}
if (bfdd_enabled) {
(void)rename(BFDD_CONF_NEXT, BFDD_CONF);
if (!bfdd_running) {
@@ -353,6 +581,21 @@ int routing_change(sr_session_ctx_t *session, struct lyd_node *config, struct ly
}
}
if (ripd_enabled) {
(void)remove(RIPD_CONF_PREV);
(void)rename(RIPD_CONF, RIPD_CONF_PREV);
(void)rename(RIPD_CONF_NEXT, RIPD_CONF);
if (!ripd_running) {
if (systemf("initctl -bnq enable ripd")) {
ERROR("Failed to enable RIP routing daemon");
rc = SR_ERR_INTERNAL;
goto err_abandon;
}
} else {
restart_zebra = true;
}
}
if (staticd_enabled) {
(void)remove(STATICD_CONF_PREV);
(void)rename(STATICD_CONF, STATICD_CONF_PREV);
@@ -389,6 +632,8 @@ int routing_change(sr_session_ctx_t *session, struct lyd_node *config, struct ly
staticd_enabled = parse_static_routes(session, lydx_get_child(cplane, "static-routes"), fp);
} else if (!strcmp(type, "infix-routing:ospfv2")) {
parse_ospf(session, lydx_get_child(cplane, "ospf"));
} else if (!strcmp(type, "infix-routing:ripv2")) {
parse_rip(session, lydx_get_child(cplane, "rip"));
}
}
+1
View File
@@ -11,6 +11,7 @@ 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"
"ietf-rip@2020-02-20.yang"
"iana-bfd-types@2021-10-21.yang"
"ietf-bfd-types@2022-09-22.yang"
"ietf-bfd@2022-09-22.yang"
File diff suppressed because it is too large Load Diff
+200 -11
View File
@@ -18,17 +18,18 @@ module infix-routing {
import ietf-bfd-types {
prefix bfd-types;
}
import ietf-rip {
prefix rip;
}
organization "KernelKit";
contact "kernelkit@googlegroups.com";
description "Deviations and augments for ietf-routing and ietf-ospf.";
description "Deviations and augments for ietf-routing, ietf-ospf, and ietf-rip.";
revision 2025-12-02 {
description "Add configurable OSPF debug logging container.
Allows users to enable specific OSPF debug categories
(bfd, packet, ism, nsm, default-information, nssa) for troubleshooting.
All debug options default to disabled to prevent log flooding.";
reference "Issue #1281";
Add RIP (Routing Information Protocol) support.";
reference "RFC 8695";
}
revision 2025-11-12 {
@@ -110,10 +111,6 @@ module infix-routing {
}
/* General routing */
deviation "/rt:routing/rt:interfaces" {
description "Initial limitation";
deviate not-supported;
}
deviation "/rt:routing/rt:router-id" {
description "Set in OSPF";
@@ -133,7 +130,10 @@ module infix-routing {
typedef infix-distribute-protocol {
type enumeration {
enum ospf {
description "Redistribute Ospf";
description "Redistribute OSPF routes";
}
enum rip {
description "Redistribute RIP routes";
}
enum static {
description "Redistribute Static routes";
@@ -150,7 +150,12 @@ module infix-routing {
identity ospfv2 {
base ospf:ospfv2;
base infix-routing-type;
description "OSPv2 (IPv4) routing protocol";
description "OSPFv2 (IPv4) routing protocol";
}
identity ripv2 {
base rip:ripv2;
base infix-routing-type;
description "RIPv2 (IPv4) routing protocol";
}
identity bfdv1 {
base bfd-types:bfdv1;
@@ -590,6 +595,190 @@ module infix-routing {
deviate not-supported;
}
/* RIP */
/* RIP features not supported in Infix/FRR */
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:originate-default-route/rip:route-policy" {
deviate not-supported;
description "Route policies are not supported.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:triggered-update-threshold" {
deviate not-supported;
description "Triggered update threshold is not supported in FRR.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:maximum-paths" {
deviate not-supported;
description "Maximum paths configuration is not supported in FRR.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:output-delay" {
deviate not-supported;
description "Output delay configuration is not supported in FRR.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:distribute-list" {
deviate not-supported;
description "Distribute lists are not supported.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:redistribute" {
deviate not-supported;
description "Use Infix-specific redistribute container instead.";
}
/* RIP Interface features not supported */
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:interfaces/rip:interface/rip:authentication" {
deviate not-supported;
description "RIP authentication is not supported.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:interfaces/rip:interface/rip:bfd" {
deviate not-supported;
description "BFD support for RIP is not available in FRR.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:interfaces/rip:interface/rip:no-listen" {
deviate not-supported;
description "No-listen mode is not supported.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:interfaces/rip:interface/rip:originate-default-route" {
deviate not-supported;
description "Per-interface default route origination is not supported.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:interfaces/rip:interface/rip:summary-address" {
deviate not-supported;
description "Summary addresses are not supported.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:interfaces/rip:interface/rip:timers" {
deviate not-supported;
description "Per-interface timers are not supported in FRR.";
}
/* RIP Operational state not supported */
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:interfaces/rip:interface/rip:statistics" {
deviate not-supported;
description "Per-interface statistics are not collected.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:next-triggered-update" {
deviate not-supported;
description "Next triggered update information is not available.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:num-of-routes" {
deviate not-supported;
description "Number of routes is not tracked separately.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:ipv6" {
deviate not-supported;
description "RIPng (IPv6) is not currently supported in Infix.";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip/rip:statistics" {
deviate not-supported;
description "Global statistics are not collected.";
}
/* RIP RPC */
deviation "/rip:clear-rip-route" {
deviate not-supported;
description "Clear RIP route RPC is not supported.";
}
/* RIP Augmentations */
augment "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip" {
description "Infix-specific redistribute container for RIP.
The standard ietf-rip redistribute container has complex protocol-specific
lists (bgp, isis, ospfv2, etc.), but Infix uses a simpler model similar to OSPF.";
container redistribute {
list redistribute {
key "protocol";
description "Redistribute protocols into RIP";
leaf protocol {
type infix-distribute-protocol;
description "Set protocol to redistribute";
}
}
}
}
augment "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rip:rip" {
description "Add support for configurable RIP debug logging.
Allows users to enable specific RIP debug categories for troubleshooting.
All debug options default to disabled to prevent log flooding in
production environments.";
container debug {
description "RIP debug logging configuration";
leaf events {
type boolean;
default false;
description "Enable RIP events debug messages (sending/receiving packets, timers, interface changes)";
}
leaf packet {
type boolean;
default false;
description "Enable detailed RIP packet debug messages (packet dumps with origin and port)";
}
leaf kernel {
type boolean;
default false;
description "Enable kernel routing table debug messages (route add/delete, interface updates)";
}
}
}
/* Add send/receive version control per RIP interface
* The standard ietf-rip model doesn't include version control,
* but FRR supports this and it's useful for controlling RIP protocol versions
* on a per-interface basis (e.g., for NBMA links or mixed networks).
*/
augment "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/"
+ "rip:rip/rip:interfaces/rip:interface" {
when "derived-from-or-self(../../../rt:type, 'infix-routing:ripv2')" {
description
"This augmentation is only valid for RIPv2.";
}
description
"Infix extension to add RIP version control per interface.";
leaf send-version {
type enumeration {
enum "1" {
description "Send RIPv1 packets";
}
enum "2" {
description "Send RIPv2 packets";
}
enum "1-2" {
description "Send both RIPv1 and RIPv2 packets";
}
}
description
"RIP version to send on this interface. If not configured, FRR defaults to version 2.";
}
leaf receive-version {
type enumeration {
enum "1" {
description "Receive RIPv1 packets";
}
enum "2" {
description "Receive RIPv2 packets";
}
enum "1-2" {
description "Receive both RIPv1 and RIPv2 packets";
}
}
description
"RIP version to receive on this interface. If not configured, FRR defaults to version 2.";
}
}
/*
* Augmentations
*/
+29
View File
@@ -528,6 +528,35 @@
</COMMAND>
</SWITCH>
</COMMAND>
<COMMAND name="rip" help="Show RIP status">
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show rip |pager
</ACTION>
<SWITCH name="optional" min="0">
<COMMAND name="route" help="Show RIP routing table">
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show rip route |pager
</ACTION>
</COMMAND>
<COMMAND name="interface" help="Show RIP 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 rip interface "$KLISH_PARAM_ifname" |pager
</ACTION>
</COMMAND>
<COMMAND name="neighbor" help="Show RIP neighbors">
<ACTION sym="script" in="tty" out="tty" interrupt="true">
show rip neighbor |pager
</ACTION>
</COMMAND>
</SWITCH>
</COMMAND>
</SWITCH>
</COMMAND>
+50 -2
View File
@@ -95,6 +95,12 @@ def interface(args: List[str]) -> None:
print("No interface data retrieved.")
return
# Also fetch routing interface list for forwarding indication
routing_data = run_sysrepocfg("/ietf-routing:routing")
if routing_data:
# Merge routing data into the main data structure
data.update(routing_data)
if RAW_OUTPUT:
print(json.dumps(data, indent=2))
return
@@ -295,6 +301,47 @@ def ospf(args: List[str]) -> None:
else:
print(f"Unknown OSPF subcommand: {subcommand}")
def rip(args: List[str]) -> None:
"""Handle show rip [subcommand] [ifname]
Subcommands:
(none) - General RIP instance information
route - RIP routing table
interface - RIP interface details (optionally for specific interface)
neighbor - RIP neighbor information
Optional:
ifname - Interface name (for interface subcommand)
"""
# Fetch operational data from sysrepocfg
data = run_sysrepocfg("/ietf-routing:routing/control-plane-protocols/control-plane-protocol")
if not data:
data = {}
if RAW_OUTPUT:
print(json.dumps(data, indent=2))
return
# Parse arguments: subcommand, optional interface name
subcommand = args[0] if len(args) > 0 and args[0] else ""
ifname = args[1] if len(args) > 1 else None
# Add metadata to data for formatters
if ifname:
data['_ifname'] = ifname
# Route to appropriate formatter
if subcommand == "":
cli_pretty(data, "show-rip")
elif subcommand == "route" or subcommand == "routes":
cli_pretty(data, "show-rip-routes")
elif subcommand == "interface" or subcommand == "interfaces":
cli_pretty(data, "show-rip-interfaces")
elif subcommand == "neighbor" or subcommand == "neighbors":
cli_pretty(data, "show-rip-neighbors")
else:
print(f"Unknown RIP subcommand: {subcommand}")
def routes(args: List[str]):
ip_version = args[0] if args and args[0] in ["ipv4", "ipv6"] else "ipv4"
@@ -445,9 +492,10 @@ def execute_command(command: str, args: List[str]):
'lldp': lldp,
'ntp': ntp,
'ospf': ospf,
'rip': rip,
'routes': routes,
'services' : services,
'software' : software,
'services': services,
'software': software,
'stp': stp,
'system': system,
'wifi': wifi
+208 -4
View File
@@ -98,6 +98,7 @@ def compress_interface_list(interfaces):
class Pad:
flags = 2
iface = 16
proto = 11
state = 12
@@ -803,6 +804,9 @@ class DhcpServer:
class Iface:
# Class variable to hold routing-enabled interfaces for the current display session
_routing_ifaces = set()
def __init__(self, data):
self.data = data
self.name = data.get('name', '')
@@ -915,13 +919,16 @@ class Iface:
return self.oper_status
def pr_name(self, pipe=""):
flag = "" if self.name in Iface._routing_ifaces else " "
print(f"{flag:<{Pad.flags}}", end="")
print(f"{pipe}{self.name:<{Pad.iface - len(pipe)}}", end="")
def pr_proto_ipv4(self, pipe=''):
for addr in self.ipv4_addr:
origin = f"({addr['origin']})" if addr.get('origin') else ""
row = f"{pipe:<{Pad.iface}}"
row = f"{'':<{Pad.flags}}"
row += f"{pipe:<{Pad.iface}}"
row += f"{'ipv4':<{Pad.proto}}"
row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']} {origin}"
print(row)
@@ -930,7 +937,8 @@ class Iface:
for addr in self.ipv6_addr:
origin = f"({addr['origin']})" if addr.get('origin') else ""
row = f"{pipe:<{Pad.iface}}"
row = f"{'':<{Pad.flags}}"
row += f"{pipe:<{Pad.iface}}"
row += f"{'ipv6':<{Pad.proto}}"
row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']} {origin}"
print(row)
@@ -938,7 +946,8 @@ class Iface:
def _pr_proto_common(self, name, phys_address, pipe=''):
row = ""
if len(pipe) > 0:
row = f"{pipe:<{Pad.iface}}"
row = f"{'':<{Pad.flags}}"
row += f"{pipe:<{Pad.iface}}"
row += f"{name:<{Pad.proto}}"
dec = Decore.green if self.oper() == "up" else Decore.red
@@ -1184,6 +1193,11 @@ class Iface:
parent.pr_proto_eth()
def pr_container(self):
# Add ⇅ flag for interfaces with IP forwarding enabled
flag = "" if self.name in Iface._routing_ifaces else " "
# Flag column is outside the gray background
print(f"{flag:<{Pad.flags}}", end="")
row = f"{self.name:<{Pad.iface}}"
row += f"{'container':<{Pad.proto}}"
row += f"{'':<{Pad.state}}"
@@ -1204,6 +1218,9 @@ class Iface:
if self.oper():
print(f"{'operational status':<{20}}: {self.oper(detail=True)}")
forwarding = "enabled" if self.name in Iface._routing_ifaces else "disabled"
print(f"{'ip forwarding':<{20}}: {forwarding}")
if self.lower_if:
print(f"{'lower-layer-if':<{20}}: {self.lower_if}")
@@ -1407,13 +1424,21 @@ def print_interface(iface):
def pr_interface_list(json):
hdr = (f"{'INTERFACE':<{Pad.iface}}"
hdr = (f"{'':<{Pad.flags}}"
f"{'INTERFACE':<{Pad.iface}}"
f"{'PROTOCOL':<{Pad.proto}}"
f"{'STATE':<{Pad.state}}"
f"{'DATA':<{Pad.data}}")
print(Decore.invert(hdr))
# Set class variable with routing-enabled interfaces (with IP forwarding)
if "ietf-routing:routing" in json:
routing_data = json["ietf-routing:routing"].get("interfaces", {})
Iface._routing_ifaces = set(routing_data.get("interface", []))
else:
Iface._routing_ifaces = set()
ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"],
key=ifname_sort)
iface = find_iface(ifaces, "lo")
@@ -1472,6 +1497,13 @@ def pr_interface_list(json):
def show_interfaces(json, name):
# Set class variable with routing-enabled interfaces (with IP forwarding)
if "ietf-routing:routing" in json:
routing_data = json["ietf-routing:routing"].get("interfaces", {})
Iface._routing_ifaces = set(routing_data.get("interface", []))
else:
Iface._routing_ifaces = set()
if name:
if not json.get("ietf-interfaces:interfaces"):
print(f"No interface data found for \"{name}\"")
@@ -3260,6 +3292,165 @@ def show_ospf_routes(json_data):
print(f"{'':<52} {next_hop_addr:<16} {outgoing_iface:<12}")
def show_rip(json_data):
"""Show RIP general instance information"""
routing = json_data.get('ietf-routing:routing', {})
protocols = routing.get('control-plane-protocols', {}).get('control-plane-protocol', [])
rip_instance = None
for protocol in protocols:
if 'ietf-rip:rip' in protocol:
rip_instance = protocol
break
if not rip_instance:
print("RIP is not configured or running")
return
rip = rip_instance.get('ietf-rip:rip', {})
# Display RIP configuration
print(" RIP Routing Process")
print()
distance = rip.get('distance', 120)
default_metric = rip.get('default-metric', 1)
num_routes = rip.get('num-of-routes', 0)
print(f" Administrative distance: {distance}")
print(f" Default metric: {default_metric}")
print(f" Number of RIP routes: {num_routes}")
print()
# Display timers if available
timers = rip.get('timers', {})
if timers:
update = timers.get('update-interval', 30)
invalid = timers.get('invalid-interval', 180)
flush = timers.get('flush-interval', 240)
print(" Timers:")
print(f" Update interval: {update} seconds")
print(f" Invalid interval: {invalid} seconds")
print(f" Flush interval: {flush} seconds")
print()
# Display interfaces
interfaces = rip.get('interfaces', {}).get('interface', [])
if interfaces:
print(f" Number of interfaces: {len(interfaces)}")
for iface in interfaces:
iface_name = iface.get('interface', 'unknown')
print(f" {iface_name}")
print()
def show_rip_routes(json_data):
"""Show RIP routing table"""
routing = json_data.get('ietf-routing:routing', {})
protocols = routing.get('control-plane-protocols', {}).get('control-plane-protocol', [])
rip_instance = None
for protocol in protocols:
if 'ietf-rip:rip' in protocol:
rip_instance = protocol
break
if not rip_instance:
print("RIP is not configured or running")
return
rip = rip_instance.get('ietf-rip:rip', {})
routes = rip.get('ipv4', {}).get('routes', {}).get('route', [])
if not routes:
print("No RIP routes")
return
# Header
hdr = f"{'PREFIX':<20} {'METRIC':<8} {'NEXT-HOP':<16} {'INTERFACE':<12}"
print(Decore.invert(hdr))
for route in routes:
prefix = route.get('ipv4-prefix', 'unknown')
metric = route.get('metric', 0)
next_hop = route.get('next-hop', '-')
interface = route.get('interface', '-')
print(f"{prefix:<20} {metric:<8} {next_hop:<16} {interface:<12}")
def show_rip_interfaces(json_data):
"""Show RIP interface information"""
routing = json_data.get('ietf-routing:routing', {})
protocols = routing.get('control-plane-protocols', {}).get('control-plane-protocol', [])
rip_instance = None
for protocol in protocols:
if 'ietf-rip:rip' in protocol:
rip_instance = protocol
break
if not rip_instance:
print("RIP is not configured or running")
return
rip = rip_instance.get('ietf-rip:rip', {})
interfaces = rip.get('interfaces', {}).get('interface', [])
if not interfaces:
print("No RIP interfaces")
return
# Header
hdr = f"{'INTERFACE':<12} {'STATUS':<10} {'SEND':<6} {'RECV':<6} {'SPLIT-HORIZON':<20} {'PASSIVE':<10}"
print(Decore.invert(hdr))
for iface in interfaces:
iface_name = iface.get('interface', 'unknown')
oper_status = iface.get('oper-status', 'down')
send_ver = iface.get('send-version', '-')
recv_ver = iface.get('receive-version', '-')
split_horizon = iface.get('split-horizon', 'simple')
passive = 'Yes' if iface.get('passive', False) else 'No'
print(f"{iface_name:<12} {oper_status:<10} {send_ver:<6} {recv_ver:<6} {split_horizon:<20} {passive:<10}")
def show_rip_neighbors(json_data):
"""Show RIP neighbor information"""
routing = json_data.get('ietf-routing:routing', {})
protocols = routing.get('control-plane-protocols', {}).get('control-plane-protocol', [])
rip_instance = None
for protocol in protocols:
if 'ietf-rip:rip' in protocol:
rip_instance = protocol
break
if not rip_instance:
print("RIP is not configured or running")
return
rip = rip_instance.get('ietf-rip:rip', {})
ipv4 = rip.get('ipv4', {})
neighbors = ipv4.get('neighbors', {}).get('neighbor', [])
if not neighbors:
print("No RIP neighbors")
return
# Header
hdr = f"{'ADDRESS':<16} {'BAD-PACKETS':<14} {'BAD-ROUTES':<12}"
print(Decore.invert(hdr))
for neighbor in neighbors:
address = neighbor.get('ipv4-address', 'unknown')
bad_packets = neighbor.get('bad-packets-rcvd', 0)
bad_routes = neighbor.get('bad-routes-rcvd', 0)
print(f"{address:<16} {bad_packets:<14} {bad_routes:<12}")
def show_bfd_status(json_data):
"""Show BFD status summary"""
routing = json_data.get('ietf-routing:routing', {})
@@ -3549,6 +3740,11 @@ def main():
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-rip', help='Show RIP instance information')
subparsers.add_parser('show-rip-routes', help='Show RIP routing table')
subparsers.add_parser('show-rip-interfaces', help='Show RIP interfaces')
subparsers.add_parser('show-rip-neighbors', help='Show RIP neighbors')
subparsers.add_parser('show-routing-table', help='Show the routing table') \
.add_argument('-i', '--ip', required=True, help='IPv4 or IPv6 address')
@@ -3602,6 +3798,14 @@ def main():
show_ospf_neighbor(json_data)
elif args.command == "show-ospf-routes":
show_ospf_routes(json_data)
elif args.command == "show-rip":
show_rip(json_data)
elif args.command == "show-rip-routes":
show_rip_routes(json_data)
elif args.command == "show-rip-interfaces":
show_rip_interfaces(json_data)
elif args.command == "show-rip-neighbors":
show_rip_neighbors(json_data)
elif args.command == "show-routing-table":
show_routing_table(json_data, args.ip)
elif args.command == "show-software":
+3 -1
View File
@@ -6,7 +6,8 @@ license = "MIT"
packages = [
{ include = "yanger" },
{ include = "cli_pretty" },
{ include = "ospf_status" }
{ include = "ospf_status" },
{ include = "rip_status" }
]
authors = [
"KernelKit developers"
@@ -21,3 +22,4 @@ build-backend = "poetry.core.masonry.api"
yanger = "yanger.__main__:main"
cli-pretty = "cli_pretty:main"
ospf-status = "ospf_status:main"
rip-status = "rip_status.rip_status:main"
+1
View File
@@ -0,0 +1 @@
# SPDX-License-Identifier: BSD-3-Clause
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/python3
# This script is used to transform the output from the show ip rip commands
# to match the ietf-rip YANG model structure.
#
# This makes the parsing for the operational parts of YANG model more easy
#
import sys
import json
import subprocess
def run_json_cmd(cmd, default=None, check=True):
"""Run a command (array of args) with JSON output and return the JSON"""
try:
result = subprocess.run(cmd, check=check, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, text=True)
output = result.stdout
data = json.loads(output)
except (subprocess.CalledProcessError, json.JSONDecodeError):
if default is not None:
return default
raise
return data
def main():
"""
Collects RIP operational data from FRR vtysh and transforms it
into a structure that matches the ietf-rip YANG model.
"""
rip_routes_cmd = ['sudo', 'vtysh', '-c', "show ip rip json"]
rip_status_cmd = ['sudo', 'vtysh', '-c', "show ip rip status json"]
try:
routes = run_json_cmd(rip_routes_cmd, default={})
status = run_json_cmd(rip_status_cmd, default={})
except (subprocess.CalledProcessError, json.JSONDecodeError):
return {}
# Build the structure matching ietf-rip YANG model
result = {
"routes": routes.get("routes", {}),
"status": status
}
return result
if __name__ == "__main__":
try:
data = main()
print(json.dumps(data, indent=2))
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
+3
View File
@@ -63,6 +63,9 @@ def main():
elif args.model == 'ietf-ospf':
from . import ietf_ospf
yang_data = ietf_ospf.operational()
elif args.model == 'ietf-rip':
from . import ietf_rip
yang_data = ietf_rip.operational()
elif args.model == 'ietf-hardware':
from . import ietf_hardware
yang_data = ietf_hardware.operational()
+270
View File
@@ -0,0 +1,270 @@
import re
from .host import HOST
def parse_rip_status():
"""Parse 'show ip rip status' text output to extract operational state
Returns dict with keys: update-interval, invalid-interval, flush-interval,
default-metric, distance, interfaces (list), neighbors (list)
"""
try:
# HOST.run expects tuple, returns text string directly
text = HOST.run(tuple(['vtysh', '-c', 'show ip rip status']), default="")
if not text:
return {}
except Exception as e:
return {}
status = {}
# Parse: "Sending updates every 30 seconds"
match = re.search(r'Sending updates every (\d+) seconds', text)
if match:
status['update-interval'] = int(match.group(1))
# Parse: "Timeout after 180 seconds"
match = re.search(r'Timeout after (\d+) seconds', text)
if match:
status['invalid-interval'] = int(match.group(1))
# Parse: "garbage collect after 240 seconds"
match = re.search(r'garbage collect after (\d+) seconds', text)
if match:
status['flush-interval'] = int(match.group(1))
# Parse: "Default redistribution metric is 1"
match = re.search(r'Default redistribution metric is (\d+)', text)
if match:
status['default-metric'] = int(match.group(1))
# Parse: "Distance: (default is 120)"
match = re.search(r'Distance: \(default is (\d+)\)', text)
if match:
status['distance'] = int(match.group(1))
# Parse interface table:
# Interface Send Recv Key-chain
# e5 2 2
interfaces = []
in_interface_section = False
for line in text.split('\n'):
line = line.strip()
# Detect start of interface section
if 'Interface' in line and 'Send' in line and 'Recv' in line:
in_interface_section = True
continue
# Stop at next section
if in_interface_section and (line.startswith('Routing for Networks:') or
line.startswith('Routing Information Sources:')):
break
# Parse interface lines
if in_interface_section and line:
# Format: "e5 2 2 "
parts = line.split()
if len(parts) >= 3 and not line.startswith('Interface'):
iface_name = parts[0]
send_version = parts[1]
recv_version = parts[2]
interfaces.append({
'name': iface_name,
'send-version': int(send_version),
'recv-version': int(recv_version)
})
if interfaces:
status['interfaces'] = interfaces
# Parse Routing Information Sources table:
# Gateway BadPackets BadRoutes Distance Last Update
# 192.168.50.2 0 0 120 00:00:09
neighbors = []
in_neighbor_section = False
for line in text.split('\n'):
line = line.strip()
# Detect start of Routing Information Sources section
if line.startswith('Routing Information Sources:'):
in_neighbor_section = True
continue
# Skip the header line
if in_neighbor_section and 'Gateway' in line and 'BadPackets' in line:
continue
# Stop at next section (Distance line or empty lines after table)
if in_neighbor_section and (line.startswith('Distance:') or
(not line and neighbors)):
break
# Parse neighbor lines
if in_neighbor_section and line:
# Format: "192.168.50.2 0 0 120 00:00:09"
parts = line.split()
if len(parts) >= 5:
try:
gateway = parts[0]
bad_packets = int(parts[1])
bad_routes = int(parts[2])
distance = int(parts[3])
last_update = parts[4] # Format: HH:MM:SS
neighbors.append({
'address': gateway,
'bad-packets': bad_packets,
'bad-routes': bad_routes,
'distance': distance,
'last-update': last_update
})
except (ValueError, IndexError):
# Skip lines that don't parse correctly
continue
if neighbors:
status['neighbors'] = neighbors
return status
def add_rip(control_protocols):
"""Populate RIP operational data
Note: FRR's RIP implementation provides JSON for routing table but not for
status commands, so we combine JSON route data with text parsing for status.
"""
# Get operational status from text parsing
status = parse_rip_status()
# Check if RIP is running - if we can't get status, it's probably not running
if not status:
return
control_protocol = {}
control_protocol["type"] = "infix-routing:ripv2"
control_protocol["name"] = "default"
control_protocol["ietf-rip:rip"] = {}
rip = control_protocol["ietf-rip:rip"]
# Add global operational state
if status.get('distance'):
rip['distance'] = status['distance']
if status.get('default-metric'):
rip['default-metric'] = status['default-metric']
# Add timers if available
if any(k in status for k in ['update-interval', 'invalid-interval', 'flush-interval']):
rip['timers'] = {}
if status.get('update-interval'):
rip['timers']['update-interval'] = status['update-interval']
if status.get('invalid-interval'):
rip['timers']['invalid-interval'] = status['invalid-interval']
if status.get('flush-interval'):
rip['timers']['flush-interval'] = status['flush-interval']
# Add interfaces if available
if status.get('interfaces'):
rip['interfaces'] = {'interface': []}
for iface in status['interfaces']:
iface_data = {
'interface': iface['name'],
'oper-status': 'up' # If it's in the list, it's operational
}
# Map FRR version numbers to YANG enum values
# FRR shows: 1=v1, 2=v2, and we assume if both are enabled it would show differently
send_ver = iface.get('send-version')
if send_ver == 1:
iface_data['send-version'] = '1'
elif send_ver == 2:
iface_data['send-version'] = '2'
# Note: FRR might show this differently for mixed mode
recv_ver = iface.get('recv-version')
if recv_ver == 1:
iface_data['receive-version'] = '1'
elif recv_ver == 2:
iface_data['receive-version'] = '2'
rip['interfaces']['interface'].append(iface_data)
# Get RIP-learned routes from routing table (JSON)
# This shows routes learned via RIP (R(n) entries), not redistributed routes
route_data = HOST.run_json(['vtysh', '-c', 'show ip route rip json'], default={})
routes = []
for prefix, entries in route_data.items():
if not entries or '/' not in prefix:
continue
# Use first entry (RIP doesn't typically have ECMP)
entry = entries[0] if isinstance(entries, list) else entries
route = {
"ipv4-prefix": prefix,
"metric": entry.get("metric", 0),
"route-type": "rip"
}
# Get next hop information
nexthops = entry.get("nexthops", [])
if nexthops:
first_hop = nexthops[0]
if first_hop.get("ip"):
route["next-hop"] = first_hop["ip"]
if first_hop.get("interfaceName"):
route["interface"] = first_hop["interfaceName"]
routes.append(route)
# Add neighbors to operational data
neighbors_list = []
if status.get('neighbors'):
for neighbor in status['neighbors']:
neighbor_data = {
'ipv4-address': neighbor['address'],
'bad-packets-rcvd': neighbor['bad-packets'],
'bad-routes-rcvd': neighbor['bad-routes']
}
# Note: IETF YANG expects last-update as yang:date-and-time
# but FRR gives us a relative time like "00:00:09"
# We'll skip last-update for now or convert it if needed
neighbors_list.append(neighbor_data)
# Add routes and neighbors to operational data
if routes or neighbors_list:
if "ipv4" not in rip:
rip["ipv4"] = {}
if routes:
rip["ipv4"]["routes"] = {
"route": routes
}
# Add route count
rip["num-of-routes"] = len(routes)
if neighbors_list:
rip["ipv4"]["neighbors"] = {
"neighbor": neighbors_list
}
# Add the control-protocol
if "ietf-routing:control-plane-protocol" not in control_protocols:
control_protocols["ietf-routing:control-plane-protocol"] = []
control_protocols["ietf-routing:control-plane-protocol"].append(control_protocol)
def operational():
"""Return RIP operational data in YANG format"""
out = {
"ietf-routing:routing": {
"control-plane-protocols": {}
}
}
add_rip(out['ietf-routing:routing']['control-plane-protocols'])
return out
+32 -1
View File
@@ -51,6 +51,7 @@ def add_protocol(routes, proto):
'static': 'static',
'ospf': 'ietf-ospf:ospfv2',
'ospf6': 'ietf-ospf:ospfv3',
'rip': 'ietf-rip:rip',
}
out = {}
@@ -75,9 +76,11 @@ def add_protocol(routes, proto):
new['source-protocol'] = pmap.get(frr, 'infix-routing:kernel')
new['route-preference'] = route.get('distance', 0)
# Metric only available in the model for OSPF routes
# Metric only available in the model for OSPF and RIP routes
if 'ospf' in frr:
new['ietf-ospf:metric'] = route.get('metric', 0)
elif 'rip' in frr:
new['ietf-rip:metric'] = route.get('metric', 0)
# See https://datatracker.ietf.org/doc/html/rfc7951#section-6.9
# for details on how presence leaves are encoded in JSON: [null]
@@ -121,9 +124,37 @@ def add_protocol(routes, proto):
insert(routes, 'routes', out)
def get_routing_interfaces():
"""Get list of interfaces with IPv4 forwarding enabled"""
import json
# Get all interfaces
links_json = HOST.run(tuple(['ip', '-j', 'link', 'show']), default="[]")
links = json.loads(links_json)
routing_ifaces = []
for link in links:
ifname = link.get('ifname')
if not ifname:
continue
# Check if IPv4 forwarding is enabled
# Note: We only check IPv4 forwarding. IPv6 forwarding behaves differently
# and will be handled separately when Linux 6.17+ force_forwarding is available.
ipv4_fwd = HOST.run(tuple(['sysctl', '-n', f'net.ipv4.conf.{ifname}.forwarding']), default="0").strip()
if ipv4_fwd == "1":
routing_ifaces.append(ifname)
return routing_ifaces
def operational():
out = {
"ietf-routing:routing": {
"interfaces": {
"interface": get_routing_interfaces()
},
"ribs": {
"rib": [{
"name": "ipv4",
+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_RIP XPATH_ROUTING_BASE "/rip"
#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"
@@ -260,6 +261,42 @@ static int sr_ospf_cb(sr_session_ctx_t *session, uint32_t, const char *,
return err;
}
static int sr_rip_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-rip",
NULL
};
const struct ly_ctx *ctx;
sr_conn_ctx_t *con;
sr_error_t err;
DEBUG("Incoming rip 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 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)
@@ -380,6 +417,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_RIP, sr_rip_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))
+21 -1
View File
@@ -7,9 +7,13 @@ Tests verifying standard routing protocols and configuration:
- OSPF basic neighbor discovery and LSA exchange
- OSPF unnumbered interface configuration
- OSPF multi-area configuration and inter-area routing
- OSPF with BFD (Bidirectional Forwarding Detection)
- OSPF with BFD (Bidirectional Forwarding Detection)
- OSPF default route advertisement and propagation
- OSPF debug logging configuration and verification
- RIP basic neighbor discovery and route exchange
- RIP passive interface configuration
- RIP route redistribution (connected, static, and OSPF)
- RIP multi-hop routing with multiple neighbors
- Route preference handling for OSPF routes
- Route preference handling for DHCP-learned routes
- Administrative distance configuration (route preference 255)
@@ -42,6 +46,22 @@ include::ospf_debug/Readme.adoc[]
<<<
include::rip_basic/Readme.adoc[]
<<<
include::rip_passive_interface/Readme.adoc[]
<<<
include::rip_redistribute/Readme.adoc[]
<<<
include::rip_multihop/Readme.adoc[]
<<<
include::route_pref_ospf/Readme.adoc[]
<<<
+12
View File
@@ -28,3 +28,15 @@
- name: OSPF Debug Logging
case: ospf_debug/test.py
- name: RIP Basic
case: rip_basic/test.py
- name: RIP Passive Interface
case: rip_passive_interface/test.py
- name: RIP Redistribution
case: rip_redistribute/test.py
- name: RIP Multi-hop
case: rip_multihop/test.py
+1
View File
@@ -0,0 +1 @@
test.adoc
+28
View File
@@ -0,0 +1,28 @@
=== RIP Basic
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/rip_basic]
==== Description
Verifies basic RIP functionality by configuring two routers (R1 and R2)
with RIP on their interconnecting link. The test ensures RIP routes are
exchanged between the routers and end-to-end connectivity is achieved.
The test PC uses data1 interface to connect to R1's data port, and data2
interface to connect to R2's data port (which does not have RIP enabled).
This verifies that RIP status information remains accessible when a router
has non-RIP interfaces.
==== Topology
image::topology.svg[RIP Basic topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to target DUTs
. Configure targets
. Wait for RIP routes to be exchanged
. Test connectivity from PC:data1 to R2 loopback via RIP
. Test connectivity from PC:data2 to R1 loopback via RIP
+215
View File
@@ -0,0 +1,215 @@
#!/usr/bin/env python3
"""RIP Basic
Verifies basic RIP functionality by configuring two routers (R1 and R2)
with RIP on their interconnecting link. The test ensures RIP routes are
exchanged between the routers and end-to-end connectivity is achieved.
The test PC uses data1 interface to connect to R1's data port, and data2
interface to connect to R2's data port (which does not have RIP enabled).
This verifies that RIP status information remains accessible when a router
has non-RIP interfaces.
"""
import infamy
import infamy.route as route
from infamy.util import until, parallel
def config_target1(target, data, link):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": data,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.10.1",
"prefix-length": 24
}]}
}, {
"name": link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.50.1",
"prefix-length": 24
}]
}
}, {
"name": "lo",
"enabled": True,
"ipv4": {
"address": [{
"ip": "192.168.100.1",
"prefix-length": 32
}]
}
}]
}
},
"ietf-system": {
"system": {
"hostname": "R1"
}
},
"ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:static",
"name": "default",
"static-routes": {
"ipv4": {
"route": [{
"destination-prefix": "192.168.33.1/32",
"next-hop": {
"special-next-hop": "blackhole"
}
}]
}
}
}, {
"type": "infix-routing:ripv2",
"name": "default",
"rip": {
"timers": {
"update-interval": 5,
"invalid-interval": 15,
"flush-interval": 20
},
"redistribute": {
"redistribute": [{
"protocol": "static"
}, {
"protocol": "connected"
}]
},
"interfaces": {
"interface": [{
"interface": link
}]
}
}
}]
}
}
}
})
def config_target2(target, link, data):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.50.2",
"prefix-length": 24
}]
}
}, {
"name": data,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.60.1",
"prefix-length": 24
}]
}
}, {
"name": "lo",
"enabled": True,
"forwarding": True,
"ipv4": {
"address": [{
"ip": "192.168.200.1",
"prefix-length": 32
}]
}
}]
}
},
"ietf-system": {
"system": {
"hostname": "R2"
}
},
"ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:ripv2",
"name": "default",
"rip": {
"timers": {
"update-interval": 5,
"invalid-interval": 15,
"flush-interval": 20
},
"redistribute": {
"redistribute": [{
"protocol": "connected"
}]
},
"interfaces": {
"interface": [{
"interface": link
}]
}
}
}]
}
}
}
})
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUTs"):
env = infamy.Env()
R1 = env.attach("R1", "mgmt")
R2 = env.attach("R2", "mgmt")
with test.step("Configure targets"):
_, R1data = env.ltop.xlate("R1", "data")
_, R2link = env.ltop.xlate("R2", "link")
_, R1link = env.ltop.xlate("R1", "link")
_, R2data = env.ltop.xlate("R2", "data")
parallel(config_target1(R1, R1data, R1link),
config_target2(R2, R2link, R2data))
with test.step("Wait for RIP routes to be exchanged"):
print("Waiting for RIP routes to propagate...")
# R1 should learn R2's loopback
until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", proto="ietf-rip:rip"), attempts=40)
# R2 should learn R1's loopback
until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", proto="ietf-rip:rip"), attempts=40)
# R2 should learn R1's static route (redistributed)
until(lambda: route.ipv4_route_exist(R2, "192.168.33.1/32", proto="ietf-rip:rip"), attempts=40)
with test.step("Test connectivity from PC:data1 to R2 loopback via RIP"):
_, hport0 = env.ltop.xlate("PC", "data1")
with infamy.IsolatedMacVlan(hport0) as ns0:
ns0.addip("192.168.10.2")
ns0.addroute("192.168.200.1/32", "192.168.10.1")
ns0.must_reach("192.168.200.1")
with test.step("Test connectivity from PC:data2 to R1 loopback via RIP"):
_, hport1 = env.ltop.xlate("PC", "data2")
with infamy.IsolatedMacVlan(hport1) as ns1:
ns1.addip("192.168.60.2")
ns1.addroute("192.168.100.1/32", "192.168.60.1")
ns1.must_reach("192.168.100.1")
test.succeed()
+34
View File
@@ -0,0 +1,34 @@
graph "2x2" {
layout="neato";
overlap="false";
esep="+40";
size=10
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
PC [
label="PC | { <mgmt1> mgmt1 | <data1> data1 | \n\n\n\n\n\n\n\n | <mgmt2> mgmt2 | <data2> data2 }",
pos="20,50!",
requires="controller",
];
R1 [
label="{ <mgmt> mgmt | <data> data | <link> link} | R1 \n 192.168.100.1/32 \n(lo)",
pos="190,85!",
requires="infix",
];
R2 [
label="{ <link> link | <mgmt> mgmt | <data> data } | R2 \n 192.168.200.1/32 \n(lo)",
pos="190,15!",
requires="infix",
];
PC:mgmt1 -- R1:mgmt [requires="mgmt", color="lightgray"]
PC:mgmt2 -- R2:mgmt [requires="mgmt", color="lightgray"]
PC:data1 -- R1:data [color="black", label="192.168.10.0/24", headlabel=".1", taillabel=".2", labeldistance=2, fontcolor="black"]
R1:link -- R2:link [label="192.168.50.0/24", headlabel=".2", taillabel=".1", labeldistance=2, fontcolor="black", color="black"]
PC:data2 -- R2:data [label="192.168.60.0/24", headlabel=".1", taillabel=".2", labeldistance=2, fontcolor="black", color="black"]
}
+89
View File
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: 2x2 Pages: 1 -->
<svg width="574pt" height="249pt"
viewBox="0.00 0.00 573.90 248.51" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 244.51)">
<title>2x2</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-244.51 569.9,-244.51 569.9,4 -4,4"/>
<!-- PC -->
<g id="node1" class="node">
<title>PC</title>
<polygon fill="none" stroke="black" points="0,-6.5 0,-234.5 91,-234.5 91,-6.5 0,-6.5"/>
<text text-anchor="middle" x="16.5" y="-116.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">PC</text>
<polyline fill="none" stroke="black" points="33,-6.5 33,-234.5 "/>
<text text-anchor="middle" x="62" y="-219.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
<polyline fill="none" stroke="black" points="33,-211.5 91,-211.5 "/>
<text text-anchor="middle" x="62" y="-196.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
<polyline fill="none" stroke="black" points="33,-188.5 91,-188.5 "/>
<polyline fill="none" stroke="black" points="33,-52.5 91,-52.5 "/>
<text text-anchor="middle" x="62" y="-37.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
<polyline fill="none" stroke="black" points="33,-29.5 91,-29.5 "/>
<text text-anchor="middle" x="62" y="-14.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
</g>
<!-- R1 -->
<g id="node2" class="node">
<title>R1</title>
<polygon fill="none" stroke="black" points="350.9,-171.01 350.9,-240.01 565.9,-240.01 565.9,-171.01 350.9,-171.01"/>
<text text-anchor="middle" x="375.9" y="-224.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="350.9,-217.01 400.9,-217.01 "/>
<text text-anchor="middle" x="375.9" y="-201.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="350.9,-194.01 400.9,-194.01 "/>
<text text-anchor="middle" x="375.9" y="-178.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
<polyline fill="none" stroke="black" points="400.9,-171.01 400.9,-240.01 "/>
<text text-anchor="middle" x="483.4" y="-216.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1 </text>
<text text-anchor="middle" x="483.4" y="-201.81" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 192.168.100.1/32 </text>
<text text-anchor="middle" x="483.4" y="-186.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
</g>
<!-- PC&#45;&#45;R1 -->
<g id="edge1" class="edge">
<title>PC:mgmt1&#45;&#45;R1:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-223.5C91.5,-223.5 350.4,-228.51 350.4,-228.51"/>
</g>
<!-- PC&#45;&#45;R1 -->
<g id="edge3" class="edge">
<title>PC:data1&#45;&#45;R1:data</title>
<path fill="none" stroke="black" stroke-width="2" d="M91.5,-200.5C91.5,-200.5 350.4,-205.51 350.4,-205.51"/>
<text text-anchor="middle" x="161.95" y="-206.81" font-family="DejaVu Serif, Book" font-size="14.00">192.168.10.0/24</text>
<text text-anchor="middle" x="332.11" y="-209.91" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
<text text-anchor="middle" x="109.79" y="-188.7" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
</g>
<!-- R2 -->
<g id="node3" class="node">
<title>R2</title>
<polygon fill="none" stroke="black" points="350.9,-1 350.9,-70 565.9,-70 565.9,-1 350.9,-1"/>
<text text-anchor="middle" x="375.9" y="-54.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
<polyline fill="none" stroke="black" points="350.9,-47 400.9,-47 "/>
<text text-anchor="middle" x="375.9" y="-31.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="350.9,-24 400.9,-24 "/>
<text text-anchor="middle" x="375.9" y="-8.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="400.9,-1 400.9,-70 "/>
<text text-anchor="middle" x="483.4" y="-46.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2 </text>
<text text-anchor="middle" x="483.4" y="-31.8" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 192.168.200.1/32 </text>
<text text-anchor="middle" x="483.4" y="-16.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
</g>
<!-- PC&#45;&#45;R2 -->
<g id="edge2" class="edge">
<title>PC:mgmt2&#45;&#45;R2:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-40.5C91.5,-40.5 350.4,-35.5 350.4,-35.5"/>
</g>
<!-- PC&#45;&#45;R2 -->
<g id="edge5" class="edge">
<title>PC:data2&#45;&#45;R2:data</title>
<path fill="none" stroke="black" stroke-width="2" d="M91.5,-17.5C91.5,-17.5 350.4,-12.5 350.4,-12.5"/>
<text text-anchor="middle" x="250.45" y="-3.8" font-family="DejaVu Serif, Book" font-size="14.00">192.168.60.0/24</text>
<text text-anchor="middle" x="332.44" y="-17.6" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
<text text-anchor="middle" x="109.46" y="-5" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
</g>
<!-- R1&#45;&#45;R2 -->
<g id="edge4" class="edge">
<title>R1:link&#45;&#45;R2:link</title>
<path fill="none" stroke="black" stroke-width="2" d="M375.4,-170.51C375.4,-170.51 375.4,-70.5 375.4,-70.5"/>
<text text-anchor="middle" x="316.4" y="-124.3" font-family="DejaVu Serif, Book" font-size="14.00">192.168.50.0/24</text>
<text text-anchor="middle" x="383.85" y="-84.92" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
<text text-anchor="middle" x="366.95" y="-148.69" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

+1
View File
@@ -0,0 +1 @@
test.adoc
+314
View File
@@ -0,0 +1,314 @@
#!/usr/bin/env python3
"""RIP Multi-hop
Verifies RIP functionality across multiple hops with three routers in a line
topology (R1 -- R2 -- R3). This test ensures:
- RIP routes propagate through multiple hops
- R2 (middle router) has two RIP neighbors
- End-to-end connectivity works across the RIP network
Topology:
PC:data1 -- R1 -- R2 -- R3 -- PC:data2
"""
import infamy
import infamy.route as route
from infamy.util import until, parallel
def config_r1(target, data, link):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": data,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.10.1",
"prefix-length": 24
}]
}
}, {
"name": link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.50.1",
"prefix-length": 24
}]
}
}, {
"name": "lo",
"enabled": True,
"ipv4": {
"address": [{
"ip": "192.168.11.1",
"prefix-length": 32
}]
}
}]
}
},
"ietf-system": {
"system": {
"hostname": "R1"
}
},
"ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:ripv2",
"name": "default",
"rip": {
"timers": {
"update-interval": 5,
"invalid-interval": 15,
"flush-interval": 20
},
"redistribute": {
"redistribute": [{
"protocol": "connected"
}]
},
"interfaces": {
"interface": [{
"interface": link
}]
}
}
}]
}
}
}
})
def config_r2(target, west, east):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": west,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.50.2",
"prefix-length": 24
}]
}
}, {
"name": east,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.60.1",
"prefix-length": 24
}]
}
}, {
"name": "lo",
"enabled": True,
"ipv4": {
"address": [{
"ip": "192.168.22.1",
"prefix-length": 32
}]
}
}]
}
},
"ietf-system": {
"system": {
"hostname": "R2"
}
},
"ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:ripv2",
"name": "default",
"rip": {
"timers": {
"update-interval": 5,
"invalid-interval": 15,
"flush-interval": 20
},
"redistribute": {
"redistribute": [{
"protocol": "connected"
}]
},
"interfaces": {
"interface": [{
"interface": west
}, {
"interface": east
}]
}
}
}]
}
}
}
})
def config_r3(target, link, data):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.60.2",
"prefix-length": 24
}]
}
}, {
"name": data,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.70.1",
"prefix-length": 24
}]
}
}, {
"name": "lo",
"enabled": True,
"ipv4": {
"address": [{
"ip": "192.168.33.1",
"prefix-length": 32
}]
}
}]
}
},
"ietf-system": {
"system": {
"hostname": "R3"
}
},
"ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:ripv2",
"name": "default",
"rip": {
"timers": {
"update-interval": 5,
"invalid-interval": 15,
"flush-interval": 20
},
"redistribute": {
"redistribute": [{
"protocol": "connected"
}]
},
"interfaces": {
"interface": [{
"interface": link
}]
}
}
}]
}
}
}
})
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUTs"):
env = infamy.Env()
R1 = env.attach("R1", "mgmt")
R2 = env.attach("R2", "mgmt")
R3 = env.attach("R3", "mgmt")
with test.step("Configure routers"):
_, R1data = env.ltop.xlate("R1", "data")
_, R1link = env.ltop.xlate("R1", "link")
_, R2west = env.ltop.xlate("R2", "west")
_, R2east = env.ltop.xlate("R2", "east")
_, R3link = env.ltop.xlate("R3", "link")
_, R3data = env.ltop.xlate("R3", "data")
parallel(config_r1(R1, R1data, R1link),
config_r2(R2, R2west, R2east),
config_r3(R3, R3link, R3data))
with test.step("Wait for RIP routes to be exchanged"):
print("Waiting for RIP routes to propagate...")
# R1 should learn R2's loopback
until(lambda: route.ipv4_route_exist(R1, "192.168.22.1/32", proto="ietf-rip:rip"), attempts=40)
# R1 should learn R3's loopback (via R2)
until(lambda: route.ipv4_route_exist(R1, "192.168.33.1/32", proto="ietf-rip:rip"), attempts=40)
# R2 should learn R1's loopback
until(lambda: route.ipv4_route_exist(R2, "192.168.11.1/32", proto="ietf-rip:rip"), attempts=40)
# R2 should learn R3's loopback
until(lambda: route.ipv4_route_exist(R2, "192.168.33.1/32", proto="ietf-rip:rip"), attempts=40)
# R3 should learn R2's loopback
until(lambda: route.ipv4_route_exist(R3, "192.168.22.1/32", proto="ietf-rip:rip"), attempts=40)
# R3 should learn R1's loopback (via R2)
until(lambda: route.ipv4_route_exist(R3, "192.168.11.1/32", proto="ietf-rip:rip"), attempts=40)
with test.step("Verify R2 has two RIP neighbors"):
print("Checking R2 has two RIP neighbors...")
# R2 should have neighbors: 192.168.50.1 (R1) and 192.168.60.2 (R3)
# Query without predicates to avoid RESTCONF encoding issues
routing_data = R2.get_data("/ietf-routing:routing/control-plane-protocols")
# Navigate to RIP protocol
protocols = routing_data.get("routing", {}).get("control-plane-protocols", {}).get("control-plane-protocol", [])
if not protocols:
raise Exception("No protocols found")
# Find RIP protocol
rip = None
for protocol in protocols:
if protocol.get("type") == "infix-routing:ripv2" and protocol.get("name") == "default":
rip = protocol.get("rip", {})
break
if not rip:
raise Exception("RIP protocol not found in control-plane-protocols")
ipv4_data = rip.get("ipv4", {})
neighbors_data = ipv4_data.get("neighbors", {})
neighbor_list = neighbors_data.get("neighbor", [])
assert len(neighbor_list) == 2, f"Expected 2 neighbors, found {len(neighbor_list)}"
neighbor_ips = [n.get("ipv4-address") for n in neighbor_list]
assert "192.168.50.1" in neighbor_ips, "R1 not in neighbor list"
assert "192.168.60.2" in neighbor_ips, "R3 not in neighbor list"
print(f"R2 has correct neighbors: {neighbor_ips}")
with test.step("Test end-to-end connectivity PC:data1 to R3 loopback"):
_, hport1 = env.ltop.xlate("PC", "data1")
with infamy.IsolatedMacVlan(hport1) as ns1:
ns1.addip("192.168.10.2")
ns1.addroute("192.168.33.1/32", "192.168.10.1")
ns1.must_reach("192.168.33.1")
with test.step("Test end-to-end connectivity PC:data2 to R1 loopback"):
_, hport2 = env.ltop.xlate("PC", "data2")
with infamy.IsolatedMacVlan(hport2) as ns2:
ns2.addip("192.168.70.2")
ns2.addroute("192.168.11.1/32", "192.168.70.1")
ns2.must_reach("192.168.11.1")
test.succeed()
@@ -0,0 +1,41 @@
graph "rip-multihop" {
layout="neato";
overlap="false";
esep="+40";
size=10
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
PC [
label="PC | { <mgmt1> mgmt1 | <data1> data1 | \n\n\n\n | <mgmt2> mgmt2 | <mgmt3> mgmt3 | <data2> data2 }",
pos="20,54!",
requires="controller",
];
R1 [
label="{ <mgmt> mgmt | <data> data | <link> link} | R1 \n 192.168.11.1/32 \n(lo)",
pos="100,70!",
requires="infix",
];
R2 [
label="{ <west> west | <mgmt> mgmt | <east> east } | R2 \n 192.168.22.1/32 \n(lo)",
pos="100,50!",
requires="infix",
];
R3 [
label="{ <link> link | <mgmt> mgmt | <data> data } | R3 \n 192.168.33.1/32 \n(lo)",
pos="100,30!",
requires="infix",
];
PC:mgmt1 -- R1:mgmt [requires="mgmt", color="lightgray"]
PC:mgmt2 -- R2:mgmt [requires="mgmt", color="lightgray"]
PC:mgmt3 -- R3:mgmt [requires="mgmt", color="lightgray"]
PC:data1 -- R1:data [label="192.168.10.0/24", headlabel=".1", taillabel=".2", labeldistance=2, fontcolor="black", color="black"]
R1:link -- R2:west [label="192.168.50.0/24", headlabel=".2", taillabel=".1", labeldistance=2, fontcolor="black", color="black"]
R2:east -- R3:link [label="192.168.60.0/24", headlabel=".2", taillabel=".1", labeldistance=2, fontcolor="black", color="black"]
R3:data -- PC:data2 [label="192.168.70.0/24", headlabel=".2", taillabel=".1", labeldistance=2, fontcolor="black", color="black"]
}
+118
View File
@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: rip&#45;multihop Pages: 1 -->
<svg width="720pt" height="360pt"
viewBox="0.00 0.00 720.00 359.57" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(0.86 0.86) rotate(0) translate(4 414.03)">
<title>rip&#45;multihop</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-414.03 833.07,-414.03 833.07,4 -4,4"/>
<!-- PC -->
<g id="node1" class="node">
<title>PC</title>
<polygon fill="none" stroke="black" points="0,-145.52 0,-332.52 91,-332.52 91,-145.52 0,-145.52"/>
<text text-anchor="middle" x="16.5" y="-235.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">PC</text>
<polyline fill="none" stroke="black" points="33,-145.52 33,-332.52 "/>
<text text-anchor="middle" x="62" y="-317.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
<polyline fill="none" stroke="black" points="33,-309.52 91,-309.52 "/>
<text text-anchor="middle" x="62" y="-294.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
<polyline fill="none" stroke="black" points="33,-286.52 91,-286.52 "/>
<polyline fill="none" stroke="black" points="33,-214.52 91,-214.52 "/>
<text text-anchor="middle" x="62" y="-199.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
<polyline fill="none" stroke="black" points="33,-191.52 91,-191.52 "/>
<text text-anchor="middle" x="62" y="-176.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt3</text>
<polyline fill="none" stroke="black" points="33,-168.52 91,-168.52 "/>
<text text-anchor="middle" x="62" y="-153.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
</g>
<!-- R1 -->
<g id="node2" class="node">
<title>R1</title>
<polygon fill="none" stroke="black" points="622.07,-340.53 622.07,-409.53 829.07,-409.53 829.07,-340.53 622.07,-340.53"/>
<text text-anchor="middle" x="647.07" y="-394.33" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="622.07,-386.53 672.07,-386.53 "/>
<text text-anchor="middle" x="647.07" y="-371.33" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="622.07,-363.53 672.07,-363.53 "/>
<text text-anchor="middle" x="647.07" y="-348.33" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
<polyline fill="none" stroke="black" points="672.07,-340.53 672.07,-409.53 "/>
<text text-anchor="middle" x="750.57" y="-386.33" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1 </text>
<text text-anchor="middle" x="750.57" y="-371.33" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 192.168.11.1/32 </text>
<text text-anchor="middle" x="750.57" y="-356.33" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
</g>
<!-- PC&#45;&#45;R1 -->
<g id="edge1" class="edge">
<title>PC:mgmt1&#45;&#45;R1:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-321.02C91.5,-321.02 621.57,-398.03 621.57,-398.03"/>
</g>
<!-- PC&#45;&#45;R1 -->
<g id="edge4" class="edge">
<title>PC:data1&#45;&#45;R1:data</title>
<path fill="none" stroke="black" stroke-width="2" d="M91.5,-298.02C91.5,-298.02 621.57,-375.03 621.57,-375.03"/>
<text text-anchor="middle" x="297.53" y="-340.33" font-family="DejaVu Serif, Book" font-size="14.00">192.168.10.0/24</text>
<text text-anchor="middle" x="602.41" y="-377.09" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
<text text-anchor="middle" x="110.65" y="-288.56" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
</g>
<!-- R2 -->
<g id="node3" class="node">
<title>R2</title>
<polygon fill="none" stroke="black" points="622.07,-170.52 622.07,-239.52 829.07,-239.52 829.07,-170.52 622.07,-170.52"/>
<text text-anchor="middle" x="647.07" y="-224.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">west</text>
<polyline fill="none" stroke="black" points="622.07,-216.52 672.07,-216.52 "/>
<text text-anchor="middle" x="647.07" y="-201.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="622.07,-193.52 672.07,-193.52 "/>
<text text-anchor="middle" x="647.07" y="-178.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">east</text>
<polyline fill="none" stroke="black" points="672.07,-170.52 672.07,-239.52 "/>
<text text-anchor="middle" x="750.57" y="-216.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2 </text>
<text text-anchor="middle" x="750.57" y="-201.32" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 192.168.22.1/32 </text>
<text text-anchor="middle" x="750.57" y="-186.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
</g>
<!-- PC&#45;&#45;R2 -->
<g id="edge2" class="edge">
<title>PC:mgmt2&#45;&#45;R2:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-203.02C91.5,-203.02 621.57,-205.02 621.57,-205.02"/>
</g>
<!-- R3 -->
<g id="node4" class="node">
<title>R3</title>
<polygon fill="none" stroke="black" points="622.07,-0.5 622.07,-69.5 829.07,-69.5 829.07,-0.5 622.07,-0.5"/>
<text text-anchor="middle" x="647.07" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
<polyline fill="none" stroke="black" points="622.07,-46.5 672.07,-46.5 "/>
<text text-anchor="middle" x="647.07" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="622.07,-23.5 672.07,-23.5 "/>
<text text-anchor="middle" x="647.07" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="672.07,-0.5 672.07,-69.5 "/>
<text text-anchor="middle" x="750.57" y="-46.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R3 </text>
<text text-anchor="middle" x="750.57" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 192.168.33.1/32 </text>
<text text-anchor="middle" x="750.57" y="-16.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
</g>
<!-- PC&#45;&#45;R3 -->
<g id="edge3" class="edge">
<title>PC:mgmt3&#45;&#45;R3:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-180.02C91.5,-180.02 621.57,-35 621.57,-35"/>
</g>
<!-- R1&#45;&#45;R2 -->
<g id="edge5" class="edge">
<title>R1:link&#45;&#45;R2:west</title>
<path fill="none" stroke="black" stroke-width="2" d="M646.57,-340.03C646.57,-340.03 646.57,-240.02 646.57,-240.02"/>
<text text-anchor="middle" x="587.57" y="-293.83" font-family="DejaVu Serif, Book" font-size="14.00">192.168.50.0/24</text>
<text text-anchor="middle" x="655.02" y="-254.44" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
<text text-anchor="middle" x="638.12" y="-318.21" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
</g>
<!-- R2&#45;&#45;R3 -->
<g id="edge6" class="edge">
<title>R2:east&#45;&#45;R3:link</title>
<path fill="none" stroke="black" stroke-width="2" d="M646.57,-170.02C646.57,-170.02 646.57,-70 646.57,-70"/>
<text text-anchor="middle" x="587.57" y="-123.81" font-family="DejaVu Serif, Book" font-size="14.00">192.168.60.0/24</text>
<text text-anchor="middle" x="655.02" y="-84.43" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
<text text-anchor="middle" x="638.12" y="-148.19" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
</g>
<!-- R3&#45;&#45;PC -->
<g id="edge7" class="edge">
<title>R3:data&#45;&#45;PC:data2</title>
<path fill="none" stroke="black" stroke-width="2" d="M621.57,-12C621.57,-12 91.5,-157.02 91.5,-157.02"/>
<text text-anchor="middle" x="297.53" y="-88.31" font-family="DejaVu Serif, Book" font-size="14.00">192.168.70.0/24</text>
<text text-anchor="middle" x="106.75" y="-140.38" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
<text text-anchor="middle" x="606.31" y="-21.24" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

+1
View File
@@ -0,0 +1 @@
test.adoc
@@ -0,0 +1,29 @@
=== RIP Passive Interface
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/rip_passive_interface]
==== Description
Verifies RIP passive interface functionality. A passive interface means that
RIP will include the interface's network in routing updates but will not send
or receive RIP updates on that interface.
R1 has two RIP-enabled interfaces:
- data: Passive interface (192.168.10.0/24 advertised but no updates sent/received)
- link: Active interface (RIP updates exchanged with R2)
R2 should learn about 192.168.10.0/24 from R1 via the link interface, even
though the data interface is passive.
==== Topology
image::topology.svg[RIP Passive Interface topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to target DUTs
. Configure targets
. Wait for RIP to exchange routes
. Verify connectivity to passive interface network
+156
View File
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""RIP Passive Interface
Verifies RIP passive interface functionality. A passive interface means that
RIP will include the interface's network in routing updates but will not send
or receive RIP updates on that interface.
R1 has two RIP-enabled interfaces:
- data: Passive interface (192.168.10.0/24 advertised but no updates sent/received)
- link: Active interface (RIP updates exchanged with R2)
R2 should learn about 192.168.10.0/24 from R1 via the link interface, even
though the data interface is passive.
"""
import infamy
import infamy.route as route
from infamy.util import until, parallel
def config_target1(target, data, link):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": data,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.10.1",
"prefix-length": 24
}]}
}, {
"name": link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.50.1",
"prefix-length": 24
}]
}
}]
}
},
"ietf-system": {
"system": {
"hostname": "R1"
}
},
"ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:ripv2",
"name": "default",
"rip": {
"timers": {
"update-interval": 5,
"invalid-interval": 15,
"flush-interval": 20
},
"interfaces": {
"interface": [{
"interface": data,
"passive": None
}, {
"interface": link
}]
}
}
}]
}
}
}
})
def config_target2(target, link):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.50.2",
"prefix-length": 24
}]
}
}]
}
},
"ietf-system": {
"system": {
"hostname": "R2"
}
},
"ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:ripv2",
"name": "default",
"rip": {
"timers": {
"update-interval": 5,
"invalid-interval": 15,
"flush-interval": 20
},
"interfaces": {
"interface": [{
"interface": link
}]
}
}
}]
}
}
}
})
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUTs"):
env = infamy.Env()
R1 = env.attach("R1", "mgmt")
R2 = env.attach("R2", "mgmt")
with test.step("Configure targets"):
_, R1data = env.ltop.xlate("R1", "data")
_, R2link = env.ltop.xlate("R2", "link")
_, R1link = env.ltop.xlate("R1", "link")
parallel(config_target1(R1, R1data, R1link),
config_target2(R2, R2link))
with test.step("Wait for RIP to exchange routes"):
print("Waiting for RIP routes to propagate...")
# R2 should learn about R1's passive interface network (192.168.10.0/24)
# even though it's passive, R1 should still advertise it
until(lambda: route.ipv4_route_exist(R2, "192.168.10.0/24", proto="ietf-rip:rip"), attempts=40)
with test.step("Verify connectivity to passive interface network"):
# Test that we can reach the passive interface from PC
_, hport0 = env.ltop.xlate("PC", "data")
with infamy.IsolatedMacVlan(hport0) as ns0:
ns0.addip("192.168.10.2")
# No need for route since we're on the same network
ns0.must_reach("192.168.10.1")
test.succeed()
@@ -0,0 +1,33 @@
graph "rip-passive" {
layout="neato";
overlap="false";
esep="+30";
size=10
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
PC [
label="PC | { <mgmt1> mgmt1 | <data> data | <mgmt2> mgmt2 }",
pos="20,50!",
requires="controller",
];
R1 [
label="{ <mgmt> mgmt | <data> data | <link> link} | R1",
pos="160,60!",
requires="infix",
];
R2 [
label="{ <link> link | <mgmt> mgmt } | R2",
pos="160,30!",
requires="infix",
];
PC:mgmt1 -- R1:mgmt [requires="mgmt", color="lightgray"]
PC:mgmt2 -- R2:mgmt [requires="mgmt", color="lightgray"]
PC:data -- R1:data [color="black", label="192.168.10.0/24", headlabel=".1", taillabel=".2", labeldistance=2, fontcolor="black"]
R1:link -- R2:link [taillabel="192.168.50.0/24", headlabel=".2", taillabel=".1", labeldistance=2, fontcolor="black", color="black"]
}
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: rip&#45;passive Pages: 1 -->
<svg width="718pt" height="200pt"
viewBox="0.00 0.00 718.06 200.01" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 196.01)">
<title>rip&#45;passive</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-196.01 714.06,-196.01 714.06,4 -4,4"/>
<!-- PC -->
<g id="node1" class="node">
<title>PC</title>
<polygon fill="none" stroke="black" points="0,-78.01 0,-147.01 91,-147.01 91,-78.01 0,-78.01"/>
<text text-anchor="middle" x="16.5" y="-108.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">PC</text>
<polyline fill="none" stroke="black" points="33,-78.01 33,-147.01 "/>
<text text-anchor="middle" x="62" y="-131.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
<polyline fill="none" stroke="black" points="33,-124.01 91,-124.01 "/>
<text text-anchor="middle" x="62" y="-108.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="33,-101.01 91,-101.01 "/>
<text text-anchor="middle" x="62" y="-85.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
</g>
<!-- R1 -->
<g id="node2" class="node">
<title>R1</title>
<polygon fill="none" stroke="black" points="627.06,-122.51 627.06,-191.51 710.06,-191.51 710.06,-122.51 627.06,-122.51"/>
<text text-anchor="middle" x="652.06" y="-176.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="627.06,-168.51 677.06,-168.51 "/>
<text text-anchor="middle" x="652.06" y="-153.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="627.06,-145.51 677.06,-145.51 "/>
<text text-anchor="middle" x="652.06" y="-130.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
<polyline fill="none" stroke="black" points="677.06,-122.51 677.06,-191.51 "/>
<text text-anchor="middle" x="693.56" y="-153.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1</text>
</g>
<!-- PC&#45;&#45;R1 -->
<g id="edge1" class="edge">
<title>PC:mgmt1&#45;&#45;R1:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-135.51C91.5,-135.51 626.56,-180.01 626.56,-180.01"/>
</g>
<!-- PC&#45;&#45;R1 -->
<g id="edge3" class="edge">
<title>PC:data&#45;&#45;R1:data</title>
<path fill="none" stroke="black" stroke-width="2" d="M91.5,-112.51C91.5,-112.51 626.56,-157.01 626.56,-157.01"/>
<text text-anchor="middle" x="300.03" y="-138.56" font-family="DejaVu Serif, Book" font-size="14.00">192.168.10.0/24</text>
<text text-anchor="middle" x="607.8" y="-160.23" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
<text text-anchor="middle" x="110.26" y="-101.89" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
</g>
<!-- R2 -->
<g id="node3" class="node">
<title>R2</title>
<polygon fill="none" stroke="black" points="627.06,-0.5 627.06,-46.5 710.06,-46.5 710.06,-0.5 627.06,-0.5"/>
<text text-anchor="middle" x="652.06" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
<polyline fill="none" stroke="black" points="627.06,-23.5 677.06,-23.5 "/>
<text text-anchor="middle" x="652.06" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="677.06,-0.5 677.06,-46.5 "/>
<text text-anchor="middle" x="693.56" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2</text>
</g>
<!-- PC&#45;&#45;R2 -->
<g id="edge2" class="edge">
<title>PC:mgmt2&#45;&#45;R2:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-89.51C91.5,-89.51 626.56,-11.5 626.56,-11.5"/>
</g>
<!-- R1&#45;&#45;R2 -->
<g id="edge4" class="edge">
<title>R1:link&#45;&#45;R2:link</title>
<path fill="none" stroke="black" stroke-width="2" d="M651.56,-122.01C651.56,-122.01 651.56,-46.5 651.56,-46.5"/>
<text text-anchor="middle" x="660.01" y="-60.93" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
<text text-anchor="middle" x="643.11" y="-100.19" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

+1
View File
@@ -0,0 +1 @@
test.adoc
@@ -0,0 +1,37 @@
=== RIP Redistribution
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/rip_redistribute]
==== Description
Verifies that RIP can redistribute routes from other protocols.
Topology:
- R1: Gateway router running both RIP and OSPF
- RIP interface to R2
- OSPF interface to R3
- Redistributes OSPF routes into RIP
- Redistributes RIP routes into OSPF
- R2: RIP-only router with loopback 192.168.200.1/32
- R3: OSPF-only router with loopback 192.168.100.1/32
Expected behavior:
- R2 (RIP) should learn R3's OSPF loopback (192.168.100.1/32) via RIP redistribution
- R3 (OSPF) should learn R2's RIP loopback (192.168.200.1/32) via OSPF redistribution
==== Topology
image::topology.svg[RIP Redistribution topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to target DUTs
. Configure routers
. Wait for OSPF to converge on R1-R3 link
. Wait for RIP to converge on R1-R2 link
. Verify R2 (RIP) learns R3's OSPF routes via redistribution
. Verify R3 (OSPF) learns R2's RIP routes via redistribution
+286
View File
@@ -0,0 +1,286 @@
#!/usr/bin/env python3
"""RIP Redistribution
Verifies that RIP can redistribute routes from other protocols.
Topology:
- R1: Gateway router running both RIP and OSPF
- RIP interface to R2
- OSPF interface to R3
- Redistributes OSPF routes into RIP
- Redistributes RIP routes into OSPF
- R2: RIP-only router with loopback 192.168.200.1/32
- R3: OSPF-only router with loopback 192.168.100.1/32
Expected behavior:
- R2 (RIP) should learn R3's OSPF loopback (192.168.100.1/32) via RIP redistribution
- R3 (OSPF) should learn R2's RIP loopback (192.168.200.1/32) via OSPF redistribution
"""
import infamy
import infamy.route as route
from infamy.util import until, parallel
def config_r1_gateway(target, rip_link, ospf_link):
"""Configure R1 as gateway running both RIP and OSPF"""
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": rip_link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.50.1",
"prefix-length": 24
}]
}
}, {
"name": ospf_link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.60.1",
"prefix-length": 24
}]
}
}]
}
},
"ietf-system": {
"system": {
"hostname": "R1"
}
},
"ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:ripv2",
"name": "default",
"rip": {
"timers": {
"update-interval": 5,
"invalid-interval": 15,
"flush-interval": 20
},
"redistribute": {
"redistribute": [{
"protocol": "ospf"
}, {
"protocol": "connected"
}]
},
"interfaces": {
"interface": [{
"interface": rip_link
}]
}
}
}, {
"type": "infix-routing:ospfv2",
"name": "default",
"ospf": {
"redistribute": {
"redistribute": [{
"protocol": "rip"
}, {
"protocol": "connected"
}]
},
"areas": {
"area": [{
"area-id": "0.0.0.0",
"interfaces": {
"interface": [{
"enabled": True,
"name": ospf_link,
"hello-interval": 1,
"dead-interval": 3
}]
}
}]
}
}
}]
}
}
}
})
def config_r2_rip(target, link):
"""Configure R2 with RIP only"""
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.50.2",
"prefix-length": 24
}]
}
}, {
"name": "lo",
"enabled": True,
"ipv4": {
"address": [{
"ip": "192.168.200.1",
"prefix-length": 32
}]
}
}]
}
},
"ietf-system": {
"system": {
"hostname": "R2"
}
},
"ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:ripv2",
"name": "default",
"rip": {
"timers": {
"update-interval": 5,
"invalid-interval": 15,
"flush-interval": 20
},
"redistribute": {
"redistribute": [{
"protocol": "connected"
}]
},
"interfaces": {
"interface": [{
"interface": link
}]
}
}
}]
}
}
}
})
def config_r3_ospf(target, link):
"""Configure R3 with OSPF only"""
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.60.2",
"prefix-length": 24
}]
}
}, {
"name": "lo",
"enabled": True,
"ipv4": {
"address": [{
"ip": "192.168.100.1",
"prefix-length": 32
}]
}
}]
}
},
"ietf-system": {
"system": {
"hostname": "R3"
}
},
"ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:ospfv2",
"name": "default",
"ospf": {
"redistribute": {
"redistribute": [{
"protocol": "connected"
}]
},
"areas": {
"area": [{
"area-id": "0.0.0.0",
"interfaces": {
"interface": [{
"enabled": True,
"name": link,
"hello-interval": 1,
"dead-interval": 3
}]
}
}]
}
}
}]
}
}
}
})
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUTs"):
env = infamy.Env()
R1 = env.attach("R1", "mgmt")
R2 = env.attach("R2", "mgmt")
R3 = env.attach("R3", "mgmt")
with test.step("Configure routers"):
_, R1rip = env.ltop.xlate("R1", "rip")
_, R1ospf = env.ltop.xlate("R1", "ospf")
_, R2link = env.ltop.xlate("R2", "link")
_, R3link = env.ltop.xlate("R3", "link")
parallel(config_r1_gateway(R1, R1rip, R1ospf),
config_r2_rip(R2, R2link),
config_r3_ospf(R3, R3link))
with test.step("Wait for OSPF to converge on R1-R3 link"):
print("Waiting for OSPF convergence...")
# R1 should learn R3's loopback via OSPF
until(lambda: route.ipv4_route_exist(R1, "192.168.100.1/32", proto="ietf-ospf:ospfv2"), attempts=40)
# R3 should learn R1's OSPF link via OSPF
until(lambda: route.ipv4_route_exist(R3, "192.168.60.0/24", proto="ietf-ospf:ospfv2"), attempts=40)
with test.step("Wait for RIP to converge on R1-R2 link"):
print("Waiting for RIP convergence...")
# R1 should learn R2's loopback via RIP
until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", proto="ietf-rip:rip"), attempts=40)
# R2 should learn R1's OSPF link (192.168.60.0/24) via RIP (redistributed from connected)
until(lambda: route.ipv4_route_exist(R2, "192.168.60.0/24", proto="ietf-rip:rip"), attempts=40)
with test.step("Verify R2 (RIP) learns R3's OSPF routes via redistribution"):
print("Checking OSPF->RIP redistribution...")
# R2 should learn R3's loopback (OSPF route) via RIP redistribution on R1
until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", proto="ietf-rip:rip"), attempts=40)
with test.step("Verify R3 (OSPF) learns R2's RIP routes via redistribution"):
print("Checking RIP->OSPF redistribution...")
# R3 should learn R2's loopback (RIP route) via OSPF redistribution on R1
until(lambda: route.ipv4_route_exist(R3, "192.168.200.1/32", proto="ietf-ospf:ospfv2"), attempts=40)
test.succeed()
@@ -0,0 +1,39 @@
graph "rip-redistribute" {
layout="neato";
overlap="false";
esep="+40";
size=10
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
PC [
label="PC | { <mgmt3> mgmt3 | \n\n | <mgmt1> mgmt1 | \n\n | <mgmt2> mgmt2 }",
pos="20,50!",
requires="controller",
];
R3 [
label="{ <mgmt> mgmt | <link> link } | R3 (OSPF) \n 192.168.100.1/32 \n(lo)",
pos="100,100!",
requires="infix",
];
R1 [
label="{ <ospf> ospf | <mgmt> mgmt | <rip> rip } | R1 (ASBR)",
pos="91,50!",
requires="infix",
];
R2 [
label="{ <link> link | <mgmt> mgmt } | R2 (RIP) \n 192.168.200.1/32 \n(lo)",
pos="100,10!",
requires="infix",
];
PC:mgmt3 -- R3:mgmt [requires="mgmt", color="lightgray"]
PC:mgmt1 -- R1:mgmt [requires="mgmt", color="lightgray"]
PC:mgmt2 -- R2:mgmt [requires="mgmt", color="lightgray"]
R3:link -- R1:ospf [label="192.168.60.0/24", headlabel=".1", taillabel=".2", labeldistance=2, fontcolor="black", color="blue"]
R1:rip -- R2:link [label="192.168.50.0/24", headlabel=".2", taillabel=".1", labeldistance=2, fontcolor="black", color="black"]
}
@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: rip&#45;redistribute Pages: 1 -->
<svg width="485pt" height="427pt"
viewBox="0.00 0.00 485.03 426.54" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 422.54)">
<title>rip&#45;redistribute</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-422.54 481.03,-422.54 481.03,4 -4,4"/>
<!-- PC -->
<g id="node1" class="node">
<title>PC</title>
<polygon fill="none" stroke="black" points="0,-114.52 0,-263.52 91,-263.52 91,-114.52 0,-114.52"/>
<text text-anchor="middle" x="16.5" y="-185.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">PC</text>
<polyline fill="none" stroke="black" points="33,-114.52 33,-263.52 "/>
<text text-anchor="middle" x="62" y="-248.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt3</text>
<polyline fill="none" stroke="black" points="33,-240.52 91,-240.52 "/>
<polyline fill="none" stroke="black" points="33,-200.52 91,-200.52 "/>
<text text-anchor="middle" x="62" y="-185.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
<polyline fill="none" stroke="black" points="33,-177.52 91,-177.52 "/>
<polyline fill="none" stroke="black" points="33,-137.52 91,-137.52 "/>
<text text-anchor="middle" x="62" y="-122.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
</g>
<!-- R3 -->
<g id="node2" class="node">
<title>R3</title>
<polygon fill="none" stroke="black" points="262.03,-365.04 262.03,-418.04 477.03,-418.04 477.03,-365.04 262.03,-365.04"/>
<text text-anchor="middle" x="287.03" y="-401.34" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="262.03,-392.04 312.03,-392.04 "/>
<text text-anchor="middle" x="287.03" y="-374.84" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
<polyline fill="none" stroke="black" points="312.03,-365.04 312.03,-418.04 "/>
<text text-anchor="middle" x="394.53" y="-402.84" font-family="DejaVu Sans Mono, Book" font-size="14.00">R3 (OSPF) </text>
<text text-anchor="middle" x="394.53" y="-387.84" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 192.168.100.1/32 </text>
<text text-anchor="middle" x="394.53" y="-372.84" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
</g>
<!-- PC&#45;&#45;R3 -->
<g id="edge1" class="edge">
<title>PC:mgmt3&#45;&#45;R3:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-252.02C91.5,-252.02 261.53,-405.54 261.53,-405.54"/>
</g>
<!-- R1 -->
<g id="node3" class="node">
<title>R1</title>
<polygon fill="none" stroke="black" points="262.58,-154.52 262.58,-223.52 403.58,-223.52 403.58,-154.52 262.58,-154.52"/>
<text text-anchor="middle" x="287.58" y="-208.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">ospf</text>
<polyline fill="none" stroke="black" points="262.58,-200.52 312.58,-200.52 "/>
<text text-anchor="middle" x="287.58" y="-185.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="262.58,-177.52 312.58,-177.52 "/>
<text text-anchor="middle" x="287.58" y="-162.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">rip</text>
<polyline fill="none" stroke="black" points="312.58,-154.52 312.58,-223.52 "/>
<text text-anchor="middle" x="358.08" y="-185.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1 (ASBR)</text>
</g>
<!-- PC&#45;&#45;R1 -->
<g id="edge2" class="edge">
<title>PC:mgmt1&#45;&#45;R1:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-189.02C91.5,-189.02 262.08,-189.02 262.08,-189.02"/>
</g>
<!-- R2 -->
<g id="node4" class="node">
<title>R2</title>
<polygon fill="none" stroke="black" points="262.03,-0.5 262.03,-53.5 477.03,-53.5 477.03,-0.5 262.03,-0.5"/>
<text text-anchor="middle" x="287.03" y="-36.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
<polyline fill="none" stroke="black" points="262.03,-27.5 312.03,-27.5 "/>
<text text-anchor="middle" x="287.03" y="-10.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="312.03,-0.5 312.03,-53.5 "/>
<text text-anchor="middle" x="394.53" y="-38.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2 (RIP) </text>
<text text-anchor="middle" x="394.53" y="-23.3" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 192.168.200.1/32 </text>
<text text-anchor="middle" x="394.53" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
</g>
<!-- PC&#45;&#45;R2 -->
<g id="edge3" class="edge">
<title>PC:mgmt2&#45;&#45;R2:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-126.02C91.5,-126.02 261.53,-14 261.53,-14"/>
</g>
<!-- R3&#45;&#45;R1 -->
<g id="edge4" class="edge">
<title>R3:link&#45;&#45;R1:ospf</title>
<path fill="none" stroke="blue" stroke-width="2" d="M286.53,-364.54C286.53,-364.54 287.08,-224.02 287.08,-224.02"/>
<text text-anchor="middle" x="227.81" y="-298.08" font-family="DejaVu Serif, Book" font-size="14.00">192.168.60.0/24</text>
<text text-anchor="middle" x="295.46" y="-238.48" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
<text text-anchor="middle" x="278.15" y="-342.68" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
</g>
<!-- R1&#45;&#45;R2 -->
<g id="edge5" class="edge">
<title>R1:rip&#45;&#45;R2:link</title>
<path fill="none" stroke="black" stroke-width="2" d="M287.08,-154.02C287.08,-154.02 286.53,-54 286.53,-54"/>
<text text-anchor="middle" x="227.81" y="-107.81" font-family="DejaVu Serif, Book" font-size="14.00">192.168.50.0/24</text>
<text text-anchor="middle" x="295.08" y="-68.38" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
<text text-anchor="middle" x="278.53" y="-132.24" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

+41 -41
View File
@@ -1,41 +1,41 @@
INTERFACE PROTOCOL STATE DATA 
lo loopback UP 
ipv4 127.0.0.1/8 (static)
ipv6 ::1/128 (static)
br0 bridge  vlan:1u pvid:1
│ ethernet UP 00:a0:85:00:03:00
│ ipv4 169.254.1.1/16 (random)
│ ipv6 fe80::2a0:85ff:fe00:300/64 (link-layer)
├ e3 bridge FORWARDING vlan:1u pvid:1
├ e4 bridge FORWARDING vlan:1u pvid:1
├ veth0b bridge FORWARDING vlan:1u pvid:1
└ veth2b bridge FORWARDING vlan:1u pvid:1
br1 bridge  
│ ethernet UP 00:a0:85:00:03:00
├ veth1b bridge FORWARDING vlan:6u pvid:6
└ veth3b bridge FORWARDING 
e1 ethernet UP 00:a0:85:00:03:01
ipv6 fe80::2a0:85ff:fe00:301/64 (link-layer)
e2 ethernet UP 00:a0:85:00:03:02
ipv6 fe80::2a0:85ff:fe00:302/64 (link-layer)
e3.8 ethernet UP 00:a0:85:00:03:03
│ ipv4 10.1.1.1/32 (static)
└ e3 ethernet UP 00:a0:85:00:03:03
e4.8 ethernet UP 00:a0:85:00:03:04
│ ipv4 10.1.1.1/32 (static)
└ e4 ethernet UP 00:a0:85:00:03:04
e5 ethernet UP 00:a0:85:00:03:05
ipv6 fe80::2a0:85ff:fe00:305/64 (link-layer)
e6 ethernet UP 00:a0:85:00:03:06
ipv4 10.1.1.101/24 (static)
ipv6 fe80::2a0:85ff:fe00:306/64 (link-layer)
e7 ethernet UP 00:a0:85:00:03:07
ipv6 fe80::2a0:85ff:fe00:307/64 (link-layer)
veth0a container container-A 
veth0b veth UP e6:19:75:86:80:dd
veth1a container container-A 
veth1b veth UP 3e:67:0f:15:24:14
veth2a container container-B 
veth2b veth UP b2:fa:30:63:98:f0
veth3a container container-B 
veth3b veth UP b2:1c:fa:a7:45:eb
INTERFACE PROTOCOL STATE DATA 
lo loopback UP 
ipv4 127.0.0.1/8 (static)
ipv6 ::1/128 (static)
br0 bridge  vlan:1u pvid:1
│ ethernet UP 00:a0:85:00:03:00
│ ipv4 169.254.1.1/16 (random)
│ ipv6 fe80::2a0:85ff:fe00:300/64 (link-layer)
├ e3 bridge FORWARDING vlan:1u pvid:1
├ e4 bridge FORWARDING vlan:1u pvid:1
├ veth0b bridge FORWARDING vlan:1u pvid:1
└ veth2b bridge FORWARDING vlan:1u pvid:1
br1 bridge  
│ ethernet UP 00:a0:85:00:03:00
├ veth1b bridge FORWARDING vlan:6u pvid:6
└ veth3b bridge FORWARDING 
e1 ethernet UP 00:a0:85:00:03:01
ipv6 fe80::2a0:85ff:fe00:301/64 (link-layer)
e2 ethernet UP 00:a0:85:00:03:02
ipv6 fe80::2a0:85ff:fe00:302/64 (link-layer)
e3.8 ethernet UP 00:a0:85:00:03:03
│ ipv4 10.1.1.1/32 (static)
└ e3 ethernet UP 00:a0:85:00:03:03
e4.8 ethernet UP 00:a0:85:00:03:04
│ ipv4 10.1.1.1/32 (static)
└ e4 ethernet UP 00:a0:85:00:03:04
e5 ethernet UP 00:a0:85:00:03:05
ipv6 fe80::2a0:85ff:fe00:305/64 (link-layer)
e6 ethernet UP 00:a0:85:00:03:06
ipv4 10.1.1.101/24 (static)
ipv6 fe80::2a0:85ff:fe00:306/64 (link-layer)
e7 ethernet UP 00:a0:85:00:03:07
ipv6 fe80::2a0:85ff:fe00:307/64 (link-layer)
veth0a container container-A 
veth0b veth UP e6:19:75:86:80:dd
veth1a container container-A 
veth1b veth UP 3e:67:0f:15:24:14
veth2a container container-B 
veth2b veth UP b2:fa:30:63:98:f0
veth3a container container-B 
veth3b veth UP b2:1c:fa:a7:45:eb
@@ -1,5 +1,8 @@
{
"ietf-routing:routing": {
"interfaces": {
"interface": []
},
"ribs": {
"rib": [
{
@@ -1,51 +1,51 @@
INTERFACE PROTOCOL STATE DATA 
lo loopback UP 
ipv4 127.0.0.1/8 (static)
ipv6 ::1/128 (static)
br-0 bridge DOWN 
ethernet DOWN 00:a0:85:00:03:00
br-D bridge  
│ ethernet UP 00:a0:85:00:03:00
│ ipv4 10.0.0.1/8 (static)
│ ipv4 192.168.20.1/24 (static)
│ ipv6 2001:db8::1/64 (static)
│ ipv6 fe80::2a0:85ff:fe00:300/64 (link-layer)
└ veth0a.20 bridge FORWARDING 
br-Q bridge  vlan:20u,30u,40t
│ ethernet UP 00:a0:85:00:03:00
├ e3 bridge FORWARDING vlan:20t,30t,40t
└ veth0b bridge FORWARDING vlan:20t,30t,40t
br-Q.40 ethernet UP 00:a0:85:00:03:00
└ br-Q ethernet UP 00:a0:85:00:03:00
br-X bridge  
│ ethernet UP 00:a0:85:00:03:00
└ e2.30 bridge FORWARDING 
e1 ethernet UP 00:a0:85:00:03:01
ipv6 fe80::2a0:85ff:fe00:301/64 (link-layer)
e2 ethernet UP 00:a0:85:00:03:02
ipv6 fe80::2a0:85ff:fe00:302/64 (link-layer)
e2.30 ethernet UP 00:a0:85:00:03:02
└ e2 ethernet UP 00:a0:85:00:03:02
e3.10 ethernet UP 00:a0:85:00:03:03
└ e3 ethernet UP 00:a0:85:00:03:03
e4 ethernet UP 00:a0:85:00:03:04
ipv6 fe80::2a0:85ff:fe00:304/64 (link-layer)
e5 ethernet UP 00:a0:85:00:03:05
ipv6 fe80::2a0:85ff:fe00:305/64 (link-layer)
e6 ethernet UP 00:a0:85:00:03:06
ipv6 fe80::2a0:85ff:fe00:306/64 (link-layer)
e7 ethernet UP 00:a0:85:00:03:07
ipv6 fe80::2a0:85ff:fe00:307/64 (link-layer)
gre-v4 gre UP 
gre-v6 gre UP 
ipv4 192.168.50.2/16 (static)
gretap-v4 gretap UP f6:d4:96:d8:9f:96
gretap-v6 gretap UP 7a:46:13:bd:fa:e6
veth0a veth UP 6e:d0:98:c4:e7:ef
veth0a.20 ethernet UP 6e:d0:98:c4:e7:ef
└ veth0a ethernet UP 6e:d0:98:c4:e7:ef
veth0b veth UP 36:da:80:06:7f:99
vxlan-v4 vxlan UP 8a:ea:59:32:df:70
ipv4 192.168.30.2/24 (static)
vxlan-v6 vxlan UP 3e:30:c6:a1:71:64
ipv4 192.168.40.2/24 (static)
INTERFACE PROTOCOL STATE DATA 
lo loopback UP 
ipv4 127.0.0.1/8 (static)
ipv6 ::1/128 (static)
br-0 bridge DOWN 
ethernet DOWN 00:a0:85:00:03:00
br-D bridge  
│ ethernet UP 00:a0:85:00:03:00
│ ipv4 10.0.0.1/8 (static)
│ ipv4 192.168.20.1/24 (static)
│ ipv6 2001:db8::1/64 (static)
│ ipv6 fe80::2a0:85ff:fe00:300/64 (link-layer)
└ veth0a.20 bridge FORWARDING 
br-Q bridge  vlan:20u,30u,40t
│ ethernet UP 00:a0:85:00:03:00
├ e3 bridge FORWARDING vlan:20t,30t,40t
└ veth0b bridge FORWARDING vlan:20t,30t,40t
br-Q.40 ethernet UP 00:a0:85:00:03:00
└ br-Q ethernet UP 00:a0:85:00:03:00
br-X bridge  
│ ethernet UP 00:a0:85:00:03:00
└ e2.30 bridge FORWARDING 
e1 ethernet UP 00:a0:85:00:03:01
ipv6 fe80::2a0:85ff:fe00:301/64 (link-layer)
e2 ethernet UP 00:a0:85:00:03:02
ipv6 fe80::2a0:85ff:fe00:302/64 (link-layer)
e2.30 ethernet UP 00:a0:85:00:03:02
└ e2 ethernet UP 00:a0:85:00:03:02
e3.10 ethernet UP 00:a0:85:00:03:03
└ e3 ethernet UP 00:a0:85:00:03:03
e4 ethernet UP 00:a0:85:00:03:04
ipv6 fe80::2a0:85ff:fe00:304/64 (link-layer)
e5 ethernet UP 00:a0:85:00:03:05
ipv6 fe80::2a0:85ff:fe00:305/64 (link-layer)
e6 ethernet UP 00:a0:85:00:03:06
ipv6 fe80::2a0:85ff:fe00:306/64 (link-layer)
e7 ethernet UP 00:a0:85:00:03:07
ipv6 fe80::2a0:85ff:fe00:307/64 (link-layer)
gre-v4 gre UP 
gre-v6 gre UP 
ipv4 192.168.50.2/16 (static)
gretap-v4 gretap UP f6:d4:96:d8:9f:96
gretap-v6 gretap UP 7a:46:13:bd:fa:e6
veth0a veth UP 6e:d0:98:c4:e7:ef
veth0a.20 ethernet UP 6e:d0:98:c4:e7:ef
└ veth0a ethernet UP 6e:d0:98:c4:e7:ef
veth0b veth UP 36:da:80:06:7f:99
vxlan-v4 vxlan UP 8a:ea:59:32:df:70
ipv4 192.168.30.2/24 (static)
vxlan-v6 vxlan UP 3e:30:c6:a1:71:64
ipv4 192.168.40.2/24 (static)
@@ -3,6 +3,7 @@ type : bridge
index : 9
mtu : 1500
operational status : down
ip forwarding : disabled
physical address : 00:a0:85:00:03:00
ipv4 addresses :
ipv6 addresses :
@@ -3,6 +3,7 @@ type : bridge
index : 15
mtu : 1500
operational status : up
ip forwarding : disabled
physical address : 00:a0:85:00:03:00
ipv4 addresses : 10.0.0.1/8 (static)
192.168.20.1/24 (static)
@@ -3,6 +3,7 @@ type : bridge
index : 16
mtu : 1500
operational status : up
ip forwarding : disabled
physical address : 00:a0:85:00:03:00
ipv4 addresses :
ipv6 addresses :
@@ -3,6 +3,7 @@ type : vlan
index : 17
mtu : 1500
operational status : up
ip forwarding : disabled
lower-layer-if : br-Q
physical address : 00:a0:85:00:03:00
ipv4 addresses :
@@ -3,6 +3,7 @@ type : bridge
index : 11
mtu : 1500
operational status : up
ip forwarding : disabled
physical address : 00:a0:85:00:03:00
ipv4 addresses :
ipv6 addresses :
@@ -3,6 +3,7 @@ type : gre
index : 19
mtu : 1476
operational status : up
ip forwarding : disabled
ipv4 addresses :
ipv6 addresses :
local address : 192.168.20.1
@@ -3,6 +3,7 @@ type : gre
index : 20
mtu : 1448
operational status : up
ip forwarding : disabled
ipv4 addresses : 192.168.50.2/16 (static)
ipv6 addresses :
local address : 2001:db8::1
@@ -3,6 +3,7 @@ type : gretap
index : 21
mtu : 1462
operational status : up
ip forwarding : disabled
physical address : f6:d4:96:d8:9f:96
ipv4 addresses :
ipv6 addresses :
@@ -3,6 +3,7 @@ type : gretap
index : 22
mtu : 1434
operational status : up
ip forwarding : disabled
physical address : 7a:46:13:bd:fa:e6
ipv4 addresses :
ipv6 addresses :
@@ -3,6 +3,7 @@ type : veth
index : 13
mtu : 1500
operational status : up
ip forwarding : disabled
physical address : 6e:d0:98:c4:e7:ef
ipv4 addresses :
ipv6 addresses :
@@ -3,6 +3,7 @@ type : vlan
index : 14
mtu : 1500
operational status : up
ip forwarding : disabled
lower-layer-if : veth0a
physical address : 6e:d0:98:c4:e7:ef
ipv4 addresses :
@@ -3,6 +3,7 @@ type : veth
index : 12
mtu : 1500
operational status : up
ip forwarding : disabled
physical address : 36:da:80:06:7f:99
ipv4 addresses :
ipv6 addresses :
@@ -3,6 +3,7 @@ type : vxlan
index : 23
mtu : 1500
operational status : up
ip forwarding : disabled
physical address : 8a:ea:59:32:df:70
ipv4 addresses : 192.168.30.2/24 (static)
ipv6 addresses :
@@ -3,6 +3,7 @@ type : vxlan
index : 24
mtu : 1500
operational status : up
ip forwarding : disabled
physical address : 3e:30:c6:a1:71:64
ipv4 addresses : 192.168.40.2/24 (static)
ipv6 addresses :