From 65bce0378ef59792ed136d6d73a6f005b0ba6520 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 12 Jan 2025 15:15:11 +0100 Subject: [PATCH 01/27] .github: install libite from git Latest libite-dev in Ubuntu does not have the latest libite features needed by confd et al. Signed-off-by: Joachim Wiberg --- .github/workflows/coverity.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index 7080d245..c0e5207a 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -55,7 +55,7 @@ jobs: sudo apt-get -y update sudo apt-get -y install pkg-config libjansson-dev libev-dev \ libcrypt-dev libglib2.0-dev libpcre2-dev \ - libuev-dev libite-dev + libuev-dev - name: Build dependencies run: | @@ -65,6 +65,8 @@ jobs: git clone https://github.com/sysrepo/sysrepo.git mkdir sysrepo/build (cd sysrepo/build && cmake .. && make all && sudo make install) + git clone https://github.com/troglobit/libite.git + (cd libite && ./autogen.sh && ./configure && make && sudo make install) make dep - name: Check applications From 039cb5db74c779513a5657c2800336cf663a6474 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 9 Dec 2024 10:13:27 +0100 Subject: [PATCH 02/27] utils: fix linter warnings, simplify, and improve log messages on update Signed-off-by: Joachim Wiberg --- src/confd/yang/confd.inc | 1 + utils/sysrepo-load-modules.sh | 114 ++++++++++++++++++++-------------- 2 files changed, 69 insertions(+), 46 deletions(-) diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index e48c492f..0a635413 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -1,5 +1,6 @@ # -*- sh -*- # REMEMBER TO UPDATE infix-interfaces ALSO IN containers.inc +#FORCE_UPDATE="infix-interfaces" MODULES=( "ietf-system@2014-08-06.yang -e authentication -e local-users -e ntp -e ntp-udp-port -e timezone-name" "iana-timezones@2013-11-19.yang" diff --git a/utils/sysrepo-load-modules.sh b/utils/sysrepo-load-modules.sh index a50fae58..fabad4d1 100755 --- a/utils/sysrepo-load-modules.sh +++ b/utils/sysrepo-load-modules.sh @@ -7,16 +7,17 @@ # their respective enabled features in a MODULES array. # Example: # MODULES=("module@revision -e feature1 -e feature2") + +# shellcheck disable=SC1090 source "$1" # optional env variable override if [ -n "$SYSREPOCTL_EXECUTABLE" ]; then SYSREPOCTL="$SYSREPOCTL_EXECUTABLE" -# avoid problems with sudo PATH -elif [ `id -u` -eq 0 ] && [ -n "$USER" ] && [ `command -v su` ]; then - SYSREPOCTL=`command sysrepoctl -l $USER` +elif [ "$(id -u)" -eq 0 ] && [ -n "$USER" ] && [ -n "$(command -v su)" ]; then + SYSREPOCTL=$(command sysrepoctl -l "$USER") else - SYSREPOCTL=`command sysrepoctl` + SYSREPOCTL=$(command sysrepoctl) fi MODDIR=${SEARCH_PATH} @@ -26,22 +27,54 @@ PERMS="660" CMD_INSTALL= -# functions -INSTALL_MODULE_CMD() { + +install() +{ if [ -z "${CMD_INSTALL}" ]; then CMD_INSTALL="'$SYSREPOCTL' -s $MODDIR -v2" fi CMD_INSTALL="$CMD_INSTALL -i $MODDIR/$1 -p '$PERMS'" - if [ ! -z "${OWNER}" ]; then + if [ -n "${OWNER}" ]; then CMD_INSTALL="$CMD_INSTALL -o '$OWNER'" fi - if [ ! -z "${GROUP}" ]; then + if [ -n "${GROUP}" ]; then CMD_INSTALL="$CMD_INSTALL -g '$GROUP'" fi } -UPDATE_MODULE() { - CMD="'$SYSREPOCTL' -U $MODDIR/$1 -s '$MODDIR' -v2" + +update() +{ + local module="$1" + local cmd="'$SYSREPOCTL' -U $MODDIR/$module -s '$MODDIR' -v2" + + local output rc + output=$(eval "$cmd" 2>&1) + rc=$? + + if [ $rc -ne 0 ]; then + if echo "$output" | grep -q "Module .* already installed"; then + echo "*** Warning: Module $module is already installed. Skipping update." + return 0 + fi + echo "*** Error: failed updating module $module: $output" >&2 + return $rc + fi + + echo "*** Successfully updated module $module." + return 0 +} + + +chperm() +{ + CMD="'$SYSREPOCTL' -c $1 -p '$PERMS' -v2" + if [ -n "${OWNER}" ]; then + CMD="$CMD -o '$OWNER'" + fi + if [ -n "${GROUP}" ]; then + CMD="$CMD -g '$GROUP'" + fi eval "$CMD" local rc=$? if [ $rc -ne 0 ]; then @@ -49,79 +82,68 @@ UPDATE_MODULE() { fi } -CHANGE_PERMS() { - CMD="'$SYSREPOCTL' -c $1 -p '$PERMS' -v2" - if [ ! -z "${OWNER}" ]; then - CMD="$CMD -o '$OWNER'" - fi - if [ ! -z "${GROUP}" ]; then - CMD="$CMD -g '$GROUP'" - fi - eval $CMD + +enable() +{ + $SYSREPOCTL -c "$1" -e "$2" -v2 local rc=$? if [ $rc -ne 0 ]; then exit $rc fi } -ENABLE_FEATURE() { - "$SYSREPOCTL" -c $1 -e $2 -v2 - local rc=$? - if [ $rc -ne 0 ]; then - exit $rc - fi -} -# get current modules -SCTL_MODULES=`$SYSREPOCTL -l` -for i in "${MODULES[@]}"; do - name=`echo "$i" | sed 's/\([^@]*\).*/\1/'` +# Skip first 5 lines of header and last 3 lines of footer +SCTL_MODULES=$($SYSREPOCTL -l |tail -n +5 |head -n -3) - SCTL_MODULE=`echo "$SCTL_MODULES" | grep "^$name \+|[^|]*| I"` +for module in "${MODULES[@]}"; do + name=$(echo "$module" | awk -F'[@.]' '{print $1}') + date=$(echo "$module" | awk -F'[@.]' '{print $2}') + + SCTL_MODULE=$(echo "$SCTL_MODULES" | grep "^$name \+|[^|]*| I") if [ -z "$SCTL_MODULE" ]; then # prepare command to install module with all its features echo "*** Installing YANG model $name ..." - INSTALL_MODULE_CMD "$i" + install "$module" continue fi - sctl_revision=`echo "$SCTL_MODULE" | sed 's/[^|]*| \([^ ]*\).*/\1/'` - revision=`echo "$i" | sed 's/[^@]*@\([^\.]*\).*/\1/'` - if [ "$sctl_revision" \< "$revision" ]; then + rev=$(echo "$SCTL_MODULE" | awk '{print $3}') + if [ "$rev" != "$date" ] || echo "$FORCE_UPDATE" | grep -qw "$name"; then # update module without any features - file=`echo "$i" | cut -d' ' -f 1` + file=$(echo "$module" | cut -d' ' -f 1) echo "*** Updating YANG model $name ($file) ..." - UPDATE_MODULE "$file" + update "$file" fi - sctl_owner=`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{3\} \([^:]*\).*/\2/'` - sctl_group=`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{3\}[^:]*:\([^ ]*\).*/\2/'` - sctl_perms=`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{4\} \([^ ]*\).*/\2/'` + #sctl_owner=`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{3\} \([^:]*\).*/\2/'` + #sctl_group=`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{3\}[^:]*:\([^ ]*\).*/\2/'` + sctl_perms=$(echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{4\} \([^ ]*\).*/\2/') if [ "$sctl_perms" != "$PERMS" ]; then # change permissions/owner echo "*** Changing YANG model $name permissions ..." - CHANGE_PERMS "$name" + chperm "$name" fi # parse sysrepoctl features and add extra space at the end for easier matching sctl_features="`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{6\}\(.*\)/\2/'` " # parse features we want to enable - features=`echo "$i" | sed 's/[^ ]* \(.*\)/\1/'` + features=`echo "$module" | sed 's/[^ ]* \(.*\)/\1/'` while [ "${features:0:3}" = "-e " ]; do # skip "-e " features=${features:3} # parse feature - feature=`echo "$features" | sed 's/\([^[:space:]]*\).*/\1/'` + feature=$(echo "$features" | sed 's/\([^[:space:]]*\).*/\1/') # enable feature if not already - sctl_feature=`echo "$sctl_features" | grep " ${feature} "` + sctl_feature=$(echo "$sctl_features" | grep " ${feature} ") if [ -z "$sctl_feature" ]; then # enable feature - ENABLE_FEATURE $name $feature + enable "$name" "$feature" fi # next iteration, skip this feature - features=`echo "$features" | sed 's/[^[:space:]]* \(.*\)/\1/'` + features=$(echo "$features" | sed 's/[^[:space:]]* \(.*\)/\1/') done done From 01d07b4942f7033b7b89dfc24effd79c1eb893e4 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 12 Jan 2025 09:19:23 +0100 Subject: [PATCH 03/27] utils: rename sysrepo-load-modules.sh -> srload Simplify name, follow new srop tool naming convention. Signed-off-by: Joachim Wiberg --- package/confd-test-mode/confd-test-mode.mk | 9 ++++++++- package/confd/confd.mk | 4 ++-- utils/{sysrepo-load-modules.sh => srload} | 0 3 files changed, 10 insertions(+), 3 deletions(-) rename utils/{sysrepo-load-modules.sh => srload} (100%) diff --git a/package/confd-test-mode/confd-test-mode.mk b/package/confd-test-mode/confd-test-mode.mk index 0786b1b8..aa144ed8 100644 --- a/package/confd-test-mode/confd-test-mode.mk +++ b/package/confd-test-mode/confd-test-mode.mk @@ -1,3 +1,9 @@ +################################################################################ +# +# confd-test-mode +# +################################################################################ + CONFD_TEST_MODE_VERSION = 1.0 CONFD_TEST_MODE_SITE_METHOD = local CONFD_TEST_MODE_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/test-mode @@ -16,7 +22,8 @@ COMMON_SYSREPO_ENV = \ define CONFD_TEST_MODE_INSTALL_YANG_MODULES $(COMMON_SYSREPO_ENV) \ - SEARCH_PATH="$(TARGET_DIR)/usr/share/yang/modules/test-mode/" $(BR2_EXTERNAL_INFIX_PATH)/utils/sysrepo-load-modules.sh $(@D)/yang/test-mode.inc + SEARCH_PATH="$(TARGET_DIR)/usr/share/yang/modules/test-mode/" \ + $(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/test-mode.inc endef define CONFD_TEST_MODE_PERMISSIONS /etc/sysrepo/data/ r 660 root wheel - - - - - diff --git a/package/confd/confd.mk b/package/confd/confd.mk index a194fd61..bcd2da91 100644 --- a/package/confd/confd.mk +++ b/package/confd/confd.mk @@ -41,13 +41,13 @@ COMMON_SYSREPO_ENV = \ define CONFD_INSTALL_YANG_MODULES $(COMMON_SYSREPO_ENV) \ - $(BR2_EXTERNAL_INFIX_PATH)/utils/sysrepo-load-modules.sh $(@D)/yang/confd.inc + $(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/confd.inc endef ifeq ($(BR2_PACKAGE_PODMAN),y) define CONFD_INSTALL_YANG_MODULES_CONTAINERS $(COMMON_SYSREPO_ENV) \ - $(BR2_EXTERNAL_INFIX_PATH)/utils/sysrepo-load-modules.sh $(@D)/yang/containers.inc + $(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/containers.inc endef endif diff --git a/utils/sysrepo-load-modules.sh b/utils/srload similarity index 100% rename from utils/sysrepo-load-modules.sh rename to utils/srload From f212f0cc169e4e5f93a8f7e10f467efa32840fbb Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Fri, 13 Dec 2024 23:19:01 +0100 Subject: [PATCH 04/27] board/common: ensure virtio ports have an initial speed/duplex The speed/duplex of a virtio interface is not eenforced in any way. The driver initializes them to unknown/unkown: https://github.com/torvalds/linux/blob/dccbe2047a5b0859de24bf463dae9eeea8e01c1e/drivers/net/virtio_net.c#L5375-L5381 It is however possible to set them since there are kernel subsystems that rely on them, e.g., miimon in the bond driver, which in turn is required for use with the 802.3ad (lacp) mode. If we do not initialize speed/duplex the miimon will complain in the log on virtual test systems every second: Dec 13 22:34:08 laggy kernel: lag0: (slave e1): failed to get link speed/duplex Signed-off-by: Joachim Wiberg --- board/common/rootfs/usr/libexec/infix/init.d/25-qemu | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100755 board/common/rootfs/usr/libexec/infix/init.d/25-qemu diff --git a/board/common/rootfs/usr/libexec/infix/init.d/25-qemu b/board/common/rootfs/usr/libexec/infix/init.d/25-qemu new file mode 100755 index 00000000..ef48b390 --- /dev/null +++ b/board/common/rootfs/usr/libexec/infix/init.d/25-qemu @@ -0,0 +1,8 @@ +#!/bin/sh +# Initialize speed/duplex of virtio interfaces +# For virtual test systems (lacp tests) + +ifaces=$(ip -d -json link show | jq -r '.[] | select(.parentbus == "virtio") | .ifname') +for iface in $ifaces; do + ethtool -s "$iface" speed 1000 duplex full +done From 6a417f2f23032bb10f4ce1f9cc62c1ff287432ea Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 9 Dec 2024 11:07:12 +0100 Subject: [PATCH 05/27] package/confd: drop unnecessary conditional Signed-off-by: Joachim Wiberg --- package/confd/confd.mk | 2 -- 1 file changed, 2 deletions(-) diff --git a/package/confd/confd.mk b/package/confd/confd.mk index bcd2da91..4d9fe8a5 100644 --- a/package/confd/confd.mk +++ b/package/confd/confd.mk @@ -63,9 +63,7 @@ endef CONFD_PRE_INSTALL_TARGET_HOOKS += CONFD_CLEANUP CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_EXTRA CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES -ifeq ($(BR2_PACKAGE_PODMAN),y) CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_CONTAINERS -endif CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_CLEANUP $(eval $(autotools-package)) From b70d0015a6a9c025efa5126e135ba2d1fc2b8816 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 12 Jan 2025 22:28:01 +0100 Subject: [PATCH 06/27] confd: silence any error from dagger 'ls *.tar.gz' Do not log error if no tarballs exist. Signed-off-by: Joachim Wiberg --- src/confd/bin/dagger | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/confd/bin/dagger b/src/confd/bin/dagger index 17e08b40..8949ca71 100755 --- a/src/confd/bin/dagger +++ b/src/confd/bin/dagger @@ -157,7 +157,7 @@ do_prune() local keep=${1:-10} cd "$basedir" - rm -f old=$(ls -rv *.tar.gz | tail "+$((keep + 1))") + rm -f old=$(ls -rv *.tar.gz 2>/dev/null | tail "+$((keep + 1))") } usage() From 1ca11f1fe75b081704763cb5a10afff35e2a7239 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 12 Jan 2025 09:27:35 +0100 Subject: [PATCH 07/27] statd: yanger: ignore 'podman' errors when built without containers - Avoid unnecessary error messages in syslog when build w/o containers - Simplify log messages slightly Signed-off-by: Joachim Wiberg --- src/statd/python/yanger/infix_containers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/statd/python/yanger/infix_containers.py b/src/statd/python/yanger/infix_containers.py index 4289fb85..8ef96e77 100644 --- a/src/statd/python/yanger/infix_containers.py +++ b/src/statd/python/yanger/infix_containers.py @@ -1,18 +1,23 @@ from .common import LOG from .host import HOST + +# Catch errors (check=True), at this point we've run 'podman ps' (below) def podman_inspect(name): """Call podman inspect {name}, return object at {path} or None.""" cmd = ['podman', 'inspect', name] try: return HOST.run_json(cmd, default=[]) except Exception as e: - LOG(f"Error running podman inspect: {e}") + LOG.error(f"failed podman inspect: {e}") return [] + +# Ignore any errors here, may be called on a build without containers def podman_ps(): """We list *all* containers, not just those in the configuraion.""" - return HOST.run_json("podman ps -a --format=json".split(), default=[]) + cmd = ['podman', 'ps', '-a', '--format=json'] + return HOST.run_json(cmd, default=[]) def network(ps, inspect): From 18d8c439bdb1afae6c7eeb6b9b8a1b27617ae9d0 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sat, 14 Dec 2024 10:11:15 +0100 Subject: [PATCH 08/27] confd: drop unused yang import Signed-off-by: Joachim Wiberg --- src/confd/yang/infix-if-bridge.yang | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/confd/yang/infix-if-bridge.yang b/src/confd/yang/infix-if-bridge.yang index cfa0e412..5806e698 100644 --- a/src/confd/yang/infix-if-bridge.yang +++ b/src/confd/yang/infix-if-bridge.yang @@ -15,9 +15,6 @@ submodule infix-if-bridge { import ietf-interfaces { prefix if; } - import ietf-ip { - prefix ip; - } import ieee802-dot1q-types { prefix dot1q-types; } From ec2e161649bfe0b1acf35c1efab9cbf6eb04921f Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 9 Dec 2024 07:59:21 +0100 Subject: [PATCH 09/27] confd: add link aggregate model, lacp and static Signed-off-by: Joachim Wiberg --- src/confd/yang/confd.inc | 2 +- src/confd/yang/containers.inc | 2 +- src/confd/yang/infix-if-lag.yang | 232 ++++++++++++++++++ src/confd/yang/infix-if-lag@2024-12-08.yang | 1 + src/confd/yang/infix-interfaces.yang | 7 + ....yang => infix-interfaces@2025-01-09.yang} | 0 6 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 src/confd/yang/infix-if-lag.yang create mode 120000 src/confd/yang/infix-if-lag@2024-12-08.yang rename src/confd/yang/{infix-interfaces@2025-01-08.yang => infix-interfaces@2025-01-09.yang} (100%) diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 0a635413..e8f4b354 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -40,7 +40,7 @@ MODULES=( "ieee802-ethernet-interface@2019-06-21.yang" "infix-ethernet-interface@2024-02-27.yang" "infix-factory-default@2023-06-28.yang" - "infix-interfaces@2025-01-08.yang -e vlan-filtering" + "infix-interfaces@2025-01-09.yang -e vlan-filtering" "infix-crypto-types@2025-02-04.yang" "infix-keystore@2025-02-04.yang" diff --git a/src/confd/yang/containers.inc b/src/confd/yang/containers.inc index 6c863c96..e6d3d8b3 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@2025-01-08.yang -e vlan-filtering -e containers" + "infix-interfaces@2025-01-09.yang -e vlan-filtering -e containers" "infix-containers@2024-11-15.yang" ) diff --git a/src/confd/yang/infix-if-lag.yang b/src/confd/yang/infix-if-lag.yang new file mode 100644 index 00000000..937c625d --- /dev/null +++ b/src/confd/yang/infix-if-lag.yang @@ -0,0 +1,232 @@ +submodule infix-if-lag { + yang-version 1.1; + belongs-to infix-interfaces { + prefix infix-if; + } + + import ietf-interfaces { + prefix if; + } + import iana-if-type { + prefix ianaift; + } + + organization "KernelKit"; + contact "kernelkit@googlegroups.com"; + description "Linux link aggregates (lag) for ietf-interfaces."; + + revision 2025-01-09 { + description "Initial revision."; + reference "internal"; + } + + /* + * Typedefs + */ + + typedef lag-type { + description "Mode values for link aggregates."; + + /* Temporarily limited to 802.1AX and Balanced XOR */ + type enumeration { + enum static { + description "Static mode (Balanced XOR)."; + value 2; + } + enum lacp { + description "IEEE 802.3ad LACP mode."; + value 4; + } + } + } + + typedef lacp-mode { + description "LACP mode values."; + + type enumeration { + enum passive { + description "LACP active mode"; + value 0; + } + enum active { + description "LACP passive mode."; + value 1; + } + } + } + + /* + * Shared settings + */ + + grouping hash { + leaf hash { + description "Transmit hash policy."; + config false; // For now, staically set to layer2 only + type string; + } + } + + grouping lacp-settings { + description "LACP mode settings."; + + container lacp { + description "Settings specific for LACP mode."; + + uses hash; + + leaf mode { + description "Operational mode of LACP, default: active. + + - Active: initiates negotiation by sending LACPDUs. + - Passive: waits for the peer to initiate. + + At least one end of the link must be in active mode. When both ends + are active, there is slightly more traffic, but the default ensures + fail-safe operation. + + Passive mode is typically used for troubleshooting, in dynamic + setups (e.g., MLAG), or to minimize the risk of unintended + aggregation. + + For most production scenarios, active mode is preferred to ensure + faster and more predictable link aggregation."; + type lacp-mode; + default "active"; + } + + leaf rate { + description "Rate of LACP keep-alives, default: slow. + + Determines the frequency of LACPDU transmission and the associated + timeout for link failure detection. + + - slow: Sends LACPDUs every 30 seconds. The associated timeout is 90 + seconds, meaning a link is considered failed after 3 consecutive + missed LACPDUs. + + - fast: Sends LACPDUs every 1 second. The associated timeout is 3 + seconds, meaning a link is considered failed after 3 consecutive + missed LACPDUs. + + The selected rate affects the responsiveness of link failure + detection and the amount of control traffic."; + type enumeration { + enum slow { + description "Send LACPDUs every 30 seconds (90-second timeout)."; + } + enum fast { + description "Send LACPDUs every 1 second (3-second timeout)."; + } + } + default "slow"; + } + } + } + + grouping static-settings { + container static { + config false; // For now, we need to read out mode and other status + + uses hash; + + leaf mode { + description "Active mode for static aggregates."; + type string; + } + } + } + + /* + * Data Nodes + */ + + augment "/if:interfaces/if:interface" { + when "derived-from-or-self(if:type,'ianaift:ieee8023adLag')" { + description "Only shown for if:type bridge"; + } + + description "Augment generic interfaces with link aggregates."; + + container lag { + description "Link aggregates are for load balancing and fault tolerance."; + + leaf mode { + description "Link aggregation mode."; + type lag-type; + mandatory true; + } + + uses lacp-settings; + uses static-settings; + + container link-monitor { + description "Link monitor properties."; + + container debounce { + description "Link flapping protection."; + + leaf up { + description "Wait before enabling link after link up."; + type uint32 { + range "0 .. 30000"; + } + units "milliseconds"; + } + + leaf down { + description "Wait before disabling link after link down."; + type uint32 { + range "0 .. 30000"; + } + units "milliseconds"; + } + } + } + } + } + + augment "/if:interfaces/if:interface/infix-if:port" { + when "derived-from-or-self(if:type,'ianaift:ethernetCsmacd') or "+ + "derived-from-or-self(if:type,'ianaift:ilan')" { + description "Applies when a LAG interface exists."; + } + + description "Augments the interface model with the link aggregate member."; + + case lag-port { + description "Extension of the IETF Interfaces model (RFC7223)."; + + container lag-port { + leaf lag { + description "LAG interface to which this interface is a member of."; + type if:interface-ref; + mandatory true; + must "deref(.)/../lag" { + error-message "Must refer to a valid LAG interface."; + } + } + leaf state { + description "Port state, active or backup member."; + config false; + type string; + } + + container lacp { + description "LACP port state, ours and partner."; + config false; + + leaf-list actor-state { + description "LACP state flags."; + type string; + } + + leaf-list partner-state { + description "LACP state flags for link partner."; + type string; + } + } + } + } + } +} diff --git a/src/confd/yang/infix-if-lag@2024-12-08.yang b/src/confd/yang/infix-if-lag@2024-12-08.yang new file mode 120000 index 00000000..e28587d4 --- /dev/null +++ b/src/confd/yang/infix-if-lag@2024-12-08.yang @@ -0,0 +1 @@ +infix-if-lag.yang \ No newline at end of file diff --git a/src/confd/yang/infix-interfaces.yang b/src/confd/yang/infix-interfaces.yang index eaf6b08c..e11461d7 100644 --- a/src/confd/yang/infix-interfaces.yang +++ b/src/confd/yang/infix-interfaces.yang @@ -18,6 +18,7 @@ module infix-interfaces { include infix-if-base; include infix-if-bridge; + include infix-if-lag; include infix-if-container; include infix-if-veth; include infix-if-vlan; @@ -28,6 +29,11 @@ module infix-interfaces { contact "kernelkit@googlegroups.com"; description "Linux bridge and lag extensions for ietf-interfaces."; + revision 2025-01-09 { + description "Add support for link aggregation, static and LACP."; + reference "internal"; + } + revision 2025-01-08 { description "Add Spanning Tree Protocol (STP) support to bridges."; reference "internal"; @@ -37,6 +43,7 @@ module infix-interfaces { description "Allow IP addresses directly on VLAN filtering bridges."; reference "internal"; } + revision 2024-11-15 { description "Two changes: - Limit name 1-15 chars, Linux limitation diff --git a/src/confd/yang/infix-interfaces@2025-01-08.yang b/src/confd/yang/infix-interfaces@2025-01-09.yang similarity index 100% rename from src/confd/yang/infix-interfaces@2025-01-08.yang rename to src/confd/yang/infix-interfaces@2025-01-09.yang From 78499757b008aaa2bf28a334950ce74808db461b Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 16 Dec 2024 00:16:54 +0100 Subject: [PATCH 10/27] confd,statd: add support for link aggregates Signed-off-by: Joachim Wiberg --- src/confd/src/Makefile.am | 1 + src/confd/src/ietf-interfaces.c | 25 +- src/confd/src/ietf-interfaces.h | 28 +- src/confd/src/ietf-ip.c | 4 +- src/confd/src/infix-if-lag.c | 189 +++++++++++ src/confd/yang/infix-if-lag.yang | 304 ++++++++++++------ src/statd/python/cli_pretty/cli_pretty.py | 119 ++++++- .../python/yanger/ietf_interfaces/lag.py | 79 +++++ .../python/yanger/ietf_interfaces/link.py | 15 +- 9 files changed, 644 insertions(+), 120 deletions(-) create mode 100644 src/confd/src/infix-if-lag.c create mode 100644 src/statd/python/yanger/ietf_interfaces/lag.py diff --git a/src/confd/src/Makefile.am b/src/confd/src/Makefile.am index aca9bde5..15f08bef 100644 --- a/src/confd/src/Makefile.am +++ b/src/confd/src/Makefile.am @@ -30,6 +30,7 @@ confd_plugin_la_SOURCES = \ ieee802-ethernet-interface.c \ ietf-ip.c \ infix-if-bridge.c \ + infix-if-lag.c \ infix-if-bridge-mcd.c \ infix-if-bridge-port.c \ infix-if-veth.c \ diff --git a/src/confd/src/ietf-interfaces.c b/src/confd/src/ietf-interfaces.c index 8f4f8d73..6219e874 100644 --- a/src/confd/src/ietf-interfaces.c +++ b/src/confd/src/ietf-interfaces.c @@ -73,6 +73,10 @@ static int ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path) inferred.data.string_val = "infix-if-type:ethernet"; else if (!fnmatch("br+([0-9])", ifname, FNM_EXTMATCH)) inferred.data.string_val = "infix-if-type:bridge"; + else if (!fnmatch("bond+([0-9])", ifname, FNM_EXTMATCH)) + inferred.data.string_val = "infix-if-type:lag"; + else if (!fnmatch("lag+([0-9])", ifname, FNM_EXTMATCH)) + inferred.data.string_val = "infix-if-type:lag"; else if (!fnmatch("docker+([0-9])", ifname, FNM_EXTMATCH)) inferred.data.string_val = "infix-if-type:bridge"; else if (!fnmatch("dummy+([0-9])", ifname, FNM_EXTMATCH)) @@ -299,6 +303,7 @@ static int netdag_gen_sysctl_setting(struct dagger *net, const char *ifname, FIL return 0; } + static int netdag_gen_sysctl(struct dagger *net, struct lyd_node *cif, struct lyd_node *dif) @@ -355,6 +360,8 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net, case IFT_GRE: case IFT_GRETAP: return gre_gen(NULL, cif, ip); + case IFT_LAG: + return lag_gen(dif, cif, ip, 1); case IFT_VETH: return veth_gen(NULL, cif, ip); case IFT_VLAN: @@ -368,7 +375,6 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net, case IFT_UNKNOWN: sr_session_set_error_message(net->session, "%s: unsupported interface type \"%s\"", ifname, lydx_get_cattr(cif, "type")); - return -ENOSYS; } __builtin_unreachable(); @@ -383,6 +389,8 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net, switch (iftype_from_iface(cif)) { case IFT_BRIDGE: return bridge_gen(dif, cif, ip, 0); + case IFT_LAG: + return lag_gen(dif, cif, ip, 0); case IFT_VLAN: return vlan_gen(dif, cif, ip); @@ -420,6 +428,9 @@ static bool netdag_must_del(struct lyd_node *dif, struct lyd_node *cif) case IFT_GRE: case IFT_GRETAP: return lydx_get_descendant(lyd_child(dif), "gre", NULL); + case IFT_LAG: + return lydx_get_child(dif, "custom-phys-address") || + lydx_get_descendant(lyd_child(dif), "lag", "mode", NULL); case IFT_VLAN: return lydx_get_descendant(lyd_child(dif), "vlan", NULL); case IFT_VETH: @@ -505,6 +516,7 @@ static int netdag_gen_iface_del(struct dagger *net, struct lyd_node *dif, case IFT_DUMMY: case IFT_GRE: case IFT_GRETAP: + case IFT_LAG: case IFT_VLAN: case IFT_VXLAN: case IFT_UNKNOWN: @@ -593,6 +605,10 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net if (err) goto err_close_ip; + err = lag_port_gen(dif, cif); + if (err) + goto err_close_ip; + /* Set type specific attributes */ if (!fixed && op != LYDX_OP_CREATE) { err = netdag_gen_afspec_set(session, net, dif, cif, ip); @@ -613,8 +629,7 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net fprintf(ip, "link set alias \"%s\" dev %s\n", attr ?: "", ifname); /* Bring interface back up, if enabled */ - attr = lydx_get_cattr(cif, "enabled"); - if (!attr || !strcmp(attr, "true")) + if (lydx_is_enabled(cif, "enabled")) fprintf(ip, "link set dev %s up state up\n", ifname); err = err ? : netdag_gen_sysctl(net, cif, dif); @@ -641,8 +656,8 @@ static int netdag_init_iface(struct lyd_node *cif) switch (iftype_from_iface(cif)) { case IFT_BRIDGE: return bridge_add_deps(cif); - /* case IFT_LAG: */ - /* return lag_add_deps(cif); */ + case IFT_LAG: + return lag_add_deps(cif); case IFT_VLAN: return vlan_add_deps(cif); case IFT_VETH: diff --git a/src/confd/src/ietf-interfaces.h b/src/confd/src/ietf-interfaces.h index a3cdf5a2..22a0d86d 100644 --- a/src/confd/src/ietf-interfaces.h +++ b/src/confd/src/ietf-interfaces.h @@ -28,6 +28,7 @@ _map(IFT_ETHISH, "infix-if-type:etherlike") \ _map(IFT_GRE, "infix-if-type:gre") \ _map(IFT_GRETAP, "infix-if-type:gretap") \ + _map(IFT_LAG, "infix-if-type:lag") \ _map(IFT_LO, "infix-if-type:loopback") \ _map(IFT_VETH, "infix-if-type:veth") \ _map(IFT_VLAN, "infix-if-type:vlan") \ @@ -70,14 +71,26 @@ static inline const char *bridge_tagtype2str(const char *type) return NULL; } -static inline bool is_bridge_port(struct lyd_node *cif) +static inline struct lyd_node *get_master(struct lyd_node *cif) { - struct lyd_node *node = lydx_get_descendant(lyd_child(cif), "bridge-port", NULL); + struct lyd_node *node; - if (!node || !lydx_get_child(node, "bridge")) - return false; + node = lydx_get_descendant(lyd_child(cif), "bridge-port", NULL); + if (node) + return lydx_get_child(node, "bridge"); - return true; + node = lydx_get_descendant(lyd_child(cif), "lag-port", NULL); + if (node) + return lydx_get_child(node, "lag"); + + return NULL; +} + +static inline bool is_member_port(struct lyd_node *cif) +{ + if (get_master(cif)) + return true; + return false; } @@ -108,6 +121,11 @@ int bridge_port_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip); /* infix-if-gre.c */ int gre_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip); +/* infix-if-lag.c */ +int lag_port_gen(struct lyd_node *dif, struct lyd_node *cif); +int lag_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip, int add); +int lag_add_deps(struct lyd_node *cif); + /* infix-if-veth.c */ bool veth_is_primary(struct lyd_node *cif); int ifchange_cand_infer_veth(sr_session_ctx_t *session, const char *path); diff --git a/src/confd/src/ietf-ip.c b/src/confd/src/ietf-ip.c index cf6fdc93..a428967b 100644 --- a/src/confd/src/ietf-ip.c +++ b/src/confd/src/ietf-ip.c @@ -22,7 +22,7 @@ int netdag_gen_ipv6_autoconf(struct dagger *net, struct lyd_node *cif, struct lyd_node *node; FILE *fp; - if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_bridge_port(cif)) { + if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_member_port(cif)) { fputs(" addrgenmode none", ip); return 0; } @@ -86,7 +86,7 @@ int netdag_gen_ipv4_autoconf(struct dagger *net, struct lyd_node *cif, 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)) + if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_member_port(cif)) goto disable; /* diff --git a/src/confd/src/infix-if-lag.c b/src/confd/src/infix-if-lag.c new file mode 100644 index 00000000..54667a0a --- /dev/null +++ b/src/confd/src/infix-if-lag.c @@ -0,0 +1,189 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ietf-interfaces.h" + +/* + * The lag-port noode is only in the dif at initial creation, or removal. + * When a lag is recreated, e.g., when changing mode, the lag-port is only + * in the cif. + * + * To simplify confd dependency handling we always call this function, so + * a lag member always has its lag master reset. This is cruicial in the + * case described above when the the lag is removed to change mode. + */ +int lag_port_gen(struct lyd_node *dif, struct lyd_node *cif) +{ + const char *ifname = lydx_get_cattr(cif, "name"); + struct lyd_node *node; + const char *lagname; + int err = 0; + FILE *fp; + + node = lydx_get_descendant(lyd_child(dif), "lag-port", NULL); + if (node) { + struct lydx_diff lagdiff = { 0 }; + + if (!lydx_get_diff(lydx_get_child(node, "lag"), &lagdiff)) + goto fail; + + if (lagdiff.old) { + fp = dagger_fopen_net_exit(&confd.netdag, lagdiff.old, + NETDAG_EXIT_LOWERS, "delete-ports.ip"); + if (!fp) { + err = -EIO; + goto fail; + } + + fprintf(fp, "link set %s nomaster\n", ifname); + fclose(fp); + + return 0; + } + + lagname = lagdiff.new; + } else { + node = lydx_get_descendant(lyd_child(cif), "lag-port", NULL); + lagname = lydx_get_cattr(node, "lag"); + if (!node || !lagname) + goto fail; /* done, nothing to do */ + } + + fp = dagger_fopen_net_init(&confd.netdag, lagname, NETDAG_INIT_LOWERS, + "add-ports.ip"); + if (!fp) + return -EIO; + + fprintf(fp, "link set %s down master %s\n", ifname, lagname); + if (lydx_is_enabled(cif, "enabled")) + fprintf(fp, "link set dev %s up state up\n", ifname); + fclose(fp); + + err = dagger_add_dep(&confd.netdag, lagname, ifname); + if (err) + ERROR("%s: unable to add dep to %s", ifname, lagname); +fail: + return err; +} + +int lag_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip, int add) +{ + const char *lagname = lydx_get_cattr(cif, "name"); + const char *op = add ? "add" : "set"; + struct lyd_node *lag, *mon; + const char *mode; + int err = 0; + + lag = lydx_get_descendant(lyd_child(cif), "lag", NULL); + if (!lag) + return -EINVAL; + + /* Must take lag down if changing mode */ + if (!add) + fprintf(ip, "link set %s down\n", lagname); + + fprintf(ip, "link %s dev %s", op, lagname); + + if (add) + link_gen_address(cif, ip); + + fprintf(ip, " type bond min_links 1"); + + mode = lydx_get_cattr(lag, "mode"); + if (!strcmp(mode, "lacp")) { + struct lyd_node *lacp = lydx_get_child(lag, "lacp"); + + if (add) + fprintf(ip, " mode 802.3ad ad_select bandwidth"); + + mode = lydx_get_cattr(lacp, "mode"); + if (!strcmp(mode, "active")) + mode = "on"; + else + mode = "off"; + + fprintf(ip, " lacp_rate %s lacp_active %s", + lydx_get_cattr(lacp, "rate"), mode); + } else { + /* XXX: mode hard-coded for now for mv88e6xxxx operation */ + if (add) + fprintf(ip, " mode balance-xor"); + } + + /* + * Required in lacp mode, we rely on it also in static mode. + * A previous attempt supported arp-monitor, but it does not + * work with mv88e6xxx link aggregates, unfortunately so was + * dropped for the final version. + */ + fprintf(ip, " miimon 100 use_carrier 1"); + + mon = lydx_get_descendant(lyd_child(lag), "link-monitor", NULL); + if (mon) { + const struct lyd_node *debounce; + + debounce = lydx_get_descendant(lyd_child(mon), "debounce", NULL); + if (debounce) { + const char *msec; + + msec = lydx_get_cattr(mon, "up"); + if (msec) + fprintf(ip, " updelay %s", msec); + msec = lydx_get_cattr(mon, "down"); + if (msec) + fprintf(ip, " downdelay %s", msec); + } + } + + fputs("\n", ip); + + /* + * When netdag_must_del() is triggered, this is when we reattach + * unmodified ports when recreating the lag. + */ + if (add) { + struct lyd_node *node, *cifs; + + cifs = lydx_get_descendant(lyd_parent(cif), "interfaces", "interface", NULL); + LYX_LIST_FOR_EACH(cifs, node, "interface") + err += lag_port_gen(NULL, node); + } + + return err; +} + +int lag_add_deps(struct lyd_node *cif) +{ + const char *lagname = lydx_get_cattr(cif, "name"); + struct ly_set *ports; + const char *portname; + int err = 0; + uint32_t i; + + ports = lydx_find_xpathf(cif, "../interface[lag-port/lag='%s']", lagname); + if (!ports) + return ERR_IFACE(cif, -ENOENT, "Unable to fetch lag ports"); + + + for (i = 0; i < ports->count; i++) { + portname = lydx_get_cattr(ports->dnodes[i], "name"); + + err = dagger_add_dep(&confd.netdag, lagname, portname); + if (err) { + ERR_IFACE(cif, err, "Unable to depend on \"%s\"", portname); + break; + } + } + + ly_set_free(ports, NULL); + return err; +} diff --git a/src/confd/yang/infix-if-lag.yang b/src/confd/yang/infix-if-lag.yang index 937c625d..9f67cc62 100644 --- a/src/confd/yang/infix-if-lag.yang +++ b/src/confd/yang/infix-if-lag.yang @@ -10,6 +10,12 @@ submodule infix-if-lag { import iana-if-type { prefix ianaift; } + import ietf-inet-types { + prefix inet; + } + import ietf-yang-types { + prefix yang; + } organization "KernelKit"; contact "kernelkit@googlegroups.com"; @@ -24,35 +30,93 @@ submodule infix-if-lag { * Typedefs */ - typedef lag-type { - description "Mode values for link aggregates."; + typedef lag-mode { + description "Mode values for link aggregates."; - /* Temporarily limited to 802.1AX and Balanced XOR */ - type enumeration { - enum static { - description "Static mode (Balanced XOR)."; - value 2; - } - enum lacp { - description "IEEE 802.3ad LACP mode."; - value 4; - } + type enumeration { + enum static { + description "Static mode (Balanced XOR)."; } + enum lacp { + description "IEEE 802.3ad LACP mode."; + } + } + } + + typedef lag-type { + description "Static mode type of distribution (driver modes)."; + + type enumeration { + enum balance-rr; + enum active-backup; + enum balance-xor; + enum broadcast; + enum balance-tlb; + enum balance-alb; + } + } + + typedef hash-policy { + description "Egress hash policy, note: offloading limitations!"; + + type enumeration { + enum layer2; + enum layer3-4; + enum layer2-3; + enum encap2-3; + enum encap3-4; + enum vlan-srcmac; + } + } + + typedef member-state { + description "Lag port membership state, taking active part or backup."; + + type enumeration { + enum backup; + enum active; + } } typedef lacp-mode { - description "LACP mode values."; + description "LACP mode values."; - type enumeration { - enum passive { - description "LACP active mode"; - value 0; - } - enum active { - description "LACP passive mode."; - value 1; - } + type enumeration { + enum passive { + description "LACP active mode"; } + enum active { + description "LACP passive mode."; + } + } + } + + typedef lacp-rate { + description "LACP rate values."; + + type enumeration { + enum slow { + description "Send LACPDUs every 30 seconds (90-second timeout)."; + } + enum fast { + description "Send LACPDUs every 1 second (3-second timeout)."; + } + } + } + + typedef lacp-state { + description "LACP port state flags."; + + type enumeration { + enum active; + enum short_timeout; + enum aggregating; + enum in_sync; + enum collecting; + enum distributing; + enum defaulted; + enum expired; + } } /* @@ -62,78 +126,8 @@ submodule infix-if-lag { grouping hash { leaf hash { description "Transmit hash policy."; + type hash-policy; config false; // For now, staically set to layer2 only - type string; - } - } - - grouping lacp-settings { - description "LACP mode settings."; - - container lacp { - description "Settings specific for LACP mode."; - - uses hash; - - leaf mode { - description "Operational mode of LACP, default: active. - - - Active: initiates negotiation by sending LACPDUs. - - Passive: waits for the peer to initiate. - - At least one end of the link must be in active mode. When both ends - are active, there is slightly more traffic, but the default ensures - fail-safe operation. - - Passive mode is typically used for troubleshooting, in dynamic - setups (e.g., MLAG), or to minimize the risk of unintended - aggregation. - - For most production scenarios, active mode is preferred to ensure - faster and more predictable link aggregation."; - type lacp-mode; - default "active"; - } - - leaf rate { - description "Rate of LACP keep-alives, default: slow. - - Determines the frequency of LACPDU transmission and the associated - timeout for link failure detection. - - - slow: Sends LACPDUs every 30 seconds. The associated timeout is 90 - seconds, meaning a link is considered failed after 3 consecutive - missed LACPDUs. - - - fast: Sends LACPDUs every 1 second. The associated timeout is 3 - seconds, meaning a link is considered failed after 3 consecutive - missed LACPDUs. - - The selected rate affects the responsiveness of link failure - detection and the amount of control traffic."; - type enumeration { - enum slow { - description "Send LACPDUs every 30 seconds (90-second timeout)."; - } - enum fast { - description "Send LACPDUs every 1 second (3-second timeout)."; - } - } - default "slow"; - } - } - } - - grouping static-settings { - container static { - config false; // For now, we need to read out mode and other status - - uses hash; - - leaf mode { - description "Active mode for static aggregates."; - type string; - } } } @@ -153,12 +147,100 @@ submodule infix-if-lag { leaf mode { description "Link aggregation mode."; - type lag-type; + type lag-mode; mandatory true; } - uses lacp-settings; - uses static-settings; + container lacp { + description "Settings specific for LACP mode."; + + uses hash; + + leaf mode { + description "Operational mode of LACP, default: active. + + - Active: initiates negotiation by sending LACPDUs. + - Passive: waits for the peer to initiate. + + At least one end of the link must be in active mode. When both ends + are active, there is slightly more traffic, but the default ensures + fail-safe operation. + + Passive mode is typically used for troubleshooting, in dynamic + setups (e.g., MLAG), or to minimize the risk of unintended + aggregation. + + For most production scenarios, active mode is preferred to ensure + faster and more predictable link aggregation."; + type lacp-mode; + default active; + } + + leaf rate { + description "Rate of LACP keep-alives, default: slow. + + Determines the frequency of LACPDU transmission and the associated + timeout for link failure detection. + + - slow: Sends LACPDUs every 30 seconds. The associated timeout is 90 + seconds, meaning a link is considered failed after 3 consecutive + missed LACPDUs. + + - fast: Sends LACPDUs every 1 second. The associated timeout is 3 + seconds, meaning a link is considered failed after 3 consecutive + missed LACPDUs. + + The selected rate affects the responsiveness of link failure + detection and the amount of control traffic."; + type lacp-rate; + default slow; + } + + leaf system-priority { + description "Sytem priority used by the node on this LAG interface. + + Lower value is higher priority for determining which node + is the controlling system."; + type uint16 { + range "1 .. 65535"; + } + } + + leaf aggregator-id { + description "Aggregator ID."; + config false; + type uint16; + } + + leaf actor-key { + description "Actor key."; + config false; + type uint16; + } + + leaf partner-key { + description "Partner key."; + config false; + type uint16; + } + + leaf partner-mac { + description "Partner MAC address."; + config false; + type yang:phys-address; + } + } + + container static { + config false; // For now, we need to read out mode and other status + + leaf mode { + description "Active mode for static aggregates."; + type lag-type; + } + + uses hash; + } container link-monitor { description "Link monitor properties."; @@ -183,6 +265,16 @@ submodule infix-if-lag { } } } + + must "not(./mode = 'lacp' and ./monitor/arp-monitor/interval > 0)" { + error-message "ARP monitor cannot be enabled in LACP mode."; + description "The Linux bond driver disables miimon when arpmon != 0."; + } + + must "not(./mode = 'lacp' and ./monitor/link-monitor/interval = 0)" { + error-message "Link monitor must be enabled in LACP mode."; + description "The Linux bond driver requires miimon in 802.3ad mode."; + } } } @@ -206,24 +298,36 @@ submodule infix-if-lag { error-message "Must refer to a valid LAG interface."; } } + leaf state { - description "Port state, active or backup member."; + description "Link state, active or backup member."; + type member-state; + config false; + } + + leaf link-failures { + description "Link failure counter, cannot be reset."; + type uint32; config false; - type string; } container lacp { description "LACP port state, ours and partner."; config false; + leaf aggregator-id { + description "Aggregator ID."; + type uint16; + } + leaf-list actor-state { description "LACP state flags."; - type string; + type lacp-state; } leaf-list partner-state { description "LACP state flags for link partner."; - type string; + type lacp-state; } } } diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 4d278b78..fa32d528 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -451,6 +451,34 @@ class Iface: self.pvid = get_json_data('', self.data, 'infix-interfaces:bridge-port', 'pvid') self.stp_state = get_json_data('', self.data, 'infix-interfaces:bridge-port', 'stp', 'cist', 'state') + + self.lag_mode = get_json_data('', self.data, 'infix-interfaces:lag', 'mode') + if self.lag_mode: + self.lag_type = get_json_data('', self.data, 'infix-interfaces:lag', 'static', 'mode') + if self.lag_mode == "lacp": + self.lag_hash = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'hash') + self.lacp_id = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'aggregator-id') + self.lacp_actor_key = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'actor-key') + self.lacp_partner_key = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'partner-key') + self.lacp_partner_mac = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'partner-mac') + self.lacp_sys_prio = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'system-priority') + else: + self.lag_hash = get_json_data('', self.data, 'infix-interfaces:lag', 'static', 'hash') + self.link_updelay = get_json_data('', self.data, 'infix-interfaces:lag', 'link-monitor', 'debounce', 'up') + self.link_downdelay = get_json_data('', self.data, 'infix-interfaces:lag', 'link-monitor', 'debounce', 'down') + + self.lacp_mode = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'mode') + rate = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'rate') + self.lacp_rate = "fast (1s)" if rate == "fast" else "slow (30 sec)" + + self.lag = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lag') + if self.lag: + self.lag_state = get_json_data('', self.data, 'infix-interfaces:lag-port', 'state') + self.lacp_id = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lacp', 'aggregator-id') + self.lacp_state = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lacp', 'actor-state') + self.lacp_pstate = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lacp', 'partner-state') + self.link_failures = get_json_data('', self.data, 'infix-interfaces:lag-port', 'link-failures') + self.containers = get_json_data('', self.data, 'infix-interfaces:container-network', 'containers') @@ -491,6 +519,9 @@ class Iface: def is_bridge(self): return self.type == "infix-if-type:bridge" + def is_lag(self): + return self.type == "infix-if-type:lag" + def is_veth(self): return self.data['type'] == "infix-if-type:veth" @@ -635,6 +666,59 @@ class Iface: self.pr_proto_ipv4() self.pr_proto_ipv6() + def pr_proto_lag(self, member=True): + data_str = "" + + row = f"{'lag':<{Pad.proto}}" + if member: + state = self.lag_state.upper() + if self.oper() == "up": + row += Decore.green(f"{state:<{Pad.state}}") + else: + row += Decore.yellow(f"{state:<{Pad.state}}") + if self.lacp_state: + lacp = ', '.join(self.lacp_state) + data_str += lacp + else: + dec = Decore.green if self.oper() == "up" else Decore.yellow + row += dec(f"{self.oper().upper():<{Pad.state}}") + data_str += f"{self.lag_mode}" + if self.lag_mode == "lacp": + data_str += f": {self.lacp_mode}" + data_str += f", rate: {self.lacp_rate}" + data_str += f", hash: {self.lag_hash}" + else: + data_str += f": {self.lag_type}" + data_str += f", hash: {self.lag_hash}" + + if data_str: + row += f"{data_str:<{Pad.data}}" + + print(row) + + def pr_lag(self, _ifaces): + self.pr_name(pipe="") + self.pr_proto_lag(member=False) + + lowers = [] + for _iface in [Iface(data) for data in _ifaces]: + if _iface.lag and _iface.lag == self.name: + lowers.append(_iface) + + if lowers: + self.pr_proto_eth(pipe='│') + self.pr_proto_ipv4(pipe='│') + self.pr_proto_ipv6(pipe='│') + else: + self.pr_proto_eth(pipe=' ') + self.pr_proto_ipv4() + self.pr_proto_ipv6() + + for i, lower in enumerate(lowers): + pipe = '└ ' if (i == len(lowers) -1) else '├ ' + lower.pr_name(pipe) + lower.pr_proto_lag() + def pr_veth(self, _ifaces): self.pr_name(pipe="") self.pr_proto_veth() @@ -716,6 +800,32 @@ class Iface: if self.phys_address: print(f"{'physical address':<{20}}: {self.phys_address}") + if self.lag_mode: + print(f"{'lag mode':<{20}}: {self.lag_mode}") + if self.lag_mode == "lacp": + print(f"{'lag hash':<{20}}: {self.lag_hash}") + print(f"{'lacp mode':<{20}}: {self.lacp_mode}") + print(f"{'lacp rate':<{20}}: {self.lacp_rate}") + print(f"{'lacp aggregate id':<{20}}: {self.lacp_id}") + print(f"{'lacp system priority':<{20}}: {self.lacp_sys_prio}") + print(f"{'lacp actor key':<{20}}: {self.lacp_actor_key}") + print(f"{'lacp partner key':<{20}}: {self.lacp_partner_key}") + print(f"{'lacp partner mac':<{20}}: {self.lacp_partner_mac}") + else: + print(f"{'lag type':<{20}}: {self.lag_type}") + print(f"{'lag hash':<{20}}: {self.lag_hash}") + print(f"{'link debounce up':<{20}}: {self.link_updelay} msec") + print(f"{'link debounce down':<{20}}: {self.link_downdelay} msec") + + if self.lag: + print(f"{'lag member':<{20}}: {self.lag}") + print(f"{'lag member state':<{20}}: {self.lag_state}") + if self.lacp_state: + print(f"{'lacp aggregate id':<{20}}: {self.lacp_id}") + print(f"{'lacp actor state':<{20}}: {', '.join(self.lacp_state)}") + print(f"{'lacp partner state':<{20}}: {', '.join(self.lacp_pstate)}") + print(f"{'link failure count':<{20}}: {self.link_failures}") + if self.ipv4_addr: first = True for addr in self.ipv4_addr: @@ -872,6 +982,10 @@ def pr_interface_list(json): iface.pr_bridge(ifaces) continue + if iface.is_lag(): + iface.pr_lag(ifaces) + continue + if iface.is_veth(): iface.pr_veth(ifaces) continue @@ -892,11 +1006,14 @@ def pr_interface_list(json): iface.pr_vlan(ifaces) continue - # These interfaces are printed by there parent, such as bridge + # These interfaces are printed by their parent, such as bridge if iface.lower_if: continue if iface.bridge: continue + if iface.lag: + continue + print_interface(iface) diff --git a/src/statd/python/yanger/ietf_interfaces/lag.py b/src/statd/python/yanger/ietf_interfaces/lag.py new file mode 100644 index 00000000..ec4c351c --- /dev/null +++ b/src/statd/python/yanger/ietf_interfaces/lag.py @@ -0,0 +1,79 @@ +"""The lag/bond oper-status always follows the carrier""" + + +def lower(iplink): + """Return a dictionary of the status of a lag member""" + port = { + "lag": iplink['master'], + } + + info = iplink['linkinfo']['info_slave_data'] + if info: + # active or backup link + port['state'] = info['state'].lower() + port['link-failures'] = info['link_failure_count'] + + # On etherlike interfaces and tap interfaces oper-status lies + # if info['mii_status'] == "DOWN": + # iface['oper-status'] = "down" + + # Initialize lacp dict only if we encounter LACP-related fields + if 'ad_aggregator_id' in info: + port['lacp'] = {} + port['lacp']['aggregator-id'] = info['ad_aggregator_id'] + port['lacp']['actor-state'] = info['ad_actor_oper_port_state_str'] + port['lacp']['partner-state'] = info['ad_partner_oper_port_state_str'] + else: + port['state'] = 'backup' + port['link-failures'] = 0 + + return port + + +def lag(iplink): + """Return a dictionary of the status of the lag""" + mode = { + "balance-xor": "static", + "802.3ad": "lacp", + } + hash_policy = { + "layer2": "layer2", + "layer3+4": "layer3-4", + "layer2+3": "layer2-3", + "encap2+3": "encap2-3", + "encap3+4": "encap3-4", + "vlan+srcmac": "vlan-srcmac", + } + bond = {} + + info = iplink["linkinfo"]["info_data"] + if info: + bond["mode"] = mode.get(info['mode'], "static") + if bond["mode"] == "lacp": + bond["lacp"] = { + "mode": 'active' if info['ad_lacp_active'] == "on" else 'passive', + "rate": info['ad_lacp_rate'], + "hash": hash_policy.get(info['xmit_hash_policy'], "layer2"), + } + + if 'ad_info' in info: + bond["lacp"]["aggregator-id"] = info['ad_info']['aggregator'] + bond["lacp"]["actor-key"] = info['ad_info']['actor_key'] + bond["lacp"]["partner-key"] = info['ad_info']['partner_key'] + bond["lacp"]["partner-mac"] = info['ad_info']['partner_mac'] + if 'ad_actor_sys_prio' in info: + bond["lacp"]["system-priority"] = info['ad_actor_sys_prio'] + else: + bond["static"] = { + "mode": info['mode'], + "hash": info['xmit_hash_policy'] + } + + bond["link-monitor"] = { + "debounce": { + "up": info['updelay'], + "down": info['downdelay'] + } + } + + return bond diff --git a/src/statd/python/yanger/ietf_interfaces/link.py b/src/statd/python/yanger/ietf_interfaces/link.py index 05a9a5a2..b53dd6a7 100644 --- a/src/statd/python/yanger/ietf_interfaces/link.py +++ b/src/statd/python/yanger/ietf_interfaces/link.py @@ -3,6 +3,7 @@ from . import common from . import bridge from . import ethernet from . import ip +from . import lag from . import tun from . import veth from . import vlan @@ -117,16 +118,16 @@ def interface(iplink, ipaddr): interface["infix-interfaces:bridge"] = br if brport := bridge.lower(iplink): interface["infix-interfaces:bridge-port"] = brport - # case "infix-if-type:lag": - # if l := lag.lag(iplink): - # interface["infix-interfaces:lag"] = l + case "infix-if-type:lag": + if lg := lag.lag(iplink): + interface["infix-interfaces:lag"] = lg case "infix-if-type:ethernet": if eth := ethernet.ethernet(iplink): interface["ieee802-ethernet-interface:ethernet"] = eth case "infix-if-type:vxlan": if vxlan := tun.vxlan(iplink): interface["infix-interfaces:vxlan"] = vxlan - case "infix-if-type:gre"|"infix-if-type:gretap": + case "infix-if-type:gre" | "infix-if-type:gretap": if gre := tun.gre(iplink): interface["infix-interfaces:gre"] = gre case "infix-if-type:veth": @@ -140,9 +141,9 @@ def interface(iplink, ipaddr): case "infix-interfaces:bridge-port": if brport := bridge.lower(iplink): interface["infix-interfaces:bridge-port"] = brport - # case "infix-interfaces:lag-port": - # if lagport := lag.lower(iplink): - # interface["infix-interfaces:lag-port"] = lagport + case "infix-interfaces:lag-port": + if lagport := lag.lower(iplink): + interface["infix-interfaces:lag-port"] = lagport return interface From d7895d5c9c02ac3b6eb652e309fa5dc45f5b680d Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 16 Dec 2024 01:17:29 +0100 Subject: [PATCH 11/27] confd: handle dup calls to dagger_add_dep() Some callbacks may run twice, e.g., lag_gen_ports(). Check if the link already exists, and is the same, then we can exit silently. However, in case the link exists and does *not* point to the same target, we log an error with current target for the post mortem. Signed-off-by: Joachim Wiberg --- src/confd/src/dagger.c | 28 +++++++++++++++++++++++++--- src/confd/src/dagger.h | 2 +- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/confd/src/dagger.c b/src/confd/src/dagger.c index 9dfd3e28..a8aea6f5 100644 --- a/src/confd/src/dagger.c +++ b/src/confd/src/dagger.c @@ -87,10 +87,32 @@ FILE *dagger_fopen_net_exit(struct dagger *d, const char *node, enum netdag_exit return dagger_fopen_current(d, "exit", node, order, script); } -int dagger_add_dep(struct dagger *d, const char *depender, const char *dependee) +int dagger_add_dep(const struct dagger *d, const char *depender, const char *dependee) { - return systemf("ln -s ../%s %s/%d/dag/%s", dependee, - d->path, d->next, depender); + char link[strlen(d->path) + strlen(depender) + strlen(dependee) + 16]; + char target[strlen(dependee) + 16]; + char path[strlen(dependee) + 16]; + ssize_t len; + + /* + * Some callbacks may run twice, double check symlink, if it + * exists already and points to the same target, we're OK. + */ + snprintf(target, sizeof(target), "../%s", dependee); + snprintf(link, sizeof(link), "%s/%d/dag/%s/%s", d->path, d->next, depender, dependee); + + len = readlink(link, path, sizeof(path)); + if (len > 0) { + path[len] = 0; + if (strcmp(target, path)) { + ERROR("Dagger dependency already exists %s -> %s", target, path); + return errno = EEXIST; + } + + return 0; /* same, ignore */ + } + + return symlink(target, link); } int dagger_add_node(struct dagger *d, const char *node) diff --git a/src/confd/src/dagger.h b/src/confd/src/dagger.h index d98469b1..959aa8d6 100644 --- a/src/confd/src/dagger.h +++ b/src/confd/src/dagger.h @@ -21,7 +21,7 @@ FILE *dagger_fopen_next(struct dagger *d, const char *action, const char *node, FILE *dagger_fopen_current(struct dagger *d, const char *action, const char *node, unsigned char prio, const char *script); -int dagger_add_dep(struct dagger *d, const char *depender, const char *dependee); +int dagger_add_dep(const struct dagger *d, const char *depender, const char *dependee); int dagger_add_node(struct dagger *d, const char *node); int dagger_abandon(struct dagger *d); int dagger_evolve(struct dagger *d); From 3dbb1759ebe120d188edc5ca5d2933649bb40211 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 19 Dec 2024 18:08:09 +0100 Subject: [PATCH 12/27] klish-plugin-infix: need CAP_NET_ADMIN for some data When reading lag status, like LACP actor system prio, port key, etc., we need CAP_NET_ADMIN. See net/bonding/bond_netlink.c Signed-off-by: Joachim Wiberg --- src/klish-plugin-infix/xml/infix.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/klish-plugin-infix/xml/infix.xml b/src/klish-plugin-infix/xml/infix.xml index d7cbd840..98a9d98a 100644 --- a/src/klish-plugin-infix/xml/infix.xml +++ b/src/klish-plugin-infix/xml/infix.xml @@ -401,11 +401,11 @@ if [ -n "$KLISH_PARAM_name" ]; then - sysrepocfg -f json -X -d operational -x \ + doas sysrepocfg -f json -X -d operational -x \ "/ietf-interfaces:interfaces/interface[name=\"$KLISH_PARAM_name\"]" | \ /usr/libexec/statd/cli-pretty "show-interfaces" -n "$KLISH_PARAM_name" |pager else - sysrepocfg -f json -X -d operational -m ietf-interfaces | \ + doas sysrepocfg -f json -X -d operational -m ietf-interfaces | \ /usr/libexec/statd/cli-pretty "show-interfaces" |pager fi From 773683bfd5300f7d6116ae068be8f17a849d2a47 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 3 Feb 2025 10:16:11 +0100 Subject: [PATCH 13/27] test: spec: catch and format dot file errors Signed-off-by: Joachim Wiberg --- test/spec/generate_spec.py | 49 +++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/test/spec/generate_spec.py b/test/spec/generate_spec.py index 71f79d8f..cb44be41 100755 --- a/test/spec/generate_spec.py +++ b/test/spec/generate_spec.py @@ -11,6 +11,7 @@ from pathlib import Path import graphviz + def replace_image_tag(text, test_dir): """ Convert images added in the description and replace with the required ifdefs to work @@ -106,33 +107,43 @@ def replace_in_steps(steps, variables): class TestCase: """All test specifcation resources for a test case""" def __init__(self, directory, rootdir=None): - self.test_dir=Path(directory) + self.test_dir = Path(directory) if rootdir: - rootdir=Path(f"{rootdir}") - self.test_dir=self.test_dir.relative_to(rootdir) - self.topology_dot=f"{directory}/topology.dot" - self.topology_image=f"{directory}/topology" - self.test_case=f"{directory}/test.py" - self.specification=f"{directory}/Readme.adoc" + rootdir = Path(f"{rootdir}") + self.test_dir = self.test_dir.relative_to(rootdir) + self.topology_dot = f"{directory}/topology.dot" + self.topology_image = f"{directory}/topology" + self.test_case = f"{directory}/test.py" + self.specification = f"{directory}/Readme.adoc" def generate_topology(self): """Generate SVG file from the topology.dot file""" - with open(self.topology_dot, 'r') as dot_file: - dot_graph = dot_file.read() - graph = graphviz.Source(dot_graph) - graph.render(self.topology_image, format='svg', cleanup=True) - pattern = r'' - content="" - with open(f"{self.topology_image}.svg", "r") as f: - content = f.read() - mod_content = re.sub(pattern, '',content, flags=re.DOTALL) - with open(f"{self.topology_image}.svg", 'w') as f: - f.write(mod_content) + with open(self.topology_dot, 'r', encoding='utf-8') as dot_file: + svg_file = f"{self.topology_image}.svg" + try: + dot_graph = dot_file.read() + graph = graphviz.Source(dot_graph) + graph.render(self.topology_image, format='svg', cleanup=True, + quiet=True) + + content = "" + with open(svg_file, 'r', encoding='utf-8') as f: + content = f.read() + + pattern = r'' + mod_content = re.sub(pattern, '', content, flags=re.DOTALL) + with open(svg_file, 'w', encoding='utf-8') as f: + f.write(mod_content) + except graphviz.backend.execute.CalledProcessError as e: + msg = e.stderr.decode() + print(f"Failed rendering SVG from {self.topology_dot}: {msg}") + except UnboundLocalError: + print(f"Failed cleaning {svg_file}, empty or missing.") def generate_specification(self, name, case_path, spec_path, variables): """Generate a ASCIIDOC specification for the test case""" - with open(case_path, 'r') as file: + with open(case_path, 'r', encoding='utf-8') as file: script_content = file.read() parsed_script = ast.parse(script_content) From 9782ac9afab6e9e186c15cf1dffc976d1fb3cbfd Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sat, 14 Dec 2024 22:11:35 +0100 Subject: [PATCH 14/27] test: update virt topologies, add lag This commit adds two SVGs for previewing the virtual test topologies. quad: fix a lot of copy-paste mistakes, rename nodes and ports to their correct names. Replace shell comments with C++ style comments, not all tools, e.g., dotty, support shell comments. The neato absolute positioning has also been fixed to make it possible to see all the edges, to facilitate this the used ports have been swapped around a abit. Colors are used according to the schema agreed on for tests. Finally, a second link for lag tests have been added between dut2 and dut3. dual: coloring similar to quad, update to new edge attribute matcher. Signed-off-by: Joachim Wiberg --- test/infamy/topologies/ring-4-duts.dot | 6 +- test/virt/dual/topology.dot.in | 31 +-- test/virt/dual/topology.svg | 112 +++++++++++ test/virt/quad/topology.dot.in | 51 ++--- test/virt/quad/topology.svg | 254 +++++++++++++++++++++++++ 5 files changed, 414 insertions(+), 40 deletions(-) create mode 100644 test/virt/dual/topology.svg create mode 100644 test/virt/quad/topology.svg diff --git a/test/infamy/topologies/ring-4-duts.dot b/test/infamy/topologies/ring-4-duts.dot index fb649e9b..c4c14a5c 100644 --- a/test/infamy/topologies/ring-4-duts.dot +++ b/test/infamy/topologies/ring-4-duts.dot @@ -43,19 +43,19 @@ graph "ring-4-duts" { host:mgmt3 -- R3:mgmt [kind=mgmt, color="lightgray"] host:mgmt4 -- R4:mgmt [kind=mgmt, color="lightgray"] - # host-Dut links + // host-Dut links host:data1 -- R1:data [color="darkgreen"] host:data2 -- R2:data [color="darkgreen"] host:data3 -- R3:data [color="darkgreen"] host:data4 -- R4:data [color="darkgreen"] - # Ring + // Ring R1:ring1 -- R2:ring2 [color="blue",headlabel=".2", label="10.0.12.1/30", taillabel=".1", labeldistance=1, fontcolor="blue"] R2:ring1 -- R3:ring2 [color="blue",headlabel=".2", label="10.0.23.0/30", taillabel=".1", labeldistance=1, fontcolor="blue"] R3:ring1 -- R4:ring2 [color="blue",headlabel=".2", label="192.168.4.0/24", taillabel=".1", labeldistance=1 fontcolor="blue"] R4:ring1 -- R1:ring2 [color="blue",headlabel=".2", label="10.0.41.0/30", taillabel=".1", labeldistance=1, fontcolor="blue"] - # Cross + // Cross R1:cross -- R3:cross [color="black", label="10.0.13.0/30\n\n"] R2:cross -- R4:cross [color="brown", label="\n\n10.0.24.0/30", fontcolor="brown"] } diff --git a/test/virt/dual/topology.dot.in b/test/virt/dual/topology.dot.in index 588a2ee2..3ce0fab1 100644 --- a/test/virt/dual/topology.dot.in +++ b/test/virt/dual/topology.dot.in @@ -2,7 +2,8 @@ graph "dual" { layout="neato"; overlap="false"; splines="true"; - esep="+20"; + esep="+30"; + sep="+30"; node [shape=record, fontname="monospace"]; edge [color="cornflowerblue", penwidth="2"]; @@ -14,14 +15,16 @@ graph "dual" { host [ label="host | { d1a | d1b | d1c | d2a | d2b | d2c }", - color="grey",fontcolor="grey",pos="0,15!", - kind="controller", + color="grey", + fontcolor="grey", + pos="0,15!", + provides="controller", ]; dut1 [ label="{ e1 | e2 | e3 } | dut1 | { e4 | e5 | e6 }", pos="10,18!", - kind="infix", + provides="infix", qn_console=9001, qn_mem="384M", qn_usb="dut1.usb" @@ -29,21 +32,21 @@ graph "dual" { dut2 [ label="{ e1 | e2 | e3 } | dut2 | { e4 | e5 | e6 }", pos="10,12!", - kind="infix", + provides="infix", qn_console=9002, qn_mem="384M", qn_usb="dut2.usb" ]; - host:d1a -- dut1:e1 [kind=mgmt] - host:d1b -- dut1:e2 - host:d1c -- dut1:e3 + host:d1a -- dut1:e1 [provides=mgmt, color="lightgray"] + host:d1b -- dut1:e2 [provides="ieee-mc"] + host:d1c -- dut1:e3 [provides="ieee-mc"] - host:d2a -- dut2:e1 [kind=mgmt] - host:d2b -- dut2:e2 - host:d2c -- dut2:e3 + host:d2a -- dut2:e1 [provides=mgmt, color="lightgray"] + host:d2b -- dut2:e2 [provides="ieee-mc"] + host:d2c -- dut2:e3 [provides="ieee-mc"] - dut1:e4 -- dut2:e6 - dut1:e5 -- dut2:e5 - dut1:e6 -- dut2:e4 + dut1:e4 -- dut2:e6 [color="black"] + dut1:e5 -- dut2:e5 [color="red"] + dut1:e6 -- dut2:e4 [color="black"] } diff --git a/test/virt/dual/topology.svg b/test/virt/dual/topology.svg new file mode 100644 index 00000000..c286cd29 --- /dev/null +++ b/test/virt/dual/topology.svg @@ -0,0 +1,112 @@ + + + + + + +dual + + + +host + +host + +d1a + +d1b + +d1c + +d2a + +d2b + +d2c + + + +dut1 + +e1 + +e2 + +e3 + +dut1 + +e4 + +e5 + +e6 + + + +host:d1a--dut1:e1 + + + + +host:d1b--dut1:e2 + + + + +host:d1c--dut1:e3 + + + + +dut2 + +e1 + +e2 + +e3 + +dut2 + +e4 + +e5 + +e6 + + + +host:d2a--dut2:e1 + + + + +host:d2b--dut2:e2 + + + + +host:d2c--dut2:e3 + + + + +dut1:e4--dut2:e6 + + + + +dut1:e5--dut2:e5 + + + + +dut1:e6--dut2:e4 + + + + diff --git a/test/virt/quad/topology.dot.in b/test/virt/quad/topology.dot.in index b5b56612..f548c857 100644 --- a/test/virt/quad/topology.dot.in +++ b/test/virt/quad/topology.dot.in @@ -2,7 +2,8 @@ graph "quad" { layout="neato"; overlap="false"; splines="true"; - esep="+20"; + esep="+30"; + sep="+30"; node [shape=record, fontname="monospace"]; edge [color="cornflowerblue", penwidth="2"]; @@ -13,30 +14,31 @@ graph "quad" { qn_append="quiet"; host [ - label="host | { d1a | d1b | d1c | d1d | d2a | d2b | d2c | d2d | d3a | d3b | d3c | d3d | d4a | d4b | d4c | d4d }", - color="grey",fontcolor="grey",pos="0,15!", + label="host | { d1a | d1b | d1c | d1d | d2a | d2b | d2c | d2d | d3a | d3b | d3c | d3d | d4a | d4b | d4c | d4d }", + color="grey", fontcolor="grey", + pos="-15,15!", provides="controller", ]; dut1 [ - label="{ e1 | e2 | e3 } | dut1 | { e4 | e5 | e6 | e7 | e8}", - pos="10,18!", + label="{ e1 | e2 | e3 | e4 } | dut1 | { e5 | e6 | e7 | e8}", + pos="10,30!", provides="infix", qn_console=9001, qn_mem="384M", qn_usb="dut1.usb" ]; dut2 [ - label="{ e1 | e2 | e3 } | dut2 | { e4 | e5 | e6 | e7 | e8}", - pos="10,12!", + label="{ e1 | e2 | e3 | e4 } | dut2 | { e5 | e6 | e7 | e8}", + pos="0,20!", provides="infix", qn_console=9002, qn_mem="384M", qn_usb="dut2.usb" ]; dut3 [ - label="{ e1 | e2 | e3 } | dut2 | { e4 | e5 | e6 | e7 | e8}", - pos="10,12!", + label="{ e1 | e2 | e3 | e4 } | dut3 | { e5 | e6 | e7 | e8}", + pos="0,10!", provides="infix", qn_console=9003, qn_mem="384M", @@ -44,41 +46,44 @@ graph "quad" { ]; dut4 [ - label="{ e1 | e2 | e3 } | dut2 | { e4 | e5 | e6 | e7 | e8}", - pos="10,12!", + label="{ e1 | e2 | e3 | e4 } | dut4 | { e5 | e6 | e7 | e8}", + pos="10,0!", provides="infix", qn_console=9004, qn_mem="384M", qn_usb="dut4.usb" ]; - host:d1a -- dut1:e1 [provides="mgmt"] + host:d1a -- dut1:e1 [provides="mgmt", color="lightgray"] host:d1b -- dut1:e2 [provides="ieee-mc"] host:d1c -- dut1:e3 [provides="ieee-mc"] host:d1d -- dut1:e4 [provides="ieee-mc"] - host:d2a -- dut2:e1 [provides="mgmt"] + host:d2a -- dut2:e1 [provides="mgmt", color="lightgray"] host:d2b -- dut2:e2 [provides="ieee-mc"] host:d2c -- dut2:e3 [provides="ieee-mc"] host:d2d -- dut2:e4 [provides="ieee-mc"] - host:d3a -- dut3:e1 [provides="mgmt"] + host:d3a -- dut3:e1 [provides="mgmt", color="lightgray"] host:d3b -- dut3:e2 [provides="ieee-mc"] host:d3c -- dut3:e3 [provides="ieee-mc"] host:d3d -- dut3:e4 [provides="ieee-mc"] - host:d4a -- dut4:e1 [provides="mgmt"] + host:d4a -- dut4:e1 [provides="mgmt", color="lightgray"] host:d4b -- dut4:e2 [provides="ieee-mc"] host:d4c -- dut4:e3 [provides="ieee-mc"] host:d4d -- dut4:e4 [provides="ieee-mc"] - # Ring - dut1:e5 -- dut2:e6 - dut2:e5 -- dut3:e6 - dut3:e5 -- dut4:e6 - dut4:e5 -- dut1:e6 + // Lag + dut2:e7 -- dut3:e6 [color="black", penwidth="3"] + dut2:e8 -- dut3:e5 [color="black", penwidth="3"] - # Cross-links - dut3:e7 -- dut1:e7 - dut2:e7 -- dut4:e7 + // Ring + dut1:e8 -- dut2:e5 [color="black"] + dut3:e8 -- dut4:e5 [color="black"] + dut4:e8 -- dut1:e5 [color="black"] + + // Cross-links + dut1:e6 -- dut3:e7 [color="red"] + dut2:e6 -- dut4:e7 [color="red"] } diff --git a/test/virt/quad/topology.svg b/test/virt/quad/topology.svg new file mode 100644 index 00000000..ff29e34f --- /dev/null +++ b/test/virt/quad/topology.svg @@ -0,0 +1,254 @@ + + + + + + +quad + + + +host + +host + +d1a + +d1b + +d1c + +d1d + +d2a + +d2b + +d2c + +d2d + +d3a + +d3b + +d3c + +d3d + +d4a + +d4b + +d4c + +d4d + + + +dut1 + +e1 + +e2 + +e3 + +e4 + +dut1 + +e5 + +e6 + +e7 + +e8 + + + +host:d1a--dut1:e1 + + + + +host:d1b--dut1:e2 + + + + +host:d1c--dut1:e3 + + + + +host:d1d--dut1:e4 + + + + +dut2 + +e1 + +e2 + +e3 + +e4 + +dut2 + +e5 + +e6 + +e7 + +e8 + + + +host:d2a--dut2:e1 + + + + +host:d2b--dut2:e2 + + + + +host:d2c--dut2:e3 + + + + +host:d2d--dut2:e4 + + + + +dut3 + +e1 + +e2 + +e3 + +e4 + +dut3 + +e5 + +e6 + +e7 + +e8 + + + +host:d3a--dut3:e1 + + + + +host:d3b--dut3:e2 + + + + +host:d3c--dut3:e3 + + + + +host:d3d--dut3:e4 + + + + +dut4 + +e1 + +e2 + +e3 + +e4 + +dut4 + +e5 + +e6 + +e7 + +e8 + + + +host:d4a--dut4:e1 + + + + +host:d4b--dut4:e2 + + + + +host:d4c--dut4:e3 + + + + +host:d4d--dut4:e4 + + + + +dut1:e8--dut2:e5 + + + + +dut1:e6--dut3:e7 + + + + +dut2:e7--dut3:e6 + + + + +dut2:e8--dut3:e5 + + + + +dut2:e6--dut4:e7 + + + + +dut3:e8--dut4:e5 + + + + +dut4:e8--dut1:e5 + + + + From f9b155b49fbff8d8ece41320da5c5c6ded1096d6 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 12 Jan 2025 09:06:00 +0100 Subject: [PATCH 15/27] test: infamy: use decorator to reduce boilerplate for test args Example use: @infamy.test_argument("--mode", help="one of 'static' or 'lacp'") class TestArgs(infamy.ArgumentParser): pass Signed-off-by: Joachim Wiberg --- test/infamy/__init__.py | 1 + test/infamy/env.py | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/test/infamy/__init__.py b/test/infamy/__init__.py index fae02eb0..4c44ec8c 100644 --- a/test/infamy/__init__.py +++ b/test/infamy/__init__.py @@ -3,6 +3,7 @@ import os from .container import Container from .env import Env from .env import ArgumentParser +from .env import test_argument from .furl import Furl from .netns import IsolatedMacVlan,IsolatedMacVlans from .sniffer import Sniffer diff --git a/test/infamy/env.py b/test/infamy/env.py index 8fd6032f..3cc85465 100644 --- a/test/infamy/env.py +++ b/test/infamy/env.py @@ -16,6 +16,7 @@ class NullEnv: ENV = NullEnv() + class ArgumentParser(): def DefaultTransport(): """Pick pseudo-random transport @@ -41,7 +42,6 @@ class ArgumentParser(): self.args.add_argument("ptop", nargs=1, metavar="topology") self.args.add_argument("-l", "--logical-topology", dest="ltop", default=top) - def add_argument(self, *args, **kwargs): kwargs["required"] = True self.args.add_argument(*args, **kwargs) @@ -49,6 +49,21 @@ class ArgumentParser(): def parse_args(self, argv): return self.args.parse_args(argv) + +def test_argument(option, **kwargs): + """See lag_failure/test.py for an example @infamy.test_argumet()""" + def decorator(cls): + super_init = cls.__init__ + + def new_init(self, *args, **kw): + super_init(self, *args, **kw) + self.add_argument(option, **kwargs) + + cls.__init__ = new_init + return cls + return decorator + + class Env(object): def __init__(self, ltop=None, args=None, argv=sys.argv[1::], environ=os.environ): if "INFAMY_ARGS" in environ: From 5bf920088089d1d74508f84b9aacbad5ab7ba860 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 12 Jan 2025 22:24:12 +0100 Subject: [PATCH 16/27] test: infamy: drop debug message breaking tap output When first starting up the YANG models are downloaded. Initially all were printed on the test console, so this was later changed to become a sort of progress output. However, that broke tap output, causing a missing leading '#' for the final "YANG models downloaded." message. Thist patch drops the output entirely from the netconf transport, it was never added to the restconf transport. Signed-off-by: Joachim Wiberg --- test/infamy/netconf.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/infamy/netconf.py b/test/infamy/netconf.py index 1bb1f040..5d856930 100644 --- a/test/infamy/netconf.py +++ b/test/infamy/netconf.py @@ -155,8 +155,7 @@ class Device(Transport): for schema in schemas: if os.path.exists(yangdir + "/" + schema['filename']) is False: self.get_schema(schema, yangdir) - sys.stdout.write("Downloading YANG model " - f"{schema['identifier']} ...\r\033[K") + print("YANG models downloaded.") def _ly_init(self, yangdir): From dc5c7732d7d7f0cdb637f23fe44068e620fab06b Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Fri, 10 Jan 2025 11:10:02 +0100 Subject: [PATCH 17/27] test: infamy: return None if port doesn't exist Follow-up to e4535aa, allowing tests to be even simpler. Signed-off-by: Joachim Wiberg --- test/infamy/transport.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/infamy/transport.py b/test/infamy/transport.py index 922a9de2..2148073b 100644 --- a/test/infamy/transport.py +++ b/test/infamy/transport.py @@ -56,7 +56,9 @@ class Transport(ABC): pass def __getitem__(self, key): - return self.mapping[key] + if key in self.mapping: + return self.mapping[key] + return None def get_iface(self, name): """Fetch target dict for iface and extract param from JSON""" From a90a39e8d877611b107471af2405694ba6a2edc9 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 8 Jan 2025 11:11:21 +0100 Subject: [PATCH 18/27] test: infamy: allow easy debugging of differences in config data Unclear if the libyang merge operation actually does it's job of merging all the data properly in the restconf backend. Signed-off-by: Joachim Wiberg --- test/infamy/netconf.py | 8 ++++++-- test/infamy/restconf.py | 11 +++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/test/infamy/netconf.py b/test/infamy/netconf.py index 5d856930..711d684a 100644 --- a/test/infamy/netconf.py +++ b/test/infamy/netconf.py @@ -308,20 +308,24 @@ class Device(Transport): break def put_config_dicts(self, models): + """PUT full configuration of all models to running-config""" config = "" infer_put_dict(self.name, models) for model in models.keys(): mod = self.ly.get_module(model) lyd = mod.parse_data_dict(models[model], no_state=True, validate=False) - config+=lyd.print_mem("xml", with_siblings=True, pretty=False)+"\n" + config += lyd.print_mem("xml", with_siblings=True, pretty=False) + "\n" + # print(f"Send new XML config: {config}") return self.put_config(config) def put_config_dict(self, modname, edit): """Convert Python dictionary to XMl and send as configuration""" mod = self.ly.get_module(modname) lyd = mod.parse_data_dict(edit, no_state=True, validate=False) - return self.put_config(lyd.print_mem("xml", with_siblings=True, pretty=False)) + config = lyd.print_mem("xml", with_siblings=True, pretty=False) + # print(f"Send new XML config: {config}") + return self.put_config(config) def call(self, call): """Call RPC, XML version""" diff --git a/test/infamy/restconf.py b/test/infamy/restconf.py index 67e1d1e0..4d6f3ce5 100644 --- a/test/infamy/restconf.py +++ b/test/infamy/restconf.py @@ -265,6 +265,7 @@ class Device(Transport): return {container: v} def put_config_dicts(self, models): + """PUT full configuration of all models to running-config""" infer_put_dict(self.name, models) running = self.get_running() @@ -273,8 +274,9 @@ class Device(Transport): lyd = mod.parse_data_dict(models[model], no_state=True, validate=False) running.merge(lyd) - return self.put_datastore("running", json.loads(running.print_mem("json", with_siblings=True, pretty=False))) - + cfg = running.print_mem("json", with_siblings=True, pretty=True) + # print(f"PUT new running-config: {cfg}") + return self.put_datastore("running", json.loads(cfg)) def put_config_dict(self, modname, edit): """Add @edit to running config and put the whole configuration""" @@ -298,8 +300,9 @@ class Device(Transport): change = mod.parse_data_dict(edit, no_state=True, validate=False) running.merge_module(change) - data = json.loads(running.print_mem("json", with_siblings=True, - pretty=False)) + cfg = running.print_mem("json", with_siblings=True, pretty=False) + # print(f"PUT new running-config: {cfg}") + data = json.loads(cfg) return self.put_datastore("running", data) From 3ef1da1096033bd147e6ec7a4f7282d3004c6d94 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 16 Jan 2025 07:55:08 +0100 Subject: [PATCH 19/27] test: minor cleanup using new APIs Signed-off-by: Joachim Wiberg --- .../bridge_vlan/bridge-vlan.svg | 4 ++ .../bridge_vlan/bridge_vlan.adoc | 25 ++++---- test/case/ietf_interfaces/bridge_vlan/test.py | 59 ++++++------------- 3 files changed, 37 insertions(+), 51 deletions(-) create mode 100644 test/case/ietf_interfaces/bridge_vlan/bridge-vlan.svg diff --git a/test/case/ietf_interfaces/bridge_vlan/bridge-vlan.svg b/test/case/ietf_interfaces/bridge_vlan/bridge-vlan.svg new file mode 100644 index 00000000..7eca2cb5 --- /dev/null +++ b/test/case/ietf_interfaces/bridge_vlan/bridge-vlan.svg @@ -0,0 +1,4 @@ + + + +
dut1
dut2
data
link
link
pc
data
ip
bridge
vlan10
bridge
ip
VLAN filtering
ip
link.10
10u
10t
VLAN trunk vid:10
10.0.0.1/24
10.0.0.2/24
10.0.0.3/24
10u
\ No newline at end of file diff --git a/test/case/ietf_interfaces/bridge_vlan/bridge_vlan.adoc b/test/case/ietf_interfaces/bridge_vlan/bridge_vlan.adoc index da408803..c52ca63c 100644 --- a/test/case/ietf_interfaces/bridge_vlan/bridge_vlan.adoc +++ b/test/case/ietf_interfaces/bridge_vlan/bridge_vlan.adoc @@ -1,15 +1,20 @@ === Bridge VLAN ==== Description -Basic test of VLAN functionality in a bridge, tagged/untagged traffic and a VLAN interface in the bridge. -.... - ¦ ¦ - ¦ vlan10 IP:10.0.0.2 ¦ br0 IP:10.0.0.3 - ¦ / ¦ / - ¦ br0 <-- VLAN filtering ¦ link.10 - ¦ u/ \t ¦ / - PC ------data link -----------------|-- link - ¦ dut1 ¦ dut2 -.... +Verify VLAN filtering bridge, with a VLAN trunk to a neighboring device, +which in turn untags one VLAN outisde a non-VLAN filtering bridge. + +.Logical network setup +ifdef::topdoc[] +image::../../test/case/ietf_interfaces/bridge_vlan/bridge-vlan.svg[] +endif::topdoc[] +ifndef::topdoc[] +ifdef::testgroup[] +image::bridge_vlan/bridge-vlan.svg[] +endif::testgroup[] +ifndef::testgroup[] +image::bridge-vlan.svg[] +endif::testgroup[] +endif::topdoc[] ==== Topology ifdef::topdoc[] diff --git a/test/case/ietf_interfaces/bridge_vlan/test.py b/test/case/ietf_interfaces/bridge_vlan/test.py index 98714329..5a2e58c4 100755 --- a/test/case/ietf_interfaces/bridge_vlan/test.py +++ b/test/case/ietf_interfaces/bridge_vlan/test.py @@ -1,32 +1,22 @@ #!/usr/bin/env python3 -""" -Bridge VLAN +"""Bridge VLAN -Basic test of VLAN functionality in a bridge, tagged/untagged traffic and a VLAN interface in the bridge. -.... - ¦ ¦ - ¦ vlan10 IP:10.0.0.2 ¦ br0 IP:10.0.0.3 - ¦ / ¦ / - ¦ br0 <-- VLAN filtering ¦ link.10 - ¦ u/ \\t ¦ / - PC ------data link -----------------|-- link - ¦ dut1 ¦ dut2 -.... +Verify VLAN filtering bridge, with a VLAN trunk to a neighboring device, +which in turn untags one VLAN outisde a non-VLAN filtering bridge. + +.Logical network setup +image::bridge-vlan.svg[] """ import infamy with infamy.Test() as test: with test.step("Set up topology and attach to target DUT"): - env = infamy.Env() + env = infamy.Env() dut1 = env.attach("dut1", "mgmt") dut2 = env.attach("dut2", "mgmt") - _, dut1_e0 = env.ltop.xlate("dut1", "data") - _, dut1_e1 = env.ltop.xlate("dut1", "link") - _, dut2_e0 = env.ltop.xlate("dut2", "link") - with test.step("Configure DUTs"): dut1.put_config_dict("ietf-interfaces", { "interfaces": { @@ -34,24 +24,20 @@ with infamy.Test() as test: { "name": "br0", "type": "infix-if-type:bridge", - "enabled": True, "bridge": { "vlans": { - "pvid": 4094, "vlan": [ { "vid": 10, - "untagged": [ dut1_e0 ], - "tagged": [ "br0", dut1_e1 ] + "untagged": [dut1["data"]], + "tagged": [dut1["link"], "br0"] } ] } } - }, - { + }, { "name": "vlan10", "type": "infix-if-type:vlan", - "enabled": True, "vlan": { "lower-layer-if": "br0", "id": 10, @@ -64,18 +50,14 @@ with infamy.Test() as test: } ] } - }, - { - "name": dut1_e0, - "enabled": True, + }, { + "name": dut1["data"], "infix-interfaces:bridge-port": { "pvid": 10, "bridge": "br0" } - }, - { - "name": dut1_e1, - "enabled": True, + }, { + "name": dut1["link"], "infix-interfaces:bridge-port": { "pvid": 10, "bridge": "br0" @@ -91,7 +73,6 @@ with infamy.Test() as test: { "name": "br0", "type": "infix-if-type:bridge", - "enabled": True, "ipv4": { "address": [ { @@ -100,17 +81,13 @@ with infamy.Test() as test: } ] } - }, - { - "name": dut2_e0, - "enabled": True - }, - { + }, { + "name": dut2["link"], + }, { "name": "e0.10", "type": "infix-if-type:vlan", - "enabled": True, "vlan": { - "lower-layer-if": dut2_e0, + "lower-layer-if": dut2["link"], "id": 10, }, "infix-interfaces:bridge-port": { From 4260d2414341a04258b40287dd183015cc406cef Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 16 Dec 2024 01:32:31 +0100 Subject: [PATCH 20/27] test: new test, verify basic lag setup Verify connectivity from host to the second DUT via the first, over a link aggregate. The lag starts in static mode and then changes to an LACP aggregate. This verifies not just basic aggregate functionality, but also changing mode, which is quite tricky to get right. Signed-off-by: Joachim Wiberg --- test/case/ietf_interfaces/Readme.adoc | 2 + .../case/ietf_interfaces/ietf_interfaces.yaml | 3 + .../ietf_interfaces/lag_basic/Readme.adoc | 1 + .../ietf_interfaces/lag_basic/lag-basic.svg | 4 + .../ietf_interfaces/lag_basic/lag_basic.adoc | 43 +++++ test/case/ietf_interfaces/lag_basic/test.py | 165 ++++++++++++++++++ .../ietf_interfaces/lag_basic/topology.dot | 33 ++++ .../ietf_interfaces/lag_basic/topology.svg | 76 ++++++++ 8 files changed, 327 insertions(+) create mode 120000 test/case/ietf_interfaces/lag_basic/Readme.adoc create mode 100644 test/case/ietf_interfaces/lag_basic/lag-basic.svg create mode 100644 test/case/ietf_interfaces/lag_basic/lag_basic.adoc create mode 100755 test/case/ietf_interfaces/lag_basic/test.py create mode 100644 test/case/ietf_interfaces/lag_basic/topology.dot create mode 100644 test/case/ietf_interfaces/lag_basic/topology.svg diff --git a/test/case/ietf_interfaces/Readme.adoc b/test/case/ietf_interfaces/Readme.adoc index ff5f893e..42f6061e 100644 --- a/test/case/ietf_interfaces/Readme.adoc +++ b/test/case/ietf_interfaces/Readme.adoc @@ -33,6 +33,8 @@ include::bridge_vlan_separation/Readme.adoc[] include::dual_bridge/Readme.adoc[] +include::lag_basic/Readme.adoc[] + include::igmp_basic/Readme.adoc[] include::igmp_vlan/Readme.adoc[] diff --git a/test/case/ietf_interfaces/ietf_interfaces.yaml b/test/case/ietf_interfaces/ietf_interfaces.yaml index 2f2bbe8e..4709465b 100644 --- a/test/case/ietf_interfaces/ietf_interfaces.yaml +++ b/test/case/ietf_interfaces/ietf_interfaces.yaml @@ -35,6 +35,9 @@ - name: ipv4_autoconf case: ipv4_autoconf/test.py +- name: lag_basic + case: lag_basic/test.py + - name: bridge_fwd_sgl_dut case: bridge_fwd_sgl_dut/test.py diff --git a/test/case/ietf_interfaces/lag_basic/Readme.adoc b/test/case/ietf_interfaces/lag_basic/Readme.adoc new file mode 120000 index 00000000..ae7efc4b --- /dev/null +++ b/test/case/ietf_interfaces/lag_basic/Readme.adoc @@ -0,0 +1 @@ +lag_basic.adoc \ No newline at end of file diff --git a/test/case/ietf_interfaces/lag_basic/lag-basic.svg b/test/case/ietf_interfaces/lag_basic/lag-basic.svg new file mode 100644 index 00000000..b25aa63b --- /dev/null +++ b/test/case/ietf_interfaces/lag_basic/lag-basic.svg @@ -0,0 +1,4 @@ + + + +
eth
eth
eth
bridge
lag
dut1
eth
eth
lag
ip
pc
eth
ip
dut2
\ No newline at end of file diff --git a/test/case/ietf_interfaces/lag_basic/lag_basic.adoc b/test/case/ietf_interfaces/lag_basic/lag_basic.adoc new file mode 100644 index 00000000..32fec881 --- /dev/null +++ b/test/case/ietf_interfaces/lag_basic/lag_basic.adoc @@ -0,0 +1,43 @@ +=== Ling Aggregation Basic +==== Description +Verify communication over a link aggregate in static and LACP operating +modes during basic failure scenarios. + +.Internal network setup, PC verifies connectivity with dut2 via dut1 +ifdef::topdoc[] +image::../../test/case/ietf_interfaces/lag_basic/lag-basic.svg[Internal networks] +endif::topdoc[] +ifndef::topdoc[] +ifdef::testgroup[] +image::lag_basic/lag-basic.svg[Internal networks] +endif::testgroup[] +ifndef::testgroup[] +image::lag-basic.svg[Internal networks] +endif::testgroup[] +endif::topdoc[] + +The host verifies connectivity with dut2 via dut1 over the aggregate for +each test step using the `mon` interface. + +==== Topology +ifdef::topdoc[] +image::{topdoc}../../test/case/ietf_interfaces/lag_basic/topology.svg[Ling Aggregation Basic topology] +endif::topdoc[] +ifndef::topdoc[] +ifdef::testgroup[] +image::lag_basic/topology.svg[Ling Aggregation Basic topology] +endif::testgroup[] +ifndef::testgroup[] +image::topology.svg[Ling Aggregation Basic topology] +endif::testgroup[] +endif::topdoc[] +==== Test sequence +. Set up topology and attach to target DUTs +. Set up LACP link aggregate, lag0, on dut1 and dut2 +. Verify failure modes for lacp mode +. Set up static link aggregate, lag0, on dut1 and dut2 +. Verify failure modes for static mode + + +<<< + diff --git a/test/case/ietf_interfaces/lag_basic/test.py b/test/case/ietf_interfaces/lag_basic/test.py new file mode 100755 index 00000000..9cbd0d25 --- /dev/null +++ b/test/case/ietf_interfaces/lag_basic/test.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +r"""Ling Aggregation Basic + +Verify communication over a link aggregate in static and LACP operating +modes during basic failure scenarios. + +.Internal network setup, PC verifies connectivity with dut2 via dut1 +image::lag-basic.svg[Internal networks] + +The host verifies connectivity with dut2 via dut1 over the aggregate for +each test step using the `mon` interface. + +""" +from time import sleep, time +from datetime import datetime +import infamy +from infamy.util import parallel, until + + +class DumbLinkBreaker: + """Encapsulates basic, dumb link-breaking ops over SSH.""" + + def __init__(self, sys, dut, netns): + self.env = sys + self.dut = dut + self.net = netns + self.tgt = {} + for i, (name, _) in dut.items(): + self.tgt[i] = env.attach(name, "mgmt", "ssh") + + def set_link(self, link, updown): + """Set link up or down, verify before returning.""" + def set_and_verify(i): + name, dut = self.dut[i] + + cmd = self.tgt[i].runsh(f"sudo ip link set {dut[link]} {updown}") + if cmd.returncode: + for out in [cmd.stdout, cmd.stderr]: + if out: + print(f"{name}: {out.rstrip()}") + raise RuntimeError(f"{name}: failed setting {link} {updown}") + + for _ in range(10): + sleep(0.1) + check = self.tgt[i].runsh(f"ip link show {dut[link]}") + if f"state {updown.upper()}" in check.stdout: + break + else: + raise RuntimeError(f"{name}: {dut[link]} did not go {updown}") + + parallel(*[lambda i=i: set_and_verify(i) for i in self.dut]) + + def fail_check(self, peer): + """Verify connectivity with peer during link failure.""" + sequence = [ + [("link1", "up"), ("link2", "up")], + [("link1", "down"), ("link2", "up")], + [("link1", "up"), ("link2", "down")], + [("link1", "up"), ("link2", "up")] + ] + + total_start = time() + for state in sequence: + state_start = time() + print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} {state}") + + for link, updown in state: + self.set_link(link, updown) + self.net.must_reach(peer, timeout=10) + + print(f"Completed in {time() - state_start:.2f}s") + + print(f"Total time: {time() - total_start:.2f}s") + + +def lag_init(mode): + """Set up mode specific attributes for the LAG""" + if mode == "lacp": + lag = [{ + "name": "lag0", + "lag": {"lacp": {"rate": "fast"}} + }] + else: + lag = [] + return lag + + +def net_init(host, addr): + """Set up DUT network, dut1 bridges host port with lag0""" + if host: + net = [{ + "name": "br0", + "type": "infix-if-type:bridge", + }, { + "name": host, + "bridge-port": {"bridge": "br0"} + }, { + "name": "lag0", + "bridge-port": {"bridge": "br0"} + }] + else: + net = [{ + "name": "lag0", + "ipv4": { + "address": [{"ip": addr, "prefix-length": 24}] + } + }] + return net + + +def dut_init(dut, mode, addr): + """Set up link aggregate on dut""" + net = net_init(dut["mon"], addr) + lag = lag_init(mode) + + dut.put_config_dict("ietf-interfaces", { + "interfaces": { + "interface": [{ + "name": "lag0", + "type": "infix-if-type:lag", + "lag": { + "mode": mode, + "link-monitor": {"interval": 100} + } + }, { + "name": dut["link1"], + "lag-port": {"lag": "lag0"} + }, { + "name": dut["link2"], + "lag-port": {"lag": "lag0"} + }] + net + lag + } + }) + + +with infamy.Test() as test: + with test.step("Set up topology and attach to target DUTs"): + env = infamy.Env() + dut1 = env.attach("dut1", "mgmt") + dut2 = env.attach("dut2", "mgmt") + + _, mon = env.ltop.xlate("host", "mon") + with infamy.IsolatedMacVlan(mon) as ns: + dm = { + '1': ("dut1", dut1), + '2': ("dut2", dut2) + } + lb = DumbLinkBreaker(env, dm, ns) + ns.addip("192.168.2.1") + + with test.step("Set up LACP link aggregate, lag0, on dut1 and dut2"): + parallel(lambda: dut_init(dut1, "lacp", None), + lambda: dut_init(dut2, "lacp", "192.168.2.42")) + + with test.step("Verify failure modes for lacp mode"): + lb.fail_check("192.168.2.42") + + with test.step("Set up static link aggregate, lag0, on dut1 and dut2"): + parallel(lambda: dut_init(dut1, "static", None), + lambda: dut_init(dut2, "static", "192.168.2.42")) + + with test.step("Verify failure modes for static mode"): + lb.fail_check("192.168.2.42") + + test.succeed() diff --git a/test/case/ietf_interfaces/lag_basic/topology.dot b/test/case/ietf_interfaces/lag_basic/topology.dot new file mode 100644 index 00000000..9715914e --- /dev/null +++ b/test/case/ietf_interfaces/lag_basic/topology.dot @@ -0,0 +1,33 @@ +graph "lag" { + layout="neato"; + overlap="false"; + esep="+23"; + + node [shape=record, fontsize=12, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="host | { mgmt1 | mon | \n\n\n\n | mgmt2 }", + pos="0,15!", + requires="controller", + ]; + + dut1 [ + label="{ mgmt | mon } | { dut1\r | { link1 | link2 } }", + pos="2,15.25!", + requires="infix", + ]; + + dut2 [ + label=" mgmt | { { link1 | link2 } | dut2\r }", + pos="2,14.75!", + requires="infix", + ]; + + host:mgmt1 -- dut1:mgmt [requires="mgmt", color=lightgray] + host:mon -- dut1:mon // Monitor connection to dut2 via dut1 + host:mgmt2 -- dut2:mgmt [requires="mgmt", color=lightgrey] + + dut1:link1 -- dut2:link1 [color=black, fontcolor=black, penwidth=3] + dut1:link2 -- dut2:link2 [color=black, fontcolor=black, penwidth=3] +} diff --git a/test/case/ietf_interfaces/lag_basic/topology.svg b/test/case/ietf_interfaces/lag_basic/topology.svg new file mode 100644 index 00000000..d346d430 --- /dev/null +++ b/test/case/ietf_interfaces/lag_basic/topology.svg @@ -0,0 +1,76 @@ + + + + + + +lag + + + +host + +host + +mgmt1 + +mon + + +mgmt2 + + + +dut1 + +mgmt + +mon + +dut1 + +link1 + +link2 + + + +host:mgmt1--dut1:mgmt + + + + +host:mon--dut1:mon + + + + +dut2 + +mgmt + +link1 + +link2 + +dut2 + + + +host:mgmt2--dut2:mgmt + + + + +dut1:link1--dut2:link1 + + + + +dut1:link2--dut2:link2 + + + + From cf16efe499db0e7fc676902b647a5e7366624174 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 19 Dec 2024 18:21:39 +0100 Subject: [PATCH 21/27] test: new test, verify degraded link aggregate members in LACP mode Verify connectivity from host to the second DUT via the first, over a link aggregate. Both aggregate member links are connected to a TPMR link breaker to hide link down from the DUTs. Signed-off-by: Joachim Wiberg --- test/case/ietf_interfaces/Readme.adoc | 2 + .../case/ietf_interfaces/ietf_interfaces.yaml | 3 + .../ietf_interfaces/lag_failure/Readme.adoc | 1 + .../lag_failure/lag-failure.svg | 4 + .../lag_failure/lag_failure.adoc | 42 +++++ test/case/ietf_interfaces/lag_failure/test.py | 143 ++++++++++++++++++ .../ietf_interfaces/lag_failure/topology.dot | 36 +++++ .../ietf_interfaces/lag_failure/topology.svg | 93 ++++++++++++ 8 files changed, 324 insertions(+) create mode 120000 test/case/ietf_interfaces/lag_failure/Readme.adoc create mode 100644 test/case/ietf_interfaces/lag_failure/lag-failure.svg create mode 100644 test/case/ietf_interfaces/lag_failure/lag_failure.adoc create mode 100755 test/case/ietf_interfaces/lag_failure/test.py create mode 100644 test/case/ietf_interfaces/lag_failure/topology.dot create mode 100644 test/case/ietf_interfaces/lag_failure/topology.svg diff --git a/test/case/ietf_interfaces/Readme.adoc b/test/case/ietf_interfaces/Readme.adoc index 42f6061e..de861eb0 100644 --- a/test/case/ietf_interfaces/Readme.adoc +++ b/test/case/ietf_interfaces/Readme.adoc @@ -35,6 +35,8 @@ include::dual_bridge/Readme.adoc[] include::lag_basic/Readme.adoc[] +include::lag_failure/Readme.adoc[] + include::igmp_basic/Readme.adoc[] include::igmp_vlan/Readme.adoc[] diff --git a/test/case/ietf_interfaces/ietf_interfaces.yaml b/test/case/ietf_interfaces/ietf_interfaces.yaml index 4709465b..88841fd3 100644 --- a/test/case/ietf_interfaces/ietf_interfaces.yaml +++ b/test/case/ietf_interfaces/ietf_interfaces.yaml @@ -38,6 +38,9 @@ - name: lag_basic case: lag_basic/test.py +- name: lag_failure + case: lag_failure/test.py + - name: bridge_fwd_sgl_dut case: bridge_fwd_sgl_dut/test.py diff --git a/test/case/ietf_interfaces/lag_failure/Readme.adoc b/test/case/ietf_interfaces/lag_failure/Readme.adoc new file mode 120000 index 00000000..ede0d845 --- /dev/null +++ b/test/case/ietf_interfaces/lag_failure/Readme.adoc @@ -0,0 +1 @@ +lag_failure.adoc \ No newline at end of file diff --git a/test/case/ietf_interfaces/lag_failure/lag-failure.svg b/test/case/ietf_interfaces/lag_failure/lag-failure.svg new file mode 100644 index 00000000..15963fed --- /dev/null +++ b/test/case/ietf_interfaces/lag_failure/lag-failure.svg @@ -0,0 +1,4 @@ + + + +
lag
dut1
eth
eth
eth
bridge
eth
eth
lag
ip
pc
eth
ip
dut2
lb1
lb2
\ No newline at end of file diff --git a/test/case/ietf_interfaces/lag_failure/lag_failure.adoc b/test/case/ietf_interfaces/lag_failure/lag_failure.adoc new file mode 100644 index 00000000..92fa04f6 --- /dev/null +++ b/test/case/ietf_interfaces/lag_failure/lag_failure.adoc @@ -0,0 +1,42 @@ +=== LACP Aggregate w/ Degraded Link +==== Description +Verify communication over an LACP link aggregate when individual member +links stop forwarding traffic, without carrier loss. + +.Logical network setup, link breakers (lb1 & lb2) here managed by host PC +ifdef::topdoc[] +image::../../test/case/ietf_interfaces/lag_failure/lag-failure.svg[] +endif::topdoc[] +ifndef::topdoc[] +ifdef::testgroup[] +image::lag_failure/lag-failure.svg[] +endif::testgroup[] +ifndef::testgroup[] +image::lag-failure.svg[] +endif::testgroup[] +endif::topdoc[] + +The host verifies connectivity with dut2 via dut1 over the aggregate for +each failure mode step using the `mon` interface. + +==== Topology +ifdef::topdoc[] +image::{topdoc}../../test/case/ietf_interfaces/lag_failure/topology.svg[LACP Aggregate w/ Degraded Link topology] +endif::topdoc[] +ifndef::topdoc[] +ifdef::testgroup[] +image::lag_failure/topology.svg[LACP Aggregate w/ Degraded Link topology] +endif::testgroup[] +ifndef::testgroup[] +image::topology.svg[LACP Aggregate w/ Degraded Link topology] +endif::testgroup[] +endif::topdoc[] +==== Test sequence +. Set up topology and attach to target DUTs +. Set up link aggregate, lag0, between dut1 and dut2 +. Initial connectivity check ... +. Verify failure modes + + +<<< + diff --git a/test/case/ietf_interfaces/lag_failure/test.py b/test/case/ietf_interfaces/lag_failure/test.py new file mode 100755 index 00000000..315c2236 --- /dev/null +++ b/test/case/ietf_interfaces/lag_failure/test.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +r"""LACP Aggregate w/ Degraded Link + +Verify communication over an LACP link aggregate when individual member +links stop forwarding traffic, without carrier loss. + +.Logical network setup, link breakers (lb1 & lb2) here managed by host PC +image::lag-failure.svg[] + +The host verifies connectivity with dut2 via dut1 over the aggregate for +each failure mode step using the `mon` interface. + +""" +from time import time +import infamy +from infamy.netns import TPMR +from infamy.util import parallel + +IPH = "192.168.2.1" +IP1 = "192.168.2.41" +IP2 = "192.168.2.42" + + +class LinkBreaker: + """Encapsulates TPMR based link-breakers.""" + + def __init__(self, sys, netns): + self.net = netns + self.lb1 = TPMR(sys.ltop.xlate("host", "lb1a")[1], + sys.ltop.xlate("host", "lb1b")[1]).start() + self.lb2 = TPMR(sys.ltop.xlate("host", "lb2a")[1], + sys.ltop.xlate("host", "lb2b")[1]).start() + + def forward(self, lb1, lb2): + """Set link breakers in forwarding or blocking state.""" + getattr(self.lb1, lb1)() + getattr(self.lb2, lb2)() + + def fail_check(self, peer): + """Verify connectivity with a given peer during failure.""" + sequence = [ + ("forward", "forward"), + ("forward", "block"), + ("block", "forward"), + ("forward", "forward") + ] + + total_start = time() + print(f"{'LB1':<8} | {'LB2':<8} | {'Status':<8}") + print("---------|----------|---------") + + for lb1, lb2 in sequence: + state_start = time() + try: + print(f"{lb1:<8} | {lb2:<8} | {'...':<8}", end="\r# ") + self.forward(lb1, lb2) + self.net.must_reach(peer, timeout=30) + print(f"{lb1:<8} | {lb2:<8} | {'OK':<8} in " + f"{time() - state_start:.2f}s") + except Exception as e: + print(f"{lb1:<8} | {lb2:<8} | {'FAIL':<8} after " + f"{time() - state_start:.2f}s") + print(f"\nError encountered: {e}") + print(f"Link breakers were in state: LB1='{lb1}', LB2='{lb2}'") + raise + + print(f"Total time: {time() - total_start:.2f}s") + + +def net_init(host, addr): + """Set up DUT network, dut1 bridges host port with lag0""" + if host: + net = [{ + "name": "br0", + "type": "infix-if-type:bridge", + "ipv4": { + "address": [{"ip": addr, "prefix-length": 24}] + } + }, { + "name": host, + "bridge-port": {"bridge": "br0"} + }, { + "name": "lag0", + "bridge-port": {"bridge": "br0"} + }] + else: + net = [{ + "name": "lag0", + "ipv4": { + "address": [{"ip": addr, "prefix-length": 24}] + } + }] + return net + + +def dut_init(dut, addr, peer): + """Configure each DUT specific according to LAG mode and peer""" + net = net_init(dut["mon"], addr) + + dut.put_config_dict("ietf-interfaces", { + "interfaces": { + "interface": [{ + "name": "lag0", + "type": "infix-if-type:lag", + "lag": { + "mode": "lacp", + "lacp": {"rate": "fast"}, + "link-monitor": {"interval": 100} + } + }, { + "name": dut["link1"], + "lag-port": {"lag": "lag0"} + }, { + "name": dut["link2"], + "lag-port": {"lag": "lag0"} + }] + net + } + }) + + +with infamy.Test() as test: + with test.step("Set up topology and attach to target DUTs"): + env = infamy.Env() + dut1 = env.attach("dut1") + dut2 = env.attach("dut2") + + _, mon = env.ltop.xlate("host", "mon") + with infamy.IsolatedMacVlan(mon) as ns: + lb = LinkBreaker(env, ns) + ns.addip(IPH) + + print(f"Setting up lag0 in LACP mode between {dut1} and {dut2}") + with test.step("Set up link aggregate, lag0, between dut1 and dut2"): + parallel(lambda: dut_init(dut1, IP1, IP2), + lambda: dut_init(dut2, IP2, IP1)) + + with test.step("Initial connectivity check ..."): + ns.must_reach(IP2, timeout=30) + + with test.step("Verify failure modes"): + lb.fail_check(IP2) + + test.succeed() diff --git a/test/case/ietf_interfaces/lag_failure/topology.dot b/test/case/ietf_interfaces/lag_failure/topology.dot new file mode 100644 index 00000000..8b24b884 --- /dev/null +++ b/test/case/ietf_interfaces/lag_failure/topology.dot @@ -0,0 +1,36 @@ +graph "lag" { + layout="neato"; + overlap="false"; + esep="+23"; + + node [shape=record, fontsize=12, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="{{ mgmt1 | mon | lb1a | lb2a | lb2b | lb1b | mgmt2 } | host}", + pos="9,0!", + requires="controller", + ]; + + dut1 [ + label="{ dut1\l | { mgmt | mon | link1 | link2 } }", + pos="0,6!", + requires="infix", + ]; + + dut2 [ + label="{ dut2\r | { link2 | link1 | mgmt } }", + pos="18,6!", + requires="infix", + ]; + + host:mgmt1 -- dut1:mgmt [requires="mgmt", color=lightgray] + host:mon -- dut1:mon // Monitor connection to dut2 via dut1 + host:mgmt2 -- dut2:mgmt [requires="mgmt", color=lightgrey] + + dut1:link1 -- host:lb1a [requires="ieee-mc", color=black, fontcolor=black] + host:lb1b -- dut2:link1 [requires="ieee-mc", color=black, fontcolor=black] + + dut1:link2 -- host:lb2a [requires="ieee-mc", color=black, fontcolor=black] + host:lb2b -- dut2:link2 [requires="ieee-mc", color=black, fontcolor=black] +} diff --git a/test/case/ietf_interfaces/lag_failure/topology.svg b/test/case/ietf_interfaces/lag_failure/topology.svg new file mode 100644 index 00000000..44888247 --- /dev/null +++ b/test/case/ietf_interfaces/lag_failure/topology.svg @@ -0,0 +1,93 @@ + + + + + + +lag + + + +host + +mgmt1 + +mon + +lb1a + +lb2a + +lb2b + +lb1b + +mgmt2 + +host + + + +dut1 + +dut1 + +mgmt + +mon + +link1 + +link2 + + + +host:mgmt1--dut1:mgmt + + + + +host:mon--dut1:mon + + + + +dut2 + +dut2 + +link2 + +link1 + +mgmt + + + +host:mgmt2--dut2:mgmt + + + + +host:lb1b--dut2:link1 + + + + +host:lb2b--dut2:link2 + + + + +dut1:link1--host:lb1a + + + + +dut1:link2--host:lb2a + + + + From f62900699a5851ca26d03fc1485032ff6e6fbfd6 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Fri, 10 Jan 2025 12:39:21 +0100 Subject: [PATCH 22/27] doc: add logo and update ingress Signed-off-by: Joachim Wiberg --- doc/README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/doc/README.md b/doc/README.md index 51323de5..9f0a65b5 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,13 +1,16 @@ +Infix - Linux <3 NETCONF Welcome to Infix, your friendly Network Operating System! On these pages you can find both user and developer documentation. -> Topics on configuring the system include CLI examples, every setting -> is also possible to perform using NETCONF. In fact, the Infix test -> system solely relies on NETCONF for configuring network topologies. +Most topics on configuring the system include CLI examples, but every +setting, as well as status read-back from the operational datastore, is +also possible to perform using NETCONF or RESTCONF. In fact, the Infix +regression test system solely relies on NETCONF and RESTCONF. -The CLI documentation is also available from inside the CLI itself using -the `help` command. +> [!TIP] +> The CLI documentation is also available from inside the CLI itself +> using the `help` command in admin-exec mode. - **CLI Topics** - [Introduction to the CLI](cli/introduction.md) @@ -18,6 +21,7 @@ the `help` command. - [Introduction](introduction.md) - [System Configuration](system.md) - [Network Configuration](networking.md) + - [DHCP Server](dhcp.md) - [Syslog Support](syslog.md) - **Infix In-Depth** - [Boot Procedure](boot.md) From f1271f28b92813b752a082e277520a1cc5a58d3e Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sat, 11 Jan 2025 11:32:45 +0100 Subject: [PATCH 23/27] doc: new section, Link Aggregation (updated images) Signed-off-by: Joachim Wiberg --- doc/img/dataplane.svg | 2 +- doc/img/lego.svg | 2 +- doc/networking.md | 263 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 263 insertions(+), 4 deletions(-) diff --git a/doc/img/dataplane.svg b/doc/img/dataplane.svg index 4974992d..07dd3422 100644 --- a/doc/img/dataplane.svg +++ b/doc/img/dataplane.svg @@ -1,4 +1,4 @@ -
Docker
Container
eth
vlan
ip
veth
veth
bridge
lag
eth
eth
eth
vlan
ip
ip
ip
\ No newline at end of file +
Docker
Container
eth
vlan
ip
veth
veth
bridge
eth
eth
eth
vlan
ip
ip
ip
lo
ip
lag
\ No newline at end of file diff --git a/doc/img/lego.svg b/doc/img/lego.svg index e63a1bc4..1cbc3717 100644 --- a/doc/img/lego.svg +++ b/doc/img/lego.svg @@ -1,4 +1,4 @@ -
lag
eth
veth
veth
bridge
ip
vlan
lo
\ No newline at end of file +
eth
veth
veth
bridge
ip
vlan
lo
lag
\ No newline at end of file diff --git a/doc/networking.md b/doc/networking.md index fcbc70a7..23bd279a 100644 --- a/doc/networking.md +++ b/doc/networking.md @@ -48,7 +48,7 @@ other traffic would be bridged as usual. | bridge | infix-if-bridge | SW implementation of an IEEE 802.1Q bridge | | ip | ietf-ip, infix-ip | IP address to the subordinate interface | | vlan | infix-if-vlan | Capture all traffic belonging to a specific 802.1Q VID | -| lag[^1] | infix-if-lag | Bond multiple interfaces into one, creating a link aggregate | +| lag | infix-if-lag | Link aggregation, static and IEEE 802.3ad (LACP) | | lo | ietf-interfaces | Software loopback interface | | eth | ieee802-ethernet-interface | Physical Ethernet device/port. | | | infix-ethernet-interface | | @@ -430,6 +430,265 @@ admin@example:/config/interface/br0/bridge/> set ieee-group-forward lldp admin@example:/config/interface/br0/bridge/> ``` + +### Link Aggregation + +A link aggregate, or *lag*, allows multiple physical interfaces to be +combined into a single logical interface, providing increased bandwidth +(in some cases) and redundancy (primarily). Two modes of qualifying lag +member ports are available: + + 1. **static**: Active members selected based on link status (carrier) + 2. **lacp:** IEEE 802.3ad Link Aggregation Control Protocol + +In LACP mode, LACPDUs are exchanged by the link partners to qualify each +lag member, while in static mode only carrier is used. This additional +exchange in LACP ensures traffic can be forwarded in both directions. + +Traffic distribution, for both modes, across the active lag member ports +is determined by the hash policy[^1]. It uses an XOR of the source, +destination MAC addresses and the EtherType field. This, IEEE +802.3ad-compliant, algorithm will place all traffic to a particular +network peer on the same link. Meaning there is no increased bandwidth +for communication between two specific devices. + +> [!TIP] +> Similar to other interface types, naming your interface `lagN`, where +> `N` is a number, allows the CLI to automatically infer the interface +> type as LAG. + + +#### Basic Configuration + +Creating a link aggregate interface and adding member ports: + +``` +admin@example:/> configure +admin@example:/config/> edit interface lag0 +admin@example:/config/interface/lag0/> set lag mode static +admin@example:/config/interface/lag0/> end +admin@example:/config/> set interface eth7 lag-port lag lag0 +admin@example:/config/> set interface eth8 lag-port lag lag0 +admin@example:/config/> leave +``` + +A static lag responds only to link (carrier) changes of member ports. +E.g., in this example egressing traffic is continuously distributed over +the two links until link down on one link is detected, triggering all +traffic to be steered to the sole remaining link. + + +#### LACP Configuration + +LACP mode provides dynamic negotiation of the link aggregate. Key +settings include: + +``` +admin@example:/> configure +admin@example:/config/> edit interface lag0 +admin@example:/config/interface/lag0/> set lag mode lacp +admin@example:/config/interface/lag0/> set lag lacp mode passive +admin@example:/config/interface/lag0/> set lag lacp rate fast +admin@example:/config/interface/lag0/> set lag lacp system-priority 100 +``` + +LACP mode supports two operational modes: + + - **active:** Initiates negotiation by sending LACPDUs (default) + - **passive:** Waits for peer to initiate negotiation + +> [!NOTE] +> At least one end of the link must be in active mode for negotiation to occur. + +The LACP rate setting controls protocol timing: + + - **slow:** LACPDUs sent every 30 seconds, with 90 second timeout (default) + - **fast:** LACPDUs sent every second, with 3 second timeout + + +#### Link Flapping + +To protect against link flapping, debounce timers can be configured to +delay link qualification. Usually only the `up` delay is needed: + +``` +admin@example:/config/interface/lag0/lag/link-monitor/> edit debounce +admin@example:/config/interface/lag0/lag/link-monitor/debounce/> set up 500 +admin@example:/config/interface/lag0/lag/link-monitor/debounce/> set down 200 +``` + +#### Operational Status, Overview + +Like other interfaces, link aggregates are also available in the general +interfaces overview in the CLI admin-exec context. Here is the above +static mode aggregate: + +``` +admin@example:/> show interfaces +INTERFACE PROTOCOL STATE DATA +lo ethernet UP 00:00:00:00:00:00 + ipv4 127.0.0.1/8 (static) + ipv6 ::1/128 (static) +. +. +. +lag0 lag UP static: balance-xor, hash: layer2 +│ ethernet UP 00:a0:85:00:02:00 +├ eth7 lag ACTIVE +└ eth8 lag ACTIVE +``` + +Same aggregate, but in LACP mode: + +``` +admin@example:/> show interfaces +INTERFACE PROTOCOL STATE DATA +lo ethernet UP 00:00:00:00:00:00 + ipv4 127.0.0.1/8 (static) + ipv6 ::1/128 (static) +. +. +. +lag0 lag UP lacp: active, rate: fast (1s), hash: layer2 +│ ethernet UP 00:a0:85:00:02:00 +├ eth7 lag ACTIVE active, short_timeout, aggregating, in_sync, collecting, distributing +└ eth8 lag ACTIVE active, short_timeout, aggregating, in_sync, collecting, distributing +``` + + +#### Operational Status, Detail + +In addition to basic status shown in the interface overview, detailed +LAG status can be inspected: + +``` +admin@example:/> show interfaces name lag0 +name : lag0 +index : 25 +mtu : 1500 +operational status : up +physical address : 00:a0:85:00:02:00 +lag mode : static +lag type : balance-xor +lag hash : layer2 +link debounce up : 0 msec +link debounce down : 0 msec +ipv4 addresses : +ipv6 addresses : +in-octets : 0 +out-octets : 2142 +``` + +Same aggregate, but in LACP mode: + +``` +admin@example:/> show interfaces name lag0 +name : lag0 +index : 24 +mtu : 1500 +operational status : up +physical address : 00:a0:85:00:02:00 +lag mode : lacp +lag hash : layer2 +lacp mode : active +lacp rate : fast (1s) +lacp aggregate id : 1 +lacp system priority: 65535 +lacp actor key : 9 +lacp partner key : 9 +lacp partner mac : 00:a0:85:00:03:00 +link debounce up : 0 msec +link debounce down : 0 msec +ipv4 addresses : +ipv6 addresses : +in-octets : 100892 +out-octets : 111776 +``` + +Member ports provide additional status information: + + - Link failure counter: number of detected link failures + - LACP state flags: various states of LACP negotiation: + - `active`: port is actively sending LACPDUs + - `short_timeout`: using fast rate (1s) vs. slow rate (30s) + - `aggregating`: port is allowed to aggregate in this LAG + - `in_sync`: port is synchronized with partner + - `collecting`: port is allowed to receive traffic + - `distributing`: port is allowed to send traffic + - `defaulted`: using default partner info (partner not responding) + - `expired`: partner info has expired (no LACPDUs received) + - Aggregator ID: unique identifier for this LAG group + - Actor state: LACP state flags for this port (local) + - Partner state: LACP state flags from the remote port + +Example member port status: + +``` +admin@example:/> show interfaces name eth7 +name : eth7 +index : 8 +mtu : 1500 +operational status : up +physical address : 00:a0:85:00:02:00 +lag member : lag0 +lag member state : active +lacp aggregate id : 1 +lacp actor state : active, short_timeout, aggregating, in_sync, collecting, distributing +lacp partner state : active, short_timeout, aggregating, in_sync, collecting, distributing +link failure count : 0 +ipv4 addresses : +ipv6 addresses : +in-octets : 473244 +out-octets : 499037 +``` + + +#### Example: Switch Uplink with LACP + +LACP mode provides the most robust operation, automatically negotiating +the link aggregate and detecting configuration mismatches. + +A common use case is connecting a switch to an upstream device: + +``` +admin@example:/> configure +admin@example:/config/> edit interface lag0 +admin@example:/config/interface/lag0/> set lag mode lacp +``` + +Enable fast LACP for quicker fail-over: + +``` +admin@example:/config/interface/lag0/> set lag lacp rate fast +``` + +Add uplink ports + +``` +admin@example:/config/interface/lag0/> end +admin@example:/config/> set interface eth7 lag-port lag lag0 +admin@example:/config/> set interface eth8 lag-port lag lag0 +``` + +Enable protection against "link flapping". + +``` +admin@example:/config/interface/lag0/> edit lag link-monitor +admin@example:/config/interface/lag0/lag/link-monitor/> edit debounce +admin@example:/config/interface/lag0/lag/link-monitor/debounce/> set up 500 +admin@example:/config/interface/lag0/lag/link-monitor/debounce/> set down 200 +admin@example:/config/interface/lag0/lag/link-monitor/debounce/> top +``` + +Add to bridge for switching + +``` +admin@example:/config/interface/lag0/lag/link-monitor/debounce/> end +admin@example:/config/> set interface lag0 bridge-port bridge br0 +admin@example:/config/> leave +``` + + ### VLAN Interfaces Creating a VLAN can be done in many ways. This section assumes VLAN @@ -1228,7 +1487,7 @@ currently supported, namely `ipv4` and `ipv6`. [4]: https://www.rfc-editor.org/rfc/rfc3442 [0]: https://frrouting.org/ -[^1]: Please note, link aggregates are not yet supported. +[^1]: `(source MAC XOR destination MAC XOR EtherType) MODULO num_links` [^2]: Link-local IPv6 addresses are implicitly enabled when enabling IPv6. IPv6 can be enabled/disabled per interface in the [ietf-ip][2] YANG model. From a6b79857db8472815c6e6228cfab4565d3cd954a Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 12 Jan 2025 11:00:10 +0100 Subject: [PATCH 24/27] doc: update ChangeLog, static+lacp link aggregation Signed-off-by: Joachim Wiberg --- doc/ChangeLog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index e277910a..5f0a2ed6 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -11,8 +11,10 @@ All notable changes to the project are documented in this file. - Upgrade Linux kernel to 6.12.13 (LTS) - YANG type for SSH private/public keys has changed, from ietf-crypto-types to infix-crypto-types + - Add support for link aggregation (lag), static (balance-xor) and LACP ### Fixes + - N/A [v25.01.0][] - 2025-01-31 @@ -1483,6 +1485,7 @@ Supported YANG models in addition to those used by sysrepo and netopeer: [buildroot]: https://buildroot.org/ [UNRELEASED]: https://github.com/kernelkit/infix/compare/v25.01.0...HEAD +[v25.02.0]: https://github.com/kernelkit/infix/compare/v25.01.0...v25.02.0 [v25.01.0]: https://github.com/kernelkit/infix/compare/v24.11.0...v25.01.0 [v24.11.1]: https://github.com/kernelkit/infix/compare/v24.11.0...v24.11.1 [v24.11.0]: https://github.com/kernelkit/infix/compare/v24.10.0...v24.11.0 From da39855cec595d904e8f276bd6a469618a55bd94 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Mon, 3 Feb 2025 16:52:01 +0100 Subject: [PATCH 25/27] test: infamy: Allow tests to refine the topology matching process To run LAG tests on hardware, we have to make sure that the chosen links are running at the same speed. We do not want to require a specific link speed from the logical topology, as the real requirement is merely that the two (or however many) links are of the _same_ speed, not any _particular_ speed. Rather than having to complicate the topology matcher, let it take care of the common pattern of matching requirements to provided features, then let tests pass custom node/edge matchers for the complicated cases. --- test/infamy/env.py | 8 ++++++-- test/infamy/topology.py | 37 +++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/test/infamy/env.py b/test/infamy/env.py index 3cc85465..59950012 100644 --- a/test/infamy/env.py +++ b/test/infamy/env.py @@ -65,7 +65,9 @@ def test_argument(option, **kwargs): class Env(object): - def __init__(self, ltop=None, args=None, argv=sys.argv[1::], environ=os.environ): + def __init__(self, ltop=None, args=None, argv=sys.argv[1::], environ=os.environ, + nodes_compatible=topology.compatible, + edge_mappings=topology.edge_mappings): if "INFAMY_ARGS" in environ: argv = shlex.split(environ["INFAMY_ARGS"]) + argv @@ -89,7 +91,9 @@ class Env(object): ldot = pydot.graph_from_dot_file(top_path)[0] self.ltop = topology.Topology(ldot) - if not self.ltop.map_to(self.ptop): + if not self.ltop.map_to(self.ptop, + nodes_compatible=nodes_compatible, + edge_mappings=edge_mappings): raise tap.TestSkip() print(repr(self.ltop)) diff --git a/test/infamy/topology.py b/test/infamy/topology.py index b3c9b26e..00f3efcf 100644 --- a/test/infamy/topology.py +++ b/test/infamy/topology.py @@ -14,25 +14,17 @@ def _qstrip(text): return text def compatible(physical, logical): - provides_set = set(physical.get("provides", "").split()) - requires_set = set(logical.get("requires", "").split()) + return logical["requires"].issubset(physical["provides"]) - return requires_set.issubset(provides_set) - -def map_edges(les, pes): +def edge_mappings(les, pes): les = les.values() pes = pes.values() for perm in permutations(pes, len(les)): candidate = tuple(zip(les, perm)) if all(map(lambda pair: compatible(pair[1], pair[0]), candidate)): - return candidate + yield candidate -def match_node(pn, ln): - return compatible(pn, ln) - -def match_edge(pes, les): - return map_edges(les, pes) is not None class Topology: def __init__(self, dotg): @@ -46,6 +38,9 @@ class Topology: repr(n.get_attributes()) attrs = { _qstrip(k): _qstrip(v) for k, v in n.get_attributes().items() if k != "label" } + for attr in ("requires", "provides"): + attrs[attr] = set(attrs.get(attr, "").split()) + self.g.add_node(name, **attrs) for e in self.dotg.get_edges(): @@ -55,6 +50,10 @@ class Topology: attrs = {_qstrip(k): _qstrip(v) for k, v in e.get_attributes().items()} attrs[sn] = sp attrs[dn] = dp + + for attr in ("requires", "provides"): + attrs[attr] = set(attrs.get(attr, "").split()) + self.g.add_edge(sn, dn, **attrs) def __repr__(self): @@ -73,13 +72,15 @@ class Topology: return out - def map_to(self, phy): + def map_to(self, phy, + nodes_compatible=compatible, edge_mappings=edge_mappings): mapper = isomorphism.MultiGraphMatcher(phy.g, self.g, - edge_match=match_edge, - node_match=match_node) + edge_match=lambda pes, les: any(edge_mappings(les, pes)), + node_match=nodes_compatible) if not mapper.subgraph_is_monomorphic(): return False +# breakpoint() self.phy = phy self.mapping = {} @@ -93,7 +94,7 @@ class Topology: les = self.g.get_edge_data(lsrc, ldst) pes = self.phy.g.get_edge_data(psrc, pdst) - for le, pe in map_edges(les, pes): + for le, pe in next(edge_mappings(les, pes)): self.mapping[lsrc][le[lsrc]] = pe[psrc] self.mapping[ldst][le[ldst]] = pe[pdst] @@ -139,15 +140,15 @@ class Topology: return None def get_mgmt_link(self, src, dst): - return self.get_link(src, dst, lambda e: compatible(e, {"requires": "mgmt"})) + return self.get_link(src, dst, lambda e: compatible(e, {"requires": {"mgmt"}})) def get_ctrl(self): - ns = self.get_nodes(lambda _, attrs: compatible(attrs, {"requires": "controller"})) + ns = self.get_nodes(lambda _, attrs: compatible(attrs, {"requires": {"controller"}})) assert len(ns) == 1 return ns[0] def get_infixen(self): - return self.get_nodes(lambda _, attrs: compatible(attrs, {"requires": "infix"})) + return self.get_nodes(lambda _, attrs: compatible(attrs, {"requires": {"infix"}})) def get_attr(self, name, default=None): From fb394db98151f80a4e87accef36ba3444606a972 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Mon, 3 Feb 2025 20:33:15 +0100 Subject: [PATCH 26/27] test: lag_*: Ensure that links making up a LAG are compatible Do not allow the topology matcher to setup a LAG where one link is running at 10G while the other one runs at 1G, since the bond will never use the 1G port as long as there the 10G link is up. --- test/case/ietf_interfaces/lag_basic/test.py | 4 +-- .../ietf_interfaces/lag_basic/topology.dot | 4 +-- test/case/ietf_interfaces/lag_failure/test.py | 3 ++- .../ietf_interfaces/lag_failure/topology.dot | 8 +++--- test/infamy/lag.py | 26 +++++++++++++++++++ 5 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 test/infamy/lag.py diff --git a/test/case/ietf_interfaces/lag_basic/test.py b/test/case/ietf_interfaces/lag_basic/test.py index 9cbd0d25..7bb01045 100755 --- a/test/case/ietf_interfaces/lag_basic/test.py +++ b/test/case/ietf_interfaces/lag_basic/test.py @@ -14,9 +14,9 @@ each test step using the `mon` interface. from time import sleep, time from datetime import datetime import infamy +import infamy.lag from infamy.util import parallel, until - class DumbLinkBreaker: """Encapsulates basic, dumb link-breaking ops over SSH.""" @@ -135,7 +135,7 @@ def dut_init(dut, mode, addr): with infamy.Test() as test: with test.step("Set up topology and attach to target DUTs"): - env = infamy.Env() + env = infamy.Env(edge_mappings=infamy.lag.edge_mappings) dut1 = env.attach("dut1", "mgmt") dut2 = env.attach("dut2", "mgmt") diff --git a/test/case/ietf_interfaces/lag_basic/topology.dot b/test/case/ietf_interfaces/lag_basic/topology.dot index 9715914e..b8b7e3ff 100644 --- a/test/case/ietf_interfaces/lag_basic/topology.dot +++ b/test/case/ietf_interfaces/lag_basic/topology.dot @@ -28,6 +28,6 @@ graph "lag" { host:mon -- dut1:mon // Monitor connection to dut2 via dut1 host:mgmt2 -- dut2:mgmt [requires="mgmt", color=lightgrey] - dut1:link1 -- dut2:link1 [color=black, fontcolor=black, penwidth=3] - dut1:link2 -- dut2:link2 [color=black, fontcolor=black, penwidth=3] + dut1:link1 -- dut2:link1 [lag=true, color=black, fontcolor=black, penwidth=3] + dut1:link2 -- dut2:link2 [lag=true, color=black, fontcolor=black, penwidth=3] } diff --git a/test/case/ietf_interfaces/lag_failure/test.py b/test/case/ietf_interfaces/lag_failure/test.py index 315c2236..a5ee002b 100755 --- a/test/case/ietf_interfaces/lag_failure/test.py +++ b/test/case/ietf_interfaces/lag_failure/test.py @@ -13,6 +13,7 @@ each failure mode step using the `mon` interface. """ from time import time import infamy +import infamy.lag from infamy.netns import TPMR from infamy.util import parallel @@ -120,7 +121,7 @@ def dut_init(dut, addr, peer): with infamy.Test() as test: with test.step("Set up topology and attach to target DUTs"): - env = infamy.Env() + env = infamy.Env(edge_mappings=infamy.lag.edge_mappings) dut1 = env.attach("dut1") dut2 = env.attach("dut2") diff --git a/test/case/ietf_interfaces/lag_failure/topology.dot b/test/case/ietf_interfaces/lag_failure/topology.dot index 8b24b884..38ca7b55 100644 --- a/test/case/ietf_interfaces/lag_failure/topology.dot +++ b/test/case/ietf_interfaces/lag_failure/topology.dot @@ -28,9 +28,9 @@ graph "lag" { host:mon -- dut1:mon // Monitor connection to dut2 via dut1 host:mgmt2 -- dut2:mgmt [requires="mgmt", color=lightgrey] - dut1:link1 -- host:lb1a [requires="ieee-mc", color=black, fontcolor=black] - host:lb1b -- dut2:link1 [requires="ieee-mc", color=black, fontcolor=black] + dut1:link1 -- host:lb1a [requires="ieee-mc", lag=true, color=black, fontcolor=black] + host:lb1b -- dut2:link1 [requires="ieee-mc", lag=true, color=black, fontcolor=black] - dut1:link2 -- host:lb2a [requires="ieee-mc", color=black, fontcolor=black] - host:lb2b -- dut2:link2 [requires="ieee-mc", color=black, fontcolor=black] + dut1:link2 -- host:lb2a [requires="ieee-mc", lag=true, color=black, fontcolor=black] + host:lb2b -- dut2:link2 [requires="ieee-mc", lag=true, color=black, fontcolor=black] } diff --git a/test/infamy/lag.py b/test/infamy/lag.py new file mode 100644 index 00000000..7a2cf5b1 --- /dev/null +++ b/test/infamy/lag.py @@ -0,0 +1,26 @@ +from . import topology + +def edge_mappings(les, pes): + """Specialized topology edge mapper for LAG tests + + In addition to the standard provides/requires validation, ensure + that for all logical ports marked with a "lag" attribute, the + corresponding physical ports are all of the same link type + (e.g. "link-10gbase-r"). + + """ + def links_compatible(candidate): + seen = None + for (le, pe) in candidate: + if le.get("lag"): + link = set(filter(lambda f: f.startswith("link-"), pe["provides"])) + if seen is None: + seen = link + elif link != seen: + return False + + return True + + for candidate in topology.edge_mappings(les, pes): + if links_compatible(candidate): + yield candidate From 3d816d252515f2b3a67a817bd2e358ae07e06bbb Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 12 Feb 2025 23:16:39 +0100 Subject: [PATCH 27/27] linux: Import mv88e6xxx standalone LAG fix --- ...e6xxx-Fix-timeout-on-waiting-for-PPU.patch | 7 +- ...x-Improve-indirect-register-access-p.patch | 7 +- ...x-Honor-ports-being-managed-via-in-b.patch | 7 +- ...x-Limit-rsvd2cpu-policy-to-user-port.patch | 7 +- ...dsa-mv88e6xxx-Add-LED-infrastructure.patch | 7 +- ...-mv88e6xxx-Add-LED-support-for-6393X.patch | 7 +- ...Use-tag-priority-as-initial-skb-prio.patch | 7 +- ...MDB-memberships-whose-L2-addresses-o.patch | 7 +- ...t-EtherType-based-priority-overrides.patch | 7 +- ...x-Support-EtherType-based-priority-o.patch | 7 +- ...a-mv88e6xxx-Add-mqprio-qdisc-support.patch | 7 +- ...x-Use-VLAN-prio-over-IP-when-both-ar.patch | 7 +- ...8e6xxx-Trap-locally-terminated-VLANs.patch | 7 +- ...0g-Support-firmware-loading-on-88X33.patch | 7 +- ...0g-Fix-power-up-when-strapped-to-sta.patch | 7 +- ...rvell10g-Add-LED-support-for-88X3310.patch | 7 +- ...0g-Support-LEDs-tied-to-a-single-med.patch | 7 +- ...phy-Do-not-resume-PHY-when-attaching.patch | 7 +- ...-classifying-unknown-multicast-as-mr.patch | 7 +- ...e-router-ports-when-forwarding-L2-mu.patch | 6 +- ...delay-for-applying-strict-multicast-.patch | 7 +- ...rentiate-MDB-additions-from-modifica.patch | 7 +- ...ie-tlv-Let-device-probe-even-when-TL.patch | 7 +- ...t-log-level-for-unauthorized-devices.patch | 7 +- ...x-collapse-disabled-state-into-block.patch | 7 +- ...x-Only-activate-LAG-offloading-when-.patch | 147 ++++++++++++++++++ 26 files changed, 196 insertions(+), 125 deletions(-) create mode 100644 patches/linux/6.12.13/0026-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch diff --git a/patches/linux/6.12.13/0001-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch b/patches/linux/6.12.13/0001-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch index 42d717af..1f077e57 100644 --- a/patches/linux/6.12.13/0001-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch +++ b/patches/linux/6.12.13/0001-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch @@ -1,18 +1,15 @@ From 773a0e32210336d682148e2e4c87add5e69105ec Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 12 Mar 2024 10:27:24 +0100 -Subject: [PATCH 01/25] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for +Subject: [PATCH 01/26] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for PPU on 6393X -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires In a multi-chip setup, delays of up to 750ms are observed before the device (6393X) signals completion of PPU initialization (Global 1, register 0, bit 15). Therefore, increase the timeout threshold to 1s. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/dsa/mv88e6xxx/chip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches/linux/6.12.13/0002-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch b/patches/linux/6.12.13/0002-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch index 8b7fb35a..c32771d7 100644 --- a/patches/linux/6.12.13/0002-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch +++ b/patches/linux/6.12.13/0002-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch @@ -1,11 +1,8 @@ From 765cc95de163acf8022b093ce227184e04b49d7b Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 27 Mar 2024 15:52:43 +0100 -Subject: [PATCH 02/25] net: dsa: mv88e6xxx: Improve indirect register access +Subject: [PATCH 02/26] net: dsa: mv88e6xxx: Improve indirect register access perf on 6393 -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires When operating in multi-chip mode, the 6393 family maps a subset of @@ -17,7 +14,7 @@ Therefore, add a new set of SMI operations which remaps accesses to such registers to the corresponding directly addressable register. All other accesses use the regular indirect interface. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/dsa/mv88e6xxx/chip.c | 7 +++ drivers/net/dsa/mv88e6xxx/global1.h | 3 ++ diff --git a/patches/linux/6.12.13/0003-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch b/patches/linux/6.12.13/0003-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch index 192dd061..33b6baba 100644 --- a/patches/linux/6.12.13/0003-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch +++ b/patches/linux/6.12.13/0003-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch @@ -1,11 +1,8 @@ From eb1ba5920254e04daff5c9a09e90b8882bf34490 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Mon, 22 Apr 2024 23:18:01 +0200 -Subject: [PATCH 03/25] net: dsa: mv88e6xxx: Honor ports being managed via +Subject: [PATCH 03/26] net: dsa: mv88e6xxx: Honor ports being managed via in-band-status -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires Keep all link parameters in their unforced states when the port is @@ -17,7 +14,7 @@ This state is the default set up by mv88e6xxx_port_setup_mac(), so all we have to do is to make the phylink MAC callbacks no-ops in cases when in-band-status is being used. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/dsa/mv88e6xxx/chip.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/patches/linux/6.12.13/0004-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch b/patches/linux/6.12.13/0004-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch index 7ba0de08..304702f8 100644 --- a/patches/linux/6.12.13/0004-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch +++ b/patches/linux/6.12.13/0004-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch @@ -1,11 +1,8 @@ From 8b3590e253746d5704650889c106cf5454e26cd3 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 24 Apr 2024 22:41:04 +0200 -Subject: [PATCH 04/25] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user +Subject: [PATCH 04/26] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user ports on 6393X -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires For packets with a DA in the IEEE reserved L2 group range, originating @@ -33,7 +30,7 @@ switch would try to trap it back to the CPU. Given that the CPU is trusted, instead assume that it indeed meant for the packet to be forwarded like any other. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/dsa/mv88e6xxx/port.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/patches/linux/6.12.13/0005-net-dsa-mv88e6xxx-Add-LED-infrastructure.patch b/patches/linux/6.12.13/0005-net-dsa-mv88e6xxx-Add-LED-infrastructure.patch index 5fe6f6d2..78f29b24 100644 --- a/patches/linux/6.12.13/0005-net-dsa-mv88e6xxx-Add-LED-infrastructure.patch +++ b/patches/linux/6.12.13/0005-net-dsa-mv88e6xxx-Add-LED-infrastructure.patch @@ -1,16 +1,13 @@ From 3c13e341f9e18cdd819584086bed0d207a3f042a Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 16 Nov 2023 19:44:32 +0100 -Subject: [PATCH 05/25] net: dsa: mv88e6xxx: Add LED infrastructure -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit +Subject: [PATCH 05/26] net: dsa: mv88e6xxx: Add LED infrastructure Organization: Wires Parse DT for LEDs and register them for devices that support it, though no actual implementations exist yet. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/dsa/mv88e6xxx/Makefile | 1 + drivers/net/dsa/mv88e6xxx/chip.c | 5 +- diff --git a/patches/linux/6.12.13/0006-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch b/patches/linux/6.12.13/0006-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch index 495d6cb3..f92c5dba 100644 --- a/patches/linux/6.12.13/0006-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch +++ b/patches/linux/6.12.13/0006-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch @@ -1,10 +1,7 @@ From eb8d96aab3ee81c40252f8c5e1628b3709e23cab Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 16 Nov 2023 21:59:35 +0100 -Subject: [PATCH 06/25] net: dsa: mv88e6xxx: Add LED support for 6393X -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit +Subject: [PATCH 06/26] net: dsa: mv88e6xxx: Add LED support for 6393X Organization: Wires Trigger support: @@ -12,7 +9,7 @@ Trigger support: - "timer" - "netdev" -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/dsa/mv88e6xxx/chip.c | 1 + drivers/net/dsa/mv88e6xxx/leds.c | 229 +++++++++++++++++++++++++++++++ diff --git a/patches/linux/6.12.13/0007-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch b/patches/linux/6.12.13/0007-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch index 92163eed..9516a801 100644 --- a/patches/linux/6.12.13/0007-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch +++ b/patches/linux/6.12.13/0007-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch @@ -1,11 +1,8 @@ From 06a7be8d8ca0241b89504595b0838ecf25b6c768 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 28 May 2024 10:38:42 +0200 -Subject: [PATCH 07/25] net: dsa: tag_dsa: Use tag priority as initial +Subject: [PATCH 07/26] net: dsa: tag_dsa: Use tag priority as initial skb->priority -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires Use the 3-bit priority field from the DSA tag as the initial packet @@ -23,7 +20,7 @@ can do with an "ingress-qos-map" on VLAN interfaces. Until that is implemented, support the setup that is likely to be the most common; a 1:1 mapping from FPri to skb->priority. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- net/dsa/tag_dsa.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/patches/linux/6.12.13/0008-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch b/patches/linux/6.12.13/0008-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch index d3d351b4..d6d8a186 100644 --- a/patches/linux/6.12.13/0008-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch +++ b/patches/linux/6.12.13/0008-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch @@ -1,11 +1,8 @@ From 0312ac7f2a3d956ff9aec3f84b3b64302e59d36c Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 16 Jan 2024 16:00:55 +0100 -Subject: [PATCH 08/25] net: dsa: Support MDB memberships whose L2 addresses +Subject: [PATCH 08/26] net: dsa: Support MDB memberships whose L2 addresses overlap -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires Multiple IP multicast groups (32 for v4, 2^80 for v6) map to the same @@ -33,7 +30,7 @@ needed to do this is already in place, since it is also needed on CPU and DSA ports. Thus, "implement" this by simply removing the guards which previously skipped reference countung on user ports. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- net/dsa/switch.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/patches/linux/6.12.13/0009-net-dsa-Support-EtherType-based-priority-overrides.patch b/patches/linux/6.12.13/0009-net-dsa-Support-EtherType-based-priority-overrides.patch index 5caa1aca..8a2ef754 100644 --- a/patches/linux/6.12.13/0009-net-dsa-Support-EtherType-based-priority-overrides.patch +++ b/patches/linux/6.12.13/0009-net-dsa-Support-EtherType-based-priority-overrides.patch @@ -1,13 +1,10 @@ From 45716feb5762311a384f38d8b6449fc652a73764 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 21 Mar 2024 19:12:15 +0100 -Subject: [PATCH 09/25] net: dsa: Support EtherType based priority overrides -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit +Subject: [PATCH 09/26] net: dsa: Support EtherType based priority overrides Organization: Wires -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- include/net/dsa.h | 4 ++++ net/dsa/user.c | 56 +++++++++++++++++++++++++++++++++++++++++++++-- diff --git a/patches/linux/6.12.13/0010-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch b/patches/linux/6.12.13/0010-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch index b1886a18..5f17111b 100644 --- a/patches/linux/6.12.13/0010-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch +++ b/patches/linux/6.12.13/0010-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch @@ -1,14 +1,11 @@ From d2802357f81b4995c92fdd6f2ab8ea923b69cef1 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 22 Mar 2024 16:15:43 +0100 -Subject: [PATCH 10/25] net: dsa: mv88e6xxx: Support EtherType based priority +Subject: [PATCH 10/26] net: dsa: mv88e6xxx: Support EtherType based priority overrides -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/dsa/mv88e6xxx/chip.c | 64 +++++++++++++++++++++++++++++ drivers/net/dsa/mv88e6xxx/chip.h | 21 ++++++++++ diff --git a/patches/linux/6.12.13/0011-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch b/patches/linux/6.12.13/0011-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch index 58887f29..a82eaf79 100644 --- a/patches/linux/6.12.13/0011-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch +++ b/patches/linux/6.12.13/0011-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch @@ -1,10 +1,7 @@ From 985f9b471326f65d2c29978b511181200c1b32dd Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 28 May 2024 11:04:22 +0200 -Subject: [PATCH 11/25] net: dsa: mv88e6xxx: Add mqprio qdisc support -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit +Subject: [PATCH 11/26] net: dsa: mv88e6xxx: Add mqprio qdisc support Organization: Wires Add support for attaching mqprio qdisc's to mv88e6xxx ports and use @@ -28,7 +25,7 @@ Since FPri is always a 3-bit field, even on older chips with only 4 physical queues, always report 8 queues and let the chip's policy handle the mapping down to the "real" number. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/dsa/mv88e6xxx/chip.c | 70 ++++++++++++++++++++++++++++++++ net/dsa/tag_dsa.c | 4 +- diff --git a/patches/linux/6.12.13/0012-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch b/patches/linux/6.12.13/0012-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch index 0e097437..a323b565 100644 --- a/patches/linux/6.12.13/0012-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch +++ b/patches/linux/6.12.13/0012-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch @@ -1,11 +1,8 @@ From da95446b056b1efc27353d2549298580978a079f Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 29 May 2024 13:20:41 +0200 -Subject: [PATCH 12/25] net: dsa: mv88e6xxx: Use VLAN prio over IP when both +Subject: [PATCH 12/26] net: dsa: mv88e6xxx: Use VLAN prio over IP when both are available -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires Switch the priority sourcing precdence to prefer VLAN PCP over IP @@ -26,7 +23,7 @@ main reasons for choosing the new default: core over trusted VLAN trunks, the packet should keep its original priority, independent of what inner protocol fields may indicate. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/dsa/mv88e6xxx/chip.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/patches/linux/6.12.13/0013-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch b/patches/linux/6.12.13/0013-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch index c211f3d6..5a9dc371 100644 --- a/patches/linux/6.12.13/0013-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch +++ b/patches/linux/6.12.13/0013-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch @@ -1,11 +1,8 @@ From e4a71c66724ba89a37dee71db5ae9e555676547e Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 26 Nov 2024 19:45:59 +0100 -Subject: [PATCH 13/25] [FIX] net: dsa: mv88e6xxx: Trap locally terminated +Subject: [PATCH 13/26] [FIX] net: dsa: mv88e6xxx: Trap locally terminated VLANs -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires Before this change, in a setup like the following, packets assigned to @@ -28,7 +25,7 @@ marked as policy entries. As the VTU policy of user ports is already set to TRAP (to ensure proper standalone port operation), this will cause all packets assigned to these VLANs to properly terminated. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/dsa/mv88e6xxx/chip.c | 33 ++++++++++++++++++-------------- include/net/switchdev.h | 4 ++++ diff --git a/patches/linux/6.12.13/0014-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch b/patches/linux/6.12.13/0014-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch index 3b75bef9..af6ff794 100644 --- a/patches/linux/6.12.13/0014-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch +++ b/patches/linux/6.12.13/0014-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch @@ -1,11 +1,8 @@ From 9ade37c2fa9f5a6faebeaa8894d9703624eae5ef Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 19 Sep 2023 18:38:10 +0200 -Subject: [PATCH 14/25] net: phy: marvell10g: Support firmware loading on +Subject: [PATCH 14/26] net: phy: marvell10g: Support firmware loading on 88X3310 -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires When probing, if a device is waiting for firmware to be loaded into @@ -15,7 +12,7 @@ We have no choice but to bail out of the probe if firmware is not available, as the device does not have any built-in image on which to fall back. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/phy/marvell10g.c | 161 +++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/patches/linux/6.12.13/0015-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch b/patches/linux/6.12.13/0015-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch index d1675448..9c8fe144 100644 --- a/patches/linux/6.12.13/0015-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch +++ b/patches/linux/6.12.13/0015-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch @@ -1,18 +1,15 @@ From 9571b0e79717fbcef4540b9a20a4fb2fe4c57f26 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 21 Nov 2023 20:15:24 +0100 -Subject: [PATCH 15/25] net: phy: marvell10g: Fix power-up when strapped to +Subject: [PATCH 15/26] net: phy: marvell10g: Fix power-up when strapped to start powered down -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires On devices which are hardware strapped to start powered down (PDSTATE == 1), make sure that we clear the power-down bit on all units affected by this setting. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/phy/marvell10g.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/patches/linux/6.12.13/0016-net-phy-marvell10g-Add-LED-support-for-88X3310.patch b/patches/linux/6.12.13/0016-net-phy-marvell10g-Add-LED-support-for-88X3310.patch index 168e2e3e..af5cd832 100644 --- a/patches/linux/6.12.13/0016-net-phy-marvell10g-Add-LED-support-for-88X3310.patch +++ b/patches/linux/6.12.13/0016-net-phy-marvell10g-Add-LED-support-for-88X3310.patch @@ -1,10 +1,7 @@ From 05a6f49a221e797fdbd21501902bd969b9c64640 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 15 Nov 2023 20:58:42 +0100 -Subject: [PATCH 16/25] net: phy: marvell10g: Add LED support for 88X3310 -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit +Subject: [PATCH 16/26] net: phy: marvell10g: Add LED support for 88X3310 Organization: Wires Pickup the LEDs from the state in which the hardware reset or @@ -21,7 +18,7 @@ Trigger support: - "netdev": Offload link or duplex information to the solid behavior; tx and/or rx activity to blink behavior. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/phy/marvell10g.c | 422 +++++++++++++++++++++++++++++++++++ 1 file changed, 422 insertions(+) diff --git a/patches/linux/6.12.13/0017-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch b/patches/linux/6.12.13/0017-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch index 66e1daba..92cc8770 100644 --- a/patches/linux/6.12.13/0017-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch +++ b/patches/linux/6.12.13/0017-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch @@ -1,11 +1,8 @@ From 202a7fccb4d46e38ed6f82788a44cf7320377549 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 12 Dec 2023 09:51:05 +0100 -Subject: [PATCH 17/25] net: phy: marvell10g: Support LEDs tied to a single +Subject: [PATCH 17/26] net: phy: marvell10g: Support LEDs tied to a single media side -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires In a combo-port setup, i.e. where both the copper and fiber interface @@ -18,7 +15,7 @@ the offloading of the "netdev" trigger, such that LEDs attached to the RJ45 jack only lights up when a copper link is established, and vice versa for the SFP cage. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/phy/marvell10g.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/patches/linux/6.12.13/0018-net-phy-Do-not-resume-PHY-when-attaching.patch b/patches/linux/6.12.13/0018-net-phy-Do-not-resume-PHY-when-attaching.patch index 00ba679d..ad2dbef6 100644 --- a/patches/linux/6.12.13/0018-net-phy-Do-not-resume-PHY-when-attaching.patch +++ b/patches/linux/6.12.13/0018-net-phy-Do-not-resume-PHY-when-attaching.patch @@ -1,10 +1,7 @@ From 31f2aa0e957643cc51be65750c4f65be8c507a18 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 27 Mar 2024 10:10:19 +0100 -Subject: [PATCH 18/25] net: phy: Do not resume PHY when attaching -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit +Subject: [PATCH 18/26] net: phy: Do not resume PHY when attaching Organization: Wires The PHY should not start negotiating with its link-partner until @@ -19,7 +16,7 @@ probing (e.g. DSA) would end up with a physical link being established, even though the corresponding interface was still administratively down. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/phy/phy_device.c | 1 - 1 file changed, 1 deletion(-) diff --git a/patches/linux/6.12.13/0019-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch b/patches/linux/6.12.13/0019-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch index 459115b1..6ea42ba4 100644 --- a/patches/linux/6.12.13/0019-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch +++ b/patches/linux/6.12.13/0019-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch @@ -1,11 +1,8 @@ From 83c259d997fa40f0b9b9a3250746531df9c730c3 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 4 Mar 2024 16:47:28 +0100 -Subject: [PATCH 19/25] net: bridge: avoid classifying unknown multicast as +Subject: [PATCH 19/26] net: bridge: avoid classifying unknown multicast as mrouters_only -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires Unknown multicast, MAC/IPv4/IPv6, should always be flooded according to @@ -19,7 +16,7 @@ Because a multicast router should always receive both known and unknown multicast. Signed-off-by: Joachim Wiberg -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- include/uapi/linux/if_bridge.h | 1 + net/bridge/br.c | 5 +++++ diff --git a/patches/linux/6.12.13/0020-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch b/patches/linux/6.12.13/0020-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch index f80267f4..6c5e012a 100644 --- a/patches/linux/6.12.13/0020-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch +++ b/patches/linux/6.12.13/0020-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch @@ -1,11 +1,8 @@ From f2d2f489eb2073e79d3908a4f9abe797ce5c5a17 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 5 Mar 2024 06:44:41 +0100 -Subject: [PATCH 20/25] net: bridge: Ignore router ports when forwarding L2 +Subject: [PATCH 20/26] net: bridge: Ignore router ports when forwarding L2 multicast -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires Multicast router ports are either statically configured or learned from @@ -15,7 +12,6 @@ of unknown multicast or using permanent MDB entries. Signed-off-by: Tobias Waldekranz Signed-off-by: Joachim Wiberg -Signed-off-by: Mattias Walström --- net/bridge/br_private.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/patches/linux/6.12.13/0021-net-bridge-drop-delay-for-applying-strict-multicast-.patch b/patches/linux/6.12.13/0021-net-bridge-drop-delay-for-applying-strict-multicast-.patch index 301ea7bd..4ce2c3d1 100644 --- a/patches/linux/6.12.13/0021-net-bridge-drop-delay-for-applying-strict-multicast-.patch +++ b/patches/linux/6.12.13/0021-net-bridge-drop-delay-for-applying-strict-multicast-.patch @@ -1,11 +1,8 @@ From 4a88856e21984b9292361ad77d7dfc13fad239a5 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 4 Apr 2024 16:36:30 +0200 -Subject: [PATCH 21/25] net: bridge: drop delay for applying strict multicast +Subject: [PATCH 21/26] net: bridge: drop delay for applying strict multicast filtering -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires This *local* patch drops the initial delay before applying strict multicast @@ -22,7 +19,7 @@ A proper fix for upstreaming could be to add a knob to disable the delay. [2]: https://lore.kernel.org/netdev/20240127175033.9640-1-linus.luessing@c0d3.blue/ Signed-off-by: Joachim Wiberg -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- net/bridge/br_multicast.c | 42 +++++++-------------------------------- net/bridge/br_private.h | 4 +--- diff --git a/patches/linux/6.12.13/0022-net-bridge-Differentiate-MDB-additions-from-modifica.patch b/patches/linux/6.12.13/0022-net-bridge-Differentiate-MDB-additions-from-modifica.patch index b73c14e5..acbf6b3e 100644 --- a/patches/linux/6.12.13/0022-net-bridge-Differentiate-MDB-additions-from-modifica.patch +++ b/patches/linux/6.12.13/0022-net-bridge-Differentiate-MDB-additions-from-modifica.patch @@ -1,11 +1,8 @@ From 12fe155cedae381d143a0b61b5375d1edb7c46e6 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 16 May 2024 14:51:54 +0200 -Subject: [PATCH 22/25] net: bridge: Differentiate MDB additions from +Subject: [PATCH 22/26] net: bridge: Differentiate MDB additions from modifications -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires Before this change, the reception of an IGMPv3 report (and analogously @@ -25,7 +22,7 @@ generated. Therefore, discriminate new groups from changes to existing groups by introducing a RTM_SETMDB events to be used in the latter scenario. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- include/uapi/linux/rtnetlink.h | 2 ++ net/bridge/br_mdb.c | 4 ++-- diff --git a/patches/linux/6.12.13/0023-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch b/patches/linux/6.12.13/0023-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch index af5bf18c..a6ef5f88 100644 --- a/patches/linux/6.12.13/0023-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch +++ b/patches/linux/6.12.13/0023-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch @@ -1,11 +1,8 @@ From d8d81ca2a348095d87110d3d6a65134f1b7ce212 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 24 Nov 2023 23:29:55 +0100 -Subject: [PATCH 23/25] nvmem: layouts: onie-tlv: Let device probe even when +Subject: [PATCH 23/26] nvmem: layouts: onie-tlv: Let device probe even when TLV is invalid -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires Before this change, probing an NVMEM device, expected to contain a @@ -17,7 +14,7 @@ be successfully probed. Therefore, settle for reporting data corruption issues in the log, and simply refrain from registering any cells in those cases. -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/nvmem/layouts/onie-tlv.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/patches/linux/6.12.13/0024-usb-core-adjust-log-level-for-unauthorized-devices.patch b/patches/linux/6.12.13/0024-usb-core-adjust-log-level-for-unauthorized-devices.patch index 09c648af..d4f07cc5 100644 --- a/patches/linux/6.12.13/0024-usb-core-adjust-log-level-for-unauthorized-devices.patch +++ b/patches/linux/6.12.13/0024-usb-core-adjust-log-level-for-unauthorized-devices.patch @@ -1,10 +1,7 @@ From 49592053a901870104c3ea6f6d4a4c18711c955d Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 29 Apr 2024 15:14:51 +0200 -Subject: [PATCH 24/25] usb: core: adjust log level for unauthorized devices -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit +Subject: [PATCH 24/26] usb: core: adjust log level for unauthorized devices Organization: Wires The fact that a USB device currently is not authorized is not an error, @@ -12,7 +9,7 @@ so let's adjust the log level so these messages slip below radar for the commonly used 'quiet' log level. Signed-off-by: Joachim Wiberg -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/usb/core/driver.c | 4 ++-- drivers/usb/core/generic.c | 2 +- diff --git a/patches/linux/6.12.13/0025-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch b/patches/linux/6.12.13/0025-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch index 0012f634..0aab713f 100644 --- a/patches/linux/6.12.13/0025-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch +++ b/patches/linux/6.12.13/0025-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch @@ -1,11 +1,8 @@ From a86053426ab5a0e9155e0b4c7c20495964fa4b59 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 16 Jan 2025 12:35:12 +0100 -Subject: [PATCH 25/25] net: dsa: mv88e6xxx: collapse disabled state into +Subject: [PATCH 25/26] net: dsa: mv88e6xxx: collapse disabled state into blocking -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Organization: Wires This patch changes the behavior of switchcore ports wrt. the port state. @@ -21,7 +18,7 @@ each member port, preventing LACPDUs from passing through to qualify the link and become active. Signed-off-by: Joachim Wiberg -Signed-off-by: Mattias Walström +Signed-off-by: Tobias Waldekranz --- drivers/net/dsa/mv88e6xxx/port.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/patches/linux/6.12.13/0026-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch b/patches/linux/6.12.13/0026-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch new file mode 100644 index 00000000..1c813e2b --- /dev/null +++ b/patches/linux/6.12.13/0026-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch @@ -0,0 +1,147 @@ +From a98487405e1a7b8b69569a3e5f7765f395c36f55 Mon Sep 17 00:00:00 2001 +From: Tobias Waldekranz +Date: Wed, 12 Feb 2025 22:03:14 +0100 +Subject: [PATCH 26/26] net: dsa: mv88e6xxx: Only activate LAG offloading when + bridged +Organization: Wires + +The current port isolation scheme for mv88e6xxx is detailed here: +https://lore.kernel.org/netdev/20220203101657.990241-1-tobias@waldekranz.com/ + +As it turns out, this is not compatible with LAGs. Consider the +following setup: + + .-----. + | CPU | + '--+--' + | + .--0--. .-----. + | sw1 9---0 sw2 | + '-----' '-4-5-' + +A LAG is created from sw2p{4,5}, using ID 0, but it is not attached +to any bridge - so port isolation is active. Let's walk through a +packet's journey to the CPU: + +1. Packet ingresses sw2p4, the MapDA bit is not set, so it is flooded + according to the PVT, which only contains sw2p0 +2. Packet egresses sw2p0 with FORWARD vid:0 dev:2 port:0(lag) +3. Packet ingresses sw1p9, the VTU policy bit is set for VLAN 0, thus + the packet is classified as MGMT and trapped to the CPU +4. Packet egresses sw1p0 with TO_CPU vid:0 dev:2 port:0 +5. Packet ingresses CPU, since no user port is mapped to sw2p0 the + packet is dropped + +The problem is that in step 2, the original source port information is +lost (replaced with "lag 0"), and then sw1 rewrites the ingressing +FORWARD to a TO_CPU, which does not have a LAG bit. As a result, after +the translation between steps 3 and 4, it now looks as if the packet +originally ingressed on sw2p0. + +Therefore, defer enabling the LAG offload until it joins a bridge. In +the example above, it means that the original source port (sw2p4) +information will be in the FORWARD sent in step 2, which allows the +existing port isolation to work as intended. + +Before joining a bridge, the offload does not offer any performance +benefit anyway. All ingressing packet will always have to go to the +CPU; egress traffic always relies on software hashing. + +Signed-off-by: Tobias Waldekranz +--- + drivers/net/dsa/mv88e6xxx/chip.c | 42 +++++++++++++++----------------- + 1 file changed, 20 insertions(+), 22 deletions(-) + +diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c +index 785cd3d0e2b1..37e79d52c175 100644 +--- a/drivers/net/dsa/mv88e6xxx/chip.c ++++ b/drivers/net/dsa/mv88e6xxx/chip.c +@@ -3065,6 +3065,7 @@ static int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port, + struct netlink_ext_ack *extack) + { + struct mv88e6xxx_chip *chip = ds->priv; ++ unsigned int lagid; + int err; + + mv88e6xxx_reg_lock(chip); +@@ -3073,6 +3074,13 @@ static int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port, + if (err) + goto unlock; + ++ if ((lagid = dsa_port_lag_id_get(dsa_to_port(ds, port)))) { ++ /* DSA LAG IDs are one-based */ ++ err = mv88e6xxx_port_set_trunk(chip, port, true, lagid - 1); ++ if (err) ++ goto unlock; ++ } ++ + err = mv88e6xxx_port_set_map_da(chip, port, true); + if (err) + goto unlock; +@@ -3117,6 +3125,14 @@ static void mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port, + "port %d failed to restore map-DA: %pe\n", + port, ERR_PTR(err)); + ++ if (dsa_port_lag_id_get(dsa_to_port(ds, port))) { ++ err = mv88e6xxx_port_set_trunk(chip, port, false, 0); ++ if (err) ++ dev_err(ds->dev, ++ "port %d failed to disable trunking: %pe\n", ++ port, ERR_PTR(err)); ++ } ++ + err = mv88e6xxx_port_commit_pvid(chip, port); + if (err) + dev_err(ds->dev, +@@ -6992,30 +7008,13 @@ static int mv88e6xxx_port_lag_join(struct dsa_switch *ds, int port, + struct netlink_ext_ack *extack) + { + struct mv88e6xxx_chip *chip = ds->priv; +- int err, id; ++ int err; + + if (!mv88e6xxx_lag_can_offload(ds, lag, info, extack)) + return -EOPNOTSUPP; + +- /* DSA LAG IDs are one-based */ +- id = lag.id - 1; +- + mv88e6xxx_reg_lock(chip); +- +- err = mv88e6xxx_port_set_trunk(chip, port, true, id); +- if (err) +- goto err_unlock; +- + err = mv88e6xxx_lag_sync_masks_map(ds, lag); +- if (err) +- goto err_clear_trunk; +- +- mv88e6xxx_reg_unlock(chip); +- return 0; +- +-err_clear_trunk: +- mv88e6xxx_port_set_trunk(chip, port, false, 0); +-err_unlock: + mv88e6xxx_reg_unlock(chip); + return err; + } +@@ -7024,13 +7023,12 @@ static int mv88e6xxx_port_lag_leave(struct dsa_switch *ds, int port, + struct dsa_lag lag) + { + struct mv88e6xxx_chip *chip = ds->priv; +- int err_sync, err_trunk; ++ int err; + + mv88e6xxx_reg_lock(chip); +- err_sync = mv88e6xxx_lag_sync_masks_map(ds, lag); +- err_trunk = mv88e6xxx_port_set_trunk(chip, port, false, 0); ++ err = mv88e6xxx_lag_sync_masks_map(ds, lag); + mv88e6xxx_reg_unlock(chip); +- return err_sync ? : err_trunk; ++ return err; + } + + static int mv88e6xxx_crosschip_lag_change(struct dsa_switch *ds, int sw_index, +-- +2.43.0 +