From 6b37a7dd7c53983aaa7be6c59e103281eedbc480 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 17 Sep 2024 06:05:07 +0200 Subject: [PATCH 01/39] doc: Update ChangeLog with major package upgrades Signed-off-by: Joachim Wiberg --- doc/ChangeLog.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 2767e294..210e1fed 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -8,11 +8,21 @@ All notable changes to the project are documented in this file. ------------------------- ### Changes +- Upgrade Buildroot to 2024.02.6 (LTS) - Upgrade Linux kernel to 6.6.52 (LTS) +- Upgrade libyang to 3.4.2 +- Upgrade sysrepo to 2.11.7 +- Upgrade netopeer2 (NETCONF) to 2.2.31 - Updated `infix-routing.yang` to declare deviations for unsupported OSPF RPCs and Notifications in `ietf-ospf.yang` - The CLI admin-exec command `show dns` now also shows any configured name servers, not just ones acquired via DHCP. Issue #510 +- Add support for IPv4 (autoconf) `request-address`. This instructs the + ZeroConf client to start with the requested address. If this is not + successful the client falls back to its default behavior. Issue #628 +- Major speedup (10x) in operational data, in particular when querying + interface status. Very noticeable in the CLI `show interfaces` + command on devices with large port counts. Issue #651 - Silence `yanger` log warnings for failing `mctl` command. Caused by `mctl` reporting no multicast filtering enabled on bridge From 9079c55944619d3b11edc62bda87019b1e6f6193 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 17 Sep 2024 04:54:02 +0200 Subject: [PATCH 02/39] doc: minor, grammar and slight clarification of example Signed-off-by: Joachim Wiberg --- doc/testing.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/testing.md b/doc/testing.md index dec586ad..1f15ecab 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -281,17 +281,18 @@ or, when running interactively: $ make test-sh 09:08:17 infamy0:test # ./9pm/9pm.py -o"--transport=restconf" case/ietf_system/hostname.py + ### Test specification The test specification is automaticly generated from the test cases, -this put some constaints on the test cases: +this puts some constraints on the test cases: All test cases must be written so that it can be understandable with -only the steps so the result can be reproduced -manually. For example: Intead of `set ip address on -the interface` it should be more human readable, for example: +only the steps so the result can be reproduced manually. E.g., instead +of `set ip address on the interface` it should be more human readable, +for example: -``` +```python test.step("Set IPv4 address on the interface dut:cross") test.step("Verify the IP address is set on dut:cross") @@ -303,9 +304,11 @@ Each test case must begin with the test name followed by a empty line and the description of the test. For example: ``` +""" Set hostname Verify that it it possible to change hostname +""" ``` The test specifaction can be genererated with: From af1a86f10ad5617c752350f5177d701f894594ea Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sat, 21 Sep 2024 10:17:33 +0200 Subject: [PATCH 03/39] board/common: reactivate scp/sftp and drop non-working cli subsystem The sftp subsystem was lost in 61213c7. Signed-off-by: Joachim Wiberg --- board/common/rootfs/etc/ssh/sshd_config | 3 +++ board/common/rootfs/etc/ssh/sshd_config.d/cli.conf | 1 - board/common/rootfs/etc/ssh/sshd_config.d/sftp.conf | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) delete mode 100644 board/common/rootfs/etc/ssh/sshd_config.d/cli.conf create mode 100644 board/common/rootfs/etc/ssh/sshd_config.d/sftp.conf diff --git a/board/common/rootfs/etc/ssh/sshd_config b/board/common/rootfs/etc/ssh/sshd_config index cf9ac977..98d81b92 100644 --- a/board/common/rootfs/etc/ssh/sshd_config +++ b/board/common/rootfs/etc/ssh/sshd_config @@ -1 +1,4 @@ +# Allow client to pass locale environment +AcceptEnv LANG LC_* + Include /etc/ssh/sshd_config.d/*.conf diff --git a/board/common/rootfs/etc/ssh/sshd_config.d/cli.conf b/board/common/rootfs/etc/ssh/sshd_config.d/cli.conf deleted file mode 100644 index 6946e76b..00000000 --- a/board/common/rootfs/etc/ssh/sshd_config.d/cli.conf +++ /dev/null @@ -1 +0,0 @@ -Subsystem cli /usr/bin/klish diff --git a/board/common/rootfs/etc/ssh/sshd_config.d/sftp.conf b/board/common/rootfs/etc/ssh/sshd_config.d/sftp.conf new file mode 100644 index 00000000..ac9b6a65 --- /dev/null +++ b/board/common/rootfs/etc/ssh/sshd_config.d/sftp.conf @@ -0,0 +1 @@ +Subsystem sftp /usr/libexec/sftp-server From 4ed4fa50527836a92ad45f6e68d3023f94532003 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 17 Sep 2024 04:54:40 +0200 Subject: [PATCH 04/39] board/common: simplify avahi-autoipd.action script - We know we always have iproute2 tools - We only use one ipv4ll client per interface, so UNBIND can be simplified to flush ifname:avahi - Log all events handled by script for troubleshooting Note: on BIND event we may be called without first getting UNBIND on previous address, hence the flush there too. Signed-off-by: Joachim Wiberg --- .../rootfs/etc/avahi/avahi-autoipd.action | 90 +++++-------------- 1 file changed, 22 insertions(+), 68 deletions(-) diff --git a/board/common/rootfs/etc/avahi/avahi-autoipd.action b/board/common/rootfs/etc/avahi/avahi-autoipd.action index 0a2cba9b..16df3899 100755 --- a/board/common/rootfs/etc/avahi/avahi-autoipd.action +++ b/board/common/rootfs/etc/avahi/avahi-autoipd.action @@ -1,24 +1,4 @@ #!/bin/sh - -# This file is part of avahi. -# -# avahi is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation; either version 2 of the -# License, or (at your option) any later version. -# -# avahi is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public -# License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with avahi; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA. - -set -e - # Command line arguments: # $1 event that happened: # BIND: Successfully claimed address @@ -30,59 +10,33 @@ set -e PATH="$PATH:/usr/bin:/usr/sbin:/bin:/sbin" +log() +{ + logger -I $$ -t zeroconf -p user.notice "$*" +} + # Use a different metric for each interface, so that we can set # identical routes to multiple interfaces. - METRIC=$((1000 + `cat "/sys/class/net/$2/ifindex" 2>/dev/null || echo 0`)) -if [ -x /bin/ip -o -x /sbin/ip ] ; then +case "$1" in + BIND) + ip addr flush dev "$2" label "$2:avahi" + ip addr add "$3"/16 brd 169.254.255.255 label "$2:avahi" scope link dev "$2" proto random + ip route add default dev "$2" metric "$METRIC" scope link proto zeroconf + log "set ipv4ll $3 on iface $2" + ;; - # We have the Linux ip tool from the iproute package + CONFLICT|UNBIND|STOP) + ip route del default dev "$2" metric "$METRIC" scope link + ip addr flush dev "$2" label "$2:avahi" + log "clr ipv4ll on iface $2" + ;; - case "$1" in - BIND) - ip addr flush dev "$2" label "$2:avahi" - ip addr add "$3"/16 brd 169.254.255.255 label "$2:avahi" scope link dev "$2" proto 6 - ip route add default dev "$2" metric "$METRIC" scope link proto zeroconf ||: - ;; - - CONFLICT|UNBIND|STOP) - ip route del default dev "$2" metric "$METRIC" scope link ||: - ip addr del "$3"/16 brd 169.254.255.255 label "$2:avahi" scope link dev "$2" - ;; - - *) - echo "Unknown event $1" >&2 - exit 1 - ;; - esac - -elif [ -x /bin/ifconfig -o -x /sbin/ifconfig ] ; then - - # We have the old ifconfig tool - - case "$1" in - BIND) - ifconfig "$2:avahi" inet "$3" netmask 255.255.0.0 broadcast 169.254.255.255 up - route add default dev "$2:avahi" metric "$METRIC" ||: - ;; - - CONFLICT|STOP|UNBIND) - route del default dev "$2:avahi" metric "$METRIC" ||: - ifconfig "$2:avahi" down - ;; - - *) - echo "Unknown event $1" >&2 - exit 1 - ;; - esac - -else - - echo "No network configuration tool found." >&2 - exit 1 - -fi + *) + log "Unknown event $1 on iface $2" + exit 1 + ;; +esac exit 0 From 151119ef0e3f83291140d544fba4688e336f54a3 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 17 Sep 2024 04:44:50 +0200 Subject: [PATCH 05/39] patches/avahi: fix address cleanup on avahi-autoipd exit Fixes #638 Signed-off-by: Joachim Wiberg --- ...-dispatcher-to-process-event-before-.patch | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 patches/avahi/0001-On-SIGTERM-allow-dispatcher-to-process-event-before-.patch diff --git a/patches/avahi/0001-On-SIGTERM-allow-dispatcher-to-process-event-before-.patch b/patches/avahi/0001-On-SIGTERM-allow-dispatcher-to-process-event-before-.patch new file mode 100644 index 00000000..918c1aab --- /dev/null +++ b/patches/avahi/0001-On-SIGTERM-allow-dispatcher-to-process-event-before-.patch @@ -0,0 +1,46 @@ +From 74ec0f8335f811e4f5becc8bbea4a52af4d3e749 Mon Sep 17 00:00:00 2001 +From: Joachim Wiberg +Date: Tue, 17 Sep 2024 04:08:12 +0200 +Subject: [PATCH] On SIGTERM, allow dispatcher to process event before exiting +Organization: Addiva Elektronik + +On non-systemd systems, like those managed by Finit, when the main PID +used by avahi-autoipd call the dispatcher on reception of SIGTERM, it +may not have time to complete the callout because the main PID exits +almost immediately. + +Even though the dispatcher run as a separate process, when the main PID +is collected by FInit it will unconditionally reap lingering processes +in the same process group. + +Add a small enough sleep to allow the dispatcher to complete the call to +/etc/avahi/avahi-autoipd.action before we exit the main PID. A better +patch would be to add support for the dispatcher to messages the main +PID when it has run the script, but there does not seem to be any such +bidirectional communication available today. + +Signed-off-by: Joachim Wiberg +--- + avahi-autoipd/main.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/avahi-autoipd/main.c b/avahi-autoipd/main.c +index 96c9188..8f7a7e5 100644 +--- a/avahi-autoipd/main.c ++++ b/avahi-autoipd/main.c +@@ -1404,8 +1404,11 @@ static int loop(int iface, uint32_t addr) { + + fail: + +- if (state == STATE_RUNNING || state == STATE_ANNOUNCING) ++ if (state == STATE_RUNNING || state == STATE_ANNOUNCING) { + do_callout(dispatcher, CALLOUT_STOP, iface, addr); ++ /* Don't leave yet, allow dispatcher to process event */ ++ sleep(2); ++ } + + avahi_free(out_packet); + avahi_free(in_packet); +-- +2.43.0 + From add52cdb38051eeacc04972e80cacd4e91472e0b Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 16 Sep 2024 11:11:07 +0200 Subject: [PATCH 06/39] confd: add support for IPv4LL request-address option This patch allows a user to specify a specific 169.254/16 address that the zeroconf client starts with when probing for a free address. If the probe fails it falls back to its default algorithm. Fixes #628 Signed-off-by: Joachim Wiberg --- doc/networking.md | 7 ++ .../skeleton-init-finit.mk | 6 +- src/confd/src/ietf-interfaces.c | 89 ++++++++++++++----- src/confd/yang/confd.inc | 2 +- src/confd/yang/infix-ip@2023-09-14.yang | 68 -------------- src/confd/yang/infix-ip@2024-09-16.yang | 79 ++++++++++++++++ 6 files changed, 157 insertions(+), 94 deletions(-) delete mode 100644 src/confd/yang/infix-ip@2023-09-14.yang create mode 100644 src/confd/yang/infix-ip@2024-09-16.yang diff --git a/doc/networking.md b/doc/networking.md index f276ef67..a800829c 100644 --- a/doc/networking.md +++ b/doc/networking.md @@ -598,6 +598,13 @@ As shown, the link-local IPv4 address is configured with `set autconf enabled true`. The resulting address (169.254.1.3/16) is of type *random* ([ietf-ip.yang][2]). +The IPv4LL client also supports a `request-address` setting which can be +used to "seed" the client's starting address. If the address is free it +will be used, otherwise it falls back to the default algorithm. + + admin@example:/config/interface/eth0/ipv4/> set autoconf request-address 169.254.1.2 + + #### Use of DHCP for IPv4 address assignment ![Using DHCP for IPv4 address assignment](img/ip-address-example-ipv4-dhcp.svg) diff --git a/package/skeleton-init-finit/skeleton-init-finit.mk b/package/skeleton-init-finit/skeleton-init-finit.mk index 0d1be306..c1339e22 100644 --- a/package/skeleton-init-finit/skeleton-init-finit.mk +++ b/package/skeleton-init-finit/skeleton-init-finit.mk @@ -45,8 +45,10 @@ endif ifeq ($(BR2_PACKAGE_AVAHI_AUTOIPD),y) define SKELETON_INIT_FINIT_SET_AVAHI_AUTOIPD - echo "service [2345789] name:zeroconf :%i avahi-autoipd --force-bind --syslog %i -- ZeroConf for %i" \ - > $(FINIT_D)/available/zeroconf@.conf + echo 'service name:zeroconf :%i env:-/etc/default/zeroconf-%i \' >$(FINIT_D)/available/zeroconf@.conf + echo ' kill:5 pid:!/run/avahi-autoipd.%i.pid \'>>$(FINIT_D)/available/zeroconf@.conf + echo ' [2345789] log avahi-autoipd $$ZEROCONF_ARGS %i \'>>$(FINIT_D)/available/zeroconf@.conf + echo ' -- ZeroConf for %i' >>$(FINIT_D)/available/zeroconf@.conf endef SKELETON_INIT_FINIT_POST_INSTALL_TARGET_HOOKS += SKELETON_INIT_FINIT_SET_AVAHI_AUTOIPD endif diff --git a/src/confd/src/ietf-interfaces.c b/src/confd/src/ietf-interfaces.c index 772abbc3..56392f56 100644 --- a/src/confd/src/ietf-interfaces.c +++ b/src/confd/src/ietf-interfaces.c @@ -537,45 +537,89 @@ static int netdag_gen_ipv6_autoconf(struct dagger *net, struct lyd_node *cif, return 0; } +/* + * Check if ipv4 is enabled, only then can autoconf be enabled, in all + * other cases it must be disabled. Since we have multiple settings in + * autoconf, we check if either is modified (diff), in which case we not + * only enable, but also "touch" the Finit service for avahi-autoipd to + * ensure it is (re)started. + * + * Note: in Infix, regardless of the IPv4 configuration, any link-local + * link-local address is disabled when the interface is being used + * as a bridge port. + * + * Also, IPv4LL is not defined for loopback, so always skip there. + */ static int netdag_gen_ipv4_autoconf(struct dagger *net, struct lyd_node *cif, struct lyd_node *dif) { struct lyd_node *ipconf = lydx_get_child(cif, "ipv4"); struct lyd_node *ipdiff = lydx_get_child(dif, "ipv4"); const char *ifname = lydx_get_cattr(dif, "name"); - struct lyd_node *node, *zcip; - struct lydx_diff nd; + struct lyd_node *zcip; + char defaults[64]; FILE *initctl; int err = 0; + if (!strcmp(ifname, "lo")) + return 0; + + /* client defults for this interface, needed in both cases */ + snprintf(defaults, sizeof(defaults), "/etc/default/zeroconf-%s", ifname); + + /* no ipv4 at all, ipv4 selectively disabled, or interface is a bridge port */ if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_bridge_port(cif)) goto disable; - if (lydx_get_op(lydx_get_child(ipdiff, "enabled")) == LYDX_OP_REPLACE) { - node = lydx_get_child(ipconf, "autoconf"); - if (node && lydx_is_enabled(node, "enabled")) - goto enable; - goto disable; - } + /* + * when enabled, we may have been enabled before, but skipped + * for various reasons: was bridge port, ipv4 was disabled... + */ + zcip = lydx_get_child(ipconf, "autoconf"); + if (zcip && lydx_is_enabled(zcip, "enabled")) { + struct lyd_node *node; + const char *addr; + int diff = 0; + FILE *fp; - zcip = lydx_get_child(ipdiff, "autoconf"); - if (!zcip || !(node = lydx_get_child(zcip, "enabled"))) - return 0; + /* check for any changes in this container */ + node = lydx_get_child(ipdiff, "autoconf"); + if (node) { + const struct lyd_node *tmp; - lydx_get_diff(node, &nd); - if (nd.new && !strcmp(nd.val, "true")) { - enable: - initctl = dagger_fopen_next(net, "init", ifname, - 60, "zeroconf-up.sh"); + tmp = lydx_get_child(node, "enabled"); + if (tmp) + diff++; + tmp = lydx_get_child(node, "request-address"); + if (tmp) + diff++; + } + + fp = fopen(defaults, "w"); + if (!fp) { + ERRNO("Failed creating %s, cannot enable IPv4LL on %s", defaults, ifname); + return -EIO; + } + + fprintf(fp, "ZEROCONF_ARGS=\"--force-bind --syslog "); + addr = lydx_get_cattr(zcip, "request-address"); + if (addr) + fprintf(fp, "--start=%s", addr); + fprintf(fp, "\"\n"); + fclose(fp); + + initctl = dagger_fopen_next(net, "init", ifname, 60, "zeroconf-up.sh"); if (!initctl) return -EIO; - fprintf(initctl, - "initctl -bnq enable zeroconf@%s.conf\n", ifname); + /* on enable, or reactivation, it is enough to ensure the service is enabled */ + fprintf(initctl, "initctl -bnq enable zeroconf@%s.conf\n", ifname); + /* on changes to autoconf we must ensure Finit restarts the service */ + if (diff) + fprintf(initctl, "initctl -bnq touch zeroconf@%s.conf\n", ifname); } else { disable: - initctl = dagger_fopen_current(net, "exit", ifname, - 40, "zeroconf-down.sh"); + initctl = dagger_fopen_current(net, "exit", ifname, 40, "zeroconf-down.sh"); if (!initctl) { /* check if in bootstrap (pre gen 0) */ if (errno == EUNATCH) @@ -583,9 +627,8 @@ static int netdag_gen_ipv4_autoconf(struct dagger *net, struct lyd_node *cif, return -EIO; } - fprintf(initctl, - "initctl -bnq disable zeroconf@%s.conf\n", ifname); - + fprintf(initctl, "initctl -bnq disable zeroconf@%s.conf\n", ifname); + fprintf(initctl, "rm -f %s\n", defaults); err = netdag_exit_reload(net); } diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 78b17949..35458c45 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -25,7 +25,7 @@ MODULES=( "ietf-hardware@2018-03-13.yang -e hardware-state" "infix-hardware@2024-04-25.yang" "ieee802-dot1q-types@2022-10-29.yang" - "infix-ip@2023-09-14.yang" + "infix-ip@2024-09-16.yang" "infix-if-type@2024-01-29.yang" "infix-routing@2024-09-20.yang" "ieee802-dot1ab-lldp@2022-03-15.yang" diff --git a/src/confd/yang/infix-ip@2023-09-14.yang b/src/confd/yang/infix-ip@2023-09-14.yang deleted file mode 100644 index 9730b92b..00000000 --- a/src/confd/yang/infix-ip@2023-09-14.yang +++ /dev/null @@ -1,68 +0,0 @@ -module infix-ip { - yang-version 1.1; - namespace "urn:infix:params:xml:ns:yang:infix-ip"; - prefix infix-ip; - - import ietf-interfaces { - prefix if; - } - import ietf-ip { - prefix ip; - } - import ietf-inet-types { - prefix inet; - } - import ietf-yang-types { - prefix yang; - } - - description - "This module augments ietf-ip with an IPv4 link-local autoconf"; - - revision 2023-09-14 { - description - "Added deviations for unsupported parts of ietf-ip."; - } - - revision 2023-04-24 { - description - "Initial revision."; - reference - "RFC 7277: A YANG Data Model for IP Management"; - } - - /* - * Data nodes - */ - augment "/if:interfaces/if:interface/ip:ipv4" { - container autoconf { - description - "Parameters to control the autoconfiguration of IPv4 address."; - - leaf enabled { - type boolean; - default false; - description - "Use a ZeroConf/IPv4LL agent to retrieve an 169.254/16 address."; - reference - "RFC 3927: Dynamic Configuration of IPv4 Link-Local Addresses"; - } - } - } - - deviation "/if:interfaces/if:interface/ip:ipv4/ip:address/ip:subnet/ip:netmask" { - deviate not-supported; - } - - deviation "/if:interfaces/if:interface/ip:ipv4/ip:neighbor" { - deviate not-supported; - } - - deviation "/if:interfaces/if:interface/ip:ipv6/ip:address/ip:status" { - deviate not-supported; - } - - deviation "/if:interfaces/if:interface/ip:ipv6/ip:neighbor" { - deviate not-supported; - } -} diff --git a/src/confd/yang/infix-ip@2024-09-16.yang b/src/confd/yang/infix-ip@2024-09-16.yang new file mode 100644 index 00000000..88a1c93f --- /dev/null +++ b/src/confd/yang/infix-ip@2024-09-16.yang @@ -0,0 +1,79 @@ +module infix-ip { + yang-version 1.1; + namespace "urn:infix:params:xml:ns:yang:infix-ip"; + prefix infix-ip; + + import ietf-interfaces { + prefix if; + } + import ietf-ip { + prefix ip; + } + import ietf-inet-types { + prefix inet; + } + import ietf-yang-types { + prefix yang; + } + + description "This module augments ietf-ip with Infix extensions and deviations."; + + revision 2024-09-16 { + description "Add support for IPv4LL request-address."; + reference "Internal."; + } + revision 2023-09-14 { + description "Added deviations for unsupported parts of ietf-ip."; + reference "Internal."; + } + revision 2023-04-24 { + description "Initial revision."; + reference "RFC 7277: A YANG Data Model for IP Management"; + } + + /* + * Data nodes + */ + augment "/if:interfaces/if:interface/ip:ipv4" { + container autoconf { + description "Parameters to control the autoconfiguration of IPv4 address."; + reference "RFC 3927: Dynamic Configuration of IPv4 Link-Local Addresses"; + + leaf enabled { + description "Use a ZeroConf/IPv4LL agent to retrieve an 169.254/16 address."; + type boolean; + } + + leaf request-address { + description "Try to acquire the specified IP address, if available. + + With this setting the IPv4LL client will start by + requesting this address. However, if it is not + available it falls back to the default algorithm."; + type inet:ipv4-address; + must "substring(., 1, 7) = '169.254'" { + error-message "Must be from the IPv4LL range 169.254.0.0/16."; + } + must "not(substring(., string-length(.) - 1, 2) = '.0' or substring(., string-length(.) - 3, 4) = '.255')" { + error-message "Addresses ending in .0 or .255 are reserved."; + } + } + } + } + + deviation "/if:interfaces/if:interface/ip:ipv4/ip:address/ip:subnet/ip:netmask" { + deviate not-supported; + } + + deviation "/if:interfaces/if:interface/ip:ipv4/ip:neighbor" { + deviate not-supported; + } + + deviation "/if:interfaces/if:interface/ip:ipv6/ip:address/ip:status" { + deviate not-supported; + } + + deviation "/if:interfaces/if:interface/ip:ipv6/ip:neighbor" { + deviate not-supported; + } +} From ec6d350bc1c7e9932afa81f74e3cd49da265cd73 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 17 Sep 2024 04:50:22 +0200 Subject: [PATCH 07/39] test/infamy: minor, check return value, and pep8 fixes Signed-off-by: Joachim Wiberg --- test/infamy/netconf.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/test/infamy/netconf.py b/test/infamy/netconf.py index 7bf5908b..10462401 100644 --- a/test/infamy/netconf.py +++ b/test/infamy/netconf.py @@ -287,8 +287,8 @@ class Device(Transport): def get_data(self, xpath=None, parse=True): """RPC to fetch operational data""" - if parse==False: - return self._get_data(xpath,parse) + if parse is False: + return self._get_data(xpath, parse) data = self._get_data(xpath) if not data: @@ -391,28 +391,36 @@ class Device(Transport): def get_iface(self, name): """Fetch target dict for iface and extract param from JSON""" content = self.get_data(iface.get_iface_xpath(name)) - interface=content.get("interfaces", {}).get("interface", None) + interface = content.get("interfaces", {}).get("interface", None) if interface is None: return None - # Restconf (rousette) address by id and netconf (netopper2) address by name + # Restconf (rousette) address by id, otherwise (netopper2) by name return interface[name] def delete_xpath(self, xpath): # Split out the model and the container from xpath' pattern = r"^/(?P[^:]+):(?P[^/]+)" match = re.search(pattern, xpath) + if not match: + raise ValueError(f"Failed parsing xpath:{xpath}") + module = match.group('module') modpath = f"/{match.group('module')}:{match.group('path')}" old = self.get_config_dict(modpath) new = copy.deepcopy(old) + libyang.xpath_del(new, xpath) + mod = self.ly.get_module(module) oldd = mod.parse_data_dict(old, no_state=True) newd = mod.parse_data_dict(new, no_state=True) + lyd = oldd.diff(newd) + if lyd is None: + raise ValueError(f"Failed generating diff for xpath:{xpath}") return self.put_config(lyd.print_mem("xml", with_siblings=True, pretty=False)) From 476c2ebe4034e84a344e4b62ff7743c3b1b90ac2 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 17 Sep 2024 04:51:53 +0200 Subject: [PATCH 08/39] test/case: update ipv4-autoconf with new test passes - Add check for specific 169.254/16 adddress (new feature) - Add check for disabling autoconf on interface (regression test #638) Signed-off-by: Joachim Wiberg --- .../ietf_interfaces/ipv4_autoconf/Readme.adoc | 13 ++-- .../ietf_interfaces/ipv4_autoconf/test.py | 68 ++++++++++++++++--- 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/test/case/ietf_interfaces/ipv4_autoconf/Readme.adoc b/test/case/ietf_interfaces/ipv4_autoconf/Readme.adoc index 208b8b85..7781348f 100644 --- a/test/case/ietf_interfaces/ipv4_autoconf/Readme.adoc +++ b/test/case/ietf_interfaces/ipv4_autoconf/Readme.adoc @@ -1,10 +1,12 @@ -=== IPv4 linklocal +=== IPv4 link-local + ==== Description Verifies that linklocal (ZeroConf) work as expected ==== Topology + ifdef::topdoc[] -image::../../test/case/ietf_interfaces/ipv4_autoconf/topology.png[IPv4 linklocal topology] +image::../../test/case/ietf_interfaces/ipv4_autoconf/topology.png[IPv4 link-local topology] endif::topdoc[] ifndef::topdoc[] ifdef::testgroup[] @@ -14,11 +16,14 @@ ifndef::testgroup[] image::topology.png[IPv4 linklocal topology] endif::testgroup[] endif::topdoc[] + ==== Test sequence . Initialize . Configure an interface with IPv4 ZeroConf IP -. Wait for linklocal address on interface +. Verify link local address exist on target:mgmt +. Configure target:mgmt with a specific IPv4 ZeroConf IP +. Verify target:mgmt has link local address 169.254.42.42 +. Remove IPv4 link-local addresses from target:mgmt <<< - diff --git a/test/case/ietf_interfaces/ipv4_autoconf/test.py b/test/case/ietf_interfaces/ipv4_autoconf/test.py index 7d048d8e..1130b237 100755 --- a/test/case/ietf_interfaces/ipv4_autoconf/test.py +++ b/test/case/ietf_interfaces/ipv4_autoconf/test.py @@ -3,24 +3,46 @@ # PC ---- e0: 10.0.0.2 # """ -IPv4 linklocal +IPv4 link-local -Verifies that linklocal (ZeroConf) work as expected +Verifies that link-local (IPv4LL/ZeroConf) address assignment work as +expected. Checks random address, the request-address setting, and +address removal on autoconf disable. """ +import ipaddress import infamy -import time import infamy.iface from infamy.util import until -def has_linklocal(target, iface): - """Check if interface as a linklocal address""" + +def has_linklocal(target, iface, request=None): + """Check if interface as a link-local address""" addrs = infamy.iface.get_ipv4_address(target, iface) if not addrs: return False for addr in addrs: if addr['origin'] == "random": - return True + if request is None: + print(f"Got IP address {addr['ip']}") + return True + if addr['ip'] == request: + print(f"Got requested IP address {addr['ip']}") + return True + return False + + +def no_linklocal(target, iface): + """Check if interface has no link-local address in the 169.254/16 range""" + addrs = infamy.iface.get_ipv4_address(target, iface) + if not addrs: + return True + for addr in addrs: + ip = ipaddress.IPv4Address(addr['ip']) + if ip in ipaddress.IPv4Network('169.254.0.0/16'): + return False + return True + with infamy.Test() as test: with test.step("Initialize"): @@ -46,7 +68,37 @@ with infamy.Test() as test: } }) - with test.step("Wait for linklocal address on interface"): - until(lambda: has_linklocal(target, tport), attempts=30) + with test.step("Verify link local address exist on target:mgmt"): + until(lambda: has_linklocal(target, tport), attempts=30) + + with test.step("Configure target:mgmt with a specific IPv4 ZeroConf IP"): + _, tport = env.ltop.xlate("target", "data") + + target.put_config_dict("ietf-interfaces", { + "interfaces": { + "interface": [ + { + "name": tport, + "enabled": True, + "ipv4": { + "autoconf": { + "enabled": True, + "request-address": "169.254.42.42" + } + } + } + ] + } + }) + + with test.step("Verify target:mgmt has link local address 169.254.42.42"): + until(lambda: has_linklocal(target, tport, request="169.254.42.42"), + attempts=30) + + with test.step("Remove IPv4 link-local addresses from target:mgmt"): + xpath = f"/ietf-interfaces:interfaces/interface[name='{tport}']" \ + "/ietf-ip:ipv4/infix-ip:autoconf" + target.delete_xpath(xpath) + until(lambda: no_linklocal(target, tport), attempts=30) test.succeed() From 10ef7c5fda943ea9d4fc9d416e02444f7a6eee8f Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 17 Sep 2024 06:11:37 +0200 Subject: [PATCH 09/39] doc: update ChangeLog with recent IPv4LL (autoconf) changes Signed-off-by: Joachim Wiberg --- doc/ChangeLog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 210e1fed..84213a72 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -48,6 +48,8 @@ All notable changes to the project are documented in this file. regression introduced in v24.06.0 - Fix #635: OSPF: all router neighbors reported as neighbor on every interface +- Fix #638: Disabling IPv4LL (autoconf) on an interface does not clean + up 169.254/16 addresses - Spellcheck path to `/var/lib/containers` when unpacking OCI archives on container upgrade - The timeout before giving up on loading the `startup-config` at boot From e483d295dac7201577ca371811f797b29f16086b Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 17 Sep 2024 06:17:53 +0200 Subject: [PATCH 10/39] statd: also silence JSONDecodeError on check=False In d616623a an attempt to silence bogus return values from mctl, when multicast snooping is disabled on a bridge. That helped get data back for operational to produce `show interfaces` *with* a bridge and ports but still emitted bogus log warnings. This patch addresses that by checking 'check' on JSONDcodeError as well before emitting any log error. Signed-off-by: Joachim Wiberg --- src/statd/python/yanger/yanger.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/statd/python/yanger/yanger.py b/src/statd/python/yanger/yanger.py index c3521b34..4f9563b5 100755 --- a/src/statd/python/yanger/yanger.py +++ b/src/statd/python/yanger/yanger.py @@ -137,8 +137,9 @@ def run_json_cmd(cmd, testfile, default=None, check=True): return default raise except json.JSONDecodeError as err: - logger.error(f"failed parsing JSON output of command: {' '.join(cmd)}" - f", error: {err}") + if check is True: + logger.error("failed parsing JSON output of command: " + f"{' '.join(cmd)}, error: {err}") if default is not None: return default raise From 0a06fc0571fbfeb25855f1741f577e6579465434 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 17 Sep 2024 06:52:22 +0200 Subject: [PATCH 11/39] confd: minor, logging and coding style - Remove duplicate logs due to logger logging also to stderr - Fix coding style in new test-override-startup code path Signed-off-by: Joachim Wiberg --- src/confd/bin/load | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/confd/bin/load b/src/confd/bin/load index 57dbce2b..cdc985a7 100755 --- a/src/confd/bin/load +++ b/src/confd/bin/load @@ -28,12 +28,12 @@ perms() note() { - logger -s -I $$ -p user.notice -t load "$*" + logger -I $$ -p user.notice -t load "$*" } err() { - logger -s -I $$ -p user.error -t load "$*" + logger -I $$ -p user.error -t load "$*" } @@ -60,12 +60,12 @@ config=$1 if [ -f "/mnt/aux/test-mode" ] && [ "$config" = "startup-config" ]; then - if [ -f "/mnt/aux/test-override-startup" ]; then - rm -f "/mnt/aux/test-override-startup" - else + if [ -f "/mnt/aux/test-override-startup" ]; then + rm -f "/mnt/aux/test-override-startup" + else note "Test mode detected, switching to test-config" config="test-config" - fi + fi fi if [ -f "$config" ]; then From 430948df0607910ee0e95e1226bb83793932e0d7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 17 Sep 2024 07:06:59 +0200 Subject: [PATCH 12/39] confd: minor, fix global variable shadowing warnings Follow-up to cfd95dc30, which made 'struct confd confd' a global variable in core.h. Signed-off-by: Joachim Wiberg --- src/confd/src/core.c | 10 +++++----- src/confd/src/core.h | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/confd/src/core.c b/src/confd/src/core.c index 050d50c7..9ba6ebf5 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: BSD-3-Clause */ #include - #include "core.h" struct confd confd; + uint32_t core_hook_prio(void) { static uint32_t hook_prio = CB_PRIO_PASSIVE; @@ -177,10 +177,10 @@ err: void sr_plugin_cleanup_cb(sr_session_ctx_t *session, void *priv) { - struct confd *confd = (struct confd *)priv; + struct confd *ptr = (struct confd *)priv; - sr_unsubscribe(confd->sub); - sr_unsubscribe(confd->fsub); - json_decref(confd->root); + sr_unsubscribe(ptr->sub); + sr_unsubscribe(ptr->fsub); + json_decref(ptr->root); closelog(); } diff --git a/src/confd/src/core.h b/src/confd/src/core.h index ea4a7d63..ae25a7d3 100644 --- a/src/confd/src/core.h +++ b/src/confd/src/core.h @@ -100,11 +100,11 @@ int core_startup_save (sr_session_ctx_t *, uint32_t, const char *, const ch static inline int register_change(sr_session_ctx_t *session, const char *module, const char *xpath, int flags, sr_module_change_cb cb, void *arg, sr_subscription_ctx_t **sub) { - struct confd *confd = (struct confd *)arg; + struct confd *ptr = (struct confd *)arg; int rc; if (!flags) { - sr_module_change_subscribe(confd->session, module, xpath, core_pre_hook, NULL, + sr_module_change_subscribe(ptr->session, module, xpath, core_pre_hook, NULL, 0, SR_SUBSCR_PASSIVE, sub); } @@ -123,9 +123,9 @@ static inline int register_change(sr_session_ctx_t *session, const char *module, * the startup-save hook for running -> startup copying. */ if (!flags) { - sr_module_change_subscribe(confd->session, module, xpath, core_post_hook, NULL, + sr_module_change_subscribe(ptr->session, module, xpath, core_post_hook, NULL, core_hook_prio(), SR_SUBSCR_PASSIVE, sub); - sr_module_change_subscribe(confd->startup, module, xpath, core_startup_save, NULL, + sr_module_change_subscribe(ptr->startup, module, xpath, core_startup_save, NULL, core_hook_prio(), SR_SUBSCR_PASSIVE, sub); } From fd8826c3f1703a9a8b96d88f90141c6dde4fddde Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 17 Sep 2024 07:12:29 +0200 Subject: [PATCH 13/39] confd: initialize sysrepo startup datastore at boot Since /etc is not retained across reboots, the sysrepo startup datastore is not used to bootstrap the Infix. Instead, /cfg/startup-config.cfg is used and loaded directly to the running datastore. For some use-cases when operating directly on sysrepo this causes a lot of confusion because startup == factory ds until any user calls the CLI command `copy running-config startup-confg`. This patch fixes this initial "glitch" by loading the starup datastore at boot on successful loading of startup-config to unning. To prevent any unintentional "auto-save" of, e.g., failure-config to startup-config, we make confd aware of this "sync" operation during bootstap. Fixes #625 Signed-off-by: Joachim Wiberg --- doc/ChangeLog.md | 2 ++ src/confd/bin/load | 5 ++++- src/confd/src/core.c | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 84213a72..4c5351fd 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -46,6 +46,8 @@ All notable changes to the project are documented in this file. bridges without multicast snooping - Fix #623: CLI command `container upgrade NAME` does not work, regression introduced in v24.06.0 +- Fix #625: initialize sysrepo startup datastore at boot. Improves + usability when working directly against the sysrepo datastores - Fix #635: OSPF: all router neighbors reported as neighbor on every interface - Fix #638: Disabling IPv4LL (autoconf) on an interface does not clean diff --git a/src/confd/bin/load b/src/confd/bin/load index cdc985a7..242fe662 100755 --- a/src/confd/bin/load +++ b/src/confd/bin/load @@ -100,7 +100,7 @@ if [ ! -f "$fn" ]; then fi note "Loading $config ..." -if ! $sysrepocfg -v3 -I"$fn" -f json; then +if ! $sysrepocfg -v2 -I"$fn" -f json; then case "$config" in startup-config) err "Failed loading $fn, reverting to Fail Secure mode!" @@ -126,6 +126,9 @@ if ! $sysrepocfg -v3 -I"$fn" -f json; then esac exit 1 +else + note "Success, syncing with startup datastore." + $sysrepocfg -v2 -d startup -C running fi note "Loaded $fn successfully." diff --git a/src/confd/src/core.c b/src/confd/src/core.c index 9ba6ebf5..183424cc 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -16,6 +16,10 @@ uint32_t core_hook_prio(void) int core_startup_save(sr_session_ctx_t *session, uint32_t sub_id, const char *module, const char *xpath, sr_event_t event, unsigned request_id, void *priv) { + /* skip in bootstrap, triggered by load script to initialize startup datastore */ + if (systemf("runlevel >/dev/null 2>&1")) + return SR_ERR_OK; + if (systemf("sysrepocfg -X/cfg/startup-config.cfg -d startup -f json")) return SR_ERR_SYS; From cc8e31f74f555a0fa1e214672dd50dddc6c30b3d Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 19 Sep 2024 06:02:42 +0200 Subject: [PATCH 14/39] board/common: adjust metric on IPv4LL and DHCP routes to match Frr Apply a distance adjustment to routes retrieved from IPv4LL/ZeroConf and DHCP to ensure that Frr (Zebra) can compare other protocol routes, e.g., static. Without this Frr will consider all routes read from the kernel to have a protocol distance of 0, meaning better than static routes that have a distance of 1. Initial proposal to fix issue #640 Signed-off-by: Joachim Wiberg --- .../common/rootfs/etc/avahi/avahi-autoipd.action | 15 ++++++++++++--- .../common/rootfs/usr/share/udhcpc/default.script | 8 ++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/board/common/rootfs/etc/avahi/avahi-autoipd.action b/board/common/rootfs/etc/avahi/avahi-autoipd.action index 16df3899..239d7853 100755 --- a/board/common/rootfs/etc/avahi/avahi-autoipd.action +++ b/board/common/rootfs/etc/avahi/avahi-autoipd.action @@ -7,6 +7,10 @@ # STOP: The daemon is terminating # $2 interface name # $3 IP adddress +# +# For details on the distance adjustment below, please see the following +# section in the Frr documentation on distance + prefence => to metric. +# https://docs.frrouting.org/en/stable-8.0/zebra.html#administrative-distance PATH="$PATH:/usr/bin:/usr/sbin:/bin:/sbin" @@ -15,9 +19,14 @@ log() logger -I $$ -t zeroconf -p user.notice "$*" } -# Use a different metric for each interface, so that we can set -# identical routes to multiple interfaces. -METRIC=$((1000 + `cat "/sys/class/net/$2/ifindex" 2>/dev/null || echo 0`)) +# Use a different metric for each interface as tie breaker, to ensure we +# can set identical routes to multiple interfaces. +IFINDEX=$(cat "/sys/class/net/$2/ifindex" 2>/dev/null || echo 0) + +# Distance adjustment to ensure that static routes set by Zebra and any +# routes learned from DHCP take precedence over these interface routes. +# A distance of 255 ensures these routes are never redistributed. +METRIC=$(((255 << 24) + 1000 + IFINDEX)) case "$1" in BIND) diff --git a/board/common/rootfs/usr/share/udhcpc/default.script b/board/common/rootfs/usr/share/udhcpc/default.script index 3a59e67e..73e4c78b 100755 --- a/board/common/rootfs/usr/share/udhcpc/default.script +++ b/board/common/rootfs/usr/share/udhcpc/default.script @@ -1,5 +1,9 @@ #!/bin/sh # This script expect a system with resolvconf (openresolv) and iproute2 +# +# For details on the distance adjustment below, please see the following +# section in the Frr documentation on distance + prefence => to metric. +# https://docs.frrouting.org/en/stable-8.0/zebra.html#administrative-distance [ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1 @@ -11,6 +15,10 @@ NTPFILE="/run/chrony/dhcp-sources.d/${interface}.sources" [ -n "$subnet" ] || subnet=32 [ -n "$metric" ] || metric=100 +# Distance adjustment to ensure that static routes set by Zebra +# can take precedence over routes learned from DHCP. +metric=$(((5 << 24) + metric)) + # Handle stateful DHCPv6 like DHCPv4 [ -n "$ipv6" ] && ip="$ipv6/128" From 84c373ef631aa767562d69a5b65d7b53bc36bd07 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 19 Sep 2024 06:01:45 +0200 Subject: [PATCH 15/39] statd: refactor metric in 'show routes' to distance/metric Signed-off-by: Joachim Wiberg --- src/statd/python/cli_pretty/cli_pretty.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 686acbc7..f56a721a 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -80,7 +80,7 @@ class Route: def __init__(self,data,ip): self.data = data self.prefix = data.get(f'ietf-{ip}-unicast-routing:destination-prefix', '') - self.protocol = data.get('source-protocol','')[14:] + self.protocol = data.get('source-protocol', '').split(':')[-1] self.pref = data.get('route-preference','') self.next_hop = [] next_hop_list=get_json_data(None, self.data, 'next-hop', 'next-hop-list') @@ -105,10 +105,22 @@ class Route: self.next_hop.append(special) else: self.next_hop.append("unspecified") + + def get_distance_and_metric(self): + if isinstance(self.pref, int): + distance = self.pref + metric = 0 + else: + distance, metric = 0, 0 + + return distance, metric + def print(self): - row = f"{self.prefix:<{PadRoute.prefix}}" + distance, metric = self.get_distance_and_metric() + pref = f"{distance}/{metric}" + row = f"{self.prefix:<{PadRoute.prefix}}" row += f"{self.next_hop[0]:<{PadRoute.next_hop}}" - row += f"{self.pref:>{PadRoute.pref}} " + row += f"{pref:>{PadRoute.pref}} " row += f"{self.protocol:<{PadRoute.protocol}}" print(row) for nh in self.next_hop[1:]: From d575538d4a6d68a30d1afb4f05b216b8eb2705fe Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 19 Sep 2024 06:08:28 +0200 Subject: [PATCH 16/39] statd: minor, pep8 fixes Signed-off-by: Joachim Wiberg --- src/statd/python/cli_pretty/cli_pretty.py | 52 ++++++++++++++++------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index f56a721a..01495a3b 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -4,24 +4,28 @@ import argparse import sys import re + class Pad: iface = 16 proto = 11 state = 12 data = 41 + class PadMdb: bridge = 7 vlan = 6 group = 20 ports = 45 + class PadRoute: prefix = 30 protocol = 10 next_hop = 30 pref = 8 + class PadSoftware: name = 10 date = 25 @@ -35,6 +39,7 @@ class PadUsbPort: name = 20 state = 10 + class Decore(): @staticmethod def decorate(sgr, txt, restore="0"): @@ -60,6 +65,7 @@ class Decore(): def underline(txt): return Decore.decorate("4", txt, "24") + def get_json_data(default, indata, *args): data = indata for arg in args: @@ -76,19 +82,20 @@ def remove_yang_prefix(key): return parts[1] return key + class Route: - def __init__(self,data,ip): + def __init__(self, data, ip): self.data = data self.prefix = data.get(f'ietf-{ip}-unicast-routing:destination-prefix', '') self.protocol = data.get('source-protocol', '').split(':')[-1] - self.pref = data.get('route-preference','') + self.pref = data.get('route-preference', '') self.next_hop = [] - next_hop_list=get_json_data(None, self.data, 'next-hop', 'next-hop-list') + next_hop_list = get_json_data(None, self.data, 'next-hop', 'next-hop-list') if next_hop_list: for nh in next_hop_list["next-hop"]: - if(nh.get(f"ietf-{ip}-unicast-routing:address")): + if nh.get(f"ietf-{ip}-unicast-routing:address"): self.next_hop.append(nh[f"ietf-{ip}-unicast-routing:address"]) - elif(nh.get("outgoing-interface")): + elif nh.get("outgoing-interface"): self.next_hop.append(nh["outgoing-interface"]) else: self.next_hop.append("unspecified") @@ -124,10 +131,11 @@ class Route: row += f"{self.protocol:<{PadRoute.protocol}}" print(row) for nh in self.next_hop[1:]: - row = f"{'':<{PadRoute.prefix}}" + row = f"{'':<{PadRoute.prefix}}" row += f"{nh:<{PadRoute.next_hop}}" print(row) + class Software: """Software bundle class """ def __init__(self, data): @@ -443,6 +451,7 @@ class Iface: row += f"{ports}" print(row) + def find_iface(_ifaces, name): for _iface in [Iface(data) for data in _ifaces]: if _iface.name == name: @@ -450,8 +459,11 @@ def find_iface(_ifaces, name): return False + def version_sort(iface): - return [int(x) if x.isdigit() else x for x in re.split(r'(\d+)', iface['name'])] + return [int(x) if x.isdigit() else x for x in re.split(r'(\d+)', + iface['name'])] + def print_interface(iface): iface.pr_name() @@ -459,6 +471,7 @@ def print_interface(iface): iface.pr_proto_ipv4() iface.pr_proto_ipv6() + def pr_interface_list(json): hdr = (f"{'INTERFACE':<{Pad.iface}}" f"{'PROTOCOL':<{Pad.proto}}" @@ -467,8 +480,8 @@ def pr_interface_list(json): print(Decore.invert(hdr)) - ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"], key=version_sort) - + ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"], + key=version_sort) iface = find_iface(ifaces, "lo") if iface: print_interface(iface) @@ -496,6 +509,7 @@ def pr_interface_list(json): continue print_interface(iface) + def show_interfaces(json, name): if name: if not json.get("ietf-interfaces:interfaces"): @@ -509,16 +523,19 @@ def show_interfaces(json, name): iface.pr_iface() else: if not json.get("ietf-interfaces:interfaces"): - print(f"Error, top level \"ietf-interfaces:interfaces\" missing") + print("Error, top level \"ietf-interfaces:interfaces\" missing") sys.exit(1) pr_interface_list(json) + def show_bridge_mdb(json): header_printed = False if not json.get("ietf-interfaces:interfaces"): - print(f"Error, top level \"ietf-interfaces:interface\" missing") + print("Error, top level \"ietf-interfaces:interface\" missing") sys.exit(1) - ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"], key=version_sort) + + ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"], + key=version_sort) for iface in [Iface(data) for data in ifaces]: if iface.type != "infix-if-type:bridge": continue @@ -532,25 +549,29 @@ def show_bridge_mdb(json): iface.pr_mdb(iface.name) iface.pr_vlans_mdb(iface.name) + def show_routing_table(json, ip): if not json.get("ietf-routing:routing"): - print(f"Error, top level \"ietf-routing:routing\" missing") + print("Error, top level \"ietf-routing:routing\" missing") sys.exit(1) + hdr = (f"{'PREFIX':<{PadRoute.prefix}}" f"{'NEXT-HOP':<{PadRoute.next_hop}}" f"{'PREF':>{PadRoute.pref}} " f"{'PROTOCOL':<{PadRoute.protocol}}") print(Decore.invert(hdr)) - for rib in get_json_data({}, json, 'ietf-routing:routing','ribs', 'rib'): + for rib in get_json_data({}, json, 'ietf-routing:routing', 'ribs', 'rib'): if rib["name"] != ip: - continue; + continue + routes = get_json_data(None, rib, "routes", "route") if routes: for r in routes: route = Route(r, ip) route.print() + def find_slot(_slots, name): for _slot in [Software(data) for data in _slots]: if _slot.name == name: @@ -558,6 +579,7 @@ def find_slot(_slots, name): return False + def show_software(json, name): if not json.get("ietf-system:system-state", "infix-system:software"): print("Error, cannot find infix-system:software") From 445ca0a9d60e57d1fd725ab448ee7a94f27fec6f Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 19 Sep 2024 05:18:25 +0200 Subject: [PATCH 17/39] board/common: restore 'show' script from 493be97 sync with present The intention is to provide a similar set of commands as klish admin-exec. However, instead of trying to make the Linux tools more accessible, we now make the Infix tooling more accessible. E.g., instead of "show ip route" mapping to "ip route show", we map it to: sysrepocfg -f json -X -d operational -x "/ietf-routing:routing/ribs" | \ /usr/libexec/statd/cli-pretty "show-routing-table" As a spin-off we alos get a much more readable infix.xml for klish: - Relocate logic to shell script wrapper 'show' - Introduce Cisco style "show ip route" and "show ipv6 route" Signed-off-by: Joachim Wiberg --- board/common/rootfs/usr/bin/show | 366 +++++++++++++++++++++++++++ src/klish-plugin-infix/xml/infix.xml | 30 ++- 2 files changed, 389 insertions(+), 7 deletions(-) create mode 100755 board/common/rootfs/usr/bin/show diff --git a/board/common/rootfs/usr/bin/show b/board/common/rootfs/usr/bin/show new file mode 100755 index 00000000..b83216a2 --- /dev/null +++ b/board/common/rootfs/usr/bin/show @@ -0,0 +1,366 @@ +#!/bin/sh +# Displays basic information about the system +# shellcheck disable=SC2048,SC2086 +. /etc/os-release + +bopt="-c" +opt="-br" +all="" +plain="" + +h1() +{ + STR="$*" + if [ -n "$plain" ]; then + echo "$STR" | tr '[:lower:]' '[:upper:]' + else + printf "\033[7m%-${COLUMNS}s\033[0m\n" "$STR" + fi +} + +h2() +{ + STR="$*" + if [ -n "$plain" ]; then + echo "$STR" + echo "$STR" | sed 's/./-/g' + else + printf "\033[1m%-${COLUMNS}s\033[0m\n" "$STR" + fi +} + +dm() +{ + if [ -n "$plain" ]; then + echo "${*}" + else + printf "\033[2m%s\033[0m\n" "$*" + fi +} + +ul() +{ + if [ -n "$plain" ]; then + printf "__%s__" "$*" + else + printf "\033[4%s\033[0m" "$*" + fi +} + +em() +{ + if [ -n "$plain" ]; then + printf "**%s**" "$*" + else + printf "\033[5m%s\033[0m" "$*" + fi +} + +usage() +{ + cat < - if [ "$KLISH_PARAM_ip" != "ipv4" ]; then - if [ "$KLISH_PARAM_ip" != "ipv6" ]; then - KLISH_PARAM_ip=ipv4 - fi - fi - sysrepocfg -f json -X -d operational -x "/ietf-routing:routing/ribs" | \ - /usr/libexec/statd/cli-pretty "show-routing-table" -i "$KLISH_PARAM_ip" + case $KLISH_PARAM_ip in + ipv4) + KLISH_PARAM_ip=ip + ;; + ipv6) + KLISH_PARAM_ip=ipv6 + ;; + esac + show -p $KLISH_PARAM_ip route @@ -383,6 +385,20 @@ mctl + + + show -p ip route + + + + + + ip -color -6 addr + + + + show -p ipv6 route + From cf0cfc2dad1812ecd188e0ea5388b2dc3582c76d Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Fri, 20 Sep 2024 15:12:03 +0200 Subject: [PATCH 18/39] confd: clarify route preference in DHCP client model Signed-off-by: Joachim Wiberg --- src/confd/yang/confd.inc | 2 +- ...2.yang => infix-dhcp-client@2024-09-20.yang} | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) rename src/confd/yang/{infix-dhcp-client@2024-04-12.yang => infix-dhcp-client@2024-09-20.yang} (87%) diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 35458c45..4c6e2981 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -30,7 +30,7 @@ MODULES=( "infix-routing@2024-09-20.yang" "ieee802-dot1ab-lldp@2022-03-15.yang" "infix-lldp@2023-08-23.yang" - "infix-dhcp-client@2024-04-12.yang" + "infix-dhcp-client@2024-09-20.yang" "infix-meta@2024-06-19.yang" "infix-system@2024-09-13.yang" "infix-services@2024-05-30.yang" diff --git a/src/confd/yang/infix-dhcp-client@2024-04-12.yang b/src/confd/yang/infix-dhcp-client@2024-09-20.yang similarity index 87% rename from src/confd/yang/infix-dhcp-client@2024-04-12.yang rename to src/confd/yang/infix-dhcp-client@2024-09-20.yang index 9991861f..9d59be45 100644 --- a/src/confd/yang/infix-dhcp-client@2024-04-12.yang +++ b/src/confd/yang/infix-dhcp-client@2024-09-20.yang @@ -10,6 +10,11 @@ module infix-dhcp-client { contact "kernelkit@googlegroups.com"; description "This module implements an IPv4 DHCP client"; + revision 2024-09-20 { + description "Routes are installed in Frr (staticd), clarify preference + vs metric and adjust default preference 100 -> 5."; + reference "internal"; + } revision 2024-04-12 { description "Adjust DHCP client hostname option, max 64 charachters."; reference "internal"; @@ -29,7 +34,7 @@ module infix-dhcp-client { typedef route-preference { type uint32; - description "This type is used for route preferences (metric)."; + description "This type is used for selecting route preference (distance)."; } typedef dhcp-options { @@ -149,12 +154,12 @@ module infix-dhcp-client { } leaf route-preference { type route-preference; - default 100; + default 5; description - "The metric any route is installed with, both option 33 and 121. - The default (100) is based on a plain Ethernet interface, the - interface type determines this, e.g. a WiFi interface would be - metric 500."; + "The preference (administrative distance) that all DHCP routes are + installed with, option 3, 33 and 121. The default preferfence (5) + is higher (less worth) than static routes, but lower than those + learned via dynamic routing protocols, like OSPF."; } } } From d5418b019fb9a1d7080fd8bd6784b31ce7a3c402 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 22 Sep 2024 09:05:42 +0200 Subject: [PATCH 19/39] confd: switch back to ietf type for route source-protocol With the redesign of the operational rib routes, using vtysh 'show ip route' instead of the kernel 'ip route' as source, we no longer need the infix source protocol. Signed-off-by: Joachim Wiberg --- src/confd/yang/confd.inc | 2 +- ...-20.yang => infix-routing@2024-09-21.yang} | 39 ++++--------------- 2 files changed, 9 insertions(+), 32 deletions(-) rename src/confd/yang/{infix-routing@2024-09-20.yang => infix-routing@2024-09-21.yang} (95%) diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 4c6e2981..8b96b08d 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -27,7 +27,7 @@ MODULES=( "ieee802-dot1q-types@2022-10-29.yang" "infix-ip@2024-09-16.yang" "infix-if-type@2024-01-29.yang" - "infix-routing@2024-09-20.yang" + "infix-routing@2024-09-21.yang" "ieee802-dot1ab-lldp@2022-03-15.yang" "infix-lldp@2023-08-23.yang" "infix-dhcp-client@2024-09-20.yang" diff --git a/src/confd/yang/infix-routing@2024-09-20.yang b/src/confd/yang/infix-routing@2024-09-21.yang similarity index 95% rename from src/confd/yang/infix-routing@2024-09-20.yang rename to src/confd/yang/infix-routing@2024-09-21.yang index 4e8ccb75..cd1fb570 100644 --- a/src/confd/yang/infix-routing@2024-09-20.yang +++ b/src/confd/yang/infix-routing@2024-09-21.yang @@ -24,6 +24,10 @@ module infix-routing { import ietf-routing-types { prefix rt-types; } + revision 2024-09-21 { + description "Drop RIB source-protocol deviation for standard, add kernel proto."; + reference "internal"; + } revision 2024-09-20 { description "Deviate to only show supported routing types"; reference "internal"; @@ -51,6 +55,7 @@ module infix-routing { description "Initial revision."; reference "internal"; } + /* General routing */ deviation "/ietf-r:routing/ietf-r:interfaces" { description "Initial limitation"; @@ -66,28 +71,10 @@ module infix-routing { deviate not-supported; } - identity infix-source-protocol { - description "Infix naming of source protocols"; - } identity kernel { - description "Kernel added route"; - base infix-source-protocol; - } - identity ospf { - description "OSPF added route"; - base infix-source-protocol; - } - identity dhcp { - description "DHCP added route"; - base infix-source-protocol; - } - identity zeroconf { - description "ZeroConf added route"; - base infix-source-protocol; - } - identity ra { - description "RA IPv6 route"; - base infix-source-protocol; + base ietf-r:routing-protocol; + description + "'Kernel' routing pseudo-protocol."; } typedef infix-distribute-protocol { @@ -104,15 +91,6 @@ module infix-routing { } } - deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:routes/ietf-r:route/ietf-r:source-protocol" { - description "Use type names from iproute2"; - deviate replace { - type identityref { - base infix-source-protocol; - } - } - } - identity infix-routing-type { description "Infix routing type"; } @@ -124,7 +102,6 @@ module infix-routing { identity static { base ietf-r:static; base infix-routing-type; - base infix-source-protocol; description "Static route"; } deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ietf-r:type" { From d33249512eca2034140e9e459b7d22a9679f6bdb Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 22 Sep 2024 11:01:41 +0200 Subject: [PATCH 20/39] statd: slight refactor of ospf-status - Ignore stderr output (ospfd not running) - Import run_json_cmd() from yanger - Ensure deafault empty object + arrays Signed-off-by: Joachim Wiberg --- src/statd/python/ospf_status/ospf_status.py | 41 +++++++++++++++------ src/statd/python/yanger/yanger.py | 9 +++-- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/statd/python/ospf_status/ospf_status.py b/src/statd/python/ospf_status/ospf_status.py index 985473f7..09bcff84 100755 --- a/src/statd/python/ospf_status/ospf_status.py +++ b/src/statd/python/ospf_status/ospf_status.py @@ -6,21 +6,38 @@ # This makes the parsing for the operational parts of YANG model more easy # +import sys import json import subprocess -def main(): - iface_out=subprocess.check_output("vtysh -c 'show ip ospf interface json'", shell=True) - ospf_out=subprocess.check_output("vtysh -c 'show ip ospf json'", shell=True) - neighbor_out=subprocess.check_output("vtysh -c 'show ip ospf neighbor detail json'", shell=True) - try: - interfaces=json.loads(iface_out) - ospf=json.loads(ospf_out) - neighbors=json.loads(neighbor_out) - except json.JSONDecodeError: - return {} # OSPFD is most likely not running - for ifname,iface in interfaces["interfaces"].items(): +def run_json_cmd(cmd, default=None, check=True): + """Run a command (array of args) with JSON output and return the JSON""" + try: + cmd.append("2>/dev/null") + 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(): + ospf_interfaces = ['sudo', 'vtysh', '-c', "show ip ospf interface json"] + ip_ospf = ['sudo', 'vtysh', '-c', "show ip ospf json"] + ospf_neigh = ['sudo', 'vtysh', '-c', "show ip ospf neighbor detail json"] + try: + interfaces = run_json_cmd(ospf_interfaces) + ospf = run_json_cmd(ip_ospf) + neighbors = run_json_cmd(ospf_neigh) + except (subprocess.CalledProcessError, json.JSONDecodeError): + return {} + + for ifname, iface in interfaces["interfaces"].items(): iface["name"] = ifname iface["neighbors"] = [] for area_id in ospf["areas"]: @@ -62,5 +79,7 @@ def main(): print(json.dumps(ospf)) + if __name__ == "__main__": main() + sys.exit(0) diff --git a/src/statd/python/yanger/yanger.py b/src/statd/python/yanger/yanger.py index 4f9563b5..7d34c089 100755 --- a/src/statd/python/yanger/yanger.py +++ b/src/statd/python/yanger/yanger.py @@ -308,9 +308,11 @@ def frr_to_ietf_neighbor_state(state): def add_ospf_routes(ospf): """Fetch OSPF routes from Frr""" cmd = ['vtysh', '-c', "show ip ospf rout json"] - data = run_json_cmd(cmd, "") - routes = [] + data = run_json_cmd(cmd, "", check=False, default=[]) + if data == []: + return # No OSPF routes available + routes = [] for prefix, info in data.items(): if prefix.find("/") == -1: # Ignore router IDs continue @@ -349,8 +351,7 @@ def add_ospf_routes(ospf): def add_ospf(control_protocols): """Populate OSPF status""" cmd = ['/usr/libexec/statd/ospf-status'] - data = run_json_cmd(cmd, "") - + data = run_json_cmd(cmd, "", check=False, default={}) if data == {}: return # No OSPF data available From 118dcd4b35fe0be0eaac4723f5d493b59db889d3 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 22 Sep 2024 11:03:33 +0200 Subject: [PATCH 21/39] statd: read system routing table from vtysh intead of kernel Signed-off-by: Joachim Wiberg --- src/statd/python/yanger/yanger.py | 82 ++++++++++++++++++------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/src/statd/python/yanger/yanger.py b/src/statd/python/yanger/yanger.py index 7d34c089..fc2e55fa 100755 --- a/src/statd/python/yanger/yanger.py +++ b/src/statd/python/yanger/yanger.py @@ -231,7 +231,17 @@ def add_hardware(hw_out): def get_routes(routes, proto, data): - """Populate routes""" + """Populate routes from vtysh JSON output""" + + # Mapping of FRR protocol names to IETF routing-protocol + pmap = { + 'kernel': 'infix-routing:kernel', + 'connected': 'direct', + 'static': 'static', + 'ospf': 'ietf-ospf:ospfv2', + 'ospf6': 'ietf-ospf:ospfv3', + } + out = {} out["route"] = [] @@ -242,58 +252,60 @@ def get_routes(routes, proto, data): default = "::/0" host_prefix_length = "128" - for entry in data: - new = {} - if entry['dst'] == "default": - entry['dst'] = default - if entry['dst'].find('/') == -1: - entry['dst'] = entry['dst'] + "/" + host_prefix_length - new[f'ietf-{proto}-unicast-routing:destination-prefix'] = entry['dst'] - new['source-protocol'] = "infix-routing:" + entry['protocol'] - if entry.get("metric"): - new['route-preference'] = entry['metric'] - else: - new['route-preference'] = 0 + for prefix, entries in data.items(): + for route in entries: + new = {} + dst = route.get('prefix', default) + if '/' not in dst: + dst = f"{dst}/{route.get('prefixLen', host_prefix_length)}" + + new[f'ietf-{proto}-unicast-routing:destination-prefix'] = dst + frr = route.get('protocol', 'infix-routing:kernel') + new['source-protocol'] = pmap.get(frr, 'infix-routing:kernel') + new['route-preference'] = route.get('distance', 0) - if entry.get('nexthops'): next_hops = [] - for hop in entry.get('nexthops'): + for hop in route.get('nexthops', []): next_hop = {} - if hop.get("dev"): - next_hop['outgoing-interface'] = hop['dev'] - if hop.get("gateway"): - next_hop[f'ietf-{proto}-unicast-routing:address'] = hop['gateway'] + if hop.get('ip'): + next_hop[f'ietf-{proto}-unicast-routing:address'] = hop['ip'] + elif hop.get('interfaceName'): + next_hop['outgoing-interface'] = hop['interfaceName'] next_hops.append(next_hop) - insert(new, 'next-hop', 'next-hop-list', 'next-hop', next_hops) - else: - next_hop = {} - if entry['type'] == "blackhole": - next_hop['special-next-hop'] = "blackhole" - if entry['type'] == "unreachable": - next_hop['special-next-hop'] = "unreachable" - if entry['type'] == "unicast": - if entry.get("dev"): - next_hop['outgoing-interface'] = entry['dev'] - if entry.get("gateway"): - next_hop[f'ietf-{proto}-unicast-routing:next-hop-address'] = entry['gateway'] + if next_hops: + new['next-hop'] = {'next-hop-list': {'next-hop': next_hops}} + else: + next_hop = {} + protocol = route.get('protocol', 'unicast') + if protocol == "blackhole": + next_hop['special-next-hop'] = "blackhole" + elif protocol == "unreachable": + next_hop['special-next-hop'] = "unreachable" + else: + if route.get('interfaceName'): + next_hop['outgoing-interface'] = route['interfaceName'] + if route.get('nexthop'): + next_hop[f'ietf-{proto}-unicast-routing:next-hop-address'] = route['nexthop'] - new['next-hop'] = next_hop + new['next-hop'] = next_hop - out['route'].append(new) + out['route'].append(new) insert(routes, 'routes', out) def add_ipv4_route(routes): """Fetch IPv4 routes from kernel and populate tree""" - data = run_json_cmd(['ip', '-4', '-s', '-d', '-j', 'route'], "ip-4-route.json") + data = run_json_cmd(['sudo', 'vtysh', '-c', "show ip route json"], + "vtysh-ip4-route.json", check=False, default={}) get_routes(routes, "ipv4", data) def add_ipv6_route(routes): """Fetch IPv6 routes from kernel and populate tree""" - data = run_json_cmd(['ip', '-6', '-s', '-d', '-j', 'route'], "ip-6-route.json") + data = run_json_cmd(['sudo', 'vtysh', '-c', "show ipv6 route json"], + "vtysh-ip6-route.json", check=False, default={}) get_routes(routes, "ipv6", data) From fa6851caa60df8ebb0ed9554d2b676e37155afc0 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 22 Sep 2024 11:05:17 +0200 Subject: [PATCH 22/39] board/common: set DHCP and ZeroConf routes using Frr/staticd This patch changes the way Infix DHCP and ZerocConf clients set their routes in the system. Instead of setting them directly in the kernel we ask FRR staticd to set them for us. The reason for this change is to be able to override routes from these protocols with locally set static routes. The routes are now set with a distance of 5 and 254, respectively, while static routes by default have a distance of 1. In contrast, kernel routes are always treated by Frr as distance 0, i.e., they are preferred over static routes. Finally, this patch drops the use of Linux legacy interface aliases, or colon interfaces, in an effort to reduce confusion for end users. This may give some odd results if using older tools like ifconfig, so we recommend using 'show interfaces' or 'ipb a' instead. Fixes #640 Signed-off-by: Joachim Wiberg --- .../rootfs/etc/avahi/avahi-autoipd.action | 43 ++++++++++++------- board/common/rootfs/etc/frr/static.d/.empty | 0 .../rootfs/etc/frr/static.d/00default.conf | 9 ++++ board/common/rootfs/usr/sbin/staticd-helper | 15 +++++++ .../rootfs/usr/share/udhcpc/default.script | 35 +++++++-------- .../skeleton-init-finit.mk | 1 + .../etc/finit.d/available/frr/staticd.conf | 5 ++- src/confd/src/ietf-routing.c | 27 +++--------- 8 files changed, 80 insertions(+), 55 deletions(-) create mode 100644 board/common/rootfs/etc/frr/static.d/.empty create mode 100644 board/common/rootfs/etc/frr/static.d/00default.conf create mode 100755 board/common/rootfs/usr/sbin/staticd-helper diff --git a/board/common/rootfs/etc/avahi/avahi-autoipd.action b/board/common/rootfs/etc/avahi/avahi-autoipd.action index 239d7853..20912b82 100755 --- a/board/common/rootfs/etc/avahi/avahi-autoipd.action +++ b/board/common/rootfs/etc/avahi/avahi-autoipd.action @@ -7,38 +7,49 @@ # STOP: The daemon is terminating # $2 interface name # $3 IP adddress -# -# For details on the distance adjustment below, please see the following -# section in the Frr documentation on distance + prefence => to metric. -# https://docs.frrouting.org/en/stable-8.0/zebra.html#administrative-distance PATH="$PATH:/usr/bin:/usr/sbin:/bin:/sbin" +NAME="/etc/frr/static.d/$2-zeroconf.conf" +NEXT="${NAME}+" log() { logger -I $$ -t zeroconf -p user.notice "$*" } -# Use a different metric for each interface as tie breaker, to ensure we -# can set identical routes to multiple interfaces. -IFINDEX=$(cat "/sys/class/net/$2/ifindex" 2>/dev/null || echo 0) +# Reduce changes needed by comparing with previous route(s) +act() +{ + case $1 in + add) + echo "! Generated by avahi-autoipd" > "$NEXT" + echo "ip route 0.0.0.0/0 $2 254" >> "$NEXT" + cmp -s "$NAME" "$NEXT" && return + mv "$NEXT" "$NAME" + ;; + del) + [ -f "$NAME" ] || return + rm "$NAME" + ;; + *) + return + ;; + esac -# Distance adjustment to ensure that static routes set by Zebra and any -# routes learned from DHCP take precedence over these interface routes. -# A distance of 255 ensures these routes are never redistributed. -METRIC=$(((255 << 24) + 1000 + IFINDEX)) + initctl -nbq restart staticd +} case "$1" in BIND) - ip addr flush dev "$2" label "$2:avahi" - ip addr add "$3"/16 brd 169.254.255.255 label "$2:avahi" scope link dev "$2" proto random - ip route add default dev "$2" metric "$METRIC" scope link proto zeroconf + ip addr flush dev "$2" proto random + ip addr add "$3"/16 brd 169.254.255.255 scope link dev "$2" proto random + act add "$2" log "set ipv4ll $3 on iface $2" ;; CONFLICT|UNBIND|STOP) - ip route del default dev "$2" metric "$METRIC" scope link - ip addr flush dev "$2" label "$2:avahi" + act del "$2" + ip addr flush dev "$2" proto random log "clr ipv4ll on iface $2" ;; diff --git a/board/common/rootfs/etc/frr/static.d/.empty b/board/common/rootfs/etc/frr/static.d/.empty new file mode 100644 index 00000000..e69de29b diff --git a/board/common/rootfs/etc/frr/static.d/00default.conf b/board/common/rootfs/etc/frr/static.d/00default.conf new file mode 100644 index 00000000..c23631f5 --- /dev/null +++ b/board/common/rootfs/etc/frr/static.d/00default.conf @@ -0,0 +1,9 @@ +! Default settings for staticd, used for both +! confd generated routes, udhcpc and zeroconf +frr defaults traditional +hostname Router +password zebra +enable password zebra +no log unique-id +log syslog informational +log facility local2 diff --git a/board/common/rootfs/usr/sbin/staticd-helper b/board/common/rootfs/usr/sbin/staticd-helper new file mode 100755 index 00000000..744b2cd3 --- /dev/null +++ b/board/common/rootfs/usr/sbin/staticd-helper @@ -0,0 +1,15 @@ +#!/bin/sh +# Sort and collate all /etc/frr/static.d/*.conf files managed by confd, +# udhcpc, and avahi-autoipd before starting staticd. + +DIRD=/etc/frr/static.d +NAME=/etc/frr/staticd.conf +NEXT=${NAME}+ + +rm -f "$NEXT" +find "$DIRD" -type f -name '*.conf' ! -name '*~' | sort | while read -r file; do + cat "$file" >> "$NEXT" +done + +cmp -s "$NAME" "$NEXT" && exit 0 +mv "$NEXT" "$NAME" diff --git a/board/common/rootfs/usr/share/udhcpc/default.script b/board/common/rootfs/usr/share/udhcpc/default.script index 73e4c78b..1917e790 100755 --- a/board/common/rootfs/usr/share/udhcpc/default.script +++ b/board/common/rootfs/usr/share/udhcpc/default.script @@ -1,9 +1,5 @@ #!/bin/sh # This script expect a system with resolvconf (openresolv) and iproute2 -# -# For details on the distance adjustment below, please see the following -# section in the Frr documentation on distance + prefence => to metric. -# https://docs.frrouting.org/en/stable-8.0/zebra.html#administrative-distance [ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1 @@ -11,13 +7,12 @@ ACTION="$1" IP_CACHE="/var/lib/misc/${interface}.cache" RESOLV_CONF="/run/resolvconf/interfaces/${interface}.conf" NTPFILE="/run/chrony/dhcp-sources.d/${interface}.sources" +NAME="/etc/frr/static.d/${interface}-dhcp.conf" +NEXT="${NAME}+" + [ -n "$broadcast" ] && BROADCAST="broadcast $broadcast" [ -n "$subnet" ] || subnet=32 -[ -n "$metric" ] || metric=100 - -# Distance adjustment to ensure that static routes set by Zebra -# can take precedence over routes learned from DHCP. -metric=$(((5 << 24) + metric)) +[ -n "$metric" ] || metric=5 # Handle stateful DHCPv6 like DHCPv4 [ -n "$ipv6" ] && ip="$ipv6/128" @@ -31,7 +26,7 @@ log() logger -I $$ -t udhcpc -p user.notice "$*" } -wwait_for_ipv6_default_route() +wait_for_ipv6_default_route() { log "waiting for IPv6 default route to be installed." while [ $IF_WAIT_DELAY -gt 0 ]; do @@ -50,28 +45,35 @@ wwait_for_ipv6_default_route() # client MUST ignore the Router option. set_dhcp_routes() { + echo "! Generated by udhcpc" > "$NEXT" if [ -n "$staticroutes" ]; then # format: dest1/mask gw1 ... destn/mask gwn set -- $staticroutes while [ -n "$1" -a -n "$2" ]; do log "adding route $1 via $2 dev $interface proto dhcp" - ip route add "$1" via "$2" dev $interface metric $metric proto dhcp + echo "ip route $1 $2 $metric tag 100" >> "$NEXT" shift 2 done elif [ -n "$router" ] ; then for i in $router ; do - ip route add default via $i dev $interface metric $((metric++)) proto dhcp + echo "ip route 0.0.0.0/0 $i $metric tag 100" >> "$NEXT" done fi + + # Reduce changes needed by comparing with previous route(s) + cmp -s "$NAME" "$NEXT" && return + mv "$NEXT" "$NAME" + + initctl -nbq restart staticd } clr_dhcp_routes() { log "deleting DHCP routes from $interface" - ip route show proto dhcp dev $interface | while read rt via nh dev dev; do - log "removing $rt nh $nh on $dev" - ip route del $rt via $nh dev $dev proto dhcp - done + [ -f "$NAME" ] || return + rm "$NAME" + + initctl -nbq restart staticd } clr_dhcp_addresses() @@ -120,7 +122,6 @@ case "$ACTION" in wait_for_ipv6_default_route fi - clr_dhcp_routes set_dhcp_routes # set hostname if given diff --git a/package/skeleton-init-finit/skeleton-init-finit.mk b/package/skeleton-init-finit/skeleton-init-finit.mk index c1339e22..30088ec0 100644 --- a/package/skeleton-init-finit/skeleton-init-finit.mk +++ b/package/skeleton-init-finit/skeleton-init-finit.mk @@ -89,6 +89,7 @@ define SKELETON_INIT_FINIT_SET_FRR for svc in babeld bfdd bgpd eigrpd isisd ldpd ospfd ospf6d pathd ripd ripng staticd vrrpd zebra; do \ cp $(SKELETON_INIT_FINIT_AVAILABLE)/frr/$$svc.conf $(FINIT_D)/available/$$svc.conf; \ done + ln -sf ../available/staticd.conf $(FINIT_D)/enabled/staticd.conf ln -sf ../available/zebra.conf $(FINIT_D)/enabled/zebra.conf endef SKELETON_INIT_FINIT_POST_INSTALL_TARGET_HOOKS += SKELETON_INIT_FINIT_SET_FRR diff --git a/package/skeleton-init-finit/skeleton/etc/finit.d/available/frr/staticd.conf b/package/skeleton-init-finit/skeleton/etc/finit.d/available/frr/staticd.conf index d6fae1ae..e3af6b5f 100644 --- a/package/skeleton-init-finit/skeleton/etc/finit.d/available/frr/staticd.conf +++ b/package/skeleton-init-finit/skeleton/etc/finit.d/available/frr/staticd.conf @@ -1 +1,4 @@ -service [2345] log:null staticd -A 127.0.0.1 -u frr -g frr -f /etc/frr/staticd.conf -- Static routing daemon +# Run staticd-helper to collate /etc/static.d/*.conf before starting staticd +service log:null pre:/usr/sbin/staticd-helper \ + [2345] staticd -A 127.0.0.1 -u frr -g frr -f /etc/frr/staticd.conf \ + -- Static routing daemon diff --git a/src/confd/src/ietf-routing.c b/src/confd/src/ietf-routing.c index de6aacfb..b3fc4dea 100644 --- a/src/confd/src/ietf-routing.c +++ b/src/confd/src/ietf-routing.c @@ -8,7 +8,7 @@ #define XPATH_BASE_ "/ietf-routing:routing/control-plane-protocols/control-plane-protocol" #define XPATH_OSPF_ XPATH_BASE_ "/ietf-ospf:ospf" -#define STATICD_CONF "/etc/frr/staticd.conf" +#define STATICD_CONF "/etc/frr/static.d/confd.conf" #define STATICD_CONF_NEXT STATICD_CONF "+" #define STATICD_CONF_PREV STATICD_CONF "-" #define OSPFD_CONF "/etc/frr/ospfd.conf" @@ -245,7 +245,7 @@ static int change_control_plane_protocols(sr_session_ctx_t *session, uint32_t su const char *xpath, sr_event_t event, unsigned request_id, void *priv) { int staticd_enabled = 0, ospfd_enabled = 0, bfdd_enabled = 0; - bool ospfd_running, staticd_running, bfdd_running; + bool ospfd_running, bfdd_running; struct lyd_node *cplane, *tmp; bool restart_zebra = false; int rc = SR_ERR_OK; @@ -260,7 +260,7 @@ static int change_control_plane_protocols(sr_session_ctx_t *session, uint32_t su ERROR("Failed to open %s", STATICD_CONF_NEXT); return SR_ERR_INTERNAL; } - fputs(FRR_STATIC_CONFIG, fp); + fputs("! Generated by Infix confd\n", fp); break; case SR_EV_ABORT: /* User abort, or other plugin failed */ @@ -272,18 +272,8 @@ static int change_control_plane_protocols(sr_session_ctx_t *session, uint32_t su staticd_enabled = fexist(STATICD_CONF_NEXT); ospfd_enabled = fexist(OSPFD_CONF_NEXT); bfdd_enabled = fexist(BFDD_CONF_NEXT); - staticd_running = !systemf("initctl -bfq status staticd"); ospfd_running = !systemf("initctl -bfq status ospfd"); bfdd_running = !systemf("initctl -bfq status bfdd"); - if (staticd_running && !staticd_enabled) { - if (systemf("initctl -bfq disable staticd")) { - ERROR("Failed to disable static routing daemon"); - rc = SR_ERR_INTERNAL; - goto err_abandon; - } - /* Remove all generated files */ - (void)remove(STATICD_CONF); - } if (bfdd_running && !bfdd_enabled) { if (systemf("initctl -bfq disable bfdd")) { @@ -337,15 +327,10 @@ static int change_control_plane_protocols(sr_session_ctx_t *session, uint32_t su (void)remove(STATICD_CONF_PREV); (void)rename(STATICD_CONF, STATICD_CONF_PREV); (void)rename(STATICD_CONF_NEXT, STATICD_CONF); - if (!staticd_running) { - if (systemf("initctl -bfq enable staticd")) { - ERROR("Failed to enable static routing daemon"); - rc = SR_ERR_INTERNAL; - goto err_abandon; - } - } else { + restart_zebra = true; + } else { + if (!remove(STATICD_CONF)) restart_zebra = true; - } } if (restart_zebra) { From f65000f29e2493e1165dfc2c3568497d294694ec Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 26 Sep 2024 15:29:11 +0200 Subject: [PATCH 23/39] board/common: update mg default configuration file Signed-off-by: Joachim Wiberg --- board/common/rootfs/etc/mg | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/board/common/rootfs/etc/mg b/board/common/rootfs/etc/mg index ac3ae7bb..b1d9167d 100644 --- a/board/common/rootfs/etc/mg +++ b/board/common/rootfs/etc/mg @@ -1 +1,6 @@ +# Micro GNU Emacs default configuration file +auto-indent-mode +auto-fill-mode make-backup-files 0 +require-final-newline T +set-fill-column 72 From 76e54420a20c7e6fb3ab463af30da8658a400cc6 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 22 Sep 2024 19:27:34 +0200 Subject: [PATCH 24/39] confd: minor, fixes & cleanup - Straighten out formatting issues in ospfd.conf (debug) - Simplify and reduce code duplication - Avoid 'restart zebra' errors in runlevel S Signed-off-by: Joachim Wiberg --- src/confd/src/ietf-routing.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/confd/src/ietf-routing.c b/src/confd/src/ietf-routing.c index b3fc4dea..e5ec4cfe 100644 --- a/src/confd/src/ietf-routing.c +++ b/src/confd/src/ietf-routing.c @@ -129,11 +129,11 @@ int parse_ospf_areas(sr_session_ctx_t *session, struct lyd_node *areas, FILE *fp int parse_ospf(sr_session_ctx_t *session, struct lyd_node *ospf) { const char *static_debug = "! OSPF default debug\ -debug ospf bfd\n \ -debug ospf packet all detail\n \ -debug ospf ism\n \ -debug ospf nsm\n \ -debug ospf default-information\n \ +debug ospf bfd\n\ +debug ospf packet all detail\n\ +debug ospf ism\n\ +debug ospf nsm\n\ +debug ospf default-information\n\ debug ospf nssa\n\ ! OSPF configuration\n"; struct lyd_node *areas, *default_route; @@ -224,14 +224,13 @@ static int parse_static_routes(sr_session_ctx_t *session, struct lyd_node *paren ipv4 = lydx_get_child(parent, "ipv4"); ipv6 = lydx_get_child(parent, "ipv6"); - ipv4 = lydx_get_child(parent, "ipv4"); - ipv6 = lydx_get_child(parent, "ipv6"); v4routes = lydx_get_child(ipv4, "route"); - v6routes = lydx_get_child(ipv6, "route"); LY_LIST_FOR(v4routes, route) { parse_route(route, fp, "ip"); num_routes++; } + + v6routes = lydx_get_child(ipv6, "route"); LY_LIST_FOR(v6routes, route) { parse_route(route, fp, "ipv6"); num_routes++; @@ -311,12 +310,10 @@ static int change_control_plane_protocols(sr_session_ctx_t *session, uint32_t su (void)rename(OSPFD_CONF, OSPFD_CONF_PREV); (void)rename(OSPFD_CONF_NEXT, OSPFD_CONF); if (!ospfd_running) { - if (ospfd_enabled) { - if (systemf("initctl -bfq enable ospfd")) { - ERROR("Failed to enable OSPF routing daemon"); - rc = SR_ERR_INTERNAL; - goto err_abandon; - } + if (systemf("initctl -bnq enable ospfd")) { + ERROR("Failed to enable OSPF routing daemon"); + rc = SR_ERR_INTERNAL; + goto err_abandon; } } else { restart_zebra = true; @@ -334,6 +331,10 @@ static int change_control_plane_protocols(sr_session_ctx_t *session, uint32_t su } if (restart_zebra) { + /* skip in runlevel S, no routing daemons run here anyway */ + if (systemf("runlevel >/dev/null 2>&1")) + return SR_ERR_OK; + if (systemf("initctl -bfq restart zebra")) { ERROR("Failed to restart zebra routing daemon"); rc = SR_ERR_INTERNAL; From 6e85ace4b0683d4fcbcf0277524a43cc01db400f Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 22 Sep 2024 19:36:25 +0200 Subject: [PATCH 25/39] test: update rib source protocol, infix-routing -> ietf-ospf Latest YANG model change reverts back to using the IETF routing-protocol type. Update tests to check for infix-routing:ospf -> ietf-ospf:ospfv2. Also, refactor and extend infamy/route.py:_exist_route() slightly for readability and PEP-8, as well as to use an optional model prefix also for next-hop-list addresses. Signed-off-by: Joachim Wiberg --- test/case/ietf_routing/ospf_basic/test.py | 4 +-- test/case/ietf_routing/ospf_multiarea/test.py | 24 ++++++------- .../ospf_unnumbered_interface/test.py | 6 ++-- test/infamy/route.py | 35 ++++++++++++------- 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/test/case/ietf_routing/ospf_basic/test.py b/test/case/ietf_routing/ospf_basic/test.py index f1f2bf06..4fedecd4 100755 --- a/test/case/ietf_routing/ospf_basic/test.py +++ b/test/case/ietf_routing/ospf_basic/test.py @@ -171,8 +171,8 @@ with infamy.Test() as test: config_target2(R2, R2link)) with test.step("Wait for OSPF routes"): print("Waiting for OSPF routes..") - until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", source_protocol = "infix-routing:ospf"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", source_protocol = "infix-routing:ospf"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", source_protocol="ietf-ospf:ospfv2"), attempts=200) with test.step("Test connectivity from PC:data to 192.168.200.1"): _, hport0 = env.ltop.xlate("PC", "data") diff --git a/test/case/ietf_routing/ospf_multiarea/test.py b/test/case/ietf_routing/ospf_multiarea/test.py index 7f16b9db..3aabb245 100755 --- a/test/case/ietf_routing/ospf_multiarea/test.py +++ b/test/case/ietf_routing/ospf_multiarea/test.py @@ -549,17 +549,17 @@ with infamy.Test() as test: with test.step("Wait for routes from OSPF on all routers"): print("Waiting for routes from OSPF") - until(lambda: route.ipv4_route_exist(R1, "10.0.0.2/32", nexthop="10.0.12.2", source_protocol = "infix-routing:ospf"), attempts=200) - until(lambda: route.ipv4_route_exist(R1, "10.0.0.3/32", nexthop="10.0.13.2", source_protocol = "infix-routing:ospf"), attempts=200) - until(lambda: route.ipv4_route_exist(R1, "10.0.0.4/32", nexthop="10.0.41.1", source_protocol = "infix-routing:ospf"), attempts=200) - until(lambda: route.ipv4_route_exist(R1, "192.168.4.0/24", nexthop="10.0.41.1", source_protocol = "infix-routing:ospf"), attempts=200) - until(lambda: route.ipv4_route_exist(R1, "10.0.24.0/30", nexthop="10.0.41.1", source_protocol = "infix-routing:ospf"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "10.0.0.1/32", nexthop="10.0.23.2", source_protocol = "infix-routing:ospf"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "10.0.0.3/32", nexthop="10.0.23.2", source_protocol = "infix-routing:ospf"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "10.0.0.4/32", nexthop="10.0.24.2", source_protocol = "infix-routing:ospf"), attempts=200) - until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0", nexthop="10.0.23.1", source_protocol = "infix-routing:ospf"), attempts=200) - until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2", source_protocol = "infix-routing:ospf"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "10.0.13.0/30", nexthop="10.0.23.2", source_protocol = "infix-routing:ospf"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "10.0.0.2/32", nexthop="10.0.12.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "10.0.0.3/32", nexthop="10.0.13.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "10.0.0.4/32", nexthop="10.0.41.1", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "192.168.4.0/24", nexthop="10.0.41.1", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "10.0.24.0/30", nexthop="10.0.41.1", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "10.0.0.1/32", nexthop="10.0.23.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "10.0.0.3/32", nexthop="10.0.23.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "10.0.0.4/32", nexthop="10.0.24.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0", nexthop="10.0.23.1", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "10.0.13.0/30", nexthop="10.0.23.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) with test.step("Verify Area 0.0.0.1 on R3 is NSSA area"): assert(route.ospf_is_area_nssa(R3, "0.0.0.1")) @@ -598,7 +598,7 @@ with infamy.Test() as test: disable_link(R1, R1ring2) # Here we should test with link breakers, to test BFD recouppling, for now disable the link with test.step("Verify that the route to 10.0.0.3 from PC:data4, go through 10.0.24.1"): - until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.24.1", source_protocol = "infix-routing:ospf"), attempts=100) + until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.24.1", source_protocol="ietf-ospf:ospfv2"), attempts=100) until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2") == False, attempts = 10) trace=ns0.traceroute("10.0.0.3") assert(len(trace) == 3) diff --git a/test/case/ietf_routing/ospf_unnumbered_interface/test.py b/test/case/ietf_routing/ospf_unnumbered_interface/test.py index 0d725b95..2f13e464 100755 --- a/test/case/ietf_routing/ospf_unnumbered_interface/test.py +++ b/test/case/ietf_routing/ospf_unnumbered_interface/test.py @@ -167,9 +167,9 @@ with infamy.Test() as test: lambda: config_target2(R2, R2link)) with test.step("Wait for OSPF routes"): print("Waiting for OSPF routes..") - until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", source_protocol = "infix-routing:ospf"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", source_protocol = "infix-routing:ospf"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "192.168.10.0/24", source_protocol = "infix-routing:ospf"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "192.168.10.0/24", source_protocol="ietf-ospf:ospfv2"), attempts=200) with test.step("Check interface type"): assert(route.ospf_get_interface_type(R1, "0.0.0.0", R1link) == "point-to-point") diff --git a/test/infamy/route.py b/test/infamy/route.py index b9d0f2cd..ce9ba096 100644 --- a/test/infamy/route.py +++ b/test/infamy/route.py @@ -7,34 +7,45 @@ def _get_routes(target, protocol): return r.get("routes", {}).get("route",{}) return {} -def _exist_route(target, prefix, nexthop, version, source_protocol): - routes = _get_routes(target, version) - route_found = False + +def _exist_route(target, prefix, nexthop, ip, source_protocol): + routes = _get_routes(target, ip) for r in routes: # netconf presents destination-prefix, restconf prefix with model - p=r.get("destination-prefix") or (version == "ipv4" and r.get("ietf-ipv4-unicast-routing:destination-prefix")) or (version == "ipv6" and r.get("ietf-ipv6-unicast-routing:destination-prefix")) + p = r.get("destination-prefix") or \ + r.get(f"ietf-{ip}-unicast-routing:destination-prefix") if p != prefix: continue if source_protocol and r.get("source-protocol") != source_protocol: return False - route_found = True - nh = r["next-hop"] - next_hop_list = nh.get("next-hop-list") if nexthop: + nh = r["next-hop"] + if not nh: + return False + + next_hop_list = nh.get("next-hop-list") if next_hop_list: for nhl in next_hop_list["next-hop"]: - address = nhl.get("address") + # netconf presents address, restconf prefix with + # model ietf-ipv4-unicast-routing:address + address = nhl.get("address") or \ + nhl.get(f"ietf-{ip}-unicast-routing:address") if address == nexthop: return True + return False else: - # netconf presents next-hop-address, restconf prefix with model ietf-ipv4-unicast-routing:next-hop-address - nh_addr=nh.get("next-hop-address", None) or (version == "ipv4" and nh.get("ietf-ipv4-unicast-routing:next-hop-address", None)) or (version == "ipv6" and nh.get("ietf-ipv6-unicast-routing:next-hop-address", None)) + # netconf presents next-hop-address, restconf prefix + # with model ietf-ipv4-unicast-routing:next-hop-address + nh_addr = nh.get("next-hop-address") or \ + nh.get(f"ietf-{ip}-unicast-routing:next-hop-address") if nh_addr == nexthop: return True - return False - return route_found + return False + else: + return True + return False def ipv4_route_exist(target, prefix, nexthop=None,source_protocol=None): return _exist_route(target, prefix, nexthop, "ipv4",source_protocol) From 0ea84cf5575d5fe649e2e1d6e01ef5a37edca87e Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 22 Sep 2024 19:40:40 +0200 Subject: [PATCH 26/39] test: minor, whitespace and PEP-8 fixes Signed-off-by: Joachim Wiberg --- test/case/ietf_routing/ospf_multiarea/test.py | 214 +++++++++--------- .../ospf_unnumbered_interface/test.py | 59 +++-- test/case/ietf_routing/static_routing/test.py | 153 ++++++------- test/infamy/route.py | 52 +++-- 4 files changed, 249 insertions(+), 229 deletions(-) diff --git a/test/case/ietf_routing/ospf_multiarea/test.py b/test/case/ietf_routing/ospf_multiarea/test.py index 3aabb245..2439b7f2 100755 --- a/test/case/ietf_routing/ospf_multiarea/test.py +++ b/test/case/ietf_routing/ospf_multiarea/test.py @@ -197,30 +197,30 @@ def config_target2(target, ring1, ring2, cross): "enabled": True, "ipv4": { "address": [{ - "ip": "10.0.0.2", - "prefix-length": 32 - },{ - "ip": "11.0.9.1", - "prefix-length": 24 - },{ - "ip": "11.0.10.1", - "prefix-length": 24 - },{ - "ip": "11.0.11.1", - "prefix-length": 24 - },{ - "ip": "11.0.12.1", - "prefix-length": 24 - },{ - "ip": "11.0.13.1", - "prefix-length": 24 - },{ - "ip": "11.0.14.1", - "prefix-length": 24 - },{ - "ip": "11.0.15.1", - "prefix-length": 24 - }] + "ip": "10.0.0.2", + "prefix-length": 32 + }, { + "ip": "11.0.9.1", + "prefix-length": 24 + }, { + "ip": "11.0.10.1", + "prefix-length": 24 + }, { + "ip": "11.0.11.1", + "prefix-length": 24 + }, { + "ip": "11.0.12.1", + "prefix-length": 24 + }, { + "ip": "11.0.13.1", + "prefix-length": 24 + }, { + "ip": "11.0.14.1", + "prefix-length": 24 + }, { + "ip": "11.0.15.1", + "prefix-length": 24 + }] } } ] @@ -243,7 +243,7 @@ def config_target2(target, ring1, ring2, cross): "areas": { "area": [{ "area-id": "0.0.0.0", - "interfaces":{ + "interfaces": { "interface": [{ "bfd": { "enabled": True @@ -251,32 +251,30 @@ def config_target2(target, ring1, ring2, cross): "name": ring2, "hello-interval": 1, "enabled": True - },{ + }, { "name": "lo", "enabled": True }] } - }, - { + }, { "area-id": "0.0.0.1", "area-type": "nssa-area", "summary": False, - "interfaces":{ + "interfaces": { "interface": [{ "bfd": { "enabled": True }, "name": ring1, "hello-interval": 1, - },{ + }, { "name": "lo", "enabled": True }] } - }, - { + }, { "area-id": "0.0.0.2", - "interfaces":{ + "interfaces": { "interface": [{ "bfd": { "enabled": True @@ -284,7 +282,7 @@ def config_target2(target, ring1, ring2, cross): "name": cross, "hello-interval": 1, "cost": 2000 - },{ + }, { "name": "lo", "enabled": True }] @@ -297,6 +295,7 @@ def config_target2(target, ring1, ring2, cross): } }) + def config_target3(target, ring2, cross, link): target.put_config_dict("ietf-interfaces", { "interfaces": { @@ -366,7 +365,7 @@ def config_target3(target, ring2, cross, link): "area-id": "0.0.0.1", "area-type": "nssa-area", "summary": False, - "interfaces":{ + "interfaces": { "interface": [{ "bfd": { "enabled": True @@ -375,18 +374,18 @@ def config_target3(target, ring2, cross, link): "hello-interval": 1, "enabled": True, "cost": 2000 - },{ + }, { "bfd": { "enabled": True }, "name": ring2, "hello-interval": 1, "enabled": True - },{ + }, { "name": link, "enabled": True, "passive": True - },{ + }, { "name": "lo", "enabled": True }] @@ -399,6 +398,7 @@ def config_target3(target, ring2, cross, link): } }) + def config_target4(target, ring1, cross, link): target.put_config_dict("ietf-interfaces", { "interfaces": { @@ -429,8 +429,8 @@ def config_target4(target, ring1, cross, link): "ipv4": { "forwarding": True, "address": [{ - "ip": "192.168.4.1", - "prefix-length": 24 + "ip": "192.168.4.1", + "prefix-length": 24 }] } }, @@ -439,8 +439,8 @@ def config_target4(target, ring1, cross, link): "enabled": True, "ipv4": { "address": [{ - "ip": "10.0.0.4", - "prefix-length": 32 + "ip": "10.0.0.4", + "prefix-length": 32 }] } } @@ -459,48 +459,48 @@ def config_target4(target, ring1, cross, link): "control-plane-protocols": { "control-plane-protocol": [ { - "type": "infix-routing:ospfv2", - "name": "default", - "ospf": { - "redistribute": { - "redistribute": [ - { - "protocol": "connected" - }] - }, - "areas": { - "area": [{ - "area-id": "0.0.0.2", - "interfaces":{ - "interface": [{ - "bfd": { - "enabled": True - }, - "name": ring1, - "hello-interval": 1, - "enabled": True - },{ - "bfd": { - "enabled": True - }, - "name": cross, - "hello-interval": 1, - "enabled": True, - "cost": 5000 - },{ - "name": "lo", - "enabled": True + "type": "infix-routing:ospfv2", + "name": "default", + "ospf": { + "redistribute": { + "redistribute": [{ + "protocol": "connected" }] - } - }] + }, + "areas": { + "area": [{ + "area-id": "0.0.0.2", + "interfaces": { + "interface": [{ + "bfd": { + "enabled": True + }, + "name": ring1, + "hello-interval": 1, + "enabled": True + }, { + "bfd": { + "enabled": True + }, + "name": cross, + "hello-interval": 1, + "enabled": True, + "cost": 5000 + }, { + "name": "lo", + "enabled": True + }] + } + }] + } } } - } ] } } }) + def disable_link(target, link): target.put_config_dict("ietf-interfaces", { "interfaces": { @@ -512,6 +512,7 @@ def disable_link(target, link): } }) + with infamy.Test() as test: with test.step("Configure targets"): env = infamy.Env() @@ -520,7 +521,6 @@ with infamy.Test() as test: R3 = env.attach("R3", "mgmt") R4 = env.attach("R4", "mgmt") - _, R1ring1 = env.ltop.xlate("R1", "ring1") _, R1ring2 = env.ltop.xlate("R1", "ring2") _, R2ring1 = env.ltop.xlate("R2", "ring1") @@ -528,17 +528,18 @@ with infamy.Test() as test: _, R3ring2 = env.ltop.xlate("R3", "ring2") _, R4ring1 = env.ltop.xlate("R4", "ring1") - _, R3data = env.ltop.xlate("R3", "data") - _, R4data = env.ltop.xlate("R4", "data") + _, R3data = env.ltop.xlate("R3", "data") + _, R4data = env.ltop.xlate("R4", "data") - _, R1cross = env.ltop.xlate("R1", "cross") - _, R2cross = env.ltop.xlate("R2", "cross") - _, R3cross = env.ltop.xlate("R3", "cross") - _, R4cross = env.ltop.xlate("R4", "cross") + _, R1cross = env.ltop.xlate("R1", "cross") + _, R2cross = env.ltop.xlate("R2", "cross") + _, R3cross = env.ltop.xlate("R3", "cross") + _, R4cross = env.ltop.xlate("R4", "cross") parallel(config_target1(R1, R1ring1, R1ring2, R1cross), config_target2(R2, R2ring1, R2ring2, R2cross), config_target3(R3, R3ring2, R3cross, R3data), config_target4(R4, R4ring1, R4cross, R4data)) + with test.step("Wait for all neighbor to peer"): print("Waiting for neighbors to peer") until(lambda: route.ospf_get_neighbor(R1, "0.0.0.0", R1ring1, "10.0.0.2"), attempts=200) @@ -564,18 +565,19 @@ with infamy.Test() as test: with test.step("Verify Area 0.0.0.1 on R3 is NSSA area"): assert(route.ospf_is_area_nssa(R3, "0.0.0.1")) - with test.step("Verify on R3, there are no routes beyond 10.0.23.1, just a default route"): # Should be only default route out of the area. - parallel(until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0"), attempts=200), - until(lambda: route.ipv4_route_exist(R3, "10.0.12.0/30") == False, attempts=5), - until(lambda: route.ipv4_route_exist(R3, "10.0.12.0/30") == False, attempts=5), - until(lambda: route.ipv4_route_exist(R3, "11.0.8.0/24") == False, attempts=5), - until(lambda: route.ipv4_route_exist(R3, "11.0.9.0/24") == False, attempts=5), - until(lambda: route.ipv4_route_exist(R3, "11.0.10.0/24") == False, attempts=5), - until(lambda: route.ipv4_route_exist(R3, "11.0.11.0/24") == False, attempts=5), - until(lambda: route.ipv4_route_exist(R3, "11.0.12.0/24") == False, attempts=5), - until(lambda: route.ipv4_route_exist(R3, "11.0.13.0/24") == False, attempts=5), - until(lambda: route.ipv4_route_exist(R3, "11.0.14.0/24") == False, attempts=5), - until(lambda: route.ipv4_route_exist(R3, "11.0.15.0/24") == False, attempts=5)) + with test.step("Verify on R3, there are no routes beyond 10.0.23.1, just a default route"): + # Should be only default route out of the area. + parallel(until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0"), attempts=200), + until(lambda: route.ipv4_route_exist(R3, "10.0.12.0/30") is False, attempts=5), + until(lambda: route.ipv4_route_exist(R3, "10.0.12.0/30") is False, attempts=5), + until(lambda: route.ipv4_route_exist(R3, "11.0.8.0/24") is False, attempts=5), + until(lambda: route.ipv4_route_exist(R3, "11.0.9.0/24") is False, attempts=5), + until(lambda: route.ipv4_route_exist(R3, "11.0.10.0/24") is False, attempts=5), + until(lambda: route.ipv4_route_exist(R3, "11.0.11.0/24") is False, attempts=5), + until(lambda: route.ipv4_route_exist(R3, "11.0.12.0/24") is False, attempts=5), + until(lambda: route.ipv4_route_exist(R3, "11.0.13.0/24") is False, attempts=5), + until(lambda: route.ipv4_route_exist(R3, "11.0.14.0/24") is False, attempts=5), + until(lambda: route.ipv4_route_exist(R3, "11.0.15.0/24") is False, attempts=5)) _, hport0 = env.ltop.xlate("PC", "data3") with infamy.IsolatedMacVlan(hport0) as ns0: @@ -589,21 +591,23 @@ with infamy.Test() as test: ns0.addip("192.168.4.2") ns0.addroute("0.0.0.0/0", "192.168.4.1") with test.step("Verify that the route to 10.0.0.3 from PC:data4, go through 10.0.41.2"): - trace=ns0.traceroute("10.0.0.3") - assert(len(trace) == 3) - assert(trace[1][1] == "10.0.41.2") - assert(trace[2][1] == "10.0.0.3") + trace = ns0.traceroute("10.0.0.3") + assert len(trace) == 3 + assert trace[1][1] == "10.0.41.2" + assert trace[2][1] == "10.0.0.3" with test.step("Break link R1:ring2 --- R4:ring1"): - disable_link(R1, R1ring2) # Here we should test with link breakers, to test BFD recouppling, for now disable the link + # Here we should test with link breakers, to test BFD + # recouppling, for now disable the link + disable_link(R1, R1ring2) with test.step("Verify that the route to 10.0.0.3 from PC:data4, go through 10.0.24.1"): until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.24.1", source_protocol="ietf-ospf:ospfv2"), attempts=100) - until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2") == False, attempts = 10) - trace=ns0.traceroute("10.0.0.3") - assert(len(trace) == 3) - assert(trace[1][1] == "10.0.24.1") - assert(trace[2][1] == "10.0.0.3") + until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2") is False, attempts=10) + trace = ns0.traceroute("10.0.0.3") + assert len(trace) == 3 + assert trace[1][1] == "10.0.24.1" + assert trace[2][1] == "10.0.0.3" ns0.must_reach("10.0.0.3") test.succeed() diff --git a/test/case/ietf_routing/ospf_unnumbered_interface/test.py b/test/case/ietf_routing/ospf_unnumbered_interface/test.py index 2f13e464..1512cb55 100755 --- a/test/case/ietf_routing/ospf_unnumbered_interface/test.py +++ b/test/case/ietf_routing/ospf_unnumbered_interface/test.py @@ -37,8 +37,8 @@ def config_target1(target, data, link): "ipv4": { "forwarding": True, "address": [{ - "ip": "10.0.0.1", - "prefix-length": 32 + "ip": "10.0.0.1", + "prefix-length": 32 }] } }, @@ -47,8 +47,8 @@ def config_target1(target, data, link): "enabled": True, "ipv4": { "address": [{ - "ip": "192.168.100.1", - "prefix-length": 32 + "ip": "192.168.100.1", + "prefix-length": 32 }] } } @@ -73,12 +73,10 @@ def config_target1(target, data, link): "hello-interval": 1, "dead-interval": 3, "interface-type": "point-to-point" - }, - { + }, { "name": data, "passive": True - }, - { + }, { "name": "lo", "passive": True }] @@ -91,6 +89,7 @@ def config_target1(target, data, link): } }) + def config_target2(target, link): target.put_config_dict("ietf-interfaces", { "interfaces": { @@ -112,8 +111,8 @@ def config_target2(target, link): "forwarding": True, "ipv4": { "address": [{ - "ip": "192.168.200.1", - "prefix-length": 32 + "ip": "192.168.200.1", + "prefix-length": 32 }] } } @@ -126,33 +125,33 @@ def config_target2(target, link): "control-plane-protocols": { "control-plane-protocol": [ { - "type": "infix-routing:ospfv2", - "name": "default", - "ospf": { - "areas": { - "area": [{ - "area-id": "0.0.0.0", - "interfaces":{ - "interface": [{ - "name": link, - "hello-interval": 1, - "dead-interval": 3, - "interface-type": "point-to-point" - }, - { - "name": "lo", - "passive": True - }] - } - }] + "type": "infix-routing:ospfv2", + "name": "default", + "ospf": { + "areas": { + "area": [{ + "area-id": "0.0.0.0", + "interfaces": { + "interface": [{ + "name": link, + "hello-interval": 1, + "dead-interval": 3, + "interface-type": "point-to-point" + }, { + "name": "lo", + "passive": True + }] + } + }] + } } } - } ] } } }) + with infamy.Test() as test: with test.step("Configure targets"): env = infamy.Env() diff --git a/test/case/ietf_routing/static_routing/test.py b/test/case/ietf_routing/static_routing/test.py index 8f7d0661..84b69c62 100755 --- a/test/case/ietf_routing/static_routing/test.py +++ b/test/case/ietf_routing/static_routing/test.py @@ -21,72 +21,70 @@ Static routing Test that it is possible to use static routes (IPv4 and IPv6) works as expected """ -import copy import infamy -import time -import infamy.iface as iface import infamy.route as route from infamy.util import until, parallel + def config_target1(target, data, link): target.put_config_dict("ietf-interfaces", { - "interfaces": { - "interface": [ - { - "name": data, - "enabled": True, - "ipv4": { - "forwarding": True, - "address": [{ - "ip": "192.168.10.1", - "prefix-length": 24 - }]}, - "ipv6": { - "forwarding": True, - "address": [{ - "ip": "2001:db8:3c4d:10::1", - "prefix-length": 64 - }] - } - }, - { - "name": link, - "enabled": True, - "ipv4": { - "forwarding": True, - "address": [{ + "interfaces": { + "interface": [ + { + "name": data, + "enabled": True, + "ipv4": { + "forwarding": True, + "address": [{ + "ip": "192.168.10.1", + "prefix-length": 24 + }]}, + "ipv6": { + "forwarding": True, + "address": [{ + "ip": "2001:db8:3c4d:10::1", + "prefix-length": 64 + }] + } + }, + { + "name": link, + "enabled": True, + "ipv4": { + "forwarding": True, + "address": [{ "ip": "192.168.50.1", "prefix-length": 24 - }] - }, - "ipv6": { - "forwarding": True, - "address": [{ - "ip": "2001:db8:3c4d:50::1", - "prefix-length": 64 - }] - } + }] }, - { - "name": "lo", - "enabled": True, - "ipv4": { - "address": [{ + "ipv6": { + "forwarding": True, + "address": [{ + "ip": "2001:db8:3c4d:50::1", + "prefix-length": 64 + }] + } + }, + { + "name": "lo", + "enabled": True, + "ipv4": { + "address": [{ "ip": "192.168.100.1", "prefix-length": 32 - }] - }, - "ipv6": { - "address": [{ - "ip": "2001:db8:3c4d:100::1", - "prefix-length": 128 - }] - } + }] + }, + "ipv6": { + "address": [{ + "ip": "2001:db8:3c4d:100::1", + "prefix-length": 128 + }] + } } - ] - } - }) + ] + } + }) target.put_config_dict("ietf-routing", { "routing": { "control-plane-protocols": { @@ -104,10 +102,10 @@ def config_target1(target, data, link): }, "ipv6": { "route": [{ - "destination-prefix": "2001:db8:3c4d:200::1/128", - "next-hop": { - "next-hop-address": "2001:db8:3c4d:50::2" - } + "destination-prefix": "2001:db8:3c4d:200::1/128", + "next-hop": { + "next-hop-address": "2001:db8:3c4d:50::2" + } }] } } @@ -115,6 +113,8 @@ def config_target1(target, data, link): } } }) + + def config_target2(target, link): target.put_config_dict("ietf-interfaces", { "interfaces": { @@ -143,8 +143,8 @@ def config_target2(target, link): "forwarding": True, "ipv4": { "address": [{ - "ip": "192.168.200.1", - "prefix-length": 32 + "ip": "192.168.200.1", + "prefix-length": 32 }] }, "ipv6": { @@ -168,18 +168,18 @@ def config_target2(target, link): "static-routes": { "ipv4": { "route": [{ - "destination-prefix": "0.0.0.0/0", - "next-hop": { - "next-hop-address": "192.168.50.1" - } - }] + "destination-prefix": "0.0.0.0/0", + "next-hop": { + "next-hop-address": "192.168.50.1" + } + }] }, "ipv6": { "route": [{ - "destination-prefix": "::/0", - "next-hop": { - "next-hop-address": "2001:db8:3c4d:50::1" - } + "destination-prefix": "::/0", + "next-hop": { + "next-hop-address": "2001:db8:3c4d:50::1" + } }] } } @@ -188,6 +188,7 @@ def config_target2(target, link): } }) + with infamy.Test() as test: with test.step("Configure targets"): env = infamy.Env() @@ -210,21 +211,21 @@ with infamy.Test() as test: _, hport0 = env.ltop.xlate("PC", "data") with infamy.IsolatedMacVlan(hport0) as ns0: with test.step("Configure host addresses and routes"): - ns0.addip("2001:db8:3c4d:10::2", prefix_length=64, proto="ipv6") - ns0.addroute("2001:db8:3c4d:200::1/128", "2001:db8:3c4d:10::1", proto="ipv6") - ns0.addip("192.168.10.2") - ns0.addroute("192.168.200.1/32", "192.168.10.1") + ns0.addip("2001:db8:3c4d:10::2", prefix_length=64, proto="ipv6") + ns0.addroute("2001:db8:3c4d:200::1/128", "2001:db8:3c4d:10::1", proto="ipv6") + ns0.addip("192.168.10.2") + ns0.addroute("192.168.200.1/32", "192.168.10.1") with test.step("Verify that dut2 is reachable on 192.168.200.1 from PC:data"): - ns0.must_reach("192.168.200.1") + ns0.must_reach("192.168.200.1") with test.step("Verify that R2 is reachable on 2001:db8:3c4d:200::1 from PC:data"): - ns0.must_reach("2001:db8:3c4d:200::1") + ns0.must_reach("2001:db8:3c4d:200::1") with test.step("Remove all static routes from R1"): R1.delete_xpath("/ietf-routing:routing/control-plane-protocols") - parallel(until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32") == False), - until(lambda: route.ipv6_route_exist(R1, "2001:db8:3c4d:200::1/128") == False)) + parallel(until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32") is False), + until(lambda: route.ipv6_route_exist(R1, "2001:db8:3c4d:200::1/128") is False)) with test.step("Verify R2 is no longer reachable on either IPv4 or IPv6 from PC:data"): infamy.parallel(ns0.must_not_reach("192.168.200.1"), diff --git a/test/infamy/route.py b/test/infamy/route.py index ce9ba096..180782f8 100644 --- a/test/infamy/route.py +++ b/test/infamy/route.py @@ -1,10 +1,15 @@ +""" +IETF Routing helper methods +""" + + def _get_routes(target, protocol): - xpath="/ietf-routing:routing/ribs" + xpath = "/ietf-routing:routing/ribs" rib = target.get_data(xpath)["routing"]["ribs"]["rib"] for r in rib: if r["name"] != protocol: continue - return r.get("routes", {}).get("route",{}) + return r.get("routes", {}).get("route", {}) return {} @@ -47,57 +52,68 @@ def _exist_route(target, prefix, nexthop, ip, source_protocol): return True return False -def ipv4_route_exist(target, prefix, nexthop=None,source_protocol=None): - return _exist_route(target, prefix, nexthop, "ipv4",source_protocol) -def ipv6_route_exist(target, prefix, nexthop=None,source_protocol=None): - return _exist_route(target, prefix, nexthop, "ipv6",source_protocol) +def ipv4_route_exist(target, prefix, nexthop=None, source_protocol=None): + return _exist_route(target, prefix, nexthop, "ipv4", source_protocol) + + +def ipv6_route_exist(target, prefix, nexthop=None, source_protocol=None): + return _exist_route(target, prefix, nexthop, "ipv6", source_protocol) def _get_ospf_status(target): - xpath="/ietf-routing:routing/control-plane-protocols" - rib = target.get_data(xpath)["routing"]["control-plane-protocols"].get("control-plane-protocol", {}) + xpath = "/ietf-routing:routing/control-plane-protocols" + protos = target.get_data(xpath)["routing"]["control-plane-protocols"] + rib = protos.get("control-plane-protocol", {}) for p in rib: if p["type"] == "infix-routing:ospfv2": return p.get("ospf") or p.get("ietf-ospf:ospf") + return [] + + def _get_ospf_status_area(target, area_id): - ospf=_get_ospf_status(target) + ospf = _get_ospf_status(target) for area in ospf.get("areas", {}).get("area", {}): if area["area-id"] == area_id: return area + return {} -def _get_ospf_status_area_interface(target, area_id, ifname): - area=_get_ospf_status_area(target,area_id) +def _get_ospf_status_area_interface(target, area_id, ifname): + area = _get_ospf_status_area(target, area_id) for interface in area.get("interfaces", {}).get("interface", {}): if interface.get("name") == ifname: return interface + return {} + def ospf_get_neighbor(target, area_id, ifname, neighbour_id, full=True): - ospf_interface=_get_ospf_status_area_interface(target,area_id, ifname) + ospf_interface = _get_ospf_status_area_interface(target, area_id, ifname) for neighbor in ospf_interface.get("neighbors", {}).get("neighbor", {}): if neighbor.get("neighbor-router-id") == neighbour_id: - if full == False: + if full is False: return True - if(neighbor.get("state") == "full"): + if neighbor.get("state") == "full": return True return False + def ospf_get_interface_type(target, area_id, ifname): - ospf_interface=_get_ospf_status_area_interface(target,area_id,ifname) + ospf_interface = _get_ospf_status_area_interface(target, area_id, ifname) return ospf_interface.get("interface-type", None) + def ospf_get_interface_passive(target, area_id, ifname): - ospf_interface=_get_ospf_status_area_interface(target,area_id,ifname) + ospf_interface = _get_ospf_status_area_interface(target, area_id, ifname) return ospf_interface.get("passive", False) -def ospf_is_area_nssa(target, area_id): - area=_get_ospf_status_area(target, area_id) +def ospf_is_area_nssa(target, area_id): + area = _get_ospf_status_area(target, area_id) if area.get("area-type", "") == "ietf-ospf:nssa-area": return True From 2063c94f422ac75488d42ab3ff02f19268f0e5cd Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 23 Sep 2024 12:17:18 +0200 Subject: [PATCH 27/39] confd: drop deviation for /if:interfaces-state Not the most obvious patch, but let me explain -- there will always be a conflict betweem usability and "maintainability". The ietf-interfaces yang model already declares /interfaces-state as deprecated, so we don't really need to add a deviation 'not-supported', for usability, however, it may be useful since the deviation helps cut out that tree completely. While working with ietf-routing and our companion models, I found that since it relies on ietf-interfaces, it also breaks the reload/reimport of infix-routing while developing extensions/changes: $ lint.sh -f tree ieee802-* iana-if-type@2023-01-26.yang \ ietf-interfaces@2018-02-20.yang \ infix-interfaces@2024-01-15.yang \ ietf-routing@2018-03-13.yang \ ietf-ipv4-unicast-routing@2018-03-13.yang \ ietf-ospf@2022-10-19.yang \ infix-routing@2024-09-22.yang libyang err : Not found node "interfaces-state" in path. (Schema location "/ietf-routing:routing-state/interfaces/interface".) libyang err : Target of leafref "interface" cannot be referenced because it is disabled. (Schema location "/ietf-routing:routing-state/interfaces/interface".) YANGLINT[E]: Parsing schema module "ietf-routing@2018-03-13.yang" failed. The only way to change/update models for a given build is to either, a) surgically remove offending models from output/target/, then reinstall all dependencies and hope it works better, or b) rebuild from distclean. In most cases you end up with alternative b). Signed-off-by: Joachim Wiberg --- src/confd/yang/confd.inc | 2 +- src/confd/yang/containers.inc | 2 +- ...024-01-15.yang => infix-interfaces@2024-09-23.yang} | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) rename src/confd/yang/{infix-interfaces@2024-01-15.yang => infix-interfaces@2024-09-23.yang} (92%) diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 8b96b08d..215113b0 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -37,7 +37,7 @@ MODULES=( "ieee802-ethernet-interface@2019-06-21.yang" "infix-ethernet-interface@2024-02-27.yang" "infix-factory-default@2023-06-28.yang" - "infix-interfaces@2024-01-15.yang -e vlan-filtering" + "infix-interfaces@2024-09-23.yang -e vlan-filtering" # from rousette "ietf-restconf@2017-01-26.yang" diff --git a/src/confd/yang/containers.inc b/src/confd/yang/containers.inc index 6ddd9166..7e63b0f2 100644 --- a/src/confd/yang/containers.inc +++ b/src/confd/yang/containers.inc @@ -1,6 +1,6 @@ # -*- sh -*- # REMEMBER TO UPDATE infix-interfaces ALSO IN confd.inc MODULES=( - "infix-interfaces@2024-01-15.yang -e vlan-filtering -e containers" + "infix-interfaces@2024-09-23.yang -e vlan-filtering -e containers" "infix-containers@2024-03-27.yang" ) diff --git a/src/confd/yang/infix-interfaces@2024-01-15.yang b/src/confd/yang/infix-interfaces@2024-09-23.yang similarity index 92% rename from src/confd/yang/infix-interfaces@2024-01-15.yang rename to src/confd/yang/infix-interfaces@2024-09-23.yang index 3b042e3e..6f807882 100644 --- a/src/confd/yang/infix-interfaces@2024-01-15.yang +++ b/src/confd/yang/infix-interfaces@2024-09-23.yang @@ -20,6 +20,11 @@ module infix-interfaces { contact "kernelkit@googlegroups.com"; description "Linux bridge and lag extensions for ietf-interfaces."; + revision 2024-09-23 { + description "Drop interfaces-state deviation, already marked deprecated."; + reference "internal"; + } + revision 2024-01-15 { description "Add support for container ports (CNI networks)."; reference "internal"; @@ -71,9 +76,4 @@ module infix-interfaces { config true; } } - - deviation "/if:interfaces-state" { - deviate not-supported; - description "Deprecated"; - } } From 35276cb2e8bf1c902eb067c14fd055a19f2e9986 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 22 Sep 2024 20:30:29 +0200 Subject: [PATCH 28/39] confd: rename ietf-routing prefix, ietf-r: -> rt: For consistency with the other IETF routing models, e.g., OSPF, let's use the same model prefix for internal references. Signed-off-by: Joachim Wiberg --- src/confd/yang/confd.inc | 2 +- src/confd/yang/infix-routing@2024-09-21.yang | 473 ------------------ src/confd/yang/infix-routing@2024-09-22.yang | 477 +++++++++++++++++++ 3 files changed, 478 insertions(+), 474 deletions(-) delete mode 100644 src/confd/yang/infix-routing@2024-09-21.yang create mode 100644 src/confd/yang/infix-routing@2024-09-22.yang diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 215113b0..539e5bd6 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -27,7 +27,7 @@ MODULES=( "ieee802-dot1q-types@2022-10-29.yang" "infix-ip@2024-09-16.yang" "infix-if-type@2024-01-29.yang" - "infix-routing@2024-09-21.yang" + "infix-routing@2024-09-22.yang" "ieee802-dot1ab-lldp@2022-03-15.yang" "infix-lldp@2023-08-23.yang" "infix-dhcp-client@2024-09-20.yang" diff --git a/src/confd/yang/infix-routing@2024-09-21.yang b/src/confd/yang/infix-routing@2024-09-21.yang deleted file mode 100644 index cd1fb570..00000000 --- a/src/confd/yang/infix-routing@2024-09-21.yang +++ /dev/null @@ -1,473 +0,0 @@ -module infix-routing { - yang-version 1.1; - namespace "urn:infix:routing:ns:yang:1.0"; - prefix infix-r; - import ietf-routing { - prefix ietf-r; - } - - import ietf-ipv4-unicast-routing { - prefix v4ur; - } - - import ietf-ipv6-unicast-routing { - prefix v6ur; - } - import ietf-ospf { - prefix ospf; - } - - import ietf-interfaces { - prefix if; - } - - import ietf-routing-types { - prefix rt-types; - } - revision 2024-09-21 { - description "Drop RIB source-protocol deviation for standard, add kernel proto."; - reference "internal"; - } - revision 2024-09-20 { - description "Deviate to only show supported routing types"; - reference "internal"; - } - revision 2024-09-13 { - description "Declare deviations for non-supported OSPF RPCs and Notifications"; - reference "internal"; - } - revision 2024-03-06 { - description "Obsolete leaf enable in favor for enabled in default-route-advertise"; - reference "internal"; - } - revision 2024-01-09 { - description "Add operational, area, bfd support - and other minor features"; - reference "internal"; - } - revision 2023-11-23 { - description "Limit to one instance per control plane protocol, - and change to infix (iproute2) specific source protocol names - for routes"; - reference "internal"; - } - revision 2023-10-27 { - description "Initial revision."; - reference "internal"; - } - - /* General routing */ - deviation "/ietf-r:routing/ietf-r:interfaces" { - description "Initial limitation"; - deviate not-supported; - } - - deviation "/ietf-r:routing/ietf-r:router-id" { - description "Set in OSPF"; - deviate not-supported; - } - deviation "/ietf-r:routing-state" { - description "Obsolete"; - deviate not-supported; - } - - identity kernel { - base ietf-r:routing-protocol; - description - "'Kernel' routing pseudo-protocol."; - } - - typedef infix-distribute-protocol { - type enumeration { - enum ospf { - description "Redistribute Ospf"; - } - enum static { - description "Redistribute Static routes"; - } - enum connected { - description "Redistribute Connected routes"; - } - } - } - - identity infix-routing-type { - description "Infix routing type"; - } - identity ospfv2 { - base ospf:ospfv2; - base infix-routing-type; - description "OSPv2 (IPv4) routing protocol"; - } - identity static { - base ietf-r:static; - base infix-routing-type; - description "Static route"; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ietf-r:type" { - deviate replace { - type identityref { - base infix-routing-type; - } - } - } - - /* Static routes */ - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ietf-r:static-routes/v4ur:ipv4/v4ur:route/v4ur:next-hop/v4ur:next-hop-options/v4ur:next-hop-list" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ietf-r:static-routes/v6ur:ipv6/v6ur:route/v6ur:next-hop/v6ur:next-hop-options/v6ur:next-hop-list" { - deviate not-supported; - } - /* show routes */ - deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:default-rib" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:active-route" { - deviate not-supported; - } - - deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:routes/ietf-r:route/ospf:metric" { - deviate not-supported; - } - - deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:routes/ietf-r:route/ospf:tag" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:routes/ietf-r:route/ospf:route-type" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:routes/ietf-r:route/ietf-r:active" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:routes/ietf-r:route/ietf-r:last-updated" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:description" { - deviate not-supported; - } - - /* OSPF */ - typedef infix-ospf-interface-type { - type enumeration { - enum broadcast { - description - "Specifies an OSPF broadcast multi-access network."; - } - enum point-to-point { - description - "Specifies an OSPF point-to-point network."; - } - } - } - - - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol" { - deviate add { - must "count(../control-plane-protocol[type = current()/type]) = 1" { - error-message "Only one instance per routing protocol is allowed."; - } - } - } -/* - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ietf-r:type" { - deviate add { - must "derived-from-or-self(., 'ospf:ospfv2') or "+ - "derived-from-or-self(., 'ietf-r:static')" { - description "Only OSPFv2 and Static routes are supported in Infix."; - } - } - } -*/ - augment "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf" { - description "ietf-ospf lack the setting to generate a default route"; - container default-route-advertise { - description "Distribute default route to network"; - leaf enable { - status obsolete; - description "Legacy, replaced by 'enabled'."; - type boolean; - } - leaf enabled { - description "Distribute default route"; - type boolean; - } - leaf always { - description "Always distribute default route, even if no local default route exist. - If set to false, a default route will only be distributed if a default route exist - in the local routing table."; - type boolean; - } - } - } - augment "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf" { - description "ietf-ospf does not contain redistribution - 7. How should route redistribution be configured? I see ietf-rip.yang has a separate - container for that purpose, but ietf-ospf.yang (and other IGP modules) don't do the - same. I also noticed the BGP model is using definition from ietf-routing-policy.yang. - - Different vendors handle redistribution in different ways. This could be added with - an augmentation if there were agreement. - - https://marc.info/?l=ms-ospf&m=166258444409552&w=2"; - container redistribute { - list redistribute { - key "protocol"; - description "Redistribute protocols into OSPF"; - leaf protocol { - type infix-distribute-protocol; - description "Set protocol to redistribute"; - } - } - } - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface" { - deviate add { - must "current()/name = 'lo' or count(../../../../ospf:areas/ospf:area/ospf:interfaces/ospf:interface[ospf:name=current()/name]) <= 1" { - error-message "Only one area per interface is allowed."; - } - } - } - - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:interface-type" { - deviate replace { - type infix-ospf-interface-type; - } - } - - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:auto-cost" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:graceful-restart" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:enabled" { - deviate not-supported; - } - - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:spf-control" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:preference" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:stub-router" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:topologies" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:statistics" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database-control" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:lsa-log" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:spf-log" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:fast-reroute" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:node-tags" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:mpls" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:nsr" { - deviate not-supported; - } - - /* OSPF Area */ - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:virtual-links" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:sham-links" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:ranges" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:database" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:topologies" { - deviate not-supported; - } - - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:statistics" { - deviate not-supported; - } - - /* OSPF Local RIB */ - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:local-rib/ospf:route/ospf:metric" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:local-rib/ospf:route/ospf:route-tag" { - deviate not-supported; - } - /* OSPF Area Interface */ - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:static-neighbors" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:multi-areas" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:node-flag" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:bfd/ospf:local-multiplier" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:bfd/ospf:interval-config-type" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:priority" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:fast-reroute" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:authentication" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:statistics" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:neighbors/ospf:neighbor/ospf:statistics" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:neighbors/ospf:neighbor/ospf:cost" - { - deviate not-supported; - } - - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:database" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:topologies" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:instance-id" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:interface-id" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:demand-circuit" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:ttl-security" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:lls" - { - deviate not-supported; - } - - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:mtu-ignore" - { - deviate not-supported; - } - - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:prefix-suppression" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:hello-timer" - { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:wait-timer" - { - deviate not-supported; - } - - /* OSPF database */ - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv3" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:opaque" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:external" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:summary" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:router" { - deviate not-supported; - } - deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:header" { - deviate not-supported; - } - - /* OSPF RPCs */ - deviation "/ospf:clear-neighbor" { - deviate not-supported; - } - - deviation "/ospf:clear-database" { - deviate not-supported; - } - - /* OSPF Notifications */ - deviation "/ospf:if-state-change" { - deviate not-supported; - } - - deviation "/ospf:if-config-error" { - deviate not-supported; - } - - deviation "/ospf:nbr-state-change" { - deviate not-supported; - } - - deviation "/ospf:nbr-restart-helper-status-change" { - deviate not-supported; - } - - deviation "/ospf:if-rx-bad-packet" { - deviate not-supported; - } - - deviation "/ospf:lsdb-approaching-overflow" { - deviate not-supported; - } - - deviation "/ospf:lsdb-overflow" { - deviate not-supported; - } - - deviation "/ospf:nssa-translator-status-change" { - deviate not-supported; - } - - deviation "/ospf:restart-status-change" { - deviate not-supported; - } - -} diff --git a/src/confd/yang/infix-routing@2024-09-22.yang b/src/confd/yang/infix-routing@2024-09-22.yang new file mode 100644 index 00000000..8b8c196e --- /dev/null +++ b/src/confd/yang/infix-routing@2024-09-22.yang @@ -0,0 +1,477 @@ +module infix-routing { + yang-version 1.1; + namespace "urn:infix:routing:ns:yang:1.0"; + prefix infix-rt; + import ietf-routing { + prefix rt; + } + + import ietf-ipv4-unicast-routing { + prefix v4ur; + } + + import ietf-ipv6-unicast-routing { + prefix v6ur; + } + import ietf-ospf { + prefix ospf; + } + + import ietf-interfaces { + prefix if; + } + + import ietf-routing-types { + prefix rt-types; + } + revision 2024-09-22 { + description "Adjust YANG prefix for consistency: ietf-r: -> rt:"; + reference "internal"; + } + revision 2024-09-21 { + description "Drop RIB source-protocol deviation for standard, add kernel proto."; + reference "internal"; + } + revision 2024-09-20 { + description "Deviate to only show supported routing types"; + reference "internal"; + } + revision 2024-09-13 { + description "Declare deviations for non-supported OSPF RPCs and Notifications"; + reference "internal"; + } + revision 2024-03-06 { + description "Obsolete leaf enable in favor for enabled in default-route-advertise"; + reference "internal"; + } + revision 2024-01-09 { + description "Add operational, area, bfd support + and other minor features"; + reference "internal"; + } + revision 2023-11-23 { + description "Limit to one instance per control plane protocol, + and change to infix (iproute2) specific source protocol names + for routes"; + reference "internal"; + } + revision 2023-10-27 { + description "Initial revision."; + reference "internal"; + } + + /* General routing */ + deviation "/rt:routing/rt:interfaces" { + description "Initial limitation"; + deviate not-supported; + } + + deviation "/rt:routing/rt:router-id" { + description "Set in OSPF"; + deviate not-supported; + } + deviation "/rt:routing-state" { + description "Obsolete"; + deviate not-supported; + } + + identity kernel { + base rt:routing-protocol; + description + "'Kernel' routing pseudo-protocol."; + } + + typedef infix-distribute-protocol { + type enumeration { + enum ospf { + description "Redistribute Ospf"; + } + enum static { + description "Redistribute Static routes"; + } + enum connected { + description "Redistribute Connected routes"; + } + } + } + + identity infix-routing-type { + description "Infix routing type"; + } + identity ospfv2 { + base ospf:ospfv2; + base infix-routing-type; + description "OSPv2 (IPv4) routing protocol"; + } + identity static { + base rt:static; + base infix-routing-type; + description "Static route"; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:type" { + deviate replace { + type identityref { + base infix-routing-type; + } + } + } + + /* Static routes */ + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:static-routes/v4ur:ipv4/v4ur:route/v4ur:next-hop/v4ur:next-hop-options/v4ur:next-hop-list" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:static-routes/v6ur:ipv6/v6ur:route/v6ur:next-hop/v6ur:next-hop-options/v6ur:next-hop-list" { + deviate not-supported; + } + /* show routes */ + deviation "/rt:routing/rt:ribs/rt:rib/rt:default-rib" { + deviate not-supported; + } + deviation "/rt:routing/rt:ribs/rt:rib/rt:active-route" { + deviate not-supported; + } + + deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/ospf:metric" { + deviate not-supported; + } + + deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/ospf:tag" { + deviate not-supported; + } + deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/ospf:route-type" { + deviate not-supported; + } + deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/rt:active" { + deviate not-supported; + } + deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/rt:last-updated" { + deviate not-supported; + } + deviation "/rt:routing/rt:ribs/rt:rib/rt:description" { + deviate not-supported; + } + + /* OSPF */ + typedef infix-ospf-interface-type { + type enumeration { + enum broadcast { + description + "Specifies an OSPF broadcast multi-access network."; + } + enum point-to-point { + description + "Specifies an OSPF point-to-point network."; + } + } + } + + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol" { + deviate add { + must "count(../control-plane-protocol[type = current()/type]) = 1" { + error-message "Only one instance per routing protocol is allowed."; + } + } + } +/* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:type" { + deviate add { + must "derived-from-or-self(., 'ospf:ospfv2') or "+ + "derived-from-or-self(., 'rt:static')" { + description "Only OSPFv2 and Static routes are supported in Infix."; + } + } + } +*/ + augment "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf" { + description "ietf-ospf lack the setting to generate a default route"; + container default-route-advertise { + description "Distribute default route to network"; + leaf enable { + status obsolete; + description "Legacy, replaced by 'enabled'."; + type boolean; + } + leaf enabled { + description "Distribute default route"; + type boolean; + } + leaf always { + description "Always distribute default route, even if no local default route exist. + If set to false, a default route will only be distributed if a default route exist + in the local routing table."; + type boolean; + } + } + } + augment "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf" { + description "ietf-ospf does not contain redistribution + 7. How should route redistribution be configured? I see ietf-rip.yang has a separate + container for that purpose, but ietf-ospf.yang (and other IGP modules) don't do the + same. I also noticed the BGP model is using definition from ietf-routing-policy.yang. + + Different vendors handle redistribution in different ways. This could be added with + an augmentation if there were agreement. + + https://marc.info/?l=ms-ospf&m=166258444409552&w=2"; + container redistribute { + list redistribute { + key "protocol"; + description "Redistribute protocols into OSPF"; + leaf protocol { + type infix-distribute-protocol; + description "Set protocol to redistribute"; + } + } + } + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface" { + deviate add { + must "current()/name = 'lo' or count(../../../../ospf:areas/ospf:area/ospf:interfaces/ospf:interface[ospf:name=current()/name]) <= 1" { + error-message "Only one area per interface is allowed."; + } + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:interface-type" { + deviate replace { + type infix-ospf-interface-type; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:auto-cost" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:graceful-restart" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:enabled" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:spf-control" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:preference" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:stub-router" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:topologies" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:statistics" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database-control" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:lsa-log" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:spf-log" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:fast-reroute" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:node-tags" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:mpls" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:nsr" { + deviate not-supported; + } + + /* OSPF Area */ + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:virtual-links" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:sham-links" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:ranges" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:database" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:topologies" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:statistics" { + deviate not-supported; + } + + /* OSPF Local RIB */ + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:local-rib/ospf:route/ospf:metric" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:local-rib/ospf:route/ospf:route-tag" { + deviate not-supported; + } + /* OSPF Area Interface */ + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:static-neighbors" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:multi-areas" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:node-flag" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:bfd/ospf:local-multiplier" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:bfd/ospf:interval-config-type" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:priority" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:fast-reroute" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:authentication" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:statistics" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:neighbors/ospf:neighbor/ospf:statistics" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:neighbors/ospf:neighbor/ospf:cost" + { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:database" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:topologies" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:instance-id" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:interface-id" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:demand-circuit" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:ttl-security" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:lls" + { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:mtu-ignore" + { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:prefix-suppression" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:hello-timer" + { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:wait-timer" + { + deviate not-supported; + } + + /* OSPF database */ + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv3" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:opaque" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:external" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:summary" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:router" { + deviate not-supported; + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:header" { + deviate not-supported; + } + + /* OSPF RPCs */ + deviation "/ospf:clear-neighbor" { + deviate not-supported; + } + + deviation "/ospf:clear-database" { + deviate not-supported; + } + + /* OSPF Notifications */ + deviation "/ospf:if-state-change" { + deviate not-supported; + } + + deviation "/ospf:if-config-error" { + deviate not-supported; + } + + deviation "/ospf:nbr-state-change" { + deviate not-supported; + } + + deviation "/ospf:nbr-restart-helper-status-change" { + deviate not-supported; + } + + deviation "/ospf:if-rx-bad-packet" { + deviate not-supported; + } + + deviation "/ospf:lsdb-approaching-overflow" { + deviate not-supported; + } + + deviation "/ospf:lsdb-overflow" { + deviate not-supported; + } + + deviation "/ospf:nssa-translator-status-change" { + deviate not-supported; + } + + deviation "/ospf:restart-status-change" { + deviate not-supported; + } + +} From dd3f1c39d14e5f6e872770f0408dfcb4be37ab0f Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 23 Sep 2024 10:23:42 +0200 Subject: [PATCH 29/39] confd: expand route info in operational data - Enable OSPF route metrics in RIB, only defined for OSPF - Enable 'active' route flag for RIB, same as 'selected >' in FRR - Enable 'last-updated' route field for RIB, used for uptime - Augment route in RIB with 'installed' flag, same as 'FIB *' in FRR The CLI "show ip route" command has been updated accordingly. The classic Cisco style output has been kept instead of separating the routes into Active and Backup, as discussed previously. Because when inspecting large route sets you want to compare same prefix vs distance/metric to understand why a certain route is selected. admin@example:/> show ip route PREFIX PREF NEXT-HOP PROTOCOL UPTIME > 0.0.0.0/0 1/0 192.168.0.42 static 0h2m6s * 192.168.122.1 0.0.0.0/0 5/0 192.168.122.1 static 0h2m6s 0.0.0.0/0 254/0 e1 static 0h2m6s >* 169.254.0.0/16 0/0 e1 direct 0h2m6s >* 192.168.0.0/24 1/0 192.168.122.1 static 0h2m6s >* 192.168.122.0/24 0/0 e1 direct 0h2m12s Signed-off-by: Joachim Wiberg --- src/confd/yang/infix-routing@2024-09-22.yang | 35 +++++----- src/statd/python/cli_pretty/cli_pretty.py | 68 ++++++++++++++++---- src/statd/python/yanger/yanger.py | 28 +++++++- 3 files changed, 101 insertions(+), 30 deletions(-) diff --git a/src/confd/yang/infix-routing@2024-09-22.yang b/src/confd/yang/infix-routing@2024-09-22.yang index 8b8c196e..b8b9f3b7 100644 --- a/src/confd/yang/infix-routing@2024-09-22.yang +++ b/src/confd/yang/infix-routing@2024-09-22.yang @@ -2,30 +2,32 @@ module infix-routing { yang-version 1.1; namespace "urn:infix:routing:ns:yang:1.0"; prefix infix-rt; + import ietf-routing { prefix rt; } - import ietf-ipv4-unicast-routing { prefix v4ur; } - import ietf-ipv6-unicast-routing { prefix v6ur; } import ietf-ospf { prefix ospf; } - import ietf-interfaces { prefix if; } - import ietf-routing-types { prefix rt-types; } + revision 2024-09-22 { - description "Adjust YANG prefix for consistency: ietf-r: -> rt:"; + description "- Enable OSPF route metrics in RIB + - Enable 'active' route flag for RIB + - Enable 'last-updated' route field for RIB + - Augment route in RIB with 'installed' flag + - Aadjust YANG prefix for consistency: ietf-r: -> rt:"; reference "internal"; } revision 2024-09-21 { @@ -130,26 +132,27 @@ module infix-routing { deviation "/rt:routing/rt:ribs/rt:rib/rt:active-route" { deviate not-supported; } - - deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/ospf:metric" { - deviate not-supported; - } - deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/ospf:tag" { deviate not-supported; } deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/ospf:route-type" { deviate not-supported; } - deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/rt:active" { - deviate not-supported; - } - deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/rt:last-updated" { - deviate not-supported; - } deviation "/rt:routing/rt:ribs/rt:rib/rt:description" { deviate not-supported; } + augment "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/" + + "rt:next-hop/rt:next-hop-options/rt:next-hop-list/" + + "rt:next-hop-list/rt:next-hop" { + description "IETF use 'active' for selected routes within a protocol (RIB). + This augment adds an 'installed' leaf for marking routes that + are installed in the kernel FIB with the given next-hop."; + leaf installed { + description "The presence of this leaf indicates that the route is + installed in the kernel FIB with the given next-hop."; + type empty; + } + } /* OSPF */ typedef infix-ospf-interface-type { diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 01495a3b..94f93703 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -3,6 +3,7 @@ import json import argparse import sys import re +from datetime import datetime, timezone class Pad: @@ -21,9 +22,10 @@ class PadMdb: class PadRoute: prefix = 30 - protocol = 10 - next_hop = 30 pref = 8 + next_hop = 30 + protocol = 10 + uptime = 10 class PadSoftware: @@ -88,17 +90,23 @@ class Route: self.data = data self.prefix = data.get(f'ietf-{ip}-unicast-routing:destination-prefix', '') self.protocol = data.get('source-protocol', '').split(':')[-1] + self.last_updated = data.get('last-updated', '') + self.active = data.get('active', False) self.pref = data.get('route-preference', '') + self.metric = data.get('ietf-ospf:metric', 0) self.next_hop = [] next_hop_list = get_json_data(None, self.data, 'next-hop', 'next-hop-list') if next_hop_list: for nh in next_hop_list["next-hop"]: if nh.get(f"ietf-{ip}-unicast-routing:address"): - self.next_hop.append(nh[f"ietf-{ip}-unicast-routing:address"]) + hop = nh[f"ietf-{ip}-unicast-routing:address"] elif nh.get("outgoing-interface"): - self.next_hop.append(nh["outgoing-interface"]) + hop = nh["outgoing-interface"] else: - self.next_hop.append("unspecified") + hop = "unspecified" + + fib = nh.get('infix-routing:installed', False) + self.next_hop.append((hop, fib)) else: interface = get_json_data(None, self.data, 'next-hop', 'outgoing-interface') address = get_json_data(None, self.data, 'next-hop', f'ietf-{ip}-unicast-routing:next-hop-address') @@ -116,23 +124,56 @@ class Route: def get_distance_and_metric(self): if isinstance(self.pref, int): distance = self.pref - metric = 0 + metric = self.metric else: distance, metric = 0, 0 return distance, metric + def datetime2uptime(self): + """Convert 'last-updated' string to uptime in AAhBBmCCs format.""" + if not self.last_updated: + return "0h0m0s" + + # Replace the colon in the timezone offset (e.g., +00:00 -> +0000) + pos = self.last_updated.rfind('+') + if pos != -1: + adjusted = self.last_updated[:pos] + self.last_updated[pos:].replace(':', '') + else: + adjusted = self.last_updated + + last_updated = datetime.strptime(adjusted, '%Y-%m-%dT%H:%M:%S%z') + current_time = datetime.now(timezone.utc) + uptime_delta = current_time - last_updated + + hours, remainder = divmod(int(uptime_delta.total_seconds()), 3600) + minutes, seconds = divmod(remainder, 60) + + return f"{hours}h{minutes}m{seconds}s" + def print(self): distance, metric = self.get_distance_and_metric() + uptime = self.datetime2uptime() pref = f"{distance}/{metric}" - row = f"{self.prefix:<{PadRoute.prefix}}" - row += f"{self.next_hop[0]:<{PadRoute.next_hop}}" + hop, fib = self.next_hop[0] + + row = ">" if self.active else " " + row += "*" if fib else " " + row += " " + row += f"{self.prefix:<{PadRoute.prefix}}" row += f"{pref:>{PadRoute.pref}} " + row += f"{hop:<{PadRoute.next_hop}}" row += f"{self.protocol:<{PadRoute.protocol}}" + row += f"{uptime:>{PadRoute.uptime}}" print(row) for nh in self.next_hop[1:]: - row = f"{'':<{PadRoute.prefix}}" - row += f"{nh:<{PadRoute.next_hop}}" + hop, fib = nh + row = " " + row += "*" if fib else " " + row += " " + row += f"{'':<{PadRoute.prefix}}" + row += f"{'':>{PadRoute.pref}} " + row += f"{hop:<{PadRoute.next_hop}}" print(row) @@ -555,10 +596,11 @@ def show_routing_table(json, ip): print("Error, top level \"ietf-routing:routing\" missing") sys.exit(1) - hdr = (f"{'PREFIX':<{PadRoute.prefix}}" - f"{'NEXT-HOP':<{PadRoute.next_hop}}" + hdr = (f" {'PREFIX':<{PadRoute.prefix}}" f"{'PREF':>{PadRoute.pref}} " - f"{'PROTOCOL':<{PadRoute.protocol}}") + f"{'NEXT-HOP':<{PadRoute.next_hop}}" + f"{'PROTOCOL':<{PadRoute.protocol}}" + f"{'UPTIME':>{PadRoute.uptime}}") print(Decore.invert(hdr)) for rib in get_json_data({}, json, 'ietf-routing:routing', 'ribs', 'rib'): diff --git a/src/statd/python/yanger/yanger.py b/src/statd/python/yanger/yanger.py index fc2e55fa..b2a3c332 100755 --- a/src/statd/python/yanger/yanger.py +++ b/src/statd/python/yanger/yanger.py @@ -6,7 +6,7 @@ import json import sys # (built-in module) import os import argparse -from datetime import datetime +from datetime import datetime, timedelta, timezone TESTPATH = "" logger = None @@ -230,6 +230,17 @@ def add_hardware(hw_out): insert(hw_out, "component", components) +def uptime2datetime(uptime): + """Convert uptime (HH:MM:SS) to YANG format (YYYY-MM-DDTHH:MM:SS+00:00)""" + h, m, s = map(int, uptime.split(':')) + uptime_delta = timedelta(hours=h, minutes=m, seconds=s) + current_time = datetime.now(timezone.utc) + last_updated = current_time - uptime_delta + date_timestd = last_updated.strftime('%Y-%m-%dT%H:%M:%S%z') + + return date_timestd[:-2] + ':' + date_timestd[-2:] + + def get_routes(routes, proto, data): """Populate routes from vtysh JSON output""" @@ -264,6 +275,18 @@ def get_routes(routes, proto, data): 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 + if 'ospf' in frr: + new['ietf-ospf: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] + if route.get('selected', False): + new['active'] = [None] + + new['last-updated'] = uptime2datetime(route.get('uptime', 0)) + installed = route.get('installed', False) + next_hops = [] for hop in route.get('nexthops', []): next_hop = {} @@ -271,6 +294,9 @@ def get_routes(routes, proto, data): next_hop[f'ietf-{proto}-unicast-routing:address'] = hop['ip'] elif hop.get('interfaceName'): next_hop['outgoing-interface'] = hop['interfaceName'] + # See zebra/zebra_vty.c:re_status_outpupt_char() + if installed and hop.get('fib', False): + next_hop['infix-routing:installed'] = [None] next_hops.append(next_hop) if next_hops: From 752b1f4a9492e48466e473ade7c67a352ed8af9f Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 23 Sep 2024 12:16:30 +0200 Subject: [PATCH 30/39] confd: add support for setting preference on static routes Signed-off-by: Joachim Wiberg --- src/confd/src/ietf-routing.c | 8 ++-- src/confd/yang/confd.inc | 2 +- ...-22.yang => infix-routing@2024-09-23.yang} | 42 +++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) rename src/confd/yang/{infix-routing@2024-09-22.yang => infix-routing@2024-09-23.yang} (93%) diff --git a/src/confd/src/ietf-routing.c b/src/confd/src/ietf-routing.c index e5ec4cfe..46e0cc2b 100644 --- a/src/confd/src/ietf-routing.c +++ b/src/confd/src/ietf-routing.c @@ -187,12 +187,14 @@ debug ospf nssa\n\ return 0; } -static int parse_route(struct lyd_node *parent, FILE *fp, char *ip) +static int parse_route(struct lyd_node *parent, FILE *fp, const char *ip) { - const char *outgoing_interface, *next_hop_address, *special_next_hop, *destination_prefix; + const char *outgoing_interface, *next_hop_address, *special_next_hop, + *destination_prefix, *route_preference; struct lyd_node *next_hop; destination_prefix = lydx_get_cattr(parent, "destination-prefix"); + route_preference = lydx_get_cattr(parent, "route-preference"); next_hop = lydx_get_child(parent, "next-hop"); outgoing_interface = lydx_get_cattr(next_hop, "outgoing-interface"); next_hop_address = lydx_get_cattr(next_hop, "next-hop-address"); @@ -211,7 +213,7 @@ static int parse_route(struct lyd_node *parent, FILE *fp, char *ip) fputs("reject", fp); else if (strcmp(special_next_hop, "receive") == 0) fputs("Null0", fp); - fputs("\n", fp); + fprintf(fp, " %s\n", route_preference); return SR_ERR_OK; } diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 539e5bd6..d2091178 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -27,7 +27,7 @@ MODULES=( "ieee802-dot1q-types@2022-10-29.yang" "infix-ip@2024-09-16.yang" "infix-if-type@2024-01-29.yang" - "infix-routing@2024-09-22.yang" + "infix-routing@2024-09-23.yang" "ieee802-dot1ab-lldp@2022-03-15.yang" "infix-lldp@2023-08-23.yang" "infix-dhcp-client@2024-09-20.yang" diff --git a/src/confd/yang/infix-routing@2024-09-22.yang b/src/confd/yang/infix-routing@2024-09-23.yang similarity index 93% rename from src/confd/yang/infix-routing@2024-09-22.yang rename to src/confd/yang/infix-routing@2024-09-23.yang index b8b9f3b7..1f1f633b 100644 --- a/src/confd/yang/infix-routing@2024-09-22.yang +++ b/src/confd/yang/infix-routing@2024-09-23.yang @@ -22,6 +22,14 @@ module infix-routing { prefix rt-types; } + organization "KernelKit"; + contact "kernelkit@googlegroups.com"; + description "Deviations and augments for ietf-routing and ietf-ospf."; + + revision 2024-09-23 { + description "Augment static routes with optional route-preference (distance)."; + reference "internal"; + } revision 2024-09-22 { description "- Enable OSPF route metrics in RIB - Enable 'active' route flag for RIB @@ -119,12 +127,46 @@ module infix-routing { } /* Static routes */ + typedef distance { + description "Capped IETF route-preference, 0-255, for distance."; + type rt:route-preference { + range "0..255"; + } + } + + grouping route-pref { + leaf route-preference { + description "Adjust preference between routes of same destination prefix. + + This attribute, also known as 'administrative distance', allows + for selecting the preferred route among routes with the same + destination prefix. A smaller value indicates a route that is + more preferred."; + type distance; + } + } + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:static-routes/v4ur:ipv4/v4ur:route/v4ur:next-hop/v4ur:next-hop-options/v4ur:next-hop-list" { deviate not-supported; } deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:static-routes/v6ur:ipv6/v6ur:route/v6ur:next-hop/v6ur:next-hop-options/v6ur:next-hop-list" { deviate not-supported; } + augment "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:static-routes/v4ur:ipv4/v4ur:route" { + uses route-pref { + refine route-preference { + default "1"; + } + } + } + augment "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:static-routes/v6ur:ipv6/v6ur:route" { + uses route-pref { + refine route-preference { + default "1"; + } + } + } + /* show routes */ deviation "/rt:routing/rt:ribs/rt:rib/rt:default-rib" { deviate not-supported; From b61398103ce0cb2dd5c96aec278ca5654c8d0cc2 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 23 Sep 2024 16:47:24 +0200 Subject: [PATCH 31/39] statd: adjust "show ip route" column widths per address proto Signed-off-by: Joachim Wiberg --- src/statd/python/cli_pretty/cli_pretty.py | 43 +++++++++++++++-------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 94f93703..2b0fd5d8 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -21,11 +21,23 @@ class PadMdb: class PadRoute: - prefix = 30 + dest = 30 pref = 8 next_hop = 30 - protocol = 10 - uptime = 10 + protocol = 6 + uptime = 9 + + @staticmethod + def set(ipv): + """Set default padding based on the IP version ('ipv4' or 'ipv6').""" + if ipv == 'ipv4': + PadRoute.dest = 18 + PadRoute.next_hop = 15 + elif ipv == 'ipv6': + PadRoute.dest = 43 + PadRoute.next_hop = 39 + else: + raise ValueError(f"unknown IP version: {ipv}") class PadSoftware: @@ -88,6 +100,7 @@ def remove_yang_prefix(key): class Route: def __init__(self, data, ip): self.data = data + self.ip = ip self.prefix = data.get(f'ietf-{ip}-unicast-routing:destination-prefix', '') self.protocol = data.get('source-protocol', '').split(':')[-1] self.last_updated = data.get('last-updated', '') @@ -152,6 +165,7 @@ class Route: return f"{hours}h{minutes}m{seconds}s" def print(self): + PadRoute.set(self.ip) distance, metric = self.get_distance_and_metric() uptime = self.datetime2uptime() pref = f"{distance}/{metric}" @@ -160,10 +174,10 @@ class Route: row = ">" if self.active else " " row += "*" if fib else " " row += " " - row += f"{self.prefix:<{PadRoute.prefix}}" - row += f"{pref:>{PadRoute.pref}} " - row += f"{hop:<{PadRoute.next_hop}}" - row += f"{self.protocol:<{PadRoute.protocol}}" + row += f"{self.prefix:<{PadRoute.dest}} " + row += f"{pref:>{PadRoute.pref}} " + row += f"{hop:<{PadRoute.next_hop}} " + row += f"{self.protocol:<{PadRoute.protocol}} " row += f"{uptime:>{PadRoute.uptime}}" print(row) for nh in self.next_hop[1:]: @@ -171,9 +185,9 @@ class Route: row = " " row += "*" if fib else " " row += " " - row += f"{'':<{PadRoute.prefix}}" - row += f"{'':>{PadRoute.pref}} " - row += f"{hop:<{PadRoute.next_hop}}" + row += f"{'':<{PadRoute.dest}} " + row += f"{'':>{PadRoute.pref}} " + row += f"{hop:<{PadRoute.next_hop}} " print(row) @@ -596,10 +610,11 @@ def show_routing_table(json, ip): print("Error, top level \"ietf-routing:routing\" missing") sys.exit(1) - hdr = (f" {'PREFIX':<{PadRoute.prefix}}" - f"{'PREF':>{PadRoute.pref}} " - f"{'NEXT-HOP':<{PadRoute.next_hop}}" - f"{'PROTOCOL':<{PadRoute.protocol}}" + PadRoute.set(ip) + hdr = (f" {'DESTINATION':<{PadRoute.dest}} " + f"{'PREF':>{PadRoute.pref}} " + f"{'NEXT-HOP':<{PadRoute.next_hop}} " + f"{'PROTO':<{PadRoute.protocol}} " f"{'UPTIME':>{PadRoute.uptime}}") print(Decore.invert(hdr)) From 7ac016b536adb10ad22fcdeab1727316aad3464f Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 24 Sep 2024 13:26:41 +0200 Subject: [PATCH 32/39] test: refactor test for running on hardware test bed Also, add some logs to track progress. Signed-off-by: Joachim Wiberg --- .../infix_services/services_basic/Readme.adoc | 7 +- .../infix_services/services_basic/test.py | 64 +++++++++---------- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/test/case/infix_services/services_basic/Readme.adoc b/test/case/infix_services/services_basic/Readme.adoc index 20e6b213..31bb1558 100644 --- a/test/case/infix_services/services_basic/Readme.adoc +++ b/test/case/infix_services/services_basic/Readme.adoc @@ -3,11 +3,6 @@ Verify that basic services like mDNS and LLDP can be enabled and disabled. We verify operation and non-operation by using tcpdump. -XXX: with socat in the Docker container we could speed up the LLDP - detection considerably by sending a probe: - -echo -ne '\x01\x80\xc2\x00\x00\x0e\x01\x80\xc2\x00\x00\x0e\x88\xcc\x02\x07\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | socat - UDP4-DATAGRAM:255.255.255.255:7010,broadcast - ==== Topology ifdef::topdoc[] image::../../test/case/infix_services/services_basic/topology.png[Services basic topology] @@ -24,7 +19,7 @@ endif::topdoc[] . Initialize . Set static IPv4 address and disable services . Start sniffer and enable services on target ... -. Disable services on target, verify they're not running anymore ... +. Disable services on target, verify no longer running ... <<< diff --git a/test/case/infix_services/services_basic/test.py b/test/case/infix_services/services_basic/test.py index 730aca5a..5923032f 100755 --- a/test/case/infix_services/services_basic/test.py +++ b/test/case/infix_services/services_basic/test.py @@ -1,32 +1,20 @@ #!/usr/bin/env python3 -# -# Verify that basic services like mDNS and LLDP can be enabled and -# disabled. We verify operation and non-operation by using tcpdump. -# -# XXX: with socat in the Docker container we could speed up the LLDP -# detection considerably by sending a probe: -# -# echo -ne "\x01\x80\xc2\x00\x00\x0e\x01\x80\xc2\x00\x00\x0e\x88\xcc\x02\x07\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" | socat - UDP4-DATAGRAM:255.255.255.255:7010,broadcast -# """ Services basic Verify that basic services like mDNS and LLDP can be enabled and disabled. We verify operation and non-operation by using tcpdump. - -XXX: with socat in the Docker container we could speed up the LLDP - detection considerably by sending a probe: - -echo -ne '\\x01\\x80\\xc2\\x00\\x00\\x0e\\x01\\x80\\xc2\\x00\\x00\\x0e\\x88\\xcc\\x02\\x07\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00' | socat - UDP4-DATAGRAM:255.255.255.255:7010,broadcast - """ import time import infamy + def toggle(updown): """Toggle port down/up to kick services""" _, port = env.ltop.xlate("target", "data") + act = "UP" if updown else "DOWN" + print(f"target: taking interface {port} {act} ...") target.put_config_dict("ietf-interfaces", { "interfaces": { "interface": [ @@ -38,38 +26,46 @@ def toggle(updown): } }) + def verify(enabled, sec): """Verify service traffic, or no traffic in case service not enabled""" _, hport = env.ltop.xlate("host", "data") with infamy.IsolatedMacVlan(hport) as netns: snif = infamy.Sniffer(netns, "port 5353 or ether proto 0x88cc") + act = "enabling" if enabled else "disabling" netns.addip("10.0.0.1") netns.addroute("0.0.0.0/0", "10.0.0.1") - # Put service enable/disable before starting tcpdump, because - # LLDP lingers and will send a final shutdown message that - # otherwise would get in the capture for disable. - target.put_config_dict("infix-services", { - "mdns": { - "enabled": enabled - } - }) - target.put_config_dict("ieee802-dot1ab-lldp", { - "lldp": { - "enabled": enabled - } - }) - + # Prevent old-state traffic from being captured before reconf toggle(False) + with snif: - time.sleep(1) + print("host: collecting network traffic ...") + + # Put service enable/disable before starting tcpdump, + # because LLDP lingers and will send a final shutdown + # message that otherwise would get in the capture for + # disable. + print(f"target: {act} LLDP and mDNS services ...") + target.put_config_dict("infix-services", { + "mdns": { + "enabled": enabled + } + }) + target.put_config_dict("ieee802-dot1ab-lldp", { + "lldp": { + "enabled": enabled + } + }) + toggle(True) - time.sleep(sec - 1) + time.sleep(sec) return snif.output() + with infamy.Test() as test: with test.step("Initialize"): env = infamy.Env() @@ -109,16 +105,16 @@ with infamy.Test() as test: with test.step("Start sniffer and enable services on target ..."): rc = verify(True, 10) - #print(rc.stdout) + print(rc.stdout) # breakpoint() if "10.0.0.10.5353" not in rc.stdout: test.fail() if "LLDP" not in rc.stdout: test.fail() - with test.step("Disable services on target, verify they're not running anymore ..."): + with test.step("Disable services on target, verify no longer running ..."): rc = verify(False, 10) - #print(rc.stdout) + print(rc.stdout) if "10.0.0.10.5353" in rc.stdout: test.fail() if "LLDP" in rc.stdout: From ae76030e700fcea922c18dc85de78cc739c77750 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 23 Sep 2024 21:22:17 +0200 Subject: [PATCH 33/39] test: update cli output reference for "show ip route" Signed-off-by: Joachim Wiberg --- test/case/cli/cli-output/show-routes-ipv4.txt | 24 +- test/case/cli/cli-output/show-routes-ipv6.txt | 21 +- test/case/cli/system-output/ip-4-route.json | 207 ------------ test/case/cli/system-output/ip-6-route.json | 1 - .../cli/system-output/vtysh-ip4-route.json | 302 ++++++++++++++++++ .../cli/system-output/vtysh-ip6-route.json | 294 +++++++++++++++++ 6 files changed, 618 insertions(+), 231 deletions(-) delete mode 100644 test/case/cli/system-output/ip-4-route.json delete mode 100644 test/case/cli/system-output/ip-6-route.json create mode 100644 test/case/cli/system-output/vtysh-ip4-route.json create mode 100644 test/case/cli/system-output/vtysh-ip6-route.json diff --git a/test/case/cli/cli-output/show-routes-ipv4.txt b/test/case/cli/cli-output/show-routes-ipv4.txt index c28fefe5..187eea5d 100644 --- a/test/case/cli/cli-output/show-routes-ipv4.txt +++ b/test/case/cli/cli-output/show-routes-ipv4.txt @@ -1,13 +1,11 @@ -PREFIX NEXT-HOP PREF PROTOCOL  -10.0.0.1/32 10.0.13.1 20 ospf -10.0.0.2/32 10.0.23.1 20 ospf -10.0.0.4/32 10.0.13.1 20 ospf - 10.0.23.1 -10.0.12.0/30 10.0.13.1 20 ospf - 10.0.23.1 -10.0.13.0/30 eth3 0 kernel -10.0.23.0/30 eth4 0 kernel -10.0.24.0/30 10.0.23.1 20 ospf -10.0.41.0/30 10.0.13.1 20 ospf -192.168.4.0/24 10.0.13.1 20 ospf - 10.0.23.1 + DESTINATION PREF NEXT-HOP PROTO UPTIME +>* 0.0.0.0/0 110/2 10.0.23.1 ospfv2 0h0m0s +>* 10.0.0.1/32 110/4000 10.0.13.1 ospfv2 0h0m0s + 10.0.0.3/32 110/0 lo ospfv2 0h0m0s +>* 10.0.0.3/32 0/0 lo direct 0h0m0s + 10.0.13.0/30 110/2000 e5 ospfv2 0h0m0s +>* 10.0.13.0/30 0/0 e5 direct 0h0m0s + 10.0.23.0/30 110/1 e6 ospfv2 0h0m0s +>* 10.0.23.0/30 0/0 e6 direct 0h0m0s + 192.168.3.0/24 110/1 e2 ospfv2 0h0m0s +>* 192.168.3.0/24 0/0 e2 direct 0h0m0s diff --git a/test/case/cli/cli-output/show-routes-ipv6.txt b/test/case/cli/cli-output/show-routes-ipv6.txt index d1bad72b..8fa8250d 100644 --- a/test/case/cli/cli-output/show-routes-ipv6.txt +++ b/test/case/cli/cli-output/show-routes-ipv6.txt @@ -1,10 +1,11 @@ -PREFIX NEXT-HOP PREF PROTOCOL  -2001:db8:3c4d:50::/64 eth4 256 kernel -2001:db8:3c4d:200::1/128 lo 256 kernel -fe80::/64 eth5 256 kernel -fe80::/64 eth3 256 kernel -fe80::/64 eth2 256 kernel -fe80::/64 eth1 256 kernel -fe80::/64 eth0 256 kernel -fe80::/64 eth4 256 kernel -::/0 2001:db8:3c4d:50::1 20 static + DESTINATION PREF NEXT-HOP PROTO UPTIME +>* ::/0 1/0 2001:db8:3c4d:50::1 static 0h0m0s +>* 2001:db8:3c4d:50::/64 0/0 e6 direct 0h0m0s +>* 2001:db8:3c4d:200::1/128 0/0 lo direct 0h0m0s + * fe80::/64 0/0 e7 direct 0h0m0s + * fe80::/64 0/0 e6 direct 0h0m0s + * fe80::/64 0/0 e5 direct 0h0m0s + * fe80::/64 0/0 e4 direct 0h0m0s + * fe80::/64 0/0 e3 direct 0h0m0s + * fe80::/64 0/0 e2 direct 0h0m0s +>* fe80::/64 0/0 e1 direct 0h0m0s diff --git a/test/case/cli/system-output/ip-4-route.json b/test/case/cli/system-output/ip-4-route.json deleted file mode 100644 index 1065855b..00000000 --- a/test/case/cli/system-output/ip-4-route.json +++ /dev/null @@ -1,207 +0,0 @@ -[ - { - "type": "unicast", - "dst": "10.0.0.1", - "nhid": 33, - "gateway": "10.0.13.1", - "dev": "eth3", - "protocol": "ospf", - "scope": "global", - "metric": 20, - "flags": [], - "nh_info": { - "id": 33, - "gateway": "10.0.13.1", - "dev": "eth3", - "scope": "link", - "protocol": "zebra", - "flags": [] - } - }, - { - "type": "unicast", - "dst": "10.0.0.2", - "nhid": 34, - "gateway": "10.0.23.1", - "dev": "eth4", - "protocol": "ospf", - "scope": "global", - "metric": 20, - "flags": [], - "nh_info": { - "id": 34, - "gateway": "10.0.23.1", - "dev": "eth4", - "scope": "link", - "protocol": "zebra", - "flags": [] - } - }, - { - "type": "unicast", - "dst": "10.0.0.4", - "nhid": 35, - "protocol": "ospf", - "scope": "global", - "metric": 20, - "flags": [], - "nh_info": { - "id": 35, - "group": [ - { - "id": 33 - }, - { - "id": 34 - } - ], - "scope": "global", - "protocol": "zebra", - "flags": [] - }, - "nexthops": [ - { - "gateway": "10.0.13.1", - "dev": "eth3", - "weight": 1, - "flags": [] - }, - { - "gateway": "10.0.23.1", - "dev": "eth4", - "weight": 1, - "flags": [] - } - ] - }, - { - "type": "unicast", - "dst": "10.0.12.0/30", - "nhid": 35, - "protocol": "ospf", - "scope": "global", - "metric": 20, - "flags": [], - "nh_info": { - "id": 35, - "group": [ - { - "id": 33 - }, - { - "id": 34 - } - ], - "scope": "global", - "protocol": "zebra", - "flags": [] - }, - "nexthops": [ - { - "gateway": "10.0.13.1", - "dev": "eth3", - "weight": 1, - "flags": [] - }, - { - "gateway": "10.0.23.1", - "dev": "eth4", - "weight": 1, - "flags": [] - } - ] - }, - { - "type": "unicast", - "dst": "10.0.13.0/30", - "dev": "eth3", - "protocol": "kernel", - "scope": "link", - "prefsrc": "10.0.13.2", - "flags": [] - }, - { - "type": "unicast", - "dst": "10.0.23.0/30", - "dev": "eth4", - "protocol": "kernel", - "scope": "link", - "prefsrc": "10.0.23.2", - "flags": [] - }, - { - "type": "unicast", - "dst": "10.0.24.0/30", - "nhid": 34, - "gateway": "10.0.23.1", - "dev": "eth4", - "protocol": "ospf", - "scope": "global", - "metric": 20, - "flags": [], - "nh_info": { - "id": 34, - "gateway": "10.0.23.1", - "dev": "eth4", - "scope": "link", - "protocol": "zebra", - "flags": [] - } - }, - { - "type": "unicast", - "dst": "10.0.41.0/30", - "nhid": 33, - "gateway": "10.0.13.1", - "dev": "eth3", - "protocol": "ospf", - "scope": "global", - "metric": 20, - "flags": [], - "nh_info": { - "id": 33, - "gateway": "10.0.13.1", - "dev": "eth3", - "scope": "link", - "protocol": "zebra", - "flags": [] - } - }, - { - "type": "unicast", - "dst": "192.168.4.0/24", - "nhid": 35, - "protocol": "ospf", - "scope": "global", - "metric": 20, - "flags": [], - "nh_info": { - "id": 35, - "group": [ - { - "id": 33 - }, - { - "id": 34 - } - ], - "scope": "global", - "protocol": "zebra", - "flags": [] - }, - "nexthops": [ - { - "gateway": "10.0.13.1", - "dev": "eth3", - "weight": 1, - "flags": [] - }, - { - "gateway": "10.0.23.1", - "dev": "eth4", - "weight": 1, - "flags": [] - } - ] - } -] diff --git a/test/case/cli/system-output/ip-6-route.json b/test/case/cli/system-output/ip-6-route.json deleted file mode 100644 index b3793834..00000000 --- a/test/case/cli/system-output/ip-6-route.json +++ /dev/null @@ -1 +0,0 @@ -[{"type":"unicast","dst":"2001:db8:3c4d:50::/64","dev":"eth4","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"2001:db8:3c4d:200::1","dev":"lo","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"fe80::/64","dev":"eth5","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"fe80::/64","dev":"eth3","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"fe80::/64","dev":"eth2","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"fe80::/64","dev":"eth1","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"fe80::/64","dev":"eth0","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"fe80::/64","dev":"eth4","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"default","nhid":23,"gateway":"2001:db8:3c4d:50::1","dev":"eth4","protocol":"static","scope":"global","metric":20,"flags":[],"pref":"medium","nh_info":{"id":23,"gateway":"2001:db8:3c4d:50::1","dev":"eth4","scope":"link","protocol":"zebra","flags":[]}}] diff --git a/test/case/cli/system-output/vtysh-ip4-route.json b/test/case/cli/system-output/vtysh-ip4-route.json new file mode 100644 index 00000000..e263ea0e --- /dev/null +++ b/test/case/cli/system-output/vtysh-ip4-route.json @@ -0,0 +1,302 @@ +{ + "0.0.0.0/0":[ + { + "prefix":"0.0.0.0/0", + "prefixLen":0, + "protocol":"ospf", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":110, + "metric":2, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":42, + "installedNexthopGroupId":42, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"10.0.23.1", + "afi":"ipv4", + "interfaceIndex":7, + "interfaceName":"e6", + "active":true, + "weight":1 + } + ] + } + ], + "10.0.0.1/32":[ + { + "prefix":"10.0.0.1/32", + "prefixLen":32, + "protocol":"ospf", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":110, + "metric":4000, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":43, + "installedNexthopGroupId":43, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"10.0.13.1", + "afi":"ipv4", + "interfaceIndex":6, + "interfaceName":"e5", + "active":true, + "weight":1 + } + ] + } + ], + "10.0.0.3/32":[ + { + "prefix":"10.0.0.3/32", + "prefixLen":32, + "protocol":"ospf", + "vrfId":0, + "vrfName":"default", + "distance":110, + "metric":0, + "table":254, + "internalStatus":0, + "internalFlags":0, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":30, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":1, + "directlyConnected":true, + "interfaceIndex":1, + "interfaceName":"lo", + "active":true, + "weight":1 + } + ] + }, + { + "prefix":"10.0.0.3/32", + "prefixLen":32, + "protocol":"connected", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":25, + "installedNexthopGroupId":25, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceIndex":1, + "interfaceName":"lo", + "active":true + } + ] + } + ], + "10.0.13.0/30":[ + { + "prefix":"10.0.13.0/30", + "prefixLen":30, + "protocol":"ospf", + "vrfId":0, + "vrfName":"default", + "distance":110, + "metric":2000, + "table":254, + "internalStatus":0, + "internalFlags":0, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":31, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":1, + "directlyConnected":true, + "interfaceIndex":6, + "interfaceName":"e5", + "active":true, + "weight":1 + } + ] + }, + { + "prefix":"10.0.13.0/30", + "prefixLen":30, + "protocol":"connected", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":20, + "installedNexthopGroupId":20, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceIndex":6, + "interfaceName":"e5", + "active":true + } + ] + } + ], + "10.0.23.0/30":[ + { + "prefix":"10.0.23.0/30", + "prefixLen":30, + "protocol":"ospf", + "vrfId":0, + "vrfName":"default", + "distance":110, + "metric":1, + "table":254, + "internalStatus":0, + "internalFlags":0, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":32, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":1, + "directlyConnected":true, + "interfaceIndex":7, + "interfaceName":"e6", + "active":true, + "weight":1 + } + ] + }, + { + "prefix":"10.0.23.0/30", + "prefixLen":30, + "protocol":"connected", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":23, + "installedNexthopGroupId":23, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceIndex":7, + "interfaceName":"e6", + "active":true + } + ] + } + ], + "192.168.3.0/24":[ + { + "prefix":"192.168.3.0/24", + "prefixLen":24, + "protocol":"ospf", + "vrfId":0, + "vrfName":"default", + "distance":110, + "metric":1, + "table":254, + "internalStatus":0, + "internalFlags":0, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":33, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":1, + "directlyConnected":true, + "interfaceIndex":3, + "interfaceName":"e2", + "active":true, + "weight":1 + } + ] + }, + { + "prefix":"192.168.3.0/24", + "prefixLen":24, + "protocol":"connected", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":17, + "installedNexthopGroupId":17, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceIndex":3, + "interfaceName":"e2", + "active":true + } + ] + } + ] +} diff --git a/test/case/cli/system-output/vtysh-ip6-route.json b/test/case/cli/system-output/vtysh-ip6-route.json new file mode 100644 index 00000000..35cd6794 --- /dev/null +++ b/test/case/cli/system-output/vtysh-ip6-route.json @@ -0,0 +1,294 @@ +{ + "::/0":[ + { + "prefix":"::/0", + "prefixLen":0, + "protocol":"static", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":1, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":73, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":24, + "installedNexthopGroupId":24, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"2001:db8:3c4d:50::1", + "afi":"ipv6", + "interfaceIndex":7, + "interfaceName":"e6", + "active":true, + "weight":1 + } + ] + } + ], + "2001:db8:3c4d:50::/64":[ + { + "prefix":"2001:db8:3c4d:50::/64", + "prefixLen":64, + "protocol":"connected", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":15, + "installedNexthopGroupId":15, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceIndex":7, + "interfaceName":"e6", + "active":true + } + ] + } + ], + "2001:db8:3c4d:200::1/128":[ + { + "prefix":"2001:db8:3c4d:200::1/128", + "prefixLen":128, + "protocol":"connected", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":13, + "installedNexthopGroupId":13, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceIndex":1, + "interfaceName":"lo", + "active":true + } + ] + } + ], + "fe80::/64":[ + { + "prefix":"fe80::/64", + "prefixLen":64, + "protocol":"connected", + "vrfId":0, + "vrfName":"default", + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":0, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":14, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceIndex":8, + "interfaceName":"e7", + "active":true + } + ] + }, + { + "prefix":"fe80::/64", + "prefixLen":64, + "protocol":"connected", + "vrfId":0, + "vrfName":"default", + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":0, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":15, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceIndex":7, + "interfaceName":"e6", + "active":true + } + ] + }, + { + "prefix":"fe80::/64", + "prefixLen":64, + "protocol":"connected", + "vrfId":0, + "vrfName":"default", + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":0, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":16, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceIndex":6, + "interfaceName":"e5", + "active":true + } + ] + }, + { + "prefix":"fe80::/64", + "prefixLen":64, + "protocol":"connected", + "vrfId":0, + "vrfName":"default", + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":0, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":17, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceIndex":5, + "interfaceName":"e4", + "active":true + } + ] + }, + { + "prefix":"fe80::/64", + "prefixLen":64, + "protocol":"connected", + "vrfId":0, + "vrfName":"default", + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":0, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":18, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceIndex":4, + "interfaceName":"e3", + "active":true + } + ] + }, + { + "prefix":"fe80::/64", + "prefixLen":64, + "protocol":"connected", + "vrfId":0, + "vrfName":"default", + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":0, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":19, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceIndex":3, + "interfaceName":"e2", + "active":true + } + ] + }, + { + "prefix":"fe80::/64", + "prefixLen":64, + "protocol":"connected", + "vrfId":0, + "vrfName":"default", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthopGroupId":20, + "installedNexthopGroupId":20, + "uptime":"00:00:00", + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceIndex":2, + "interfaceName":"e1", + "active":true + } + ] + } + ] +} From 8ea0af6385f1d6d8f43ff1524f313d442bbf71a3 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 23 Sep 2024 23:02:37 +0200 Subject: [PATCH 34/39] test: refactor infamy route helpers Signed-off-by: Joachim Wiberg --- test/case/ietf_routing/ospf_basic/test.py | 4 ++-- test/case/ietf_routing/ospf_multiarea/test.py | 24 +++++++++---------- .../ospf_unnumbered_interface/test.py | 6 ++--- test/infamy/route.py | 21 +++++++++------- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/test/case/ietf_routing/ospf_basic/test.py b/test/case/ietf_routing/ospf_basic/test.py index 4fedecd4..432a2787 100755 --- a/test/case/ietf_routing/ospf_basic/test.py +++ b/test/case/ietf_routing/ospf_basic/test.py @@ -171,8 +171,8 @@ with infamy.Test() as test: config_target2(R2, R2link)) with test.step("Wait for OSPF routes"): print("Waiting for OSPF routes..") - until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", source_protocol="ietf-ospf:ospfv2"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", proto="ietf-ospf:ospfv2"), attempts=200) with test.step("Test connectivity from PC:data to 192.168.200.1"): _, hport0 = env.ltop.xlate("PC", "data") diff --git a/test/case/ietf_routing/ospf_multiarea/test.py b/test/case/ietf_routing/ospf_multiarea/test.py index 2439b7f2..df43da8c 100755 --- a/test/case/ietf_routing/ospf_multiarea/test.py +++ b/test/case/ietf_routing/ospf_multiarea/test.py @@ -550,17 +550,17 @@ with infamy.Test() as test: with test.step("Wait for routes from OSPF on all routers"): print("Waiting for routes from OSPF") - until(lambda: route.ipv4_route_exist(R1, "10.0.0.2/32", nexthop="10.0.12.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) - until(lambda: route.ipv4_route_exist(R1, "10.0.0.3/32", nexthop="10.0.13.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) - until(lambda: route.ipv4_route_exist(R1, "10.0.0.4/32", nexthop="10.0.41.1", source_protocol="ietf-ospf:ospfv2"), attempts=200) - until(lambda: route.ipv4_route_exist(R1, "192.168.4.0/24", nexthop="10.0.41.1", source_protocol="ietf-ospf:ospfv2"), attempts=200) - until(lambda: route.ipv4_route_exist(R1, "10.0.24.0/30", nexthop="10.0.41.1", source_protocol="ietf-ospf:ospfv2"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "10.0.0.1/32", nexthop="10.0.23.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "10.0.0.3/32", nexthop="10.0.23.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "10.0.0.4/32", nexthop="10.0.24.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) - until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0", nexthop="10.0.23.1", source_protocol="ietf-ospf:ospfv2"), attempts=200) - until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "10.0.13.0/30", nexthop="10.0.23.2", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "10.0.0.2/32", nexthop="10.0.12.2", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "10.0.0.3/32", nexthop="10.0.13.2", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "10.0.0.4/32", nexthop="10.0.41.1", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "192.168.4.0/24", nexthop="10.0.41.1", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "10.0.24.0/30", nexthop="10.0.41.1", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "10.0.0.1/32", nexthop="10.0.23.2", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "10.0.0.3/32", nexthop="10.0.23.2", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "10.0.0.4/32", nexthop="10.0.24.2", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0", nexthop="10.0.23.1", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "10.0.13.0/30", nexthop="10.0.23.2", proto="ietf-ospf:ospfv2"), attempts=200) with test.step("Verify Area 0.0.0.1 on R3 is NSSA area"): assert(route.ospf_is_area_nssa(R3, "0.0.0.1")) @@ -602,7 +602,7 @@ with infamy.Test() as test: disable_link(R1, R1ring2) with test.step("Verify that the route to 10.0.0.3 from PC:data4, go through 10.0.24.1"): - until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.24.1", source_protocol="ietf-ospf:ospfv2"), attempts=100) + until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.24.1", proto="ietf-ospf:ospfv2"), attempts=100) until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2") is False, attempts=10) trace = ns0.traceroute("10.0.0.3") assert len(trace) == 3 diff --git a/test/case/ietf_routing/ospf_unnumbered_interface/test.py b/test/case/ietf_routing/ospf_unnumbered_interface/test.py index 1512cb55..01290d7d 100755 --- a/test/case/ietf_routing/ospf_unnumbered_interface/test.py +++ b/test/case/ietf_routing/ospf_unnumbered_interface/test.py @@ -166,9 +166,9 @@ with infamy.Test() as test: lambda: config_target2(R2, R2link)) with test.step("Wait for OSPF routes"): print("Waiting for OSPF routes..") - until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", source_protocol="ietf-ospf:ospfv2"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", source_protocol="ietf-ospf:ospfv2"), attempts=200) - until(lambda: route.ipv4_route_exist(R2, "192.168.10.0/24", source_protocol="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "192.168.10.0/24", proto="ietf-ospf:ospfv2"), attempts=200) with test.step("Check interface type"): assert(route.ospf_get_interface_type(R1, "0.0.0.0", R1link) == "point-to-point") diff --git a/test/infamy/route.py b/test/infamy/route.py index 180782f8..84bb96db 100644 --- a/test/infamy/route.py +++ b/test/infamy/route.py @@ -13,19 +13,22 @@ def _get_routes(target, protocol): return {} -def _exist_route(target, prefix, nexthop, ip, source_protocol): +def _exist_route(target, dest, nexthop=None, ip=None, proto=None, pref=None): routes = _get_routes(target, ip) for r in routes: # netconf presents destination-prefix, restconf prefix with model - p = r.get("destination-prefix") or \ + dst = r.get("destination-prefix") or \ r.get(f"ietf-{ip}-unicast-routing:destination-prefix") - if p != prefix: + if dst != dest: continue - if source_protocol and r.get("source-protocol") != source_protocol: + if proto is not None and r.get("source-protocol") != proto: return False - if nexthop: + if pref is not None and r.get("route-preference") != pref: + return False + + if nexthop is not None: nh = r["next-hop"] if not nh: return False @@ -53,12 +56,12 @@ def _exist_route(target, prefix, nexthop, ip, source_protocol): return False -def ipv4_route_exist(target, prefix, nexthop=None, source_protocol=None): - return _exist_route(target, prefix, nexthop, "ipv4", source_protocol) +def ipv4_route_exist(target, dest, nexthop=None, proto=None, pref=None): + return _exist_route(target, dest, nexthop=nexthop, ip="ipv4", proto=proto, pref=pref) -def ipv6_route_exist(target, prefix, nexthop=None, source_protocol=None): - return _exist_route(target, prefix, nexthop, "ipv6", source_protocol) +def ipv6_route_exist(target, dest, nexthop=None, proto=None, pref=None): + return _exist_route(target, dest, nexthop=nexthop, ip="ipv6", proto=proto, pref=pref) def _get_ospf_status(target): From dc1a2c39e817911dfecb603f09f4e1a363ee9c0c Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 23 Sep 2024 23:03:37 +0200 Subject: [PATCH 35/39] test: update with static route canary Now that all routes acquired from DHCP (and ZeroConf) are installed in the kernel FIB via Frr (staticd -> zebra -> kernel), instead of directly to the kernel FIB, let's extend this test slightly. Add a canary static route that is installed before the DHCP client is even started. This canary route should be installed along with the DHCP (classless) route(s) when the interface comes up and acquires a DHCP lease. We take this opportunity to also verify that the route-preference is properly carried over from test to operational RIB status. Signed-off-by: Joachim Wiberg --- test/case/infix_dhcp/dhcp_routes/test.py | 87 ++++++++++++++++-------- 1 file changed, 60 insertions(+), 27 deletions(-) diff --git a/test/case/infix_dhcp/dhcp_routes/test.py b/test/case/infix_dhcp/dhcp_routes/test.py index c20f717c..50dfc639 100755 --- a/test/case/infix_dhcp/dhcp_routes/test.py +++ b/test/case/infix_dhcp/dhcp_routes/test.py @@ -1,47 +1,80 @@ #!/usr/bin/env python3 -# Verify DHCP option 121 (staticroutes) is used over option 3 """ -DHCP router +DHCP option 121 vs option 3 -Verify DHCP option 121 (staticroutes) is used over option 3 and that the routes exist in -the operational datastore +Verify DHCP option 121 (staticroutes) is used over option 3 and that the +routes exist in the operational datastore. + +Installing unrelated routes from a DHCP server should not affect already +existing routes. To verify this a canary route is set up in the client +before initiating DHCP. This canary route does not need to be reachable +before a DHCP lease has been acquired. """ -import time -import infamy, infamy.dhcp -import infamy.iface as iface +import infamy +import infamy.dhcp import infamy.route as route from infamy.util import until with infamy.Test() as test: - PREFIX = '10.0.0.0/24' - ROUTER = '192.168.0.254' - with test.step("Initialize"): + PREFIX = '10.0.0.0/24' + ROUTER = '192.168.0.254' + CANARY = '20.0.0.0/24' + CANHOP = '192.168.0.2' + + with test.step("Setting up client"): env = infamy.Env() client = env.attach("client", "mgmt") _, host = env.ltop.xlate("host", "data") + _, port = env.ltop.xlate("client", "data") + + # Install canary route to smoke out any regressions in + # how the DHCP client installs routes in the kernel FIB + client.put_config_dict("ietf-routing", { + "routing": { + "control-plane-protocols": { + "control-plane-protocol": [{ + "type": "infix-routing:static", + "name": "default", + "static-routes": { + "ipv4": { + "route": [{ + "destination-prefix": CANARY, + "next-hop": { + "next-hop-address": CANHOP + }, + "infix-routing:route-preference": 250 + }] + } + } + }] + } + } + }) + + client.put_config_dict("infix-dhcp-client", { + "dhcp-client": { + "client-if": [{ + "if-name": f"{port}", + "option": [ + {"name": "router"}, + {"name": "staticroutes"} + ] + }] + } + }) with infamy.IsolatedMacVlan(host) as netns: netns.addip("192.168.0.1") with infamy.dhcp.Server(netns, prefix=PREFIX, router=ROUTER): - _, port = env.ltop.xlate("client", "data") - config = { - "dhcp-client": { - "client-if": [{ - "if-name": f"{port}", - "option": [ - { "name": "router" }, - { "name": "staticroutes" } - ] - }] - } - } - client.put_config_dict("infix-dhcp-client", config) - - with test.step(f"Verify client sets up correct route via {ROUTER}"): - # Wait for client to set the classless static route, option 121 + with test.step("Verify client use classless routes, option 121"): until(lambda: route.ipv4_route_exist(client, PREFIX, ROUTER)) - # Ensure client did *not* use option 3 (option 121 takes precedence) + + with test.step("Verify client did *not* use option 3"): if route.ipv4_route_exist(client, "0.0.0.0/0", ROUTER): test.fail() + with test.step("Verify client has canary route, 20.0.0.0/24"): + until(lambda: route.ipv4_route_exist(client, CANARY, + CANHOP, pref=250)) + test.succeed() From 5aed8e048833aac76fb94f870d26b4db7f372de3 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 23 Sep 2024 22:48:30 +0200 Subject: [PATCH 36/39] test: update specifications Signed-off-by: Joachim Wiberg --- .../ietf_interfaces/ipv4_autoconf/Readme.adoc | 12 +++++----- .../ietf_routing/static_routing/Readme.adoc | 6 ++--- test/case/ietf_routing/static_routing/test.py | 8 +++---- test/case/infix_dhcp/dhcp_routes/Readme.adoc | 22 +++++++++++++------ 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/test/case/ietf_interfaces/ipv4_autoconf/Readme.adoc b/test/case/ietf_interfaces/ipv4_autoconf/Readme.adoc index 7781348f..1d66d60c 100644 --- a/test/case/ietf_interfaces/ipv4_autoconf/Readme.adoc +++ b/test/case/ietf_interfaces/ipv4_autoconf/Readme.adoc @@ -1,22 +1,21 @@ === IPv4 link-local - ==== Description -Verifies that linklocal (ZeroConf) work as expected +Verifies that link-local (IPv4LL/ZeroConf) address assignment work as +expected. Checks random address, the request-address setting, and +address removal on autoconf disable. ==== Topology - ifdef::topdoc[] image::../../test/case/ietf_interfaces/ipv4_autoconf/topology.png[IPv4 link-local topology] endif::topdoc[] ifndef::topdoc[] ifdef::testgroup[] -image::ipv4_autoconf/topology.png[IPv4 linklocal topology] +image::ipv4_autoconf/topology.png[IPv4 link-local topology] endif::testgroup[] ifndef::testgroup[] -image::topology.png[IPv4 linklocal topology] +image::topology.png[IPv4 link-local topology] endif::testgroup[] endif::topdoc[] - ==== Test sequence . Initialize . Configure an interface with IPv4 ZeroConf IP @@ -27,3 +26,4 @@ endif::topdoc[] <<< + diff --git a/test/case/ietf_routing/static_routing/Readme.adoc b/test/case/ietf_routing/static_routing/Readme.adoc index 0ac75a07..322faa67 100644 --- a/test/case/ietf_routing/static_routing/Readme.adoc +++ b/test/case/ietf_routing/static_routing/Readme.adoc @@ -1,7 +1,7 @@ === Static routing ==== Description -Test that it is possible to use static routes (IPv4 and IPv6) -works as expected +Verify that it is possible to add static routes, both IPv4 and IPv6, and +that data forwarding works as expected via an intermediate device. ==== Topology ifdef::topdoc[] @@ -19,7 +19,7 @@ endif::topdoc[] . Configure targets . Wait for routes . Configure host addresses and routes -. Verify that dut2 is reachable on 192.168.200.1 from PC:data +. Verify that R2 is reachable on 192.168.200.1 from PC:data . Verify that R2 is reachable on 2001:db8:3c4d:200::1 from PC:data . Remove all static routes from R1 . Verify R2 is no longer reachable on either IPv4 or IPv6 from PC:data diff --git a/test/case/ietf_routing/static_routing/test.py b/test/case/ietf_routing/static_routing/test.py index 84b69c62..4e744cb6 100755 --- a/test/case/ietf_routing/static_routing/test.py +++ b/test/case/ietf_routing/static_routing/test.py @@ -3,7 +3,7 @@ # | # +--------------+ +-------------+ # | |192.168.50.0/24 | | -# | DUT1 .1 +----------------+ .2 DUT2 |-192.168.200.1/32 (lo) +# | R1 .1 +----------------+ .2 R2 |-192.168.200.1/32 (lo) # | .1 | | .1 | # +--+------+----+ +---+-----+---+ # | | | @@ -18,8 +18,8 @@ """ Static routing -Test that it is possible to use static routes (IPv4 and IPv6) -works as expected +Verify that it is possible to add static routes, both IPv4 and IPv6, and +that data forwarding works as expected via an intermediate device. """ import infamy import infamy.route as route @@ -216,7 +216,7 @@ with infamy.Test() as test: ns0.addip("192.168.10.2") ns0.addroute("192.168.200.1/32", "192.168.10.1") - with test.step("Verify that dut2 is reachable on 192.168.200.1 from PC:data"): + with test.step("Verify that R2 is reachable on 192.168.200.1 from PC:data"): ns0.must_reach("192.168.200.1") with test.step("Verify that R2 is reachable on 2001:db8:3c4d:200::1 from PC:data"): diff --git a/test/case/infix_dhcp/dhcp_routes/Readme.adoc b/test/case/infix_dhcp/dhcp_routes/Readme.adoc index 472d1617..d3c167d8 100644 --- a/test/case/infix_dhcp/dhcp_routes/Readme.adoc +++ b/test/case/infix_dhcp/dhcp_routes/Readme.adoc @@ -1,22 +1,30 @@ -=== DHCP router +=== DHCP option 121 vs option 3 ==== Description -Verify DHCP option 121 (staticroutes) is used over option 3 and that the routes exist in -the operational datastore +Verify DHCP option 121 (staticroutes) is used over option 3 and that the +routes exist in the operational datastore. + +Installing unrelated routes from a DHCP server should not affect already +existing routes. To verify this a canary route is set up in the client +before initiating DHCP. This canary route does not need to be reachable +before a DHCP lease has been acquired. ==== Topology ifdef::topdoc[] -image::../../test/case/infix_dhcp/dhcp_routes/topology.png[DHCP router topology] +image::../../test/case/infix_dhcp/dhcp_routes/topology.png[DHCP option 121 vs option 3 topology] endif::topdoc[] ifndef::topdoc[] ifdef::testgroup[] -image::dhcp_routes/topology.png[DHCP router topology] +image::dhcp_routes/topology.png[DHCP option 121 vs option 3 topology] endif::testgroup[] ifndef::testgroup[] -image::topology.png[DHCP router topology] +image::topology.png[DHCP option 121 vs option 3 topology] endif::testgroup[] endif::topdoc[] ==== Test sequence -. Initialize +. Setting up client +. Verify client use classless routes, option 121 +. Verify client did *not* use option 3 +. Verify client has canary route, 20.0.0.0/24 <<< From 1101d303962929a903c60ac865fe49d6afcabafb Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 23 Sep 2024 22:48:49 +0200 Subject: [PATCH 37/39] doc: break out testing requriements from devguide Signed-off-by: Joachim Wiberg --- doc/developers-guide.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/doc/developers-guide.md b/doc/developers-guide.md index c88e3dbf..d90d88ea 100644 --- a/doc/developers-guide.md +++ b/doc/developers-guide.md @@ -70,17 +70,6 @@ $ sudo apt install bc binutils build-essential bzip2 cpio \ autopoint bison flex autoconf automake ``` -For testing, a few more tools and services are required on your system: - -```bash -$ sudo apt install jq graphviz qemu-system-x86 qemu-system-arm \ - ethtool gdb-multiarch tcpdump tshark -``` - -To be able to build the test specification you also need: -```bash -$ sudo apt-get install python3-graphviz ruby-asciidoctor-pdf -``` > For details, see the Getting Started and System Requirements sections > of the [excellent manual][1]. @@ -98,6 +87,23 @@ To see available defconfigs for supported targets, use: make list-defconfigs +### Test + +Working with the regression test framework, *Infamy*, a few more tools +and services are required on your system: + +```bash +$ sudo apt install jq graphviz qemu-system-x86 qemu-system-arm \ + ethtool gdb-multiarch tcpdump tshark +``` + +To be able to build the test specification you also need: + +```bash +$ sudo apt-get install python3-graphviz ruby-asciidoctor-pdf +``` + + Development ----------- From 8b60ddf70a00e442c38d2adcbc70f4dbce0580fb Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 24 Sep 2024 11:18:21 +0200 Subject: [PATCH 38/39] doc: update static routing and add route pref section This patch mainly updates the static routing chapter, and adds a new section on route preferences, but it also makes slight touch-ups at a few other places as well. Signed-off-by: Joachim Wiberg --- doc/networking.md | 188 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 146 insertions(+), 42 deletions(-) diff --git a/doc/networking.md b/doc/networking.md index a800829c..7d5e26c7 100644 --- a/doc/networking.md +++ b/doc/networking.md @@ -664,6 +664,7 @@ admin@example:/> ipv6 ::1/128 (static) admin@example:/> + #### Stateless Auto-configuration of Global IPv6 Address ![Auto-configuration of global IPv6](img/ip-address-example-ipv6-auto-global.svg) @@ -700,6 +701,7 @@ below. ipv6 ::1/128 (static) admin@example:/> + #### Random Link Identifiers for IPv6 Stateless Autoconfiguration ![Auto-configuration of global IPv6](img/ip-address-example-ipv6-auto-global.svg) @@ -738,7 +740,9 @@ possible to specify use of a random identifier ([ietf-ip.yang][2] and Both the link-local address (fe80::) and the global address (2001:) have changed type to *random*. + ### IPv4 forwarding + To be able to route (static or dynamic) on the interface it is required to enable forwarding. This setting controlls if packets received on this interface can be forwarded. @@ -749,7 +753,9 @@ received on this interface can be forwarded. admin@example:/> ``` + ### IPv6 forwarding + This flag behaves totally different than for IPv4. For IPv6 the ability to route between interfaces is always enabled, instead this flag controls if the interface will be in host/router mode. @@ -767,18 +773,36 @@ flag controls if the interface will be in host/router mode. admin@example:/config/interface/eth0/> leave admin@example:/> ``` + + ## Routing support -| **Yang Model** | **Description** | -|:--------------------------|:--------------------------------------------------------------------------------------| -| ietf-routing | Base model, used to set configuration and read operational status in the other models | -| ietf-ipv4-unicast-routing | Static IPv4 unicast routing | -| ietf-ipv6-unicast-routing | Static IPv6 unicast routing | -| ietf-ospf | OSPF routing | -| infix-routing | Infix deviations | +Currently supported YANG models: + +| **YANG Model** | **Description** | +|:--------------------------|:--------------------------------| +| ietf-routing | Base model for all other models | +| ietf-ipv4-unicast-routing | Static IPv4 unicast routing | +| ietf-ipv6-unicast-routing | Static IPv6 unicast routing | +| ietf-ospf | OSPF routing | +| infix-routing | Infix deviations and extensions | + +The base model, ietf-routing, is where all the other models hook in. It +is used to set configuration and read operational status (RIB tables) in +the other models. + +> **Note:** the standard IETF routing models allows multiple instances, +> but Infix currently *only support one instance* per routing protocol! +> In the examples presented here, the instance name `default` is used. + ### IPv4 Static routes -Remember to enable [IPv4 forwarding](#IPv4-forwarding) for the interfaces. + +The standard IETF model for static routes reside under the `static` +control plane protocol. For our examples we use the instance name +`default`, you can use any name. + +For a route with destination 192.168.200.0/24 via 192.168.1.1: admin@example:/> configure admin@example:/config/> edit routing control-plane-protocol static name default @@ -786,7 +810,18 @@ Remember to enable [IPv4 forwarding](#IPv4-forwarding) for the interfaces. admin@example:/config/routing/control-plane-protocol/static/name/default/> leave admin@example:/> -> **Note:** You can only have one instance per routing protocol. +For a "floating" static route with destination 10.0.0.0/16 via a backup +router 192.168.1.1, using the highest possible distance: + + admin@example:/> configure + admin@example:/config/> edit routing control-plane-protocol static name default + admin@example:/config/routing/control-plane-protocol/static/name/default/> set ipv4 route 10.0.0.0/16 next-hop next-hop-address 192.168.1.1 route-preference 254 + admin@example:/config/routing/control-plane-protocol/static/name/default/> leave + admin@example:/> + +> Remember to enable [IPv4 forwarding](#IPv4-forwarding) for the +> interfaces you want to route between. + ### IPv6 Static routes @@ -796,24 +831,25 @@ Remember to enable [IPv4 forwarding](#IPv4-forwarding) for the interfaces. admin@example:/config/routing/control-plane-protocol/static/name/default/> leave admin@example:/> -> **Note:** You can only have one instance per routing protocol. ### OSPFv2 Routing -Infix supports OSPF dynamic routing for IPv4, i.e., OSPFv2. -Remember to enable [IPv4 forwarding](#IPv4-forwarding) for the -interfaces you want to run OSPFv2. + +The system supports OSPF dynamic routing for IPv4, i.e., OSPFv2. To +enable OSPF and set one active interface in area 0: admin@example:/config/> edit routing control-plane-protocol ospfv2 name default - admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> set ospf area 0.0.0.0 interface e0 enabled true + admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> set ospf area 0.0.0.0 interface e0 enabled admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> leave admin@example:/> -> **Note:** You can only have one instance per routing protocol. +> Remember to enable [IPv4 forwarding](#IPv4-forwarding) for all the +> interfaces you want to route between. + #### OSPF area types -In addition to *regular* OSPF areas, area types *NSSA* and *Stub* are supported. -To configure a NSSA area with summary routes: +In addition to *regular* OSPF areas, area types *NSSA* and *Stub* are +also supported. To configure an NSSA area with summary routes: admin@example:/config/> edit routing control-plane-protocol ospfv2 name default admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> set ospf area 0.0.0.1 area-type nssa-area @@ -821,8 +857,10 @@ To configure a NSSA area with summary routes: admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> leave admin@example:/> + #### Bidirectional Forwarding Detection (BFD) -It is possible to enable BFD per interface to speed up detection of + +It is possible to enable BFD per OSPF interface to speed up detection of link loss. admin@example:/config/> edit routing control-plane-protocol ospfv2 name default @@ -830,12 +868,13 @@ link loss. admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> leave admin@example:/> + #### OSPF interface settings We have already seen how to enable OSPF per interface (*enabled true*) -and BFD for OSPF per interface (*bfd enabled true*). These and other -OSPF interface settings are done in context of an OSFP area, e.g., -*area 0.0.0.0*. Available commands can be listed using the `?` mark. +and BFD for OSPF per interface (*bfd enabled true*). These and other +OSPF interface settings are done in context of an OSFP area, e.g., *area +0.0.0.0*. Available commands can be listed using the `?` mark. admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> edit ospf area 0.0.0.0 admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/ospf/area/0.0.0.0/> edit interface e0 @@ -877,47 +916,112 @@ routes`. ### View routing table -The routing table can be viewed from the operational datastore over -NETCONF or using the CLI: +The routing table can be inspected from the operational datastore, XPath +`/routing/ribs`, using sysrepocfg, NETCONF/RESTCONF, or using the CLI. + #### IPv4 routing table - admin@example:/> show routes ipv4 - PREFIX NEXT-HOP PREF PROTOCOL - 192.168.1.0/24 e0 kernel - 192.168.200.0/24 192.168.1.1 20 static +This CLI example shows the IPv4 routing table with a few connected +routes and some routes learned from OSPF. See the next section for +an explanation of route preferences (PREF). + +The `>` at the start of a line marks a selected route (in the IETF YANG +model referred to as *active*), if there are more than one route with +the same destination the `*` marks the next-hop used and installed in +the kernel FIB (the YANG model refers to this as *installed*). + + admin@example:/> show ip route + DESTINATION PREF NEXT-HOP PROTO UPTIME + >* 0.0.0.0/0 110/2 10.0.23.1 ospfv2 4h2m43s + >* 10.0.0.1/32 110/4000 10.0.13.1 ospfv2 4h2m43s + 10.0.0.3/32 110/0 lo ospfv2 4h2m57s + >* 10.0.0.3/32 0/0 lo direct 4h2m58s + 10.0.13.0/30 110/2000 e5 ospfv2 4h2m57s + >* 10.0.13.0/30 0/0 e5 direct 4h2m58s + 10.0.23.0/30 110/1 e6 ospfv2 4h2m57s + >* 10.0.23.0/30 0/0 e6 direct 4h2m58s + 192.168.3.0/24 110/1 e2 ospfv2 4h2m57s + >* 192.168.3.0/24 0/0 e2 direct 4h2m58s admin@example:/> + #### IPv6 routing table - admin@example:/> show routes ipv6 - PREFIX NEXT-HOP PREF PROTOCOL - 2001:db8:3c4d:50::/64 eth4 256 kernel - fe80::/64 eth5 256 kernel - fe80::/64 eth3 256 kernel - fe80::/64 eth1 256 kernel - fe80::/64 eth0 256 kernel - fe80::/64 eth2 256 kernel - fe80::/64 eth4 256 kernel +This CLI example show the IPv6 routing table. + + admin@example:/> show ipv6 route + DESTINATION PREF NEXT-HOP PROTO UPTIME + >* ::/0 1/0 2001:db8:3c4d:50::1 static 0h1m20s + >* 2001:db8:3c4d:50::/64 0/0 e6 direct 0h1m20s + >* 2001:db8:3c4d:200::1/128 0/0 lo direct 0h1m20s + * fe80::/64 0/0 e7 direct 0h1m20s + * fe80::/64 0/0 e6 direct 0h1m20s + * fe80::/64 0/0 e5 direct 0h1m20s + * fe80::/64 0/0 e4 direct 0h1m20s + * fe80::/64 0/0 e3 direct 0h1m20s + * fe80::/64 0/0 e2 direct 0h1m20s + >* fe80::/64 0/0 e1 direct 0h1m20s admin@example:/> + +#### Route Preference + +The operating system leverages FRRouting ([Frr][4]) as routing engine +for both static and dynamic routing. Even routes injected from a DHCP +client, and IPv4 link-local (IPv4) routes, are injected into Frr to let +it weigh all routes before installing them into the kernel routing table +(sometimes referred to as FIB). + +Routes have different weights made up from a *distance* and a *metric*. +The kernel routing table only talks about *metric*, which unfortunately +is **not the same** -- this is one of the reasons why the term *route +preference* is used instead. It is recommended to use the CLI, or any +of the other previously mentioned YANG based front-ends, to inspect the +routing table. + +Default distances used (lower numeric value wins): + +| **Distance** | **Protocol** | +|--------------|-----------------------------------------| +| 0 | Kernel routes, i.e., connected routes | +| 1 | Static routes | +| 5 | DHCP routes | +| 110 | OSPF | +| 254 | IPv4LL (ZeroConf) device routes | +| 255 | Route will not be used or redistributed | + +Hence, a route learned from OSPF may be overridden by a static route set +locally. By default, even a route to the same destination, but with a +different next-hop, learned from a DHCP server wins over an OSPF route. + +The distance used for static routes and DHCP routes can be changed by +setting a different *routing preference* value. + +> **Note:** the kernel metric is an unsigned 32-bit value, which is read +> by Frr as (upper) 8 bits distance and 24 bits metric. But it does not +> write it back to the kernel FIB this way, only selected routes are +> candidates to be installed in the FIB by Frr. + + #### Source protocol The source protocol describes the origin of the route. -| **Protocol** | **Description** | -|:-------------|:---------------------------------------------------------------------| -| kernel | Added when setting a subnet address on an interface | -| static | User created static routes | -| dhcp | Routes retrieved from DHCP | -| ospf | Routes retreived from OSPFv2 | +| **Protocol** | **Description** | +|:-------------|:----------------------------------------------------| +| kernel | Added when setting a subnet address on an interface | +| static | User created, learned from DHCP, or IPv4LL | +| ospfv2 | Routes learned from OSPFv2 | The YANG model *ietf-routing* support multiple ribs but only two are currently supported, namely `ipv4` and `ipv6`. + [1]: https://www.rfc-editor.org/rfc/rfc8343 [2]: https://www.rfc-editor.org/rfc/rfc8344 [3]: https://www.rfc-editor.org/rfc/rfc8981 +[4]: https://frrouting.org/ [^1]: Please note, link aggregates are not yet supported in Infix. [^2]: Link-local IPv6 addresses are implicitly enabled when enabling From d9fa4d989815c6807313a382ddfa0a83a9db059e Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 24 Sep 2024 11:37:44 +0200 Subject: [PATCH 39/39] doc: update ChangeLog with info on route installation overhaul Signed-off-by: Joachim Wiberg --- doc/ChangeLog.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 4c5351fd..83a1e48e 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -40,24 +40,32 @@ All notable changes to the project are documented in this file. - Fix #613: CLI regression in tab completion of container commands, e.g., `container shell `. Regression introduced in v24.08.0 - Fix #616: Silent failure when selecting bash as login shell for - non-admin user, this silent lock has been removed. + non-admin user, this silent lock has been removed - Fix #618: CLI command `show interfaces` does not show bridges and bridge ports, regression introduced in v24.08.0 -- only affects bridges without multicast snooping - Fix #623: CLI command `container upgrade NAME` does not work, regression introduced in v24.06.0 - Fix #625: initialize sysrepo startup datastore at boot. Improves - usability when working directly against the sysrepo datastores + usability when working directly against the sysrepo datastores from + the shell with `sysrepocfg` and `sysrepoctl` tools - Fix #635: OSPF: all router neighbors reported as neighbor on every interface - Fix #638: Disabling IPv4LL (autoconf) on an interface does not clean up 169.254/16 addresses +- Fix #640: unable to set static default route due to priority inversion + from DHCP or IPv4LL (ZeroConf) clients setting their routes directly + in the kernel. This has resulted in a complete overhaul of route + management, using FRRouting for all routes, including DHCP and IPv4LL + routes, presentation in the CLI, and also support for custom route + preference for static routes. - Spellcheck path to `/var/lib/containers` when unpacking OCI archives on container upgrade - The timeout before giving up on loading the `startup-config` at boot is now 1 minute, just like operations via other front-ends (NETCONF and RESTCONF). This was previously (incorrectly) set to 10 seconds. + [v24.08.0][] - 2024-08-30 ------------------------- @@ -1123,6 +1131,7 @@ Supported YANG models in addition to those used by sysrepo and netopeer: [buildroot]: https://buildroot.org/ [UNRELEASED]: https://github.com/kernelkit/infix/compare/v24.08.0...HEAD +[v24.09.0]: https://github.com/kernelkit/infix/compare/v24.08.0...v24.09.0 [v24.08.0]: https://github.com/kernelkit/infix/compare/v24.06.0...v24.08.0 [v24.06.0]: https://github.com/kernelkit/infix/compare/v24.04.0...v24.06.0 [v24.04.2]: https://github.com/kernelkit/infix/compare/v24.04.1...v24.04.2