Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

bonding: fix state transition issue in link monitoring

Since de77ecd4ef02 ("bonding: improve link-status update in
mii-monitoring"), the bonding driver has utilized two separate variables
to indicate the next link state a particular slave should transition to.
Each is used to communicate to a different portion of the link state
change commit logic; one to the bond_miimon_commit function itself, and
another to the state transition logic.

Unfortunately, the two variables can become unsynchronized,
resulting in incorrect link state transitions within bonding. This can
cause slaves to become stuck in an incorrect link state until a
subsequent carrier state transition.

The issue occurs when a special case in bond_slave_netdev_event
sets slave->link directly to BOND_LINK_FAIL. On the next pass through
bond_miimon_inspect after the slave goes carrier up, the BOND_LINK_FAIL
case will set the proposed next state (link_new_state) to BOND_LINK_UP,
but the new_link to BOND_LINK_DOWN. The setting of the final link state
from new_link comes after that from link_new_state, and so the slave
will end up incorrectly in _DOWN state.

Resolve this by combining the two variables into one.

Reported-by: Aleksei Zakharov <zakharov.a.g@yandex.ru>
Reported-by: Sha Zhang <zhangsha.zhang@huawei.com>
Cc: Mahesh Bandewar <maheshb@google.com>
Fixes: de77ecd4ef02 ("bonding: improve link-status update in mii-monitoring")
Signed-off-by: Jay Vosburgh <jay.vosburgh@canonical.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Jay Vosburgh and committed by
David S. Miller
1899bb32 41de23e2

+23 -24
+22 -22
drivers/net/bonding/bond_main.c
··· 2083 2083 ignore_updelay = !rcu_dereference(bond->curr_active_slave); 2084 2084 2085 2085 bond_for_each_slave_rcu(bond, slave, iter) { 2086 - slave->new_link = BOND_LINK_NOCHANGE; 2087 - slave->link_new_state = slave->link; 2086 + bond_propose_link_state(slave, BOND_LINK_NOCHANGE); 2088 2087 2089 2088 link_state = bond_check_dev_link(bond, slave->dev, 0); 2090 2089 ··· 2117 2118 } 2118 2119 2119 2120 if (slave->delay <= 0) { 2120 - slave->new_link = BOND_LINK_DOWN; 2121 + bond_propose_link_state(slave, BOND_LINK_DOWN); 2121 2122 commit++; 2122 2123 continue; 2123 2124 } ··· 2154 2155 slave->delay = 0; 2155 2156 2156 2157 if (slave->delay <= 0) { 2157 - slave->new_link = BOND_LINK_UP; 2158 + bond_propose_link_state(slave, BOND_LINK_UP); 2158 2159 commit++; 2159 2160 ignore_updelay = false; 2160 2161 continue; ··· 2192 2193 struct slave *slave, *primary; 2193 2194 2194 2195 bond_for_each_slave(bond, slave, iter) { 2195 - switch (slave->new_link) { 2196 + switch (slave->link_new_state) { 2196 2197 case BOND_LINK_NOCHANGE: 2197 2198 /* For 802.3ad mode, check current slave speed and 2198 2199 * duplex again in case its port was disabled after ··· 2264 2265 2265 2266 default: 2266 2267 slave_err(bond->dev, slave->dev, "invalid new link %d on slave\n", 2267 - slave->new_link); 2268 - slave->new_link = BOND_LINK_NOCHANGE; 2268 + slave->link_new_state); 2269 + bond_propose_link_state(slave, BOND_LINK_NOCHANGE); 2269 2270 2270 2271 continue; 2271 2272 } ··· 2673 2674 bond_for_each_slave_rcu(bond, slave, iter) { 2674 2675 unsigned long trans_start = dev_trans_start(slave->dev); 2675 2676 2676 - slave->new_link = BOND_LINK_NOCHANGE; 2677 + bond_propose_link_state(slave, BOND_LINK_NOCHANGE); 2677 2678 2678 2679 if (slave->link != BOND_LINK_UP) { 2679 2680 if (bond_time_in_interval(bond, trans_start, 1) && 2680 2681 bond_time_in_interval(bond, slave->last_rx, 1)) { 2681 2682 2682 - slave->new_link = BOND_LINK_UP; 2683 + bond_propose_link_state(slave, BOND_LINK_UP); 2683 2684 slave_state_changed = 1; 2684 2685 2685 2686 /* primary_slave has no meaning in round-robin ··· 2704 2705 if (!bond_time_in_interval(bond, trans_start, 2) || 2705 2706 !bond_time_in_interval(bond, slave->last_rx, 2)) { 2706 2707 2707 - slave->new_link = BOND_LINK_DOWN; 2708 + bond_propose_link_state(slave, BOND_LINK_DOWN); 2708 2709 slave_state_changed = 1; 2709 2710 2710 2711 if (slave->link_failure_count < UINT_MAX) ··· 2735 2736 goto re_arm; 2736 2737 2737 2738 bond_for_each_slave(bond, slave, iter) { 2738 - if (slave->new_link != BOND_LINK_NOCHANGE) 2739 - slave->link = slave->new_link; 2739 + if (slave->link_new_state != BOND_LINK_NOCHANGE) 2740 + slave->link = slave->link_new_state; 2740 2741 } 2741 2742 2742 2743 if (slave_state_changed) { ··· 2759 2760 } 2760 2761 2761 2762 /* Called to inspect slaves for active-backup mode ARP monitor link state 2762 - * changes. Sets new_link in slaves to specify what action should take 2763 - * place for the slave. Returns 0 if no changes are found, >0 if changes 2764 - * to link states must be committed. 2763 + * changes. Sets proposed link state in slaves to specify what action 2764 + * should take place for the slave. Returns 0 if no changes are found, >0 2765 + * if changes to link states must be committed. 2765 2766 * 2766 2767 * Called with rcu_read_lock held. 2767 2768 */ ··· 2773 2774 int commit = 0; 2774 2775 2775 2776 bond_for_each_slave_rcu(bond, slave, iter) { 2776 - slave->new_link = BOND_LINK_NOCHANGE; 2777 + bond_propose_link_state(slave, BOND_LINK_NOCHANGE); 2777 2778 last_rx = slave_last_rx(bond, slave); 2778 2779 2779 2780 if (slave->link != BOND_LINK_UP) { 2780 2781 if (bond_time_in_interval(bond, last_rx, 1)) { 2781 - slave->new_link = BOND_LINK_UP; 2782 + bond_propose_link_state(slave, BOND_LINK_UP); 2782 2783 commit++; 2783 2784 } 2784 2785 continue; ··· 2806 2807 if (!bond_is_active_slave(slave) && 2807 2808 !rcu_access_pointer(bond->current_arp_slave) && 2808 2809 !bond_time_in_interval(bond, last_rx, 3)) { 2809 - slave->new_link = BOND_LINK_DOWN; 2810 + bond_propose_link_state(slave, BOND_LINK_DOWN); 2810 2811 commit++; 2811 2812 } 2812 2813 ··· 2819 2820 if (bond_is_active_slave(slave) && 2820 2821 (!bond_time_in_interval(bond, trans_start, 2) || 2821 2822 !bond_time_in_interval(bond, last_rx, 2))) { 2822 - slave->new_link = BOND_LINK_DOWN; 2823 + bond_propose_link_state(slave, BOND_LINK_DOWN); 2823 2824 commit++; 2824 2825 } 2825 2826 } ··· 2839 2840 struct slave *slave; 2840 2841 2841 2842 bond_for_each_slave(bond, slave, iter) { 2842 - switch (slave->new_link) { 2843 + switch (slave->link_new_state) { 2843 2844 case BOND_LINK_NOCHANGE: 2844 2845 continue; 2845 2846 ··· 2889 2890 continue; 2890 2891 2891 2892 default: 2892 - slave_err(bond->dev, slave->dev, "impossible: new_link %d on slave\n", 2893 - slave->new_link); 2893 + slave_err(bond->dev, slave->dev, 2894 + "impossible: link_new_state %d on slave\n", 2895 + slave->link_new_state); 2894 2896 continue; 2895 2897 } 2896 2898
+1 -2
include/net/bonding.h
··· 159 159 unsigned long target_last_arp_rx[BOND_MAX_ARP_TARGETS]; 160 160 s8 link; /* one of BOND_LINK_XXXX */ 161 161 s8 link_new_state; /* one of BOND_LINK_XXXX */ 162 - s8 new_link; 163 162 u8 backup:1, /* indicates backup slave. Value corresponds with 164 163 BOND_STATE_ACTIVE and BOND_STATE_BACKUP */ 165 164 inactive:1, /* indicates inactive slave */ ··· 548 549 549 550 static inline void bond_commit_link_state(struct slave *slave, bool notify) 550 551 { 551 - if (slave->link == slave->link_new_state) 552 + if (slave->link_new_state == BOND_LINK_NOCHANGE) 552 553 return; 553 554 554 555 slave->link = slave->link_new_state;