diff --git a/board/common/rootfs/etc/avahi/avahi-autoipd.action b/board/common/rootfs/etc/avahi/avahi-autoipd.action index 0a2cba9b..20912b82 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 @@ -29,60 +9,54 @@ set -e # $3 IP adddress PATH="$PATH:/usr/bin:/usr/sbin:/bin:/sbin" +NAME="/etc/frr/static.d/$2-zeroconf.conf" +NEXT="${NAME}+" -# Use a different metric for each interface, so that we can set -# identical routes to multiple interfaces. +log() +{ + logger -I $$ -t zeroconf -p user.notice "$*" +} -METRIC=$((1000 + `cat "/sys/class/net/$2/ifindex" 2>/dev/null || echo 0`)) - -if [ -x /bin/ip -o -x /sbin/ip ] ; then - - # We have the Linux ip tool from the iproute package - - 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 - ;; +# 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 -elif [ -x /bin/ifconfig -o -x /sbin/ifconfig ] ; then + initctl -nbq restart staticd +} - # We have the old ifconfig tool +case "$1" in + BIND) + 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" + ;; - 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|UNBIND|STOP) + act del "$2" + ip addr flush dev "$2" proto random + log "clr ipv4ll on iface $2" + ;; - 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 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/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 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 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 <> "$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 3a59e67e..1917e790 100755 --- a/board/common/rootfs/usr/share/udhcpc/default.script +++ b/board/common/rootfs/usr/share/udhcpc/default.script @@ -7,9 +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 +[ -n "$metric" ] || metric=5 # Handle stateful DHCPv6 like DHCPv4 [ -n "$ipv6" ] && ip="$ipv6/128" @@ -23,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 @@ -42,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() @@ -112,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/doc/ChangeLog.md b/doc/ChangeLog.md index 2767e294..83a1e48e 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 @@ -30,20 +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 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 ------------------------- @@ -1109,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 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 ----------- diff --git a/doc/networking.md b/doc/networking.md index f276ef67..7d5e26c7 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) @@ -657,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) @@ -693,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) @@ -731,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. @@ -742,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. @@ -760,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 @@ -779,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 @@ -789,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 @@ -814,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 @@ -823,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 @@ -870,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 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: diff --git a/package/skeleton-init-finit/skeleton-init-finit.mk b/package/skeleton-init-finit/skeleton-init-finit.mk index 0d1be306..30088ec0 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 @@ -87,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/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 + diff --git a/src/confd/bin/load b/src/confd/bin/load index 57dbce2b..242fe662 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 @@ -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 050d50c7..183424cc 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; @@ -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; @@ -177,10 +181,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); } 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/src/ietf-routing.c b/src/confd/src/ietf-routing.c index de6aacfb..46e0cc2b 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" @@ -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; @@ -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; } @@ -224,14 +226,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++; @@ -245,7 +246,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 +261,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 +273,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")) { @@ -321,12 +312,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; @@ -337,18 +326,17 @@ 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) { + /* 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; diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 78b17949..d2091178 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -25,19 +25,19 @@ 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" + "infix-routing@2024-09-23.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" "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-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."; } } } 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"; - } } 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; + } +} diff --git a/src/confd/yang/infix-routing@2024-09-20.yang b/src/confd/yang/infix-routing@2024-09-20.yang deleted file mode 100644 index 4e8ccb75..00000000 --- a/src/confd/yang/infix-routing@2024-09-20.yang +++ /dev/null @@ -1,496 +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-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 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; - } - - typedef infix-distribute-protocol { - type enumeration { - enum ospf { - description "Redistribute Ospf"; - } - enum static { - description "Redistribute Static routes"; - } - enum connected { - description "Redistribute Connected routes"; - } - } - } - - 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"; - } - identity ospfv2 { - base ospf:ospfv2; - base infix-routing-type; - description "OSPv2 (IPv4) routing protocol"; - } - 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" { - 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-23.yang b/src/confd/yang/infix-routing@2024-09-23.yang new file mode 100644 index 00000000..1f1f633b --- /dev/null +++ b/src/confd/yang/infix-routing@2024-09-23.yang @@ -0,0 +1,522 @@ +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; + } + + 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 + - 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 { + 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 */ + 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; + } + 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: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: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 { + 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; + } + +} diff --git a/src/klish-plugin-infix/xml/infix.xml b/src/klish-plugin-infix/xml/infix.xml index 22b7ed33..fd29468e 100644 --- a/src/klish-plugin-infix/xml/infix.xml +++ b/src/klish-plugin-infix/xml/infix.xml @@ -331,13 +331,15 @@ - 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 + diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 686acbc7..2b0fd5d8 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -3,6 +3,8 @@ import json import argparse import sys import re +from datetime import datetime, timezone + class Pad: iface = 16 @@ -10,17 +12,33 @@ class Pad: state = 12 data = 41 + class PadMdb: bridge = 7 vlan = 6 group = 20 ports = 45 + class PadRoute: - prefix = 30 - protocol = 10 - next_hop = 30 + dest = 30 pref = 8 + next_hop = 30 + 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: name = 10 @@ -35,6 +53,7 @@ class PadUsbPort: name = 20 state = 10 + class Decore(): @staticmethod def decorate(sgr, txt, restore="0"): @@ -60,6 +79,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,22 +96,30 @@ 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.ip = ip self.prefix = data.get(f'ietf-{ip}-unicast-routing:destination-prefix', '') - self.protocol = data.get('source-protocol','')[14:] - self.pref = data.get('route-preference','') + 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') + 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"]) - elif(nh.get("outgoing-interface")): - self.next_hop.append(nh["outgoing-interface"]) + if nh.get(f"ietf-{ip}-unicast-routing:address"): + hop = nh[f"ietf-{ip}-unicast-routing:address"] + elif nh.get("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') @@ -105,17 +133,64 @@ 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 = 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): - row = f"{self.prefix:<{PadRoute.prefix}}" - row += f"{self.next_hop[0]:<{PadRoute.next_hop}}" - row += f"{self.pref:>{PadRoute.pref}} " - row += f"{self.protocol:<{PadRoute.protocol}}" + PadRoute.set(self.ip) + distance, metric = self.get_distance_and_metric() + uptime = self.datetime2uptime() + pref = f"{distance}/{metric}" + hop, fib = self.next_hop[0] + + row = ">" if self.active else " " + row += "*" if fib else " " + row += " " + 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:]: - row = f"{'':<{PadRoute.prefix}}" - row += f"{nh:<{PadRoute.next_hop}}" + hop, fib = nh + row = " " + row += "*" if fib else " " + row += " " + row += f"{'':<{PadRoute.dest}} " + row += f"{'':>{PadRoute.pref}} " + row += f"{hop:<{PadRoute.next_hop}} " print(row) + class Software: """Software bundle class """ def __init__(self, data): @@ -431,6 +506,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: @@ -438,8 +514,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() @@ -447,6 +526,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}}" @@ -455,8 +535,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) @@ -484,6 +564,7 @@ def pr_interface_list(json): continue print_interface(iface) + def show_interfaces(json, name): if name: if not json.get("ietf-interfaces:interfaces"): @@ -497,16 +578,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 @@ -520,25 +604,31 @@ 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}}") + + 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)) - 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: @@ -546,6 +636,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") 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 c3521b34..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 @@ -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 @@ -229,8 +230,29 @@ 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""" + """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"] = [] @@ -241,58 +263,75 @@ 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) + + # 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) - 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'] + # 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) - 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) @@ -307,9 +346,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 @@ -348,8 +389,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 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 + } + ] + } + ] +} diff --git a/test/case/ietf_interfaces/ipv4_autoconf/Readme.adoc b/test/case/ietf_interfaces/ipv4_autoconf/Readme.adoc index 208b8b85..1d66d60c 100644 --- a/test/case/ietf_interfaces/ipv4_autoconf/Readme.adoc +++ b/test/case/ietf_interfaces/ipv4_autoconf/Readme.adoc @@ -1,23 +1,28 @@ -=== IPv4 linklocal +=== 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 linklocal topology] +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 -. 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() diff --git a/test/case/ietf_routing/ospf_basic/test.py b/test/case/ietf_routing/ospf_basic/test.py index f1f2bf06..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 = "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", 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 7f16b9db..df43da8c 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) @@ -549,33 +550,34 @@ 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", 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")) - 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 = "infix-routing:ospf"), 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.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 + 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 0d725b95..01290d7d 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() @@ -167,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 = "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", 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/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 8f7d0661..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,75 +18,73 @@ """ 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 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") + 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"): - 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/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 <<< 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() 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: 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)) diff --git a/test/infamy/route.py b/test/infamy/route.py index b9d0f2cd..84bb96db 100644 --- a/test/infamy/route.py +++ b/test/infamy/route.py @@ -1,92 +1,122 @@ +""" +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 {} -def _exist_route(target, prefix, nexthop, version, source_protocol): - routes = _get_routes(target, version) - route_found = False + +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 (version == "ipv4" and r.get("ietf-ipv4-unicast-routing:destination-prefix")) or (version == "ipv6" and r.get("ietf-ipv6-unicast-routing:destination-prefix")) - if p != prefix: + dst = r.get("destination-prefix") or \ + r.get(f"ietf-{ip}-unicast-routing:destination-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 - route_found = True - nh = r["next-hop"] - next_hop_list = nh.get("next-hop-list") - 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 + + 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) -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, 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, 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): - 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