Merge branch 'bonding-3ad-fix-carrier-state-with-no-usable-slaves'

Louis Scalbert says:

====================
bonding: 3ad: fix carrier state with no usable slaves

This series addresses a blackholing issue and a subsequent link-flapping
issue in the 802.3ad bonding driver when dealing with inactive slaves
and the `min_links` parameter.

When an 802.3ad (LACP) bonding interface has no slaves in the
collecting/distributing state, the bonding master still reports
carrier as up as long as at least 'min_links' slaves have carrier.

In this situation, only one slave is effectively used for TX/RX,
while traffic received on other slaves is dropped. Upper-layer
daemons therefore consider the interface operational, even though
traffic may be blackholed if the lack of LACP negotiation means
the partner is not ready to deal with traffic.

This patchset introduces an optional behavior, widely adopted across
the industry, to address this issue. It consists of bringing the
bonding master interface down to signal to upper-layer processes
that it is not usable.

This patchset depends on the following iproute2 change:
ip/bond: add lacp_strict support
Link: https://lore.kernel.org/netdev/20260408152409.276358-1-louis.scalbert@6wind.com/

Patch 1 adds the missing IFLA_BOND_BROADCAST_NEIGH in if_link
UAPI header.

Patch 2 adds missing broadcast-neigh to YAML rt-link specs.

Patch 3 introduces the lacp_strict configuration knob, which is
applied in the subsequent patch. The default (off) mode preserves
the existing behavior, while the strict mode (on) is intended to force
the bonding master carrier down in this situation.

Patch 4 addresses the core issue when lacp_strict is set to strict.
It ensures that carrier is asserted only when at least 'min_links'
slaves are in the Collecting/Distributing state.

Patch 5 fixes a side effect of the previous patch. Tightening the carrier
logic exposes a state persistence bug: when a physical link goes down,
the LACP collecting/distributing flags remain set. When the link returns,
the interface briefly hallucinates that it is ready, bounces the carrier
up, and then drops it again once LACP renegotiation starts. Fix by
resetting Collecting and Distributing state as soon as the link goes
down.

Patch 6 adds a test for bonding lacp_strict both modes.
====================

Link: https://patch.msgid.link/20260603150331.1919611-1-louis.scalbert@6wind.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2026-06-10 07:20:27 -07:00
12 changed files with 450 additions and 3 deletions

View File

@@ -1353,6 +1353,12 @@ attribute-sets:
-
name: coupled-control
type: u8
-
name: broadcast-neigh
type: u8
-
name: lacp-strict
type: u8
-
name: bond-ad-info-attrs
name-prefix: ifla-bond-ad-info-

View File

@@ -619,6 +619,29 @@ min_links
aggregator cannot be active without at least one available link,
setting this option to 0 or to 1 has the exact same effect.
lacp_strict
Specifies the fallback behavior of a bonding when LACP negotiation
fails on all slave links, i.e. when no slave is in the
Collecting_Distributing state, while at least `min_links` link still
reports carrier up.
This option is only applicable to 802.3ad mode (mode 4).
Valid values are:
off or 0
One interface of the bond is selected to be active, in order to
facilitate communication with peer devices that do not implement
LACP.
on or 1
Interfaces are only permitted to be made active if they have an
active LACP partner and have successfully reached
Collecting_Distributing state.
The default value is 0 (off).
mode
Specifies one of the bonding policies. The default is

View File

@@ -745,6 +745,21 @@ static void __set_agg_ports_ready(struct aggregator *aggregator, int val)
}
}
static int __agg_usable_ports(struct aggregator *agg)
{
struct port *port;
int valid = 0;
for (port = agg->lag_ports; port;
port = port->next_port_in_aggregator) {
if (port->actor_oper_port_state & LACP_STATE_COLLECTING &&
port->actor_oper_port_state & LACP_STATE_DISTRIBUTING)
valid++;
}
return valid;
}
static int __agg_active_ports(struct aggregator *agg)
{
struct port *port;
@@ -1179,10 +1194,10 @@ static void ad_mux_machine(struct port *port, bool *update_slave_arr)
switch (port->sm_mux_state) {
case AD_MUX_DETACHED:
port->actor_oper_port_state &= ~LACP_STATE_SYNCHRONIZATION;
ad_disable_collecting_distributing(port,
update_slave_arr);
port->actor_oper_port_state &= ~LACP_STATE_COLLECTING;
port->actor_oper_port_state &= ~LACP_STATE_DISTRIBUTING;
ad_disable_collecting_distributing(port,
update_slave_arr);
port->ntt = true;
break;
case AD_MUX_WAITING:
@@ -1322,6 +1337,7 @@ static void ad_rx_machine(struct lacpdu *lacpdu, struct port *port)
fallthrough;
case AD_RX_PORT_DISABLED:
port->sm_vars &= ~AD_PORT_MATCHED;
port->partner_oper.port_state &= ~LACP_STATE_SYNCHRONIZATION;
break;
case AD_RX_LACP_DISABLED:
port->sm_vars &= ~AD_PORT_SELECTED;
@@ -2107,6 +2123,7 @@ static void ad_disable_distributing(struct port *port, bool *update_slave_arr)
port->actor_port_number,
aggregator->aggregator_identifier);
__disable_distributing_port(port);
bond_3ad_set_carrier(port->slave->bond);
/* Slave array needs an update */
*update_slave_arr = true;
}
@@ -2130,6 +2147,7 @@ static void ad_enable_collecting_distributing(struct port *port,
port->actor_port_number,
aggregator->aggregator_identifier);
__enable_port(port);
bond_3ad_set_carrier(port->slave->bond);
/* Slave array needs update */
*update_slave_arr = true;
/* Should notify peers if possible */
@@ -2153,6 +2171,7 @@ static void ad_disable_collecting_distributing(struct port *port,
port->actor_port_number,
aggregator->aggregator_identifier);
__disable_port(port);
bond_3ad_set_carrier(port->slave->bond);
/* Slave array needs an update */
*update_slave_arr = true;
}
@@ -2832,7 +2851,9 @@ int bond_3ad_set_carrier(struct bonding *bond)
active = __get_active_agg(&(SLAVE_AD_INFO(first_slave)->aggregator));
if (active) {
/* are enough slaves available to consider link up? */
if (__agg_active_ports(active) < bond->params.min_links) {
if ((bond->params.lacp_strict ? __agg_usable_ports(active)
: __agg_active_ports(active)) <
bond->params.min_links) {
if (netif_carrier_ok(bond->dev)) {
netif_carrier_off(bond->dev);
goto out;

View File

@@ -6457,6 +6457,7 @@ static int __init bond_check_params(struct bond_params *params)
params->ad_user_port_key = ad_user_port_key;
params->coupled_control = 1;
params->broadcast_neighbor = 0;
params->lacp_strict = 0;
if (packets_per_slave > 0) {
params->reciprocal_packets_per_slave =
reciprocal_value(packets_per_slave);

View File

@@ -143,6 +143,7 @@ static const struct nla_policy bond_policy[IFLA_BOND_MAX + 1] = {
[IFLA_BOND_NS_IP6_TARGET] = { .type = NLA_NESTED },
[IFLA_BOND_COUPLED_CONTROL] = { .type = NLA_U8 },
[IFLA_BOND_BROADCAST_NEIGH] = { .type = NLA_U8 },
[IFLA_BOND_LACP_STRICT] = { .type = NLA_U8 },
};
static const struct nla_policy bond_slave_policy[IFLA_BOND_SLAVE_MAX + 1] = {
@@ -599,6 +600,16 @@ static int bond_changelink(struct net_device *bond_dev, struct nlattr *tb[],
return err;
}
if (data[IFLA_BOND_LACP_STRICT]) {
int fallback_mode = nla_get_u8(data[IFLA_BOND_LACP_STRICT]);
bond_opt_initval(&newval, fallback_mode);
err = __bond_opt_set(bond, BOND_OPT_LACP_STRICT, &newval,
data[IFLA_BOND_LACP_STRICT], extack);
if (err)
return err;
}
return 0;
}
@@ -671,6 +682,7 @@ static size_t bond_get_size(const struct net_device *bond_dev)
nla_total_size(sizeof(struct in6_addr)) * BOND_MAX_NS_TARGETS +
nla_total_size(sizeof(u8)) + /* IFLA_BOND_COUPLED_CONTROL */
nla_total_size(sizeof(u8)) + /* IFLA_BOND_BROADCAST_NEIGH */
nla_total_size(sizeof(u8)) + /* IFLA_BOND_LACP_STRICT */
0;
}
@@ -838,6 +850,10 @@ static int bond_fill_info(struct sk_buff *skb,
bond->params.broadcast_neighbor))
goto nla_put_failure;
if (nla_put_u8(skb, IFLA_BOND_LACP_STRICT,
bond->params.lacp_strict))
goto nla_put_failure;
if (BOND_MODE(bond) == BOND_MODE_8023AD) {
struct ad_info info;

View File

@@ -68,6 +68,8 @@ static int bond_option_lacp_active_set(struct bonding *bond,
const struct bond_opt_value *newval);
static int bond_option_lacp_rate_set(struct bonding *bond,
const struct bond_opt_value *newval);
static int bond_option_lacp_strict_set(struct bonding *bond,
const struct bond_opt_value *newval);
static int bond_option_ad_select_set(struct bonding *bond,
const struct bond_opt_value *newval);
static int bond_option_queue_id_set(struct bonding *bond,
@@ -162,6 +164,12 @@ static const struct bond_opt_value bond_lacp_rate_tbl[] = {
{ NULL, -1, 0},
};
static const struct bond_opt_value bond_lacp_strict_tbl[] = {
{ "off", 0, BOND_VALFLAG_DEFAULT},
{ "on", 1, 0},
{ NULL, -1, 0 }
};
static const struct bond_opt_value bond_ad_select_tbl[] = {
{ "stable", BOND_AD_STABLE, BOND_VALFLAG_DEFAULT},
{ "bandwidth", BOND_AD_BANDWIDTH, 0},
@@ -363,6 +371,14 @@ static const struct bond_option bond_opts[BOND_OPT_LAST] = {
.values = bond_lacp_rate_tbl,
.set = bond_option_lacp_rate_set
},
[BOND_OPT_LACP_STRICT] = {
.id = BOND_OPT_LACP_STRICT,
.name = "lacp_strict",
.desc = "Define the LACP fallback mode when no slaves have negotiated",
.unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_8023AD)),
.values = bond_lacp_strict_tbl,
.set = bond_option_lacp_strict_set
},
[BOND_OPT_MINLINKS] = {
.id = BOND_OPT_MINLINKS,
.name = "min_links",
@@ -1685,6 +1701,17 @@ static int bond_option_lacp_rate_set(struct bonding *bond,
return 0;
}
static int bond_option_lacp_strict_set(struct bonding *bond,
const struct bond_opt_value *newval)
{
netdev_dbg(bond->dev, "Setting LACP fallback to %s (%llu)\n",
newval->string, newval->value);
bond->params.lacp_strict = newval->value;
bond_3ad_set_carrier(bond);
return 0;
}
static int bond_option_ad_select_set(struct bonding *bond,
const struct bond_opt_value *newval)
{

View File

@@ -79,6 +79,7 @@ enum {
BOND_OPT_COUPLED_CONTROL,
BOND_OPT_BROADCAST_NEIGH,
BOND_OPT_ACTOR_PORT_PRIO,
BOND_OPT_LACP_STRICT,
BOND_OPT_LAST
};

View File

@@ -129,6 +129,7 @@ struct bond_params {
int peer_notif_delay;
int lacp_active;
int lacp_fast;
int lacp_strict;
unsigned int min_links;
int ad_select;
char primary[IFNAMSIZ];

View File

@@ -1603,6 +1603,7 @@ enum {
IFLA_BOND_NS_IP6_TARGET,
IFLA_BOND_COUPLED_CONTROL,
IFLA_BOND_BROADCAST_NEIGH,
IFLA_BOND_LACP_STRICT,
__IFLA_BOND_MAX,
};

View File

@@ -1526,6 +1526,8 @@ enum {
IFLA_BOND_MISSED_MAX,
IFLA_BOND_NS_IP6_TARGET,
IFLA_BOND_COUPLED_CONTROL,
IFLA_BOND_BROADCAST_NEIGH,
IFLA_BOND_LACP_STRICT,
__IFLA_BOND_MAX,
};

View File

@@ -8,6 +8,7 @@ TEST_PROGS := \
bond-lladdr-target.sh \
bond_ipsec_offload.sh \
bond_lacp_prio.sh \
bond_lacp_strict.sh \
bond_macvlan_ipvlan.sh \
bond_options.sh \
bond_passive_lacp.sh \

View File

@@ -0,0 +1,347 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Testing if bond lacp_strict works
#
# Partner (p_ns)
# +--------------------------+
# | bond0 |
# | + |
# | eth0 | eth1 |
# | +---+---+ |
# | | | |
# +--------------------------+
# | | eth0 | eth1 |
# | | | |
# |(br_ns) | br0 | br1 |
# | | | |
# | | eth2 | eth3 |
# +--------------------------+
# | | | |
# | +---+---+ |
# | eth0 | eth1 |
# | + |
# | bond0 |
# +--------------------------+
# Dut (d_ns)
lib_dir=$(dirname "$0")
# shellcheck disable=SC1090
source "$lib_dir"/../../../net/lib.sh
COLLECTING_DISTRIBUTING_MASK=48
COLLECTING_DISTRIBUTING=48
FAILED=0
setup_links()
{
# shellcheck disable=SC2154
ip -n "${p_ns}" link add eth0 type veth peer name eth0 netns "${br_ns}"
ip -n "${p_ns}" link add eth1 type veth peer name eth1 netns "${br_ns}"
ip -n "${d_ns}" link add eth0 type veth peer name eth2 netns "${br_ns}"
ip -n "${d_ns}" link add eth1 type veth peer name eth3 netns "${br_ns}"
ip -n "${br_ns}" link add br0 type bridge
ip -n "${br_ns}" link add br1 type bridge
ip -n "${br_ns}" link set dev br0 type bridge stp_state 0
ip -n "${br_ns}" link set dev br1 type bridge stp_state 0
ip -n "${br_ns}" link set eth0 master br0
ip -n "${br_ns}" link set eth2 master br0
ip -n "${br_ns}" link set eth1 master br1
ip -n "${br_ns}" link set eth3 master br1
# Allow LACP trames forwarding on bridge ports
ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br0/brif/eth0/group_fwd_mask"
ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br1/brif/eth1/group_fwd_mask"
ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br0/brif/eth2/group_fwd_mask"
ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br1/brif/eth3/group_fwd_mask"
ip -n "${br_ns}" link set eth0 up
ip -n "${br_ns}" link set eth2 up
ip -n "${br_ns}" link set eth1 up
ip -n "${br_ns}" link set eth3 up
ip -n "${br_ns}" link set br0 up
ip -n "${br_ns}" link set br1 up
ip -n "${d_ns}" link add bond0 type bond mode 802.3ad miimon 100 \
lacp_rate fast min_links 1
ip -n "${p_ns}" link add bond0 type bond mode 802.3ad miimon 100 \
lacp_rate fast min_links 1
ip -n "${d_ns}" link set eth0 master bond0
ip -n "${d_ns}" link set eth1 master bond0
ip -n "${p_ns}" link set eth0 master bond0
ip -n "${p_ns}" link set eth1 master bond0
ip -n "${d_ns}" link set bond0 up
ip -n "${p_ns}" link set bond0 up
}
test_master_carrier() {
local expected=$1
local mode_name=$2
local carrier
carrier=$(ip netns exec "${d_ns}" cat /sys/class/net/bond0/carrier)
[ "$carrier" == "1" ] && carrier="up" || carrier="down"
[ "$carrier" == "$expected" ] && return
echo "FAIL: Expected carrier $expected in lacp_strict $mode_name mode, got $carrier"
RET=1
}
compare_state() {
local actual_state=$1
local expected_state=$2
local iface=$3
local last_attempt=$4
[ $((actual_state & COLLECTING_DISTRIBUTING_MASK)) -eq "$expected_state" ] \
&& return 0
[ "$last_attempt" -ne 1 ] && return 1
printf "FAIL: Expected LACP %s actor state to " "$iface"
if [ "$expected_state" -eq $COLLECTING_DISTRIBUTING ]; then
echo "be in Collecting/Distributing state"
else
echo "have neither Collecting nor Distributing set."
fi
return 1
}
_test_lacp_port_state() {
local interface=$1
local expected=$2
local last_attempt=$3
local eth0_actor_state eth1_actor_state
local ret=0
# shellcheck disable=SC2016
while IFS='=' read -r k v; do
printf -v "$k" '%s' "$v"
done < <(
ip netns exec "${d_ns}" awk '
/^Slave Interface: / { iface=$3 }
/details actor lacp pdu:/ { ctx="actor" }
/details partner lacp pdu:/ { ctx="partner" }
/^[[:space:]]+port state: / {
if (ctx == "actor") {
gsub(":", "", iface)
printf "%s_%s_state=%s\n", iface, ctx, $3
}
}
' /proc/net/bonding/bond0
)
if [ "$interface" == "eth0" ] || [ "$interface" == "both" ]; then
compare_state "$eth0_actor_state" "$expected" eth0 "$last_attempt" || ret=1
fi
if [ "$interface" == "eth1" ] || [ "$interface" == "both" ]; then
compare_state "$eth1_actor_state" "$expected" eth1 "$last_attempt" || ret=1
fi
return $ret
}
test_lacp_port_state() {
local interface=$1
local expected=$2
local retry=$3
local last_attempt=0
local attempt=1
local ret=1
while [ $attempt -le $((retry + 1)) ]; do
[ $attempt -eq $((retry + 1)) ] && last_attempt=1
_test_lacp_port_state "$interface" "$expected" "$last_attempt" && return
((attempt++))
sleep 1
done
RET=1
}
trap cleanup_all_ns EXIT
setup_ns d_ns p_ns br_ns
setup_links
# Initial state
RET=0
mode=off
test_lacp_port_state both $COLLECTING_DISTRIBUTING 3
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 up"
# partner eth0 down, eth1 up
# (replicate eth0 state to dut eth0 by shutting a bridge port)
RET=0
ip -n "${p_ns}" link set eth0 down
ip -n "${br_ns}" link set eth2 down
test_lacp_port_state eth0 $FAILED 5
test_lacp_port_state eth1 $COLLECTING_DISTRIBUTING 1
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 down"
# partner eth0 and eth1 down
RET=0
ip -n "${p_ns}" link set eth1 down
ip -n "${br_ns}" link set eth3 down
test_lacp_port_state both $FAILED 5
test_master_carrier down $mode # down because of min_links
log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 down"
# partner eth0 up, eth1 down
RET=0
ip -n "${p_ns}" link set eth0 up
ip -n "${br_ns}" link set eth2 up
test_lacp_port_state eth0 $COLLECTING_DISTRIBUTING 60
test_lacp_port_state eth1 $FAILED 1
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 up, eth1 down"
# partner eth0 and eth1 up
RET=0
ip -n "${p_ns}" link set eth1 up
ip -n "${br_ns}" link set eth3 up
test_lacp_port_state both $COLLECTING_DISTRIBUTING 60
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 up"
# partner eth0 stops LACP and eth1 up
RET=0
ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br0/brif/eth0/group_fwd_mask"
ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br0/brif/eth2/group_fwd_mask"
test_lacp_port_state eth0 $FAILED 5
test_lacp_port_state eth1 $COLLECTING_DISTRIBUTING 1
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 stopped sending LACP"
# partner eth0 and eth1 stop LACP
RET=0
ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br1/brif/eth1/group_fwd_mask"
ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br1/brif/eth3/group_fwd_mask"
test_lacp_port_state both $FAILED 5
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 stopped sending LACP"
# switch to lacp_strict on
RET=0
mode=on
ip -n "${d_ns}" link set dev bond0 type bond lacp_strict $mode
test_lacp_port_state both $FAILED 1
test_master_carrier down $mode
log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 stopped sending LACP"
# switch back to lacp_strict off mode
RET=0
mode=off
ip -n "${d_ns}" link set dev bond0 type bond lacp_strict $mode
test_lacp_port_state both $FAILED 1
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 stopped sending LACP"
# eth0 recovers LACP
RET=0
ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br0/brif/eth0/group_fwd_mask"
ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br0/brif/eth2/group_fwd_mask"
test_lacp_port_state eth0 $COLLECTING_DISTRIBUTING 60
test_lacp_port_state eth1 $FAILED 1
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 recovered and eth1 stopped sending LACP"
# eth1 recovers LACP
RET=0
ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br1/brif/eth1/group_fwd_mask"
ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br1/brif/eth3/group_fwd_mask"
test_lacp_port_state both $COLLECTING_DISTRIBUTING 60
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 recovered LACP"
# switch to lacp_strict on
RET=0
mode=on
ip -n "${d_ns}" link set dev bond0 type bond lacp_strict $mode
test_lacp_port_state both $COLLECTING_DISTRIBUTING 1
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 up"
# partner eth0 down, eth1 up
RET=0
ip -n "${p_ns}" link set eth0 down
ip -n "${br_ns}" link set eth2 down
test_lacp_port_state eth0 $FAILED 5
test_lacp_port_state eth1 $COLLECTING_DISTRIBUTING 1
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 down"
# partner eth0 and eth1 down
RET=0
ip -n "${p_ns}" link set eth1 down
ip -n "${br_ns}" link set eth3 down
test_lacp_port_state both $FAILED 5
test_master_carrier down $mode # down because of min_links
log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 down"
# partner eth0 up, eth1 down
RET=0
ip -n "${p_ns}" link set eth0 up
ip -n "${br_ns}" link set eth2 up
test_lacp_port_state eth0 $COLLECTING_DISTRIBUTING 60
test_lacp_port_state eth1 $FAILED 1
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 up, eth1 down"
# partner eth0 and eth1 up
RET=0
ip -n "${p_ns}" link set eth1 up
ip -n "${br_ns}" link set eth3 up
test_lacp_port_state both $COLLECTING_DISTRIBUTING 60
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 up"
# partner eth0 stops LACP and eth1 up
RET=0
ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br0/brif/eth0/group_fwd_mask"
ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br0/brif/eth2/group_fwd_mask"
test_lacp_port_state eth0 $FAILED 5
test_lacp_port_state eth1 $COLLECTING_DISTRIBUTING 1
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 stopped sending LACP"
# partner eth0 and eth1 stop LACP
RET=0
ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br1/brif/eth1/group_fwd_mask"
ip netns exec "${br_ns}" sh -c "echo 0 > /sys/class/net/br1/brif/eth3/group_fwd_mask"
test_lacp_port_state both $FAILED 5
test_master_carrier down $mode
log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 stopped sending LACP"
# eth0 recovers LACP
RET=0
ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br0/brif/eth0/group_fwd_mask"
ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br0/brif/eth2/group_fwd_mask"
test_lacp_port_state eth0 $COLLECTING_DISTRIBUTING 60
test_lacp_port_state eth1 $FAILED 1
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 recovered and eth1 stopped sending LACP"
# eth1 recovers LACP
# shellcheck disable=SC2034
RET=0
ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br1/brif/eth1/group_fwd_mask"
ip netns exec "${br_ns}" sh -c "echo 4 > /sys/class/net/br1/brif/eth3/group_fwd_mask"
test_lacp_port_state both $COLLECTING_DISTRIBUTING 60
test_master_carrier up $mode
log_test "bond LACP" "lacp_strict $mode - eth0 and eth1 recovered LACP"
exit "${EXIT_STATUS}"