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
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
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
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 @@
+
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)
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 @@
-
\ No newline at end of file
+
\ 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 @@
-
\ No newline at end of file
+
\ 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.
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..4d9fe8a5 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
@@ -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))
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
+
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()
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/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);
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/confd.inc b/src/confd/yang/confd.inc
index e48c492f..e8f4b354 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"
@@ -39,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-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;
}
diff --git a/src/confd/yang/infix-if-lag.yang b/src/confd/yang/infix-if-lag.yang
new file mode 100644
index 00000000..9f67cc62
--- /dev/null
+++ b/src/confd/yang/infix-if-lag.yang
@@ -0,0 +1,336 @@
+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;
+ }
+ import ietf-inet-types {
+ prefix inet;
+ }
+ import ietf-yang-types {
+ prefix yang;
+ }
+
+ 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-mode {
+ description "Mode values for link aggregates.";
+
+ 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.";
+
+ 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;
+ }
+ }
+
+ /*
+ * Shared settings
+ */
+
+ grouping hash {
+ leaf hash {
+ description "Transmit hash policy.";
+ type hash-policy;
+ config false; // For now, staically set to layer2 only
+ }
+ }
+
+ /*
+ * 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-mode;
+ mandatory true;
+ }
+
+ 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.";
+
+ 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";
+ }
+ }
+ }
+
+ 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.";
+ }
+ }
+ }
+
+ 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 "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;
+ }
+
+ 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 lacp-state;
+ }
+
+ leaf-list partner-state {
+ description "LACP state flags for link partner.";
+ type lacp-state;
+ }
+ }
+ }
+ }
+ }
+}
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
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
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
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):
diff --git a/test/case/ietf_interfaces/Readme.adoc b/test/case/ietf_interfaces/Readme.adoc
index ff5f893e..de861eb0 100644
--- a/test/case/ietf_interfaces/Readme.adoc
+++ b/test/case/ietf_interfaces/Readme.adoc
@@ -33,6 +33,10 @@ include::bridge_vlan_separation/Readme.adoc[]
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/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 @@
+
+
+
+
\ 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": {
diff --git a/test/case/ietf_interfaces/ietf_interfaces.yaml b/test/case/ietf_interfaces/ietf_interfaces.yaml
index 2f2bbe8e..88841fd3 100644
--- a/test/case/ietf_interfaces/ietf_interfaces.yaml
+++ b/test/case/ietf_interfaces/ietf_interfaces.yaml
@@ -35,6 +35,12 @@
- name: ipv4_autoconf
case: ipv4_autoconf/test.py
+- 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_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 @@
+
+
+
+
\ 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..7bb01045
--- /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
+import infamy.lag
+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(edge_mappings=infamy.lag.edge_mappings)
+ 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..b8b7e3ff
--- /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 [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_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 @@
+
+
+
+
+
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 @@
+
+
+
+
\ 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..a5ee002b
--- /dev/null
+++ b/test/case/ietf_interfaces/lag_failure/test.py
@@ -0,0 +1,144 @@
+#!/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
+import infamy.lag
+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(edge_mappings=infamy.lag.edge_mappings)
+ 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..38ca7b55
--- /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", 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", lag=true, color=black, fontcolor=black]
+ host:lb2b -- dut2:link2 [requires="ieee-mc", lag=true, 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 @@
+
+
+
+
+
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..59950012 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,8 +49,25 @@ 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):
+ 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
@@ -74,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/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
diff --git a/test/infamy/netconf.py b/test/infamy/netconf.py
index 1bb1f040..711d684a 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):
@@ -309,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)
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/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):
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"""
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)
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 @@
+
+
+
+
+
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 @@
+
+
+
+
+
diff --git a/utils/sysrepo-load-modules.sh b/utils/srload
similarity index 52%
rename from utils/sysrepo-load-modules.sh
rename to utils/srload
index a50fae58..fabad4d1 100755
--- a/utils/sysrepo-load-modules.sh
+++ b/utils/srload
@@ -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