From 034dd340b08be1f2f0477ad16131d609f9dbd53c Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 30 Aug 2026 09:22:00 -0700 Subject: Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure --- drivers/net/bonding/Makefile | 12 + drivers/net/bonding/bond_3ad.c | 3057 +++++++++++++++ drivers/net/bonding/bond_alb.c | 1837 +++++++++ drivers/net/bonding/bond_debugfs.c | 116 + drivers/net/bonding/bond_main.c | 6676 ++++++++++++++++++++++++++++++++ drivers/net/bonding/bond_netlink.c | 1018 +++++ drivers/net/bonding/bond_options.c | 1941 ++++++++++ drivers/net/bonding/bond_procfs.c | 340 ++ drivers/net/bonding/bond_sysfs.c | 864 +++++ drivers/net/bonding/bond_sysfs_slave.c | 145 + 10 files changed, 16006 insertions(+) create mode 100644 drivers/net/bonding/Makefile create mode 100644 drivers/net/bonding/bond_3ad.c create mode 100644 drivers/net/bonding/bond_alb.c create mode 100644 drivers/net/bonding/bond_debugfs.c create mode 100644 drivers/net/bonding/bond_main.c create mode 100644 drivers/net/bonding/bond_netlink.c create mode 100644 drivers/net/bonding/bond_options.c create mode 100644 drivers/net/bonding/bond_procfs.c create mode 100644 drivers/net/bonding/bond_sysfs.c create mode 100644 drivers/net/bonding/bond_sysfs_slave.c (limited to 'drivers/net/bonding') diff --git a/drivers/net/bonding/Makefile b/drivers/net/bonding/Makefile new file mode 100644 index 000000000..30e8ae3da --- /dev/null +++ b/drivers/net/bonding/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the Ethernet Bonding driver +# + +obj-$(CONFIG_BONDING) += bonding.o + +bonding-objs := bond_main.o bond_3ad.o bond_alb.o bond_sysfs.o bond_sysfs_slave.o bond_debugfs.o bond_netlink.o bond_options.o + +proc-$(CONFIG_PROC_FS) += bond_procfs.o +bonding-objs += $(proc-y) + diff --git a/drivers/net/bonding/bond_3ad.c b/drivers/net/bonding/bond_3ad.c new file mode 100644 index 000000000..b8e4b4d68 --- /dev/null +++ b/drivers/net/bonding/bond_3ad.c @@ -0,0 +1,3057 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 1999 - 2004 Intel Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* General definitions */ +#define AD_SHORT_TIMEOUT 1 +#define AD_LONG_TIMEOUT 0 +#define AD_STANDBY 0x2 +#define AD_MAX_TX_IN_SECOND 3 +#define AD_COLLECTOR_MAX_DELAY 0 + +/* Timer definitions (43.4.4 in the 802.3ad standard) */ +#define AD_FAST_PERIODIC_TIME 1 +#define AD_SLOW_PERIODIC_TIME 30 +#define AD_SHORT_TIMEOUT_TIME (3*AD_FAST_PERIODIC_TIME) +#define AD_LONG_TIMEOUT_TIME (3*AD_SLOW_PERIODIC_TIME) +#define AD_CHURN_DETECTION_TIME 60 +#define AD_AGGREGATE_WAIT_TIME 2 + +/* Port Variables definitions used by the State Machines (43.4.7 in the + * 802.3ad standard) + */ +#define AD_PORT_BEGIN 0x1 +#define AD_PORT_LACP_ENABLED 0x2 +#define AD_PORT_ACTOR_CHURN 0x4 +#define AD_PORT_PARTNER_CHURN 0x8 +#define AD_PORT_READY 0x10 +#define AD_PORT_READY_N 0x20 +#define AD_PORT_MATCHED 0x40 +#define AD_PORT_STANDBY 0x80 +#define AD_PORT_SELECTED 0x100 +#define AD_PORT_MOVED 0x200 +#define AD_PORT_CHURNED (AD_PORT_ACTOR_CHURN | AD_PORT_PARTNER_CHURN) + +/* Port Key definitions + * key is determined according to the link speed, duplex and + * user key (which is yet not supported) + * -------------------------------------------------------------- + * Port key | User key (10 bits) | Speed (5 bits) | Duplex| + * -------------------------------------------------------------- + * |15 6|5 1|0 + */ +#define AD_DUPLEX_KEY_MASKS 0x1 +#define AD_SPEED_KEY_MASKS 0x3E +#define AD_USER_KEY_MASKS 0xFFC0 + +enum ad_link_speed_type { + AD_LINK_SPEED_1MBPS = 1, + AD_LINK_SPEED_10MBPS, + AD_LINK_SPEED_100MBPS, + AD_LINK_SPEED_1000MBPS, + AD_LINK_SPEED_2500MBPS, + AD_LINK_SPEED_5000MBPS, + AD_LINK_SPEED_10000MBPS, + AD_LINK_SPEED_14000MBPS, + AD_LINK_SPEED_20000MBPS, + AD_LINK_SPEED_25000MBPS, + AD_LINK_SPEED_40000MBPS, + AD_LINK_SPEED_50000MBPS, + AD_LINK_SPEED_56000MBPS, + AD_LINK_SPEED_80000MBPS, + AD_LINK_SPEED_100000MBPS, + AD_LINK_SPEED_200000MBPS, + AD_LINK_SPEED_400000MBPS, + AD_LINK_SPEED_800000MBPS, + AD_LINK_SPEED_1600000MBPS, +}; + +/* compare MAC addresses */ +#define MAC_ADDRESS_EQUAL(A, B) \ + ether_addr_equal_64bits((const u8 *)A, (const u8 *)B) + +static const u16 ad_ticks_per_sec = 1000 / AD_TIMER_INTERVAL; +static const int ad_delta_in_ticks = (AD_TIMER_INTERVAL * HZ) / 1000; + +const u8 lacpdu_mcast_addr[ETH_ALEN + 2] __long_aligned = { + 0x01, 0x80, 0xC2, 0x00, 0x00, 0x02 +}; + +/* ================= main 802.3ad protocol functions ================== */ +static int ad_lacpdu_send(struct port *port); +static int ad_marker_send(struct port *port, struct bond_marker *marker); +static void ad_mux_machine(struct port *port, bool *update_slave_arr); +static void ad_rx_machine(struct lacpdu *lacpdu, struct port *port); +static void ad_tx_machine(struct port *port); +static void ad_periodic_machine(struct port *port); +static void ad_port_selection_logic(struct port *port, bool *update_slave_arr); +static void ad_agg_selection_logic(struct aggregator *aggregator, + bool *update_slave_arr); +static void ad_clear_agg(struct aggregator *aggregator); +static void ad_initialize_agg(struct aggregator *aggregator); +static void ad_initialize_port(struct port *port, const struct bond_params *bond_params); +static void ad_enable_collecting(struct port *port); +static void ad_disable_distributing(struct port *port, + bool *update_slave_arr); +static void ad_enable_collecting_distributing(struct port *port, + bool *update_slave_arr); +static void ad_disable_collecting_distributing(struct port *port, + bool *update_slave_arr); +static void ad_marker_info_received(struct bond_marker *marker_info, + struct port *port); +static void ad_marker_response_received(struct bond_marker *marker, + struct port *port); +static void ad_update_actor_keys(struct port *port, bool reset); + + +/* ================= api to bonding and kernel code ================== */ + +/** + * __get_bond_by_port - get the port's bonding struct + * @port: the port we're looking at + * + * Return @port's bonding struct, or %NULL if it can't be found. + */ +static inline struct bonding *__get_bond_by_port(struct port *port) +{ + if (port->slave == NULL) + return NULL; + + return bond_get_bond_by_slave(port->slave); +} + +/** + * __get_first_agg - get the first aggregator in the bond + * @port: the port we're looking at + * + * Return the aggregator of the first slave in @bond, or %NULL if it can't be + * found. + * The caller must hold RCU or RTNL lock. + */ +static inline struct aggregator *__get_first_agg(struct port *port) +{ + struct bonding *bond = __get_bond_by_port(port); + struct slave *first_slave; + struct aggregator *agg; + + /* If there's no bond for this port, or bond has no slaves */ + if (bond == NULL) + return NULL; + + rcu_read_lock(); + first_slave = bond_first_slave_rcu(bond); + agg = first_slave ? &(SLAVE_AD_INFO(first_slave)->aggregator) : NULL; + rcu_read_unlock(); + + return agg; +} + +/** + * __agg_has_partner - see if we have a partner + * @agg: the agregator we're looking at + * + * Return nonzero if aggregator has a partner (denoted by a non-zero ether + * address for the partner). Return 0 if not. + */ +static inline int __agg_has_partner(struct aggregator *agg) +{ + return !is_zero_ether_addr(agg->partner_system.mac_addr_value); +} + +/** + * __disable_distributing_port - disable the port's slave for distributing. + * Port will still be able to collect. + * @port: the port we're looking at + * + * This will disable only distributing on the port's slave. + */ +static void __disable_distributing_port(struct port *port) +{ + bond_set_slave_tx_disabled_flags(port->slave, BOND_SLAVE_NOTIFY_LATER); +} + +/** + * __enable_collecting_port - enable the port's slave for collecting, + * if it's up + * @port: the port we're looking at + * + * This will enable only collecting on the port's slave. + */ +static void __enable_collecting_port(struct port *port) +{ + struct slave *slave = port->slave; + + if (slave->link == BOND_LINK_UP && bond_slave_is_up(slave)) + bond_set_slave_rx_enabled_flags(slave, BOND_SLAVE_NOTIFY_LATER); +} + +/** + * __disable_port - disable the port's slave + * @port: the port we're looking at + * + * This will disable both collecting and distributing on the port's slave. + */ +static inline void __disable_port(struct port *port) +{ + bond_set_slave_inactive_flags(port->slave, BOND_SLAVE_NOTIFY_LATER); +} + +/** + * __enable_port - enable the port's slave, if it's up + * @port: the port we're looking at + * + * This will enable both collecting and distributing on the port's slave. + */ +static inline void __enable_port(struct port *port) +{ + struct slave *slave = port->slave; + + if ((slave->link == BOND_LINK_UP) && bond_slave_is_up(slave)) + bond_set_slave_active_flags(slave, BOND_SLAVE_NOTIFY_LATER); +} + +/** + * __port_move_to_attached_state - check if port should transition back to attached + * state. + * @port: the port we're looking at + */ +static bool __port_move_to_attached_state(struct port *port) +{ + if (!(port->sm_vars & AD_PORT_SELECTED) || + (port->sm_vars & AD_PORT_STANDBY) || + !(port->partner_oper.port_state & LACP_STATE_SYNCHRONIZATION) || + !(port->actor_oper_port_state & LACP_STATE_SYNCHRONIZATION)) + port->sm_mux_state = AD_MUX_ATTACHED; + + return port->sm_mux_state == AD_MUX_ATTACHED; +} + +/** + * __port_is_collecting_distributing - check if the port's slave is in the + * combined collecting/distributing state + * @port: the port we're looking at + */ +static int __port_is_collecting_distributing(struct port *port) +{ + return bond_is_active_slave(port->slave); +} + +/** + * __get_agg_selection_mode - get the aggregator selection mode + * @port: the port we're looking at + * + * Get the aggregator selection mode. Can be %STABLE, %BANDWIDTH or %COUNT. + */ +static inline u32 __get_agg_selection_mode(struct port *port) +{ + struct bonding *bond = __get_bond_by_port(port); + + if (bond == NULL) + return BOND_AD_STABLE; + + return bond->params.ad_select; +} + +/** + * __check_agg_selection_timer - check if the selection timer has expired + * @port: the port we're looking at + */ +static inline int __check_agg_selection_timer(struct port *port) +{ + struct bonding *bond = __get_bond_by_port(port); + + if (bond == NULL) + return 0; + + return atomic_read(&BOND_AD_INFO(bond).agg_select_timer) ? 1 : 0; +} + +/** + * __get_link_speed - get a port's speed + * @port: the port we're looking at + * + * Return @port's speed in 802.3ad enum format. i.e. one of: + * 0, + * %AD_LINK_SPEED_10MBPS, + * %AD_LINK_SPEED_100MBPS, + * %AD_LINK_SPEED_1000MBPS, + * %AD_LINK_SPEED_2500MBPS, + * %AD_LINK_SPEED_5000MBPS, + * %AD_LINK_SPEED_10000MBPS + * %AD_LINK_SPEED_14000MBPS, + * %AD_LINK_SPEED_20000MBPS + * %AD_LINK_SPEED_25000MBPS + * %AD_LINK_SPEED_40000MBPS + * %AD_LINK_SPEED_50000MBPS + * %AD_LINK_SPEED_56000MBPS + * %AD_LINK_SPEED_80000MBPS + * %AD_LINK_SPEED_100000MBPS + * %AD_LINK_SPEED_200000MBPS + * %AD_LINK_SPEED_400000MBPS + * %AD_LINK_SPEED_800000MBPS + * %AD_LINK_SPEED_1600000MBPS + */ +static u16 __get_link_speed(struct port *port) +{ + struct slave *slave = port->slave; + u16 speed; + + /* this if covers only a special case: when the configuration starts + * with link down, it sets the speed to 0. + * This is done in spite of the fact that the e100 driver reports 0 + * to be compatible with MVT in the future. + */ + if (slave->link != BOND_LINK_UP) + speed = 0; + else { + switch (slave->speed) { + case SPEED_10: + speed = AD_LINK_SPEED_10MBPS; + break; + + case SPEED_100: + speed = AD_LINK_SPEED_100MBPS; + break; + + case SPEED_1000: + speed = AD_LINK_SPEED_1000MBPS; + break; + + case SPEED_2500: + speed = AD_LINK_SPEED_2500MBPS; + break; + + case SPEED_5000: + speed = AD_LINK_SPEED_5000MBPS; + break; + + case SPEED_10000: + speed = AD_LINK_SPEED_10000MBPS; + break; + + case SPEED_14000: + speed = AD_LINK_SPEED_14000MBPS; + break; + + case SPEED_20000: + speed = AD_LINK_SPEED_20000MBPS; + break; + + case SPEED_25000: + speed = AD_LINK_SPEED_25000MBPS; + break; + + case SPEED_40000: + speed = AD_LINK_SPEED_40000MBPS; + break; + + case SPEED_50000: + speed = AD_LINK_SPEED_50000MBPS; + break; + + case SPEED_56000: + speed = AD_LINK_SPEED_56000MBPS; + break; + + case SPEED_80000: + speed = AD_LINK_SPEED_80000MBPS; + break; + + case SPEED_100000: + speed = AD_LINK_SPEED_100000MBPS; + break; + + case SPEED_200000: + speed = AD_LINK_SPEED_200000MBPS; + break; + + case SPEED_400000: + speed = AD_LINK_SPEED_400000MBPS; + break; + + case SPEED_800000: + speed = AD_LINK_SPEED_800000MBPS; + break; + + case SPEED_1600000: + speed = AD_LINK_SPEED_1600000MBPS; + break; + + default: + /* unknown speed value from ethtool. shouldn't happen */ + if (slave->speed != SPEED_UNKNOWN) + pr_err_once("%s: (slave %s): unknown ethtool speed (%d) for port %d (set it to 0)\n", + slave->bond->dev->name, + slave->dev->name, slave->speed, + port->actor_port_number); + speed = 0; + break; + } + } + + slave_dbg(slave->bond->dev, slave->dev, "Port %d Received link speed %d update from adapter\n", + port->actor_port_number, speed); + return speed; +} + +/** + * __get_duplex - get a port's duplex + * @port: the port we're looking at + * + * Return @port's duplex in 802.3ad bitmask format. i.e.: + * 0x01 if in full duplex + * 0x00 otherwise + */ +static u8 __get_duplex(struct port *port) +{ + struct slave *slave = port->slave; + u8 retval = 0x0; + + /* handling a special case: when the configuration starts with + * link down, it sets the duplex to 0. + */ + if (slave->link == BOND_LINK_UP) { + switch (slave->duplex) { + case DUPLEX_FULL: + retval = 0x1; + slave_dbg(slave->bond->dev, slave->dev, "Port %d Received status full duplex update from adapter\n", + port->actor_port_number); + break; + case DUPLEX_HALF: + default: + retval = 0x0; + slave_dbg(slave->bond->dev, slave->dev, "Port %d Received status NOT full duplex update from adapter\n", + port->actor_port_number); + break; + } + } + return retval; +} + +static void __ad_actor_update_port(struct port *port) +{ + const struct bonding *bond = bond_get_bond_by_slave(port->slave); + + port->actor_system = BOND_AD_INFO(bond).system.sys_mac_addr; + port->actor_system_priority = BOND_AD_INFO(bond).system.sys_priority; + port->actor_port_priority = SLAVE_AD_INFO(port->slave)->port_priority; +} + +/* Conversions */ + +/** + * __ad_timer_to_ticks - convert a given timer type to AD module ticks + * @timer_type: which timer to operate + * @par: timer parameter. see below + * + * If @timer_type is %current_while_timer, @par indicates long/short timer. + * If @timer_type is %periodic_timer, @par is one of %FAST_PERIODIC_TIME, + * %SLOW_PERIODIC_TIME. + */ +static u16 __ad_timer_to_ticks(u16 timer_type, u16 par) +{ + u16 retval = 0; /* to silence the compiler */ + + switch (timer_type) { + case AD_CURRENT_WHILE_TIMER: /* for rx machine usage */ + if (par) + retval = (AD_SHORT_TIMEOUT_TIME*ad_ticks_per_sec); + else + retval = (AD_LONG_TIMEOUT_TIME*ad_ticks_per_sec); + break; + case AD_ACTOR_CHURN_TIMER: /* for local churn machine */ + retval = (AD_CHURN_DETECTION_TIME*ad_ticks_per_sec); + break; + case AD_PERIODIC_TIMER: /* for periodic machine */ + retval = (par*ad_ticks_per_sec); /* long timeout */ + break; + case AD_PARTNER_CHURN_TIMER: /* for remote churn machine */ + retval = (AD_CHURN_DETECTION_TIME*ad_ticks_per_sec); + break; + case AD_WAIT_WHILE_TIMER: /* for selection machine */ + retval = (AD_AGGREGATE_WAIT_TIME*ad_ticks_per_sec); + break; + } + + return retval; +} + + +/* ================= ad_rx_machine helper functions ================== */ + +/** + * __choose_matched - update a port's matched variable from a received lacpdu + * @lacpdu: the lacpdu we've received + * @port: the port we're looking at + * + * Update the value of the matched variable, using parameter values from a + * newly received lacpdu. Parameter values for the partner carried in the + * received PDU are compared with the corresponding operational parameter + * values for the actor. Matched is set to TRUE if all of these parameters + * match and the PDU parameter partner_state.aggregation has the same value as + * actor_oper_port_state.aggregation and lacp will actively maintain the link + * in the aggregation. Matched is also set to TRUE if the value of + * actor_state.aggregation in the received PDU is set to FALSE, i.e., indicates + * an individual link and lacp will actively maintain the link. Otherwise, + * matched is set to FALSE. LACP is considered to be actively maintaining the + * link if either the PDU's actor_state.lacp_activity variable is TRUE or both + * the actor's actor_oper_port_state.lacp_activity and the PDU's + * partner_state.lacp_activity variables are TRUE. + * + * Note: the AD_PORT_MATCHED "variable" is not specified by 802.3ad; it is + * used here to implement the language from 802.3ad 43.4.9 that requires + * recordPDU to "match" the LACPDU parameters to the stored values. + */ +static void __choose_matched(struct lacpdu *lacpdu, struct port *port) +{ + /* check if all parameters are alike + * or this is individual link(aggregation == FALSE) + * then update the state machine Matched variable. + */ + if (((ntohs(lacpdu->partner_port) == port->actor_port_number) && + (ntohs(lacpdu->partner_port_priority) == port->actor_port_priority) && + MAC_ADDRESS_EQUAL(&(lacpdu->partner_system), &(port->actor_system)) && + (ntohs(lacpdu->partner_system_priority) == port->actor_system_priority) && + (ntohs(lacpdu->partner_key) == port->actor_oper_port_key) && + ((lacpdu->partner_state & LACP_STATE_AGGREGATION) == (port->actor_oper_port_state & LACP_STATE_AGGREGATION))) || + ((lacpdu->actor_state & LACP_STATE_AGGREGATION) == 0) + ) { + port->sm_vars |= AD_PORT_MATCHED; + } else { + port->sm_vars &= ~AD_PORT_MATCHED; + } +} + +/** + * __record_pdu - record parameters from a received lacpdu + * @lacpdu: the lacpdu we've received + * @port: the port we're looking at + * + * Record the parameter values for the Actor carried in a received lacpdu as + * the current partner operational parameter values and sets + * actor_oper_port_state.defaulted to FALSE. + */ +static void __record_pdu(struct lacpdu *lacpdu, struct port *port) +{ + if (lacpdu && port) { + struct port_params *partner = &port->partner_oper; + + __choose_matched(lacpdu, port); + /* record the new parameter values for the partner + * operational + */ + partner->port_number = ntohs(lacpdu->actor_port); + partner->port_priority = ntohs(lacpdu->actor_port_priority); + partner->system = lacpdu->actor_system; + partner->system_priority = ntohs(lacpdu->actor_system_priority); + partner->key = ntohs(lacpdu->actor_key); + partner->port_state = lacpdu->actor_state; + + /* set actor_oper_port_state.defaulted to FALSE */ + port->actor_oper_port_state &= ~LACP_STATE_DEFAULTED; + + /* set the partner sync. to on if the partner is sync, + * and the port is matched + */ + if ((port->sm_vars & AD_PORT_MATCHED) && + (lacpdu->actor_state & LACP_STATE_SYNCHRONIZATION)) { + partner->port_state |= LACP_STATE_SYNCHRONIZATION; + slave_dbg(port->slave->bond->dev, port->slave->dev, + "partner sync=1\n"); + } else { + partner->port_state &= ~LACP_STATE_SYNCHRONIZATION; + slave_dbg(port->slave->bond->dev, port->slave->dev, + "partner sync=0\n"); + } + } +} + +/** + * __record_default - record default parameters + * @port: the port we're looking at + * + * This function records the default parameter values for the partner carried + * in the Partner Admin parameters as the current partner operational parameter + * values and sets actor_oper_port_state.defaulted to TRUE. + */ +static void __record_default(struct port *port) +{ + if (port) { + /* record the partner admin parameters */ + memcpy(&port->partner_oper, &port->partner_admin, + sizeof(struct port_params)); + + /* set actor_oper_port_state.defaulted to true */ + port->actor_oper_port_state |= LACP_STATE_DEFAULTED; + } +} + +/** + * __update_selected - update a port's Selected variable from a received lacpdu + * @lacpdu: the lacpdu we've received + * @port: the port we're looking at + * + * Update the value of the selected variable, using parameter values from a + * newly received lacpdu. The parameter values for the Actor carried in the + * received PDU are compared with the corresponding operational parameter + * values for the ports partner. If one or more of the comparisons shows that + * the value(s) received in the PDU differ from the current operational values, + * then selected is set to FALSE and actor_oper_port_state.synchronization is + * set to out_of_sync. Otherwise, selected remains unchanged. + */ +static void __update_selected(struct lacpdu *lacpdu, struct port *port) +{ + if (lacpdu && port) { + const struct port_params *partner = &port->partner_oper; + + /* check if any parameter is different then + * update the state machine selected variable. + */ + if (ntohs(lacpdu->actor_port) != partner->port_number || + ntohs(lacpdu->actor_port_priority) != partner->port_priority || + !MAC_ADDRESS_EQUAL(&lacpdu->actor_system, &partner->system) || + ntohs(lacpdu->actor_system_priority) != partner->system_priority || + ntohs(lacpdu->actor_key) != partner->key || + (lacpdu->actor_state & LACP_STATE_AGGREGATION) != (partner->port_state & LACP_STATE_AGGREGATION)) { + port->sm_vars &= ~AD_PORT_SELECTED; + } + } +} + +/** + * __update_default_selected - update a port's Selected variable from Partner + * @port: the port we're looking at + * + * This function updates the value of the selected variable, using the partner + * administrative parameter values. The administrative values are compared with + * the corresponding operational parameter values for the partner. If one or + * more of the comparisons shows that the administrative value(s) differ from + * the current operational values, then Selected is set to FALSE and + * actor_oper_port_state.synchronization is set to OUT_OF_SYNC. Otherwise, + * Selected remains unchanged. + */ +static void __update_default_selected(struct port *port) +{ + if (port) { + const struct port_params *admin = &port->partner_admin; + const struct port_params *oper = &port->partner_oper; + + /* check if any parameter is different then + * update the state machine selected variable. + */ + if (admin->port_number != oper->port_number || + admin->port_priority != oper->port_priority || + !MAC_ADDRESS_EQUAL(&admin->system, &oper->system) || + admin->system_priority != oper->system_priority || + admin->key != oper->key || + (admin->port_state & LACP_STATE_AGGREGATION) + != (oper->port_state & LACP_STATE_AGGREGATION)) { + port->sm_vars &= ~AD_PORT_SELECTED; + } + } +} + +/** + * __update_ntt - update a port's ntt variable from a received lacpdu + * @lacpdu: the lacpdu we've received + * @port: the port we're looking at + * + * Updates the value of the ntt variable, using parameter values from a newly + * received lacpdu. The parameter values for the partner carried in the + * received PDU are compared with the corresponding operational parameter + * values for the Actor. If one or more of the comparisons shows that the + * value(s) received in the PDU differ from the current operational values, + * then ntt is set to TRUE. Otherwise, ntt remains unchanged. + */ +static void __update_ntt(struct lacpdu *lacpdu, struct port *port) +{ + /* validate lacpdu and port */ + if (lacpdu && port) { + /* check if any parameter is different then + * update the port->ntt. + */ + if ((ntohs(lacpdu->partner_port) != port->actor_port_number) || + (ntohs(lacpdu->partner_port_priority) != port->actor_port_priority) || + !MAC_ADDRESS_EQUAL(&(lacpdu->partner_system), &(port->actor_system)) || + (ntohs(lacpdu->partner_system_priority) != port->actor_system_priority) || + (ntohs(lacpdu->partner_key) != port->actor_oper_port_key) || + ((lacpdu->partner_state & LACP_STATE_LACP_ACTIVITY) != (port->actor_oper_port_state & LACP_STATE_LACP_ACTIVITY)) || + ((lacpdu->partner_state & LACP_STATE_LACP_TIMEOUT) != (port->actor_oper_port_state & LACP_STATE_LACP_TIMEOUT)) || + ((lacpdu->partner_state & LACP_STATE_SYNCHRONIZATION) != (port->actor_oper_port_state & LACP_STATE_SYNCHRONIZATION)) || + ((lacpdu->partner_state & LACP_STATE_AGGREGATION) != (port->actor_oper_port_state & LACP_STATE_AGGREGATION)) + ) { + port->ntt = true; + } + } +} + +/** + * __agg_ports_are_ready - check if all ports in an aggregator are ready + * @aggregator: the aggregator we're looking at + * + */ +static int __agg_ports_are_ready(struct aggregator *aggregator) +{ + struct port *port; + int retval = 1; + + if (aggregator) { + /* scan all ports in this aggregator to verfy if they are + * all ready. + */ + for (port = aggregator->lag_ports; + port; + port = port->next_port_in_aggregator) { + if (!(port->sm_vars & AD_PORT_READY_N)) { + retval = 0; + break; + } + } + } + + return retval; +} + +/** + * __set_agg_ports_ready - set value of Ready bit in all ports of an aggregator + * @aggregator: the aggregator we're looking at + * @val: Should the ports' ready bit be set on or off + * + */ +static void __set_agg_ports_ready(struct aggregator *aggregator, int val) +{ + struct port *port; + + for (port = aggregator->lag_ports; port; + port = port->next_port_in_aggregator) { + if (val) + port->sm_vars |= AD_PORT_READY; + else + port->sm_vars &= ~AD_PORT_READY; + } +} + +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(const struct aggregator *agg) +{ + const struct port *port; + int active = 0; + + for (port = agg->lag_ports; port; + port = port->next_port_in_aggregator) { + if (READ_ONCE(port->is_enabled)) + active++; + } + + return active; +} + +static unsigned int __agg_ports_priority(const struct aggregator *agg) +{ + struct port *port = agg->lag_ports; + unsigned int prio = 0; + + for (; port; port = port->next_port_in_aggregator) + if (port->is_enabled) + prio += port->actor_port_priority; + + return prio; +} + +/** + * __get_agg_bandwidth - get the total bandwidth of an aggregator + * @aggregator: the aggregator we're looking at + * + */ +static u32 __get_agg_bandwidth(struct aggregator *aggregator) +{ + int nports = __agg_active_ports(aggregator); + u32 bandwidth = 0; + + if (nports) { + switch (__get_link_speed(aggregator->lag_ports)) { + case AD_LINK_SPEED_1MBPS: + bandwidth = nports; + break; + case AD_LINK_SPEED_10MBPS: + bandwidth = nports * 10; + break; + case AD_LINK_SPEED_100MBPS: + bandwidth = nports * 100; + break; + case AD_LINK_SPEED_1000MBPS: + bandwidth = nports * 1000; + break; + case AD_LINK_SPEED_2500MBPS: + bandwidth = nports * 2500; + break; + case AD_LINK_SPEED_5000MBPS: + bandwidth = nports * 5000; + break; + case AD_LINK_SPEED_10000MBPS: + bandwidth = nports * 10000; + break; + case AD_LINK_SPEED_14000MBPS: + bandwidth = nports * 14000; + break; + case AD_LINK_SPEED_20000MBPS: + bandwidth = nports * 20000; + break; + case AD_LINK_SPEED_25000MBPS: + bandwidth = nports * 25000; + break; + case AD_LINK_SPEED_40000MBPS: + bandwidth = nports * 40000; + break; + case AD_LINK_SPEED_50000MBPS: + bandwidth = nports * 50000; + break; + case AD_LINK_SPEED_56000MBPS: + bandwidth = nports * 56000; + break; + case AD_LINK_SPEED_80000MBPS: + bandwidth = nports * 80000; + break; + case AD_LINK_SPEED_100000MBPS: + bandwidth = nports * 100000; + break; + case AD_LINK_SPEED_200000MBPS: + bandwidth = nports * 200000; + break; + case AD_LINK_SPEED_400000MBPS: + bandwidth = nports * 400000; + break; + case AD_LINK_SPEED_800000MBPS: + bandwidth = nports * 800000; + break; + case AD_LINK_SPEED_1600000MBPS: + bandwidth = nports * 1600000; + break; + default: + bandwidth = 0; /* to silence the compiler */ + } + } + return bandwidth; +} + +/** + * __get_active_agg - get the current active aggregator + * @aggregator: the aggregator we're looking at + * + * Caller must hold RCU lock. + */ +static struct aggregator *__get_active_agg(struct aggregator *aggregator) +{ + struct bonding *bond = aggregator->slave->bond; + struct list_head *iter; + struct slave *slave; + + bond_for_each_slave_rcu(bond, slave, iter) + if (SLAVE_AD_INFO(slave)->aggregator.is_active) + return &(SLAVE_AD_INFO(slave)->aggregator); + + return NULL; +} + +/** + * __update_lacpdu_from_port - update a port's lacpdu fields + * @port: the port we're looking at + */ +static inline void __update_lacpdu_from_port(struct port *port) +{ + struct lacpdu *lacpdu = &port->lacpdu; + const struct port_params *partner = &port->partner_oper; + + /* update current actual Actor parameters + * lacpdu->subtype initialized + * lacpdu->version_number initialized + * lacpdu->tlv_type_actor_info initialized + * lacpdu->actor_information_length initialized + */ + + lacpdu->actor_system_priority = htons(port->actor_system_priority); + lacpdu->actor_system = port->actor_system; + lacpdu->actor_key = htons(port->actor_oper_port_key); + lacpdu->actor_port_priority = htons(port->actor_port_priority); + lacpdu->actor_port = htons(port->actor_port_number); + lacpdu->actor_state = port->actor_oper_port_state; + slave_dbg(port->slave->bond->dev, port->slave->dev, + "update lacpdu: actor port state %x\n", + port->actor_oper_port_state); + + /* lacpdu->reserved_3_1 initialized + * lacpdu->tlv_type_partner_info initialized + * lacpdu->partner_information_length initialized + */ + + lacpdu->partner_system_priority = htons(partner->system_priority); + lacpdu->partner_system = partner->system; + lacpdu->partner_key = htons(partner->key); + lacpdu->partner_port_priority = htons(partner->port_priority); + lacpdu->partner_port = htons(partner->port_number); + lacpdu->partner_state = partner->port_state; + + /* lacpdu->reserved_3_2 initialized + * lacpdu->tlv_type_collector_info initialized + * lacpdu->collector_information_length initialized + * collector_max_delay initialized + * reserved_12[12] initialized + * tlv_type_terminator initialized + * terminator_length initialized + * reserved_50[50] initialized + */ +} + +/* ================= main 802.3ad protocol code ========================= */ + +/** + * ad_lacpdu_send - send out a lacpdu packet on a given port + * @port: the port we're looking at + * + * Returns: 0 on success + * < 0 on error + */ +static int ad_lacpdu_send(struct port *port) +{ + struct slave *slave = port->slave; + struct sk_buff *skb; + struct lacpdu_header *lacpdu_header; + int length = sizeof(struct lacpdu_header); + + skb = dev_alloc_skb(length); + if (!skb) + return -ENOMEM; + + atomic64_inc(&SLAVE_AD_INFO(slave)->stats.lacpdu_tx); + atomic64_inc(&BOND_AD_INFO(slave->bond).stats.lacpdu_tx); + + skb->dev = slave->dev; + skb_reset_mac_header(skb); + skb->network_header = skb->mac_header + ETH_HLEN; + skb->protocol = PKT_TYPE_LACPDU; + skb->priority = TC_PRIO_CONTROL; + + lacpdu_header = skb_put(skb, length); + + ether_addr_copy(lacpdu_header->hdr.h_dest, lacpdu_mcast_addr); + /* Note: source address is set to be the member's PERMANENT address, + * because we use it to identify loopback lacpdus in receive. + */ + ether_addr_copy(lacpdu_header->hdr.h_source, slave->perm_hwaddr); + lacpdu_header->hdr.h_proto = PKT_TYPE_LACPDU; + + lacpdu_header->lacpdu = port->lacpdu; + + dev_queue_xmit(skb); + + return 0; +} + +/** + * ad_marker_send - send marker information/response on a given port + * @port: the port we're looking at + * @marker: marker data to send + * + * Returns: 0 on success + * < 0 on error + */ +static int ad_marker_send(struct port *port, struct bond_marker *marker) +{ + struct slave *slave = port->slave; + struct sk_buff *skb; + struct bond_marker_header *marker_header; + int length = sizeof(struct bond_marker_header); + + skb = dev_alloc_skb(length + 16); + if (!skb) + return -ENOMEM; + + switch (marker->tlv_type) { + case AD_MARKER_INFORMATION_SUBTYPE: + atomic64_inc(&SLAVE_AD_INFO(slave)->stats.marker_tx); + atomic64_inc(&BOND_AD_INFO(slave->bond).stats.marker_tx); + break; + case AD_MARKER_RESPONSE_SUBTYPE: + atomic64_inc(&SLAVE_AD_INFO(slave)->stats.marker_resp_tx); + atomic64_inc(&BOND_AD_INFO(slave->bond).stats.marker_resp_tx); + break; + } + + skb_reserve(skb, 16); + + skb->dev = slave->dev; + skb_reset_mac_header(skb); + skb->network_header = skb->mac_header + ETH_HLEN; + skb->protocol = PKT_TYPE_LACPDU; + + marker_header = skb_put(skb, length); + + ether_addr_copy(marker_header->hdr.h_dest, lacpdu_mcast_addr); + /* Note: source address is set to be the member's PERMANENT address, + * because we use it to identify loopback MARKERs in receive. + */ + ether_addr_copy(marker_header->hdr.h_source, slave->perm_hwaddr); + marker_header->hdr.h_proto = PKT_TYPE_LACPDU; + + marker_header->marker = *marker; + + dev_queue_xmit(skb); + + return 0; +} + +static void ad_cond_set_peer_notif(struct port *port) +{ + struct bonding *bond = port->slave->bond; + + if (bond->params.broadcast_neighbor) + bond_peer_notify_work_rearm(bond, 0); +} + +/** + * ad_mux_machine - handle a port's mux state machine + * @port: the port we're looking at + * @update_slave_arr: Does slave array need update? + */ +static void ad_mux_machine(struct port *port, bool *update_slave_arr) +{ + struct bonding *bond = __get_bond_by_port(port); + struct aggregator *aggregator; + mux_states_t last_state; + + /* keep current State Machine state to compare later if it was + * changed + */ + last_state = port->sm_mux_state; + + aggregator = rcu_dereference(port->aggregator); + if (port->sm_vars & AD_PORT_BEGIN) { + port->sm_mux_state = AD_MUX_DETACHED; + } else { + switch (port->sm_mux_state) { + case AD_MUX_DETACHED: + if ((port->sm_vars & AD_PORT_SELECTED) + || (port->sm_vars & AD_PORT_STANDBY)) + /* if SELECTED or STANDBY */ + port->sm_mux_state = AD_MUX_WAITING; + break; + case AD_MUX_WAITING: + /* if SELECTED == FALSE return to DETACH state */ + if (!(port->sm_vars & AD_PORT_SELECTED)) { + port->sm_vars &= ~AD_PORT_READY_N; + /* in order to withhold the Selection Logic to + * check all ports READY_N value every callback + * cycle to update ready variable, we check + * READY_N and update READY here + */ + __set_agg_ports_ready(aggregator, __agg_ports_are_ready(aggregator)); + port->sm_mux_state = AD_MUX_DETACHED; + break; + } + + /* check if the wait_while_timer expired */ + if (port->sm_mux_timer_counter + && !(--port->sm_mux_timer_counter)) + port->sm_vars |= AD_PORT_READY_N; + + /* in order to withhold the selection logic to check + * all ports READY_N value every callback cycle to + * update ready variable, we check READY_N and update + * READY here + */ + __set_agg_ports_ready(aggregator, __agg_ports_are_ready(aggregator)); + + /* if the wait_while_timer expired, and the port is + * in READY state, move to ATTACHED state + */ + if ((port->sm_vars & AD_PORT_READY) + && !port->sm_mux_timer_counter) + port->sm_mux_state = AD_MUX_ATTACHED; + break; + case AD_MUX_ATTACHED: + /* check also if agg_select_timer expired (so the + * edable port will take place only after this timer) + */ + if ((port->sm_vars & AD_PORT_SELECTED) && + (port->partner_oper.port_state & LACP_STATE_SYNCHRONIZATION) && + !__check_agg_selection_timer(port)) { + if (aggregator->is_active) { + int state = AD_MUX_COLLECTING_DISTRIBUTING; + + if (!bond->params.coupled_control) + state = AD_MUX_COLLECTING; + port->sm_mux_state = state; + } + } else if (!(port->sm_vars & AD_PORT_SELECTED) || + (port->sm_vars & AD_PORT_STANDBY)) { + /* if UNSELECTED or STANDBY */ + port->sm_vars &= ~AD_PORT_READY_N; + /* in order to withhold the selection logic to + * check all ports READY_N value every callback + * cycle to update ready variable, we check + * READY_N and update READY here + */ + __set_agg_ports_ready(aggregator, __agg_ports_are_ready(aggregator)); + port->sm_mux_state = AD_MUX_DETACHED; + } else if (aggregator->is_active) { + port->actor_oper_port_state |= + LACP_STATE_SYNCHRONIZATION; + } + break; + case AD_MUX_COLLECTING_DISTRIBUTING: + if (!__port_move_to_attached_state(port)) { + /* if port state hasn't changed make + * sure that a collecting distributing + * port in an active aggregator is enabled + */ + if (aggregator->is_active && + !__port_is_collecting_distributing(port)) { + __enable_port(port); + *update_slave_arr = true; + } + } + break; + case AD_MUX_COLLECTING: + if (!__port_move_to_attached_state(port)) { + if ((port->sm_vars & AD_PORT_SELECTED) && + (port->partner_oper.port_state & LACP_STATE_SYNCHRONIZATION) && + (port->partner_oper.port_state & LACP_STATE_COLLECTING)) { + port->sm_mux_state = AD_MUX_DISTRIBUTING; + } else { + /* If port state hasn't changed, make sure that a collecting + * port is enabled for an active aggregator. + */ + struct slave *slave = port->slave; + + if (aggregator->is_active && + bond_is_slave_rx_disabled(slave)) { + ad_enable_collecting(port); + *update_slave_arr = true; + } + } + } + break; + case AD_MUX_DISTRIBUTING: + if (!(port->sm_vars & AD_PORT_SELECTED) || + (port->sm_vars & AD_PORT_STANDBY) || + !(port->partner_oper.port_state & LACP_STATE_COLLECTING) || + !(port->partner_oper.port_state & LACP_STATE_SYNCHRONIZATION) || + !(port->actor_oper_port_state & LACP_STATE_SYNCHRONIZATION)) { + port->sm_mux_state = AD_MUX_COLLECTING; + } else { + /* if port state hasn't changed make + * sure that a collecting distributing + * port in an active aggregator is enabled + */ + if (aggregator && + aggregator->is_active && + !__port_is_collecting_distributing(port)) { + __enable_port(port); + *update_slave_arr = true; + } + } + break; + default: + break; + } + } + + /* check if the state machine was changed */ + if (port->sm_mux_state != last_state) { + slave_dbg(port->slave->bond->dev, port->slave->dev, + "Mux Machine: Port=%d, Last State=%d, Curr State=%d\n", + port->actor_port_number, + last_state, + port->sm_mux_state); + switch (port->sm_mux_state) { + case AD_MUX_DETACHED: + port->actor_oper_port_state &= ~LACP_STATE_SYNCHRONIZATION; + 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: + port->sm_mux_timer_counter = __ad_timer_to_ticks(AD_WAIT_WHILE_TIMER, 0); + break; + case AD_MUX_ATTACHED: + if (aggregator->is_active) + port->actor_oper_port_state |= + LACP_STATE_SYNCHRONIZATION; + else + port->actor_oper_port_state &= + ~LACP_STATE_SYNCHRONIZATION; + 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_COLLECTING_DISTRIBUTING: + port->actor_oper_port_state |= LACP_STATE_COLLECTING; + port->actor_oper_port_state |= LACP_STATE_DISTRIBUTING; + port->actor_oper_port_state |= LACP_STATE_SYNCHRONIZATION; + ad_enable_collecting_distributing(port, + update_slave_arr); + port->ntt = true; + break; + case AD_MUX_COLLECTING: + port->actor_oper_port_state |= LACP_STATE_COLLECTING; + port->actor_oper_port_state &= ~LACP_STATE_DISTRIBUTING; + port->actor_oper_port_state |= LACP_STATE_SYNCHRONIZATION; + ad_enable_collecting(port); + ad_disable_distributing(port, update_slave_arr); + port->ntt = true; + break; + case AD_MUX_DISTRIBUTING: + port->actor_oper_port_state |= LACP_STATE_DISTRIBUTING; + port->actor_oper_port_state |= LACP_STATE_SYNCHRONIZATION; + ad_enable_collecting_distributing(port, + update_slave_arr); + break; + default: + break; + } + } +} + +/** + * ad_rx_machine - handle a port's rx State Machine + * @lacpdu: the lacpdu we've received + * @port: the port we're looking at + * + * If lacpdu arrived, stop previous timer (if exists) and set the next state as + * CURRENT. If timer expired set the state machine in the proper state. + * In other cases, this function checks if we need to switch to other state. + */ +static void ad_rx_machine(struct lacpdu *lacpdu, struct port *port) +{ + rx_states_t last_state; + + /* keep current State Machine state to compare later if it was + * changed + */ + last_state = port->sm_rx_state; + + if (lacpdu) { + atomic64_inc(&SLAVE_AD_INFO(port->slave)->stats.lacpdu_rx); + atomic64_inc(&BOND_AD_INFO(port->slave->bond).stats.lacpdu_rx); + } + /* check if state machine should change state */ + + /* first, check if port was reinitialized */ + if (port->sm_vars & AD_PORT_BEGIN) { + port->sm_rx_state = AD_RX_INITIALIZE; + port->sm_vars |= AD_PORT_CHURNED; + /* check if port is not enabled */ + } else if (!(port->sm_vars & AD_PORT_BEGIN) && !port->is_enabled) + port->sm_rx_state = AD_RX_PORT_DISABLED; + /* check if new lacpdu arrived */ + else if (lacpdu && ((port->sm_rx_state == AD_RX_EXPIRED) || + (port->sm_rx_state == AD_RX_DEFAULTED) || + (port->sm_rx_state == AD_RX_CURRENT))) { + if (port->sm_rx_state != AD_RX_CURRENT) + port->sm_vars |= AD_PORT_CHURNED; + port->sm_rx_timer_counter = 0; + port->sm_rx_state = AD_RX_CURRENT; + } else { + /* if timer is on, and if it is expired */ + if (port->sm_rx_timer_counter && + !(--port->sm_rx_timer_counter)) { + switch (port->sm_rx_state) { + case AD_RX_EXPIRED: + port->sm_rx_state = AD_RX_DEFAULTED; + break; + case AD_RX_CURRENT: + port->sm_rx_state = AD_RX_EXPIRED; + break; + default: + break; + } + } else { + /* if no lacpdu arrived and no timer is on */ + switch (port->sm_rx_state) { + case AD_RX_PORT_DISABLED: + if (port->is_enabled && + (port->sm_vars & AD_PORT_LACP_ENABLED)) + port->sm_rx_state = AD_RX_EXPIRED; + else if (port->is_enabled + && ((port->sm_vars + & AD_PORT_LACP_ENABLED) == 0)) + port->sm_rx_state = AD_RX_LACP_DISABLED; + break; + default: + break; + + } + } + } + + /* check if the State machine was changed or new lacpdu arrived */ + if ((port->sm_rx_state != last_state) || (lacpdu)) { + slave_dbg(port->slave->bond->dev, port->slave->dev, + "Rx Machine: Port=%d, Last State=%d, Curr State=%d\n", + port->actor_port_number, + last_state, + port->sm_rx_state); + switch (port->sm_rx_state) { + case AD_RX_INITIALIZE: + if (!(port->actor_oper_port_key & AD_DUPLEX_KEY_MASKS)) + port->sm_vars &= ~AD_PORT_LACP_ENABLED; + else + port->sm_vars |= AD_PORT_LACP_ENABLED; + port->sm_vars &= ~AD_PORT_SELECTED; + __record_default(port); + port->actor_oper_port_state &= ~LACP_STATE_EXPIRED; + port->sm_rx_state = AD_RX_PORT_DISABLED; + + 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; + __record_default(port); + port->partner_oper.port_state &= ~LACP_STATE_AGGREGATION; + port->sm_vars |= AD_PORT_MATCHED; + port->actor_oper_port_state &= ~LACP_STATE_EXPIRED; + break; + case AD_RX_EXPIRED: + /* Reset of the Synchronization flag (Standard 43.4.12) + * This reset cause to disable this port in the + * COLLECTING_DISTRIBUTING state of the mux machine in + * case of EXPIRED even if LINK_DOWN didn't arrive for + * the port. + */ + port->sm_vars &= ~AD_PORT_MATCHED; + /* Based on IEEE 8021AX-2014, Figure 6-18 - Receive + * machine state diagram, the statue should be + * Partner_Oper_Port_State.Synchronization = FALSE; + * Partner_Oper_Port_State.LACP_Timeout = Short Timeout; + * start current_while_timer(Short Timeout); + * Actor_Oper_Port_State.Expired = TRUE; + */ + port->partner_oper.port_state &= ~LACP_STATE_SYNCHRONIZATION; + port->partner_oper.port_state |= LACP_STATE_LACP_TIMEOUT; + port->sm_rx_timer_counter = __ad_timer_to_ticks(AD_CURRENT_WHILE_TIMER, (u16)(AD_SHORT_TIMEOUT)); + port->actor_oper_port_state |= LACP_STATE_EXPIRED; + port->sm_vars |= AD_PORT_CHURNED; + break; + case AD_RX_DEFAULTED: + __update_default_selected(port); + __record_default(port); + port->sm_vars |= AD_PORT_MATCHED; + port->actor_oper_port_state &= ~LACP_STATE_EXPIRED; + break; + case AD_RX_CURRENT: + /* detect loopback situation */ + if (MAC_ADDRESS_EQUAL(&(lacpdu->actor_system), + &(port->actor_system))) { + slave_err(port->slave->bond->dev, port->slave->dev, "An illegal loopback occurred on slave\n" + "Check the configuration to verify that all adapters are connected to 802.3ad compliant switch ports\n"); + return; + } + __update_selected(lacpdu, port); + __update_ntt(lacpdu, port); + __record_pdu(lacpdu, port); + port->sm_rx_timer_counter = __ad_timer_to_ticks(AD_CURRENT_WHILE_TIMER, (u16)(port->actor_oper_port_state & LACP_STATE_LACP_TIMEOUT)); + port->actor_oper_port_state &= ~LACP_STATE_EXPIRED; + break; + default: + break; + } + } +} + +/** + * ad_churn_machine - handle port churn's state machine + * @port: the port we're looking at + * + */ +static void ad_churn_machine(struct port *port) +{ + if (port->sm_vars & AD_PORT_CHURNED) { + port->sm_vars &= ~AD_PORT_CHURNED; + WRITE_ONCE(port->sm_churn_actor_state, AD_CHURN_MONITOR); + WRITE_ONCE(port->sm_churn_partner_state, AD_CHURN_MONITOR); + port->sm_churn_actor_timer_counter = + __ad_timer_to_ticks(AD_ACTOR_CHURN_TIMER, 0); + port->sm_churn_partner_timer_counter = + __ad_timer_to_ticks(AD_PARTNER_CHURN_TIMER, 0); + return; + } + if (port->sm_churn_actor_timer_counter && + !(--port->sm_churn_actor_timer_counter) && + port->sm_churn_actor_state == AD_CHURN_MONITOR) { + if (port->actor_oper_port_state & LACP_STATE_SYNCHRONIZATION) { + WRITE_ONCE(port->sm_churn_actor_state, AD_NO_CHURN); + } else { + WRITE_ONCE(port->churn_actor_count, + port->churn_actor_count + 1); + WRITE_ONCE(port->sm_churn_actor_state, AD_CHURN); + } + } + if (port->sm_churn_partner_timer_counter && + !(--port->sm_churn_partner_timer_counter) && + port->sm_churn_partner_state == AD_CHURN_MONITOR) { + if (port->partner_oper.port_state & LACP_STATE_SYNCHRONIZATION) { + WRITE_ONCE(port->sm_churn_partner_state, AD_NO_CHURN); + } else { + WRITE_ONCE(port->churn_partner_count, + port->churn_partner_count + 1); + WRITE_ONCE(port->sm_churn_partner_state, AD_CHURN); + } + } +} + +/** + * ad_tx_machine - handle a port's tx state machine + * @port: the port we're looking at + */ +static void ad_tx_machine(struct port *port) +{ + /* check if tx timer expired, to verify that we do not send more than + * 3 packets per second + */ + if (!port->sm_tx_timer_counter || !(--port->sm_tx_timer_counter)) { + /* check if there is something to send */ + if (port->ntt && (port->sm_vars & AD_PORT_LACP_ENABLED)) { + __update_lacpdu_from_port(port); + + if (ad_lacpdu_send(port) >= 0) { + slave_dbg(port->slave->bond->dev, + port->slave->dev, + "Sent LACPDU on port %d\n", + port->actor_port_number); + + /* mark ntt as false, so it will not be sent + * again until demanded + */ + port->ntt = false; + + /* restart tx timer(to verify that we will not + * exceed AD_MAX_TX_IN_SECOND + */ + port->sm_tx_timer_counter = ad_ticks_per_sec / AD_MAX_TX_IN_SECOND; + } + } + } +} + +/** + * ad_periodic_machine - handle a port's periodic state machine + * @port: the port we're looking at + * + * Turn ntt flag on priodically to perform periodic transmission of lacpdu's. + */ +static void ad_periodic_machine(struct port *port) +{ + periodic_states_t last_state; + + /* keep current state machine state to compare later if it was changed */ + last_state = port->sm_periodic_state; + + /* check if port was reinitialized */ + if (((port->sm_vars & AD_PORT_BEGIN) || !(port->sm_vars & AD_PORT_LACP_ENABLED) || !port->is_enabled) || + (!(port->actor_oper_port_state & LACP_STATE_LACP_ACTIVITY) && !(port->partner_oper.port_state & LACP_STATE_LACP_ACTIVITY))) { + port->sm_periodic_state = AD_NO_PERIODIC; + } + /* check if state machine should change state */ + else if (port->sm_periodic_timer_counter) { + /* check if periodic state machine expired */ + if (!(--port->sm_periodic_timer_counter)) { + /* if expired then do tx */ + port->sm_periodic_state = AD_PERIODIC_TX; + } else { + /* If not expired, check if there is some new timeout + * parameter from the partner state + */ + switch (port->sm_periodic_state) { + case AD_FAST_PERIODIC: + if (!(port->partner_oper.port_state + & LACP_STATE_LACP_TIMEOUT)) + port->sm_periodic_state = AD_SLOW_PERIODIC; + break; + case AD_SLOW_PERIODIC: + if ((port->partner_oper.port_state & LACP_STATE_LACP_TIMEOUT)) { + port->sm_periodic_timer_counter = 0; + port->sm_periodic_state = AD_PERIODIC_TX; + } + break; + default: + break; + } + } + } else { + switch (port->sm_periodic_state) { + case AD_NO_PERIODIC: + port->sm_periodic_state = AD_FAST_PERIODIC; + break; + case AD_PERIODIC_TX: + if (!(port->partner_oper.port_state & + LACP_STATE_LACP_TIMEOUT)) + port->sm_periodic_state = AD_SLOW_PERIODIC; + else + port->sm_periodic_state = AD_FAST_PERIODIC; + break; + default: + break; + } + } + + /* check if the state machine was changed */ + if (port->sm_periodic_state != last_state) { + slave_dbg(port->slave->bond->dev, port->slave->dev, + "Periodic Machine: Port=%d, Last State=%d, Curr State=%d\n", + port->actor_port_number, last_state, + port->sm_periodic_state); + switch (port->sm_periodic_state) { + case AD_NO_PERIODIC: + port->sm_periodic_timer_counter = 0; + break; + case AD_FAST_PERIODIC: + /* decrement 1 tick we lost in the PERIODIC_TX cycle */ + port->sm_periodic_timer_counter = __ad_timer_to_ticks(AD_PERIODIC_TIMER, (u16)(AD_FAST_PERIODIC_TIME))-1; + break; + case AD_SLOW_PERIODIC: + /* decrement 1 tick we lost in the PERIODIC_TX cycle */ + port->sm_periodic_timer_counter = __ad_timer_to_ticks(AD_PERIODIC_TIMER, (u16)(AD_SLOW_PERIODIC_TIME))-1; + break; + case AD_PERIODIC_TX: + port->ntt = true; + break; + default: + break; + } + } +} + +/** + * ad_port_selection_logic - select aggregation groups + * @port: the port we're looking at + * @update_slave_arr: Does slave array need update? + * + * Select aggregation groups, and assign each port for it's aggregetor. The + * selection logic is called in the inititalization (after all the handshkes), + * and after every lacpdu receive (if selected is off). + */ +static void ad_port_selection_logic(struct port *port, bool *update_slave_arr) +{ + struct aggregator *aggregator, *free_aggregator = NULL, *temp_aggregator; + struct port *last_port = NULL, *curr_port; + struct list_head *iter; + struct bonding *bond; + struct slave *slave; + int found = 0; + + /* if the port is already Selected, do nothing */ + if (port->sm_vars & AD_PORT_SELECTED) + return; + + bond = __get_bond_by_port(port); + + /* if the port is connected to other aggregator, detach it */ + temp_aggregator = rcu_dereference(port->aggregator); + if (temp_aggregator) { + /* detach the port from its former aggregator */ + for (curr_port = temp_aggregator->lag_ports; curr_port; + last_port = curr_port, + curr_port = curr_port->next_port_in_aggregator) { + if (curr_port == port) { + temp_aggregator->num_of_ports--; + /* if it is the first port attached to the + * aggregator + */ + if (!last_port) { + temp_aggregator->lag_ports = + port->next_port_in_aggregator; + } else { + /* not the first port attached to the + * aggregator + */ + last_port->next_port_in_aggregator = + port->next_port_in_aggregator; + } + + /* clear the port's relations to this + * aggregator + */ + RCU_INIT_POINTER(port->aggregator, NULL); + port->next_port_in_aggregator = NULL; + port->actor_port_aggregator_identifier = 0; + + slave_dbg(bond->dev, port->slave->dev, "Port %d left LAG %d\n", + port->actor_port_number, + temp_aggregator->aggregator_identifier); + /* if the aggregator is empty, clear its + * parameters, and set it ready to be attached + */ + if (!temp_aggregator->lag_ports) + ad_clear_agg(temp_aggregator); + break; + } + } + if (!curr_port) { + /* meaning: the port was related to an aggregator + * but was not on the aggregator port list + */ + net_warn_ratelimited("%s: (slave %s): Warning: Port %d was related to aggregator %d but was not on its port list\n", + port->slave->bond->dev->name, + port->slave->dev->name, + port->actor_port_number, + temp_aggregator->aggregator_identifier); + } + } + /* search on all aggregators for a suitable aggregator for this port */ + bond_for_each_slave(bond, slave, iter) { + aggregator = &(SLAVE_AD_INFO(slave)->aggregator); + + /* keep a free aggregator for later use(if needed) */ + if (!aggregator->lag_ports) { + if (!free_aggregator) + free_aggregator = aggregator; + continue; + } + /* check if current aggregator suits us */ + if (((aggregator->actor_oper_aggregator_key == port->actor_oper_port_key) && /* if all parameters match AND */ + MAC_ADDRESS_EQUAL(&(aggregator->partner_system), &(port->partner_oper.system)) && + (aggregator->partner_system_priority == port->partner_oper.system_priority) && + (aggregator->partner_oper_aggregator_key == port->partner_oper.key) + ) && + ((__agg_has_partner(aggregator) && /* partner answers */ + !aggregator->is_individual) /* but is not individual OR */ + ) + ) { + /* attach to the founded aggregator */ + rcu_assign_pointer(port->aggregator, aggregator); + port->actor_port_aggregator_identifier = + aggregator->aggregator_identifier; + port->next_port_in_aggregator = aggregator->lag_ports; + aggregator->num_of_ports++; + aggregator->lag_ports = port; + slave_dbg(bond->dev, slave->dev, "Port %d joined LAG %d (existing LAG)\n", + port->actor_port_number, + aggregator->aggregator_identifier); + + /* mark this port as selected */ + port->sm_vars |= AD_PORT_SELECTED; + found = 1; + break; + } + } + + /* the port couldn't find an aggregator - attach it to a new + * aggregator + */ + if (!found) { + if (free_aggregator) { + /* assign port a new aggregator */ + port->actor_port_aggregator_identifier = + free_aggregator->aggregator_identifier; + + /* update the new aggregator's parameters + * if port was responsed from the end-user + */ + if (port->actor_oper_port_key & AD_DUPLEX_KEY_MASKS) + /* if port is full duplex */ + free_aggregator->is_individual = false; + else + free_aggregator->is_individual = true; + + free_aggregator->actor_admin_aggregator_key = + port->actor_admin_port_key; + free_aggregator->actor_oper_aggregator_key = + port->actor_oper_port_key; + free_aggregator->partner_system = + port->partner_oper.system; + free_aggregator->partner_system_priority = + port->partner_oper.system_priority; + free_aggregator->partner_oper_aggregator_key = port->partner_oper.key; + free_aggregator->receive_state = 1; + free_aggregator->transmit_state = 1; + free_aggregator->lag_ports = port; + free_aggregator->num_of_ports++; + + rcu_assign_pointer(port->aggregator, free_aggregator); + + /* mark this port as selected */ + port->sm_vars |= AD_PORT_SELECTED; + + slave_dbg(bond->dev, port->slave->dev, "Port %d joined LAG %d (new LAG)\n", + port->actor_port_number, + free_aggregator->aggregator_identifier); + } else { + slave_err(bond->dev, port->slave->dev, + "Port %d did not find a suitable aggregator\n", + port->actor_port_number); + return; + } + } + /* if all aggregator's ports are READY_N == TRUE, set ready=TRUE + * in all aggregator's ports, else set ready=FALSE in all + * aggregator's ports + */ + aggregator = rcu_dereference(port->aggregator); + __set_agg_ports_ready(aggregator, __agg_ports_are_ready(aggregator)); + + ad_agg_selection_logic(__get_first_agg(port), update_slave_arr); + + if (!aggregator->is_active) + port->actor_oper_port_state &= ~LACP_STATE_SYNCHRONIZATION; +} + +/* Decide if "agg" is a better choice for the new active aggregator that + * the current best, according to the ad_select policy. + */ +static struct aggregator *ad_agg_selection_test(struct aggregator *best, + struct aggregator *curr) +{ + /* 0. If no best, select current. + * + * 1. If the current agg is not individual, and the best is + * individual, select current. + * + * 2. If current agg is individual and the best is not, keep best. + * + * 3. Therefore, current and best are both individual or both not + * individual, so: + * + * 3a. If current agg partner replied, and best agg partner did not, + * select current. + * + * 3b. If current agg partner did not reply and best agg partner + * did reply, keep best. + * + * 4. Therefore, current and best both have partner replies or + * both do not, so perform selection policy: + * + * BOND_AD_PRIO: Select by total priority of ports. If priority + * is equal, select by count. + * + * BOND_AD_COUNT: Select by count of ports. If count is equal, + * select by bandwidth. + * + * BOND_AD_STABLE, BOND_AD_BANDWIDTH: Select by bandwidth. + */ + if (!best) + return curr; + + if (!curr->is_individual && best->is_individual) + return curr; + + if (curr->is_individual && !best->is_individual) + return best; + + if (__agg_has_partner(curr) && !__agg_has_partner(best)) + return curr; + + if (!__agg_has_partner(curr) && __agg_has_partner(best)) + return best; + + switch (__get_agg_selection_mode(curr->lag_ports)) { + case BOND_AD_PRIO: + if (__agg_ports_priority(curr) > __agg_ports_priority(best)) + return curr; + + if (__agg_ports_priority(curr) < __agg_ports_priority(best)) + return best; + + fallthrough; + case BOND_AD_COUNT: + if (__agg_active_ports(curr) > __agg_active_ports(best)) + return curr; + + if (__agg_active_ports(curr) < __agg_active_ports(best)) + return best; + + fallthrough; + case BOND_AD_STABLE: + case BOND_AD_BANDWIDTH: + if (__get_agg_bandwidth(curr) > __get_agg_bandwidth(best)) + return curr; + + break; + + default: + net_warn_ratelimited("%s: (slave %s): Impossible agg select mode %d\n", + curr->slave->bond->dev->name, + curr->slave->dev->name, + __get_agg_selection_mode(curr->lag_ports)); + break; + } + + return best; +} + +static int agg_device_up(const struct aggregator *agg) +{ + struct port *port = agg->lag_ports; + + if (!port) + return 0; + + for (port = agg->lag_ports; port; + port = port->next_port_in_aggregator) { + if (netif_running(port->slave->dev) && + netif_carrier_ok(port->slave->dev)) + return 1; + } + + return 0; +} + +/** + * ad_agg_selection_logic - select an aggregation group for a team + * @agg: the aggregator we're looking at + * @update_slave_arr: Does slave array need update? + * + * It is assumed that only one aggregator may be selected for a team. + * + * The logic of this function is to select the aggregator according to + * the ad_select policy: + * + * BOND_AD_STABLE: select the aggregator with the most ports attached to + * it, and to reselect the active aggregator only if the previous + * aggregator has no more ports related to it. + * + * BOND_AD_BANDWIDTH: select the aggregator with the highest total + * bandwidth, and reselect whenever a link state change takes place or the + * set of slaves in the bond changes. + * + * BOND_AD_COUNT: select the aggregator with largest number of ports + * (slaves), and reselect whenever a link state change takes place or the + * set of slaves in the bond changes. + * + * BOND_AD_PRIO: select the aggregator with highest total priority of ports + * (slaves), and reselect whenever a link state change takes place or the + * set of slaves in the bond changes. + * + * FIXME: this function MUST be called with the first agg in the bond, or + * __get_active_agg() won't work correctly. This function should be better + * called with the bond itself, and retrieve the first agg from it. + */ +static void ad_agg_selection_logic(struct aggregator *agg, + bool *update_slave_arr) +{ + struct aggregator *best, *active, *origin; + struct bonding *bond = agg->slave->bond; + struct list_head *iter; + struct slave *slave; + struct port *port; + + rcu_read_lock(); + origin = agg; + active = __get_active_agg(agg); + best = (active && agg_device_up(active)) ? active : NULL; + + bond_for_each_slave_rcu(bond, slave, iter) { + agg = &(SLAVE_AD_INFO(slave)->aggregator); + + agg->is_active = 0; + + if (__agg_active_ports(agg) && agg_device_up(agg)) + best = ad_agg_selection_test(best, agg); + } + + if (best && + __get_agg_selection_mode(best->lag_ports) == BOND_AD_STABLE) { + /* For the STABLE policy, don't replace the old active + * aggregator if it's still active (it has an answering + * partner) or if both the best and active don't have an + * answering partner. + */ + if (active && active->lag_ports && + __agg_active_ports(active) && + (__agg_has_partner(active) || + (!__agg_has_partner(active) && + !__agg_has_partner(best)))) { + if (!(!active->actor_oper_aggregator_key && + best->actor_oper_aggregator_key)) { + best = NULL; + active->is_active = 1; + } + } + } + + if (best && (best == active)) { + best = NULL; + active->is_active = 1; + } + + /* if there is new best aggregator, activate it */ + if (best) { + netdev_dbg(bond->dev, "(slave %s): best Agg=%d; P=%d; a k=%d; p k=%d; Ind=%d; Act=%d\n", + best->slave ? best->slave->dev->name : "NULL", + best->aggregator_identifier, best->num_of_ports, + best->actor_oper_aggregator_key, + best->partner_oper_aggregator_key, + best->is_individual, best->is_active); + netdev_dbg(bond->dev, "(slave %s): best ports %p slave %p\n", + best->slave ? best->slave->dev->name : "NULL", + best->lag_ports, best->slave); + + bond_for_each_slave_rcu(bond, slave, iter) { + agg = &(SLAVE_AD_INFO(slave)->aggregator); + + slave_dbg(bond->dev, slave->dev, "Agg=%d; P=%d; a k=%d; p k=%d; Ind=%d; Act=%d\n", + agg->aggregator_identifier, agg->num_of_ports, + agg->actor_oper_aggregator_key, + agg->partner_oper_aggregator_key, + agg->is_individual, agg->is_active); + } + + /* check if any partner replies */ + if (best->is_individual) + net_warn_ratelimited("%s: Warning: No 802.3ad response from the link partner for any adapters in the bond\n", + bond->dev->name); + + best->is_active = 1; + netdev_dbg(bond->dev, "(slave %s): LAG %d chosen as the active LAG\n", + best->slave ? best->slave->dev->name : "NULL", + best->aggregator_identifier); + netdev_dbg(bond->dev, "(slave %s): Agg=%d; P=%d; a k=%d; p k=%d; Ind=%d; Act=%d\n", + best->slave ? best->slave->dev->name : "NULL", + best->aggregator_identifier, best->num_of_ports, + best->actor_oper_aggregator_key, + best->partner_oper_aggregator_key, + best->is_individual, best->is_active); + + /* disable the ports that were related to the former + * active_aggregator + */ + if (active) { + for (port = active->lag_ports; port; + port = port->next_port_in_aggregator) { + __disable_port(port); + } + } + /* Slave array needs update. */ + *update_slave_arr = true; + } + + /* if the selected aggregator is of join individuals + * (partner_system is NULL), enable their ports + */ + active = __get_active_agg(origin); + + if (active) { + if (!__agg_has_partner(active)) { + for (port = active->lag_ports; port; + port = port->next_port_in_aggregator) { + __enable_port(port); + } + *update_slave_arr = true; + } + } + + rcu_read_unlock(); + + bond_3ad_set_carrier(bond); +} + +/** + * ad_clear_agg - clear a given aggregator's parameters + * @aggregator: the aggregator we're looking at + */ +static void ad_clear_agg(struct aggregator *aggregator) +{ + if (aggregator) { + aggregator->is_individual = false; + aggregator->actor_admin_aggregator_key = 0; + aggregator->actor_oper_aggregator_key = 0; + eth_zero_addr(aggregator->partner_system.mac_addr_value); + aggregator->partner_system_priority = 0; + aggregator->partner_oper_aggregator_key = 0; + aggregator->receive_state = 0; + aggregator->transmit_state = 0; + aggregator->lag_ports = NULL; + aggregator->is_active = 0; + aggregator->num_of_ports = 0; + pr_debug("%s: LAG %d was cleared\n", + aggregator->slave ? + aggregator->slave->dev->name : "NULL", + aggregator->aggregator_identifier); + } +} + +/** + * ad_initialize_agg - initialize a given aggregator's parameters + * @aggregator: the aggregator we're looking at + */ +static void ad_initialize_agg(struct aggregator *aggregator) +{ + if (aggregator) { + ad_clear_agg(aggregator); + + eth_zero_addr(aggregator->aggregator_mac_address.mac_addr_value); + aggregator->aggregator_identifier = 0; + aggregator->slave = NULL; + } +} + +/** + * ad_initialize_port - initialize a given port's parameters + * @port: the port we're looking at + * @bond_params: bond parameters we will use + */ +static void ad_initialize_port(struct port *port, const struct bond_params *bond_params) +{ + static const struct port_params tmpl = { + .system_priority = 0xffff, + .key = 1, + .port_number = 1, + .port_priority = 0xff, + .port_state = 0, + }; + static const struct lacpdu lacpdu = { + .subtype = 0x01, + .version_number = 0x01, + .tlv_type_actor_info = 0x01, + .actor_information_length = 0x14, + .tlv_type_partner_info = 0x02, + .partner_information_length = 0x14, + .tlv_type_collector_info = 0x03, + .collector_information_length = 0x10, + .collector_max_delay = htons(AD_COLLECTOR_MAX_DELAY), + }; + + if (port) { + port->actor_port_priority = 0xff; + port->actor_port_aggregator_identifier = 0; + port->ntt = false; + port->actor_admin_port_state = LACP_STATE_AGGREGATION; + port->actor_oper_port_state = LACP_STATE_AGGREGATION; + if (bond_params->lacp_active) { + port->actor_admin_port_state |= LACP_STATE_LACP_ACTIVITY; + port->actor_oper_port_state |= LACP_STATE_LACP_ACTIVITY; + } + + if (bond_params->lacp_fast) + port->actor_oper_port_state |= LACP_STATE_LACP_TIMEOUT; + + memcpy(&port->partner_admin, &tmpl, sizeof(tmpl)); + memcpy(&port->partner_oper, &tmpl, sizeof(tmpl)); + + port->is_enabled = true; + /* private parameters */ + port->sm_vars = AD_PORT_BEGIN | AD_PORT_LACP_ENABLED; + port->sm_rx_state = 0; + port->sm_rx_timer_counter = 0; + port->sm_periodic_state = 0; + port->sm_periodic_timer_counter = 0; + port->sm_mux_state = 0; + port->sm_mux_timer_counter = 0; + port->sm_tx_state = 0; + port->aggregator = NULL; + port->next_port_in_aggregator = NULL; + port->transaction_id = 0; + + port->sm_churn_actor_timer_counter = 0; + port->sm_churn_actor_state = 0; + port->churn_actor_count = 0; + port->sm_churn_partner_timer_counter = 0; + port->sm_churn_partner_state = 0; + port->churn_partner_count = 0; + + memcpy(&port->lacpdu, &lacpdu, sizeof(lacpdu)); + } +} + +/** + * ad_enable_collecting - enable a port's receive + * @port: the port we're looking at + * + * Enable @port if it's in an active aggregator + */ +static void ad_enable_collecting(struct port *port) +{ + struct aggregator *aggregator = rcu_dereference(port->aggregator); + + if (aggregator->is_active) { + struct slave *slave = port->slave; + + slave_dbg(slave->bond->dev, slave->dev, + "Enabling collecting on port %d (LAG %d)\n", + port->actor_port_number, + aggregator->aggregator_identifier); + __enable_collecting_port(port); + } +} + +/** + * ad_disable_distributing - disable a port's transmit + * @port: the port we're looking at + * @update_slave_arr: Does slave array need update? + */ +static void ad_disable_distributing(struct port *port, bool *update_slave_arr) +{ + struct aggregator *aggregator = rcu_dereference(port->aggregator); + + if (aggregator && __agg_has_partner(aggregator)) { + slave_dbg(port->slave->bond->dev, port->slave->dev, + "Disabling distributing on port %d (LAG %d)\n", + 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; + } +} + +/** + * ad_enable_collecting_distributing - enable a port's transmit/receive + * @port: the port we're looking at + * @update_slave_arr: Does slave array need update? + * + * Enable @port if it's in an active aggregator + */ +static void ad_enable_collecting_distributing(struct port *port, + bool *update_slave_arr) +{ + struct aggregator *aggregator = rcu_dereference(port->aggregator); + + if (aggregator->is_active) { + slave_dbg(port->slave->bond->dev, port->slave->dev, + "Enabling port %d (LAG %d)\n", + 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 */ + ad_cond_set_peer_notif(port); + } +} + +/** + * ad_disable_collecting_distributing - disable a port's transmit/receive + * @port: the port we're looking at + * @update_slave_arr: Does slave array need update? + */ +static void ad_disable_collecting_distributing(struct port *port, + bool *update_slave_arr) +{ + struct aggregator *aggregator = rcu_dereference(port->aggregator); + + if (aggregator && __agg_has_partner(aggregator)) { + slave_dbg(port->slave->bond->dev, port->slave->dev, + "Disabling port %d (LAG %d)\n", + 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; + } +} + +/** + * ad_marker_info_received - handle receive of a Marker information frame + * @marker_info: Marker info received + * @port: the port we're looking at + */ +static void ad_marker_info_received(struct bond_marker *marker_info, + struct port *port) +{ + struct bond_marker marker; + + atomic64_inc(&SLAVE_AD_INFO(port->slave)->stats.marker_rx); + atomic64_inc(&BOND_AD_INFO(port->slave->bond).stats.marker_rx); + + /* copy the received marker data to the response marker */ + memcpy(&marker, marker_info, sizeof(struct bond_marker)); + /* change the marker subtype to marker response */ + marker.tlv_type = AD_MARKER_RESPONSE_SUBTYPE; + + /* send the marker response */ + if (ad_marker_send(port, &marker) >= 0) + slave_dbg(port->slave->bond->dev, port->slave->dev, + "Sent Marker Response on port %d\n", + port->actor_port_number); +} + +/** + * ad_marker_response_received - handle receive of a marker response frame + * @marker: marker PDU received + * @port: the port we're looking at + * + * This function does nothing since we decided not to implement send and handle + * response for marker PDU's, in this stage, but only to respond to marker + * information. + */ +static void ad_marker_response_received(struct bond_marker *marker, + struct port *port) +{ + atomic64_inc(&SLAVE_AD_INFO(port->slave)->stats.marker_resp_rx); + atomic64_inc(&BOND_AD_INFO(port->slave->bond).stats.marker_resp_rx); + + /* DO NOTHING, SINCE WE DECIDED NOT TO IMPLEMENT THIS FEATURE FOR NOW */ +} + +/* ========= AD exported functions to the main bonding code ========= */ + +/* Check aggregators status in team every T seconds */ +#define AD_AGGREGATOR_SELECTION_TIMER 8 + +/** + * bond_3ad_initiate_agg_selection - initate aggregator selection + * @bond: bonding struct + * @timeout: timeout value to set + * + * Set the aggregation selection timer, to initiate an agg selection in + * the very near future. Called during first initialization, and during + * any down to up transitions of the bond. + */ +void bond_3ad_initiate_agg_selection(struct bonding *bond, int timeout) +{ + atomic_set(&BOND_AD_INFO(bond).agg_select_timer, timeout); +} + +/** + * bond_3ad_initialize - initialize a bond's 802.3ad parameters and structures + * @bond: bonding struct to work on + * + * Can be called only after the mac address of the bond is set. + */ +void bond_3ad_initialize(struct bonding *bond) +{ + BOND_AD_INFO(bond).aggregator_identifier = 0; + BOND_AD_INFO(bond).system.sys_priority = + bond->params.ad_actor_sys_prio; + if (is_zero_ether_addr(bond->params.ad_actor_system)) + BOND_AD_INFO(bond).system.sys_mac_addr = + *((struct mac_addr *)bond->dev->dev_addr); + else + BOND_AD_INFO(bond).system.sys_mac_addr = + *((struct mac_addr *)bond->params.ad_actor_system); + + bond_3ad_initiate_agg_selection(bond, + AD_AGGREGATOR_SELECTION_TIMER * + ad_ticks_per_sec); +} + +/** + * bond_3ad_bind_slave - initialize a slave's port + * @slave: slave struct to work on + * + * Returns: 0 on success + * < 0 on error + */ +void bond_3ad_bind_slave(struct slave *slave) +{ + struct bonding *bond = bond_get_bond_by_slave(slave); + struct port *port; + struct aggregator *aggregator; + + /* check that the slave has not been initialized yet. */ + if (SLAVE_AD_INFO(slave)->port.slave != slave) { + + /* port initialization */ + port = &(SLAVE_AD_INFO(slave)->port); + + ad_initialize_port(port, &bond->params); + + /* Port priority is initialized. Update it to slave's ad info */ + SLAVE_AD_INFO(slave)->port_priority = port->actor_port_priority; + + port->slave = slave; + port->actor_port_number = SLAVE_AD_INFO(slave)->id; + /* key is determined according to the link speed, duplex and + * user key + */ + port->actor_admin_port_key = bond->params.ad_user_port_key << 6; + ad_update_actor_keys(port, false); + /* actor system is the bond's system */ + __ad_actor_update_port(port); + /* tx timer(to verify that no more than MAX_TX_IN_SECOND + * lacpdu's are sent in one second) + */ + port->sm_tx_timer_counter = ad_ticks_per_sec/AD_MAX_TX_IN_SECOND; + + __disable_port(port); + + /* aggregator initialization */ + aggregator = &(SLAVE_AD_INFO(slave)->aggregator); + + ad_initialize_agg(aggregator); + + aggregator->aggregator_mac_address = *((struct mac_addr *)bond->dev->dev_addr); + aggregator->aggregator_identifier = ++BOND_AD_INFO(bond).aggregator_identifier; + aggregator->slave = slave; + aggregator->is_active = 0; + aggregator->num_of_ports = 0; + } +} + +/** + * bond_3ad_unbind_slave - deinitialize a slave's port + * @slave: slave struct to work on + * + * Search for the aggregator that is related to this port, remove the + * aggregator and assign another aggregator for other port related to it + * (if any), and remove the port. + */ +void bond_3ad_unbind_slave(struct slave *slave) +{ + struct port *port, *prev_port, *temp_port; + struct aggregator *aggregator, *new_aggregator, *temp_aggregator; + int select_new_active_agg = 0; + struct bonding *bond = slave->bond; + struct slave *slave_iter; + struct list_head *iter; + bool dummy_slave_update; /* Ignore this value as caller updates array */ + + /* Sync against bond_3ad_state_machine_handler() */ + spin_lock_bh(&bond->mode_lock); + aggregator = &(SLAVE_AD_INFO(slave)->aggregator); + port = &(SLAVE_AD_INFO(slave)->port); + + /* if slave is null, the whole port is not initialized */ + if (!port->slave) { + slave_warn(bond->dev, slave->dev, "Trying to unbind an uninitialized port\n"); + goto out; + } + + slave_dbg(bond->dev, slave->dev, "Unbinding Link Aggregation Group %d\n", + aggregator->aggregator_identifier); + + /* Tell the partner that this port is not suitable for aggregation */ + port->actor_oper_port_state &= ~LACP_STATE_SYNCHRONIZATION; + port->actor_oper_port_state &= ~LACP_STATE_COLLECTING; + port->actor_oper_port_state &= ~LACP_STATE_DISTRIBUTING; + port->actor_oper_port_state &= ~LACP_STATE_AGGREGATION; + __update_lacpdu_from_port(port); + ad_lacpdu_send(port); + + /* check if this aggregator is occupied */ + if (aggregator->lag_ports) { + /* check if there are other ports related to this aggregator + * except the port related to this slave(thats ensure us that + * there is a reason to search for new aggregator, and that we + * will find one + */ + if ((aggregator->lag_ports != port) || + (aggregator->lag_ports->next_port_in_aggregator)) { + /* find new aggregator for the related port(s) */ + bond_for_each_slave(bond, slave_iter, iter) { + new_aggregator = &(SLAVE_AD_INFO(slave_iter)->aggregator); + /* if the new aggregator is empty, or it is + * connected to our port only + */ + if (!new_aggregator->lag_ports || + ((new_aggregator->lag_ports == port) && + !new_aggregator->lag_ports->next_port_in_aggregator)) + break; + } + if (!slave_iter) + new_aggregator = NULL; + + /* if new aggregator found, copy the aggregator's + * parameters and connect the related lag_ports to the + * new aggregator + */ + if ((new_aggregator) && ((!new_aggregator->lag_ports) || ((new_aggregator->lag_ports == port) && !new_aggregator->lag_ports->next_port_in_aggregator))) { + slave_dbg(bond->dev, slave->dev, "Some port(s) related to LAG %d - replacing with LAG %d\n", + aggregator->aggregator_identifier, + new_aggregator->aggregator_identifier); + + if ((new_aggregator->lag_ports == port) && + new_aggregator->is_active) { + slave_info(bond->dev, slave->dev, "Removing an active aggregator\n"); + select_new_active_agg = 1; + } + + new_aggregator->is_individual = aggregator->is_individual; + new_aggregator->actor_admin_aggregator_key = aggregator->actor_admin_aggregator_key; + new_aggregator->actor_oper_aggregator_key = aggregator->actor_oper_aggregator_key; + new_aggregator->partner_system = aggregator->partner_system; + new_aggregator->partner_system_priority = aggregator->partner_system_priority; + new_aggregator->partner_oper_aggregator_key = aggregator->partner_oper_aggregator_key; + new_aggregator->receive_state = aggregator->receive_state; + new_aggregator->transmit_state = aggregator->transmit_state; + new_aggregator->lag_ports = aggregator->lag_ports; + new_aggregator->is_active = aggregator->is_active; + new_aggregator->num_of_ports = aggregator->num_of_ports; + + /* update the information that is written on + * the ports about the aggregator + */ + for (temp_port = aggregator->lag_ports; temp_port; + temp_port = temp_port->next_port_in_aggregator) { + rcu_assign_pointer(temp_port->aggregator, new_aggregator); + temp_port->actor_port_aggregator_identifier = new_aggregator->aggregator_identifier; + } + + ad_clear_agg(aggregator); + + if (select_new_active_agg) + ad_agg_selection_logic(__get_first_agg(port), + &dummy_slave_update); + } else { + slave_warn(bond->dev, slave->dev, "unbinding aggregator, and could not find a new aggregator for its ports\n"); + } + } else { + /* in case that the only port related to this + * aggregator is the one we want to remove + */ + select_new_active_agg = aggregator->is_active; + ad_clear_agg(aggregator); + if (select_new_active_agg) { + slave_info(bond->dev, slave->dev, "Removing an active aggregator\n"); + /* select new active aggregator */ + temp_aggregator = __get_first_agg(port); + if (temp_aggregator) + ad_agg_selection_logic(temp_aggregator, + &dummy_slave_update); + } + } + } + + slave_dbg(bond->dev, slave->dev, "Unbinding port %d\n", port->actor_port_number); + + /* find the aggregator that this port is connected to */ + bond_for_each_slave(bond, slave_iter, iter) { + temp_aggregator = &(SLAVE_AD_INFO(slave_iter)->aggregator); + prev_port = NULL; + /* search the port in the aggregator's related ports */ + for (temp_port = temp_aggregator->lag_ports; temp_port; + prev_port = temp_port, + temp_port = temp_port->next_port_in_aggregator) { + if (temp_port == port) { + /* the aggregator found - detach the port from + * this aggregator + */ + if (prev_port) + prev_port->next_port_in_aggregator = temp_port->next_port_in_aggregator; + else + temp_aggregator->lag_ports = temp_port->next_port_in_aggregator; + temp_aggregator->num_of_ports--; + if (__agg_active_ports(temp_aggregator) == 0) { + select_new_active_agg = temp_aggregator->is_active; + if (temp_aggregator->num_of_ports == 0) + ad_clear_agg(temp_aggregator); + if (select_new_active_agg) { + slave_info(bond->dev, slave->dev, "Removing an active aggregator\n"); + /* select new active aggregator */ + ad_agg_selection_logic(__get_first_agg(port), + &dummy_slave_update); + } + } + break; + } + } + } + port->slave = NULL; + +out: + spin_unlock_bh(&bond->mode_lock); +} + +/** + * bond_3ad_update_ad_actor_settings - reflect change of actor settings to ports + * @bond: bonding struct to work on + * + * If an ad_actor setting gets changed we need to update the individual port + * settings so the bond device will use the new values when it gets upped. + */ +void bond_3ad_update_ad_actor_settings(struct bonding *bond) +{ + struct list_head *iter; + struct slave *slave; + + ASSERT_RTNL(); + + BOND_AD_INFO(bond).system.sys_priority = bond->params.ad_actor_sys_prio; + if (is_zero_ether_addr(bond->params.ad_actor_system)) + BOND_AD_INFO(bond).system.sys_mac_addr = + *((struct mac_addr *)bond->dev->dev_addr); + else + BOND_AD_INFO(bond).system.sys_mac_addr = + *((struct mac_addr *)bond->params.ad_actor_system); + + spin_lock_bh(&bond->mode_lock); + bond_for_each_slave(bond, slave, iter) { + struct port *port = &(SLAVE_AD_INFO(slave))->port; + + __ad_actor_update_port(port); + port->ntt = true; + } + spin_unlock_bh(&bond->mode_lock); +} + +/** + * bond_agg_timer_advance - advance agg_select_timer + * @bond: bonding structure + * + * Return true when agg_select_timer reaches 0. + */ +static bool bond_agg_timer_advance(struct bonding *bond) +{ + int val, nval; + + while (1) { + val = atomic_read(&BOND_AD_INFO(bond).agg_select_timer); + if (!val) + return false; + nval = val - 1; + if (atomic_cmpxchg(&BOND_AD_INFO(bond).agg_select_timer, + val, nval) == val) + break; + } + return nval == 0; +} + +/** + * bond_3ad_state_machine_handler - handle state machines timeout + * @work: work context to fetch bonding struct to work on from + * + * The state machine handling concept in this module is to check every tick + * which state machine should operate any function. The execution order is + * round robin, so when we have an interaction between state machines, the + * reply of one to each other might be delayed until next tick. + * + * This function also complete the initialization when the agg_select_timer + * times out, and it selects an aggregator for the ports that are yet not + * related to any aggregator, and selects the active aggregator for a bond. + */ +void bond_3ad_state_machine_handler(struct work_struct *work) +{ + struct bonding *bond = container_of(work, struct bonding, + ad_work.work); + struct aggregator *aggregator; + struct list_head *iter; + struct slave *slave; + struct port *port; + bool should_notify_rtnl = BOND_SLAVE_NOTIFY_LATER; + bool update_slave_arr = false; + + /* Lock to protect data accessed by all (e.g., port->sm_vars) and + * against running with bond_3ad_unbind_slave. ad_rx_machine may run + * concurrently due to incoming LACPDU as well. + */ + spin_lock_bh(&bond->mode_lock); + rcu_read_lock(); + + /* check if there are any slaves */ + if (!bond_has_slaves(bond)) + goto re_arm; + + if (bond_agg_timer_advance(bond)) { + slave = bond_first_slave_rcu(bond); + port = slave ? &(SLAVE_AD_INFO(slave)->port) : NULL; + + /* select the active aggregator for the bond */ + if (port) { + if (!port->slave) { + net_warn_ratelimited("%s: Warning: bond's first port is uninitialized\n", + bond->dev->name); + goto re_arm; + } + + aggregator = __get_first_agg(port); + ad_agg_selection_logic(aggregator, &update_slave_arr); + } + bond_3ad_set_carrier(bond); + } + + /* for each port run the state machines */ + bond_for_each_slave_rcu(bond, slave, iter) { + port = &(SLAVE_AD_INFO(slave)->port); + if (!port->slave) { + net_warn_ratelimited("%s: Warning: Found an uninitialized port\n", + bond->dev->name); + goto re_arm; + } + + ad_rx_machine(NULL, port); + ad_periodic_machine(port); + ad_port_selection_logic(port, &update_slave_arr); + ad_mux_machine(port, &update_slave_arr); + ad_tx_machine(port); + ad_churn_machine(port); + + /* turn off the BEGIN bit, since we already handled it */ + if (port->sm_vars & AD_PORT_BEGIN) + port->sm_vars &= ~AD_PORT_BEGIN; + } + +re_arm: + bond_for_each_slave_rcu(bond, slave, iter) { + if (slave->should_notify) { + should_notify_rtnl = BOND_SLAVE_NOTIFY_NOW; + break; + } + } + rcu_read_unlock(); + spin_unlock_bh(&bond->mode_lock); + + if (update_slave_arr) + bond_slave_arr_work_rearm(bond, 0); + + if (should_notify_rtnl && rtnl_trylock()) { + bond_slave_state_notify(bond); + rtnl_unlock(); + } + queue_delayed_work(bond->wq, &bond->ad_work, ad_delta_in_ticks); +} + +/** + * bond_3ad_rx_indication - handle a received frame + * @lacpdu: received lacpdu + * @slave: slave struct to work on + * + * It is assumed that frames that were sent on this NIC don't returned as new + * received frames (loopback). Since only the payload is given to this + * function, it check for loopback. + */ +static int bond_3ad_rx_indication(struct lacpdu *lacpdu, struct slave *slave) +{ + struct bonding *bond = slave->bond; + int ret = RX_HANDLER_ANOTHER; + struct bond_marker *marker; + struct port *port; + atomic64_t *stat; + + port = &(SLAVE_AD_INFO(slave)->port); + if (!port->slave) { + net_warn_ratelimited("%s: Warning: port of slave %s is uninitialized\n", + slave->dev->name, slave->bond->dev->name); + return ret; + } + + switch (lacpdu->subtype) { + case AD_TYPE_LACPDU: + ret = RX_HANDLER_CONSUMED; + slave_dbg(slave->bond->dev, slave->dev, + "Received LACPDU on port %d\n", + port->actor_port_number); + /* Protect against concurrent state machines */ + spin_lock(&slave->bond->mode_lock); + ad_rx_machine(lacpdu, port); + spin_unlock(&slave->bond->mode_lock); + break; + case AD_TYPE_MARKER: + ret = RX_HANDLER_CONSUMED; + /* No need to convert fields to Little Endian since we + * don't use the marker's fields. + */ + marker = (struct bond_marker *)lacpdu; + switch (marker->tlv_type) { + case AD_MARKER_INFORMATION_SUBTYPE: + slave_dbg(slave->bond->dev, slave->dev, "Received Marker Information on port %d\n", + port->actor_port_number); + ad_marker_info_received(marker, port); + break; + case AD_MARKER_RESPONSE_SUBTYPE: + slave_dbg(slave->bond->dev, slave->dev, "Received Marker Response on port %d\n", + port->actor_port_number); + ad_marker_response_received(marker, port); + break; + default: + slave_dbg(slave->bond->dev, slave->dev, "Received an unknown Marker subtype on port %d\n", + port->actor_port_number); + stat = &SLAVE_AD_INFO(slave)->stats.marker_unknown_rx; + atomic64_inc(stat); + stat = &BOND_AD_INFO(bond).stats.marker_unknown_rx; + atomic64_inc(stat); + } + break; + default: + atomic64_inc(&SLAVE_AD_INFO(slave)->stats.lacpdu_unknown_rx); + atomic64_inc(&BOND_AD_INFO(bond).stats.lacpdu_unknown_rx); + } + + return ret; +} + +/** + * ad_update_actor_keys - Update the oper / admin keys for a port based on + * its current speed and duplex settings. + * + * @port: the port we'are looking at + * @reset: Boolean to just reset the speed and the duplex part of the key + * + * The logic to change the oper / admin keys is: + * (a) A full duplex port can participate in LACP with partner. + * (b) When the speed is changed, LACP need to be reinitiated. + */ +static void ad_update_actor_keys(struct port *port, bool reset) +{ + u8 duplex = 0; + u16 ospeed = 0, speed = 0; + u16 old_oper_key = port->actor_oper_port_key; + + port->actor_admin_port_key &= ~(AD_SPEED_KEY_MASKS|AD_DUPLEX_KEY_MASKS); + if (!reset) { + speed = __get_link_speed(port); + ospeed = (old_oper_key & AD_SPEED_KEY_MASKS) >> 1; + duplex = __get_duplex(port); + port->actor_admin_port_key |= (speed << 1) | duplex; + } + port->actor_oper_port_key = port->actor_admin_port_key; + + if (old_oper_key != port->actor_oper_port_key) { + /* Only 'duplex' port participates in LACP */ + if (duplex) + port->sm_vars |= AD_PORT_LACP_ENABLED; + else + port->sm_vars &= ~AD_PORT_LACP_ENABLED; + + if (!reset) { + if (!speed) { + slave_err(port->slave->bond->dev, + port->slave->dev, + "speed changed to 0 on port %d\n", + port->actor_port_number); + } else if (duplex && ospeed != speed) { + /* Speed change restarts LACP state-machine */ + port->sm_vars |= AD_PORT_BEGIN; + } + } + } +} + +/** + * bond_3ad_adapter_speed_duplex_changed - handle a slave's speed / duplex + * change indication + * + * @slave: slave struct to work on + * + * Handle reselection of aggregator (if needed) for this port. + */ +void bond_3ad_adapter_speed_duplex_changed(struct slave *slave) +{ + struct port *port; + + port = &(SLAVE_AD_INFO(slave)->port); + + /* if slave is null, the whole port is not initialized */ + if (!port->slave) { + slave_warn(slave->bond->dev, slave->dev, + "speed/duplex changed for uninitialized port\n"); + return; + } + + spin_lock_bh(&slave->bond->mode_lock); + ad_update_actor_keys(port, false); + spin_unlock_bh(&slave->bond->mode_lock); + slave_dbg(slave->bond->dev, slave->dev, "Port %d changed speed/duplex\n", + port->actor_port_number); +} + +/** + * bond_3ad_handle_link_change - handle a slave's link status change indication + * @slave: slave struct to work on + * @link: whether the link is now up or down + * + * Handle reselection of aggregator (if needed) for this port. + */ +void bond_3ad_handle_link_change(struct slave *slave, char link) +{ + struct aggregator *agg; + struct port *port; + bool dummy; + + port = &(SLAVE_AD_INFO(slave)->port); + + /* if slave is null, the whole port is not initialized */ + if (!port->slave) { + slave_warn(slave->bond->dev, slave->dev, "link status changed for uninitialized port\n"); + return; + } + + spin_lock_bh(&slave->bond->mode_lock); + /* on link down we are zeroing duplex and speed since + * some of the adaptors(ce1000.lan) report full duplex/speed + * instead of N/A(duplex) / 0(speed). + * + * on link up we are forcing recheck on the duplex and speed since + * some of he adaptors(ce1000.lan) report. + */ + if (link == BOND_LINK_UP) { + WRITE_ONCE(port->is_enabled, true); + ad_update_actor_keys(port, false); + } else { + /* link has failed */ + WRITE_ONCE(port->is_enabled, false); + ad_update_actor_keys(port, true); + } + agg = __get_first_agg(port); + ad_agg_selection_logic(agg, &dummy); + + spin_unlock_bh(&slave->bond->mode_lock); + + slave_dbg(slave->bond->dev, slave->dev, "Port %d changed link status to %s\n", + port->actor_port_number, + link == BOND_LINK_UP ? "UP" : "DOWN"); + + /* RTNL is held and mode_lock is released so it's safe + * to update slave_array here. + */ + bond_update_slave_arr(slave->bond, NULL); +} + +/** + * bond_3ad_set_carrier - set link state for bonding master + * @bond: bonding structure + * + * if we have an active aggregator, we're up, if not, we're down. + * Presumes that we cannot have an active aggregator if there are + * no slaves with link up. + * + * This behavior complies with IEEE 802.3 section 43.3.9. + * + * Called by bond_set_carrier(). Return zero if carrier state does not + * change, nonzero if it does. + */ +int bond_3ad_set_carrier(struct bonding *bond) +{ + struct aggregator *active; + struct slave *first_slave; + int ret = 1; + + rcu_read_lock(); + first_slave = bond_first_slave_rcu(bond); + if (!first_slave) { + ret = 0; + goto out; + } + active = __get_active_agg(&(SLAVE_AD_INFO(first_slave)->aggregator)); + if (active) { + /* are enough slaves available to consider link up? */ + 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; + } + } else if (!netif_carrier_ok(bond->dev)) { + netif_carrier_on(bond->dev); + goto out; + } + } else if (netif_carrier_ok(bond->dev)) { + netif_carrier_off(bond->dev); + } +out: + rcu_read_unlock(); + return ret; +} + +/** + * __bond_3ad_get_active_agg_info - get information of the active aggregator + * @bond: bonding struct to work on + * @ad_info: ad_info struct to fill with the bond's info + * + * Returns: 0 on success + * < 0 on error + */ +int __bond_3ad_get_active_agg_info(const struct bonding *bond, + struct ad_info *ad_info) +{ + const struct aggregator *aggregator = NULL, *tmp; + struct ad_slave_info *ad_slave_info; + const struct port *port; + struct list_head *iter; + struct slave *slave; + + bond_for_each_slave_rcu(bond, slave, iter) { + ad_slave_info = SLAVE_AD_INFO(slave); + if (!ad_slave_info) + continue; + port = &ad_slave_info->port; + tmp = rcu_dereference(port->aggregator); + if (tmp && tmp->is_active) { + aggregator = tmp; + break; + } + } + + if (!aggregator) + return -1; + + ad_info->aggregator_id = aggregator->aggregator_identifier; + ad_info->ports = __agg_active_ports(aggregator); + ad_info->actor_key = aggregator->actor_oper_aggregator_key; + ad_info->partner_key = aggregator->partner_oper_aggregator_key; + ether_addr_copy(ad_info->partner_system, + aggregator->partner_system.mac_addr_value); + return 0; +} + +int bond_3ad_get_active_agg_info(const struct bonding *bond, struct ad_info *ad_info) +{ + int ret; + + rcu_read_lock(); + ret = __bond_3ad_get_active_agg_info(bond, ad_info); + rcu_read_unlock(); + + return ret; +} + +int bond_3ad_lacpdu_recv(const struct sk_buff *skb, struct bonding *bond, + struct slave *slave) +{ + struct lacpdu *lacpdu, _lacpdu; + + if (skb->protocol != PKT_TYPE_LACPDU) + return RX_HANDLER_ANOTHER; + + if (!MAC_ADDRESS_EQUAL(eth_hdr(skb)->h_dest, lacpdu_mcast_addr)) + return RX_HANDLER_ANOTHER; + + lacpdu = skb_header_pointer(skb, 0, sizeof(_lacpdu), &_lacpdu); + if (!lacpdu) { + atomic64_inc(&SLAVE_AD_INFO(slave)->stats.lacpdu_illegal_rx); + atomic64_inc(&BOND_AD_INFO(bond).stats.lacpdu_illegal_rx); + return RX_HANDLER_ANOTHER; + } + + return bond_3ad_rx_indication(lacpdu, slave); +} + +/** + * bond_3ad_update_lacp_rate - change the lacp rate + * @bond: bonding struct + * + * When modify lacp_rate parameter via sysfs, + * update actor_oper_port_state of each port. + * + * Hold bond->mode_lock, + * so we can modify port->actor_oper_port_state, + * no matter bond is up or down. + */ +void bond_3ad_update_lacp_rate(struct bonding *bond) +{ + struct port *port = NULL; + struct list_head *iter; + struct slave *slave; + int lacp_fast; + + lacp_fast = bond->params.lacp_fast; + spin_lock_bh(&bond->mode_lock); + bond_for_each_slave(bond, slave, iter) { + port = &(SLAVE_AD_INFO(slave)->port); + if (lacp_fast) + port->actor_oper_port_state |= LACP_STATE_LACP_TIMEOUT; + else + port->actor_oper_port_state &= ~LACP_STATE_LACP_TIMEOUT; + } + spin_unlock_bh(&bond->mode_lock); +} + +/** + * bond_3ad_update_lacp_active - change the lacp active + * @bond: bonding struct + * + * Update actor_oper_port_state when lacp_active is modified. + */ +void bond_3ad_update_lacp_active(struct bonding *bond) +{ + struct port *port = NULL; + struct list_head *iter; + struct slave *slave; + int lacp_active; + + lacp_active = bond->params.lacp_active; + spin_lock_bh(&bond->mode_lock); + bond_for_each_slave(bond, slave, iter) { + port = &(SLAVE_AD_INFO(slave)->port); + if (lacp_active) + port->actor_oper_port_state |= LACP_STATE_LACP_ACTIVITY; + else + port->actor_oper_port_state &= ~LACP_STATE_LACP_ACTIVITY; + } + spin_unlock_bh(&bond->mode_lock); +} + +size_t bond_3ad_stats_size(void) +{ + return nla_total_size_64bit(sizeof(u64)) + /* BOND_3AD_STAT_LACPDU_RX */ + nla_total_size_64bit(sizeof(u64)) + /* BOND_3AD_STAT_LACPDU_TX */ + nla_total_size_64bit(sizeof(u64)) + /* BOND_3AD_STAT_LACPDU_UNKNOWN_RX */ + nla_total_size_64bit(sizeof(u64)) + /* BOND_3AD_STAT_LACPDU_ILLEGAL_RX */ + nla_total_size_64bit(sizeof(u64)) + /* BOND_3AD_STAT_MARKER_RX */ + nla_total_size_64bit(sizeof(u64)) + /* BOND_3AD_STAT_MARKER_TX */ + nla_total_size_64bit(sizeof(u64)) + /* BOND_3AD_STAT_MARKER_RESP_RX */ + nla_total_size_64bit(sizeof(u64)) + /* BOND_3AD_STAT_MARKER_RESP_TX */ + nla_total_size_64bit(sizeof(u64)); /* BOND_3AD_STAT_MARKER_UNKNOWN_RX */ +} + +int bond_3ad_stats_fill(struct sk_buff *skb, struct bond_3ad_stats *stats) +{ + u64 val; + + val = atomic64_read(&stats->lacpdu_rx); + if (nla_put_u64_64bit(skb, BOND_3AD_STAT_LACPDU_RX, val, + BOND_3AD_STAT_PAD)) + return -EMSGSIZE; + val = atomic64_read(&stats->lacpdu_tx); + if (nla_put_u64_64bit(skb, BOND_3AD_STAT_LACPDU_TX, val, + BOND_3AD_STAT_PAD)) + return -EMSGSIZE; + val = atomic64_read(&stats->lacpdu_unknown_rx); + if (nla_put_u64_64bit(skb, BOND_3AD_STAT_LACPDU_UNKNOWN_RX, val, + BOND_3AD_STAT_PAD)) + return -EMSGSIZE; + val = atomic64_read(&stats->lacpdu_illegal_rx); + if (nla_put_u64_64bit(skb, BOND_3AD_STAT_LACPDU_ILLEGAL_RX, val, + BOND_3AD_STAT_PAD)) + return -EMSGSIZE; + + val = atomic64_read(&stats->marker_rx); + if (nla_put_u64_64bit(skb, BOND_3AD_STAT_MARKER_RX, val, + BOND_3AD_STAT_PAD)) + return -EMSGSIZE; + val = atomic64_read(&stats->marker_tx); + if (nla_put_u64_64bit(skb, BOND_3AD_STAT_MARKER_TX, val, + BOND_3AD_STAT_PAD)) + return -EMSGSIZE; + val = atomic64_read(&stats->marker_resp_rx); + if (nla_put_u64_64bit(skb, BOND_3AD_STAT_MARKER_RESP_RX, val, + BOND_3AD_STAT_PAD)) + return -EMSGSIZE; + val = atomic64_read(&stats->marker_resp_tx); + if (nla_put_u64_64bit(skb, BOND_3AD_STAT_MARKER_RESP_TX, val, + BOND_3AD_STAT_PAD)) + return -EMSGSIZE; + val = atomic64_read(&stats->marker_unknown_rx); + if (nla_put_u64_64bit(skb, BOND_3AD_STAT_MARKER_UNKNOWN_RX, val, + BOND_3AD_STAT_PAD)) + return -EMSGSIZE; + + return 0; +} diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c new file mode 100644 index 000000000..839f7482d --- /dev/null +++ b/drivers/net/bonding/bond_alb.c @@ -0,0 +1,1837 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 1999 - 2004 Intel Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const u8 mac_v6_allmcast[ETH_ALEN + 2] __long_aligned = { + 0x33, 0x33, 0x00, 0x00, 0x00, 0x01 +}; +static const int alb_delta_in_ticks = HZ / ALB_TIMER_TICKS_PER_SEC; + +#pragma pack(1) +struct learning_pkt { + u8 mac_dst[ETH_ALEN]; + u8 mac_src[ETH_ALEN]; + __be16 type; + u8 padding[ETH_ZLEN - ETH_HLEN]; +}; + +struct arp_pkt { + __be16 hw_addr_space; + __be16 prot_addr_space; + u8 hw_addr_len; + u8 prot_addr_len; + __be16 op_code; + u8 mac_src[ETH_ALEN]; /* sender hardware address */ + __be32 ip_src; /* sender IP address */ + u8 mac_dst[ETH_ALEN]; /* target hardware address */ + __be32 ip_dst; /* target IP address */ +}; +#pragma pack() + +/* Forward declaration */ +static void alb_send_learning_packets(struct slave *slave, const u8 mac_addr[], + bool strict_match); +static void rlb_purge_src_ip(struct bonding *bond, struct arp_pkt *arp); +static void rlb_src_unlink(struct bonding *bond, u32 index); +static void rlb_src_link(struct bonding *bond, u32 ip_src_hash, + u32 ip_dst_hash); + +static inline u8 _simple_hash(const u8 *hash_start, int hash_size) +{ + int i; + u8 hash = 0; + + for (i = 0; i < hash_size; i++) + hash ^= hash_start[i]; + + return hash; +} + +/*********************** tlb specific functions ***************************/ + +static inline void tlb_init_table_entry(struct tlb_client_info *entry, int save_load) +{ + if (save_load) { + entry->load_history = 1 + entry->tx_bytes / + BOND_TLB_REBALANCE_INTERVAL; + entry->tx_bytes = 0; + } + + entry->tx_slave = NULL; + entry->next = TLB_NULL_INDEX; + entry->prev = TLB_NULL_INDEX; +} + +static inline void tlb_init_slave(struct slave *slave) +{ + SLAVE_TLB_INFO(slave).load = 0; + SLAVE_TLB_INFO(slave).head = TLB_NULL_INDEX; +} + +static void __tlb_clear_slave(struct bonding *bond, struct slave *slave, + int save_load) +{ + struct tlb_client_info *tx_hash_table; + u32 index; + + /* clear slave from tx_hashtbl */ + tx_hash_table = BOND_ALB_INFO(bond).tx_hashtbl; + + /* skip this if we've already freed the tx hash table */ + if (tx_hash_table) { + index = SLAVE_TLB_INFO(slave).head; + while (index != TLB_NULL_INDEX) { + u32 next_index = tx_hash_table[index].next; + + tlb_init_table_entry(&tx_hash_table[index], save_load); + index = next_index; + } + } + + tlb_init_slave(slave); +} + +static void tlb_clear_slave(struct bonding *bond, struct slave *slave, + int save_load) +{ + spin_lock_bh(&bond->mode_lock); + __tlb_clear_slave(bond, slave, save_load); + spin_unlock_bh(&bond->mode_lock); +} + +/* Must be called before starting the monitor timer */ +static int tlb_initialize(struct bonding *bond) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + int size = TLB_HASH_TABLE_SIZE * sizeof(struct tlb_client_info); + struct tlb_client_info *new_hashtbl; + int i; + + new_hashtbl = kzalloc(size, GFP_KERNEL); + if (!new_hashtbl) + return -ENOMEM; + + spin_lock_bh(&bond->mode_lock); + + bond_info->tx_hashtbl = new_hashtbl; + + for (i = 0; i < TLB_HASH_TABLE_SIZE; i++) + tlb_init_table_entry(&bond_info->tx_hashtbl[i], 0); + + spin_unlock_bh(&bond->mode_lock); + + return 0; +} + +/* Must be called only after all slaves have been released */ +static void tlb_deinitialize(struct bonding *bond) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + + spin_lock_bh(&bond->mode_lock); + + kfree(bond_info->tx_hashtbl); + bond_info->tx_hashtbl = NULL; + + spin_unlock_bh(&bond->mode_lock); +} + +static long long compute_gap(struct slave *slave) +{ + return (s64) (slave->speed << 20) - /* Convert to Megabit per sec */ + (s64) (SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */ +} + +static struct slave *tlb_get_least_loaded_slave(struct bonding *bond) +{ + struct slave *slave, *least_loaded; + struct list_head *iter; + long long max_gap; + + least_loaded = NULL; + max_gap = LLONG_MIN; + + /* Find the slave with the largest gap */ + bond_for_each_slave_rcu(bond, slave, iter) { + if (bond_slave_can_tx(slave)) { + long long gap = compute_gap(slave); + + if (max_gap < gap) { + least_loaded = slave; + max_gap = gap; + } + } + } + + return least_loaded; +} + +static struct slave *__tlb_choose_channel(struct bonding *bond, u32 hash_index, + u32 skb_len) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct tlb_client_info *hash_table; + struct slave *assigned_slave; + + hash_table = bond_info->tx_hashtbl; + assigned_slave = hash_table[hash_index].tx_slave; + if (!assigned_slave) { + assigned_slave = tlb_get_least_loaded_slave(bond); + + if (assigned_slave) { + struct tlb_slave_info *slave_info = + &(SLAVE_TLB_INFO(assigned_slave)); + u32 next_index = slave_info->head; + + hash_table[hash_index].tx_slave = assigned_slave; + hash_table[hash_index].next = next_index; + hash_table[hash_index].prev = TLB_NULL_INDEX; + + if (next_index != TLB_NULL_INDEX) + hash_table[next_index].prev = hash_index; + + slave_info->head = hash_index; + slave_info->load += + hash_table[hash_index].load_history; + } + } + + if (assigned_slave) + hash_table[hash_index].tx_bytes += skb_len; + + return assigned_slave; +} + +static struct slave *tlb_choose_channel(struct bonding *bond, u32 hash_index, + u32 skb_len) +{ + struct slave *tx_slave; + + /* We don't need to disable softirq here, because + * tlb_choose_channel() is only called by bond_alb_xmit() + * which already has softirq disabled. + */ + spin_lock(&bond->mode_lock); + tx_slave = __tlb_choose_channel(bond, hash_index, skb_len); + spin_unlock(&bond->mode_lock); + + return tx_slave; +} + +/*********************** rlb specific functions ***************************/ + +/* when an ARP REPLY is received from a client update its info + * in the rx_hashtbl + */ +static void rlb_update_entry_from_arp(struct bonding *bond, struct arp_pkt *arp) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct rlb_client_info *client_info; + u32 hash_index; + + spin_lock_bh(&bond->mode_lock); + + hash_index = _simple_hash((u8 *)&(arp->ip_src), sizeof(arp->ip_src)); + client_info = &(bond_info->rx_hashtbl[hash_index]); + + if ((client_info->assigned) && + (client_info->ip_src == arp->ip_dst) && + (client_info->ip_dst == arp->ip_src) && + (!ether_addr_equal_64bits(client_info->mac_dst, arp->mac_src))) { + /* update the clients MAC address */ + ether_addr_copy(client_info->mac_dst, arp->mac_src); + client_info->ntt = 1; + bond_info->rx_ntt = 1; + } + + spin_unlock_bh(&bond->mode_lock); +} + +static int rlb_arp_recv(const struct sk_buff *skb, struct bonding *bond, + struct slave *slave) +{ + struct arp_pkt *arp, _arp; + + if (skb->protocol != cpu_to_be16(ETH_P_ARP)) + goto out; + + arp = skb_header_pointer(skb, 0, sizeof(_arp), &_arp); + if (!arp) + goto out; + + /* We received an ARP from arp->ip_src. + * We might have used this IP address previously (on the bonding host + * itself or on a system that is bridged together with the bond). + * However, if arp->mac_src is different than what is stored in + * rx_hashtbl, some other host is now using the IP and we must prevent + * sending out client updates with this IP address and the old MAC + * address. + * Clean up all hash table entries that have this address as ip_src but + * have a different mac_src. + */ + rlb_purge_src_ip(bond, arp); + + if (arp->op_code == htons(ARPOP_REPLY)) { + /* update rx hash table for this ARP */ + rlb_update_entry_from_arp(bond, arp); + slave_dbg(bond->dev, slave->dev, "Server received an ARP Reply from client\n"); + } +out: + return RX_HANDLER_ANOTHER; +} + +/* Caller must hold rcu_read_lock() */ +static struct slave *__rlb_next_rx_slave(struct bonding *bond) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct slave *before = NULL, *rx_slave = NULL, *slave; + struct list_head *iter; + bool found = false; + + bond_for_each_slave_rcu(bond, slave, iter) { + if (!bond_slave_can_tx(slave)) + continue; + if (!found) { + if (!before || before->speed < slave->speed) + before = slave; + } else { + if (!rx_slave || rx_slave->speed < slave->speed) + rx_slave = slave; + } + if (slave == bond_info->rx_slave) + found = true; + } + /* we didn't find anything after the current or we have something + * better before and up to the current slave + */ + if (!rx_slave || (before && rx_slave->speed < before->speed)) + rx_slave = before; + + if (rx_slave) + bond_info->rx_slave = rx_slave; + + return rx_slave; +} + +/* Caller must hold RTNL, rcu_read_lock is obtained only to silence checkers */ +static struct slave *rlb_next_rx_slave(struct bonding *bond) +{ + struct slave *rx_slave; + + ASSERT_RTNL(); + + rcu_read_lock(); + rx_slave = __rlb_next_rx_slave(bond); + rcu_read_unlock(); + + return rx_slave; +} + +/* teach the switch the mac of a disabled slave + * on the primary for fault tolerance + * + * Caller must hold RTNL + */ +static void rlb_teach_disabled_mac_on_primary(struct bonding *bond, + const u8 addr[]) +{ + struct slave *curr_active = rtnl_dereference(bond->curr_active_slave); + + if (!curr_active) + return; + + if (!bond->alb_info.primary_is_promisc) { + if (!dev_set_promiscuity(curr_active->dev, 1)) + bond->alb_info.primary_is_promisc = 1; + else + bond->alb_info.primary_is_promisc = 0; + } + + bond->alb_info.rlb_promisc_timeout_counter = 0; + + alb_send_learning_packets(curr_active, addr, true); +} + +/* slave being removed should not be active at this point + * + * Caller must hold rtnl. + */ +static void rlb_clear_slave(struct bonding *bond, struct slave *slave) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct rlb_client_info *rx_hash_table; + u32 index, next_index; + + /* clear slave from rx_hashtbl */ + spin_lock_bh(&bond->mode_lock); + + rx_hash_table = bond_info->rx_hashtbl; + index = bond_info->rx_hashtbl_used_head; + for (; index != RLB_NULL_INDEX; index = next_index) { + next_index = rx_hash_table[index].used_next; + if (rx_hash_table[index].slave == slave) { + struct slave *assigned_slave = rlb_next_rx_slave(bond); + + if (assigned_slave) { + rx_hash_table[index].slave = assigned_slave; + if (is_valid_ether_addr(rx_hash_table[index].mac_dst)) { + bond_info->rx_hashtbl[index].ntt = 1; + bond_info->rx_ntt = 1; + /* A slave has been removed from the + * table because it is either disabled + * or being released. We must retry the + * update to avoid clients from not + * being updated & disconnecting when + * there is stress + */ + bond_info->rlb_update_retry_counter = + RLB_UPDATE_RETRY; + } + } else { /* there is no active slave */ + rx_hash_table[index].slave = NULL; + } + } + } + + spin_unlock_bh(&bond->mode_lock); + + if (slave != rtnl_dereference(bond->curr_active_slave)) + rlb_teach_disabled_mac_on_primary(bond, slave->dev->dev_addr); +} + +static void rlb_update_client(struct rlb_client_info *client_info) +{ + int i; + + if (!client_info->slave || !is_valid_ether_addr(client_info->mac_dst)) + return; + + for (i = 0; i < RLB_ARP_BURST_SIZE; i++) { + struct sk_buff *skb; + + skb = arp_create(ARPOP_REPLY, ETH_P_ARP, + client_info->ip_dst, + client_info->slave->dev, + client_info->ip_src, + client_info->mac_dst, + client_info->slave->dev->dev_addr, + client_info->mac_dst); + if (!skb) { + slave_err(client_info->slave->bond->dev, + client_info->slave->dev, + "failed to create an ARP packet\n"); + continue; + } + + skb->dev = client_info->slave->dev; + + if (client_info->vlan_id) { + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), + client_info->vlan_id); + } + + arp_xmit(skb); + } +} + +/* sends ARP REPLIES that update the clients that need updating */ +static void rlb_update_rx_clients(struct bonding *bond) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct rlb_client_info *client_info; + u32 hash_index; + + spin_lock_bh(&bond->mode_lock); + + hash_index = bond_info->rx_hashtbl_used_head; + for (; hash_index != RLB_NULL_INDEX; + hash_index = client_info->used_next) { + client_info = &(bond_info->rx_hashtbl[hash_index]); + if (client_info->ntt) { + rlb_update_client(client_info); + if (bond_info->rlb_update_retry_counter == 0) + client_info->ntt = 0; + } + } + + /* do not update the entries again until this counter is zero so that + * not to confuse the clients. + */ + bond_info->rlb_update_delay_counter = RLB_UPDATE_DELAY; + + spin_unlock_bh(&bond->mode_lock); +} + +/* The slave was assigned a new mac address - update the clients */ +static void rlb_req_update_slave_clients(struct bonding *bond, struct slave *slave) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct rlb_client_info *client_info; + int ntt = 0; + u32 hash_index; + + spin_lock_bh(&bond->mode_lock); + + hash_index = bond_info->rx_hashtbl_used_head; + for (; hash_index != RLB_NULL_INDEX; + hash_index = client_info->used_next) { + client_info = &(bond_info->rx_hashtbl[hash_index]); + + if ((client_info->slave == slave) && + is_valid_ether_addr(client_info->mac_dst)) { + client_info->ntt = 1; + ntt = 1; + } + } + + /* update the team's flag only after the whole iteration */ + if (ntt) { + bond_info->rx_ntt = 1; + /* fasten the change */ + bond_info->rlb_update_retry_counter = RLB_UPDATE_RETRY; + } + + spin_unlock_bh(&bond->mode_lock); +} + +/* mark all clients using src_ip to be updated */ +static void rlb_req_update_subnet_clients(struct bonding *bond, __be32 src_ip) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct rlb_client_info *client_info; + u32 hash_index; + + spin_lock(&bond->mode_lock); + + hash_index = bond_info->rx_hashtbl_used_head; + for (; hash_index != RLB_NULL_INDEX; + hash_index = client_info->used_next) { + client_info = &(bond_info->rx_hashtbl[hash_index]); + + if (!client_info->slave) { + netdev_err(bond->dev, "found a client with no channel in the client's hash table\n"); + continue; + } + /* update all clients using this src_ip, that are not assigned + * to the team's address (curr_active_slave) and have a known + * unicast mac address. + */ + if ((client_info->ip_src == src_ip) && + !ether_addr_equal_64bits(client_info->slave->dev->dev_addr, + bond->dev->dev_addr) && + is_valid_ether_addr(client_info->mac_dst)) { + client_info->ntt = 1; + bond_info->rx_ntt = 1; + } + } + + spin_unlock(&bond->mode_lock); +} + +static struct slave *rlb_choose_channel(struct sk_buff *skb, + struct bonding *bond, + const struct arp_pkt *arp) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct slave *assigned_slave, *curr_active_slave; + struct rlb_client_info *client_info; + u32 hash_index = 0; + + spin_lock(&bond->mode_lock); + + curr_active_slave = rcu_dereference(bond->curr_active_slave); + + hash_index = _simple_hash((u8 *)&arp->ip_dst, sizeof(arp->ip_dst)); + client_info = &(bond_info->rx_hashtbl[hash_index]); + + if (client_info->assigned) { + if ((client_info->ip_src == arp->ip_src) && + (client_info->ip_dst == arp->ip_dst)) { + /* the entry is already assigned to this client */ + if (!is_broadcast_ether_addr(arp->mac_dst)) { + /* update mac address from arp */ + ether_addr_copy(client_info->mac_dst, arp->mac_dst); + } + ether_addr_copy(client_info->mac_src, arp->mac_src); + + assigned_slave = client_info->slave; + if (assigned_slave) { + spin_unlock(&bond->mode_lock); + return assigned_slave; + } + } else { + /* the entry is already assigned to some other client, + * move the old client to primary (curr_active_slave) so + * that the new client can be assigned to this entry. + */ + if (curr_active_slave && + client_info->slave != curr_active_slave) { + client_info->slave = curr_active_slave; + rlb_update_client(client_info); + } + } + } + /* assign a new slave */ + assigned_slave = __rlb_next_rx_slave(bond); + + if (assigned_slave) { + if (!(client_info->assigned && + client_info->ip_src == arp->ip_src)) { + /* ip_src is going to be updated, + * fix the src hash list + */ + u32 hash_src = _simple_hash((u8 *)&arp->ip_src, + sizeof(arp->ip_src)); + rlb_src_unlink(bond, hash_index); + rlb_src_link(bond, hash_src, hash_index); + } + + client_info->ip_src = arp->ip_src; + client_info->ip_dst = arp->ip_dst; + /* arp->mac_dst is broadcast for arp requests. + * will be updated with clients actual unicast mac address + * upon receiving an arp reply. + */ + ether_addr_copy(client_info->mac_dst, arp->mac_dst); + ether_addr_copy(client_info->mac_src, arp->mac_src); + client_info->slave = assigned_slave; + + if (is_valid_ether_addr(client_info->mac_dst)) { + client_info->ntt = 1; + bond->alb_info.rx_ntt = 1; + } else { + client_info->ntt = 0; + } + + if (vlan_get_tag(skb, &client_info->vlan_id)) + client_info->vlan_id = 0; + + if (!client_info->assigned) { + u32 prev_tbl_head = bond_info->rx_hashtbl_used_head; + + bond_info->rx_hashtbl_used_head = hash_index; + client_info->used_next = prev_tbl_head; + if (prev_tbl_head != RLB_NULL_INDEX) { + bond_info->rx_hashtbl[prev_tbl_head].used_prev = + hash_index; + } + client_info->assigned = 1; + } + } + + spin_unlock(&bond->mode_lock); + + return assigned_slave; +} + +/* chooses (and returns) transmit channel for arp reply + * does not choose channel for other arp types since they are + * sent on the curr_active_slave + */ +static struct slave *rlb_arp_xmit(struct sk_buff *skb, struct bonding *bond) +{ + struct slave *tx_slave = NULL; + struct net_device *dev; + struct arp_pkt *arp; + + if (!pskb_network_may_pull(skb, sizeof(*arp))) + return NULL; + arp = (struct arp_pkt *)skb_network_header(skb); + + /* Don't modify or load balance ARPs that do not originate + * from the bond itself or a VLAN directly above the bond. + */ + if (!bond_slave_has_mac_rcu(bond, arp->mac_src)) + return NULL; + + dev = ip_dev_find(dev_net(bond->dev), arp->ip_src); + if (dev) { + if (netif_is_any_bridge_master(dev)) { + dev_put(dev); + return NULL; + } + dev_put(dev); + } + + if (arp->op_code == htons(ARPOP_REPLY)) { + /* the arp must be sent on the selected rx channel */ + tx_slave = rlb_choose_channel(skb, bond, arp); + if (tx_slave) + bond_hw_addr_copy(arp->mac_src, tx_slave->dev->dev_addr, + tx_slave->dev->addr_len); + netdev_dbg(bond->dev, "(slave %s): Server sent ARP Reply packet\n", + tx_slave ? tx_slave->dev->name : "NULL"); + } else if (arp->op_code == htons(ARPOP_REQUEST)) { + /* Create an entry in the rx_hashtbl for this client as a + * place holder. + * When the arp reply is received the entry will be updated + * with the correct unicast address of the client. + */ + tx_slave = rlb_choose_channel(skb, bond, arp); + + /* The ARP reply packets must be delayed so that + * they can cancel out the influence of the ARP request. + */ + bond->alb_info.rlb_update_delay_counter = RLB_UPDATE_DELAY; + + /* arp requests are broadcast and are sent on the primary + * the arp request will collapse all clients on the subnet to + * the primary slave. We must register these clients to be + * updated with their assigned mac. + */ + rlb_req_update_subnet_clients(bond, arp->ip_src); + netdev_dbg(bond->dev, "(slave %s): Server sent ARP Request packet\n", + tx_slave ? tx_slave->dev->name : "NULL"); + } + + return tx_slave; +} + +static void rlb_rebalance(struct bonding *bond) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct slave *assigned_slave; + struct rlb_client_info *client_info; + int ntt; + u32 hash_index; + + spin_lock_bh(&bond->mode_lock); + + ntt = 0; + hash_index = bond_info->rx_hashtbl_used_head; + for (; hash_index != RLB_NULL_INDEX; + hash_index = client_info->used_next) { + client_info = &(bond_info->rx_hashtbl[hash_index]); + assigned_slave = __rlb_next_rx_slave(bond); + if (assigned_slave && (client_info->slave != assigned_slave)) { + client_info->slave = assigned_slave; + if (!is_zero_ether_addr(client_info->mac_dst)) { + client_info->ntt = 1; + ntt = 1; + } + } + } + + /* update the team's flag only after the whole iteration */ + if (ntt) + bond_info->rx_ntt = 1; + spin_unlock_bh(&bond->mode_lock); +} + +/* Caller must hold mode_lock */ +static void rlb_init_table_entry_dst(struct rlb_client_info *entry) +{ + entry->used_next = RLB_NULL_INDEX; + entry->used_prev = RLB_NULL_INDEX; + entry->assigned = 0; + entry->slave = NULL; + entry->vlan_id = 0; +} +static void rlb_init_table_entry_src(struct rlb_client_info *entry) +{ + entry->src_first = RLB_NULL_INDEX; + entry->src_prev = RLB_NULL_INDEX; + entry->src_next = RLB_NULL_INDEX; +} + +static void rlb_init_table_entry(struct rlb_client_info *entry) +{ + memset(entry, 0, sizeof(struct rlb_client_info)); + rlb_init_table_entry_dst(entry); + rlb_init_table_entry_src(entry); +} + +static void rlb_delete_table_entry_dst(struct bonding *bond, u32 index) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + u32 next_index = bond_info->rx_hashtbl[index].used_next; + u32 prev_index = bond_info->rx_hashtbl[index].used_prev; + + if (index == bond_info->rx_hashtbl_used_head) + bond_info->rx_hashtbl_used_head = next_index; + if (prev_index != RLB_NULL_INDEX) + bond_info->rx_hashtbl[prev_index].used_next = next_index; + if (next_index != RLB_NULL_INDEX) + bond_info->rx_hashtbl[next_index].used_prev = prev_index; +} + +/* unlink a rlb hash table entry from the src list */ +static void rlb_src_unlink(struct bonding *bond, u32 index) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + u32 next_index = bond_info->rx_hashtbl[index].src_next; + u32 prev_index = bond_info->rx_hashtbl[index].src_prev; + + bond_info->rx_hashtbl[index].src_next = RLB_NULL_INDEX; + bond_info->rx_hashtbl[index].src_prev = RLB_NULL_INDEX; + + if (next_index != RLB_NULL_INDEX) + bond_info->rx_hashtbl[next_index].src_prev = prev_index; + + if (prev_index == RLB_NULL_INDEX) + return; + + /* is prev_index pointing to the head of this list? */ + if (bond_info->rx_hashtbl[prev_index].src_first == index) + bond_info->rx_hashtbl[prev_index].src_first = next_index; + else + bond_info->rx_hashtbl[prev_index].src_next = next_index; + +} + +static void rlb_delete_table_entry(struct bonding *bond, u32 index) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct rlb_client_info *entry = &(bond_info->rx_hashtbl[index]); + + rlb_delete_table_entry_dst(bond, index); + rlb_init_table_entry_dst(entry); + + rlb_src_unlink(bond, index); +} + +/* add the rx_hashtbl[ip_dst_hash] entry to the list + * of entries with identical ip_src_hash + */ +static void rlb_src_link(struct bonding *bond, u32 ip_src_hash, u32 ip_dst_hash) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + u32 next; + + bond_info->rx_hashtbl[ip_dst_hash].src_prev = ip_src_hash; + next = bond_info->rx_hashtbl[ip_src_hash].src_first; + bond_info->rx_hashtbl[ip_dst_hash].src_next = next; + if (next != RLB_NULL_INDEX) + bond_info->rx_hashtbl[next].src_prev = ip_dst_hash; + bond_info->rx_hashtbl[ip_src_hash].src_first = ip_dst_hash; +} + +/* deletes all rx_hashtbl entries with arp->ip_src if their mac_src does + * not match arp->mac_src + */ +static void rlb_purge_src_ip(struct bonding *bond, struct arp_pkt *arp) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + u32 ip_src_hash = _simple_hash((u8 *)&(arp->ip_src), sizeof(arp->ip_src)); + u32 index; + + spin_lock_bh(&bond->mode_lock); + + index = bond_info->rx_hashtbl[ip_src_hash].src_first; + while (index != RLB_NULL_INDEX) { + struct rlb_client_info *entry = &(bond_info->rx_hashtbl[index]); + u32 next_index = entry->src_next; + + if (entry->ip_src == arp->ip_src && + !ether_addr_equal_64bits(arp->mac_src, entry->mac_src)) + rlb_delete_table_entry(bond, index); + index = next_index; + } + spin_unlock_bh(&bond->mode_lock); +} + +static int rlb_initialize(struct bonding *bond) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct rlb_client_info *new_hashtbl; + int size = RLB_HASH_TABLE_SIZE * sizeof(struct rlb_client_info); + int i; + + new_hashtbl = kmalloc(size, GFP_KERNEL); + if (!new_hashtbl) + return -1; + + spin_lock_bh(&bond->mode_lock); + + bond_info->rx_hashtbl = new_hashtbl; + + bond_info->rx_hashtbl_used_head = RLB_NULL_INDEX; + + for (i = 0; i < RLB_HASH_TABLE_SIZE; i++) + rlb_init_table_entry(bond_info->rx_hashtbl + i); + + spin_unlock_bh(&bond->mode_lock); + + /* register to receive ARPs */ + bond->recv_probe = rlb_arp_recv; + + return 0; +} + +static void rlb_deinitialize(struct bonding *bond) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + + spin_lock_bh(&bond->mode_lock); + + kfree(bond_info->rx_hashtbl); + bond_info->rx_hashtbl = NULL; + bond_info->rx_hashtbl_used_head = RLB_NULL_INDEX; + + spin_unlock_bh(&bond->mode_lock); +} + +static void rlb_clear_vlan(struct bonding *bond, unsigned short vlan_id) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + u32 curr_index; + + spin_lock_bh(&bond->mode_lock); + + curr_index = bond_info->rx_hashtbl_used_head; + while (curr_index != RLB_NULL_INDEX) { + struct rlb_client_info *curr = &(bond_info->rx_hashtbl[curr_index]); + u32 next_index = bond_info->rx_hashtbl[curr_index].used_next; + + if (curr->vlan_id == vlan_id) + rlb_delete_table_entry(bond, curr_index); + + curr_index = next_index; + } + + spin_unlock_bh(&bond->mode_lock); +} + +/*********************** tlb/rlb shared functions *********************/ + +static void alb_send_lp_vid(struct slave *slave, const u8 mac_addr[], + __be16 vlan_proto, u16 vid) +{ + struct learning_pkt pkt; + struct sk_buff *skb; + int size = sizeof(struct learning_pkt); + + memset(&pkt, 0, size); + ether_addr_copy(pkt.mac_dst, mac_addr); + ether_addr_copy(pkt.mac_src, mac_addr); + pkt.type = cpu_to_be16(ETH_P_LOOPBACK); + + skb = dev_alloc_skb(size); + if (!skb) + return; + + skb_put_data(skb, &pkt, size); + + skb_reset_mac_header(skb); + skb->network_header = skb->mac_header + ETH_HLEN; + skb->protocol = pkt.type; + skb->priority = TC_PRIO_CONTROL; + skb->dev = slave->dev; + + slave_dbg(slave->bond->dev, slave->dev, + "Send learning packet: mac %pM vlan %d\n", mac_addr, vid); + + if (vid) + __vlan_hwaccel_put_tag(skb, vlan_proto, vid); + + dev_queue_xmit(skb); +} + +struct alb_walk_data { + struct bonding *bond; + struct slave *slave; + const u8 *mac_addr; + bool strict_match; +}; + +static int alb_upper_dev_walk(struct net_device *upper, + struct netdev_nested_priv *priv) +{ + struct alb_walk_data *data = (struct alb_walk_data *)priv->data; + bool strict_match = data->strict_match; + const u8 *mac_addr = data->mac_addr; + struct bonding *bond = data->bond; + struct slave *slave = data->slave; + struct bond_vlan_tag *tags; + + if (is_vlan_dev(upper) && + bond->dev->lower_level == upper->lower_level - 1) { + if (upper->addr_assign_type == NET_ADDR_STOLEN) { + alb_send_lp_vid(slave, mac_addr, + vlan_dev_vlan_proto(upper), + vlan_dev_vlan_id(upper)); + } else { + alb_send_lp_vid(slave, upper->dev_addr, + vlan_dev_vlan_proto(upper), + vlan_dev_vlan_id(upper)); + } + } + + /* If this is a macvlan device, then only send updates + * when strict_match is turned off. + */ + if (netif_is_macvlan(upper) && !strict_match) { + tags = bond_verify_device_path(bond->dev, upper, 0); + if (IS_ERR_OR_NULL(tags)) + return -ENOMEM; + + alb_send_lp_vid(slave, upper->dev_addr, + tags[0].vlan_proto, tags[0].vlan_id); + kfree(tags); + } + + return 0; +} + +static void alb_send_learning_packets(struct slave *slave, const u8 mac_addr[], + bool strict_match) +{ + struct bonding *bond = bond_get_bond_by_slave(slave); + struct netdev_nested_priv priv; + struct alb_walk_data data = { + .strict_match = strict_match, + .mac_addr = mac_addr, + .slave = slave, + .bond = bond, + }; + + priv.data = (void *)&data; + /* send untagged */ + alb_send_lp_vid(slave, mac_addr, 0, 0); + + /* loop through all devices and see if we need to send a packet + * for that device. + */ + rcu_read_lock(); + netdev_walk_all_upper_dev_rcu(bond->dev, alb_upper_dev_walk, &priv); + rcu_read_unlock(); +} + +static int alb_set_slave_mac_addr(struct slave *slave, const u8 addr[], + unsigned int len) +{ + struct net_device *dev = slave->dev; + struct sockaddr_storage ss; + + if (BOND_MODE(slave->bond) == BOND_MODE_TLB) { + __dev_addr_set(dev, addr, len); + return 0; + } + + /* for rlb each slave must have a unique hw mac addresses so that + * each slave will receive packets destined to a different mac + */ + memcpy(ss.__data, addr, len); + ss.ss_family = dev->type; + if (dev_set_mac_address(dev, &ss, NULL)) { + slave_err(slave->bond->dev, dev, "dev_set_mac_address on slave failed! ALB mode requires that the base driver support setting the hw address also when the network device's interface is open\n"); + return -EOPNOTSUPP; + } + return 0; +} + +/* Swap MAC addresses between two slaves. + * + * Called with RTNL held, and no other locks. + */ +static void alb_swap_mac_addr(struct slave *slave1, struct slave *slave2) +{ + u8 tmp_mac_addr[MAX_ADDR_LEN]; + + bond_hw_addr_copy(tmp_mac_addr, slave1->dev->dev_addr, + slave1->dev->addr_len); + alb_set_slave_mac_addr(slave1, slave2->dev->dev_addr, + slave2->dev->addr_len); + alb_set_slave_mac_addr(slave2, tmp_mac_addr, + slave1->dev->addr_len); + +} + +/* Send learning packets after MAC address swap. + * + * Called with RTNL and no other locks + */ +static void alb_fasten_mac_swap(struct bonding *bond, struct slave *slave1, + struct slave *slave2) +{ + int slaves_state_differ = (bond_slave_can_tx(slave1) != bond_slave_can_tx(slave2)); + struct slave *disabled_slave = NULL; + + ASSERT_RTNL(); + + /* fasten the change in the switch */ + if (bond_slave_can_tx(slave1)) { + alb_send_learning_packets(slave1, slave1->dev->dev_addr, false); + if (bond->alb_info.rlb_enabled) { + /* inform the clients that the mac address + * has changed + */ + rlb_req_update_slave_clients(bond, slave1); + } + } else { + disabled_slave = slave1; + } + + if (bond_slave_can_tx(slave2)) { + alb_send_learning_packets(slave2, slave2->dev->dev_addr, false); + if (bond->alb_info.rlb_enabled) { + /* inform the clients that the mac address + * has changed + */ + rlb_req_update_slave_clients(bond, slave2); + } + } else { + disabled_slave = slave2; + } + + if (bond->alb_info.rlb_enabled && slaves_state_differ) { + /* A disabled slave was assigned an active mac addr */ + rlb_teach_disabled_mac_on_primary(bond, + disabled_slave->dev->dev_addr); + } +} + +/** + * alb_change_hw_addr_on_detach + * @bond: bonding we're working on + * @slave: the slave that was just detached + * + * We assume that @slave was already detached from the slave list. + * + * If @slave's permanent hw address is different both from its current + * address and from @bond's address, then somewhere in the bond there's + * a slave that has @slave's permanet address as its current address. + * We'll make sure that slave no longer uses @slave's permanent address. + * + * Caller must hold RTNL and no other locks + */ +static void alb_change_hw_addr_on_detach(struct bonding *bond, struct slave *slave) +{ + int perm_curr_diff; + int perm_bond_diff; + struct slave *found_slave; + + perm_curr_diff = !ether_addr_equal_64bits(slave->perm_hwaddr, + slave->dev->dev_addr); + perm_bond_diff = !ether_addr_equal_64bits(slave->perm_hwaddr, + bond->dev->dev_addr); + + if (perm_curr_diff && perm_bond_diff) { + found_slave = bond_slave_has_mac(bond, slave->perm_hwaddr); + + if (found_slave) { + alb_swap_mac_addr(slave, found_slave); + alb_fasten_mac_swap(bond, slave, found_slave); + } + } +} + +/** + * alb_handle_addr_collision_on_attach + * @bond: bonding we're working on + * @slave: the slave that was just attached + * + * checks uniqueness of slave's mac address and handles the case the + * new slave uses the bonds mac address. + * + * If the permanent hw address of @slave is @bond's hw address, we need to + * find a different hw address to give @slave, that isn't in use by any other + * slave in the bond. This address must be, of course, one of the permanent + * addresses of the other slaves. + * + * We go over the slave list, and for each slave there we compare its + * permanent hw address with the current address of all the other slaves. + * If no match was found, then we've found a slave with a permanent address + * that isn't used by any other slave in the bond, so we can assign it to + * @slave. + * + * assumption: this function is called before @slave is attached to the + * bond slave list. + */ +static int alb_handle_addr_collision_on_attach(struct bonding *bond, struct slave *slave) +{ + struct slave *has_bond_addr = rcu_access_pointer(bond->curr_active_slave); + struct slave *tmp_slave1, *free_mac_slave = NULL; + struct list_head *iter; + + if (!bond_has_slaves(bond)) { + /* this is the first slave */ + return 0; + } + + /* if slave's mac address differs from bond's mac address + * check uniqueness of slave's mac address against the other + * slaves in the bond. + */ + if (!ether_addr_equal_64bits(slave->perm_hwaddr, bond->dev->dev_addr)) { + if (!bond_slave_has_mac(bond, slave->dev->dev_addr)) + return 0; + + /* Try setting slave mac to bond address and fall-through + * to code handling that situation below... + */ + alb_set_slave_mac_addr(slave, bond->dev->dev_addr, + bond->dev->addr_len); + } + + /* The slave's address is equal to the address of the bond. + * Search for a spare address in the bond for this slave. + */ + bond_for_each_slave(bond, tmp_slave1, iter) { + if (!bond_slave_has_mac(bond, tmp_slave1->perm_hwaddr)) { + /* no slave has tmp_slave1's perm addr + * as its curr addr + */ + free_mac_slave = tmp_slave1; + break; + } + + if (!has_bond_addr) { + if (ether_addr_equal_64bits(tmp_slave1->dev->dev_addr, + bond->dev->dev_addr)) { + + has_bond_addr = tmp_slave1; + } + } + } + + if (free_mac_slave) { + alb_set_slave_mac_addr(slave, free_mac_slave->perm_hwaddr, + free_mac_slave->dev->addr_len); + + slave_warn(bond->dev, slave->dev, "the slave hw address is in use by the bond; giving it the hw address of %s\n", + free_mac_slave->dev->name); + + } else if (has_bond_addr) { + slave_err(bond->dev, slave->dev, "the slave hw address is in use by the bond; couldn't find a slave with a free hw address to give it (this should not have happened)\n"); + return -EFAULT; + } + + return 0; +} + +/** + * alb_set_mac_address + * @bond: bonding we're working on + * @addr: MAC address to set + * + * In TLB mode all slaves are configured to the bond's hw address, but set + * their dev_addr field to different addresses (based on their permanent hw + * addresses). + * + * For each slave, this function sets the interface to the new address and then + * changes its dev_addr field to its previous value. + * + * Unwinding assumes bond's mac address has not yet changed. + */ +static int alb_set_mac_address(struct bonding *bond, void *addr) +{ + struct slave *slave, *rollback_slave; + struct list_head *iter; + struct sockaddr_storage ss; + char tmp_addr[MAX_ADDR_LEN]; + int res; + + if (bond->alb_info.rlb_enabled) + return 0; + + bond_for_each_slave(bond, slave, iter) { + /* save net_device's current hw address */ + bond_hw_addr_copy(tmp_addr, slave->dev->dev_addr, + slave->dev->addr_len); + + res = dev_set_mac_address(slave->dev, addr, NULL); + + /* restore net_device's hw address */ + dev_addr_set(slave->dev, tmp_addr); + + if (res) + goto unwind; + } + + return 0; + +unwind: + memcpy(ss.__data, bond->dev->dev_addr, bond->dev->addr_len); + ss.ss_family = bond->dev->type; + + /* unwind from head to the slave that failed */ + bond_for_each_slave(bond, rollback_slave, iter) { + if (rollback_slave == slave) + break; + bond_hw_addr_copy(tmp_addr, rollback_slave->dev->dev_addr, + rollback_slave->dev->addr_len); + dev_set_mac_address(rollback_slave->dev, &ss, NULL); + dev_addr_set(rollback_slave->dev, tmp_addr); + } + + return res; +} + +/* determine if the packet is NA or NS */ +static bool alb_determine_nd(struct sk_buff *skb, struct bonding *bond) +{ + struct ipv6hdr *ip6hdr; + struct icmp6hdr *hdr; + + if (!pskb_network_may_pull(skb, sizeof(*ip6hdr))) + return true; + + ip6hdr = ipv6_hdr(skb); + if (ip6hdr->nexthdr != IPPROTO_ICMPV6) + return false; + + if (!pskb_network_may_pull(skb, sizeof(*ip6hdr) + sizeof(*hdr))) + return true; + + hdr = icmp6_hdr(skb); + return hdr->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT || + hdr->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION; +} + +/************************ exported alb functions ************************/ + +int bond_alb_initialize(struct bonding *bond, int rlb_enabled) +{ + int res; + + res = tlb_initialize(bond); + if (res) + return res; + + if (rlb_enabled) { + res = rlb_initialize(bond); + if (res) { + tlb_deinitialize(bond); + return res; + } + bond->alb_info.rlb_enabled = 1; + } else { + bond->alb_info.rlb_enabled = 0; + } + + return 0; +} + +void bond_alb_deinitialize(struct bonding *bond) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + + tlb_deinitialize(bond); + + if (bond_info->rlb_enabled) + rlb_deinitialize(bond); +} + +static netdev_tx_t bond_do_alb_xmit(struct sk_buff *skb, struct bonding *bond, + struct slave *tx_slave) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct ethhdr *eth_data = eth_hdr(skb); + + if (!tx_slave) { + /* unbalanced or unassigned, send through primary */ + tx_slave = rcu_dereference(bond->curr_active_slave); + if (bond->params.tlb_dynamic_lb) + bond_info->unbalanced_load += skb->len; + } + + if (tx_slave && bond_slave_can_tx(tx_slave)) { + if (tx_slave != rcu_access_pointer(bond->curr_active_slave)) { + ether_addr_copy(eth_data->h_source, + tx_slave->dev->dev_addr); + } + + return bond_dev_queue_xmit(bond, skb, tx_slave->dev); + } + + if (tx_slave && bond->params.tlb_dynamic_lb) { + spin_lock(&bond->mode_lock); + __tlb_clear_slave(bond, tx_slave, 0); + spin_unlock(&bond->mode_lock); + } + + /* no suitable interface, frame not sent */ + return bond_tx_drop(bond->dev, skb); +} + +struct slave *bond_xmit_tlb_slave_get(struct bonding *bond, + struct sk_buff *skb) +{ + struct slave *tx_slave = NULL; + struct ethhdr *eth_data; + u32 hash_index; + + skb_reset_mac_header(skb); + eth_data = eth_hdr(skb); + + /* Do not TX balance any multicast or broadcast */ + if (!is_multicast_ether_addr(eth_data->h_dest)) { + switch (skb->protocol) { + case htons(ETH_P_IPV6): + if (alb_determine_nd(skb, bond)) + break; + fallthrough; + case htons(ETH_P_IP): + hash_index = bond_xmit_hash(bond, skb); + if (bond->params.tlb_dynamic_lb) { + tx_slave = tlb_choose_channel(bond, + hash_index & 0xFF, + skb->len); + } else { + struct bond_up_slave *slaves; + unsigned int count; + + slaves = rcu_dereference(bond->usable_slaves); + count = slaves ? READ_ONCE(slaves->count) : 0; + if (likely(count)) + tx_slave = slaves->arr[hash_index % + count]; + } + break; + } + } + return tx_slave; +} + +netdev_tx_t bond_tlb_xmit(struct sk_buff *skb, struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct slave *tx_slave; + + tx_slave = bond_xmit_tlb_slave_get(bond, skb); + return bond_do_alb_xmit(skb, bond, tx_slave); +} + +struct slave *bond_xmit_alb_slave_get(struct bonding *bond, + struct sk_buff *skb) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + static const __be32 ip_bcast = htonl(0xffffffff); + struct slave *tx_slave = NULL; + const u8 *hash_start = NULL; + bool do_tx_balance = true; + struct ethhdr *eth_data; + u32 hash_index = 0; + int hash_size = 0; + + skb_reset_mac_header(skb); + eth_data = eth_hdr(skb); + + switch (ntohs(skb->protocol)) { + case ETH_P_IP: { + const struct iphdr *iph; + + if (is_broadcast_ether_addr(eth_data->h_dest) || + !pskb_network_may_pull(skb, sizeof(*iph))) { + do_tx_balance = false; + break; + } + iph = ip_hdr(skb); + if (iph->daddr == ip_bcast || iph->protocol == IPPROTO_IGMP) { + do_tx_balance = false; + break; + } + hash_start = (char *)&(iph->daddr); + hash_size = sizeof(iph->daddr); + break; + } + case ETH_P_IPV6: { + const struct ipv6hdr *ip6hdr; + + /* IPv6 doesn't really use broadcast mac address, but leave + * that here just in case. + */ + if (is_broadcast_ether_addr(eth_data->h_dest)) { + do_tx_balance = false; + break; + } + + /* IPv6 uses all-nodes multicast as an equivalent to + * broadcasts in IPv4. + */ + if (ether_addr_equal_64bits(eth_data->h_dest, mac_v6_allmcast)) { + do_tx_balance = false; + break; + } + + if (alb_determine_nd(skb, bond)) { + do_tx_balance = false; + break; + } + + /* The IPv6 header is pulled by alb_determine_nd */ + /* Additionally, DAD probes should not be tx-balanced as that + * will lead to false positives for duplicate addresses and + * prevent address configuration from working. + */ + ip6hdr = ipv6_hdr(skb); + if (ipv6_addr_any(&ip6hdr->saddr)) { + do_tx_balance = false; + break; + } + + hash_start = (char *)&ip6hdr->daddr; + hash_size = sizeof(ip6hdr->daddr); + break; + } + case ETH_P_ARP: + do_tx_balance = false; + if (bond_info->rlb_enabled) + tx_slave = rlb_arp_xmit(skb, bond); + break; + default: + do_tx_balance = false; + break; + } + + if (do_tx_balance) { + if (bond->params.tlb_dynamic_lb) { + hash_index = _simple_hash(hash_start, hash_size); + tx_slave = tlb_choose_channel(bond, hash_index, skb->len); + } else { + /* + * do_tx_balance means we are free to select the tx_slave + * So we do exactly what tlb would do for hash selection + */ + + struct bond_up_slave *slaves; + unsigned int count; + + slaves = rcu_dereference(bond->usable_slaves); + count = slaves ? READ_ONCE(slaves->count) : 0; + if (likely(count)) + tx_slave = slaves->arr[bond_xmit_hash(bond, skb) % + count]; + } + } + return tx_slave; +} + +netdev_tx_t bond_alb_xmit(struct sk_buff *skb, struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct slave *tx_slave = NULL; + + tx_slave = bond_xmit_alb_slave_get(bond, skb); + return bond_do_alb_xmit(skb, bond, tx_slave); +} + +void bond_alb_monitor(struct work_struct *work) +{ + struct bonding *bond = container_of(work, struct bonding, + alb_work.work); + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct slave *slave, *curr; + struct list_head *iter; + + if (!bond_has_slaves(bond)) { + atomic_set(&bond_info->tx_rebalance_counter, 0); + bond_info->lp_counter = 0; + goto re_arm; + } + + rcu_read_lock(); + + atomic_inc(&bond_info->tx_rebalance_counter); + bond_info->lp_counter++; + + /* send learning packets */ + if (bond_info->lp_counter >= BOND_ALB_LP_TICKS(bond)) { + bool strict_match; + + bond_for_each_slave_rcu(bond, slave, iter) { + /* If updating current_active, use all currently + * user mac addresses (!strict_match). Otherwise, only + * use mac of the slave device. + * In RLB mode, we always use strict matches. + */ + strict_match = (slave != rcu_access_pointer(bond->curr_active_slave) || + bond_info->rlb_enabled); + alb_send_learning_packets(slave, slave->dev->dev_addr, + strict_match); + } + bond_info->lp_counter = 0; + } + + /* rebalance tx traffic */ + if (atomic_read(&bond_info->tx_rebalance_counter) >= BOND_TLB_REBALANCE_TICKS) { + bond_for_each_slave_rcu(bond, slave, iter) { + tlb_clear_slave(bond, slave, 1); + if (slave == rcu_access_pointer(bond->curr_active_slave)) { + SLAVE_TLB_INFO(slave).load = + bond_info->unbalanced_load / + BOND_TLB_REBALANCE_INTERVAL; + bond_info->unbalanced_load = 0; + } + } + atomic_set(&bond_info->tx_rebalance_counter, 0); + } + + if (bond_info->rlb_enabled) { + if (bond_info->primary_is_promisc && + (++bond_info->rlb_promisc_timeout_counter >= RLB_PROMISC_TIMEOUT)) { + + /* dev_set_promiscuity requires rtnl and + * nothing else. Avoid race with bond_close. + */ + rcu_read_unlock(); + if (!rtnl_trylock()) + goto re_arm; + + bond_info->rlb_promisc_timeout_counter = 0; + + /* If the primary was set to promiscuous mode + * because a slave was disabled then + * it can now leave promiscuous mode. + */ + curr = rtnl_dereference(bond->curr_active_slave); + if (bond_info->primary_is_promisc && curr) { + dev_set_promiscuity(curr->dev, -1); + bond_info->primary_is_promisc = 0; + } + + rtnl_unlock(); + rcu_read_lock(); + } + + if (bond_info->rlb_rebalance) { + bond_info->rlb_rebalance = 0; + rlb_rebalance(bond); + } + + /* check if clients need updating */ + if (bond_info->rx_ntt) { + if (bond_info->rlb_update_delay_counter) { + --bond_info->rlb_update_delay_counter; + } else { + rlb_update_rx_clients(bond); + if (bond_info->rlb_update_retry_counter) + --bond_info->rlb_update_retry_counter; + else + bond_info->rx_ntt = 0; + } + } + } + rcu_read_unlock(); +re_arm: + queue_delayed_work(bond->wq, &bond->alb_work, alb_delta_in_ticks); +} + +/* assumption: called before the slave is attached to the bond + * and not locked by the bond lock + */ +int bond_alb_init_slave(struct bonding *bond, struct slave *slave) +{ + int res; + + res = alb_set_slave_mac_addr(slave, slave->perm_hwaddr, + slave->dev->addr_len); + if (res) + return res; + + res = alb_handle_addr_collision_on_attach(bond, slave); + if (res) + return res; + + tlb_init_slave(slave); + + /* order a rebalance ASAP */ + atomic_set(&bond->alb_info.tx_rebalance_counter, + BOND_TLB_REBALANCE_TICKS); + + if (bond->alb_info.rlb_enabled) + bond->alb_info.rlb_rebalance = 1; + + return 0; +} + +/* Remove slave from tlb and rlb hash tables, and fix up MAC addresses + * if necessary. + * + * Caller must hold RTNL and no other locks + */ +void bond_alb_deinit_slave(struct bonding *bond, struct slave *slave) +{ + if (bond_has_slaves(bond)) + alb_change_hw_addr_on_detach(bond, slave); + + tlb_clear_slave(bond, slave, 0); + + if (bond->alb_info.rlb_enabled) { + bond->alb_info.rx_slave = NULL; + rlb_clear_slave(bond, slave); + } + +} + +void bond_alb_handle_link_change(struct bonding *bond, struct slave *slave, char link) +{ + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + + if (link == BOND_LINK_DOWN) { + tlb_clear_slave(bond, slave, 0); + if (bond->alb_info.rlb_enabled) + rlb_clear_slave(bond, slave); + } else if (link == BOND_LINK_UP) { + /* order a rebalance ASAP */ + atomic_set(&bond_info->tx_rebalance_counter, + BOND_TLB_REBALANCE_TICKS); + if (bond->alb_info.rlb_enabled) { + bond->alb_info.rlb_rebalance = 1; + /* If the updelay module parameter is smaller than the + * forwarding delay of the switch the rebalance will + * not work because the rebalance arp replies will + * not be forwarded to the clients.. + */ + } + } + + if (bond_is_nondyn_tlb(bond)) { + if (bond_update_slave_arr(bond, NULL)) + pr_err("Failed to build slave-array for TLB mode.\n"); + } +} + +/** + * bond_alb_handle_active_change - assign new curr_active_slave + * @bond: our bonding struct + * @new_slave: new slave to assign + * + * Set the bond->curr_active_slave to @new_slave and handle + * mac address swapping and promiscuity changes as needed. + * + * Caller must hold RTNL + */ +void bond_alb_handle_active_change(struct bonding *bond, struct slave *new_slave) +{ + struct slave *swap_slave; + struct slave *curr_active; + + curr_active = rtnl_dereference(bond->curr_active_slave); + if (curr_active == new_slave) + return; + + if (curr_active && bond->alb_info.primary_is_promisc) { + dev_set_promiscuity(curr_active->dev, -1); + bond->alb_info.primary_is_promisc = 0; + bond->alb_info.rlb_promisc_timeout_counter = 0; + } + + swap_slave = curr_active; + rcu_assign_pointer(bond->curr_active_slave, new_slave); + + if (!new_slave || !bond_has_slaves(bond)) + return; + + /* set the new curr_active_slave to the bonds mac address + * i.e. swap mac addresses of old curr_active_slave and new curr_active_slave + */ + if (!swap_slave) + swap_slave = bond_slave_has_mac(bond, bond->dev->dev_addr); + + /* Arrange for swap_slave and new_slave to temporarily be + * ignored so we can mess with their MAC addresses without + * fear of interference from transmit activity. + */ + if (swap_slave) + tlb_clear_slave(bond, swap_slave, 1); + tlb_clear_slave(bond, new_slave, 1); + + /* in TLB mode, the slave might flip down/up with the old dev_addr, + * and thus filter bond->dev_addr's packets, so force bond's mac + */ + if (BOND_MODE(bond) == BOND_MODE_TLB) { + struct sockaddr_storage ss; + u8 tmp_addr[MAX_ADDR_LEN]; + + bond_hw_addr_copy(tmp_addr, new_slave->dev->dev_addr, + new_slave->dev->addr_len); + + bond_hw_addr_copy(ss.__data, bond->dev->dev_addr, + bond->dev->addr_len); + ss.ss_family = bond->dev->type; + /* we don't care if it can't change its mac, best effort */ + dev_set_mac_address(new_slave->dev, &ss, NULL); + + dev_addr_set(new_slave->dev, tmp_addr); + } + + /* curr_active_slave must be set before calling alb_swap_mac_addr */ + if (swap_slave) { + /* swap mac address */ + alb_swap_mac_addr(swap_slave, new_slave); + alb_fasten_mac_swap(bond, swap_slave, new_slave); + } else { + /* set the new_slave to the bond mac address */ + alb_set_slave_mac_addr(new_slave, bond->dev->dev_addr, + bond->dev->addr_len); + alb_send_learning_packets(new_slave, bond->dev->dev_addr, + false); + } +} + +/* Called with RTNL */ +int bond_alb_set_mac_address(struct net_device *bond_dev, void *addr) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct sockaddr_storage *ss = addr; + struct slave *curr_active; + struct slave *swap_slave; + int res; + + if (!is_valid_ether_addr(ss->__data)) + return -EADDRNOTAVAIL; + + res = alb_set_mac_address(bond, addr); + if (res) + return res; + + dev_addr_set(bond_dev, ss->__data); + + /* If there is no curr_active_slave there is nothing else to do. + * Otherwise we'll need to pass the new address to it and handle + * duplications. + */ + curr_active = rtnl_dereference(bond->curr_active_slave); + if (!curr_active) + return 0; + + swap_slave = bond_slave_has_mac(bond, bond_dev->dev_addr); + + if (swap_slave) { + alb_swap_mac_addr(swap_slave, curr_active); + alb_fasten_mac_swap(bond, swap_slave, curr_active); + } else { + alb_set_slave_mac_addr(curr_active, bond_dev->dev_addr, + bond_dev->addr_len); + + alb_send_learning_packets(curr_active, + bond_dev->dev_addr, false); + if (bond->alb_info.rlb_enabled) { + /* inform clients mac address has changed */ + rlb_req_update_slave_clients(bond, curr_active); + } + } + + return 0; +} + +void bond_alb_clear_vlan(struct bonding *bond, unsigned short vlan_id) +{ + if (bond->alb_info.rlb_enabled) + rlb_clear_vlan(bond, vlan_id); +} + diff --git a/drivers/net/bonding/bond_debugfs.c b/drivers/net/bonding/bond_debugfs.c new file mode 100644 index 000000000..8967b65f6 --- /dev/null +++ b/drivers/net/bonding/bond_debugfs.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include + +#include +#include + +#if defined(CONFIG_DEBUG_FS) && !defined(CONFIG_NET_NS) + +#include +#include + +static struct dentry *bonding_debug_root; + +/* Show RLB hash table */ +static int bond_debug_rlb_hash_show(struct seq_file *m, void *v) +{ + struct bonding *bond = m->private; + struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); + struct rlb_client_info *client_info; + u32 hash_index; + + if (BOND_MODE(bond) != BOND_MODE_ALB) + return 0; + + seq_printf(m, "SourceIP DestinationIP " + "Destination MAC DEV\n"); + + spin_lock_bh(&bond->mode_lock); + + hash_index = bond_info->rx_hashtbl_used_head; + for (; hash_index != RLB_NULL_INDEX; + hash_index = client_info->used_next) { + client_info = &(bond_info->rx_hashtbl[hash_index]); + if (client_info->slave) + seq_printf(m, "%-15pI4 %-15pI4 %-17pM %s\n", + &client_info->ip_src, + &client_info->ip_dst, + &client_info->mac_dst, + client_info->slave->dev->name); + else + seq_printf(m, "%-15pI4 %-15pI4 %-17pM (none)\n", + &client_info->ip_src, + &client_info->ip_dst, + &client_info->mac_dst); + } + + spin_unlock_bh(&bond->mode_lock); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(bond_debug_rlb_hash); + +void bond_debug_register(struct bonding *bond) +{ + bond->debug_dir = + debugfs_create_dir(bond->dev->name, bonding_debug_root); + + debugfs_create_file("rlb_hash_table", 0400, bond->debug_dir, + bond, &bond_debug_rlb_hash_fops); +} + +void bond_debug_unregister(struct bonding *bond) +{ + debugfs_remove_recursive(bond->debug_dir); +} + +void bond_debug_reregister(struct bonding *bond) +{ + int err = debugfs_change_name(bond->debug_dir, "%s", bond->dev->name); + if (err) { + netdev_warn(bond->dev, "failed to reregister, so just unregister old one\n"); + bond_debug_unregister(bond); + } +} + +void __init bond_create_debugfs(void) +{ + bonding_debug_root = debugfs_create_dir("bonding", NULL); + + if (IS_ERR(bonding_debug_root)) + pr_warn("Warning: Cannot create bonding directory in debugfs\n"); +} + +void bond_destroy_debugfs(void) +{ + debugfs_remove_recursive(bonding_debug_root); + bonding_debug_root = NULL; +} + + +#else /* !CONFIG_DEBUG_FS */ + +void bond_debug_register(struct bonding *bond) +{ +} + +void bond_debug_unregister(struct bonding *bond) +{ +} + +void bond_debug_reregister(struct bonding *bond) +{ +} + +void __init bond_create_debugfs(void) +{ +} + +void bond_destroy_debugfs(void) +{ +} + +#endif /* CONFIG_DEBUG_FS */ diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c new file mode 100644 index 000000000..ef9eb0c53 --- /dev/null +++ b/drivers/net/bonding/bond_main.c @@ -0,0 +1,6676 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * originally based on the dummy device. + * + * Copyright 1999, Thomas Davis, tadavis@lbl.gov. + * Based on dummy.c, and eql.c devices. + * + * bonding.c: an Ethernet Bonding driver + * + * This is useful to talk to a Cisco EtherChannel compatible equipment: + * Cisco 5500 + * Sun Trunking (Solaris) + * Alteon AceDirector Trunks + * Linux Bonding + * and probably many L2 switches ... + * + * How it works: + * ifconfig bond0 ipaddress netmask up + * will setup a network device, with an ip address. No mac address + * will be assigned at this time. The hw mac address will come from + * the first slave bonded to the channel. All slaves will then use + * this hw mac address. + * + * ifconfig bond0 down + * will release all slaves, marking them as down. + * + * ifenslave bond0 eth0 + * will attach eth0 to bond0 as a slave. eth0 hw mac address will either + * a: be used as initial mac address + * b: if a hw mac address already is there, eth0's hw mac address + * will then be set from bond0. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_TLS_DEVICE) +#include +#endif +#include +#include +#include + +/*---------------------------- Module parameters ----------------------------*/ + +/* monitor all links that often (in milliseconds). <=0 disables monitoring */ + +static int max_bonds = BOND_DEFAULT_MAX_BONDS; +static int tx_queues = BOND_DEFAULT_TX_QUEUES; +static int num_peer_notif = 1; +static int miimon; +static int updelay; +static int downdelay; +static int use_carrier = 1; +static char *mode; +static char *primary; +static char *primary_reselect; +static char *lacp_rate; +static int min_links; +static char *ad_select; +static char *xmit_hash_policy; +static int arp_interval; +static char *arp_ip_target[BOND_MAX_ARP_TARGETS]; +static char *arp_validate; +static char *arp_all_targets; +static char *fail_over_mac; +static int all_slaves_active; +static struct bond_params bonding_defaults; +static int resend_igmp = BOND_DEFAULT_RESEND_IGMP; +static int packets_per_slave = 1; +static int lp_interval = BOND_ALB_DEFAULT_LP_INTERVAL; + +module_param(max_bonds, int, 0); +MODULE_PARM_DESC(max_bonds, "Max number of bonded devices"); +module_param(tx_queues, int, 0); +MODULE_PARM_DESC(tx_queues, "Max number of transmit queues (default = 16)"); +module_param_named(num_grat_arp, num_peer_notif, int, 0644); +MODULE_PARM_DESC(num_grat_arp, "Number of peer notifications to send on " + "failover event (alias of num_unsol_na)"); +module_param_named(num_unsol_na, num_peer_notif, int, 0644); +MODULE_PARM_DESC(num_unsol_na, "Number of peer notifications to send on " + "failover event (alias of num_grat_arp)"); +module_param(miimon, int, 0); +MODULE_PARM_DESC(miimon, "Link check interval in milliseconds"); +module_param(updelay, int, 0); +MODULE_PARM_DESC(updelay, "Delay before considering link up, in milliseconds"); +module_param(downdelay, int, 0); +MODULE_PARM_DESC(downdelay, "Delay before considering link down, " + "in milliseconds"); +module_param(use_carrier, int, 0); +MODULE_PARM_DESC(use_carrier, "option obsolete, use_carrier cannot be disabled"); +module_param(mode, charp, 0); +MODULE_PARM_DESC(mode, "Mode of operation; 0 for balance-rr, " + "1 for active-backup, 2 for balance-xor, " + "3 for broadcast, 4 for 802.3ad, 5 for balance-tlb, " + "6 for balance-alb"); +module_param(primary, charp, 0); +MODULE_PARM_DESC(primary, "Primary network device to use"); +module_param(primary_reselect, charp, 0); +MODULE_PARM_DESC(primary_reselect, "Reselect primary slave " + "once it comes up; " + "0 for always (default), " + "1 for only if speed of primary is " + "better, " + "2 for only on active slave " + "failure"); +module_param(lacp_rate, charp, 0); +MODULE_PARM_DESC(lacp_rate, "LACPDU tx rate to request from 802.3ad partner; " + "0 for slow, 1 for fast"); +module_param(ad_select, charp, 0); +MODULE_PARM_DESC(ad_select, "802.3ad aggregation selection logic; " + "0 for stable (default), 1 for bandwidth, " + "2 for count"); +module_param(min_links, int, 0); +MODULE_PARM_DESC(min_links, "Minimum number of available links before turning on carrier"); + +module_param(xmit_hash_policy, charp, 0); +MODULE_PARM_DESC(xmit_hash_policy, "balance-alb, balance-tlb, balance-xor, 802.3ad hashing method; " + "0 for layer 2 (default), 1 for layer 3+4, " + "2 for layer 2+3, 3 for encap layer 2+3, " + "4 for encap layer 3+4, 5 for vlan+srcmac"); +module_param(arp_interval, int, 0); +MODULE_PARM_DESC(arp_interval, "arp interval in milliseconds"); +module_param_array(arp_ip_target, charp, NULL, 0); +MODULE_PARM_DESC(arp_ip_target, "arp targets in n.n.n.n form"); +module_param(arp_validate, charp, 0); +MODULE_PARM_DESC(arp_validate, "validate src/dst of ARP probes; " + "0 for none (default), 1 for active, " + "2 for backup, 3 for all"); +module_param(arp_all_targets, charp, 0); +MODULE_PARM_DESC(arp_all_targets, "fail on any/all arp targets timeout; 0 for any (default), 1 for all"); +module_param(fail_over_mac, charp, 0); +MODULE_PARM_DESC(fail_over_mac, "For active-backup, do not set all slaves to " + "the same MAC; 0 for none (default), " + "1 for active, 2 for follow"); +module_param(all_slaves_active, int, 0); +MODULE_PARM_DESC(all_slaves_active, "Keep all frames received on an interface " + "by setting active flag for all slaves; " + "0 for never (default), 1 for always."); +module_param(resend_igmp, int, 0); +MODULE_PARM_DESC(resend_igmp, "Number of IGMP membership reports to send on " + "link failure"); +module_param(packets_per_slave, int, 0); +MODULE_PARM_DESC(packets_per_slave, "Packets to send per slave in balance-rr " + "mode; 0 for a random slave, 1 packet per " + "slave (default), >1 packets per slave."); +module_param(lp_interval, uint, 0); +MODULE_PARM_DESC(lp_interval, "The number of seconds between instances where " + "the bonding driver sends learning packets to " + "each slaves peer switch. The default is 1."); + +/*----------------------------- Global variables ----------------------------*/ + +#ifdef CONFIG_NET_POLL_CONTROLLER +DEFINE_STATIC_KEY_FALSE(netpoll_block_tx); +#endif + +unsigned int bond_net_id __read_mostly; + +DEFINE_STATIC_KEY_FALSE(bond_bcast_neigh_enabled); + +static const struct flow_dissector_key flow_keys_bonding_keys[] = { + { + .key_id = FLOW_DISSECTOR_KEY_CONTROL, + .offset = offsetof(struct flow_keys, control), + }, + { + .key_id = FLOW_DISSECTOR_KEY_BASIC, + .offset = offsetof(struct flow_keys, basic), + }, + { + .key_id = FLOW_DISSECTOR_KEY_IPV4_ADDRS, + .offset = offsetof(struct flow_keys, addrs.v4addrs), + }, + { + .key_id = FLOW_DISSECTOR_KEY_IPV6_ADDRS, + .offset = offsetof(struct flow_keys, addrs.v6addrs), + }, + { + .key_id = FLOW_DISSECTOR_KEY_TIPC, + .offset = offsetof(struct flow_keys, addrs.tipckey), + }, + { + .key_id = FLOW_DISSECTOR_KEY_PORTS, + .offset = offsetof(struct flow_keys, ports), + }, + { + .key_id = FLOW_DISSECTOR_KEY_ICMP, + .offset = offsetof(struct flow_keys, icmp), + }, + { + .key_id = FLOW_DISSECTOR_KEY_VLAN, + .offset = offsetof(struct flow_keys, vlan), + }, + { + .key_id = FLOW_DISSECTOR_KEY_FLOW_LABEL, + .offset = offsetof(struct flow_keys, tags), + }, + { + .key_id = FLOW_DISSECTOR_KEY_GRE_KEYID, + .offset = offsetof(struct flow_keys, keyid), + }, +}; + +static struct flow_dissector flow_keys_bonding __read_mostly; + +/*-------------------------- Forward declarations ---------------------------*/ + +static int bond_init(struct net_device *bond_dev); +static void bond_uninit(struct net_device *bond_dev); +static void bond_get_stats(struct net_device *bond_dev, + struct rtnl_link_stats64 *stats); +static void bond_slave_arr_handler(struct work_struct *work); +static bool bond_time_in_interval(struct bonding *bond, unsigned long last_act, + int mod); +static void bond_netdev_notify_work(struct work_struct *work); + +/*---------------------------- General routines -----------------------------*/ + +const char *bond_mode_name(int mode) +{ + static const char *names[] = { + [BOND_MODE_ROUNDROBIN] = "load balancing (round-robin)", + [BOND_MODE_ACTIVEBACKUP] = "fault-tolerance (active-backup)", + [BOND_MODE_XOR] = "load balancing (xor)", + [BOND_MODE_BROADCAST] = "fault-tolerance (broadcast)", + [BOND_MODE_8023AD] = "IEEE 802.3ad Dynamic link aggregation", + [BOND_MODE_TLB] = "transmit load balancing", + [BOND_MODE_ALB] = "adaptive load balancing", + }; + + if (mode < BOND_MODE_ROUNDROBIN || mode > BOND_MODE_ALB) + return "unknown"; + + return names[mode]; +} + +/** + * bond_dev_queue_xmit - Prepare skb for xmit. + * + * @bond: bond device that got this skb for tx. + * @skb: hw accel VLAN tagged skb to transmit + * @slave_dev: slave that is supposed to xmit this skbuff + */ +netdev_tx_t bond_dev_queue_xmit(struct bonding *bond, struct sk_buff *skb, + struct net_device *slave_dev) +{ + skb->dev = slave_dev; + + BUILD_BUG_ON(sizeof(skb->queue_mapping) != + sizeof(qdisc_skb_cb(skb)->slave_dev_queue_mapping)); + skb_set_queue_mapping(skb, qdisc_skb_cb(skb)->slave_dev_queue_mapping); + + if (unlikely(netpoll_tx_running(bond->dev))) + return bond_netpoll_send_skb(bond_get_slave_by_dev(bond, slave_dev), skb); + + return dev_queue_xmit(skb); +} + +static bool bond_sk_check(struct bonding *bond) +{ + switch (BOND_MODE(bond)) { + case BOND_MODE_8023AD: + case BOND_MODE_XOR: + if (bond->params.xmit_policy == BOND_XMIT_POLICY_LAYER34) + return true; + fallthrough; + default: + return false; + } +} + +bool __bond_xdp_check(int mode, int xmit_policy) +{ + switch (mode) { + case BOND_MODE_ROUNDROBIN: + case BOND_MODE_ACTIVEBACKUP: + return true; + case BOND_MODE_8023AD: + case BOND_MODE_XOR: + /* vlan+srcmac is not supported with XDP as in most cases the 802.1q + * payload is not in the packet due to hardware offload. + */ + if (xmit_policy != BOND_XMIT_POLICY_VLAN_SRCMAC) + return true; + fallthrough; + default: + return false; + } +} + +bool bond_xdp_check(struct bonding *bond, int mode) +{ + return __bond_xdp_check(mode, bond->params.xmit_policy); +} + +/*---------------------------------- VLAN -----------------------------------*/ + +/* In the following 2 functions, bond_vlan_rx_add_vid and bond_vlan_rx_kill_vid, + * We don't protect the slave list iteration with a lock because: + * a. This operation is performed in IOCTL context, + * b. The operation is protected by the RTNL semaphore in the 8021q code, + * c. Holding a lock with BH disabled while directly calling a base driver + * entry point is generally a BAD idea. + * + * The design of synchronization/protection for this operation in the 8021q + * module is good for one or more VLAN devices over a single physical device + * and cannot be extended for a teaming solution like bonding, so there is a + * potential race condition here where a net device from the vlan group might + * be referenced (either by a base driver or the 8021q code) while it is being + * removed from the system. However, it turns out we're not making matters + * worse, and if it works for regular VLAN usage it will work here too. +*/ + +/** + * bond_vlan_rx_add_vid - Propagates adding an id to slaves + * @bond_dev: bonding net device that got called + * @proto: network protocol ID + * @vid: vlan id being added + */ +static int bond_vlan_rx_add_vid(struct net_device *bond_dev, + __be16 proto, u16 vid) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct slave *slave, *rollback_slave; + struct list_head *iter; + int res; + + bond_for_each_slave(bond, slave, iter) { + res = vlan_vid_add(slave->dev, proto, vid); + if (res) + goto unwind; + } + + return 0; + +unwind: + /* unwind to the slave that failed */ + bond_for_each_slave(bond, rollback_slave, iter) { + if (rollback_slave == slave) + break; + + vlan_vid_del(rollback_slave->dev, proto, vid); + } + + return res; +} + +/** + * bond_vlan_rx_kill_vid - Propagates deleting an id to slaves + * @bond_dev: bonding net device that got called + * @proto: network protocol ID + * @vid: vlan id being removed + */ +static int bond_vlan_rx_kill_vid(struct net_device *bond_dev, + __be16 proto, u16 vid) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct list_head *iter; + struct slave *slave; + + bond_for_each_slave(bond, slave, iter) + vlan_vid_del(slave->dev, proto, vid); + + if (bond_is_lb(bond)) + bond_alb_clear_vlan(bond, vid); + + return 0; +} + +/*---------------------------------- XFRM -----------------------------------*/ + +#ifdef CONFIG_XFRM_OFFLOAD +/** + * bond_ipsec_dev - Get active device for IPsec offload + * @xs: pointer to transformer state struct + * + * Context: caller must hold rcu_read_lock. + * + * Return: the device for ipsec offload, or NULL if not exist. + **/ +static struct net_device *bond_ipsec_dev(struct xfrm_state *xs) +{ + struct net_device *bond_dev = xs->xso.dev; + struct bonding *bond; + struct slave *slave; + + bond = netdev_priv(bond_dev); + if (BOND_MODE(bond) != BOND_MODE_ACTIVEBACKUP) + return NULL; + + slave = rcu_dereference(bond->curr_active_slave); + if (!slave) + return NULL; + + if (!xs->xso.real_dev) + return NULL; + + if (xs->xso.real_dev != slave->dev) + pr_warn_ratelimited("%s: (slave %s): not same with IPsec offload real dev %s\n", + bond_dev->name, slave->dev->name, xs->xso.real_dev->name); + + return slave->dev; +} + +/** + * bond_ipsec_add_sa - program device with a security association + * @bond_dev: pointer to the bond net device + * @xs: pointer to transformer state struct + * @extack: extack point to fill failure reason + **/ +static int bond_ipsec_add_sa(struct net_device *bond_dev, + struct xfrm_state *xs, + struct netlink_ext_ack *extack) +{ + struct net_device *real_dev; + netdevice_tracker tracker; + struct bond_ipsec *ipsec; + struct bonding *bond; + struct slave *slave; + int err; + + if (!bond_dev) + return -EINVAL; + + rcu_read_lock(); + bond = netdev_priv(bond_dev); + slave = rcu_dereference(bond->curr_active_slave); + real_dev = slave ? slave->dev : NULL; + netdev_hold(real_dev, &tracker, GFP_ATOMIC); + rcu_read_unlock(); + if (!real_dev) { + err = -ENODEV; + goto out; + } + + if (!real_dev->xfrmdev_ops || + !real_dev->xfrmdev_ops->xdo_dev_state_add || + netif_is_bond_master(real_dev)) { + NL_SET_ERR_MSG_MOD(extack, "Slave does not support ipsec offload"); + err = -EINVAL; + goto out; + } + + ipsec = kmalloc_obj(*ipsec); + if (!ipsec) { + err = -ENOMEM; + goto out; + } + + err = real_dev->xfrmdev_ops->xdo_dev_state_add(real_dev, xs, extack); + if (!err) { + xs->xso.real_dev = real_dev; + ipsec->xs = xs; + INIT_LIST_HEAD(&ipsec->list); + mutex_lock(&bond->ipsec_lock); + list_add(&ipsec->list, &bond->ipsec_list); + mutex_unlock(&bond->ipsec_lock); + } else { + kfree(ipsec); + } +out: + netdev_put(real_dev, &tracker); + return err; +} + +static void bond_ipsec_add_sa_all(struct bonding *bond) +{ + struct net_device *bond_dev = bond->dev; + struct net_device *real_dev; + struct bond_ipsec *ipsec; + struct slave *slave; + + slave = rtnl_dereference(bond->curr_active_slave); + real_dev = slave ? slave->dev : NULL; + if (!real_dev) + return; + + mutex_lock(&bond->ipsec_lock); + if (!real_dev->xfrmdev_ops || + !real_dev->xfrmdev_ops->xdo_dev_state_add || + netif_is_bond_master(real_dev)) { + if (!list_empty(&bond->ipsec_list)) + slave_warn(bond_dev, real_dev, + "%s: no slave xdo_dev_state_add\n", + __func__); + goto out; + } + + list_for_each_entry(ipsec, &bond->ipsec_list, list) { + /* If new state is added before ipsec_lock acquired */ + if (ipsec->xs->xso.real_dev == real_dev) + continue; + + if (real_dev->xfrmdev_ops->xdo_dev_state_add(real_dev, + ipsec->xs, NULL)) { + slave_warn(bond_dev, real_dev, "%s: failed to add SA\n", __func__); + continue; + } + + spin_lock_bh(&ipsec->xs->lock); + /* xs might have been killed by the user during the migration + * to the new dev, but bond_ipsec_del_sa() should have done + * nothing, as xso.real_dev is NULL. + * Delete it from the device we just added it to. The pending + * bond_ipsec_free_sa() call will do the rest of the cleanup. + */ + if (ipsec->xs->km.state == XFRM_STATE_DEAD && + real_dev->xfrmdev_ops->xdo_dev_state_delete) + real_dev->xfrmdev_ops->xdo_dev_state_delete(real_dev, + ipsec->xs); + ipsec->xs->xso.real_dev = real_dev; + spin_unlock_bh(&ipsec->xs->lock); + } +out: + mutex_unlock(&bond->ipsec_lock); +} + +/** + * bond_ipsec_del_sa - clear out this specific SA + * @bond_dev: pointer to the bond net device + * @xs: pointer to transformer state struct + **/ +static void bond_ipsec_del_sa(struct net_device *bond_dev, + struct xfrm_state *xs) +{ + struct net_device *real_dev; + + if (!bond_dev || !xs->xso.real_dev) + return; + + real_dev = xs->xso.real_dev; + + if (!real_dev->xfrmdev_ops || + !real_dev->xfrmdev_ops->xdo_dev_state_delete || + netif_is_bond_master(real_dev)) { + slave_warn(bond_dev, real_dev, "%s: no slave xdo_dev_state_delete\n", __func__); + return; + } + + real_dev->xfrmdev_ops->xdo_dev_state_delete(real_dev, xs); +} + +static void bond_ipsec_del_sa_all(struct bonding *bond) +{ + struct net_device *bond_dev = bond->dev; + struct net_device *real_dev; + struct bond_ipsec *ipsec; + struct slave *slave; + + slave = rtnl_dereference(bond->curr_active_slave); + real_dev = slave ? slave->dev : NULL; + if (!real_dev) + return; + + mutex_lock(&bond->ipsec_lock); + list_for_each_entry(ipsec, &bond->ipsec_list, list) { + if (!ipsec->xs->xso.real_dev) + continue; + + if (!real_dev->xfrmdev_ops || + !real_dev->xfrmdev_ops->xdo_dev_state_delete || + netif_is_bond_master(real_dev)) { + slave_warn(bond_dev, real_dev, + "%s: no slave xdo_dev_state_delete\n", + __func__); + continue; + } + + spin_lock_bh(&ipsec->xs->lock); + ipsec->xs->xso.real_dev = NULL; + /* Don't double delete states killed by the user. */ + if (ipsec->xs->km.state != XFRM_STATE_DEAD) + real_dev->xfrmdev_ops->xdo_dev_state_delete(real_dev, + ipsec->xs); + spin_unlock_bh(&ipsec->xs->lock); + + if (real_dev->xfrmdev_ops->xdo_dev_state_free) + real_dev->xfrmdev_ops->xdo_dev_state_free(real_dev, + ipsec->xs); + } + mutex_unlock(&bond->ipsec_lock); +} + +static void bond_ipsec_free_sa(struct net_device *bond_dev, + struct xfrm_state *xs) +{ + struct net_device *real_dev; + struct bond_ipsec *ipsec; + struct bonding *bond; + + if (!bond_dev) + return; + + bond = netdev_priv(bond_dev); + + mutex_lock(&bond->ipsec_lock); + if (!xs->xso.real_dev) + goto out; + + real_dev = xs->xso.real_dev; + + xs->xso.real_dev = NULL; + if (real_dev->xfrmdev_ops && + real_dev->xfrmdev_ops->xdo_dev_state_free) + real_dev->xfrmdev_ops->xdo_dev_state_free(real_dev, xs); +out: + list_for_each_entry(ipsec, &bond->ipsec_list, list) { + if (ipsec->xs == xs) { + list_del(&ipsec->list); + kfree(ipsec); + break; + } + } + mutex_unlock(&bond->ipsec_lock); +} + +/** + * bond_ipsec_offload_ok - can this packet use the xfrm hw offload + * @skb: current data packet + * @xs: pointer to transformer state struct + **/ +static bool bond_ipsec_offload_ok(struct sk_buff *skb, struct xfrm_state *xs) +{ + struct net_device *real_dev; + + rcu_read_lock(); + real_dev = bond_ipsec_dev(xs); + if (!real_dev || netif_is_bond_master(real_dev)) { + rcu_read_unlock(); + return false; + } + + rcu_read_unlock(); + return true; +} + +/** + * bond_advance_esn_state - ESN support for IPSec HW offload + * @xs: pointer to transformer state struct + **/ +static void bond_advance_esn_state(struct xfrm_state *xs) +{ + struct net_device *real_dev; + + rcu_read_lock(); + real_dev = bond_ipsec_dev(xs); + if (!real_dev) + goto out; + + if (!real_dev->xfrmdev_ops || + !real_dev->xfrmdev_ops->xdo_dev_state_advance_esn) { + pr_warn_ratelimited("%s: %s doesn't support xdo_dev_state_advance_esn\n", __func__, real_dev->name); + goto out; + } + + real_dev->xfrmdev_ops->xdo_dev_state_advance_esn(xs); +out: + rcu_read_unlock(); +} + +/** + * bond_xfrm_update_stats - Update xfrm state + * @xs: pointer to transformer state struct + **/ +static void bond_xfrm_update_stats(struct xfrm_state *xs) +{ + struct net_device *real_dev; + + rcu_read_lock(); + real_dev = bond_ipsec_dev(xs); + if (!real_dev) + goto out; + + if (!real_dev->xfrmdev_ops || + !real_dev->xfrmdev_ops->xdo_dev_state_update_stats) { + pr_warn_ratelimited("%s: %s doesn't support xdo_dev_state_update_stats\n", __func__, real_dev->name); + goto out; + } + + real_dev->xfrmdev_ops->xdo_dev_state_update_stats(xs); +out: + rcu_read_unlock(); +} + +static const struct xfrmdev_ops bond_xfrmdev_ops = { + .xdo_dev_state_add = bond_ipsec_add_sa, + .xdo_dev_state_delete = bond_ipsec_del_sa, + .xdo_dev_state_free = bond_ipsec_free_sa, + .xdo_dev_offload_ok = bond_ipsec_offload_ok, + .xdo_dev_state_advance_esn = bond_advance_esn_state, + .xdo_dev_state_update_stats = bond_xfrm_update_stats, +}; +#endif /* CONFIG_XFRM_OFFLOAD */ + +/*------------------------------- Link status -------------------------------*/ + +/* Set the carrier state for the master according to the state of its + * slaves. If any slaves are up, the master is up. In 802.3ad mode, + * do special 802.3ad magic. + * + * Returns zero if carrier state does not change, nonzero if it does. + */ +int bond_set_carrier(struct bonding *bond) +{ + struct list_head *iter; + struct slave *slave; + + if (!bond_has_slaves(bond)) + goto down; + + if (BOND_MODE(bond) == BOND_MODE_8023AD) + return bond_3ad_set_carrier(bond); + + bond_for_each_slave(bond, slave, iter) { + if (slave->link == BOND_LINK_UP) { + if (!netif_carrier_ok(bond->dev)) { + netif_carrier_on(bond->dev); + return 1; + } + return 0; + } + } + +down: + if (netif_carrier_ok(bond->dev)) { + netif_carrier_off(bond->dev); + return 1; + } + return 0; +} + +/* Get link speed and duplex from the slave's base driver + * using ethtool. If for some reason the call fails or the + * values are invalid, set speed and duplex to -1, + * and return. Return 1 if speed or duplex settings are + * UNKNOWN; 0 otherwise. + * + * Caller must hold the slave's netdev ops lock. The notifier path + * (bond_netdev_event NETDEV_CHANGE/UP) reaches us with the slave's ops + * lock held; other call sites take it explicitly. + */ +static int bond_update_speed_duplex(struct slave *slave) +{ + struct net_device *slave_dev = slave->dev; + struct ethtool_link_ksettings ecmd; + int res; + + res = netif_get_link_ksettings(slave_dev, &ecmd); + if (res < 0) + goto speed_duplex_unknown; + if (ecmd.base.speed == 0 || ecmd.base.speed == ((__u32)-1)) + goto speed_duplex_unknown; + switch (ecmd.base.duplex) { + case DUPLEX_FULL: + case DUPLEX_HALF: + break; + default: + goto speed_duplex_unknown; + } + + slave->speed = ecmd.base.speed; + slave->duplex = ecmd.base.duplex; + + return 0; + +speed_duplex_unknown: + slave->speed = SPEED_UNKNOWN; + slave->duplex = DUPLEX_UNKNOWN; + + return 1; +} + +const char *bond_slave_link_status(s8 link) +{ + switch (link) { + case BOND_LINK_UP: + return "up"; + case BOND_LINK_FAIL: + return "going down"; + case BOND_LINK_DOWN: + return "down"; + case BOND_LINK_BACK: + return "going back"; + default: + return "unknown"; + } +} + +/*----------------------------- Multicast list ------------------------------*/ + +/* Push the promiscuity flag down to appropriate slaves */ +static int bond_set_promiscuity(struct bonding *bond, int inc) +{ + struct list_head *iter; + int err = 0; + + if (bond_uses_primary(bond)) { + struct slave *curr_active = rtnl_dereference(bond->curr_active_slave); + + if (curr_active) + err = dev_set_promiscuity(curr_active->dev, inc); + } else { + struct slave *slave; + + bond_for_each_slave(bond, slave, iter) { + err = dev_set_promiscuity(slave->dev, inc); + if (err) + return err; + } + } + return err; +} + +/* Push the allmulti flag down to all slaves */ +static int bond_set_allmulti(struct bonding *bond, int inc) +{ + struct list_head *iter; + int err = 0; + + if (bond_uses_primary(bond)) { + struct slave *curr_active = rtnl_dereference(bond->curr_active_slave); + + if (curr_active) + err = dev_set_allmulti(curr_active->dev, inc); + } else { + struct slave *slave; + + bond_for_each_slave(bond, slave, iter) { + err = dev_set_allmulti(slave->dev, inc); + if (err) + return err; + } + } + return err; +} + +/* Retrieve the list of registered multicast addresses for the bonding + * device and retransmit an IGMP JOIN request to the current active + * slave. + */ +static void bond_resend_igmp_join_requests_delayed(struct work_struct *work) +{ + struct bonding *bond = container_of(work, struct bonding, + mcast_work.work); + + if (!rtnl_trylock()) { + queue_delayed_work(bond->wq, &bond->mcast_work, 1); + return; + } + call_netdevice_notifiers(NETDEV_RESEND_IGMP, bond->dev); + + if (bond->igmp_retrans > 1) { + bond->igmp_retrans--; + queue_delayed_work(bond->wq, &bond->mcast_work, HZ/5); + } + rtnl_unlock(); +} + +/* Flush bond's hardware addresses from slave */ +static void bond_hw_addr_flush(struct net_device *bond_dev, + struct net_device *slave_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + + dev_uc_unsync(slave_dev, bond_dev); + dev_mc_unsync(slave_dev, bond_dev); + + if (BOND_MODE(bond) == BOND_MODE_8023AD) + dev_mc_del(slave_dev, lacpdu_mcast_addr); +} + +/*--------------------------- Active slave change ---------------------------*/ + +/* Update the hardware address list and promisc/allmulti for the new and + * old active slaves (if any). Modes that are not using primary keep all + * slaves up date at all times; only the modes that use primary need to call + * this function to swap these settings during a failover. + */ +static void bond_hw_addr_swap(struct bonding *bond, struct slave *new_active, + struct slave *old_active) +{ + if (old_active) { + if (bond->dev->flags & IFF_PROMISC) + dev_set_promiscuity(old_active->dev, -1); + + if (bond->dev->flags & IFF_ALLMULTI) + dev_set_allmulti(old_active->dev, -1); + + if (bond->dev->flags & IFF_UP) + bond_hw_addr_flush(bond->dev, old_active->dev); + + bond_slave_ns_maddrs_add(bond, old_active); + } + + if (new_active) { + /* FIXME: Signal errors upstream. */ + if (bond->dev->flags & IFF_PROMISC) + dev_set_promiscuity(new_active->dev, 1); + + if (bond->dev->flags & IFF_ALLMULTI) + dev_set_allmulti(new_active->dev, 1); + + if (bond->dev->flags & IFF_UP) { + netif_addr_lock_bh(bond->dev); + dev_uc_sync(new_active->dev, bond->dev); + dev_mc_sync(new_active->dev, bond->dev); + netif_addr_unlock_bh(bond->dev); + } + + bond_slave_ns_maddrs_del(bond, new_active); + } +} + +/** + * bond_set_dev_addr - clone slave's address to bond + * @bond_dev: bond net device + * @slave_dev: slave net device + * + * Should be called with RTNL held. + */ +static int bond_set_dev_addr(struct net_device *bond_dev, + struct net_device *slave_dev) +{ + int err; + + slave_dbg(bond_dev, slave_dev, "bond_dev=%p slave_dev=%p slave_dev->addr_len=%d\n", + bond_dev, slave_dev, slave_dev->addr_len); + err = netif_pre_changeaddr_notify(bond_dev, slave_dev->dev_addr, NULL); + if (err) + return err; + + __dev_addr_set(bond_dev, slave_dev->dev_addr, slave_dev->addr_len); + bond_dev->addr_assign_type = NET_ADDR_STOLEN; + call_netdevice_notifiers(NETDEV_CHANGEADDR, bond_dev); + return 0; +} + +static struct slave *bond_get_old_active(struct bonding *bond, + struct slave *new_active) +{ + struct slave *slave; + struct list_head *iter; + + bond_for_each_slave(bond, slave, iter) { + if (slave == new_active) + continue; + + if (ether_addr_equal(bond->dev->dev_addr, slave->dev->dev_addr)) + return slave; + } + + return NULL; +} + +/* bond_do_fail_over_mac + * + * Perform special MAC address swapping for fail_over_mac settings + * + * Called with RTNL + */ +static void bond_do_fail_over_mac(struct bonding *bond, + struct slave *new_active, + struct slave *old_active) +{ + u8 tmp_mac[MAX_ADDR_LEN]; + struct sockaddr_storage ss; + int rv; + + switch (bond->params.fail_over_mac) { + case BOND_FOM_ACTIVE: + if (new_active) { + rv = bond_set_dev_addr(bond->dev, new_active->dev); + if (rv) + slave_err(bond->dev, new_active->dev, "Error %d setting bond MAC from slave\n", + -rv); + } + break; + case BOND_FOM_FOLLOW: + /* if new_active && old_active, swap them + * if just old_active, do nothing (going to no active slave) + * if just new_active, set new_active to bond's MAC + */ + if (!new_active) + return; + + if (!old_active) + old_active = bond_get_old_active(bond, new_active); + + if (old_active) { + bond_hw_addr_copy(tmp_mac, new_active->dev->dev_addr, + new_active->dev->addr_len); + bond_hw_addr_copy(ss.__data, + old_active->dev->dev_addr, + old_active->dev->addr_len); + ss.ss_family = new_active->dev->type; + } else { + bond_hw_addr_copy(ss.__data, bond->dev->dev_addr, + bond->dev->addr_len); + ss.ss_family = bond->dev->type; + } + + rv = dev_set_mac_address(new_active->dev, &ss, NULL); + if (rv) { + slave_err(bond->dev, new_active->dev, "Error %d setting MAC of new active slave\n", + -rv); + goto out; + } + + if (!old_active) + goto out; + + bond_hw_addr_copy(ss.__data, tmp_mac, + new_active->dev->addr_len); + ss.ss_family = old_active->dev->type; + + rv = dev_set_mac_address(old_active->dev, &ss, NULL); + if (rv) + slave_err(bond->dev, old_active->dev, "Error %d setting MAC of old active slave\n", + -rv); +out: + break; + default: + netdev_err(bond->dev, "bond_do_fail_over_mac impossible: bad policy %d\n", + bond->params.fail_over_mac); + break; + } + +} + +/** + * bond_choose_primary_or_current - select the primary or high priority slave + * @bond: our bonding struct + * + * - Check if there is a primary link. If the primary link was set and is up, + * go on and do link reselection. + * + * - If primary link is not set or down, find the highest priority link. + * If the highest priority link is not current slave, set it as primary + * link and do link reselection. + */ +static struct slave *bond_choose_primary_or_current(struct bonding *bond) +{ + struct slave *prim = rtnl_dereference(bond->primary_slave); + struct slave *curr = rtnl_dereference(bond->curr_active_slave); + struct slave *slave, *hprio = NULL; + struct list_head *iter; + + if (!prim || prim->link != BOND_LINK_UP) { + bond_for_each_slave(bond, slave, iter) { + if (slave->link == BOND_LINK_UP) { + hprio = hprio ?: slave; + if (slave->prio > hprio->prio) + hprio = slave; + } + } + + if (hprio && hprio != curr) { + prim = hprio; + goto link_reselect; + } + + if (!curr || curr->link != BOND_LINK_UP) + return NULL; + return curr; + } + + if (bond->force_primary) { + bond->force_primary = false; + return prim; + } + +link_reselect: + if (!curr || curr->link != BOND_LINK_UP) + return prim; + + /* At this point, prim and curr are both up */ + switch (bond->params.primary_reselect) { + case BOND_PRI_RESELECT_ALWAYS: + return prim; + case BOND_PRI_RESELECT_BETTER: + if (prim->speed < curr->speed) + return curr; + if (prim->speed == curr->speed && prim->duplex <= curr->duplex) + return curr; + return prim; + case BOND_PRI_RESELECT_FAILURE: + return curr; + default: + netdev_err(bond->dev, "impossible primary_reselect %d\n", + bond->params.primary_reselect); + return curr; + } +} + +/** + * bond_find_best_slave - select the best available slave to be the active one + * @bond: our bonding struct + */ +static struct slave *bond_find_best_slave(struct bonding *bond) +{ + struct slave *slave, *bestslave = NULL; + struct list_head *iter; + int mintime = bond->params.updelay; + + slave = bond_choose_primary_or_current(bond); + if (slave) + return slave; + + bond_for_each_slave(bond, slave, iter) { + if (slave->link == BOND_LINK_UP) + return slave; + if (slave->link == BOND_LINK_BACK && bond_slave_is_up(slave) && + slave->delay < mintime) { + mintime = slave->delay; + bestslave = slave; + } + } + + return bestslave; +} + +/* must be called in RCU critical section or with RTNL held */ +static bool bond_should_notify_peers(struct bonding *bond) +{ + struct bond_up_slave *usable; + struct slave *slave = NULL; + + if (!bond->send_peer_notif || + bond->send_peer_notif % + max(1, bond->params.peer_notif_delay) != 0 || + !netif_carrier_ok(bond->dev)) + return false; + + /* The send_peer_notif is set by active-backup or 8023ad + * mode, and cleared in bond_close() when changing mode. + * It is safe to only check bond mode here. + */ + if (BOND_MODE(bond) == BOND_MODE_8023AD) { + usable = rcu_dereference_rtnl(bond->usable_slaves); + if (!usable || !READ_ONCE(usable->count)) + return false; + } else { + slave = rcu_dereference_rtnl(bond->curr_active_slave); + if (!slave || test_bit(__LINK_STATE_LINKWATCH_PENDING, + &slave->dev->state)) + return false; + } + + netdev_dbg(bond->dev, "bond_should_notify_peers: slave %s\n", + slave ? slave->dev->name : "all"); + + return true; +} + +/* Use this to update send_peer_notif when RTNL may be held in other places. */ +void bond_peer_notify_work_rearm(struct bonding *bond, unsigned long delay) +{ + queue_delayed_work(bond->wq, &bond->peer_notify_work, delay); +} + +/* Peer notify update handler. Holds only RTNL */ +static void bond_peer_notify_reset(struct bonding *bond) +{ + WRITE_ONCE(bond->send_peer_notif, + bond->params.num_peer_notif * + max(1, bond->params.peer_notif_delay)); +} + +static void bond_peer_notify_handler(struct work_struct *work) +{ + struct bonding *bond = container_of(work, struct bonding, + peer_notify_work.work); + + if (!rtnl_trylock()) { + bond_peer_notify_work_rearm(bond, 1); + return; + } + + bond_peer_notify_reset(bond); + + rtnl_unlock(); +} + +/* Peer notify events post. Holds only RTNL */ +static void bond_peer_notify_may_events(struct bonding *bond, bool force) +{ + bool notified = false; + + if (bond_should_notify_peers(bond)) { + notified = true; + call_netdevice_notifiers(NETDEV_NOTIFY_PEERS, bond->dev); + } + + if (notified || force) + bond->send_peer_notif--; +} + +/** + * bond_change_active_slave - change the active slave into the specified one + * @bond: our bonding struct + * @new_active: the new slave to make the active one + * + * Set the new slave to the bond's settings and unset them on the old + * curr_active_slave. + * Setting include flags, mc-list, promiscuity, allmulti, etc. + * + * If @new's link state is %BOND_LINK_BACK we'll set it to %BOND_LINK_UP, + * because it is apparently the best available slave we have, even though its + * updelay hasn't timed out yet. + * + * Caller must hold RTNL. + */ +void bond_change_active_slave(struct bonding *bond, struct slave *new_active) +{ + struct slave *old_active; + + ASSERT_RTNL(); + + old_active = rtnl_dereference(bond->curr_active_slave); + + if (old_active == new_active) + return; + +#ifdef CONFIG_XFRM_OFFLOAD + bond_ipsec_del_sa_all(bond); +#endif /* CONFIG_XFRM_OFFLOAD */ + + if (new_active) { + new_active->last_link_up = jiffies; + + if (new_active->link == BOND_LINK_BACK) { + if (bond_uses_primary(bond)) { + slave_info(bond->dev, new_active->dev, "making interface the new active one %d ms earlier\n", + (bond->params.updelay - new_active->delay) * bond->params.miimon); + } + + new_active->delay = 0; + bond_set_slave_link_state(new_active, BOND_LINK_UP, + BOND_SLAVE_NOTIFY_NOW); + + if (BOND_MODE(bond) == BOND_MODE_8023AD) + bond_3ad_handle_link_change(new_active, BOND_LINK_UP); + + if (bond_is_lb(bond)) + bond_alb_handle_link_change(bond, new_active, BOND_LINK_UP); + } else { + if (bond_uses_primary(bond)) + slave_info(bond->dev, new_active->dev, "making interface the new active one\n"); + } + } + + if (bond_uses_primary(bond)) + bond_hw_addr_swap(bond, new_active, old_active); + + if (bond_is_lb(bond)) { + bond_alb_handle_active_change(bond, new_active); + if (old_active) + bond_set_slave_inactive_flags(old_active, + BOND_SLAVE_NOTIFY_NOW); + if (new_active) + bond_set_slave_active_flags(new_active, + BOND_SLAVE_NOTIFY_NOW); + } else { + rcu_assign_pointer(bond->curr_active_slave, new_active); + } + + if (BOND_MODE(bond) == BOND_MODE_ACTIVEBACKUP) { + if (old_active) + bond_set_slave_inactive_flags(old_active, + BOND_SLAVE_NOTIFY_NOW); + + if (new_active) { + bond_set_slave_active_flags(new_active, + BOND_SLAVE_NOTIFY_NOW); + + if (bond->params.fail_over_mac) + bond_do_fail_over_mac(bond, new_active, + old_active); + + call_netdevice_notifiers(NETDEV_BONDING_FAILOVER, bond->dev); + + if (netif_running(bond->dev)) { + bond_peer_notify_reset(bond); + bond_peer_notify_may_events(bond, false); + } + } + } + +#ifdef CONFIG_XFRM_OFFLOAD + bond_ipsec_add_sa_all(bond); +#endif /* CONFIG_XFRM_OFFLOAD */ + + /* resend IGMP joins since active slave has changed or + * all were sent on curr_active_slave. + * resend only if bond is brought up with the affected + * bonding modes and the retransmission is enabled + */ + if (netif_running(bond->dev) && (bond->params.resend_igmp > 0) && + ((bond_uses_primary(bond) && new_active) || + BOND_MODE(bond) == BOND_MODE_ROUNDROBIN)) { + bond->igmp_retrans = bond->params.resend_igmp; + queue_delayed_work(bond->wq, &bond->mcast_work, 1); + } +} + +/** + * bond_select_active_slave - select a new active slave, if needed + * @bond: our bonding struct + * + * This functions should be called when one of the following occurs: + * - The old curr_active_slave has been released or lost its link. + * - The primary_slave has got its link back. + * - A slave has got its link back and there's no old curr_active_slave. + * + * Caller must hold RTNL. + */ +void bond_select_active_slave(struct bonding *bond) +{ + struct slave *best_slave; + int rv; + + ASSERT_RTNL(); + + best_slave = bond_find_best_slave(bond); + if (best_slave != rtnl_dereference(bond->curr_active_slave)) { + bond_change_active_slave(bond, best_slave); + rv = bond_set_carrier(bond); + if (!rv) + return; + + if (netif_carrier_ok(bond->dev)) + netdev_info(bond->dev, "active interface up!\n"); + else + netdev_info(bond->dev, "now running without any active interface!\n"); + } +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +static inline int slave_enable_netpoll(struct slave *slave) +{ + struct netpoll *np; + int err = 0; + + np = kzalloc_obj(*np); + err = -ENOMEM; + if (!np) + goto out; + + err = __netpoll_setup(np, slave->dev); + if (err) { + kfree(np); + goto out; + } + slave->np = np; +out: + return err; +} +static inline void slave_disable_netpoll(struct slave *slave) +{ + struct netpoll *np = slave->np; + + if (!np) + return; + + slave->np = NULL; + + __netpoll_free(np); +} + +static void bond_poll_controller(struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct slave *slave = NULL; + struct list_head *iter; + struct ad_info ad_info; + + if (BOND_MODE(bond) == BOND_MODE_8023AD) + if (bond_3ad_get_active_agg_info(bond, &ad_info)) + return; + + bond_for_each_slave_rcu(bond, slave, iter) { + if (!bond_slave_is_up(slave)) + continue; + + if (BOND_MODE(bond) == BOND_MODE_8023AD) { + struct aggregator *agg = + rcu_dereference(SLAVE_AD_INFO(slave)->port.aggregator); + + if (agg && + agg->aggregator_identifier != ad_info.aggregator_id) + continue; + } + + netpoll_poll_dev(slave->dev); + } +} + +static void bond_netpoll_cleanup(struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct list_head *iter; + struct slave *slave; + + bond_for_each_slave(bond, slave, iter) + if (bond_slave_is_up(slave)) + slave_disable_netpoll(slave); +} + +static int bond_netpoll_setup(struct net_device *dev) +{ + struct bonding *bond = netdev_priv(dev); + struct list_head *iter; + struct slave *slave; + int err = 0; + + bond_for_each_slave(bond, slave, iter) { + err = slave_enable_netpoll(slave); + if (err) { + bond_netpoll_cleanup(dev); + break; + } + } + return err; +} +#else +static inline int slave_enable_netpoll(struct slave *slave) +{ + return 0; +} +static inline void slave_disable_netpoll(struct slave *slave) +{ +} +static void bond_netpoll_cleanup(struct net_device *bond_dev) +{ +} +#endif + +/*---------------------------------- IOCTL ----------------------------------*/ + +static netdev_features_t bond_fix_features(struct net_device *dev, + netdev_features_t features) +{ + struct bonding *bond = netdev_priv(dev); + struct list_head *iter; + netdev_features_t mask; + struct slave *slave; + + mask = features; + features = netdev_base_features(features); + + bond_for_each_slave(bond, slave, iter) { + features = netdev_increment_features(features, + slave->dev->features, + mask); + } + features = netdev_add_tso_features(features, mask); + + return features; +} + +static int bond_header_create(struct sk_buff *skb, struct net_device *bond_dev, + unsigned short type, const void *daddr, + const void *saddr, unsigned int len) +{ + struct bonding *bond = netdev_priv(bond_dev); + const struct header_ops *slave_ops; + struct slave *slave; + int ret = 0; + + rcu_read_lock(); + slave = rcu_dereference(bond->curr_active_slave); + if (slave) { + slave_ops = READ_ONCE(slave->dev->header_ops); + if (slave_ops && slave_ops->create) + ret = slave_ops->create(skb, slave->dev, + type, daddr, saddr, len); + } + rcu_read_unlock(); + return ret; +} + +static int bond_header_parse(const struct sk_buff *skb, + const struct net_device *dev, + unsigned char *haddr) +{ + struct bonding *bond = netdev_priv(dev); + const struct header_ops *slave_ops; + struct slave *slave; + int ret = 0; + + rcu_read_lock(); + slave = rcu_dereference(bond->curr_active_slave); + if (slave) { + slave_ops = READ_ONCE(slave->dev->header_ops); + if (slave_ops && slave_ops->parse) + ret = slave_ops->parse(skb, slave->dev, haddr); + } + rcu_read_unlock(); + return ret; +} + +static const struct header_ops bond_header_ops = { + .create = bond_header_create, + .parse = bond_header_parse, +}; + +static void bond_setup_by_slave(struct net_device *bond_dev, + struct net_device *slave_dev) +{ + bool was_up = !!(bond_dev->flags & IFF_UP); + + dev_close(bond_dev); + + bond_dev->header_ops = slave_dev->header_ops ? + &bond_header_ops : NULL; + + bond_dev->type = slave_dev->type; + bond_dev->hard_header_len = slave_dev->hard_header_len; + bond_dev->needed_headroom = slave_dev->needed_headroom; + bond_dev->addr_len = slave_dev->addr_len; + + memcpy(bond_dev->broadcast, slave_dev->broadcast, + slave_dev->addr_len); + + if (slave_dev->flags & IFF_POINTOPOINT) { + bond_dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST); + bond_dev->flags |= (IFF_POINTOPOINT | IFF_NOARP); + } + if (was_up) + dev_open(bond_dev, NULL); +} + +/* On bonding slaves other than the currently active slave, suppress + * duplicates except for alb non-mcast/bcast. + */ +static bool bond_should_deliver_exact_match(struct sk_buff *skb, + struct slave *slave, + struct bonding *bond) +{ + if (bond_is_slave_inactive(slave)) { + if (BOND_MODE(bond) == BOND_MODE_ALB && + skb->pkt_type != PACKET_BROADCAST && + skb->pkt_type != PACKET_MULTICAST) + return false; + return true; + } + return false; +} + +static rx_handler_result_t bond_handle_frame(struct sk_buff **pskb) +{ + struct sk_buff *skb = *pskb; + struct slave *slave; + struct bonding *bond; + int (*recv_probe)(const struct sk_buff *, struct bonding *, + struct slave *); + int ret = RX_HANDLER_ANOTHER; + + skb = skb_share_check(skb, GFP_ATOMIC); + if (unlikely(!skb)) + return RX_HANDLER_CONSUMED; + + *pskb = skb; + + slave = bond_slave_get_rcu(skb->dev); + bond = slave->bond; + + recv_probe = READ_ONCE(bond->recv_probe); + if (recv_probe) { + ret = recv_probe(skb, bond, slave); + if (ret == RX_HANDLER_CONSUMED) { + consume_skb(skb); + return ret; + } + } + + /* + * For packets determined by bond_should_deliver_exact_match() call to + * be suppressed we want to make an exception for link-local packets. + * This is necessary for e.g. LLDP daemons to be able to monitor + * inactive slave links without being forced to bind to them + * explicitly. + * + * At the same time, packets that are passed to the bonding master + * (including link-local ones) can have their originating interface + * determined via PACKET_ORIGDEV socket option. + */ + if (bond_should_deliver_exact_match(skb, slave, bond)) { + if (is_link_local_ether_addr(eth_hdr(skb)->h_dest)) + return RX_HANDLER_PASS; + return RX_HANDLER_EXACT; + } + + skb->dev = bond->dev; + + if (BOND_MODE(bond) == BOND_MODE_ALB && + netif_is_bridge_port(bond->dev) && + skb->pkt_type == PACKET_HOST) { + + if (unlikely(skb_cow_head(skb, + skb->data - skb_mac_header(skb)))) { + kfree_skb(skb); + return RX_HANDLER_CONSUMED; + } + bond_hw_addr_copy(eth_hdr(skb)->h_dest, bond->dev->dev_addr, + bond->dev->addr_len); + } + + return ret; +} + +static enum netdev_lag_tx_type bond_lag_tx_type(struct bonding *bond) +{ + switch (BOND_MODE(bond)) { + case BOND_MODE_ROUNDROBIN: + return NETDEV_LAG_TX_TYPE_ROUNDROBIN; + case BOND_MODE_ACTIVEBACKUP: + return NETDEV_LAG_TX_TYPE_ACTIVEBACKUP; + case BOND_MODE_BROADCAST: + return NETDEV_LAG_TX_TYPE_BROADCAST; + case BOND_MODE_XOR: + case BOND_MODE_8023AD: + return NETDEV_LAG_TX_TYPE_HASH; + default: + return NETDEV_LAG_TX_TYPE_UNKNOWN; + } +} + +static enum netdev_lag_hash bond_lag_hash_type(struct bonding *bond, + enum netdev_lag_tx_type type) +{ + if (type != NETDEV_LAG_TX_TYPE_HASH) + return NETDEV_LAG_HASH_NONE; + + switch (bond->params.xmit_policy) { + case BOND_XMIT_POLICY_LAYER2: + return NETDEV_LAG_HASH_L2; + case BOND_XMIT_POLICY_LAYER34: + return NETDEV_LAG_HASH_L34; + case BOND_XMIT_POLICY_LAYER23: + return NETDEV_LAG_HASH_L23; + case BOND_XMIT_POLICY_ENCAP23: + return NETDEV_LAG_HASH_E23; + case BOND_XMIT_POLICY_ENCAP34: + return NETDEV_LAG_HASH_E34; + case BOND_XMIT_POLICY_VLAN_SRCMAC: + return NETDEV_LAG_HASH_VLAN_SRCMAC; + default: + return NETDEV_LAG_HASH_UNKNOWN; + } +} + +static int bond_master_upper_dev_link(struct bonding *bond, struct slave *slave, + struct netlink_ext_ack *extack) +{ + struct netdev_lag_upper_info lag_upper_info; + enum netdev_lag_tx_type type; + int err; + + type = bond_lag_tx_type(bond); + lag_upper_info.tx_type = type; + lag_upper_info.hash_type = bond_lag_hash_type(bond, type); + + err = netdev_master_upper_dev_link(slave->dev, bond->dev, slave, + &lag_upper_info, extack); + if (err) + return err; + + slave->dev->flags |= IFF_SLAVE; + return 0; +} + +static void bond_upper_dev_unlink(struct bonding *bond, struct slave *slave) +{ + netdev_upper_dev_unlink(slave->dev, bond->dev); + slave->dev->flags &= ~IFF_SLAVE; +} + +static void slave_kobj_release(struct kobject *kobj) +{ + struct slave *slave = to_slave(kobj); + struct bonding *bond = bond_get_bond_by_slave(slave); + + cancel_delayed_work_sync(&slave->notify_work); + if (BOND_MODE(bond) == BOND_MODE_8023AD) + kfree(SLAVE_AD_INFO(slave)); + + kfree(slave); +} + +static struct kobj_type slave_ktype = { + .release = slave_kobj_release, +#ifdef CONFIG_SYSFS + .sysfs_ops = &slave_sysfs_ops, +#endif +}; + +static int bond_kobj_init(struct slave *slave) +{ + int err; + + err = kobject_init_and_add(&slave->kobj, &slave_ktype, + &(slave->dev->dev.kobj), "bonding_slave"); + if (err) + kobject_put(&slave->kobj); + + return err; +} + +static struct slave *bond_alloc_slave(struct bonding *bond, + struct net_device *slave_dev) +{ + struct slave *slave = NULL; + + slave = kzalloc_obj(*slave); + if (!slave) + return NULL; + + slave->bond = bond; + slave->dev = slave_dev; + INIT_DELAYED_WORK(&slave->notify_work, bond_netdev_notify_work); + + if (bond_kobj_init(slave)) + return NULL; + + if (BOND_MODE(bond) == BOND_MODE_8023AD) { + SLAVE_AD_INFO(slave) = kzalloc_obj(struct ad_slave_info); + if (!SLAVE_AD_INFO(slave)) { + kobject_put(&slave->kobj); + return NULL; + } + } + + return slave; +} + +static void bond_fill_ifbond(struct bonding *bond, struct ifbond *info) +{ + info->bond_mode = BOND_MODE(bond); + info->miimon = bond->params.miimon; + info->num_slaves = bond->slave_cnt; +} + +static void bond_fill_ifslave(struct slave *slave, struct ifslave *info) +{ + strcpy(info->slave_name, slave->dev->name); + info->link = slave->link; + info->state = bond_slave_state(slave); + info->link_failure_count = slave->link_failure_count; +} + +static void bond_netdev_notify_work(struct work_struct *_work) +{ + struct slave *slave = container_of(_work, struct slave, + notify_work.work); + + if (rtnl_trylock()) { + struct netdev_bonding_info binfo; + + bond_fill_ifslave(slave, &binfo.slave); + bond_fill_ifbond(slave->bond, &binfo.master); + netdev_bonding_info_change(slave->dev, &binfo); + rtnl_unlock(); + } else { + queue_delayed_work(slave->bond->wq, &slave->notify_work, 1); + } +} + +void bond_queue_slave_event(struct slave *slave) +{ + queue_delayed_work(slave->bond->wq, &slave->notify_work, 0); +} + +void bond_lower_state_changed(struct slave *slave) +{ + struct netdev_lag_lower_state_info info; + + info.link_up = slave->link == BOND_LINK_UP || + slave->link == BOND_LINK_FAIL; + info.tx_enabled = bond_is_active_slave(slave); + netdev_lower_state_changed(slave->dev, &info); +} + +#define BOND_NL_ERR(bond_dev, extack, errmsg) do { \ + if (extack) \ + NL_SET_ERR_MSG(extack, errmsg); \ + else \ + netdev_err(bond_dev, "Error: %s\n", errmsg); \ +} while (0) + +#define SLAVE_NL_ERR(bond_dev, slave_dev, extack, errmsg) do { \ + if (extack) \ + NL_SET_ERR_MSG(extack, errmsg); \ + else \ + slave_err(bond_dev, slave_dev, "Error: %s\n", errmsg); \ +} while (0) + +/* The bonding driver uses ether_setup() to convert a master bond device + * to ARPHRD_ETHER, that resets the target netdevice's flags so we always + * have to restore the IFF_MASTER flag, and only restore IFF_SLAVE and IFF_UP + * if they were set + */ +static void bond_ether_setup(struct net_device *bond_dev) +{ + unsigned int flags = bond_dev->flags & (IFF_SLAVE | IFF_UP); + + ether_setup(bond_dev); + bond_dev->flags |= IFF_MASTER | flags; + bond_dev->priv_flags &= ~IFF_TX_SKB_SHARING; +} + +void bond_xdp_set_features(struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + xdp_features_t val = NETDEV_XDP_ACT_MASK; + struct list_head *iter; + struct slave *slave; + + ASSERT_RTNL(); + + if (!bond_xdp_check(bond, BOND_MODE(bond)) || !bond_has_slaves(bond)) { + xdp_clear_features_flag(bond_dev); + return; + } + + bond_for_each_slave(bond, slave, iter) + val &= slave->dev->xdp_features; + + val &= ~NETDEV_XDP_ACT_XSK_ZEROCOPY; + + xdp_set_features_flag(bond_dev, val); +} + +/* enslave device to bond device */ +int bond_enslave(struct net_device *bond_dev, struct net_device *slave_dev, + struct netlink_ext_ack *extack) +{ + struct bonding *bond = netdev_priv(bond_dev); + const struct net_device_ops *slave_ops = slave_dev->netdev_ops; + struct slave *new_slave = NULL, *prev_slave; + struct sockaddr_storage ss; + int res = 0, i; + + if (slave_dev->type == ARPHRD_CAN) { + BOND_NL_ERR(bond_dev, extack, + "CAN devices cannot be enslaved"); + return -EPERM; + } + + if (slave_dev->flags & IFF_MASTER && + !netif_is_bond_master(slave_dev)) { + BOND_NL_ERR(bond_dev, extack, + "Device type (master device) cannot be enslaved"); + return -EPERM; + } + + /* already in-use? */ + if (netdev_is_rx_handler_busy(slave_dev)) { + SLAVE_NL_ERR(bond_dev, slave_dev, extack, + "Device is in use and cannot be enslaved"); + return -EBUSY; + } + + if (bond_dev == slave_dev) { + BOND_NL_ERR(bond_dev, extack, "Cannot enslave bond to itself."); + return -EPERM; + } + + /* vlan challenged mutual exclusion */ + /* no need to lock since we're protected by rtnl_lock */ + if (slave_dev->features & NETIF_F_VLAN_CHALLENGED) { + slave_dbg(bond_dev, slave_dev, "is NETIF_F_VLAN_CHALLENGED\n"); + if (vlan_uses_dev(bond_dev)) { + SLAVE_NL_ERR(bond_dev, slave_dev, extack, + "Can not enslave VLAN challenged device to VLAN enabled bond"); + return -EPERM; + } else { + slave_warn(bond_dev, slave_dev, "enslaved VLAN challenged slave. Adding VLANs will be blocked as long as it is part of bond.\n"); + } + } else { + slave_dbg(bond_dev, slave_dev, "is !NETIF_F_VLAN_CHALLENGED\n"); + } + + if (slave_dev->features & NETIF_F_HW_ESP) + slave_dbg(bond_dev, slave_dev, "is esp-hw-offload capable\n"); + + /* Old ifenslave binaries are no longer supported. These can + * be identified with moderate accuracy by the state of the slave: + * the current ifenslave will set the interface down prior to + * enslaving it; the old ifenslave will not. + */ + if (slave_dev->flags & IFF_UP) { + SLAVE_NL_ERR(bond_dev, slave_dev, extack, + "Device can not be enslaved while up"); + return -EPERM; + } + + /* set bonding device ether type by slave - bonding netdevices are + * created with ether_setup, so when the slave type is not ARPHRD_ETHER + * there is a need to override some of the type dependent attribs/funcs. + * + * bond ether type mutual exclusion - don't allow slaves of dissimilar + * ether type (eg ARPHRD_ETHER and ARPHRD_INFINIBAND) share the same bond + */ + if (!bond_has_slaves(bond)) { + if (bond_dev->type != slave_dev->type) { + if (slave_dev->type != ARPHRD_ETHER && + BOND_MODE(bond) == BOND_MODE_8023AD) { + SLAVE_NL_ERR(bond_dev, slave_dev, extack, + "8023AD mode requires Ethernet devices"); + return -EINVAL; + } + slave_dbg(bond_dev, slave_dev, "change device type from %d to %d\n", + bond_dev->type, slave_dev->type); + + res = call_netdevice_notifiers(NETDEV_PRE_TYPE_CHANGE, + bond_dev); + res = notifier_to_errno(res); + if (res) { + slave_err(bond_dev, slave_dev, "refused to change device type\n"); + return -EBUSY; + } + + /* Flush unicast and multicast addresses */ + dev_uc_flush(bond_dev); + dev_mc_flush(bond_dev); + + if (slave_dev->type != ARPHRD_ETHER) + bond_setup_by_slave(bond_dev, slave_dev); + else + bond_ether_setup(bond_dev); + + call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, + bond_dev); + } + } else if (bond_dev->type != slave_dev->type) { + SLAVE_NL_ERR(bond_dev, slave_dev, extack, + "Device type is different from other slaves"); + return -EINVAL; + } + + if (slave_dev->type == ARPHRD_INFINIBAND && + BOND_MODE(bond) != BOND_MODE_ACTIVEBACKUP) { + SLAVE_NL_ERR(bond_dev, slave_dev, extack, + "Only active-backup mode is supported for infiniband slaves"); + res = -EOPNOTSUPP; + goto err_undo_flags; + } + + if (!slave_ops->ndo_set_mac_address || + slave_dev->type == ARPHRD_INFINIBAND) { + slave_warn(bond_dev, slave_dev, "The slave device specified does not support setting the MAC address\n"); + if (BOND_MODE(bond) == BOND_MODE_ACTIVEBACKUP && + bond->params.fail_over_mac != BOND_FOM_ACTIVE) { + if (!bond_has_slaves(bond)) { + bond->params.fail_over_mac = BOND_FOM_ACTIVE; + slave_warn(bond_dev, slave_dev, "Setting fail_over_mac to active for active-backup mode\n"); + } else { + SLAVE_NL_ERR(bond_dev, slave_dev, extack, + "Slave device does not support setting the MAC address, but fail_over_mac is not set to active"); + res = -EOPNOTSUPP; + goto err_undo_flags; + } + } + } + + call_netdevice_notifiers(NETDEV_JOIN, slave_dev); + + /* If this is the first slave, then we need to set the master's hardware + * address to be the same as the slave's. + */ + if (!bond_has_slaves(bond) && + bond->dev->addr_assign_type == NET_ADDR_RANDOM) { + res = bond_set_dev_addr(bond->dev, slave_dev); + if (res) + goto err_undo_flags; + } + + new_slave = bond_alloc_slave(bond, slave_dev); + if (!new_slave) { + res = -ENOMEM; + goto err_undo_flags; + } + + /* Set the new_slave's queue_id to be zero. Queue ID mapping + * is set via sysfs or module option if desired. + */ + new_slave->queue_id = 0; + + /* Save slave's original mtu and then set it to match the bond */ + new_slave->original_mtu = slave_dev->mtu; + res = dev_set_mtu(slave_dev, bond->dev->mtu); + if (res) { + slave_err(bond_dev, slave_dev, "Error %d calling dev_set_mtu\n", res); + goto err_free; + } + + /* Save slave's original ("permanent") mac address for modes + * that need it, and for restoring it upon release, and then + * set it to the master's address + */ + bond_hw_addr_copy(new_slave->perm_hwaddr, slave_dev->dev_addr, + slave_dev->addr_len); + + if (!bond->params.fail_over_mac || + BOND_MODE(bond) != BOND_MODE_ACTIVEBACKUP) { + /* Set slave to master's mac address. The application already + * set the master's mac address to that of the first slave + */ + memcpy(ss.__data, bond_dev->dev_addr, bond_dev->addr_len); + } else if (bond->params.fail_over_mac == BOND_FOM_FOLLOW && + BOND_MODE(bond) == BOND_MODE_ACTIVEBACKUP && + bond_has_slaves(bond) && + memcmp(slave_dev->dev_addr, bond_dev->dev_addr, bond_dev->addr_len) == 0) { + /* Set slave to random address to avoid duplicate mac + * address in later fail over. + */ + eth_random_addr(ss.__data); + } else { + goto skip_mac_set; + } + + ss.ss_family = slave_dev->type; + res = dev_set_mac_address(slave_dev, &ss, extack); + if (res) { + slave_err(bond_dev, slave_dev, "Error %d calling set_mac_address\n", res); + goto err_restore_mtu; + } + +skip_mac_set: + + /* set no_addrconf flag before open to prevent IPv6 addrconf */ + slave_dev->priv_flags |= IFF_NO_ADDRCONF; + + /* open the slave since the application closed it */ + res = dev_open(slave_dev, extack); + if (res) { + slave_err(bond_dev, slave_dev, "Opening slave failed\n"); + goto err_restore_mac; + } + + slave_dev->priv_flags |= IFF_BONDING; + /* initialize slave stats */ + dev_get_stats(new_slave->dev, &new_slave->slave_stats); + + if (bond_is_lb(bond)) { + /* bond_alb_init_slave() must be called before all other stages since + * it might fail and we do not want to have to undo everything + */ + res = bond_alb_init_slave(bond, new_slave); + if (res) + goto err_close; + } + + res = vlan_vids_add_by_dev(slave_dev, bond_dev); + if (res) { + slave_err(bond_dev, slave_dev, "Couldn't add bond vlan ids\n"); + goto err_close; + } + + prev_slave = bond_last_slave(bond); + + new_slave->delay = 0; + new_slave->link_failure_count = 0; + + netdev_lock_ops(slave_dev); + res = bond_update_speed_duplex(new_slave); + netdev_unlock_ops(slave_dev); + if (res && bond_needs_speed_duplex(bond)) + new_slave->link = BOND_LINK_DOWN; + + new_slave->last_rx = jiffies - + (msecs_to_jiffies(bond->params.arp_interval) + 1); + for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) + new_slave->target_last_arp_rx[i] = new_slave->last_rx; + + new_slave->last_tx = new_slave->last_rx; + + /* check for initial state */ + new_slave->link = BOND_LINK_NOCHANGE; + if (bond->params.miimon) { + if (netif_running(slave_dev) && netif_carrier_ok(slave_dev)) { + if (bond->params.updelay) { + bond_set_slave_link_state(new_slave, + BOND_LINK_BACK, + BOND_SLAVE_NOTIFY_NOW); + new_slave->delay = bond->params.updelay; + } else { + bond_set_slave_link_state(new_slave, + BOND_LINK_UP, + BOND_SLAVE_NOTIFY_NOW); + } + } else { + bond_set_slave_link_state(new_slave, BOND_LINK_DOWN, + BOND_SLAVE_NOTIFY_NOW); + } + } else if (bond->params.arp_interval) { + bond_set_slave_link_state(new_slave, + (netif_carrier_ok(slave_dev) ? + BOND_LINK_UP : BOND_LINK_DOWN), + BOND_SLAVE_NOTIFY_NOW); + } else { + bond_set_slave_link_state(new_slave, BOND_LINK_UP, + BOND_SLAVE_NOTIFY_NOW); + } + + if (new_slave->link != BOND_LINK_DOWN) + new_slave->last_link_up = jiffies; + slave_dbg(bond_dev, slave_dev, "Initial state of slave is BOND_LINK_%s\n", + new_slave->link == BOND_LINK_DOWN ? "DOWN" : + (new_slave->link == BOND_LINK_UP ? "UP" : "BACK")); + + if (bond_uses_primary(bond) && bond->params.primary[0]) { + /* if there is a primary slave, remember it */ + if (strcmp(bond->params.primary, new_slave->dev->name) == 0) { + rcu_assign_pointer(bond->primary_slave, new_slave); + bond->force_primary = true; + } + } + + switch (BOND_MODE(bond)) { + case BOND_MODE_ACTIVEBACKUP: + bond_set_slave_inactive_flags(new_slave, + BOND_SLAVE_NOTIFY_NOW); + break; + case BOND_MODE_8023AD: + /* in 802.3ad mode, the internal mechanism + * will activate the slaves in the selected + * aggregator + */ + bond_set_slave_inactive_flags(new_slave, BOND_SLAVE_NOTIFY_NOW); + /* if this is the first slave */ + if (!prev_slave) { + SLAVE_AD_INFO(new_slave)->id = 1; + /* Initialize AD with the number of times that the AD timer is called in 1 second + * can be called only after the mac address of the bond is set + */ + bond_3ad_initialize(bond); + } else { + SLAVE_AD_INFO(new_slave)->id = + SLAVE_AD_INFO(prev_slave)->id + 1; + } + + bond_3ad_bind_slave(new_slave); + break; + case BOND_MODE_TLB: + case BOND_MODE_ALB: + bond_set_active_slave(new_slave); + bond_set_slave_inactive_flags(new_slave, BOND_SLAVE_NOTIFY_NOW); + break; + default: + slave_dbg(bond_dev, slave_dev, "This slave is always active in trunk mode\n"); + + /* always active in trunk mode */ + bond_set_active_slave(new_slave); + + /* In trunking mode there is little meaning to curr_active_slave + * anyway (it holds no special properties of the bond device), + * so we can change it without calling change_active_interface() + */ + if (!rcu_access_pointer(bond->curr_active_slave) && + new_slave->link == BOND_LINK_UP) + rcu_assign_pointer(bond->curr_active_slave, new_slave); + + break; + } /* switch(bond_mode) */ + +#ifdef CONFIG_NET_POLL_CONTROLLER + if (bond->dev->npinfo) { + if (slave_enable_netpoll(new_slave)) { + slave_info(bond_dev, slave_dev, "master_dev is using netpoll, but new slave device does not support netpoll\n"); + res = -EBUSY; + goto err_detach; + } + } +#endif + + if (!(bond_dev->features & NETIF_F_LRO)) + dev_disable_lro(slave_dev); + + res = netdev_rx_handler_register(slave_dev, bond_handle_frame, + new_slave); + if (res) { + slave_dbg(bond_dev, slave_dev, "Error %d calling netdev_rx_handler_register\n", res); + goto err_detach; + } + + res = bond_master_upper_dev_link(bond, new_slave, extack); + if (res) { + slave_dbg(bond_dev, slave_dev, "Error %d calling bond_master_upper_dev_link\n", res); + goto err_unregister; + } + + bond_lower_state_changed(new_slave); + + res = bond_sysfs_slave_add(new_slave); + if (res) { + slave_dbg(bond_dev, slave_dev, "Error %d calling bond_sysfs_slave_add\n", res); + goto err_upper_unlink; + } + + /* If the mode uses primary, then the following is handled by + * bond_change_active_slave(). + */ + if (!bond_uses_primary(bond)) { + /* set promiscuity level to new slave */ + if (bond_dev->flags & IFF_PROMISC) { + res = dev_set_promiscuity(slave_dev, 1); + if (res) + goto err_sysfs_del; + } + + /* set allmulti level to new slave */ + if (bond_dev->flags & IFF_ALLMULTI) { + res = dev_set_allmulti(slave_dev, 1); + if (res) { + if (bond_dev->flags & IFF_PROMISC) + dev_set_promiscuity(slave_dev, -1); + goto err_sysfs_del; + } + } + + if (bond_dev->flags & IFF_UP) { + netif_addr_lock_bh(bond_dev); + dev_mc_sync_multiple(slave_dev, bond_dev); + dev_uc_sync_multiple(slave_dev, bond_dev); + netif_addr_unlock_bh(bond_dev); + + if (BOND_MODE(bond) == BOND_MODE_8023AD) + dev_mc_add(slave_dev, lacpdu_mcast_addr); + } + } + + bond->slave_cnt++; + netdev_compute_master_upper_features(bond->dev, true); + bond_set_carrier(bond); + + /* Needs to be called before bond_select_active_slave(), which will + * remove the maddrs if the slave is selected as active slave. + */ + bond_slave_ns_maddrs_add(bond, new_slave); + + if (bond_uses_primary(bond)) { + block_netpoll_tx(); + bond_select_active_slave(bond); + unblock_netpoll_tx(); + } + + if (!slave_dev->netdev_ops->ndo_bpf || + !slave_dev->netdev_ops->ndo_xdp_xmit) { + if (bond->xdp_prog) { + SLAVE_NL_ERR(bond_dev, slave_dev, extack, + "Slave does not support XDP"); + res = -EOPNOTSUPP; + goto err_sysfs_del; + } + } else if (bond->xdp_prog) { + struct netdev_bpf xdp = { + .command = XDP_SETUP_PROG, + .flags = 0, + .prog = bond->xdp_prog, + .extack = extack, + }; + + if (dev_xdp_prog_count(slave_dev) > 0) { + SLAVE_NL_ERR(bond_dev, slave_dev, extack, + "Slave has XDP program loaded, please unload before enslaving"); + res = -EOPNOTSUPP; + goto err_sysfs_del; + } + + res = dev_xdp_propagate(slave_dev, &xdp); + if (res < 0) { + /* ndo_bpf() sets extack error message */ + slave_dbg(bond_dev, slave_dev, "Error %d calling ndo_bpf\n", res); + goto err_sysfs_del; + } + if (bond->xdp_prog) + bpf_prog_inc(bond->xdp_prog); + } + + /* broadcast mode uses the all_slaves to loop through slaves. */ + if (bond_mode_can_use_xmit_hash(bond) || + BOND_MODE(bond) == BOND_MODE_BROADCAST) + bond_update_slave_arr(bond, NULL); + + bond_xdp_set_features(bond_dev); + + slave_info(bond_dev, slave_dev, "Enslaving as %s interface with %s link\n", + bond_is_active_slave(new_slave) ? "an active" : "a backup", + new_slave->link != BOND_LINK_DOWN ? "an up" : "a down"); + + /* enslave is successful */ + bond_queue_slave_event(new_slave); + return 0; + +/* Undo stages on error */ +err_sysfs_del: + bond_sysfs_slave_del(new_slave); + +err_upper_unlink: + bond_upper_dev_unlink(bond, new_slave); + +err_unregister: + netdev_rx_handler_unregister(slave_dev); + +err_detach: + vlan_vids_del_by_dev(slave_dev, bond_dev); + if (rcu_access_pointer(bond->primary_slave) == new_slave) + RCU_INIT_POINTER(bond->primary_slave, NULL); + if (rcu_access_pointer(bond->curr_active_slave) == new_slave) { + block_netpoll_tx(); + bond_change_active_slave(bond, NULL); + bond_select_active_slave(bond); + unblock_netpoll_tx(); + } + /* either primary_slave or curr_active_slave might've changed */ + synchronize_rcu(); + slave_disable_netpoll(new_slave); + +err_close: + if (!netif_is_bond_master(slave_dev)) + slave_dev->priv_flags &= ~IFF_BONDING; + dev_close(slave_dev); + +err_restore_mac: + slave_dev->priv_flags &= ~IFF_NO_ADDRCONF; + if (!bond->params.fail_over_mac || + BOND_MODE(bond) != BOND_MODE_ACTIVEBACKUP) { + /* XXX TODO - fom follow mode needs to change master's + * MAC if this slave's MAC is in use by the bond, or at + * least print a warning. + */ + bond_hw_addr_copy(ss.__data, new_slave->perm_hwaddr, + new_slave->dev->addr_len); + ss.ss_family = slave_dev->type; + dev_set_mac_address(slave_dev, &ss, NULL); + } + +err_restore_mtu: + dev_set_mtu(slave_dev, new_slave->original_mtu); + +err_free: + kobject_put(&new_slave->kobj); + +err_undo_flags: + /* Enslave of first slave has failed and we need to fix master's mac */ + if (!bond_has_slaves(bond)) { + if (ether_addr_equal_64bits(bond_dev->dev_addr, + slave_dev->dev_addr)) + eth_hw_addr_random(bond_dev); + if (bond_dev->type != ARPHRD_ETHER) { + dev_close(bond_dev); + bond_ether_setup(bond_dev); + } + } + + return res; +} + +/* Try to release the slave device from the bond device + * It is legal to access curr_active_slave without a lock because all the function + * is RTNL-locked. If "all" is true it means that the function is being called + * while destroying a bond interface and all slaves are being released. + * + * The rules for slave state should be: + * for Active/Backup: + * Active stays on all backups go down + * for Bonded connections: + * The first up interface should be left on and all others downed. + */ +static int __bond_release_one(struct net_device *bond_dev, + struct net_device *slave_dev, + bool all, bool unregister) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct slave *slave, *oldcurrent; + struct sockaddr_storage ss; + int old_flags = bond_dev->flags; + netdev_features_t old_features = bond_dev->features; + + /* slave is not a slave or master is not master of this slave */ + if (!(slave_dev->flags & IFF_SLAVE) || + !netdev_has_upper_dev(slave_dev, bond_dev)) { + slave_dbg(bond_dev, slave_dev, "cannot release slave\n"); + return -EINVAL; + } + + block_netpoll_tx(); + + slave = bond_get_slave_by_dev(bond, slave_dev); + if (!slave) { + /* not a slave of this bond */ + slave_info(bond_dev, slave_dev, "interface not enslaved\n"); + unblock_netpoll_tx(); + return -EINVAL; + } + + bond_set_slave_inactive_flags(slave, BOND_SLAVE_NOTIFY_NOW); + + bond_sysfs_slave_del(slave); + + /* recompute stats just before removing the slave */ + bond_get_stats(bond->dev, &bond->bond_stats); + + if (bond->xdp_prog) { + struct netdev_bpf xdp = { + .command = XDP_SETUP_PROG, + .flags = 0, + .prog = NULL, + .extack = NULL, + }; + if (dev_xdp_propagate(slave_dev, &xdp)) + slave_warn(bond_dev, slave_dev, "failed to unload XDP program\n"); + } + + /* unregister rx_handler early so bond_handle_frame wouldn't be called + * for this slave anymore. + */ + netdev_rx_handler_unregister(slave_dev); + + if (BOND_MODE(bond) == BOND_MODE_8023AD) + bond_3ad_unbind_slave(slave); + + bond_upper_dev_unlink(bond, slave); + + if (bond_mode_can_use_xmit_hash(bond) || + BOND_MODE(bond) == BOND_MODE_BROADCAST) + bond_update_slave_arr(bond, slave); + + slave_info(bond_dev, slave_dev, "Releasing %s interface\n", + bond_is_active_slave(slave) ? "active" : "backup"); + + oldcurrent = rcu_access_pointer(bond->curr_active_slave); + + RCU_INIT_POINTER(bond->current_arp_slave, NULL); + + if (!all && (bond->params.fail_over_mac != BOND_FOM_ACTIVE || + BOND_MODE(bond) != BOND_MODE_ACTIVEBACKUP)) { + if (ether_addr_equal_64bits(bond_dev->dev_addr, slave->perm_hwaddr) && + bond_has_slaves(bond)) + slave_warn(bond_dev, slave_dev, "the permanent HWaddr of slave - %pM - is still in use by bond - set the HWaddr of slave to a different address to avoid conflicts\n", + slave->perm_hwaddr); + } + + if (rtnl_dereference(bond->primary_slave) == slave) + RCU_INIT_POINTER(bond->primary_slave, NULL); + + if (oldcurrent == slave) + bond_change_active_slave(bond, NULL); + + /* Must be called after bond_change_active_slave () as the slave + * might change from an active slave to a backup slave. Then it is + * necessary to clear the maddrs on the backup slave. + */ + bond_slave_ns_maddrs_del(bond, slave); + + if (bond_is_lb(bond)) { + /* Must be called only after the slave has been + * detached from the list and the curr_active_slave + * has been cleared (if our_slave == old_current), + * but before a new active slave is selected. + */ + bond_alb_deinit_slave(bond, slave); + } + + if (all) { + RCU_INIT_POINTER(bond->curr_active_slave, NULL); + } else if (oldcurrent == slave) { + /* Note that we hold RTNL over this sequence, so there + * is no concern that another slave add/remove event + * will interfere. + */ + bond_select_active_slave(bond); + } + + bond_set_carrier(bond); + if (!bond_has_slaves(bond)) + eth_hw_addr_random(bond_dev); + + unblock_netpoll_tx(); + synchronize_rcu(); + bond->slave_cnt--; + + if (!bond_has_slaves(bond)) { + call_netdevice_notifiers(NETDEV_CHANGEADDR, bond->dev); + call_netdevice_notifiers(NETDEV_RELEASE, bond->dev); + } + + netdev_compute_master_upper_features(bond->dev, true); + if (!(bond_dev->features & NETIF_F_VLAN_CHALLENGED) && + (old_features & NETIF_F_VLAN_CHALLENGED)) + slave_info(bond_dev, slave_dev, "last VLAN challenged slave left bond - VLAN blocking is removed\n"); + + vlan_vids_del_by_dev(slave_dev, bond_dev); + + /* If the mode uses primary, then this case was handled above by + * bond_change_active_slave(..., NULL) + */ + if (!bond_uses_primary(bond)) { + /* unset promiscuity level from slave + * NOTE: The NETDEV_CHANGEADDR call above may change the value + * of the IFF_PROMISC flag in the bond_dev, but we need the + * value of that flag before that change, as that was the value + * when this slave was attached, so we cache at the start of the + * function and use it here. Same goes for ALLMULTI below + */ + if (old_flags & IFF_PROMISC) + dev_set_promiscuity(slave_dev, -1); + + /* unset allmulti level from slave */ + if (old_flags & IFF_ALLMULTI) + dev_set_allmulti(slave_dev, -1); + + if (old_flags & IFF_UP) + bond_hw_addr_flush(bond_dev, slave_dev); + } + + slave_disable_netpoll(slave); + + /* close slave before restoring its mac address */ + dev_close(slave_dev); + + slave_dev->priv_flags &= ~IFF_NO_ADDRCONF; + + if (bond->params.fail_over_mac != BOND_FOM_ACTIVE || + BOND_MODE(bond) != BOND_MODE_ACTIVEBACKUP) { + /* restore original ("permanent") mac address */ + bond_hw_addr_copy(ss.__data, slave->perm_hwaddr, + slave->dev->addr_len); + ss.ss_family = slave_dev->type; + dev_set_mac_address(slave_dev, &ss, NULL); + } + + if (unregister) { + netdev_lock_ops(slave_dev); + __netif_set_mtu(slave_dev, slave->original_mtu); + netdev_unlock_ops(slave_dev); + } else { + dev_set_mtu(slave_dev, slave->original_mtu); + } + + if (!netif_is_bond_master(slave_dev)) + slave_dev->priv_flags &= ~IFF_BONDING; + + bond_xdp_set_features(bond_dev); + kobject_put(&slave->kobj); + + return 0; +} + +/* A wrapper used because of ndo_del_link */ +int bond_release(struct net_device *bond_dev, struct net_device *slave_dev) +{ + return __bond_release_one(bond_dev, slave_dev, false, false); +} + +/* First release a slave and then destroy the bond if no more slaves are left. + * Must be under rtnl_lock when this function is called. + */ +static int bond_release_and_destroy(struct net_device *bond_dev, + struct net_device *slave_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + int ret; + + ret = __bond_release_one(bond_dev, slave_dev, false, true); + if (ret == 0 && !bond_has_slaves(bond) && + bond_dev->reg_state != NETREG_UNREGISTERING) { + bond_dev->priv_flags |= IFF_DISABLE_NETPOLL; + netdev_info(bond_dev, "Destroying bond\n"); + bond_remove_proc_entry(bond); + unregister_netdevice(bond_dev); + } + return ret; +} + +static void bond_info_query(struct net_device *bond_dev, struct ifbond *info) +{ + struct bonding *bond = netdev_priv(bond_dev); + + bond_fill_ifbond(bond, info); +} + +static int bond_slave_info_query(struct net_device *bond_dev, struct ifslave *info) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct list_head *iter; + int i = 0, res = -ENODEV; + struct slave *slave; + + bond_for_each_slave(bond, slave, iter) { + if (i++ == (int)info->slave_id) { + res = 0; + bond_fill_ifslave(slave, info); + break; + } + } + + return res; +} + +/*-------------------------------- Monitoring -------------------------------*/ + +/* called with rcu_read_lock() */ +static int bond_miimon_inspect(struct bonding *bond) +{ + bool ignore_updelay = false; + int link_state, commit = 0; + struct list_head *iter; + struct slave *slave; + + if (BOND_MODE(bond) == BOND_MODE_ACTIVEBACKUP) { + ignore_updelay = !rcu_dereference(bond->curr_active_slave); + } else { + struct bond_up_slave *usable_slaves; + + usable_slaves = rcu_dereference(bond->usable_slaves); + + if (usable_slaves && usable_slaves->count == 0) + ignore_updelay = true; + } + + bond_for_each_slave_rcu(bond, slave, iter) { + bond_propose_link_state(slave, BOND_LINK_NOCHANGE); + + link_state = netif_running(slave->dev) && + netif_carrier_ok(slave->dev); + + switch (slave->link) { + case BOND_LINK_UP: + if (link_state) + continue; + + bond_propose_link_state(slave, BOND_LINK_FAIL); + commit++; + slave->delay = bond->params.downdelay; + if (slave->delay && net_ratelimit()) { + slave_info(bond->dev, slave->dev, "link status down for %sinterface, disabling it in %d ms\n", + (BOND_MODE(bond) == + BOND_MODE_ACTIVEBACKUP) ? + (bond_is_active_slave(slave) ? + "active " : "backup ") : "", + bond->params.downdelay * bond->params.miimon); + } + fallthrough; + case BOND_LINK_FAIL: + if (link_state) { + /* recovered before downdelay expired */ + bond_propose_link_state(slave, BOND_LINK_UP); + slave->last_link_up = jiffies; + if (net_ratelimit()) + slave_info(bond->dev, slave->dev, "link status up again after %d ms\n", + (bond->params.downdelay - slave->delay) * + bond->params.miimon); + commit++; + continue; + } + + if (slave->delay <= 0) { + bond_propose_link_state(slave, BOND_LINK_DOWN); + commit++; + continue; + } + + slave->delay--; + break; + + case BOND_LINK_DOWN: + if (!link_state) + continue; + + bond_propose_link_state(slave, BOND_LINK_BACK); + commit++; + slave->delay = bond->params.updelay; + + if (slave->delay && net_ratelimit()) { + slave_info(bond->dev, slave->dev, "link status up, enabling it in %d ms\n", + ignore_updelay ? 0 : + bond->params.updelay * + bond->params.miimon); + } + fallthrough; + case BOND_LINK_BACK: + if (!link_state) { + bond_propose_link_state(slave, BOND_LINK_DOWN); + if (net_ratelimit()) + slave_info(bond->dev, slave->dev, "link status down again after %d ms\n", + (bond->params.updelay - slave->delay) * + bond->params.miimon); + commit++; + continue; + } + + if (ignore_updelay) + slave->delay = 0; + + if (slave->delay <= 0) { + bond_propose_link_state(slave, BOND_LINK_UP); + commit++; + ignore_updelay = false; + continue; + } + + slave->delay--; + break; + } + } + + return commit; +} + +static void bond_miimon_link_change(struct bonding *bond, + struct slave *slave, + char link) +{ + switch (BOND_MODE(bond)) { + case BOND_MODE_8023AD: + bond_3ad_handle_link_change(slave, link); + break; + case BOND_MODE_TLB: + case BOND_MODE_ALB: + bond_alb_handle_link_change(bond, slave, link); + break; + case BOND_MODE_XOR: + bond_update_slave_arr(bond, NULL); + break; + } +} + +static void bond_miimon_commit(struct bonding *bond) +{ + struct slave *slave, *primary, *active; + bool do_failover = false; + struct list_head *iter; + int err; + + ASSERT_RTNL(); + + bond_for_each_slave(bond, slave, iter) { + switch (slave->link_new_state) { + case BOND_LINK_NOCHANGE: + /* For 802.3ad mode, check current slave speed and + * duplex again in case its port was disabled after + * invalid speed/duplex reporting but recovered before + * link monitoring could make a decision on the actual + * link status + */ + if (BOND_MODE(bond) == BOND_MODE_8023AD && + slave->link == BOND_LINK_UP) + bond_3ad_adapter_speed_duplex_changed(slave); + continue; + + case BOND_LINK_UP: + netdev_lock_ops(slave->dev); + err = bond_update_speed_duplex(slave); + netdev_unlock_ops(slave->dev); + if (err && bond_needs_speed_duplex(bond)) { + slave->link = BOND_LINK_DOWN; + if (net_ratelimit()) + slave_warn(bond->dev, slave->dev, + "failed to get link speed/duplex\n"); + continue; + } + bond_set_slave_link_state(slave, BOND_LINK_UP, + BOND_SLAVE_NOTIFY_NOW); + slave->last_link_up = jiffies; + + primary = rtnl_dereference(bond->primary_slave); + if (BOND_MODE(bond) == BOND_MODE_8023AD) { + /* prevent it from being the active one */ + bond_set_backup_slave(slave); + } else if (BOND_MODE(bond) != BOND_MODE_ACTIVEBACKUP) { + /* make it immediately active */ + bond_set_active_slave(slave); + } + + slave_info(bond->dev, slave->dev, "link status definitely up, %u Mbps %s duplex\n", + slave->speed == SPEED_UNKNOWN ? 0 : slave->speed, + slave->duplex ? "full" : "half"); + + bond_miimon_link_change(bond, slave, BOND_LINK_UP); + + active = rtnl_dereference(bond->curr_active_slave); + if (!active || slave == primary || slave->prio > active->prio) + do_failover = true; + + continue; + + case BOND_LINK_DOWN: + if (slave->link_failure_count < UINT_MAX) + slave->link_failure_count++; + + bond_set_slave_link_state(slave, BOND_LINK_DOWN, + BOND_SLAVE_NOTIFY_NOW); + + if (BOND_MODE(bond) == BOND_MODE_ACTIVEBACKUP || + BOND_MODE(bond) == BOND_MODE_8023AD) + bond_set_slave_inactive_flags(slave, + BOND_SLAVE_NOTIFY_NOW); + + slave_info(bond->dev, slave->dev, "link status definitely down, disabling slave\n"); + + bond_miimon_link_change(bond, slave, BOND_LINK_DOWN); + + if (slave == rcu_access_pointer(bond->curr_active_slave)) + do_failover = true; + + continue; + + case BOND_LINK_FAIL: + case BOND_LINK_BACK: + slave_dbg(bond->dev, slave->dev, "link_new_state %d on slave\n", + slave->link_new_state); + continue; + + default: + slave_err(bond->dev, slave->dev, "invalid link_new_state %d on slave\n", + slave->link_new_state); + bond_propose_link_state(slave, BOND_LINK_NOCHANGE); + + continue; + } + } + + if (do_failover) { + block_netpoll_tx(); + bond_select_active_slave(bond); + unblock_netpoll_tx(); + } + + bond_set_carrier(bond); +} + +/* bond_mii_monitor + * + * Really a wrapper that splits the mii monitor into two phases: an + * inspection, then (if inspection indicates something needs to be done) + * an acquisition of appropriate locks followed by a commit phase to + * implement whatever link state changes are indicated. + */ +static void bond_mii_monitor(struct work_struct *work) +{ + struct bonding *bond = container_of(work, struct bonding, + mii_work.work); + struct list_head *iter; + struct slave *slave; + unsigned long delay; + bool commit; + + delay = msecs_to_jiffies(bond->params.miimon); + + if (!bond_has_slaves(bond)) + goto re_arm; + + rcu_read_lock(); + + commit = !!bond_miimon_inspect(bond); + + rcu_read_unlock(); + + if (commit || READ_ONCE(bond->send_peer_notif)) { + /* Race avoidance with bond_close cancel of workqueue */ + if (!rtnl_trylock()) { + delay = 1; + goto re_arm; + } + + if (commit) { + bond_for_each_slave(bond, slave, iter) { + bond_commit_link_state(slave, + BOND_SLAVE_NOTIFY_LATER); + } + bond_miimon_commit(bond); + } + + if (bond->send_peer_notif) + bond_peer_notify_may_events(bond, true); + + rtnl_unlock(); /* might sleep, hold no other locks */ + } + +re_arm: + if (bond->params.miimon) + queue_delayed_work(bond->wq, &bond->mii_work, delay); +} + +static int bond_upper_dev_walk(struct net_device *upper, + struct netdev_nested_priv *priv) +{ + __be32 ip = *(__be32 *)priv->data; + + return ip == bond_confirm_addr(upper, 0, ip); +} + +static bool bond_has_this_ip(struct bonding *bond, __be32 ip) +{ + struct netdev_nested_priv priv = { + .data = (void *)&ip, + }; + bool ret = false; + + if (ip == bond_confirm_addr(bond->dev, 0, ip)) + return true; + + rcu_read_lock(); + if (netdev_walk_all_upper_dev_rcu(bond->dev, bond_upper_dev_walk, &priv)) + ret = true; + rcu_read_unlock(); + + return ret; +} + +#define BOND_VLAN_PROTO_NONE cpu_to_be16(0xffff) + +static bool bond_handle_vlan(struct slave *slave, struct bond_vlan_tag *tags, + struct sk_buff *skb) +{ + struct net_device *bond_dev = slave->bond->dev; + struct net_device *slave_dev = slave->dev; + struct bond_vlan_tag *outer_tag = tags; + + if (!tags || tags->vlan_proto == BOND_VLAN_PROTO_NONE) + return true; + + tags++; + + /* Go through all the tags backwards and add them to the packet */ + while (tags->vlan_proto != BOND_VLAN_PROTO_NONE) { + if (!tags->vlan_id) { + tags++; + continue; + } + + slave_dbg(bond_dev, slave_dev, "inner tag: proto %X vid %X\n", + ntohs(outer_tag->vlan_proto), tags->vlan_id); + skb = vlan_insert_tag_set_proto(skb, tags->vlan_proto, + tags->vlan_id); + if (!skb) { + net_err_ratelimited("failed to insert inner VLAN tag\n"); + return false; + } + + tags++; + } + /* Set the outer tag */ + if (outer_tag->vlan_id) { + slave_dbg(bond_dev, slave_dev, "outer tag: proto %X vid %X\n", + ntohs(outer_tag->vlan_proto), outer_tag->vlan_id); + __vlan_hwaccel_put_tag(skb, outer_tag->vlan_proto, + outer_tag->vlan_id); + } + + return true; +} + +/* We go to the (large) trouble of VLAN tagging ARP frames because + * switches in VLAN mode (especially if ports are configured as + * "native" to a VLAN) might not pass non-tagged frames. + */ +static void bond_arp_send(struct slave *slave, int arp_op, __be32 dest_ip, + __be32 src_ip, struct bond_vlan_tag *tags) +{ + struct net_device *bond_dev = slave->bond->dev; + struct net_device *slave_dev = slave->dev; + struct sk_buff *skb; + + slave_dbg(bond_dev, slave_dev, "arp %d on slave: dst %pI4 src %pI4\n", + arp_op, &dest_ip, &src_ip); + + skb = arp_create(arp_op, ETH_P_ARP, dest_ip, slave_dev, src_ip, + NULL, slave_dev->dev_addr, NULL); + + if (!skb) { + net_err_ratelimited("ARP packet allocation failed\n"); + return; + } + + if (bond_handle_vlan(slave, tags, skb)) { + slave_update_last_tx(slave); + arp_xmit(skb); + } + + return; +} + +/* Validate the device path between the @start_dev and the @end_dev. + * The path is valid if the @end_dev is reachable through device + * stacking. + * When the path is validated, collect any vlan information in the + * path. + */ +struct bond_vlan_tag *bond_verify_device_path(struct net_device *start_dev, + struct net_device *end_dev, + int level) +{ + struct bond_vlan_tag *tags; + struct net_device *upper; + struct list_head *iter; + + if (start_dev == end_dev) { + tags = kzalloc_objs(*tags, level + 1, GFP_ATOMIC); + if (!tags) + return ERR_PTR(-ENOMEM); + tags[level].vlan_proto = BOND_VLAN_PROTO_NONE; + return tags; + } + + netdev_for_each_upper_dev_rcu(start_dev, upper, iter) { + tags = bond_verify_device_path(upper, end_dev, level + 1); + if (IS_ERR_OR_NULL(tags)) { + if (IS_ERR(tags)) + return tags; + continue; + } + if (is_vlan_dev(upper)) { + tags[level].vlan_proto = vlan_dev_vlan_proto(upper); + tags[level].vlan_id = vlan_dev_vlan_id(upper); + } + + return tags; + } + + return NULL; +} + +static void bond_arp_send_all(struct bonding *bond, struct slave *slave) +{ + struct rtable *rt; + struct bond_vlan_tag *tags; + __be32 *targets = bond->params.arp_targets, addr; + int i; + + for (i = 0; i < BOND_MAX_ARP_TARGETS && targets[i]; i++) { + slave_dbg(bond->dev, slave->dev, "%s: target %pI4\n", + __func__, &targets[i]); + tags = NULL; + + /* Find out through which dev should the packet go */ + rt = ip_route_output(dev_net(bond->dev), targets[i], 0, 0, 0, + RT_SCOPE_LINK); + if (IS_ERR(rt)) { + /* there's no route to target - try to send arp + * probe to generate any traffic (arp_validate=0) + */ + if (bond->params.arp_validate) + pr_warn_once("%s: no route to arp_ip_target %pI4 and arp_validate is set\n", + bond->dev->name, + &targets[i]); + bond_arp_send(slave, ARPOP_REQUEST, targets[i], + 0, tags); + continue; + } + + /* bond device itself */ + if (rt->dst.dev == bond->dev) + goto found; + + rcu_read_lock(); + tags = bond_verify_device_path(bond->dev, rt->dst.dev, 0); + rcu_read_unlock(); + + if (!IS_ERR_OR_NULL(tags)) + goto found; + + /* Not our device - skip */ + slave_dbg(bond->dev, slave->dev, "no path to arp_ip_target %pI4 via rt.dev %s\n", + &targets[i], rt->dst.dev ? rt->dst.dev->name : "NULL"); + + ip_rt_put(rt); + continue; + +found: + addr = bond_confirm_addr(rt->dst.dev, targets[i], 0); + ip_rt_put(rt); + bond_arp_send(slave, ARPOP_REQUEST, targets[i], addr, tags); + kfree(tags); + } +} + +static void bond_validate_arp(struct bonding *bond, struct slave *slave, __be32 sip, __be32 tip) +{ + int i; + + if (!sip || !bond_has_this_ip(bond, tip)) { + slave_dbg(bond->dev, slave->dev, "%s: sip %pI4 tip %pI4 not found\n", + __func__, &sip, &tip); + return; + } + + i = bond_get_targets_ip(bond->params.arp_targets, sip); + if (i == -1) { + slave_dbg(bond->dev, slave->dev, "%s: sip %pI4 not found in targets\n", + __func__, &sip); + return; + } + WRITE_ONCE(slave->last_rx, jiffies); + WRITE_ONCE(slave->target_last_arp_rx[i], jiffies); +} + +static int bond_arp_rcv(const struct sk_buff *skb, struct bonding *bond, + struct slave *slave) +{ + struct arphdr *arp = (struct arphdr *)skb->data; + struct slave *curr_active_slave, *curr_arp_slave; + unsigned char *arp_ptr; + __be32 sip, tip; + unsigned int alen; + + alen = arp_hdr_len(bond->dev); + + if (alen > skb_headlen(skb)) { + arp = kmalloc(alen, GFP_ATOMIC); + if (!arp) + goto out_unlock; + if (skb_copy_bits(skb, 0, arp, alen) < 0) + goto out_unlock; + } + + if (arp->ar_hln != bond->dev->addr_len || + skb->pkt_type == PACKET_OTHERHOST || + skb->pkt_type == PACKET_LOOPBACK || + arp->ar_hrd != htons(ARPHRD_ETHER) || + arp->ar_pro != htons(ETH_P_IP) || + arp->ar_pln != 4) + goto out_unlock; + + arp_ptr = (unsigned char *)(arp + 1); + arp_ptr += bond->dev->addr_len; + memcpy(&sip, arp_ptr, 4); + arp_ptr += 4 + bond->dev->addr_len; + memcpy(&tip, arp_ptr, 4); + + slave_dbg(bond->dev, slave->dev, "%s: %s/%d av %d sv %d sip %pI4 tip %pI4\n", + __func__, slave->dev->name, bond_slave_state(slave), + bond->params.arp_validate, slave_do_arp_validate(bond, slave), + &sip, &tip); + + curr_active_slave = rcu_dereference(bond->curr_active_slave); + curr_arp_slave = rcu_dereference(bond->current_arp_slave); + + /* We 'trust' the received ARP enough to validate it if: + * + * (a) the slave receiving the ARP is active (which includes the + * current ARP slave, if any), or + * + * (b) the receiving slave isn't active, but there is a currently + * active slave and it received valid arp reply(s) after it became + * the currently active slave, or + * + * (c) there is an ARP slave that sent an ARP during the prior ARP + * interval, and we receive an ARP reply on any slave. We accept + * these because switch FDB update delays may deliver the ARP + * reply to a slave other than the sender of the ARP request. + * + * Note: for (b), backup slaves are receiving the broadcast ARP + * request, not a reply. This request passes from the sending + * slave through the L2 switch(es) to the receiving slave. Since + * this is checking the request, sip/tip are swapped for + * validation. + * + * This is done to avoid endless looping when we can't reach the + * arp_ip_target and fool ourselves with our own arp requests. + */ + if (bond_is_active_slave(slave)) + bond_validate_arp(bond, slave, sip, tip); + else if (curr_active_slave && + time_after(slave_last_rx(bond, curr_active_slave), + curr_active_slave->last_link_up)) + bond_validate_arp(bond, slave, tip, sip); + else if (curr_arp_slave && (arp->ar_op == htons(ARPOP_REPLY)) && + bond_time_in_interval(bond, slave_last_tx(curr_arp_slave), 1)) + bond_validate_arp(bond, slave, sip, tip); + +out_unlock: + if (arp != (struct arphdr *)skb->data) + kfree(arp); + return RX_HANDLER_ANOTHER; +} + +#if IS_ENABLED(CONFIG_IPV6) +static void bond_ns_send(struct slave *slave, const struct in6_addr *daddr, + const struct in6_addr *saddr, struct bond_vlan_tag *tags) +{ + struct net_device *bond_dev = slave->bond->dev; + struct net_device *slave_dev = slave->dev; + struct in6_addr mcaddr; + struct sk_buff *skb; + + slave_dbg(bond_dev, slave_dev, "NS on slave: dst %pI6c src %pI6c\n", + daddr, saddr); + + skb = ndisc_ns_create(slave_dev, daddr, saddr, 0); + if (!skb) { + net_err_ratelimited("NS packet allocation failed\n"); + return; + } + + addrconf_addr_solict_mult(daddr, &mcaddr); + if (bond_handle_vlan(slave, tags, skb)) { + slave_update_last_tx(slave); + ndisc_send_skb(skb, &mcaddr, saddr); + } +} + +static void bond_ns_send_all(struct bonding *bond, struct slave *slave) +{ + struct in6_addr *targets = bond->params.ns_targets; + struct bond_vlan_tag *tags; + struct dst_entry *dst; + struct in6_addr saddr; + struct flowi6 fl6; + int i; + + for (i = 0; i < BOND_MAX_NS_TARGETS && !ipv6_addr_any(&targets[i]); i++) { + slave_dbg(bond->dev, slave->dev, "%s: target %pI6c\n", + __func__, &targets[i]); + tags = NULL; + + /* Find out through which dev should the packet go */ + memset(&fl6, 0, sizeof(struct flowi6)); + fl6.daddr = targets[i]; + + dst = ip6_route_output(dev_net(bond->dev), NULL, &fl6); + if (dst->error) { + dst_release(dst); + /* there's no route to target - try to send arp + * probe to generate any traffic (arp_validate=0) + */ + if (bond->params.arp_validate) + pr_warn_once("%s: no route to ns_ip6_target %pI6c and arp_validate is set\n", + bond->dev->name, + &targets[i]); + bond_ns_send(slave, &targets[i], &in6addr_any, tags); + continue; + } + + /* bond device itself */ + if (dst->dev == bond->dev) + goto found; + + rcu_read_lock(); + tags = bond_verify_device_path(bond->dev, dst->dev, 0); + rcu_read_unlock(); + + if (!IS_ERR_OR_NULL(tags)) + goto found; + + /* Not our device - skip */ + slave_dbg(bond->dev, slave->dev, "no path to ns_ip6_target %pI6c via dst->dev %s\n", + &targets[i], dst->dev ? dst->dev->name : "NULL"); + + dst_release(dst); + continue; + +found: + if (!ipv6_dev_get_saddr(dev_net(dst->dev), dst->dev, &targets[i], 0, &saddr)) + bond_ns_send(slave, &targets[i], &saddr, tags); + else + bond_ns_send(slave, &targets[i], &in6addr_any, tags); + + dst_release(dst); + kfree(tags); + } +} + +static int bond_confirm_addr6(struct net_device *dev, + struct netdev_nested_priv *priv) +{ + struct in6_addr *addr = (struct in6_addr *)priv->data; + + return ipv6_chk_addr(dev_net(dev), addr, dev, 0); +} + +static bool bond_has_this_ip6(struct bonding *bond, struct in6_addr *addr) +{ + struct netdev_nested_priv priv = { + .data = addr, + }; + int ret = false; + + if (bond_confirm_addr6(bond->dev, &priv)) + return true; + + rcu_read_lock(); + if (netdev_walk_all_upper_dev_rcu(bond->dev, bond_confirm_addr6, &priv)) + ret = true; + rcu_read_unlock(); + + return ret; +} + +static void bond_validate_na(struct bonding *bond, struct slave *slave, + struct in6_addr *saddr, struct in6_addr *daddr) +{ + int i; + + /* Ignore NAs that: + * 1. Source address is unspecified address. + * 2. Dest address is neither all-nodes multicast address nor + * exist on bond interface. + */ + if (ipv6_addr_any(saddr) || + (!ipv6_addr_equal(daddr, &in6addr_linklocal_allnodes) && + !bond_has_this_ip6(bond, daddr))) { + slave_dbg(bond->dev, slave->dev, "%s: sip %pI6c tip %pI6c not found\n", + __func__, saddr, daddr); + return; + } + + i = bond_get_targets_ip6(bond->params.ns_targets, saddr); + if (i == -1) { + slave_dbg(bond->dev, slave->dev, "%s: sip %pI6c not found in targets\n", + __func__, saddr); + return; + } + WRITE_ONCE(slave->last_rx, jiffies); + WRITE_ONCE(slave->target_last_arp_rx[i], jiffies); +} + +static int bond_na_rcv(const struct sk_buff *skb, struct bonding *bond, + struct slave *slave) +{ + struct slave *curr_active_slave, *curr_arp_slave; + struct in6_addr *saddr, *daddr; + struct { + struct ipv6hdr ip6; + struct icmp6hdr icmp6; + } *combined, _combined; + + if (skb->pkt_type == PACKET_OTHERHOST || + skb->pkt_type == PACKET_LOOPBACK) + goto out; + + combined = skb_header_pointer(skb, 0, sizeof(_combined), &_combined); + if (!combined || combined->ip6.nexthdr != NEXTHDR_ICMP || + (combined->icmp6.icmp6_type != NDISC_NEIGHBOUR_SOLICITATION && + combined->icmp6.icmp6_type != NDISC_NEIGHBOUR_ADVERTISEMENT)) + goto out; + + saddr = &combined->ip6.saddr; + daddr = &combined->ip6.daddr; + + slave_dbg(bond->dev, slave->dev, "%s: %s/%d av %d sv %d sip %pI6c tip %pI6c\n", + __func__, slave->dev->name, bond_slave_state(slave), + bond->params.arp_validate, slave_do_arp_validate(bond, slave), + saddr, daddr); + + curr_active_slave = rcu_dereference(bond->curr_active_slave); + curr_arp_slave = rcu_dereference(bond->current_arp_slave); + + /* We 'trust' the received ARP enough to validate it if: + * see bond_arp_rcv(). + */ + if (bond_is_active_slave(slave)) + bond_validate_na(bond, slave, saddr, daddr); + else if (curr_active_slave && + time_after(slave_last_rx(bond, curr_active_slave), + curr_active_slave->last_link_up)) + bond_validate_na(bond, slave, daddr, saddr); + else if (curr_arp_slave && + bond_time_in_interval(bond, slave_last_tx(curr_arp_slave), 1)) + bond_validate_na(bond, slave, saddr, daddr); + +out: + return RX_HANDLER_ANOTHER; +} +#endif + +int bond_rcv_validate(const struct sk_buff *skb, struct bonding *bond, + struct slave *slave) +{ +#if IS_ENABLED(CONFIG_IPV6) + bool is_ipv6 = skb->protocol == __cpu_to_be16(ETH_P_IPV6); +#endif + bool is_arp = skb->protocol == __cpu_to_be16(ETH_P_ARP); + + slave_dbg(bond->dev, slave->dev, "%s: skb->dev %s\n", + __func__, skb->dev->name); + + /* Use arp validate logic for both ARP and NS */ + if (!slave_do_arp_validate(bond, slave)) { + if ((slave_do_arp_validate_only(bond) && is_arp) || +#if IS_ENABLED(CONFIG_IPV6) + (slave_do_arp_validate_only(bond) && is_ipv6) || +#endif + !slave_do_arp_validate_only(bond)) + WRITE_ONCE(slave->last_rx, jiffies); + return RX_HANDLER_ANOTHER; + } else if (is_arp) { + return bond_arp_rcv(skb, bond, slave); +#if IS_ENABLED(CONFIG_IPV6) + } else if (is_ipv6 && likely(ipv6_mod_enabled())) { + return bond_na_rcv(skb, bond, slave); +#endif + } else { + return RX_HANDLER_ANOTHER; + } +} + +static void bond_send_validate(struct bonding *bond, struct slave *slave) +{ + bond_arp_send_all(bond, slave); +#if IS_ENABLED(CONFIG_IPV6) + if (likely(ipv6_mod_enabled())) + bond_ns_send_all(bond, slave); +#endif +} + +/* function to verify if we're in the arp_interval timeslice, returns true if + * (last_act - arp_interval) <= jiffies <= (last_act + mod * arp_interval + + * arp_interval/2) . the arp_interval/2 is needed for really fast networks. + */ +static bool bond_time_in_interval(struct bonding *bond, unsigned long last_act, + int mod) +{ + int delta_in_ticks = msecs_to_jiffies(bond->params.arp_interval); + + return time_in_range(jiffies, + last_act - delta_in_ticks, + last_act + mod * delta_in_ticks + delta_in_ticks/2); +} + +/* This function is called regularly to monitor each slave's link + * ensuring that traffic is being sent and received when arp monitoring + * is used in load-balancing mode. if the adapter has been dormant, then an + * arp is transmitted to generate traffic. see activebackup_arp_monitor for + * arp monitoring in active backup mode. + */ +static void bond_loadbalance_arp_mon(struct bonding *bond) +{ + struct slave *slave, *oldcurrent; + struct list_head *iter; + int do_failover = 0, slave_state_changed = 0; + + if (!bond_has_slaves(bond)) + goto re_arm; + + rcu_read_lock(); + + oldcurrent = rcu_dereference(bond->curr_active_slave); + /* see if any of the previous devices are up now (i.e. they have + * xmt and rcv traffic). the curr_active_slave does not come into + * the picture unless it is null. also, slave->last_link_up is not + * needed here because we send an arp on each slave and give a slave + * as long as it needs to get the tx/rx within the delta. + * TODO: what about up/down delay in arp mode? it wasn't here before + * so it can wait + */ + bond_for_each_slave_rcu(bond, slave, iter) { + unsigned long last_tx = slave_last_tx(slave); + + bond_propose_link_state(slave, BOND_LINK_NOCHANGE); + + if (slave->link != BOND_LINK_UP) { + if (bond_time_in_interval(bond, last_tx, 1) && + bond_time_in_interval(bond, READ_ONCE(slave->last_rx), 1)) { + + bond_propose_link_state(slave, BOND_LINK_UP); + slave_state_changed = 1; + + /* primary_slave has no meaning in round-robin + * mode. the window of a slave being up and + * curr_active_slave being null after enslaving + * is closed. + */ + if (!oldcurrent) { + slave_info(bond->dev, slave->dev, "link status definitely up\n"); + do_failover = 1; + } else { + slave_info(bond->dev, slave->dev, "interface is now up\n"); + } + } + } else { + /* slave->link == BOND_LINK_UP */ + + /* not all switches will respond to an arp request + * when the source ip is 0, so don't take the link down + * if we don't know our ip yet + */ + if (!bond_time_in_interval(bond, last_tx, + bond->params.missed_max) || + !bond_time_in_interval(bond, READ_ONCE(slave->last_rx), + bond->params.missed_max)) { + + bond_propose_link_state(slave, BOND_LINK_DOWN); + slave_state_changed = 1; + + if (slave->link_failure_count < UINT_MAX) + slave->link_failure_count++; + + slave_info(bond->dev, slave->dev, "interface is now down\n"); + + if (slave == oldcurrent) + do_failover = 1; + } + } + + /* note: if switch is in round-robin mode, all links + * must tx arp to ensure all links rx an arp - otherwise + * links may oscillate or not come up at all; if switch is + * in something like xor mode, there is nothing we can + * do - all replies will be rx'ed on same link causing slaves + * to be unstable during low/no traffic periods + */ + if (bond_slave_is_up(slave)) + bond_send_validate(bond, slave); + } + + rcu_read_unlock(); + + if (do_failover || slave_state_changed) { + if (!rtnl_trylock()) + goto re_arm; + + bond_for_each_slave(bond, slave, iter) { + if (slave->link_new_state != BOND_LINK_NOCHANGE) + slave->link = slave->link_new_state; + } + + if (slave_state_changed) { + bond_slave_state_change(bond); + if (BOND_MODE(bond) == BOND_MODE_XOR) + bond_update_slave_arr(bond, NULL); + } + if (do_failover) { + block_netpoll_tx(); + bond_select_active_slave(bond); + unblock_netpoll_tx(); + } + rtnl_unlock(); + } + +re_arm: + if (bond->params.arp_interval) + queue_delayed_work(bond->wq, &bond->arp_work, + msecs_to_jiffies(bond->params.arp_interval)); +} + +/* Called to inspect slaves for active-backup mode ARP monitor link state + * changes. Sets proposed link state in slaves to specify what action + * should take place for the slave. Returns 0 if no changes are found, >0 + * if changes to link states must be committed. + * + * Called with rcu_read_lock held. + */ +static int bond_ab_arp_inspect(struct bonding *bond) +{ + unsigned long last_tx, last_rx; + struct list_head *iter; + struct slave *slave; + int commit = 0; + + bond_for_each_slave_rcu(bond, slave, iter) { + bond_propose_link_state(slave, BOND_LINK_NOCHANGE); + last_rx = slave_last_rx(bond, slave); + + if (slave->link != BOND_LINK_UP) { + if (bond_time_in_interval(bond, last_rx, 1)) { + bond_propose_link_state(slave, BOND_LINK_UP); + commit++; + } else if (slave->link == BOND_LINK_BACK) { + bond_propose_link_state(slave, BOND_LINK_FAIL); + commit++; + } + continue; + } + + /* Give slaves 2*delta after being enslaved or made + * active. This avoids bouncing, as the last receive + * times need a full ARP monitor cycle to be updated. + */ + if (bond_time_in_interval(bond, slave->last_link_up, 2)) + continue; + + /* Backup slave is down if: + * - No current_arp_slave AND + * - more than (missed_max+1)*delta since last receive AND + * - the bond has an IP address + * + * Note: a non-null current_arp_slave indicates + * the curr_active_slave went down and we are + * searching for a new one; under this condition + * we only take the curr_active_slave down - this + * gives each slave a chance to tx/rx traffic + * before being taken out + */ + if (!bond_is_active_slave(slave) && + !rcu_access_pointer(bond->current_arp_slave) && + !bond_time_in_interval(bond, last_rx, bond->params.missed_max + 1)) { + bond_propose_link_state(slave, BOND_LINK_DOWN); + commit++; + } + + /* Active slave is down if: + * - more than missed_max*delta since transmitting OR + * - (more than missed_max*delta since receive AND + * the bond has an IP address) + */ + last_tx = slave_last_tx(slave); + if (bond_is_active_slave(slave) && + (!bond_time_in_interval(bond, last_tx, bond->params.missed_max) || + !bond_time_in_interval(bond, last_rx, bond->params.missed_max))) { + bond_propose_link_state(slave, BOND_LINK_DOWN); + commit++; + } + } + + return commit; +} + +/* Called to commit link state changes noted by inspection step of + * active-backup mode ARP monitor. + * + * Called with RTNL hold. + */ +static void bond_ab_arp_commit(struct bonding *bond) +{ + bool do_failover = false; + struct list_head *iter; + unsigned long last_tx; + struct slave *slave; + + bond_for_each_slave(bond, slave, iter) { + switch (slave->link_new_state) { + case BOND_LINK_NOCHANGE: + continue; + + case BOND_LINK_UP: + last_tx = slave_last_tx(slave); + if (rtnl_dereference(bond->curr_active_slave) != slave || + (!rtnl_dereference(bond->curr_active_slave) && + bond_time_in_interval(bond, last_tx, 1))) { + struct slave *current_arp_slave; + + current_arp_slave = rtnl_dereference(bond->current_arp_slave); + bond_set_slave_link_state(slave, BOND_LINK_UP, + BOND_SLAVE_NOTIFY_NOW); + if (current_arp_slave) { + bond_set_slave_inactive_flags( + current_arp_slave, + BOND_SLAVE_NOTIFY_NOW); + RCU_INIT_POINTER(bond->current_arp_slave, NULL); + } + + slave_info(bond->dev, slave->dev, "link status definitely up\n"); + + if (!rtnl_dereference(bond->curr_active_slave) || + slave == rtnl_dereference(bond->primary_slave) || + slave->prio > rtnl_dereference(bond->curr_active_slave)->prio) + do_failover = true; + + } + + continue; + + case BOND_LINK_DOWN: + if (slave->link_failure_count < UINT_MAX) + slave->link_failure_count++; + + bond_set_slave_link_state(slave, BOND_LINK_DOWN, + BOND_SLAVE_NOTIFY_NOW); + bond_set_slave_inactive_flags(slave, + BOND_SLAVE_NOTIFY_NOW); + + slave_info(bond->dev, slave->dev, "link status definitely down, disabling slave\n"); + + if (slave == rtnl_dereference(bond->curr_active_slave)) { + RCU_INIT_POINTER(bond->current_arp_slave, NULL); + do_failover = true; + } + + continue; + + case BOND_LINK_FAIL: + bond_set_slave_link_state(slave, BOND_LINK_FAIL, + BOND_SLAVE_NOTIFY_NOW); + bond_set_slave_inactive_flags(slave, + BOND_SLAVE_NOTIFY_NOW); + + /* A slave has just been enslaved and has become + * the current active slave. + */ + if (rtnl_dereference(bond->curr_active_slave)) + RCU_INIT_POINTER(bond->current_arp_slave, NULL); + continue; + + default: + slave_err(bond->dev, slave->dev, + "impossible: link_new_state %d on slave\n", + slave->link_new_state); + continue; + } + } + + if (do_failover) { + block_netpoll_tx(); + bond_select_active_slave(bond); + unblock_netpoll_tx(); + } + + bond_set_carrier(bond); +} + +/* Send ARP probes for active-backup mode ARP monitor. + * + * Called with rcu_read_lock held. + */ +static bool bond_ab_arp_probe(struct bonding *bond) +{ + struct slave *slave, *before = NULL, *new_slave = NULL, + *curr_arp_slave = rcu_dereference(bond->current_arp_slave), + *curr_active_slave = rcu_dereference(bond->curr_active_slave); + struct list_head *iter; + bool found = false; + bool should_notify_rtnl = BOND_SLAVE_NOTIFY_LATER; + + if (curr_arp_slave && curr_active_slave) + netdev_info(bond->dev, "PROBE: c_arp %s && cas %s BAD\n", + curr_arp_slave->dev->name, + curr_active_slave->dev->name); + + if (curr_active_slave) { + bond_send_validate(bond, curr_active_slave); + return should_notify_rtnl; + } + + /* if we don't have a curr_active_slave, search for the next available + * backup slave from the current_arp_slave and make it the candidate + * for becoming the curr_active_slave + */ + + if (!curr_arp_slave) { + curr_arp_slave = bond_first_slave_rcu(bond); + if (!curr_arp_slave) + return should_notify_rtnl; + } + + bond_for_each_slave_rcu(bond, slave, iter) { + if (!found && !before && bond_slave_is_up(slave)) + before = slave; + + if (found && !new_slave && bond_slave_is_up(slave)) + new_slave = slave; + /* if the link state is up at this point, we + * mark it down - this can happen if we have + * simultaneous link failures and + * reselect_active_interface doesn't make this + * one the current slave so it is still marked + * up when it is actually down + */ + if (!bond_slave_is_up(slave) && slave->link == BOND_LINK_UP) { + bond_set_slave_link_state(slave, BOND_LINK_DOWN, + BOND_SLAVE_NOTIFY_LATER); + if (slave->link_failure_count < UINT_MAX) + slave->link_failure_count++; + + bond_set_slave_inactive_flags(slave, + BOND_SLAVE_NOTIFY_LATER); + + slave_info(bond->dev, slave->dev, "backup interface is now down\n"); + } + if (slave == curr_arp_slave) + found = true; + } + + if (!new_slave && before) + new_slave = before; + + if (!new_slave) + goto check_state; + + bond_set_slave_link_state(new_slave, BOND_LINK_BACK, + BOND_SLAVE_NOTIFY_LATER); + bond_set_slave_active_flags(new_slave, BOND_SLAVE_NOTIFY_LATER); + bond_send_validate(bond, new_slave); + new_slave->last_link_up = jiffies; + rcu_assign_pointer(bond->current_arp_slave, new_slave); + +check_state: + bond_for_each_slave_rcu(bond, slave, iter) { + if (slave->should_notify || slave->should_notify_link) { + should_notify_rtnl = BOND_SLAVE_NOTIFY_NOW; + break; + } + } + return should_notify_rtnl; +} + +static void bond_activebackup_arp_mon(struct bonding *bond) +{ + bool should_notify_rtnl; + int delta_in_ticks; + + delta_in_ticks = msecs_to_jiffies(bond->params.arp_interval); + + if (!bond_has_slaves(bond)) + goto re_arm; + + rcu_read_lock(); + + if (bond_ab_arp_inspect(bond)) { + rcu_read_unlock(); + + /* Race avoidance with bond_close flush of workqueue */ + if (!rtnl_trylock()) { + delta_in_ticks = 1; + goto re_arm; + } + + bond_ab_arp_commit(bond); + + rtnl_unlock(); + rcu_read_lock(); + } + + should_notify_rtnl = bond_ab_arp_probe(bond); + rcu_read_unlock(); + + if (READ_ONCE(bond->send_peer_notif) || should_notify_rtnl) { + if (!rtnl_trylock()) { + delta_in_ticks = 1; + goto re_arm; + } + + if (bond->send_peer_notif) + bond_peer_notify_may_events(bond, true); + + if (should_notify_rtnl) { + bond_slave_state_notify(bond); + bond_slave_link_notify(bond); + } + + rtnl_unlock(); + } + +re_arm: + if (bond->params.arp_interval) + queue_delayed_work(bond->wq, &bond->arp_work, delta_in_ticks); +} + +static void bond_arp_monitor(struct work_struct *work) +{ + struct bonding *bond = container_of(work, struct bonding, + arp_work.work); + + if (BOND_MODE(bond) == BOND_MODE_ACTIVEBACKUP) + bond_activebackup_arp_mon(bond); + else + bond_loadbalance_arp_mon(bond); +} + +/*-------------------------- netdev event handling --------------------------*/ + +/* Change device name */ +static int bond_event_changename(struct bonding *bond) +{ + bond_remove_proc_entry(bond); + bond_create_proc_entry(bond); + + bond_debug_reregister(bond); + + return NOTIFY_DONE; +} + +static int bond_master_netdev_event(unsigned long event, + struct net_device *bond_dev) +{ + struct bonding *event_bond = netdev_priv(bond_dev); + + netdev_dbg(bond_dev, "%s called\n", __func__); + + switch (event) { + case NETDEV_CHANGENAME: + return bond_event_changename(event_bond); + case NETDEV_UNREGISTER: + bond_remove_proc_entry(event_bond); +#ifdef CONFIG_XFRM_OFFLOAD + xfrm_dev_state_flush(dev_net(bond_dev), bond_dev, true); +#endif /* CONFIG_XFRM_OFFLOAD */ + break; + case NETDEV_REGISTER: + bond_create_proc_entry(event_bond); + break; + default: + break; + } + + return NOTIFY_DONE; +} + +static int bond_slave_netdev_event(unsigned long event, + struct net_device *slave_dev) +{ + struct slave *slave = bond_slave_get_rtnl(slave_dev), *primary; + struct bonding *bond; + struct net_device *bond_dev; + + /* A netdev event can be generated while enslaving a device + * before netdev_rx_handler_register is called in which case + * slave will be NULL + */ + if (!slave) { + netdev_dbg(slave_dev, "%s called on NULL slave\n", __func__); + return NOTIFY_DONE; + } + + bond_dev = slave->bond->dev; + bond = slave->bond; + primary = rtnl_dereference(bond->primary_slave); + + slave_dbg(bond_dev, slave_dev, "%s called\n", __func__); + + switch (event) { + case NETDEV_UNREGISTER: + if (bond_dev->type != ARPHRD_ETHER) + bond_release_and_destroy(bond_dev, slave_dev); + else + __bond_release_one(bond_dev, slave_dev, false, true); + break; + case NETDEV_UP: + case NETDEV_CHANGE: + /* For 802.3ad mode only: + * Getting invalid Speed/Duplex values here will put slave + * in weird state. Mark it as link-fail if the link was + * previously up or link-down if it hasn't yet come up, and + * let link-monitoring (miimon) set it right when correct + * speeds/duplex are available. + */ + if (bond_update_speed_duplex(slave) && + BOND_MODE(bond) == BOND_MODE_8023AD) { + if (slave->last_link_up) + slave->link = BOND_LINK_FAIL; + else + slave->link = BOND_LINK_DOWN; + } + + if (BOND_MODE(bond) == BOND_MODE_8023AD) + bond_3ad_adapter_speed_duplex_changed(slave); + fallthrough; + case NETDEV_DOWN: + /* Refresh slave-array if applicable! + * If the setup does not use miimon or arpmon (mode-specific!), + * then these events will not cause the slave-array to be + * refreshed. This will cause xmit to use a slave that is not + * usable. Avoid such situation by refeshing the array at these + * events. If these (miimon/arpmon) parameters are configured + * then array gets refreshed twice and that should be fine! + */ + if (bond_mode_can_use_xmit_hash(bond)) + bond_update_slave_arr(bond, NULL); + break; + case NETDEV_CHANGEMTU: + /* TODO: Should slaves be allowed to + * independently alter their MTU? For + * an active-backup bond, slaves need + * not be the same type of device, so + * MTUs may vary. For other modes, + * slaves arguably should have the + * same MTUs. To do this, we'd need to + * take over the slave's change_mtu + * function for the duration of their + * servitude. + */ + break; + case NETDEV_CHANGENAME: + /* we don't care if we don't have primary set */ + if (!bond_uses_primary(bond) || + !bond->params.primary[0]) + break; + + if (slave == primary) { + /* slave's name changed - he's no longer primary */ + RCU_INIT_POINTER(bond->primary_slave, NULL); + } else if (!strcmp(slave_dev->name, bond->params.primary)) { + /* we have a new primary slave */ + rcu_assign_pointer(bond->primary_slave, slave); + } else { /* we didn't change primary - exit */ + break; + } + + netdev_info(bond->dev, "Primary slave changed to %s, reselecting active slave\n", + primary ? slave_dev->name : "none"); + + block_netpoll_tx(); + bond_select_active_slave(bond); + unblock_netpoll_tx(); + break; + case NETDEV_FEAT_CHANGE: + if (!bond->notifier_ctx) { + bond->notifier_ctx = true; + netdev_compute_master_upper_features(bond->dev, true); + bond->notifier_ctx = false; + } + break; + case NETDEV_RESEND_IGMP: + /* Propagate to master device */ + call_netdevice_notifiers(event, slave->bond->dev); + break; + case NETDEV_XDP_FEAT_CHANGE: + bond_xdp_set_features(bond_dev); + break; + default: + break; + } + + return NOTIFY_DONE; +} + +/* bond_netdev_event: handle netdev notifier chain events. + * + * This function receives events for the netdev chain. The caller (an + * ioctl handler calling blocking_notifier_call_chain) holds the necessary + * locks for us to safely manipulate the slave devices (RTNL lock, + * dev_probe_lock). + */ +static int bond_netdev_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct net_device *event_dev = netdev_notifier_info_to_dev(ptr); + + netdev_dbg(event_dev, "%s received %s\n", + __func__, netdev_cmd_to_name(event)); + + if (!(event_dev->priv_flags & IFF_BONDING)) + return NOTIFY_DONE; + + if (event_dev->flags & IFF_MASTER) { + int ret; + + ret = bond_master_netdev_event(event, event_dev); + if (ret != NOTIFY_DONE) + return ret; + } + + if (event_dev->flags & IFF_SLAVE) + return bond_slave_netdev_event(event, event_dev); + + return NOTIFY_DONE; +} + +static struct notifier_block bond_netdev_notifier = { + .notifier_call = bond_netdev_event, +}; + +/*---------------------------- Hashing Policies -----------------------------*/ + +/* Helper to access data in a packet, with or without a backing skb. + * If skb is given the data is linearized if necessary via pskb_may_pull. + */ +static inline const void *bond_pull_data(struct sk_buff *skb, + const void *data, int hlen, int n) +{ + if (likely(n <= hlen)) + return data; + else if (skb && likely(pskb_may_pull(skb, n))) + return skb->data; + + return NULL; +} + +/* L2 hash helper */ +static inline u32 bond_eth_hash(struct sk_buff *skb, const void *data, int mhoff, int hlen) +{ + struct ethhdr *ep; + + data = bond_pull_data(skb, data, hlen, mhoff + sizeof(struct ethhdr)); + if (!data) + return 0; + + ep = (struct ethhdr *)(data + mhoff); + return ep->h_dest[5] ^ ep->h_source[5] ^ be16_to_cpu(ep->h_proto); +} + +static bool bond_flow_ip(struct sk_buff *skb, struct flow_keys *fk, const void *data, + int hlen, __be16 l2_proto, int *nhoff, int *ip_proto, bool l34) +{ + const struct ipv6hdr *iph6; + const struct iphdr *iph; + + if (l2_proto == htons(ETH_P_IP)) { + data = bond_pull_data(skb, data, hlen, *nhoff + sizeof(*iph)); + if (!data) + return false; + + iph = (const struct iphdr *)(data + *nhoff); + iph_to_flow_copy_v4addrs(fk, iph); + *nhoff += iph->ihl << 2; + if (!ip_is_fragment(iph)) + *ip_proto = iph->protocol; + } else if (l2_proto == htons(ETH_P_IPV6)) { + data = bond_pull_data(skb, data, hlen, *nhoff + sizeof(*iph6)); + if (!data) + return false; + + iph6 = (const struct ipv6hdr *)(data + *nhoff); + iph_to_flow_copy_v6addrs(fk, iph6); + *nhoff += sizeof(*iph6); + *ip_proto = iph6->nexthdr; + } else { + return false; + } + + if (l34 && *ip_proto >= 0) + fk->ports.ports = skb_flow_get_ports(skb, *nhoff, *ip_proto, data, hlen); + + return true; +} + +static u32 bond_vlan_srcmac_hash(struct sk_buff *skb, const void *data, int mhoff, int hlen) +{ + u32 srcmac_vendor = 0, srcmac_dev = 0; + struct ethhdr *mac_hdr; + u16 vlan = 0; + int i; + + data = bond_pull_data(skb, data, hlen, mhoff + sizeof(struct ethhdr)); + if (!data) + return 0; + mac_hdr = (struct ethhdr *)(data + mhoff); + + for (i = 0; i < 3; i++) + srcmac_vendor = (srcmac_vendor << 8) | mac_hdr->h_source[i]; + + for (i = 3; i < ETH_ALEN; i++) + srcmac_dev = (srcmac_dev << 8) | mac_hdr->h_source[i]; + + if (skb && skb_vlan_tag_present(skb)) + vlan = skb_vlan_tag_get(skb); + + return vlan ^ srcmac_vendor ^ srcmac_dev; +} + +/* Extract the appropriate headers based on bond's xmit policy */ +static bool bond_flow_dissect(struct bonding *bond, struct sk_buff *skb, const void *data, + __be16 l2_proto, int nhoff, int hlen, struct flow_keys *fk) +{ + bool l34 = bond->params.xmit_policy == BOND_XMIT_POLICY_LAYER34; + int ip_proto = -1; + + switch (bond->params.xmit_policy) { + case BOND_XMIT_POLICY_ENCAP23: + case BOND_XMIT_POLICY_ENCAP34: + memset(fk, 0, sizeof(*fk)); + return __skb_flow_dissect(dev_net(bond->dev), skb, + &flow_keys_bonding, fk, data, + l2_proto, nhoff, hlen, 0); + default: + break; + } + + fk->ports.ports = 0; + memset(&fk->icmp, 0, sizeof(fk->icmp)); + if (!bond_flow_ip(skb, fk, data, hlen, l2_proto, &nhoff, &ip_proto, l34)) + return false; + + /* ICMP error packets contains at least 8 bytes of the header + * of the packet which generated the error. Use this information + * to correlate ICMP error packets within the same flow which + * generated the error. + */ + if (ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6) { + skb_flow_get_icmp_tci(skb, &fk->icmp, data, nhoff, hlen); + if (ip_proto == IPPROTO_ICMP) { + if (!icmp_is_err(fk->icmp.type)) + return true; + + nhoff += sizeof(struct icmphdr); + } else if (ip_proto == IPPROTO_ICMPV6) { + if (!icmpv6_is_err(fk->icmp.type)) + return true; + + nhoff += sizeof(struct icmp6hdr); + } + return bond_flow_ip(skb, fk, data, hlen, l2_proto, &nhoff, &ip_proto, l34); + } + + return true; +} + +static u32 bond_ip_hash(u32 hash, struct flow_keys *flow, int xmit_policy) +{ + hash ^= (__force u32)flow_get_u32_dst(flow) ^ + (__force u32)flow_get_u32_src(flow); + hash ^= (hash >> 16); + hash ^= (hash >> 8); + + /* discard lowest hash bit to deal with the common even ports pattern */ + if (xmit_policy == BOND_XMIT_POLICY_LAYER34 || + xmit_policy == BOND_XMIT_POLICY_ENCAP34) + return hash >> 1; + + return hash; +} + +/* Generate hash based on xmit policy. If @skb is given it is used to linearize + * the data as required, but this function can be used without it if the data is + * known to be linear (e.g. with xdp_buff). + */ +static u32 __bond_xmit_hash(struct bonding *bond, struct sk_buff *skb, const void *data, + __be16 l2_proto, int mhoff, int nhoff, int hlen) +{ + struct flow_keys flow; + u32 hash; + + if (bond->params.xmit_policy == BOND_XMIT_POLICY_VLAN_SRCMAC) + return bond_vlan_srcmac_hash(skb, data, mhoff, hlen); + + if (bond->params.xmit_policy == BOND_XMIT_POLICY_LAYER2 || + !bond_flow_dissect(bond, skb, data, l2_proto, nhoff, hlen, &flow)) + return bond_eth_hash(skb, data, mhoff, hlen); + + if (bond->params.xmit_policy == BOND_XMIT_POLICY_LAYER23 || + bond->params.xmit_policy == BOND_XMIT_POLICY_ENCAP23) { + hash = bond_eth_hash(skb, data, mhoff, hlen); + } else { + if (flow.icmp.id) + memcpy(&hash, &flow.icmp, sizeof(hash)); + else + memcpy(&hash, &flow.ports.ports, sizeof(hash)); + } + + return bond_ip_hash(hash, &flow, bond->params.xmit_policy); +} + +/** + * bond_xmit_hash - generate a hash value based on the xmit policy + * @bond: bonding device + * @skb: buffer to use for headers + * + * This function will extract the necessary headers from the skb buffer and use + * them to generate a hash based on the xmit_policy set in the bonding device + */ +u32 bond_xmit_hash(struct bonding *bond, struct sk_buff *skb) +{ + if (bond->params.xmit_policy == BOND_XMIT_POLICY_ENCAP34 && + skb->l4_hash) + return skb->hash; + + return __bond_xmit_hash(bond, skb, skb->data, skb->protocol, + 0, skb_network_offset(skb), + skb_headlen(skb)); +} + +/** + * bond_xmit_hash_xdp - generate a hash value based on the xmit policy + * @bond: bonding device + * @xdp: buffer to use for headers + * + * The XDP variant of bond_xmit_hash. + */ +static u32 bond_xmit_hash_xdp(struct bonding *bond, struct xdp_buff *xdp) +{ + struct ethhdr *eth; + + if (xdp->data + sizeof(struct ethhdr) > xdp->data_end) + return 0; + + eth = (struct ethhdr *)xdp->data; + + return __bond_xmit_hash(bond, NULL, xdp->data, eth->h_proto, 0, + sizeof(struct ethhdr), xdp->data_end - xdp->data); +} + +/*-------------------------- Device entry points ----------------------------*/ + +void bond_work_init_all(struct bonding *bond) +{ + /* ndo_stop, bond_close() will try to flush the work under + * the rtnl lock. The workqueue must not block on rtnl lock + * to avoid deadlock. + */ + INIT_DELAYED_WORK(&bond->mcast_work, + bond_resend_igmp_join_requests_delayed); + INIT_DELAYED_WORK(&bond->alb_work, bond_alb_monitor); + INIT_DELAYED_WORK(&bond->mii_work, bond_mii_monitor); + INIT_DELAYED_WORK(&bond->arp_work, bond_arp_monitor); + INIT_DELAYED_WORK(&bond->ad_work, bond_3ad_state_machine_handler); + INIT_DELAYED_WORK(&bond->slave_arr_work, bond_slave_arr_handler); + INIT_DELAYED_WORK(&bond->peer_notify_work, bond_peer_notify_handler); +} + +void bond_work_cancel_all(struct bonding *bond) +{ + cancel_delayed_work_sync(&bond->mii_work); + cancel_delayed_work_sync(&bond->arp_work); + cancel_delayed_work_sync(&bond->alb_work); + cancel_delayed_work_sync(&bond->ad_work); + cancel_delayed_work_sync(&bond->mcast_work); + cancel_delayed_work_sync(&bond->slave_arr_work); + cancel_delayed_work_sync(&bond->peer_notify_work); +} + +static int bond_open(struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct list_head *iter; + struct slave *slave; + + if (BOND_MODE(bond) == BOND_MODE_ROUNDROBIN && !bond->rr_tx_counter) { + bond->rr_tx_counter = alloc_percpu(u32); + if (!bond->rr_tx_counter) + return -ENOMEM; + } + + /* reset slave->backup and slave->inactive */ + if (bond_has_slaves(bond)) { + bond_for_each_slave(bond, slave, iter) { + if (bond_uses_primary(bond) && + slave != rcu_access_pointer(bond->curr_active_slave)) { + bond_set_slave_inactive_flags(slave, + BOND_SLAVE_NOTIFY_NOW); + } else if (BOND_MODE(bond) != BOND_MODE_8023AD) { + bond_set_slave_active_flags(slave, + BOND_SLAVE_NOTIFY_NOW); + } + } + } + + if (bond_is_lb(bond)) { + /* bond_alb_initialize must be called before the timer + * is started. + */ + if (bond_alb_initialize(bond, (BOND_MODE(bond) == BOND_MODE_ALB))) + return -ENOMEM; + if (bond->params.tlb_dynamic_lb || BOND_MODE(bond) == BOND_MODE_ALB) + queue_delayed_work(bond->wq, &bond->alb_work, 0); + } + + if (bond->params.miimon) /* link check interval, in milliseconds. */ + queue_delayed_work(bond->wq, &bond->mii_work, 0); + + if (bond->params.arp_interval) { /* arp interval, in milliseconds. */ + queue_delayed_work(bond->wq, &bond->arp_work, 0); + bond->recv_probe = bond_rcv_validate; + } + + if (BOND_MODE(bond) == BOND_MODE_8023AD) { + queue_delayed_work(bond->wq, &bond->ad_work, 0); + /* register to receive LACPDUs */ + bond->recv_probe = bond_3ad_lacpdu_recv; + bond_3ad_initiate_agg_selection(bond, 1); + + bond_for_each_slave(bond, slave, iter) + dev_mc_add(slave->dev, lacpdu_mcast_addr); + + if (bond->params.broadcast_neighbor) + static_branch_inc(&bond_bcast_neigh_enabled); + } + + if (bond_mode_can_use_xmit_hash(bond)) + bond_update_slave_arr(bond, NULL); + + return 0; +} + +static int bond_close(struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct slave *slave; + + bond_work_cancel_all(bond); + bond->send_peer_notif = 0; + WRITE_ONCE(bond->recv_probe, NULL); + + /* Wait for any in-flight RX handlers */ + synchronize_net(); + + if (bond_is_lb(bond)) + bond_alb_deinitialize(bond); + + if (BOND_MODE(bond) == BOND_MODE_8023AD && + bond->params.broadcast_neighbor) + static_branch_dec(&bond_bcast_neigh_enabled); + + if (bond_uses_primary(bond)) { + rcu_read_lock(); + slave = rcu_dereference(bond->curr_active_slave); + if (slave) + bond_hw_addr_flush(bond_dev, slave->dev); + rcu_read_unlock(); + } else { + struct list_head *iter; + + bond_for_each_slave(bond, slave, iter) + bond_hw_addr_flush(bond_dev, slave->dev); + } + + return 0; +} + +/* fold stats, assuming all rtnl_link_stats64 fields are u64, but + * that some drivers can provide 32bit values only. + */ +static void bond_fold_stats(struct rtnl_link_stats64 *_res, + const struct rtnl_link_stats64 *_new, + const struct rtnl_link_stats64 *_old) +{ + const u64 *new = (const u64 *)_new; + const u64 *old = (const u64 *)_old; + u64 *res = (u64 *)_res; + int i; + + for (i = 0; i < sizeof(*_res) / sizeof(u64); i++) { + u64 nv = new[i]; + u64 ov = old[i]; + s64 delta = nv - ov; + + /* detects if this particular field is 32bit only */ + if (((nv | ov) >> 32) == 0) + delta = (s64)(s32)((u32)nv - (u32)ov); + + /* filter anomalies, some drivers reset their stats + * at down/up events. + */ + if (delta > 0) + res[i] += delta; + } +} + +#ifdef CONFIG_LOCKDEP +static int bond_get_lowest_level_rcu(struct net_device *dev) +{ + struct net_device *ldev, *next, *now, *dev_stack[MAX_NEST_DEV + 1]; + struct list_head *niter, *iter, *iter_stack[MAX_NEST_DEV + 1]; + int cur = 0, max = 0; + + now = dev; + iter = &dev->adj_list.lower; + + while (1) { + next = NULL; + while (1) { + ldev = netdev_next_lower_dev_rcu(now, &iter); + if (!ldev) + break; + + next = ldev; + niter = &ldev->adj_list.lower; + dev_stack[cur] = now; + iter_stack[cur++] = iter; + if (max <= cur) + max = cur; + break; + } + + if (!next) { + if (!cur) + return max; + next = dev_stack[--cur]; + niter = iter_stack[cur]; + } + + now = next; + iter = niter; + } + + return max; +} +#endif + +static void bond_get_stats(struct net_device *bond_dev, + struct rtnl_link_stats64 *stats) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct rtnl_link_stats64 temp; + struct list_head *iter; + struct slave *slave; + int nest_level = 0; + + + rcu_read_lock(); +#ifdef CONFIG_LOCKDEP + nest_level = bond_get_lowest_level_rcu(bond_dev); +#endif + + spin_lock_nested(&bond->stats_lock, nest_level); + memcpy(stats, &bond->bond_stats, sizeof(*stats)); + + bond_for_each_slave_rcu(bond, slave, iter) { + const struct rtnl_link_stats64 *new = + dev_get_stats(slave->dev, &temp); + + bond_fold_stats(stats, new, &slave->slave_stats); + + /* save off the slave stats for the next run */ + memcpy(&slave->slave_stats, new, sizeof(*new)); + } + + memcpy(&bond->bond_stats, stats, sizeof(*stats)); + spin_unlock(&bond->stats_lock); + rcu_read_unlock(); +} + +static int bond_eth_ioctl(struct net_device *bond_dev, struct ifreq *ifr, int cmd) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct mii_ioctl_data *mii = NULL; + + netdev_dbg(bond_dev, "bond_eth_ioctl: cmd=%d\n", cmd); + + switch (cmd) { + case SIOCGMIIPHY: + mii = if_mii(ifr); + if (!mii) + return -EINVAL; + + mii->phy_id = 0; + fallthrough; + case SIOCGMIIREG: + /* We do this again just in case we were called by SIOCGMIIREG + * instead of SIOCGMIIPHY. + */ + mii = if_mii(ifr); + if (!mii) + return -EINVAL; + + if (mii->reg_num == 1) { + mii->val_out = 0; + if (netif_carrier_ok(bond->dev)) + mii->val_out = BMSR_LSTATUS; + } + + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int bond_do_ioctl(struct net_device *bond_dev, struct ifreq *ifr, int cmd) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct net_device *slave_dev = NULL; + struct ifbond k_binfo; + struct ifbond __user *u_binfo = NULL; + struct ifslave k_sinfo; + struct ifslave __user *u_sinfo = NULL; + struct bond_opt_value newval; + struct net *net; + int res = 0; + + netdev_dbg(bond_dev, "bond_ioctl: cmd=%d\n", cmd); + + switch (cmd) { + case SIOCBONDINFOQUERY: + u_binfo = (struct ifbond __user *)ifr->ifr_data; + + if (copy_from_user(&k_binfo, u_binfo, sizeof(ifbond))) + return -EFAULT; + + bond_info_query(bond_dev, &k_binfo); + if (copy_to_user(u_binfo, &k_binfo, sizeof(ifbond))) + return -EFAULT; + + return 0; + case SIOCBONDSLAVEINFOQUERY: + u_sinfo = (struct ifslave __user *)ifr->ifr_data; + + if (copy_from_user(&k_sinfo, u_sinfo, sizeof(ifslave))) + return -EFAULT; + + res = bond_slave_info_query(bond_dev, &k_sinfo); + if (res == 0 && + copy_to_user(u_sinfo, &k_sinfo, sizeof(ifslave))) + return -EFAULT; + + return res; + default: + break; + } + + net = dev_net(bond_dev); + + if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) + return -EPERM; + + slave_dev = __dev_get_by_name(net, ifr->ifr_slave); + + if (!slave_dev) + return -ENODEV; + + slave_dbg(bond_dev, slave_dev, "slave_dev=%p:\n", slave_dev); + + switch (cmd) { + case SIOCBONDENSLAVE: + res = bond_enslave(bond_dev, slave_dev, NULL); + break; + case SIOCBONDRELEASE: + res = bond_release(bond_dev, slave_dev); + break; + case SIOCBONDSETHWADDR: + res = bond_set_dev_addr(bond_dev, slave_dev); + break; + case SIOCBONDCHANGEACTIVE: + bond_opt_initstr(&newval, slave_dev->name); + res = __bond_opt_set_notify(bond, BOND_OPT_ACTIVE_SLAVE, + &newval); + break; + default: + res = -EOPNOTSUPP; + } + + return res; +} + +static int bond_siocdevprivate(struct net_device *bond_dev, struct ifreq *ifr, + void __user *data, int cmd) +{ + struct ifreq ifrdata = { .ifr_data = data }; + + switch (cmd) { + case BOND_INFO_QUERY_OLD: + return bond_do_ioctl(bond_dev, &ifrdata, SIOCBONDINFOQUERY); + case BOND_SLAVE_INFO_QUERY_OLD: + return bond_do_ioctl(bond_dev, &ifrdata, SIOCBONDSLAVEINFOQUERY); + case BOND_ENSLAVE_OLD: + return bond_do_ioctl(bond_dev, ifr, SIOCBONDENSLAVE); + case BOND_RELEASE_OLD: + return bond_do_ioctl(bond_dev, ifr, SIOCBONDRELEASE); + case BOND_SETHWADDR_OLD: + return bond_do_ioctl(bond_dev, ifr, SIOCBONDSETHWADDR); + case BOND_CHANGE_ACTIVE_OLD: + return bond_do_ioctl(bond_dev, ifr, SIOCBONDCHANGEACTIVE); + } + + return -EOPNOTSUPP; +} + +static void bond_change_rx_flags(struct net_device *bond_dev, int change) +{ + struct bonding *bond = netdev_priv(bond_dev); + + if (change & IFF_PROMISC) + bond_set_promiscuity(bond, + bond_dev->flags & IFF_PROMISC ? 1 : -1); + + if (change & IFF_ALLMULTI) + bond_set_allmulti(bond, + bond_dev->flags & IFF_ALLMULTI ? 1 : -1); +} + +static void bond_set_rx_mode(struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct list_head *iter; + struct slave *slave; + + rcu_read_lock(); + if (bond_uses_primary(bond)) { + slave = rcu_dereference(bond->curr_active_slave); + if (slave) { + dev_uc_sync(slave->dev, bond_dev); + dev_mc_sync(slave->dev, bond_dev); + } + } else { + bond_for_each_slave_rcu(bond, slave, iter) { + dev_uc_sync_multiple(slave->dev, bond_dev); + dev_mc_sync_multiple(slave->dev, bond_dev); + } + } + rcu_read_unlock(); +} + +static int bond_neigh_init(struct neighbour *n) +{ + struct bonding *bond = netdev_priv(n->dev); + const struct net_device_ops *slave_ops; + struct neigh_parms parms; + struct slave *slave; + int ret = 0; + + rcu_read_lock(); + slave = bond_first_slave_rcu(bond); + if (!slave) + goto out; + slave_ops = slave->dev->netdev_ops; + if (!slave_ops->ndo_neigh_setup) + goto out; + + /* TODO: find another way [1] to implement this. + * Passing a zeroed structure is fragile, + * but at least we do not pass garbage. + * + * [1] One way would be that ndo_neigh_setup() never touch + * struct neigh_parms, but propagate the new neigh_setup() + * back to ___neigh_create() / neigh_parms_alloc() + */ + memset(&parms, 0, sizeof(parms)); + ret = slave_ops->ndo_neigh_setup(slave->dev, &parms); + + if (ret) + goto out; + + if (parms.neigh_setup) + ret = parms.neigh_setup(n); +out: + rcu_read_unlock(); + return ret; +} + +/* The bonding ndo_neigh_setup is called at init time beofre any + * slave exists. So we must declare proxy setup function which will + * be used at run time to resolve the actual slave neigh param setup. + * + * It's also called by master devices (such as vlans) to setup their + * underlying devices. In that case - do nothing, we're already set up from + * our init. + */ +static int bond_neigh_setup(struct net_device *dev, + struct neigh_parms *parms) +{ + /* modify only our neigh_parms */ + if (parms->dev == dev) + parms->neigh_setup = bond_neigh_init; + + return 0; +} + +/* Change the MTU of all of a master's slaves to match the master */ +static int bond_change_mtu(struct net_device *bond_dev, int new_mtu) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct slave *slave, *rollback_slave; + struct list_head *iter; + int res = 0; + + netdev_dbg(bond_dev, "bond=%p, new_mtu=%d\n", bond, new_mtu); + + bond_for_each_slave(bond, slave, iter) { + slave_dbg(bond_dev, slave->dev, "s %p c_m %p\n", + slave, slave->dev->netdev_ops->ndo_change_mtu); + + res = dev_set_mtu(slave->dev, new_mtu); + + if (res) { + /* If we failed to set the slave's mtu to the new value + * we must abort the operation even in ACTIVE_BACKUP + * mode, because if we allow the backup slaves to have + * different mtu values than the active slave we'll + * need to change their mtu when doing a failover. That + * means changing their mtu from timer context, which + * is probably not a good idea. + */ + slave_dbg(bond_dev, slave->dev, "err %d setting mtu to %d\n", + res, new_mtu); + goto unwind; + } + } + + WRITE_ONCE(bond_dev->mtu, new_mtu); + + return 0; + +unwind: + /* unwind from head to the slave that failed */ + bond_for_each_slave(bond, rollback_slave, iter) { + int tmp_res; + + if (rollback_slave == slave) + break; + + tmp_res = dev_set_mtu(rollback_slave->dev, bond_dev->mtu); + if (tmp_res) + slave_dbg(bond_dev, rollback_slave->dev, "unwind err %d\n", + tmp_res); + } + + return res; +} + +/* Change HW address + * + * Note that many devices must be down to change the HW address, and + * downing the master releases all slaves. We can make bonds full of + * bonding devices to test this, however. + */ +static int bond_set_mac_address(struct net_device *bond_dev, void *addr) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct slave *slave, *rollback_slave; + struct sockaddr_storage *ss = addr, tmp_ss; + struct list_head *iter; + int res = 0; + + if (BOND_MODE(bond) == BOND_MODE_ALB) + return bond_alb_set_mac_address(bond_dev, addr); + + + netdev_dbg(bond_dev, "%s: bond=%p\n", __func__, bond); + + /* If fail_over_mac is enabled, do nothing and return success. + * Returning an error causes ifenslave to fail. + */ + if (bond->params.fail_over_mac && + BOND_MODE(bond) == BOND_MODE_ACTIVEBACKUP) + return 0; + + if (!is_valid_ether_addr(ss->__data)) + return -EADDRNOTAVAIL; + + bond_for_each_slave(bond, slave, iter) { + slave_dbg(bond_dev, slave->dev, "%s: slave=%p\n", + __func__, slave); + res = dev_set_mac_address(slave->dev, addr, NULL); + if (res) { + slave_dbg(bond_dev, slave->dev, "%s: err %d\n", + __func__, res); + goto unwind; + } + } + + /* success */ + dev_addr_set(bond_dev, ss->__data); + return 0; + +unwind: + memcpy(tmp_ss.__data, bond_dev->dev_addr, bond_dev->addr_len); + tmp_ss.ss_family = bond_dev->type; + + /* unwind from head to the slave that failed */ + bond_for_each_slave(bond, rollback_slave, iter) { + int tmp_res; + + if (rollback_slave == slave) + break; + + tmp_res = dev_set_mac_address(rollback_slave->dev, &tmp_ss, NULL); + if (tmp_res) { + slave_dbg(bond_dev, rollback_slave->dev, "%s: unwind err %d\n", + __func__, tmp_res); + } + } + + return res; +} + +/** + * bond_get_slave_by_id - get xmit slave with slave_id + * @bond: bonding device that is transmitting + * @slave_id: slave id up to slave_cnt-1 through which to transmit + * + * This function tries to get slave with slave_id but in case + * it fails, it tries to find the first available slave for transmission. + */ +static struct slave *bond_get_slave_by_id(struct bonding *bond, + int slave_id) +{ + struct list_head *iter; + struct slave *slave; + int i = slave_id; + + /* Here we start from the slave with slave_id */ + bond_for_each_slave_rcu(bond, slave, iter) { + if (--i < 0) { + if (bond_slave_can_tx(slave)) + return slave; + } + } + + /* Here we start from the first slave up to slave_id */ + i = slave_id; + bond_for_each_slave_rcu(bond, slave, iter) { + if (--i < 0) + break; + if (bond_slave_can_tx(slave)) + return slave; + } + /* no slave that can tx has been found */ + return NULL; +} + +/** + * bond_rr_gen_slave_id - generate slave id based on packets_per_slave + * @bond: bonding device to use + * + * Based on the value of the bonding device's packets_per_slave parameter + * this function generates a slave id, which is usually used as the next + * slave to transmit through. + */ +static u32 bond_rr_gen_slave_id(struct bonding *bond) +{ + u32 slave_id; + struct reciprocal_value reciprocal_packets_per_slave; + int packets_per_slave = bond->params.packets_per_slave; + + switch (packets_per_slave) { + case 0: + slave_id = get_random_u32(); + break; + case 1: + slave_id = this_cpu_inc_return(*bond->rr_tx_counter); + break; + default: + reciprocal_packets_per_slave = + bond->params.reciprocal_packets_per_slave; + slave_id = this_cpu_inc_return(*bond->rr_tx_counter); + slave_id = reciprocal_divide(slave_id, + reciprocal_packets_per_slave); + break; + } + + return slave_id; +} + +static struct slave *bond_xmit_roundrobin_slave_get(struct bonding *bond, + struct sk_buff *skb) +{ + struct slave *slave; + int slave_cnt; + u32 slave_id; + + /* Start with the curr_active_slave that joined the bond as the + * default for sending IGMP traffic. For failover purposes one + * needs to maintain some consistency for the interface that will + * send the join/membership reports. The curr_active_slave found + * will send all of this type of traffic. + */ + if (skb->protocol == htons(ETH_P_IP)) { + int noff = skb_network_offset(skb); + struct iphdr *iph; + + if (unlikely(!pskb_may_pull(skb, noff + sizeof(*iph)))) + goto non_igmp; + + iph = ip_hdr(skb); + if (iph->protocol == IPPROTO_IGMP) { + slave = rcu_dereference(bond->curr_active_slave); + if (slave) + return slave; + return bond_get_slave_by_id(bond, 0); + } + } + +non_igmp: + slave_cnt = READ_ONCE(bond->slave_cnt); + if (likely(slave_cnt)) { + slave_id = bond_rr_gen_slave_id(bond) % slave_cnt; + return bond_get_slave_by_id(bond, slave_id); + } + return NULL; +} + +static struct slave *bond_xdp_xmit_roundrobin_slave_get(struct bonding *bond, + struct xdp_buff *xdp) +{ + struct slave *slave; + int slave_cnt; + u32 slave_id; + const struct ethhdr *eth; + void *data = xdp->data; + + if (data + sizeof(struct ethhdr) > xdp->data_end) + goto non_igmp; + + eth = (struct ethhdr *)data; + data += sizeof(struct ethhdr); + + /* See comment on IGMP in bond_xmit_roundrobin_slave_get() */ + if (eth->h_proto == htons(ETH_P_IP)) { + const struct iphdr *iph; + + if (data + sizeof(struct iphdr) > xdp->data_end) + goto non_igmp; + + iph = (struct iphdr *)data; + + if (iph->protocol == IPPROTO_IGMP) { + slave = rcu_dereference(bond->curr_active_slave); + if (slave) + return slave; + return bond_get_slave_by_id(bond, 0); + } + } + +non_igmp: + slave_cnt = READ_ONCE(bond->slave_cnt); + if (likely(slave_cnt)) { + slave_id = bond_rr_gen_slave_id(bond) % slave_cnt; + return bond_get_slave_by_id(bond, slave_id); + } + return NULL; +} + +static netdev_tx_t bond_xmit_roundrobin(struct sk_buff *skb, + struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct slave *slave; + + slave = bond_xmit_roundrobin_slave_get(bond, skb); + if (likely(slave)) + return bond_dev_queue_xmit(bond, skb, slave->dev); + + return bond_tx_drop(bond_dev, skb); +} + +static struct slave *bond_xmit_activebackup_slave_get(struct bonding *bond) +{ + return rcu_dereference(bond->curr_active_slave); +} + +/* In active-backup mode, we know that bond->curr_active_slave is always valid if + * the bond has a usable interface. + */ +static netdev_tx_t bond_xmit_activebackup(struct sk_buff *skb, + struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct slave *slave; + + slave = bond_xmit_activebackup_slave_get(bond); + if (slave) + return bond_dev_queue_xmit(bond, skb, slave->dev); + + return bond_tx_drop(bond_dev, skb); +} + +/* Use this to update slave_array when (a) it's not appropriate to update + * slave_array right away (note that update_slave_array() may sleep) + * and / or (b) RTNL is not held. + */ +void bond_slave_arr_work_rearm(struct bonding *bond, unsigned long delay) +{ + queue_delayed_work(bond->wq, &bond->slave_arr_work, delay); +} + +/* Slave array work handler. Holds only RTNL */ +static void bond_slave_arr_handler(struct work_struct *work) +{ + struct bonding *bond = container_of(work, struct bonding, + slave_arr_work.work); + int ret; + + if (!rtnl_trylock()) + goto err; + + ret = bond_update_slave_arr(bond, NULL); + rtnl_unlock(); + if (ret) { + pr_warn_ratelimited("Failed to update slave array from WT\n"); + goto err; + } + return; + +err: + bond_slave_arr_work_rearm(bond, 1); +} + +static void bond_skip_slave(struct bond_up_slave *slaves, + struct slave *skipslave) +{ + int idx; + + /* Rare situation where caller has asked to skip a specific + * slave but allocation failed (most likely!). BTW this is + * only possible when the call is initiated from + * __bond_release_one(). In this situation; overwrite the + * skipslave entry in the array with the last entry from the + * array to avoid a situation where the xmit path may choose + * this to-be-skipped slave to send a packet out. + */ + for (idx = 0; slaves && idx < slaves->count; idx++) { + if (skipslave == slaves->arr[idx]) { + slaves->arr[idx] = + slaves->arr[slaves->count - 1]; + slaves->count--; + break; + } + } +} + +static void bond_set_slave_arr(struct bonding *bond, + struct bond_up_slave *usable_slaves, + struct bond_up_slave *all_slaves) +{ + struct bond_up_slave *usable, *all; + + all = rtnl_dereference(bond->all_slaves); + rcu_assign_pointer(bond->all_slaves, all_slaves); + kfree_rcu(all, rcu); + + if (BOND_MODE(bond) == BOND_MODE_BROADCAST) { + kfree_rcu(usable_slaves, rcu); + return; + } + + usable = rtnl_dereference(bond->usable_slaves); + rcu_assign_pointer(bond->usable_slaves, usable_slaves); + kfree_rcu(usable, rcu); +} + +static void bond_reset_slave_arr(struct bonding *bond) +{ + bond_set_slave_arr(bond, NULL, NULL); +} + +/* Build the usable slaves array in control path for modes that use xmit-hash + * to determine the slave interface - + * (a) BOND_MODE_8023AD + * (b) BOND_MODE_XOR + * (c) (BOND_MODE_TLB || BOND_MODE_ALB) && tlb_dynamic_lb == 0 + * + * The caller is expected to hold RTNL only and NO other lock! + */ +int bond_update_slave_arr(struct bonding *bond, struct slave *skipslave) +{ + struct bond_up_slave *usable_slaves = NULL, *all_slaves = NULL; + struct slave *slave; + struct list_head *iter; + int agg_id = 0; + int ret = 0; + + might_sleep(); + + usable_slaves = kzalloc_flex(*usable_slaves, arr, bond->slave_cnt); + all_slaves = kzalloc_flex(*all_slaves, arr, bond->slave_cnt); + if (!usable_slaves || !all_slaves) { + ret = -ENOMEM; + goto out; + } + if (BOND_MODE(bond) == BOND_MODE_8023AD) { + struct ad_info ad_info; + + spin_lock_bh(&bond->mode_lock); + if (bond_3ad_get_active_agg_info(bond, &ad_info)) { + spin_unlock_bh(&bond->mode_lock); + pr_debug("bond_3ad_get_active_agg_info failed\n"); + /* No active aggragator means it's not safe to use + * the previous array. + */ + bond_reset_slave_arr(bond); + goto out; + } + spin_unlock_bh(&bond->mode_lock); + agg_id = ad_info.aggregator_id; + } + rcu_read_lock(); + bond_for_each_slave(bond, slave, iter) { + if (skipslave == slave) + continue; + + all_slaves->arr[all_slaves->count++] = slave; + if (BOND_MODE(bond) == BOND_MODE_8023AD) { + const struct aggregator *agg; + + agg = rcu_dereference(SLAVE_AD_INFO(slave)->port.aggregator); + if (!agg || agg->aggregator_identifier != agg_id) + continue; + } + if (!bond_slave_can_tx(slave)) + continue; + + slave_dbg(bond->dev, slave->dev, "Adding slave to tx hash array[%d]\n", + usable_slaves->count); + + usable_slaves->arr[usable_slaves->count++] = slave; + } + rcu_read_unlock(); + + bond_set_slave_arr(bond, usable_slaves, all_slaves); + return ret; +out: + if (ret != 0 && skipslave) { + bond_skip_slave(rtnl_dereference(bond->all_slaves), + skipslave); + bond_skip_slave(rtnl_dereference(bond->usable_slaves), + skipslave); + } + kfree_rcu(all_slaves, rcu); + kfree_rcu(usable_slaves, rcu); + + return ret; +} + +static struct slave *bond_xmit_3ad_xor_slave_get(struct bonding *bond, + struct sk_buff *skb, + struct bond_up_slave *slaves) +{ + struct slave *slave; + unsigned int count; + u32 hash; + + hash = bond_xmit_hash(bond, skb); + count = slaves ? READ_ONCE(slaves->count) : 0; + if (unlikely(!count)) + return NULL; + + slave = slaves->arr[hash % count]; + return slave; +} + +static struct slave *bond_xdp_xmit_3ad_xor_slave_get(struct bonding *bond, + struct xdp_buff *xdp) +{ + struct bond_up_slave *slaves; + unsigned int count; + u32 hash; + + hash = bond_xmit_hash_xdp(bond, xdp); + slaves = rcu_dereference(bond->usable_slaves); + count = slaves ? READ_ONCE(slaves->count) : 0; + if (unlikely(!count)) + return NULL; + + return slaves->arr[hash % count]; +} + +static bool bond_should_broadcast_neighbor(struct sk_buff *skb, + struct net_device *dev) +{ + struct bonding *bond = netdev_priv(dev); + struct { + struct ipv6hdr ip6; + struct icmp6hdr icmp6; + } *combined, _combined; + + if (!static_branch_unlikely(&bond_bcast_neigh_enabled)) + return false; + + if (!bond->params.broadcast_neighbor) + return false; + + if (skb->protocol == htons(ETH_P_ARP)) + return true; + + if (skb->protocol == htons(ETH_P_IPV6)) { + combined = skb_header_pointer(skb, skb_mac_header_len(skb), + sizeof(_combined), + &_combined); + if (combined && combined->ip6.nexthdr == NEXTHDR_ICMP && + (combined->icmp6.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION || + combined->icmp6.icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT)) + return true; + } + + return false; +} + +/* Use this Xmit function for 3AD as well as XOR modes. The current + * usable slave array is formed in the control path. The xmit function + * just calculates hash and sends the packet out. + */ +static netdev_tx_t bond_3ad_xor_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct bonding *bond = netdev_priv(dev); + struct bond_up_slave *slaves; + struct slave *slave; + + slaves = rcu_dereference(bond->usable_slaves); + slave = bond_xmit_3ad_xor_slave_get(bond, skb, slaves); + if (likely(slave)) + return bond_dev_queue_xmit(bond, skb, slave->dev); + + return bond_tx_drop(dev, skb); +} + +/* in broadcast mode, we send everything to all or usable slave interfaces. + * under rcu_read_lock when this function is called. + */ +static netdev_tx_t bond_xmit_broadcast(struct sk_buff *skb, + struct net_device *bond_dev, + bool all_slaves) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct bond_up_slave *slaves; + bool xmit_suc = false; + bool skb_used = false; + int slaves_count, i; + + if (all_slaves) + slaves = rcu_dereference(bond->all_slaves); + else + slaves = rcu_dereference(bond->usable_slaves); + + slaves_count = slaves ? READ_ONCE(slaves->count) : 0; + for (i = 0; i < slaves_count; i++) { + struct slave *slave = slaves->arr[i]; + struct sk_buff *skb2; + + if (!(bond_slave_is_up(slave) && slave->link == BOND_LINK_UP)) + continue; + + if (i + 1 == slaves_count) { + skb2 = skb; + skb_used = true; + } else { + skb2 = skb_clone(skb, GFP_ATOMIC); + if (!skb2) { + net_err_ratelimited("%s: Error: %s: skb_clone() failed\n", + bond_dev->name, __func__); + continue; + } + } + + if (bond_dev_queue_xmit(bond, skb2, slave->dev) == NETDEV_TX_OK) + xmit_suc = true; + } + + if (!skb_used) + dev_kfree_skb_any(skb); + + if (xmit_suc) + return NETDEV_TX_OK; + + dev_core_stats_tx_dropped_inc(bond_dev); + return NET_XMIT_DROP; +} + +/*------------------------- Device initialization ---------------------------*/ + +/* Lookup the slave that corresponds to a qid */ +static inline int bond_slave_override(struct bonding *bond, + struct sk_buff *skb) +{ + struct slave *slave = NULL; + struct list_head *iter; + + if (!skb_rx_queue_recorded(skb)) + return 1; + + /* Find out if any slaves have the same mapping as this skb. */ + bond_for_each_slave_rcu(bond, slave, iter) { + if (READ_ONCE(slave->queue_id) == skb_get_queue_mapping(skb)) { + if (bond_slave_is_up(slave) && + slave->link == BOND_LINK_UP) { + bond_dev_queue_xmit(bond, skb, slave->dev); + return 0; + } + /* If the slave isn't UP, use default transmit policy. */ + break; + } + } + + return 1; +} + + +static u16 bond_select_queue(struct net_device *dev, struct sk_buff *skb, + struct net_device *sb_dev) +{ + /* This helper function exists to help dev_pick_tx get the correct + * destination queue. Using a helper function skips a call to + * skb_tx_hash and will put the skbs in the queue we expect on their + * way down to the bonding driver. + */ + u16 txq = skb_rx_queue_recorded(skb) ? skb_get_rx_queue(skb) : 0; + + /* Save the original txq to restore before passing to the driver */ + qdisc_skb_cb(skb)->slave_dev_queue_mapping = skb_get_queue_mapping(skb); + + if (unlikely(txq >= dev->real_num_tx_queues)) { + do { + txq -= dev->real_num_tx_queues; + } while (txq >= dev->real_num_tx_queues); + } + return txq; +} + +static struct net_device *bond_xmit_get_slave(struct net_device *master_dev, + struct sk_buff *skb, + bool all_slaves) +{ + struct bonding *bond = netdev_priv(master_dev); + struct bond_up_slave *slaves; + struct slave *slave = NULL; + + switch (BOND_MODE(bond)) { + case BOND_MODE_ROUNDROBIN: + slave = bond_xmit_roundrobin_slave_get(bond, skb); + break; + case BOND_MODE_ACTIVEBACKUP: + slave = bond_xmit_activebackup_slave_get(bond); + break; + case BOND_MODE_8023AD: + case BOND_MODE_XOR: + if (all_slaves) + slaves = rcu_dereference(bond->all_slaves); + else + slaves = rcu_dereference(bond->usable_slaves); + slave = bond_xmit_3ad_xor_slave_get(bond, skb, slaves); + break; + case BOND_MODE_BROADCAST: + break; + case BOND_MODE_ALB: + slave = bond_xmit_alb_slave_get(bond, skb); + break; + case BOND_MODE_TLB: + slave = bond_xmit_tlb_slave_get(bond, skb); + break; + default: + /* Should never happen, mode already checked */ + WARN_ONCE(true, "Unknown bonding mode"); + break; + } + + if (slave) + return slave->dev; + return NULL; +} + +static void bond_sk_to_flow(struct sock *sk, struct flow_keys *flow) +{ + switch (sk->sk_family) { +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: + if (ipv6_only_sock(sk) || + ipv6_addr_type(&sk->sk_v6_daddr) != IPV6_ADDR_MAPPED) { + flow->control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS; + flow->addrs.v6addrs.src = inet6_sk(sk)->saddr; + flow->addrs.v6addrs.dst = sk->sk_v6_daddr; + break; + } + fallthrough; +#endif + default: /* AF_INET */ + flow->control.addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS; + flow->addrs.v4addrs.src = inet_sk(sk)->inet_rcv_saddr; + flow->addrs.v4addrs.dst = inet_sk(sk)->inet_daddr; + break; + } + + flow->ports.src = inet_sk(sk)->inet_sport; + flow->ports.dst = inet_sk(sk)->inet_dport; +} + +/** + * bond_sk_hash_l34 - generate a hash value based on the socket's L3 and L4 fields + * @sk: socket to use for headers + * + * This function will extract the necessary field from the socket and use + * them to generate a hash based on the LAYER34 xmit_policy. + * Assumes that sk is a TCP or UDP socket. + */ +static u32 bond_sk_hash_l34(struct sock *sk) +{ + struct flow_keys flow; + u32 hash; + + bond_sk_to_flow(sk, &flow); + + /* L4 */ + memcpy(&hash, &flow.ports.ports, sizeof(hash)); + /* L3 */ + return bond_ip_hash(hash, &flow, BOND_XMIT_POLICY_LAYER34); +} + +static struct net_device *__bond_sk_get_lower_dev(struct bonding *bond, + struct sock *sk) +{ + struct bond_up_slave *slaves; + struct slave *slave; + unsigned int count; + u32 hash; + + slaves = rcu_dereference(bond->usable_slaves); + count = slaves ? READ_ONCE(slaves->count) : 0; + if (unlikely(!count)) + return NULL; + + hash = bond_sk_hash_l34(sk); + slave = slaves->arr[hash % count]; + + return slave->dev; +} + +static struct net_device *bond_sk_get_lower_dev(struct net_device *dev, + struct sock *sk) +{ + struct bonding *bond = netdev_priv(dev); + struct net_device *lower = NULL; + + rcu_read_lock(); + if (bond_sk_check(bond)) + lower = __bond_sk_get_lower_dev(bond, sk); + rcu_read_unlock(); + + return lower; +} + +#if IS_ENABLED(CONFIG_TLS_DEVICE) +static netdev_tx_t bond_tls_device_xmit(struct bonding *bond, struct sk_buff *skb, + struct net_device *dev) +{ + struct net_device *tls_netdev = rcu_dereference(tls_get_ctx(skb->sk)->netdev); + + /* tls_netdev might become NULL, even if tls_is_skb_tx_device_offloaded + * was true, if tls_device_down is running in parallel, but it's OK, + * because bond_get_slave_by_dev has a NULL check. + */ + if (likely(bond_get_slave_by_dev(bond, tls_netdev))) + return bond_dev_queue_xmit(bond, skb, tls_netdev); + return bond_tx_drop(dev, skb); +} +#endif + +static netdev_tx_t __bond_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct bonding *bond = netdev_priv(dev); + + if (bond_should_override_tx_queue(bond) && + !bond_slave_override(bond, skb)) + return NETDEV_TX_OK; + +#if IS_ENABLED(CONFIG_TLS_DEVICE) + if (tls_is_skb_tx_device_offloaded(skb)) + return bond_tls_device_xmit(bond, skb, dev); +#endif + + switch (BOND_MODE(bond)) { + case BOND_MODE_ROUNDROBIN: + return bond_xmit_roundrobin(skb, dev); + case BOND_MODE_ACTIVEBACKUP: + return bond_xmit_activebackup(skb, dev); + case BOND_MODE_8023AD: + if (bond_should_broadcast_neighbor(skb, dev)) + return bond_xmit_broadcast(skb, dev, false); + fallthrough; + case BOND_MODE_XOR: + return bond_3ad_xor_xmit(skb, dev); + case BOND_MODE_BROADCAST: + return bond_xmit_broadcast(skb, dev, true); + case BOND_MODE_ALB: + return bond_alb_xmit(skb, dev); + case BOND_MODE_TLB: + return bond_tlb_xmit(skb, dev); + default: + /* Should never happen, mode already checked */ + netdev_err(dev, "Unknown bonding mode %d\n", BOND_MODE(bond)); + WARN_ON_ONCE(1); + return bond_tx_drop(dev, skb); + } +} + +static netdev_tx_t bond_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct bonding *bond = netdev_priv(dev); + netdev_tx_t ret = NETDEV_TX_OK; + + /* If we risk deadlock from transmitting this in the + * netpoll path, tell netpoll to queue the frame for later tx + */ + if (unlikely(is_netpoll_tx_blocked(dev))) + return NETDEV_TX_BUSY; + + rcu_read_lock(); + if (bond_has_slaves(bond)) + ret = __bond_start_xmit(skb, dev); + else + ret = bond_tx_drop(dev, skb); + rcu_read_unlock(); + + return ret; +} + +static struct net_device * +bond_xdp_get_xmit_slave(struct net_device *bond_dev, struct xdp_buff *xdp) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct slave *slave; + + /* Caller needs to hold rcu_read_lock() */ + + switch (BOND_MODE(bond)) { + case BOND_MODE_ROUNDROBIN: + slave = bond_xdp_xmit_roundrobin_slave_get(bond, xdp); + break; + + case BOND_MODE_ACTIVEBACKUP: + slave = bond_xmit_activebackup_slave_get(bond); + break; + + case BOND_MODE_8023AD: + case BOND_MODE_XOR: + slave = bond_xdp_xmit_3ad_xor_slave_get(bond, xdp); + break; + + default: + if (net_ratelimit()) + netdev_err(bond_dev, "Unknown bonding mode %d for xdp xmit\n", + BOND_MODE(bond)); + return NULL; + } + + if (slave) + return slave->dev; + + return NULL; +} + +static int bond_xdp_xmit(struct net_device *bond_dev, + int n, struct xdp_frame **frames, u32 flags) +{ + int nxmit, err = -ENXIO; + + rcu_read_lock(); + + for (nxmit = 0; nxmit < n; nxmit++) { + struct xdp_frame *frame = frames[nxmit]; + struct xdp_frame *frames1[] = {frame}; + struct net_device *slave_dev; + struct xdp_buff xdp; + + xdp_convert_frame_to_buff(frame, &xdp); + + slave_dev = bond_xdp_get_xmit_slave(bond_dev, &xdp); + if (!slave_dev) { + err = -ENXIO; + break; + } + + err = slave_dev->netdev_ops->ndo_xdp_xmit(slave_dev, 1, frames1, flags); + if (err < 1) + break; + } + + rcu_read_unlock(); + + /* If error happened on the first frame then we can pass the error up, otherwise + * report the number of frames that were xmitted. + */ + if (err < 0) + return (nxmit == 0 ? err : nxmit); + + return nxmit; +} + +static int bond_xdp_set(struct net_device *dev, struct bpf_prog *prog, + struct netlink_ext_ack *extack) +{ + struct bonding *bond = netdev_priv(dev); + struct list_head *iter; + struct slave *slave, *rollback_slave; + struct bpf_prog *old_prog; + struct netdev_bpf xdp = { + .command = XDP_SETUP_PROG, + .flags = 0, + .prog = prog, + .extack = extack, + }; + int err; + + ASSERT_RTNL(); + + if (!bond_xdp_check(bond, BOND_MODE(bond))) { + BOND_NL_ERR(dev, extack, + "No native XDP support for the current bonding mode"); + return -EOPNOTSUPP; + } + + old_prog = bond->xdp_prog; + bond->xdp_prog = prog; + + bond_for_each_slave(bond, slave, iter) { + struct net_device *slave_dev = slave->dev; + + if (!slave_dev->netdev_ops->ndo_bpf || + !slave_dev->netdev_ops->ndo_xdp_xmit) { + SLAVE_NL_ERR(dev, slave_dev, extack, + "Slave device does not support XDP"); + err = -EOPNOTSUPP; + goto err; + } + + if (dev_xdp_prog_count(slave_dev) > 0) { + SLAVE_NL_ERR(dev, slave_dev, extack, + "Slave has XDP program loaded, please unload before enslaving"); + err = -EOPNOTSUPP; + goto err; + } + + err = dev_xdp_propagate(slave_dev, &xdp); + if (err < 0) { + /* ndo_bpf() sets extack error message */ + slave_err(dev, slave_dev, "Error %d calling ndo_bpf\n", err); + goto err; + } + if (prog) + bpf_prog_inc(prog); + } + + if (prog) { + static_branch_inc(&bpf_master_redirect_enabled_key); + } else if (old_prog) { + bpf_prog_put(old_prog); + static_branch_dec(&bpf_master_redirect_enabled_key); + } + + return 0; + +err: + /* unwind the program changes */ + bond->xdp_prog = old_prog; + xdp.prog = old_prog; + xdp.extack = NULL; /* do not overwrite original error */ + + bond_for_each_slave(bond, rollback_slave, iter) { + struct net_device *slave_dev = rollback_slave->dev; + int err_unwind; + + if (slave == rollback_slave) + break; + + err_unwind = dev_xdp_propagate(slave_dev, &xdp); + if (err_unwind < 0) + slave_err(dev, slave_dev, + "Error %d when unwinding XDP program change\n", err_unwind); + else if (xdp.prog) + bpf_prog_inc(xdp.prog); + } + return err; +} + +static int bond_xdp(struct net_device *dev, struct netdev_bpf *xdp) +{ + switch (xdp->command) { + case XDP_SETUP_PROG: + return bond_xdp_set(dev, xdp->prog, xdp->extack); + default: + return -EINVAL; + } +} + +static u32 bond_mode_bcast_speed(struct slave *slave, u32 speed) +{ + if (speed == 0 || speed == SPEED_UNKNOWN) + speed = slave->speed; + else + speed = min(speed, slave->speed); + + return speed; +} + +/* Set the BOND_PHC_INDEX flag to notify user space */ +static int bond_set_phc_index_flag(struct kernel_hwtstamp_config *kernel_cfg) +{ + struct ifreq *ifr = kernel_cfg->ifr; + struct hwtstamp_config cfg; + + if (kernel_cfg->copied_to_user) { + /* Lower device has a legacy implementation */ + if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg))) + return -EFAULT; + + cfg.flags |= HWTSTAMP_FLAG_BONDED_PHC_INDEX; + if (copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg))) + return -EFAULT; + } else { + kernel_cfg->flags |= HWTSTAMP_FLAG_BONDED_PHC_INDEX; + } + + return 0; +} + +static int bond_hwtstamp_get(struct net_device *dev, + struct kernel_hwtstamp_config *cfg) +{ + struct bonding *bond = netdev_priv(dev); + struct net_device *real_dev; + int err; + + real_dev = bond_option_active_slave_get_rcu(bond); + if (!real_dev) + return -EOPNOTSUPP; + + err = generic_hwtstamp_get_lower(real_dev, cfg); + if (err) + return err; + + return bond_set_phc_index_flag(cfg); +} + +static int bond_hwtstamp_set(struct net_device *dev, + struct kernel_hwtstamp_config *cfg, + struct netlink_ext_ack *extack) +{ + struct bonding *bond = netdev_priv(dev); + struct net_device *real_dev; + int err; + + if (!(cfg->flags & HWTSTAMP_FLAG_BONDED_PHC_INDEX)) + return -EOPNOTSUPP; + + real_dev = bond_option_active_slave_get_rcu(bond); + if (!real_dev) + return -EOPNOTSUPP; + + err = generic_hwtstamp_set_lower(real_dev, cfg, extack); + if (err) + return err; + + return bond_set_phc_index_flag(cfg); +} + +static int bond_ethtool_get_link_ksettings(struct net_device *bond_dev, + struct ethtool_link_ksettings *cmd) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct list_head *iter; + struct slave *slave; + u32 speed = 0; + + cmd->base.duplex = DUPLEX_UNKNOWN; + cmd->base.port = PORT_OTHER; + + /* Since bond_slave_can_tx returns false for all inactive or down slaves, we + * do not need to check mode. Though link speed might not represent + * the true receive or transmit bandwidth (not all modes are symmetric) + * this is an accurate maximum. + */ + bond_for_each_slave(bond, slave, iter) { + if (bond_slave_can_tx(slave)) { + netdev_lock_ops(slave->dev); + bond_update_speed_duplex(slave); + netdev_unlock_ops(slave->dev); + if (slave->speed != SPEED_UNKNOWN) { + if (BOND_MODE(bond) == BOND_MODE_BROADCAST) + speed = bond_mode_bcast_speed(slave, + speed); + else + speed += slave->speed; + } + if (cmd->base.duplex == DUPLEX_UNKNOWN && + slave->duplex != DUPLEX_UNKNOWN) + cmd->base.duplex = slave->duplex; + } + } + cmd->base.speed = speed ? : SPEED_UNKNOWN; + + return 0; +} + +static void bond_ethtool_get_drvinfo(struct net_device *bond_dev, + struct ethtool_drvinfo *drvinfo) +{ + strscpy(drvinfo->driver, KBUILD_MODNAME, sizeof(drvinfo->driver)); + snprintf(drvinfo->fw_version, sizeof(drvinfo->fw_version), "%d", + BOND_ABI_VERSION); +} + +static int bond_ethtool_get_ts_info(struct net_device *bond_dev, + struct kernel_ethtool_ts_info *info) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct kernel_ethtool_ts_info ts_info; + struct net_device *real_dev; + bool sw_tx_support = false; + struct list_head *iter; + struct slave *slave; + int ret = 0; + + rcu_read_lock(); + real_dev = bond_option_active_slave_get_rcu(bond); + dev_hold(real_dev); + rcu_read_unlock(); + + if (real_dev) { + ret = ethtool_get_ts_info_by_layer(real_dev, info); + } else { + /* Check if all slaves support software tx timestamping */ + rcu_read_lock(); + bond_for_each_slave_rcu(bond, slave, iter) { + ret = ethtool_get_ts_info_by_layer(slave->dev, &ts_info); + if (!ret && (ts_info.so_timestamping & SOF_TIMESTAMPING_TX_SOFTWARE)) { + sw_tx_support = true; + continue; + } + + sw_tx_support = false; + break; + } + rcu_read_unlock(); + } + + if (sw_tx_support) + info->so_timestamping |= SOF_TIMESTAMPING_TX_SOFTWARE; + + dev_put(real_dev); + return ret; +} + +static const struct ethtool_ops bond_ethtool_ops = { + .get_drvinfo = bond_ethtool_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_link_ksettings = bond_ethtool_get_link_ksettings, + .get_ts_info = bond_ethtool_get_ts_info, +}; + +static const struct net_device_ops bond_netdev_ops = { + .ndo_init = bond_init, + .ndo_uninit = bond_uninit, + .ndo_open = bond_open, + .ndo_stop = bond_close, + .ndo_start_xmit = bond_start_xmit, + .ndo_select_queue = bond_select_queue, + .ndo_get_stats64 = bond_get_stats, + .ndo_eth_ioctl = bond_eth_ioctl, + .ndo_siocbond = bond_do_ioctl, + .ndo_siocdevprivate = bond_siocdevprivate, + .ndo_change_rx_flags = bond_change_rx_flags, + .ndo_set_rx_mode = bond_set_rx_mode, + .ndo_change_mtu = bond_change_mtu, + .ndo_set_mac_address = bond_set_mac_address, + .ndo_neigh_setup = bond_neigh_setup, + .ndo_vlan_rx_add_vid = bond_vlan_rx_add_vid, + .ndo_vlan_rx_kill_vid = bond_vlan_rx_kill_vid, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_netpoll_setup = bond_netpoll_setup, + .ndo_netpoll_cleanup = bond_netpoll_cleanup, + .ndo_poll_controller = bond_poll_controller, +#endif + .ndo_add_slave = bond_enslave, + .ndo_del_slave = bond_release, + .ndo_fix_features = bond_fix_features, + .ndo_features_check = passthru_features_check, + .ndo_get_xmit_slave = bond_xmit_get_slave, + .ndo_sk_get_lower_dev = bond_sk_get_lower_dev, + .ndo_bpf = bond_xdp, + .ndo_xdp_xmit = bond_xdp_xmit, + .ndo_xdp_get_xmit_slave = bond_xdp_get_xmit_slave, + .ndo_hwtstamp_get = bond_hwtstamp_get, + .ndo_hwtstamp_set = bond_hwtstamp_set, +}; + +static const struct device_type bond_type = { + .name = "bond", +}; + +static void bond_destructor(struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + + if (bond->wq) + destroy_workqueue(bond->wq); + + free_percpu(bond->rr_tx_counter); +} + +void bond_setup(struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + + spin_lock_init(&bond->mode_lock); + bond->params = bonding_defaults; + + /* Initialize pointers */ + bond->dev = bond_dev; + + /* Initialize the device entry points */ + ether_setup(bond_dev); + bond_dev->max_mtu = ETH_MAX_MTU; + bond_dev->netdev_ops = &bond_netdev_ops; + bond_dev->ethtool_ops = &bond_ethtool_ops; + + bond_dev->needs_free_netdev = true; + bond_dev->priv_destructor = bond_destructor; + + SET_NETDEV_DEVTYPE(bond_dev, &bond_type); + + /* Initialize the device options */ + bond_dev->flags |= IFF_MASTER; + bond_dev->priv_flags |= IFF_BONDING | IFF_UNICAST_FLT | IFF_NO_QUEUE; + bond_dev->priv_flags &= ~(IFF_XMIT_DST_RELEASE | IFF_TX_SKB_SHARING); + +#ifdef CONFIG_XFRM_OFFLOAD + /* set up xfrm device ops (only supported in active-backup right now) */ + bond_dev->xfrmdev_ops = &bond_xfrmdev_ops; + INIT_LIST_HEAD(&bond->ipsec_list); + mutex_init(&bond->ipsec_lock); +#endif /* CONFIG_XFRM_OFFLOAD */ + + /* don't acquire bond device's netif_tx_lock when transmitting */ + bond_dev->lltx = true; + + /* Don't allow bond devices to change network namespaces. */ + bond_dev->netns_immutable = true; + + /* By default, we declare the bond to be fully + * VLAN hardware accelerated capable. Special + * care is taken in the various xmit functions + * when there are slaves that are not hw accel + * capable + */ + + bond_dev->hw_features = MASTER_UPPER_DEV_VLAN_FEATURES | + NETIF_F_HW_VLAN_CTAG_RX | + NETIF_F_HW_VLAN_CTAG_FILTER | + NETIF_F_HW_VLAN_STAG_RX | + NETIF_F_HW_VLAN_STAG_FILTER; + + bond_dev->hw_features |= NETIF_F_GSO_ENCAP_ALL; + bond_dev->features |= bond_dev->hw_features; + bond_dev->features |= NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_STAG_TX; + bond_dev->features |= NETIF_F_GSO_PARTIAL; +#ifdef CONFIG_XFRM_OFFLOAD + bond_dev->hw_features |= BOND_XFRM_FEATURES; + /* Only enable XFRM features if this is an active-backup config */ + if (BOND_MODE(bond) == BOND_MODE_ACTIVEBACKUP) + bond_dev->features |= BOND_XFRM_FEATURES; +#endif /* CONFIG_XFRM_OFFLOAD */ +} + +/* Destroy a bonding device. + * Must be under rtnl_lock when this function is called. + */ +static void bond_uninit(struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct list_head *iter; + struct slave *slave; + + bond_netpoll_cleanup(bond_dev); + + /* Release the bonded slaves */ + bond_for_each_slave(bond, slave, iter) + __bond_release_one(bond_dev, slave->dev, true, true); + netdev_info(bond_dev, "Released all slaves\n"); + +#ifdef CONFIG_XFRM_OFFLOAD + mutex_destroy(&bond->ipsec_lock); +#endif /* CONFIG_XFRM_OFFLOAD */ + + bond_set_slave_arr(bond, NULL, NULL); + + list_del_rcu(&bond->bond_list); + + bond_debug_unregister(bond); +} + +/*------------------------- Module initialization ---------------------------*/ + +static int __init bond_check_params(struct bond_params *params) +{ + int arp_validate_value, fail_over_mac_value, primary_reselect_value, i; + struct bond_opt_value newval; + const struct bond_opt_value *valptr; + int arp_all_targets_value = 0; + u16 ad_actor_sys_prio = 0; + u16 ad_user_port_key = 0; + __be32 arp_target[BOND_MAX_ARP_TARGETS] = { 0 }; + int arp_ip_count; + int bond_mode = BOND_MODE_ROUNDROBIN; + int xmit_hashtype = BOND_XMIT_POLICY_LAYER2; + int lacp_fast = 0; + int tlb_dynamic_lb; + + /* Convert string parameters. */ + if (mode) { + bond_opt_initstr(&newval, mode); + valptr = bond_opt_parse(bond_opt_get(BOND_OPT_MODE), &newval); + if (!valptr) { + pr_err("Error: Invalid bonding mode \"%s\"\n", mode); + return -EINVAL; + } + bond_mode = valptr->value; + } + + if (xmit_hash_policy) { + if (bond_mode == BOND_MODE_ROUNDROBIN || + bond_mode == BOND_MODE_ACTIVEBACKUP || + bond_mode == BOND_MODE_BROADCAST) { + pr_info("xmit_hash_policy param is irrelevant in mode %s\n", + bond_mode_name(bond_mode)); + } else { + bond_opt_initstr(&newval, xmit_hash_policy); + valptr = bond_opt_parse(bond_opt_get(BOND_OPT_XMIT_HASH), + &newval); + if (!valptr) { + pr_err("Error: Invalid xmit_hash_policy \"%s\"\n", + xmit_hash_policy); + return -EINVAL; + } + xmit_hashtype = valptr->value; + } + } + + if (lacp_rate) { + if (bond_mode != BOND_MODE_8023AD) { + pr_info("lacp_rate param is irrelevant in mode %s\n", + bond_mode_name(bond_mode)); + } else { + bond_opt_initstr(&newval, lacp_rate); + valptr = bond_opt_parse(bond_opt_get(BOND_OPT_LACP_RATE), + &newval); + if (!valptr) { + pr_err("Error: Invalid lacp rate \"%s\"\n", + lacp_rate); + return -EINVAL; + } + lacp_fast = valptr->value; + } + } + + if (ad_select) { + bond_opt_initstr(&newval, ad_select); + valptr = bond_opt_parse(bond_opt_get(BOND_OPT_AD_SELECT), + &newval); + if (!valptr) { + pr_err("Error: Invalid ad_select \"%s\"\n", ad_select); + return -EINVAL; + } + params->ad_select = valptr->value; + if (bond_mode != BOND_MODE_8023AD) + pr_warn("ad_select param only affects 802.3ad mode\n"); + } else { + params->ad_select = BOND_AD_STABLE; + } + + if (max_bonds < 0) { + pr_warn("Warning: max_bonds (%d) not in range %d-%d, so it was reset to BOND_DEFAULT_MAX_BONDS (%d)\n", + max_bonds, 0, INT_MAX, BOND_DEFAULT_MAX_BONDS); + max_bonds = BOND_DEFAULT_MAX_BONDS; + } + + if (miimon < 0) { + pr_warn("Warning: miimon module parameter (%d), not in range 0-%d, so it was reset to 0\n", + miimon, INT_MAX); + miimon = 0; + } + + if (updelay < 0) { + pr_warn("Warning: updelay module parameter (%d), not in range 0-%d, so it was reset to 0\n", + updelay, INT_MAX); + updelay = 0; + } + + if (downdelay < 0) { + pr_warn("Warning: downdelay module parameter (%d), not in range 0-%d, so it was reset to 0\n", + downdelay, INT_MAX); + downdelay = 0; + } + + if (use_carrier != 1) { + pr_err("Error: invalid use_carrier parameter (%d)\n", + use_carrier); + return -EINVAL; + } + + if (num_peer_notif < 0 || num_peer_notif > 255) { + pr_warn("Warning: num_grat_arp/num_unsol_na (%d) not in range 0-255 so it was reset to 1\n", + num_peer_notif); + num_peer_notif = 1; + } + + /* reset values for 802.3ad/TLB/ALB */ + if (!bond_mode_uses_arp(bond_mode)) { + if (!miimon) { + pr_warn("Warning: miimon must be specified, otherwise bonding will not detect link failure, speed and duplex which are essential for 802.3ad operation\n"); + pr_warn("Forcing miimon to 100msec\n"); + miimon = BOND_DEFAULT_MIIMON; + } + } + + if (tx_queues < 1 || tx_queues > 255) { + pr_warn("Warning: tx_queues (%d) should be between 1 and 255, resetting to %d\n", + tx_queues, BOND_DEFAULT_TX_QUEUES); + tx_queues = BOND_DEFAULT_TX_QUEUES; + } + + if ((all_slaves_active != 0) && (all_slaves_active != 1)) { + pr_warn("Warning: all_slaves_active module parameter (%d), not of valid value (0/1), so it was set to 0\n", + all_slaves_active); + all_slaves_active = 0; + } + + if (resend_igmp < 0 || resend_igmp > 255) { + pr_warn("Warning: resend_igmp (%d) should be between 0 and 255, resetting to %d\n", + resend_igmp, BOND_DEFAULT_RESEND_IGMP); + resend_igmp = BOND_DEFAULT_RESEND_IGMP; + } + + bond_opt_initval(&newval, packets_per_slave); + if (!bond_opt_parse(bond_opt_get(BOND_OPT_PACKETS_PER_SLAVE), &newval)) { + pr_warn("Warning: packets_per_slave (%d) should be between 0 and %u resetting to 1\n", + packets_per_slave, USHRT_MAX); + packets_per_slave = 1; + } + + if (bond_mode == BOND_MODE_ALB) { + pr_notice("In ALB mode you might experience client disconnections upon reconnection of a link if the bonding module updelay parameter (%d msec) is incompatible with the forwarding delay time of the switch\n", + updelay); + } + + if (!miimon) { + if (updelay || downdelay) { + /* just warn the user the up/down delay will have + * no effect since miimon is zero... + */ + pr_warn("Warning: miimon module parameter not set and updelay (%d) or downdelay (%d) module parameter is set; updelay and downdelay have no effect unless miimon is set\n", + updelay, downdelay); + } + } else { + /* don't allow arp monitoring */ + if (arp_interval) { + pr_warn("Warning: miimon (%d) and arp_interval (%d) can't be used simultaneously, disabling ARP monitoring\n", + miimon, arp_interval); + arp_interval = 0; + } + + if ((updelay % miimon) != 0) { + pr_warn("Warning: updelay (%d) is not a multiple of miimon (%d), updelay rounded to %d ms\n", + updelay, miimon, (updelay / miimon) * miimon); + } + + updelay /= miimon; + + if ((downdelay % miimon) != 0) { + pr_warn("Warning: downdelay (%d) is not a multiple of miimon (%d), downdelay rounded to %d ms\n", + downdelay, miimon, + (downdelay / miimon) * miimon); + } + + downdelay /= miimon; + } + + if (arp_interval < 0) { + pr_warn("Warning: arp_interval module parameter (%d), not in range 0-%d, so it was reset to 0\n", + arp_interval, INT_MAX); + arp_interval = 0; + } + + for (arp_ip_count = 0, i = 0; + (arp_ip_count < BOND_MAX_ARP_TARGETS) && arp_ip_target[i]; i++) { + __be32 ip; + + /* not a complete check, but good enough to catch mistakes */ + if (!in4_pton(arp_ip_target[i], -1, (u8 *)&ip, -1, NULL) || + !bond_is_ip_target_ok(ip)) { + pr_warn("Warning: bad arp_ip_target module parameter (%s), ARP monitoring will not be performed\n", + arp_ip_target[i]); + arp_interval = 0; + } else { + if (bond_get_targets_ip(arp_target, ip) == -1) + arp_target[arp_ip_count++] = ip; + else + pr_warn("Warning: duplicate address %pI4 in arp_ip_target, skipping\n", + &ip); + } + } + + if (arp_interval && !arp_ip_count) { + /* don't allow arping if no arp_ip_target given... */ + pr_warn("Warning: arp_interval module parameter (%d) specified without providing an arp_ip_target parameter, arp_interval was reset to 0\n", + arp_interval); + arp_interval = 0; + } + + if (arp_validate) { + if (!arp_interval) { + pr_err("arp_validate requires arp_interval\n"); + return -EINVAL; + } + + bond_opt_initstr(&newval, arp_validate); + valptr = bond_opt_parse(bond_opt_get(BOND_OPT_ARP_VALIDATE), + &newval); + if (!valptr) { + pr_err("Error: invalid arp_validate \"%s\"\n", + arp_validate); + return -EINVAL; + } + arp_validate_value = valptr->value; + } else { + arp_validate_value = 0; + } + + if (arp_all_targets) { + bond_opt_initstr(&newval, arp_all_targets); + valptr = bond_opt_parse(bond_opt_get(BOND_OPT_ARP_ALL_TARGETS), + &newval); + if (!valptr) { + pr_err("Error: invalid arp_all_targets_value \"%s\"\n", + arp_all_targets); + arp_all_targets_value = 0; + } else { + arp_all_targets_value = valptr->value; + } + } + + if (miimon) { + pr_info("MII link monitoring set to %d ms\n", miimon); + } else if (arp_interval) { + valptr = bond_opt_get_val(BOND_OPT_ARP_VALIDATE, + arp_validate_value); + pr_info("ARP monitoring set to %d ms, validate %s, with %d target(s):", + arp_interval, valptr->string, arp_ip_count); + + for (i = 0; i < arp_ip_count; i++) + pr_cont(" %s", arp_ip_target[i]); + + pr_cont("\n"); + + } else if (max_bonds) { + /* miimon and arp_interval not set, we need one so things + * work as expected, see bonding.txt for details + */ + pr_debug("Warning: either miimon or arp_interval and arp_ip_target module parameters must be specified, otherwise bonding will not detect link failures! see bonding.txt for details\n"); + } + + if (primary && !bond_mode_uses_primary(bond_mode)) { + /* currently, using a primary only makes sense + * in active backup, TLB or ALB modes + */ + pr_warn("Warning: %s primary device specified but has no effect in %s mode\n", + primary, bond_mode_name(bond_mode)); + primary = NULL; + } + + if (primary && primary_reselect) { + bond_opt_initstr(&newval, primary_reselect); + valptr = bond_opt_parse(bond_opt_get(BOND_OPT_PRIMARY_RESELECT), + &newval); + if (!valptr) { + pr_err("Error: Invalid primary_reselect \"%s\"\n", + primary_reselect); + return -EINVAL; + } + primary_reselect_value = valptr->value; + } else { + primary_reselect_value = BOND_PRI_RESELECT_ALWAYS; + } + + if (fail_over_mac) { + bond_opt_initstr(&newval, fail_over_mac); + valptr = bond_opt_parse(bond_opt_get(BOND_OPT_FAIL_OVER_MAC), + &newval); + if (!valptr) { + pr_err("Error: invalid fail_over_mac \"%s\"\n", + fail_over_mac); + return -EINVAL; + } + fail_over_mac_value = valptr->value; + if (bond_mode != BOND_MODE_ACTIVEBACKUP) + pr_warn("Warning: fail_over_mac only affects active-backup mode\n"); + } else { + fail_over_mac_value = BOND_FOM_NONE; + } + + bond_opt_initstr(&newval, "default"); + valptr = bond_opt_parse( + bond_opt_get(BOND_OPT_AD_ACTOR_SYS_PRIO), + &newval); + if (!valptr) { + pr_err("Error: No ad_actor_sys_prio default value"); + return -EINVAL; + } + ad_actor_sys_prio = valptr->value; + + valptr = bond_opt_parse(bond_opt_get(BOND_OPT_AD_USER_PORT_KEY), + &newval); + if (!valptr) { + pr_err("Error: No ad_user_port_key default value"); + return -EINVAL; + } + ad_user_port_key = valptr->value; + + bond_opt_initstr(&newval, "default"); + valptr = bond_opt_parse(bond_opt_get(BOND_OPT_TLB_DYNAMIC_LB), &newval); + if (!valptr) { + pr_err("Error: No tlb_dynamic_lb default value"); + return -EINVAL; + } + tlb_dynamic_lb = valptr->value; + + if (lp_interval == 0) { + pr_warn("Warning: ip_interval must be between 1 and %d, so it was reset to %d\n", + INT_MAX, BOND_ALB_DEFAULT_LP_INTERVAL); + lp_interval = BOND_ALB_DEFAULT_LP_INTERVAL; + } + + /* fill params struct with the proper values */ + params->mode = bond_mode; + params->xmit_policy = xmit_hashtype; + params->miimon = miimon; + params->num_peer_notif = num_peer_notif; + params->arp_interval = arp_interval; + params->arp_validate = arp_validate_value; + params->arp_all_targets = arp_all_targets_value; + params->missed_max = 2; + params->updelay = updelay; + params->downdelay = downdelay; + params->peer_notif_delay = 0; + params->lacp_active = 1; + params->lacp_fast = lacp_fast; + params->primary[0] = 0; + params->primary_reselect = primary_reselect_value; + params->fail_over_mac = fail_over_mac_value; + params->tx_queues = tx_queues; + params->all_slaves_active = all_slaves_active; + params->resend_igmp = resend_igmp; + params->min_links = min_links; + params->lp_interval = lp_interval; + params->packets_per_slave = packets_per_slave; + params->tlb_dynamic_lb = tlb_dynamic_lb; + params->ad_actor_sys_prio = ad_actor_sys_prio; + eth_zero_addr(params->ad_actor_system); + 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); + } else { + /* reciprocal_packets_per_slave is unused if + * packets_per_slave is 0 or 1, just initialize it + */ + params->reciprocal_packets_per_slave = + (struct reciprocal_value) { 0 }; + } + + if (primary) + strscpy_pad(params->primary, primary, sizeof(params->primary)); + + memcpy(params->arp_targets, arp_target, sizeof(arp_target)); +#if IS_ENABLED(CONFIG_IPV6) + memset(params->ns_targets, 0, sizeof(struct in6_addr) * BOND_MAX_NS_TARGETS); +#endif + + return 0; +} + +/* Called from registration process */ +static int bond_init(struct net_device *bond_dev) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct bond_net *bn = net_generic(dev_net(bond_dev), bond_net_id); + + netdev_dbg(bond_dev, "Begin bond_init\n"); + + bond->wq = alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM, + bond_dev->name); + if (!bond->wq) + return -ENOMEM; + + bond->notifier_ctx = false; + + spin_lock_init(&bond->stats_lock); + netdev_lockdep_set_classes(bond_dev); + + list_add_tail_rcu(&bond->bond_list, &bn->dev_list); + + bond_prepare_sysfs_group(bond); + + bond_debug_register(bond); + + /* Ensure valid dev_addr */ + if (is_zero_ether_addr(bond_dev->dev_addr) && + bond_dev->addr_assign_type == NET_ADDR_PERM) + eth_hw_addr_random(bond_dev); + + return 0; +} + +unsigned int bond_get_num_tx_queues(void) +{ + return tx_queues; +} + +/* Create a new bond based on the specified name and bonding parameters. + * If name is NULL, obtain a suitable "bond%d" name for us. + * Caller must NOT hold rtnl_lock; we need to release it here before we + * set up our sysfs entries. + */ +int bond_create(struct net *net, const char *name) +{ + struct net_device *bond_dev; + struct bonding *bond; + int res = -ENOMEM; + + rtnl_lock(); + + bond_dev = alloc_netdev_mq(sizeof(struct bonding), + name ? name : "bond%d", NET_NAME_UNKNOWN, + bond_setup, tx_queues); + if (!bond_dev) + goto out; + + bond = netdev_priv(bond_dev); + dev_net_set(bond_dev, net); + bond_dev->rtnl_link_ops = &bond_link_ops; + + res = register_netdevice(bond_dev); + if (res < 0) { + free_netdev(bond_dev); + goto out; + } + + netif_carrier_off(bond_dev); + + bond_work_init_all(bond); + +out: + rtnl_unlock(); + return res; +} + +static int __net_init bond_net_init(struct net *net) +{ + struct bond_net *bn = net_generic(net, bond_net_id); + + bn->net = net; + INIT_LIST_HEAD(&bn->dev_list); + + bond_create_proc_dir(bn); + bond_create_sysfs(bn); + + return 0; +} + +/* According to commit 69b0216ac255 ("bonding: fix bonding_masters + * race condition in bond unloading") we need to remove sysfs files + * before we remove our devices (done later in bond_net_exit_rtnl()) + */ +static void __net_exit bond_net_pre_exit(struct net *net) +{ + struct bond_net *bn = net_generic(net, bond_net_id); + + bond_destroy_sysfs(bn); +} + +static void __net_exit bond_net_exit_rtnl(struct net *net, + struct list_head *dev_kill_list) +{ + struct bond_net *bn = net_generic(net, bond_net_id); + struct bonding *bond, *tmp_bond; + + /* Kill off any bonds created after unregistering bond rtnl ops */ + list_for_each_entry_safe(bond, tmp_bond, &bn->dev_list, bond_list) + unregister_netdevice_queue(bond->dev, dev_kill_list); +} + +/* According to commit 23fa5c2caae0 ("bonding: destroy proc directory + * only after all bonds are gone") bond_destroy_proc_dir() is called + * after bond_net_exit_rtnl() has completed. + */ +static void __net_exit bond_net_exit_batch(struct list_head *net_list) +{ + struct bond_net *bn; + struct net *net; + + list_for_each_entry(net, net_list, exit_list) { + bn = net_generic(net, bond_net_id); + bond_destroy_proc_dir(bn); + } +} + +static struct pernet_operations bond_net_ops = { + .init = bond_net_init, + .pre_exit = bond_net_pre_exit, + .exit_rtnl = bond_net_exit_rtnl, + .exit_batch = bond_net_exit_batch, + .id = &bond_net_id, + .size = sizeof(struct bond_net), +}; + +static int __init bonding_init(void) +{ + int i; + int res; + + res = bond_check_params(&bonding_defaults); + if (res) + goto out; + + bond_create_debugfs(); + + res = register_pernet_subsys(&bond_net_ops); + if (res) + goto err_net_ops; + + res = bond_netlink_init(); + if (res) + goto err_link; + + for (i = 0; i < max_bonds; i++) { + res = bond_create(&init_net, NULL); + if (res) + goto err; + } + + skb_flow_dissector_init(&flow_keys_bonding, + flow_keys_bonding_keys, + ARRAY_SIZE(flow_keys_bonding_keys)); + + res = register_netdevice_notifier(&bond_netdev_notifier); + if (res) + goto err; +out: + return res; +err: + bond_netlink_fini(); +err_link: + unregister_pernet_subsys(&bond_net_ops); +err_net_ops: + bond_destroy_debugfs(); + goto out; + +} + +static void __exit bonding_exit(void) +{ + unregister_netdevice_notifier(&bond_netdev_notifier); + + bond_netlink_fini(); + unregister_pernet_subsys(&bond_net_ops); + + bond_destroy_debugfs(); + +#ifdef CONFIG_NET_POLL_CONTROLLER + /* Make sure we don't have an imbalance on our netpoll blocking */ + WARN_ON(static_branch_unlikely(&netpoll_block_tx)); +#endif +} + +module_init(bonding_init); +module_exit(bonding_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Ethernet Channel Bonding Driver"); +MODULE_AUTHOR("Thomas Davis, tadavis@lbl.gov and many others"); +MODULE_IMPORT_NS("NETDEV_INTERNAL"); diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c new file mode 100644 index 000000000..a28828b6e --- /dev/null +++ b/drivers/net/bonding/bond_netlink.c @@ -0,0 +1,1018 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * drivers/net/bond/bond_netlink.c - Netlink interface for bonding + * Copyright (c) 2013 Jiri Pirko + * Copyright (c) 2013 Scott Feldman + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static size_t bond_get_slave_size(const struct net_device *bond_dev, + const struct net_device *slave_dev) +{ + return nla_total_size(sizeof(u8)) + /* IFLA_BOND_SLAVE_STATE */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_SLAVE_MII_STATUS */ + nla_total_size(sizeof(u32)) + /* IFLA_BOND_SLAVE_LINK_FAILURE_COUNT */ + nla_total_size(MAX_ADDR_LEN) + /* IFLA_BOND_SLAVE_PERM_HWADDR */ + nla_total_size(sizeof(u16)) + /* IFLA_BOND_SLAVE_QUEUE_ID */ + nla_total_size(sizeof(u16)) + /* IFLA_BOND_SLAVE_AD_AGGREGATOR_ID */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE */ + nla_total_size(sizeof(u16)) + /* IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE */ + nla_total_size(sizeof(s32)) + /* IFLA_BOND_SLAVE_PRIO */ + nla_total_size(sizeof(u16)) + /* IFLA_BOND_SLAVE_ACTOR_PORT_PRIO */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_SLAVE_AD_CHURN_ACTOR_STATE */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_SLAVE_AD_CHURN_PARTNER_STATE */ + 0; +} + +static int bond_fill_slave_info(struct sk_buff *skb, + const struct net_device *bond_dev, + const struct net_device *slave_dev) +{ + struct slave *slave = bond_slave_get_rtnl(slave_dev); + + if (nla_put_u8(skb, IFLA_BOND_SLAVE_STATE, bond_slave_state(slave))) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_SLAVE_MII_STATUS, slave->link)) + goto nla_put_failure; + + if (nla_put_u32(skb, IFLA_BOND_SLAVE_LINK_FAILURE_COUNT, + slave->link_failure_count)) + goto nla_put_failure; + + if (nla_put(skb, IFLA_BOND_SLAVE_PERM_HWADDR, + slave_dev->addr_len, slave->perm_hwaddr)) + goto nla_put_failure; + + if (nla_put_u16(skb, IFLA_BOND_SLAVE_QUEUE_ID, + READ_ONCE(slave->queue_id))) + goto nla_put_failure; + + if (nla_put_s32(skb, IFLA_BOND_SLAVE_PRIO, slave->prio)) + goto nla_put_failure; + + if (BOND_MODE(slave->bond) == BOND_MODE_8023AD) { + const struct aggregator *agg; + const struct port *ad_port; + + ad_port = &SLAVE_AD_INFO(slave)->port; + rcu_read_lock(); + agg = rcu_dereference(SLAVE_AD_INFO(slave)->port.aggregator); + if (agg) { + if (nla_put_u16(skb, IFLA_BOND_SLAVE_AD_AGGREGATOR_ID, + agg->aggregator_identifier)) + goto nla_put_failure_rcu; + if (nla_put_u8(skb, + IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE, + ad_port->actor_oper_port_state)) + goto nla_put_failure_rcu; + if (nla_put_u16(skb, + IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE, + ad_port->partner_oper.port_state)) + goto nla_put_failure_rcu; + + if (nla_put_u8(skb, IFLA_BOND_SLAVE_AD_CHURN_ACTOR_STATE, + READ_ONCE(ad_port->sm_churn_actor_state))) + goto nla_put_failure_rcu; + if (nla_put_u8(skb, IFLA_BOND_SLAVE_AD_CHURN_PARTNER_STATE, + READ_ONCE(ad_port->sm_churn_partner_state))) + goto nla_put_failure_rcu; + } + rcu_read_unlock(); + + if (nla_put_u16(skb, IFLA_BOND_SLAVE_ACTOR_PORT_PRIO, + SLAVE_AD_INFO(slave)->port_priority)) + goto nla_put_failure; + } + + return 0; + +nla_put_failure_rcu: + rcu_read_unlock(); +nla_put_failure: + return -EMSGSIZE; +} + +/* Limit the max delay range to 300s */ +static const struct netlink_range_validation delay_range = { + .max = 300000, +}; + +static const struct nla_policy bond_policy[IFLA_BOND_MAX + 1] = { + [IFLA_BOND_MODE] = { .type = NLA_U8 }, + [IFLA_BOND_ACTIVE_SLAVE] = { .type = NLA_U32 }, + [IFLA_BOND_MIIMON] = { .type = NLA_U32 }, + [IFLA_BOND_UPDELAY] = { .type = NLA_U32 }, + [IFLA_BOND_DOWNDELAY] = { .type = NLA_U32 }, + [IFLA_BOND_USE_CARRIER] = { .type = NLA_U8 }, + [IFLA_BOND_ARP_INTERVAL] = { .type = NLA_U32 }, + [IFLA_BOND_ARP_IP_TARGET] = { .type = NLA_NESTED }, + [IFLA_BOND_ARP_VALIDATE] = { .type = NLA_U32 }, + [IFLA_BOND_ARP_ALL_TARGETS] = { .type = NLA_U32 }, + [IFLA_BOND_PRIMARY] = { .type = NLA_U32 }, + [IFLA_BOND_PRIMARY_RESELECT] = { .type = NLA_U8 }, + [IFLA_BOND_FAIL_OVER_MAC] = { .type = NLA_U8 }, + [IFLA_BOND_XMIT_HASH_POLICY] = { .type = NLA_U8 }, + [IFLA_BOND_RESEND_IGMP] = { .type = NLA_U32 }, + [IFLA_BOND_NUM_PEER_NOTIF] = { .type = NLA_U8 }, + [IFLA_BOND_ALL_SLAVES_ACTIVE] = { .type = NLA_U8 }, + [IFLA_BOND_MIN_LINKS] = { .type = NLA_U32 }, + [IFLA_BOND_LP_INTERVAL] = { .type = NLA_U32 }, + [IFLA_BOND_PACKETS_PER_SLAVE] = { .type = NLA_U32 }, + [IFLA_BOND_AD_LACP_ACTIVE] = { .type = NLA_U8 }, + [IFLA_BOND_AD_LACP_RATE] = { .type = NLA_U8 }, + [IFLA_BOND_AD_SELECT] = { .type = NLA_U8 }, + [IFLA_BOND_AD_INFO] = { .type = NLA_NESTED }, + [IFLA_BOND_AD_ACTOR_SYS_PRIO] = { .type = NLA_U16 }, + [IFLA_BOND_AD_USER_PORT_KEY] = { .type = NLA_U16 }, + [IFLA_BOND_AD_ACTOR_SYSTEM] = { .type = NLA_BINARY, + .len = ETH_ALEN }, + [IFLA_BOND_TLB_DYNAMIC_LB] = { .type = NLA_U8 }, + [IFLA_BOND_PEER_NOTIF_DELAY] = NLA_POLICY_FULL_RANGE(NLA_U32, &delay_range), + [IFLA_BOND_MISSED_MAX] = { .type = NLA_U8 }, + [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] = { + [IFLA_BOND_SLAVE_QUEUE_ID] = { .type = NLA_U16 }, + [IFLA_BOND_SLAVE_PRIO] = { .type = NLA_S32 }, + [IFLA_BOND_SLAVE_ACTOR_PORT_PRIO] = { .type = NLA_U16 }, +}; + +static int bond_validate(struct nlattr *tb[], struct nlattr *data[], + struct netlink_ext_ack *extack) +{ + if (tb[IFLA_ADDRESS]) { + if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN) + return -EINVAL; + if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS]))) + return -EADDRNOTAVAIL; + } + return 0; +} + +static int bond_slave_changelink(struct net_device *bond_dev, + struct net_device *slave_dev, + struct nlattr *tb[], struct nlattr *data[], + struct netlink_ext_ack *extack) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct bond_opt_value newval; + int err; + + if (!data) + return 0; + + if (data[IFLA_BOND_SLAVE_QUEUE_ID]) { + u16 queue_id = nla_get_u16(data[IFLA_BOND_SLAVE_QUEUE_ID]); + char queue_id_str[IFNAMSIZ + 7]; + + /* queue_id option setting expects slave_name:queue_id */ + snprintf(queue_id_str, sizeof(queue_id_str), "%s:%u\n", + slave_dev->name, queue_id); + bond_opt_initstr(&newval, queue_id_str); + err = __bond_opt_set(bond, BOND_OPT_QUEUE_ID, &newval, + data[IFLA_BOND_SLAVE_QUEUE_ID], extack); + if (err) + return err; + } + + if (data[IFLA_BOND_SLAVE_PRIO]) { + int prio = nla_get_s32(data[IFLA_BOND_SLAVE_PRIO]); + + bond_opt_slave_initval(&newval, &slave_dev, prio); + err = __bond_opt_set(bond, BOND_OPT_PRIO, &newval, + data[IFLA_BOND_SLAVE_PRIO], extack); + if (err) + return err; + } + + if (data[IFLA_BOND_SLAVE_ACTOR_PORT_PRIO]) { + u16 ad_prio = nla_get_u16(data[IFLA_BOND_SLAVE_ACTOR_PORT_PRIO]); + + bond_opt_slave_initval(&newval, &slave_dev, ad_prio); + err = __bond_opt_set(bond, BOND_OPT_ACTOR_PORT_PRIO, &newval, + data[IFLA_BOND_SLAVE_ACTOR_PORT_PRIO], extack); + if (err) + return err; + } + + return 0; +} + +static int bond_changelink(struct net_device *bond_dev, struct nlattr *tb[], + struct nlattr *data[], + struct netlink_ext_ack *extack) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct bond_opt_value newval; + int miimon = 0; + int err = 0; + + if (!data) + return 0; + + if (data[IFLA_BOND_MODE]) { + int mode = nla_get_u8(data[IFLA_BOND_MODE]); + + bond_opt_initval(&newval, mode); + err = __bond_opt_set(bond, BOND_OPT_MODE, &newval, + data[IFLA_BOND_MODE], extack); + if (err) + return err; + } + if (data[IFLA_BOND_ACTIVE_SLAVE]) { + int ifindex = nla_get_u32(data[IFLA_BOND_ACTIVE_SLAVE]); + struct net_device *slave_dev; + char *active_slave = ""; + + if (ifindex != 0) { + slave_dev = __dev_get_by_index(dev_net(bond_dev), + ifindex); + if (!slave_dev) + return -ENODEV; + active_slave = slave_dev->name; + } + bond_opt_initstr(&newval, active_slave); + err = __bond_opt_set(bond, BOND_OPT_ACTIVE_SLAVE, &newval, + data[IFLA_BOND_ACTIVE_SLAVE], extack); + if (err) + return err; + } + if (data[IFLA_BOND_MIIMON]) { + miimon = nla_get_u32(data[IFLA_BOND_MIIMON]); + + bond_opt_initval(&newval, miimon); + err = __bond_opt_set(bond, BOND_OPT_MIIMON, &newval, + data[IFLA_BOND_MIIMON], extack); + if (err) + return err; + } + if (data[IFLA_BOND_UPDELAY]) { + int updelay = nla_get_u32(data[IFLA_BOND_UPDELAY]); + + bond_opt_initval(&newval, updelay); + err = __bond_opt_set(bond, BOND_OPT_UPDELAY, &newval, + data[IFLA_BOND_UPDELAY], extack); + if (err) + return err; + } + if (data[IFLA_BOND_DOWNDELAY]) { + int downdelay = nla_get_u32(data[IFLA_BOND_DOWNDELAY]); + + bond_opt_initval(&newval, downdelay); + err = __bond_opt_set(bond, BOND_OPT_DOWNDELAY, &newval, + data[IFLA_BOND_DOWNDELAY], extack); + if (err) + return err; + } + if (data[IFLA_BOND_PEER_NOTIF_DELAY]) { + int delay = nla_get_u32(data[IFLA_BOND_PEER_NOTIF_DELAY]); + + bond_opt_initval(&newval, delay); + err = __bond_opt_set(bond, BOND_OPT_PEER_NOTIF_DELAY, &newval, + data[IFLA_BOND_PEER_NOTIF_DELAY], extack); + if (err) + return err; + } + if (data[IFLA_BOND_USE_CARRIER]) { + if (nla_get_u8(data[IFLA_BOND_USE_CARRIER]) != 1) { + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_BOND_USE_CARRIER], + "option obsolete, use_carrier cannot be disabled"); + return -EINVAL; + } + } + if (data[IFLA_BOND_ARP_INTERVAL]) { + int arp_interval = nla_get_u32(data[IFLA_BOND_ARP_INTERVAL]); + + if (arp_interval && miimon) { + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_BOND_ARP_INTERVAL], + "ARP monitoring cannot be used with MII monitoring"); + return -EINVAL; + } + + bond_opt_initval(&newval, arp_interval); + err = __bond_opt_set(bond, BOND_OPT_ARP_INTERVAL, &newval, + data[IFLA_BOND_ARP_INTERVAL], extack); + if (err) + return err; + } + if (data[IFLA_BOND_ARP_IP_TARGET]) { + struct nlattr *attr; + int i = 0, rem; + + bond_option_arp_ip_targets_clear(bond); + nla_for_each_nested(attr, data[IFLA_BOND_ARP_IP_TARGET], rem) { + __be32 target; + + if (nla_len(attr) < sizeof(target)) + return -EINVAL; + + target = nla_get_be32(attr); + + bond_opt_initval(&newval, (__force u64)target); + err = __bond_opt_set(bond, BOND_OPT_ARP_TARGETS, + &newval, + data[IFLA_BOND_ARP_IP_TARGET], + extack); + if (err) + break; + i++; + } + if (i == 0 && bond->params.arp_interval) + netdev_warn(bond->dev, "Removing last arp target with arp_interval on\n"); + if (err) + return err; + } +#if IS_ENABLED(CONFIG_IPV6) + if (data[IFLA_BOND_NS_IP6_TARGET]) { + struct nlattr *attr; + int i = 0, rem; + + bond_option_ns_ip6_targets_clear(bond); + nla_for_each_nested(attr, data[IFLA_BOND_NS_IP6_TARGET], rem) { + struct in6_addr addr6; + + if (nla_len(attr) < sizeof(addr6)) { + NL_SET_ERR_MSG(extack, "Invalid IPv6 address"); + return -EINVAL; + } + + addr6 = nla_get_in6_addr(attr); + + bond_opt_initextra(&newval, &addr6, sizeof(addr6)); + err = __bond_opt_set(bond, BOND_OPT_NS_TARGETS, + &newval, + data[IFLA_BOND_NS_IP6_TARGET], + extack); + if (err) + break; + i++; + } + if (i == 0 && bond->params.arp_interval) + netdev_warn(bond->dev, "Removing last ns target with arp_interval on\n"); + if (err) + return err; + } +#endif + if (data[IFLA_BOND_ARP_VALIDATE]) { + int arp_validate = nla_get_u32(data[IFLA_BOND_ARP_VALIDATE]); + + if (arp_validate && miimon) { + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_BOND_ARP_VALIDATE], + "ARP validating cannot be used with MII monitoring"); + return -EINVAL; + } + + bond_opt_initval(&newval, arp_validate); + err = __bond_opt_set(bond, BOND_OPT_ARP_VALIDATE, &newval, + data[IFLA_BOND_ARP_VALIDATE], extack); + if (err) + return err; + } + if (data[IFLA_BOND_ARP_ALL_TARGETS]) { + int arp_all_targets = + nla_get_u32(data[IFLA_BOND_ARP_ALL_TARGETS]); + + bond_opt_initval(&newval, arp_all_targets); + err = __bond_opt_set(bond, BOND_OPT_ARP_ALL_TARGETS, &newval, + data[IFLA_BOND_ARP_ALL_TARGETS], extack); + if (err) + return err; + } + if (data[IFLA_BOND_PRIMARY]) { + int ifindex = nla_get_u32(data[IFLA_BOND_PRIMARY]); + struct net_device *dev; + char *primary = ""; + + dev = __dev_get_by_index(dev_net(bond_dev), ifindex); + if (dev) + primary = dev->name; + + bond_opt_initstr(&newval, primary); + err = __bond_opt_set(bond, BOND_OPT_PRIMARY, &newval, + data[IFLA_BOND_PRIMARY], extack); + if (err) + return err; + } + if (data[IFLA_BOND_PRIMARY_RESELECT]) { + int primary_reselect = + nla_get_u8(data[IFLA_BOND_PRIMARY_RESELECT]); + + bond_opt_initval(&newval, primary_reselect); + err = __bond_opt_set(bond, BOND_OPT_PRIMARY_RESELECT, &newval, + data[IFLA_BOND_PRIMARY_RESELECT], extack); + if (err) + return err; + } + if (data[IFLA_BOND_FAIL_OVER_MAC]) { + int fail_over_mac = + nla_get_u8(data[IFLA_BOND_FAIL_OVER_MAC]); + + bond_opt_initval(&newval, fail_over_mac); + err = __bond_opt_set(bond, BOND_OPT_FAIL_OVER_MAC, &newval, + data[IFLA_BOND_FAIL_OVER_MAC], extack); + if (err) + return err; + } + if (data[IFLA_BOND_XMIT_HASH_POLICY]) { + int xmit_hash_policy = + nla_get_u8(data[IFLA_BOND_XMIT_HASH_POLICY]); + + bond_opt_initval(&newval, xmit_hash_policy); + err = __bond_opt_set(bond, BOND_OPT_XMIT_HASH, &newval, + data[IFLA_BOND_XMIT_HASH_POLICY], extack); + if (err) + return err; + } + if (data[IFLA_BOND_RESEND_IGMP]) { + int resend_igmp = + nla_get_u32(data[IFLA_BOND_RESEND_IGMP]); + + bond_opt_initval(&newval, resend_igmp); + err = __bond_opt_set(bond, BOND_OPT_RESEND_IGMP, &newval, + data[IFLA_BOND_RESEND_IGMP], extack); + if (err) + return err; + } + if (data[IFLA_BOND_NUM_PEER_NOTIF]) { + int num_peer_notif = + nla_get_u8(data[IFLA_BOND_NUM_PEER_NOTIF]); + + bond_opt_initval(&newval, num_peer_notif); + err = __bond_opt_set(bond, BOND_OPT_NUM_PEER_NOTIF, &newval, + data[IFLA_BOND_NUM_PEER_NOTIF], extack); + if (err) + return err; + } + if (data[IFLA_BOND_ALL_SLAVES_ACTIVE]) { + int all_slaves_active = + nla_get_u8(data[IFLA_BOND_ALL_SLAVES_ACTIVE]); + + bond_opt_initval(&newval, all_slaves_active); + err = __bond_opt_set(bond, BOND_OPT_ALL_SLAVES_ACTIVE, &newval, + data[IFLA_BOND_ALL_SLAVES_ACTIVE], extack); + if (err) + return err; + } + if (data[IFLA_BOND_MIN_LINKS]) { + int min_links = + nla_get_u32(data[IFLA_BOND_MIN_LINKS]); + + bond_opt_initval(&newval, min_links); + err = __bond_opt_set(bond, BOND_OPT_MINLINKS, &newval, + data[IFLA_BOND_MIN_LINKS], extack); + if (err) + return err; + } + if (data[IFLA_BOND_LP_INTERVAL]) { + int lp_interval = + nla_get_u32(data[IFLA_BOND_LP_INTERVAL]); + + bond_opt_initval(&newval, lp_interval); + err = __bond_opt_set(bond, BOND_OPT_LP_INTERVAL, &newval, + data[IFLA_BOND_LP_INTERVAL], extack); + if (err) + return err; + } + if (data[IFLA_BOND_PACKETS_PER_SLAVE]) { + int packets_per_slave = + nla_get_u32(data[IFLA_BOND_PACKETS_PER_SLAVE]); + + bond_opt_initval(&newval, packets_per_slave); + err = __bond_opt_set(bond, BOND_OPT_PACKETS_PER_SLAVE, &newval, + data[IFLA_BOND_PACKETS_PER_SLAVE], extack); + if (err) + return err; + } + + if (data[IFLA_BOND_AD_LACP_ACTIVE]) { + int lacp_active = nla_get_u8(data[IFLA_BOND_AD_LACP_ACTIVE]); + + bond_opt_initval(&newval, lacp_active); + err = __bond_opt_set(bond, BOND_OPT_LACP_ACTIVE, &newval, + data[IFLA_BOND_AD_LACP_ACTIVE], extack); + if (err) + return err; + } + + if (data[IFLA_BOND_AD_LACP_RATE]) { + int lacp_rate = + nla_get_u8(data[IFLA_BOND_AD_LACP_RATE]); + + bond_opt_initval(&newval, lacp_rate); + err = __bond_opt_set(bond, BOND_OPT_LACP_RATE, &newval, + data[IFLA_BOND_AD_LACP_RATE], extack); + if (err) + return err; + } + if (data[IFLA_BOND_AD_SELECT]) { + int ad_select = + nla_get_u8(data[IFLA_BOND_AD_SELECT]); + + bond_opt_initval(&newval, ad_select); + err = __bond_opt_set(bond, BOND_OPT_AD_SELECT, &newval, + data[IFLA_BOND_AD_SELECT], extack); + if (err) + return err; + } + if (data[IFLA_BOND_AD_ACTOR_SYS_PRIO]) { + int actor_sys_prio = + nla_get_u16(data[IFLA_BOND_AD_ACTOR_SYS_PRIO]); + + bond_opt_initval(&newval, actor_sys_prio); + err = __bond_opt_set(bond, BOND_OPT_AD_ACTOR_SYS_PRIO, &newval, + data[IFLA_BOND_AD_ACTOR_SYS_PRIO], extack); + if (err) + return err; + } + if (data[IFLA_BOND_AD_USER_PORT_KEY]) { + int port_key = + nla_get_u16(data[IFLA_BOND_AD_USER_PORT_KEY]); + + bond_opt_initval(&newval, port_key); + err = __bond_opt_set(bond, BOND_OPT_AD_USER_PORT_KEY, &newval, + data[IFLA_BOND_AD_USER_PORT_KEY], extack); + if (err) + return err; + } + if (data[IFLA_BOND_AD_ACTOR_SYSTEM]) { + if (nla_len(data[IFLA_BOND_AD_ACTOR_SYSTEM]) != ETH_ALEN) + return -EINVAL; + + bond_opt_initval(&newval, + nla_get_u64(data[IFLA_BOND_AD_ACTOR_SYSTEM])); + err = __bond_opt_set(bond, BOND_OPT_AD_ACTOR_SYSTEM, &newval, + data[IFLA_BOND_AD_ACTOR_SYSTEM], extack); + if (err) + return err; + } + if (data[IFLA_BOND_TLB_DYNAMIC_LB]) { + int dynamic_lb = nla_get_u8(data[IFLA_BOND_TLB_DYNAMIC_LB]); + + bond_opt_initval(&newval, dynamic_lb); + err = __bond_opt_set(bond, BOND_OPT_TLB_DYNAMIC_LB, &newval, + data[IFLA_BOND_TLB_DYNAMIC_LB], extack); + if (err) + return err; + } + + if (data[IFLA_BOND_MISSED_MAX]) { + int missed_max = nla_get_u8(data[IFLA_BOND_MISSED_MAX]); + + bond_opt_initval(&newval, missed_max); + err = __bond_opt_set(bond, BOND_OPT_MISSED_MAX, &newval, + data[IFLA_BOND_MISSED_MAX], extack); + if (err) + return err; + } + + if (data[IFLA_BOND_COUPLED_CONTROL]) { + int coupled_control = nla_get_u8(data[IFLA_BOND_COUPLED_CONTROL]); + + bond_opt_initval(&newval, coupled_control); + err = __bond_opt_set(bond, BOND_OPT_COUPLED_CONTROL, &newval, + data[IFLA_BOND_COUPLED_CONTROL], extack); + if (err) + return err; + } + + if (data[IFLA_BOND_BROADCAST_NEIGH]) { + int broadcast_neigh = nla_get_u8(data[IFLA_BOND_BROADCAST_NEIGH]); + + bond_opt_initval(&newval, broadcast_neigh); + err = __bond_opt_set(bond, BOND_OPT_BROADCAST_NEIGH, &newval, + data[IFLA_BOND_BROADCAST_NEIGH], extack); + if (err) + 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; +} + +static int bond_newlink(struct net_device *bond_dev, + struct rtnl_newlink_params *params, + struct netlink_ext_ack *extack) +{ + struct bonding *bond = netdev_priv(bond_dev); + struct nlattr **data = params->data; + struct nlattr **tb = params->tb; + int err; + + err = register_netdevice(bond_dev); + if (err) + return err; + + netif_carrier_off(bond_dev); + bond_work_init_all(bond); + + err = bond_changelink(bond_dev, tb, data, extack); + if (err) { + bond_work_cancel_all(bond); + unregister_netdevice(bond_dev); + } + + return err; +} + +static size_t bond_get_size(const struct net_device *bond_dev) +{ + return nla_total_size(sizeof(u8)) + /* IFLA_BOND_MODE */ + nla_total_size(sizeof(u32)) + /* IFLA_BOND_ACTIVE_SLAVE */ + nla_total_size(sizeof(u32)) + /* IFLA_BOND_MIIMON */ + nla_total_size(sizeof(u32)) + /* IFLA_BOND_UPDELAY */ + nla_total_size(sizeof(u32)) + /* IFLA_BOND_DOWNDELAY */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_USE_CARRIER */ + nla_total_size(sizeof(u32)) + /* IFLA_BOND_ARP_INTERVAL */ + /* IFLA_BOND_ARP_IP_TARGET */ + nla_total_size(sizeof(struct nlattr)) + + nla_total_size(sizeof(u32)) * BOND_MAX_ARP_TARGETS + + nla_total_size(sizeof(u32)) + /* IFLA_BOND_ARP_VALIDATE */ + nla_total_size(sizeof(u32)) + /* IFLA_BOND_ARP_ALL_TARGETS */ + nla_total_size(sizeof(u32)) + /* IFLA_BOND_PRIMARY */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_PRIMARY_RESELECT */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_FAIL_OVER_MAC */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_XMIT_HASH_POLICY */ + nla_total_size(sizeof(u32)) + /* IFLA_BOND_RESEND_IGMP */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_NUM_PEER_NOTIF */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_ALL_SLAVES_ACTIVE */ + nla_total_size(sizeof(u32)) + /* IFLA_BOND_MIN_LINKS */ + nla_total_size(sizeof(u32)) + /* IFLA_BOND_LP_INTERVAL */ + nla_total_size(sizeof(u32)) + /* IFLA_BOND_PACKETS_PER_SLAVE */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_AD_LACP_ACTIVE */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_AD_LACP_RATE */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_AD_SELECT */ + nla_total_size(sizeof(struct nlattr)) + /* IFLA_BOND_AD_INFO */ + nla_total_size(sizeof(u16)) + /* IFLA_BOND_AD_INFO_AGGREGATOR */ + nla_total_size(sizeof(u16)) + /* IFLA_BOND_AD_INFO_NUM_PORTS */ + nla_total_size(sizeof(u16)) + /* IFLA_BOND_AD_INFO_ACTOR_KEY */ + nla_total_size(sizeof(u16)) + /* IFLA_BOND_AD_INFO_PARTNER_KEY*/ + nla_total_size(ETH_ALEN) + /* IFLA_BOND_AD_INFO_PARTNER_MAC*/ + nla_total_size(sizeof(u16)) + /* IFLA_BOND_AD_ACTOR_SYS_PRIO */ + nla_total_size(sizeof(u16)) + /* IFLA_BOND_AD_USER_PORT_KEY */ + nla_total_size(ETH_ALEN) + /* IFLA_BOND_AD_ACTOR_SYSTEM */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_TLB_DYNAMIC_LB */ + nla_total_size(sizeof(u32)) + /* IFLA_BOND_PEER_NOTIF_DELAY */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_MISSED_MAX */ + /* IFLA_BOND_NS_IP6_TARGET */ + nla_total_size(sizeof(struct nlattr)) + + 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; +} + +static int bond_option_active_slave_get_ifindex_rcu(const struct bonding *bond) +{ + const struct net_device *dev = NULL; + const struct slave *slave; + + slave = rcu_dereference(bond->curr_active_slave); + if (slave) + dev = slave->dev; + return dev ? dev->ifindex : 0; +} + +static int bond_fill_info(struct sk_buff *skb, + const struct net_device *bond_dev) +{ + const struct bonding *bond = netdev_priv(bond_dev); + int i, targets_added, miimon, mode; + const struct slave *primary; + struct nlattr *targets; + + rcu_read_lock(); + mode = READ_ONCE(bond->params.mode); + if (nla_put_u8(skb, IFLA_BOND_MODE, mode)) + goto nla_put_failure; + + if (bond_mode_uses_primary(mode)) { + int ifindex = bond_option_active_slave_get_ifindex_rcu(bond); + + if (ifindex && nla_put_u32(skb, IFLA_BOND_ACTIVE_SLAVE, ifindex)) + goto nla_put_failure; + } + + miimon = READ_ONCE(bond->params.miimon); + if (nla_put_u32(skb, IFLA_BOND_MIIMON, miimon)) + goto nla_put_failure; + + if (nla_put_u32(skb, IFLA_BOND_UPDELAY, + READ_ONCE(bond->params.updelay) * miimon)) + goto nla_put_failure; + + if (nla_put_u32(skb, IFLA_BOND_DOWNDELAY, + READ_ONCE(bond->params.downdelay) * miimon)) + goto nla_put_failure; + + if (nla_put_u32(skb, IFLA_BOND_PEER_NOTIF_DELAY, + READ_ONCE(bond->params.peer_notif_delay) * miimon)) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_USE_CARRIER, 1)) + goto nla_put_failure; + + if (nla_put_u32(skb, IFLA_BOND_ARP_INTERVAL, + READ_ONCE(bond->params.arp_interval))) + goto nla_put_failure; + + targets = nla_nest_start_noflag(skb, IFLA_BOND_ARP_IP_TARGET); + if (!targets) + goto nla_put_failure; + + targets_added = 0; + for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) { + __be32 t = READ_ONCE(bond->params.arp_targets[i]); + + if (t) { + if (nla_put_be32(skb, i, t)) + goto nla_put_failure; + targets_added = 1; + } + } + + if (targets_added) + nla_nest_end(skb, targets); + else + nla_nest_cancel(skb, targets); + + if (nla_put_u32(skb, IFLA_BOND_ARP_VALIDATE, + READ_ONCE(bond->params.arp_validate))) + goto nla_put_failure; + + if (nla_put_u32(skb, IFLA_BOND_ARP_ALL_TARGETS, + READ_ONCE(bond->params.arp_all_targets))) + goto nla_put_failure; + +#if IS_ENABLED(CONFIG_IPV6) + targets = nla_nest_start(skb, IFLA_BOND_NS_IP6_TARGET); + if (!targets) + goto nla_put_failure; + + targets_added = 0; + for (i = 0; i < BOND_MAX_NS_TARGETS; i++) { + /* Note: IPv6 addresses can not be read in an atomic READ_ONCE() yet. + * We accept this minor race for the moment. + */ + if (!ipv6_addr_any(&bond->params.ns_targets[i])) { + if (nla_put_in6_addr(skb, i, &bond->params.ns_targets[i])) + goto nla_put_failure; + targets_added = 1; + } + } + + if (targets_added) + nla_nest_end(skb, targets); + else + nla_nest_cancel(skb, targets); +#endif + + primary = rcu_dereference(bond->primary_slave); + if (primary && + nla_put_u32(skb, IFLA_BOND_PRIMARY, primary->dev->ifindex)) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_PRIMARY_RESELECT, + READ_ONCE(bond->params.primary_reselect))) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_FAIL_OVER_MAC, + READ_ONCE(bond->params.fail_over_mac))) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_XMIT_HASH_POLICY, + READ_ONCE(bond->params.xmit_policy))) + goto nla_put_failure; + + if (nla_put_u32(skb, IFLA_BOND_RESEND_IGMP, + READ_ONCE(bond->params.resend_igmp))) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_NUM_PEER_NOTIF, + READ_ONCE(bond->params.num_peer_notif))) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_ALL_SLAVES_ACTIVE, + READ_ONCE(bond->params.all_slaves_active))) + goto nla_put_failure; + + if (nla_put_u32(skb, IFLA_BOND_MIN_LINKS, + READ_ONCE(bond->params.min_links))) + goto nla_put_failure; + + if (nla_put_u32(skb, IFLA_BOND_LP_INTERVAL, + READ_ONCE(bond->params.lp_interval))) + goto nla_put_failure; + + if (nla_put_u32(skb, IFLA_BOND_PACKETS_PER_SLAVE, + READ_ONCE(bond->params.packets_per_slave))) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_AD_LACP_ACTIVE, + READ_ONCE(bond->params.lacp_active))) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_AD_LACP_RATE, + READ_ONCE(bond->params.lacp_fast))) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_AD_SELECT, + READ_ONCE(bond->params.ad_select))) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_TLB_DYNAMIC_LB, + READ_ONCE(bond->params.tlb_dynamic_lb))) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_MISSED_MAX, + READ_ONCE(bond->params.missed_max))) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_COUPLED_CONTROL, + READ_ONCE(bond->params.coupled_control))) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_BROADCAST_NEIGH, + READ_ONCE(bond->params.broadcast_neighbor))) + goto nla_put_failure; + + if (nla_put_u8(skb, IFLA_BOND_LACP_STRICT, + READ_ONCE(bond->params.lacp_strict))) + goto nla_put_failure; + + if (mode == BOND_MODE_8023AD) { + struct ad_info info; + + if (capable(CAP_NET_ADMIN)) { + if (nla_put_u16(skb, IFLA_BOND_AD_ACTOR_SYS_PRIO, + READ_ONCE(bond->params.ad_actor_sys_prio))) + goto nla_put_failure; + + if (nla_put_u16(skb, IFLA_BOND_AD_USER_PORT_KEY, + READ_ONCE(bond->params.ad_user_port_key))) + goto nla_put_failure; + + /* Small race here, this is a minor trade off. */ + if (nla_put(skb, IFLA_BOND_AD_ACTOR_SYSTEM, + ETH_ALEN, &bond->params.ad_actor_system)) + goto nla_put_failure; + } + if (!__bond_3ad_get_active_agg_info(bond, &info)) { + struct nlattr *nest; + + nest = nla_nest_start_noflag(skb, IFLA_BOND_AD_INFO); + if (!nest) + goto nla_put_failure; + + if (nla_put_u16(skb, IFLA_BOND_AD_INFO_AGGREGATOR, + info.aggregator_id)) + goto nla_put_failure; + if (nla_put_u16(skb, IFLA_BOND_AD_INFO_NUM_PORTS, + info.ports)) + goto nla_put_failure; + if (nla_put_u16(skb, IFLA_BOND_AD_INFO_ACTOR_KEY, + info.actor_key)) + goto nla_put_failure; + if (nla_put_u16(skb, IFLA_BOND_AD_INFO_PARTNER_KEY, + info.partner_key)) + goto nla_put_failure; + if (nla_put(skb, IFLA_BOND_AD_INFO_PARTNER_MAC, + sizeof(info.partner_system), + &info.partner_system)) + goto nla_put_failure; + + nla_nest_end(skb, nest); + } + } + + rcu_read_unlock(); + return 0; + +nla_put_failure: + rcu_read_unlock(); + return -EMSGSIZE; +} + +static size_t bond_get_linkxstats_size(const struct net_device *dev, int attr) +{ + switch (attr) { + case IFLA_STATS_LINK_XSTATS: + case IFLA_STATS_LINK_XSTATS_SLAVE: + break; + default: + return 0; + } + + return bond_3ad_stats_size() + nla_total_size(0); +} + +static int bond_fill_linkxstats(struct sk_buff *skb, + const struct net_device *dev, + int *prividx, int attr) +{ + struct nlattr *nla __maybe_unused; + struct slave *slave = NULL; + struct nlattr *nest, *nest2; + struct bonding *bond; + + switch (attr) { + case IFLA_STATS_LINK_XSTATS: + bond = netdev_priv(dev); + break; + case IFLA_STATS_LINK_XSTATS_SLAVE: + slave = bond_slave_get_rtnl(dev); + if (!slave) + return 0; + bond = slave->bond; + break; + default: + return -EINVAL; + } + + nest = nla_nest_start_noflag(skb, LINK_XSTATS_TYPE_BOND); + if (!nest) + return -EMSGSIZE; + if (BOND_MODE(bond) == BOND_MODE_8023AD) { + struct bond_3ad_stats *stats; + + if (slave) + stats = &SLAVE_AD_INFO(slave)->stats; + else + stats = &BOND_AD_INFO(bond).stats; + + nest2 = nla_nest_start_noflag(skb, BOND_XSTATS_3AD); + if (!nest2) { + nla_nest_end(skb, nest); + return -EMSGSIZE; + } + + if (bond_3ad_stats_fill(skb, stats)) { + nla_nest_cancel(skb, nest2); + nla_nest_end(skb, nest); + return -EMSGSIZE; + } + nla_nest_end(skb, nest2); + } + nla_nest_end(skb, nest); + + return 0; +} + +struct rtnl_link_ops bond_link_ops __read_mostly = { + .kind = "bond", + .priv_size = sizeof(struct bonding), + .setup = bond_setup, + .maxtype = IFLA_BOND_MAX, + .policy = bond_policy, + .validate = bond_validate, + .newlink = bond_newlink, + .changelink = bond_changelink, + .get_size = bond_get_size, + .fill_info = bond_fill_info, + .get_num_tx_queues = bond_get_num_tx_queues, + .get_num_rx_queues = bond_get_num_tx_queues, /* Use the same number + as for TX queues */ + .fill_linkxstats = bond_fill_linkxstats, + .get_linkxstats_size = bond_get_linkxstats_size, + .slave_maxtype = IFLA_BOND_SLAVE_MAX, + .slave_policy = bond_slave_policy, + .slave_changelink = bond_slave_changelink, + .get_slave_size = bond_get_slave_size, + .fill_slave_info = bond_fill_slave_info, +}; + +int __init bond_netlink_init(void) +{ + return rtnl_link_register(&bond_link_ops); +} + +void bond_netlink_fini(void) +{ + rtnl_link_unregister(&bond_link_ops); +} + +MODULE_ALIAS_RTNL_LINK("bond"); diff --git a/drivers/net/bonding/bond_options.c b/drivers/net/bonding/bond_options.c new file mode 100644 index 000000000..36b8d8938 --- /dev/null +++ b/drivers/net/bonding/bond_options.c @@ -0,0 +1,1941 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * drivers/net/bond/bond_options.c - bonding options + * Copyright (c) 2013 Jiri Pirko + * Copyright (c) 2013 Scott Feldman + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static int bond_option_active_slave_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_miimon_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_updelay_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_downdelay_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_peer_notif_delay_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_use_carrier_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_arp_interval_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_arp_ip_target_add(struct bonding *bond, __be32 target); +static int bond_option_arp_ip_target_rem(struct bonding *bond, __be32 target); +static int bond_option_arp_ip_targets_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_ns_ip6_targets_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_arp_validate_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_arp_all_targets_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_prio_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_primary_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_primary_reselect_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_fail_over_mac_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_xmit_hash_policy_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_resend_igmp_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_num_peer_notif_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_all_slaves_active_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_min_links_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_lp_interval_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_pps_set(struct bonding *bond, + const struct bond_opt_value *newval); +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, + const struct bond_opt_value *newval); +static int bond_option_mode_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_slaves_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_tlb_dynamic_lb_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_ad_actor_sys_prio_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_actor_port_prio_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_ad_actor_system_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_ad_user_port_key_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_missed_max_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_coupled_control_set(struct bonding *bond, + const struct bond_opt_value *newval); +static int bond_option_broadcast_neigh_set(struct bonding *bond, + const struct bond_opt_value *newval); + +static const struct bond_opt_value bond_mode_tbl[] = { + { "balance-rr", BOND_MODE_ROUNDROBIN, BOND_VALFLAG_DEFAULT}, + { "active-backup", BOND_MODE_ACTIVEBACKUP, 0}, + { "balance-xor", BOND_MODE_XOR, 0}, + { "broadcast", BOND_MODE_BROADCAST, 0}, + { "802.3ad", BOND_MODE_8023AD, 0}, + { "balance-tlb", BOND_MODE_TLB, 0}, + { "balance-alb", BOND_MODE_ALB, 0}, + { NULL, -1, 0}, +}; + +static const struct bond_opt_value bond_pps_tbl[] = { + { "default", 1, BOND_VALFLAG_DEFAULT}, + { "maxval", USHRT_MAX, BOND_VALFLAG_MAX}, + { NULL, -1, 0}, +}; + +static const struct bond_opt_value bond_xmit_hashtype_tbl[] = { + { "layer2", BOND_XMIT_POLICY_LAYER2, BOND_VALFLAG_DEFAULT}, + { "layer3+4", BOND_XMIT_POLICY_LAYER34, 0}, + { "layer2+3", BOND_XMIT_POLICY_LAYER23, 0}, + { "encap2+3", BOND_XMIT_POLICY_ENCAP23, 0}, + { "encap3+4", BOND_XMIT_POLICY_ENCAP34, 0}, + { "vlan+srcmac", BOND_XMIT_POLICY_VLAN_SRCMAC, 0}, + { NULL, -1, 0}, +}; + +static const struct bond_opt_value bond_arp_validate_tbl[] = { + { "none", BOND_ARP_VALIDATE_NONE, BOND_VALFLAG_DEFAULT}, + { "active", BOND_ARP_VALIDATE_ACTIVE, 0}, + { "backup", BOND_ARP_VALIDATE_BACKUP, 0}, + { "all", BOND_ARP_VALIDATE_ALL, 0}, + { "filter", BOND_ARP_FILTER, 0}, + { "filter_active", BOND_ARP_FILTER_ACTIVE, 0}, + { "filter_backup", BOND_ARP_FILTER_BACKUP, 0}, + { NULL, -1, 0}, +}; + +static const struct bond_opt_value bond_arp_all_targets_tbl[] = { + { "any", BOND_ARP_TARGETS_ANY, BOND_VALFLAG_DEFAULT}, + { "all", BOND_ARP_TARGETS_ALL, 0}, + { NULL, -1, 0}, +}; + +static const struct bond_opt_value bond_fail_over_mac_tbl[] = { + { "none", BOND_FOM_NONE, BOND_VALFLAG_DEFAULT}, + { "active", BOND_FOM_ACTIVE, 0}, + { "follow", BOND_FOM_FOLLOW, 0}, + { NULL, -1, 0}, +}; + +static const struct bond_opt_value bond_intmax_tbl[] = { + { "off", 0, BOND_VALFLAG_DEFAULT}, + { "maxval", INT_MAX, BOND_VALFLAG_MAX}, + { NULL, -1, 0} +}; + +static const struct bond_opt_value bond_lacp_active[] = { + { "off", 0, 0}, + { "on", 1, BOND_VALFLAG_DEFAULT}, + { NULL, -1, 0} +}; + +static const struct bond_opt_value bond_lacp_rate_tbl[] = { + { "slow", AD_LACP_SLOW, 0}, + { "fast", AD_LACP_FAST, 0}, + { 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}, + { "count", BOND_AD_COUNT, 0}, + { "actor_port_prio", BOND_AD_PRIO, 0}, + { NULL, -1, 0}, +}; + +static const struct bond_opt_value bond_num_peer_notif_tbl[] = { + { "off", 0, 0}, + { "maxval", 255, BOND_VALFLAG_MAX}, + { "default", 1, BOND_VALFLAG_DEFAULT}, + { NULL, -1, 0} +}; + +static const struct bond_opt_value bond_peer_notif_delay_tbl[] = { + { "off", 0, 0}, + { "maxval", 300000, BOND_VALFLAG_MAX}, + { NULL, -1, 0} +}; + +static const struct bond_opt_value bond_primary_reselect_tbl[] = { + { "always", BOND_PRI_RESELECT_ALWAYS, BOND_VALFLAG_DEFAULT}, + { "better", BOND_PRI_RESELECT_BETTER, 0}, + { "failure", BOND_PRI_RESELECT_FAILURE, 0}, + { NULL, -1}, +}; + +static const struct bond_opt_value bond_use_carrier_tbl[] = { + { "on", 1, BOND_VALFLAG_DEFAULT}, + { NULL, -1, 0} +}; + +static const struct bond_opt_value bond_all_slaves_active_tbl[] = { + { "off", 0, BOND_VALFLAG_DEFAULT}, + { "on", 1, 0}, + { NULL, -1, 0} +}; + +static const struct bond_opt_value bond_resend_igmp_tbl[] = { + { "off", 0, 0}, + { "maxval", 255, BOND_VALFLAG_MAX}, + { "default", 1, BOND_VALFLAG_DEFAULT}, + { NULL, -1, 0} +}; + +static const struct bond_opt_value bond_lp_interval_tbl[] = { + { "minval", 1, BOND_VALFLAG_MIN | BOND_VALFLAG_DEFAULT}, + { "maxval", INT_MAX, BOND_VALFLAG_MAX}, + { NULL, -1, 0}, +}; + +static const struct bond_opt_value bond_tlb_dynamic_lb_tbl[] = { + { "off", 0, 0}, + { "on", 1, BOND_VALFLAG_DEFAULT}, + { NULL, -1, 0} +}; + +static const struct bond_opt_value bond_ad_actor_sys_prio_tbl[] = { + { "minval", 1, BOND_VALFLAG_MIN}, + { "maxval", 65535, BOND_VALFLAG_MAX | BOND_VALFLAG_DEFAULT}, + { NULL, -1, 0}, +}; + +static const struct bond_opt_value bond_ad_user_port_key_tbl[] = { + { "minval", 0, BOND_VALFLAG_MIN | BOND_VALFLAG_DEFAULT}, + { "maxval", 1023, BOND_VALFLAG_MAX}, + { NULL, -1, 0}, +}; + +static const struct bond_opt_value bond_missed_max_tbl[] = { + { "minval", 1, BOND_VALFLAG_MIN}, + { "maxval", 255, BOND_VALFLAG_MAX}, + { "default", 2, BOND_VALFLAG_DEFAULT}, + { NULL, -1, 0}, +}; + +static const struct bond_opt_value bond_coupled_control_tbl[] = { + { "on", 1, BOND_VALFLAG_DEFAULT}, + { "off", 0, 0}, + { NULL, -1, 0}, +}; + +static const struct bond_opt_value bond_broadcast_neigh_tbl[] = { + { "off", 0, BOND_VALFLAG_DEFAULT}, + { "on", 1, 0}, + { NULL, -1, 0} +}; + +static const struct bond_option bond_opts[BOND_OPT_LAST] = { + [BOND_OPT_MODE] = { + .id = BOND_OPT_MODE, + .name = "mode", + .desc = "bond device mode", + .flags = BOND_OPTFLAG_NOSLAVES | BOND_OPTFLAG_IFDOWN, + .values = bond_mode_tbl, + .set = bond_option_mode_set + }, + [BOND_OPT_PACKETS_PER_SLAVE] = { + .id = BOND_OPT_PACKETS_PER_SLAVE, + .name = "packets_per_slave", + .desc = "Packets to send per slave in RR mode", + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_ROUNDROBIN)), + .values = bond_pps_tbl, + .set = bond_option_pps_set + }, + [BOND_OPT_XMIT_HASH] = { + .id = BOND_OPT_XMIT_HASH, + .name = "xmit_hash_policy", + .desc = "balance-xor, 802.3ad, and tlb hashing method", + .values = bond_xmit_hashtype_tbl, + .set = bond_option_xmit_hash_policy_set + }, + [BOND_OPT_ARP_VALIDATE] = { + .id = BOND_OPT_ARP_VALIDATE, + .name = "arp_validate", + .desc = "validate src/dst of ARP probes", + .unsuppmodes = BIT(BOND_MODE_8023AD) | BIT(BOND_MODE_TLB) | + BIT(BOND_MODE_ALB), + .values = bond_arp_validate_tbl, + .set = bond_option_arp_validate_set + }, + [BOND_OPT_ARP_ALL_TARGETS] = { + .id = BOND_OPT_ARP_ALL_TARGETS, + .name = "arp_all_targets", + .desc = "fail on any/all arp targets timeout", + .values = bond_arp_all_targets_tbl, + .set = bond_option_arp_all_targets_set + }, + [BOND_OPT_FAIL_OVER_MAC] = { + .id = BOND_OPT_FAIL_OVER_MAC, + .name = "fail_over_mac", + .desc = "For active-backup, do not set all slaves to the same MAC", + .flags = BOND_OPTFLAG_NOSLAVES, + .values = bond_fail_over_mac_tbl, + .set = bond_option_fail_over_mac_set + }, + [BOND_OPT_ARP_INTERVAL] = { + .id = BOND_OPT_ARP_INTERVAL, + .name = "arp_interval", + .desc = "arp interval in milliseconds", + .unsuppmodes = BIT(BOND_MODE_8023AD) | BIT(BOND_MODE_TLB) | + BIT(BOND_MODE_ALB), + .values = bond_intmax_tbl, + .set = bond_option_arp_interval_set + }, + [BOND_OPT_MISSED_MAX] = { + .id = BOND_OPT_MISSED_MAX, + .name = "arp_missed_max", + .desc = "Maximum number of missed ARP interval", + .unsuppmodes = BIT(BOND_MODE_8023AD) | BIT(BOND_MODE_TLB) | + BIT(BOND_MODE_ALB), + .values = bond_missed_max_tbl, + .set = bond_option_missed_max_set + }, + [BOND_OPT_ARP_TARGETS] = { + .id = BOND_OPT_ARP_TARGETS, + .name = "arp_ip_target", + .desc = "arp targets in n.n.n.n form", + .flags = BOND_OPTFLAG_RAWVAL, + .set = bond_option_arp_ip_targets_set + }, + [BOND_OPT_NS_TARGETS] = { + .id = BOND_OPT_NS_TARGETS, + .name = "ns_ip6_target", + .desc = "NS targets in ffff:ffff::ffff:ffff form", + .flags = BOND_OPTFLAG_RAWVAL, + .set = bond_option_ns_ip6_targets_set + }, + [BOND_OPT_DOWNDELAY] = { + .id = BOND_OPT_DOWNDELAY, + .name = "downdelay", + .desc = "Delay before considering link down, in milliseconds", + .values = bond_intmax_tbl, + .set = bond_option_downdelay_set + }, + [BOND_OPT_UPDELAY] = { + .id = BOND_OPT_UPDELAY, + .name = "updelay", + .desc = "Delay before considering link up, in milliseconds", + .values = bond_intmax_tbl, + .set = bond_option_updelay_set + }, + [BOND_OPT_LACP_ACTIVE] = { + .id = BOND_OPT_LACP_ACTIVE, + .name = "lacp_active", + .desc = "Send LACPDU frames with configured lacp rate or acts as speak when spoken to", + .flags = BOND_OPTFLAG_IFDOWN, + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_8023AD)), + .values = bond_lacp_active, + .set = bond_option_lacp_active_set + }, + [BOND_OPT_LACP_RATE] = { + .id = BOND_OPT_LACP_RATE, + .name = "lacp_rate", + .desc = "LACPDU tx rate to request from 802.3ad partner", + .flags = BOND_OPTFLAG_IFDOWN, + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_8023AD)), + .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", + .desc = "Minimum number of available links before turning on carrier", + .values = bond_intmax_tbl, + .set = bond_option_min_links_set + }, + [BOND_OPT_AD_SELECT] = { + .id = BOND_OPT_AD_SELECT, + .name = "ad_select", + .desc = "802.3ad aggregation selection logic", + .flags = BOND_OPTFLAG_IFDOWN, + .values = bond_ad_select_tbl, + .set = bond_option_ad_select_set + }, + [BOND_OPT_NUM_PEER_NOTIF] = { + .id = BOND_OPT_NUM_PEER_NOTIF, + .name = "num_unsol_na", + .desc = "Number of peer notifications to send on failover event", + .values = bond_num_peer_notif_tbl, + .set = bond_option_num_peer_notif_set + }, + [BOND_OPT_MIIMON] = { + .id = BOND_OPT_MIIMON, + .name = "miimon", + .desc = "Link check interval in milliseconds", + .values = bond_intmax_tbl, + .set = bond_option_miimon_set + }, + [BOND_OPT_PRIO] = { + .id = BOND_OPT_PRIO, + .name = "prio", + .desc = "Link priority for failover re-selection", + .flags = BOND_OPTFLAG_RAWVAL, + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_ACTIVEBACKUP) | + BIT(BOND_MODE_TLB) | + BIT(BOND_MODE_ALB)), + .set = bond_option_prio_set + }, + [BOND_OPT_PRIMARY] = { + .id = BOND_OPT_PRIMARY, + .name = "primary", + .desc = "Primary network device to use", + .flags = BOND_OPTFLAG_RAWVAL, + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_ACTIVEBACKUP) | + BIT(BOND_MODE_TLB) | + BIT(BOND_MODE_ALB)), + .set = bond_option_primary_set + }, + [BOND_OPT_PRIMARY_RESELECT] = { + .id = BOND_OPT_PRIMARY_RESELECT, + .name = "primary_reselect", + .desc = "Reselect primary slave once it comes up", + .values = bond_primary_reselect_tbl, + .set = bond_option_primary_reselect_set + }, + [BOND_OPT_USE_CARRIER] = { + .id = BOND_OPT_USE_CARRIER, + .name = "use_carrier", + .desc = "option obsolete, use_carrier cannot be disabled", + .values = bond_use_carrier_tbl, + .set = bond_option_use_carrier_set + }, + [BOND_OPT_ACTIVE_SLAVE] = { + .id = BOND_OPT_ACTIVE_SLAVE, + .name = "active_slave", + .desc = "Currently active slave", + .flags = BOND_OPTFLAG_RAWVAL, + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_ACTIVEBACKUP) | + BIT(BOND_MODE_TLB) | + BIT(BOND_MODE_ALB)), + .set = bond_option_active_slave_set + }, + [BOND_OPT_QUEUE_ID] = { + .id = BOND_OPT_QUEUE_ID, + .name = "queue_id", + .desc = "Set queue id of a slave", + .flags = BOND_OPTFLAG_RAWVAL, + .set = bond_option_queue_id_set + }, + [BOND_OPT_ALL_SLAVES_ACTIVE] = { + .id = BOND_OPT_ALL_SLAVES_ACTIVE, + .name = "all_slaves_active", + .desc = "Keep all frames received on an interface by setting active flag for all slaves", + .values = bond_all_slaves_active_tbl, + .set = bond_option_all_slaves_active_set + }, + [BOND_OPT_RESEND_IGMP] = { + .id = BOND_OPT_RESEND_IGMP, + .name = "resend_igmp", + .desc = "Number of IGMP membership reports to send on link failure", + .values = bond_resend_igmp_tbl, + .set = bond_option_resend_igmp_set + }, + [BOND_OPT_LP_INTERVAL] = { + .id = BOND_OPT_LP_INTERVAL, + .name = "lp_interval", + .desc = "The number of seconds between instances where the bonding driver sends learning packets to each slave's peer switch", + .values = bond_lp_interval_tbl, + .set = bond_option_lp_interval_set + }, + [BOND_OPT_SLAVES] = { + .id = BOND_OPT_SLAVES, + .name = "slaves", + .desc = "Slave membership management", + .flags = BOND_OPTFLAG_RAWVAL, + .set = bond_option_slaves_set + }, + [BOND_OPT_TLB_DYNAMIC_LB] = { + .id = BOND_OPT_TLB_DYNAMIC_LB, + .name = "tlb_dynamic_lb", + .desc = "Enable dynamic flow shuffling", + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_TLB) | BIT(BOND_MODE_ALB)), + .values = bond_tlb_dynamic_lb_tbl, + .flags = BOND_OPTFLAG_IFDOWN, + .set = bond_option_tlb_dynamic_lb_set, + }, + [BOND_OPT_AD_ACTOR_SYS_PRIO] = { + .id = BOND_OPT_AD_ACTOR_SYS_PRIO, + .name = "ad_actor_sys_prio", + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_8023AD)), + .values = bond_ad_actor_sys_prio_tbl, + .set = bond_option_ad_actor_sys_prio_set, + }, + [BOND_OPT_ACTOR_PORT_PRIO] = { + .id = BOND_OPT_ACTOR_PORT_PRIO, + .name = "actor_port_prio", + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_8023AD)), + .flags = BOND_OPTFLAG_RAWVAL, + .set = bond_option_actor_port_prio_set, + }, + [BOND_OPT_AD_ACTOR_SYSTEM] = { + .id = BOND_OPT_AD_ACTOR_SYSTEM, + .name = "ad_actor_system", + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_8023AD)), + .flags = BOND_OPTFLAG_RAWVAL, + .set = bond_option_ad_actor_system_set, + }, + [BOND_OPT_AD_USER_PORT_KEY] = { + .id = BOND_OPT_AD_USER_PORT_KEY, + .name = "ad_user_port_key", + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_8023AD)), + .flags = BOND_OPTFLAG_IFDOWN, + .values = bond_ad_user_port_key_tbl, + .set = bond_option_ad_user_port_key_set, + }, + [BOND_OPT_NUM_PEER_NOTIF_ALIAS] = { + .id = BOND_OPT_NUM_PEER_NOTIF_ALIAS, + .name = "num_grat_arp", + .desc = "Number of peer notifications to send on failover event", + .values = bond_num_peer_notif_tbl, + .set = bond_option_num_peer_notif_set + }, + [BOND_OPT_PEER_NOTIF_DELAY] = { + .id = BOND_OPT_PEER_NOTIF_DELAY, + .name = "peer_notif_delay", + .desc = "Delay between each peer notification on failover event, in milliseconds", + .values = bond_peer_notif_delay_tbl, + .set = bond_option_peer_notif_delay_set + }, + [BOND_OPT_COUPLED_CONTROL] = { + .id = BOND_OPT_COUPLED_CONTROL, + .name = "coupled_control", + .desc = "Opt into using coupled control MUX for LACP states", + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_8023AD)), + .flags = BOND_OPTFLAG_IFDOWN, + .values = bond_coupled_control_tbl, + .set = bond_option_coupled_control_set, + }, + [BOND_OPT_BROADCAST_NEIGH] = { + .id = BOND_OPT_BROADCAST_NEIGH, + .name = "broadcast_neighbor", + .desc = "Broadcast neighbor packets to all active slaves", + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_8023AD)), + .values = bond_broadcast_neigh_tbl, + .set = bond_option_broadcast_neigh_set, + } +}; + +/* Searches for an option by name */ +const struct bond_option *bond_opt_get_by_name(const char *name) +{ + const struct bond_option *opt; + int option; + + for (option = 0; option < BOND_OPT_LAST; option++) { + opt = bond_opt_get(option); + if (opt && !strcmp(opt->name, name)) + return opt; + } + + return NULL; +} + +/* Searches for a value in opt's values[] table */ +const struct bond_opt_value *bond_opt_get_val(unsigned int option, u64 val) +{ + const struct bond_option *opt; + int i; + + opt = bond_opt_get(option); + if (WARN_ON(!opt)) + return NULL; + for (i = 0; opt->values && opt->values[i].string; i++) + if (opt->values[i].value == val) + return &opt->values[i]; + + return NULL; +} + +/* Searches for a value in opt's values[] table which matches the flagmask */ +static const struct bond_opt_value *bond_opt_get_flags(const struct bond_option *opt, + u32 flagmask) +{ + int i; + + for (i = 0; opt->values && opt->values[i].string; i++) + if (opt->values[i].flags & flagmask) + return &opt->values[i]; + + return NULL; +} + +/* If maxval is missing then there's no range to check. In case minval is + * missing then it's considered to be 0. + */ +static bool bond_opt_check_range(const struct bond_option *opt, u64 val) +{ + const struct bond_opt_value *minval, *maxval; + + minval = bond_opt_get_flags(opt, BOND_VALFLAG_MIN); + maxval = bond_opt_get_flags(opt, BOND_VALFLAG_MAX); + if (!maxval || (minval && val < minval->value) || val > maxval->value) + return false; + + return true; +} + +/** + * bond_opt_parse - parse option value + * @opt: the option to parse against + * @val: value to parse + * + * This function tries to extract the value from @val and check if it's + * a possible match for the option and returns NULL if a match isn't found, + * or the struct_opt_value that matched. It also strips the new line from + * @val->string if it's present. + */ +const struct bond_opt_value *bond_opt_parse(const struct bond_option *opt, + struct bond_opt_value *val) +{ + char *p, valstr[BOND_OPT_MAX_NAMELEN + 1] = { 0, }; + const struct bond_opt_value *tbl; + const struct bond_opt_value *ret = NULL; + bool checkval; + int i, rv; + + /* No parsing if the option wants a raw val */ + if (opt->flags & BOND_OPTFLAG_RAWVAL) + return val; + + tbl = opt->values; + if (!tbl) + goto out; + + /* ULLONG_MAX is used to bypass string processing */ + checkval = val->value != ULLONG_MAX; + if (!checkval) { + if (!val->string) + goto out; + p = strchr(val->string, '\n'); + if (p) + *p = '\0'; + for (p = val->string; *p; p++) + if (!(isdigit(*p) || isspace(*p))) + break; + /* The following code extracts the string to match or the value + * and sets checkval appropriately + */ + if (*p) { + rv = sscanf(val->string, "%32s", valstr); + } else { + rv = sscanf(val->string, "%llu", &val->value); + checkval = true; + } + if (!rv) + goto out; + } + + for (i = 0; tbl[i].string; i++) { + /* Check for exact match */ + if (checkval) { + if (val->value == tbl[i].value) + ret = &tbl[i]; + } else { + if (!strcmp(valstr, "default") && + (tbl[i].flags & BOND_VALFLAG_DEFAULT)) + ret = &tbl[i]; + + if (!strcmp(valstr, tbl[i].string)) + ret = &tbl[i]; + } + /* Found an exact match */ + if (ret) + goto out; + } + /* Possible range match */ + if (checkval && bond_opt_check_range(opt, val->value)) + ret = val; +out: + return ret; +} + +/* Check opt's dependencies against bond mode and currently set options */ +static int bond_opt_check_deps(struct bonding *bond, + const struct bond_option *opt) +{ + struct bond_params *params = &bond->params; + + if (test_bit(params->mode, &opt->unsuppmodes)) + return -EACCES; + if ((opt->flags & BOND_OPTFLAG_NOSLAVES) && bond_has_slaves(bond)) + return -ENOTEMPTY; + if ((opt->flags & BOND_OPTFLAG_IFDOWN) && (bond->dev->flags & IFF_UP)) + return -EBUSY; + + return 0; +} + +static void bond_opt_dep_print(struct bonding *bond, + const struct bond_option *opt, + struct nlattr *bad_attr, + struct netlink_ext_ack *extack) +{ + const struct bond_opt_value *modeval; + struct bond_params *params; + + params = &bond->params; + modeval = bond_opt_get_val(BOND_OPT_MODE, params->mode); + if (test_bit(params->mode, &opt->unsuppmodes)) { + netdev_err(bond->dev, "option %s: mode dependency failed, not supported in mode %s(%llu)\n", + opt->name, modeval->string, modeval->value); + NL_SET_ERR_MSG_ATTR(extack, bad_attr, + "option not supported in mode"); + } +} + +static void bond_opt_error_interpret(struct bonding *bond, + const struct bond_option *opt, + int error, const struct bond_opt_value *val, + struct nlattr *bad_attr, + struct netlink_ext_ack *extack) +{ + const struct bond_opt_value *minval, *maxval; + char *p; + + switch (error) { + case -EINVAL: + NL_SET_ERR_MSG_ATTR(extack, bad_attr, "invalid option value"); + if (val) { + if (val->string) { + /* sometimes RAWVAL opts may have new lines */ + p = strchr(val->string, '\n'); + if (p) + *p = '\0'; + netdev_err(bond->dev, "option %s: invalid value (%s)\n", + opt->name, val->string); + } else { + netdev_err(bond->dev, "option %s: invalid value (%llu)\n", + opt->name, val->value); + } + } + minval = bond_opt_get_flags(opt, BOND_VALFLAG_MIN); + maxval = bond_opt_get_flags(opt, BOND_VALFLAG_MAX); + if (!maxval) + break; + netdev_err(bond->dev, "option %s: allowed values %llu - %llu\n", + opt->name, minval ? minval->value : 0, maxval->value); + break; + case -EACCES: + bond_opt_dep_print(bond, opt, bad_attr, extack); + break; + case -ENOTEMPTY: + NL_SET_ERR_MSG_ATTR(extack, bad_attr, + "unable to set option because the bond device has slaves"); + netdev_err(bond->dev, "option %s: unable to set because the bond device has slaves\n", + opt->name); + break; + case -EBUSY: + NL_SET_ERR_MSG_ATTR(extack, bad_attr, + "unable to set option because the bond is up"); + netdev_err(bond->dev, "option %s: unable to set because the bond device is up\n", + opt->name); + break; + case -ENODEV: + if (val && val->string) { + p = strchr(val->string, '\n'); + if (p) + *p = '\0'; + netdev_err(bond->dev, "option %s: interface %s does not exist!\n", + opt->name, val->string); + NL_SET_ERR_MSG_ATTR(extack, bad_attr, + "interface does not exist"); + } + break; + default: + break; + } +} + +/** + * __bond_opt_set - set a bonding option + * @bond: target bond device + * @option: option to set + * @val: value to set it to + * @bad_attr: netlink attribue that caused the error + * @extack: extended netlink error structure, used when an error message + * needs to be returned to the caller via netlink + * + * This function is used to change the bond's option value, it can be + * used for both enabling/changing an option and for disabling it. RTNL lock + * must be obtained before calling this function. + */ +int __bond_opt_set(struct bonding *bond, + unsigned int option, struct bond_opt_value *val, + struct nlattr *bad_attr, struct netlink_ext_ack *extack) +{ + const struct bond_opt_value *retval = NULL; + const struct bond_option *opt; + int ret = -ENOENT; + + ASSERT_RTNL(); + + opt = bond_opt_get(option); + if (WARN_ON(!val) || WARN_ON(!opt)) + goto out; + ret = bond_opt_check_deps(bond, opt); + if (ret) + goto out; + retval = bond_opt_parse(opt, val); + if (!retval) { + ret = -EINVAL; + goto out; + } + ret = opt->set(bond, retval); +out: + if (ret) + bond_opt_error_interpret(bond, opt, ret, val, bad_attr, extack); + + return ret; +} +/** + * __bond_opt_set_notify - set a bonding option + * @bond: target bond device + * @option: option to set + * @val: value to set it to + * + * This function is used to change the bond's option value and trigger + * a notification to user sapce. It can be used for both enabling/changing + * an option and for disabling it. RTNL lock must be obtained before calling + * this function. + */ +int __bond_opt_set_notify(struct bonding *bond, + unsigned int option, struct bond_opt_value *val) +{ + int ret; + + ASSERT_RTNL(); + + ret = __bond_opt_set(bond, option, val, NULL, NULL); + + if (!ret && (bond->dev->reg_state == NETREG_REGISTERED)) + call_netdevice_notifiers(NETDEV_CHANGEINFODATA, bond->dev); + + return ret; +} + +/** + * bond_opt_tryset_rtnl - try to acquire rtnl and call __bond_opt_set + * @bond: target bond device + * @option: option to set + * @buf: value to set it to + * + * This function tries to acquire RTNL without blocking and if successful + * calls __bond_opt_set. It is mainly used for sysfs option manipulation. + */ +int bond_opt_tryset_rtnl(struct bonding *bond, unsigned int option, char *buf) +{ + struct bond_opt_value optval; + int ret; + + if (!rtnl_trylock()) + return restart_syscall(); + bond_opt_initstr(&optval, buf); + ret = __bond_opt_set_notify(bond, option, &optval); + rtnl_unlock(); + + return ret; +} + +/** + * bond_opt_get - get a pointer to an option + * @option: option for which to return a pointer + * + * This function checks if option is valid and if so returns a pointer + * to its entry in the bond_opts[] option array. + */ +const struct bond_option *bond_opt_get(unsigned int option) +{ + if (!BOND_OPT_VALID(option)) + return NULL; + + return &bond_opts[option]; +} + +static bool bond_set_xfrm_features(struct bonding *bond) +{ + if (!IS_ENABLED(CONFIG_XFRM_OFFLOAD)) + return false; + + if (BOND_MODE(bond) == BOND_MODE_ACTIVEBACKUP) + bond->dev->wanted_features |= BOND_XFRM_FEATURES; + else + bond->dev->wanted_features &= ~BOND_XFRM_FEATURES; + + return true; +} + +static int bond_option_mode_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + if (bond->xdp_prog && !bond_xdp_check(bond, newval->value)) + return -EOPNOTSUPP; + + if (!bond_mode_uses_arp(newval->value)) { + if (bond->params.arp_interval) { + netdev_dbg(bond->dev, "%s mode is incompatible with arp monitoring, start mii monitoring\n", + newval->string); + /* disable arp monitoring */ + WRITE_ONCE(bond->params.arp_interval, 0); + } + + if (!bond->params.miimon) { + /* set miimon to default value */ + WRITE_ONCE(bond->params.miimon, BOND_DEFAULT_MIIMON); + netdev_dbg(bond->dev, "Setting MII monitoring interval to %d\n", + bond->params.miimon); + } + } + + if (newval->value == BOND_MODE_ALB) + WRITE_ONCE(bond->params.tlb_dynamic_lb, 1); + + /* don't cache arp_validate between modes */ + WRITE_ONCE(bond->params.arp_validate, BOND_ARP_VALIDATE_NONE); + WRITE_ONCE(bond->params.mode, newval->value); + + /* When changing mode, the bond device is down, we may reduce + * the bond_bcast_neigh_enabled in bond_close() if broadcast_neighbor + * enabled in 8023ad mode. Therefore, only clear broadcast_neighbor + * to 0. + */ + WRITE_ONCE(bond->params.broadcast_neighbor, 0); + + if (bond->dev->reg_state == NETREG_REGISTERED) { + bool update = false; + + update |= bond_set_xfrm_features(bond); + + if (update) + netdev_update_features(bond->dev); + } + + bond_xdp_set_features(bond->dev); + + return 0; +} + +static int bond_option_active_slave_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + char ifname[IFNAMSIZ] = { 0, }; + struct net_device *slave_dev; + int ret = 0; + + sscanf(newval->string, "%15s", ifname); /* IFNAMSIZ */ + if (!strlen(ifname) || newval->string[0] == '\n') { + slave_dev = NULL; + } else { + slave_dev = __dev_get_by_name(dev_net(bond->dev), ifname); + if (!slave_dev) + return -ENODEV; + } + + if (slave_dev) { + if (!netif_is_bond_slave(slave_dev)) { + slave_err(bond->dev, slave_dev, "Device is not bonding slave\n"); + return -EINVAL; + } + + if (bond->dev != netdev_master_upper_dev_get(slave_dev)) { + slave_err(bond->dev, slave_dev, "Device is not our slave\n"); + return -EINVAL; + } + } + + block_netpoll_tx(); + /* check to see if we are clearing active */ + if (!slave_dev) { + netdev_dbg(bond->dev, "Clearing current active slave\n"); + bond_change_active_slave(bond, NULL); + bond_select_active_slave(bond); + } else { + struct slave *old_active = rtnl_dereference(bond->curr_active_slave); + struct slave *new_active = bond_slave_get_rtnl(slave_dev); + + BUG_ON(!new_active); + + if (new_active == old_active) { + /* do nothing */ + slave_dbg(bond->dev, new_active->dev, "is already the current active slave\n"); + } else { + if (old_active && (new_active->link == BOND_LINK_UP) && + bond_slave_is_up(new_active)) { + slave_dbg(bond->dev, new_active->dev, "Setting as active slave\n"); + bond_change_active_slave(bond, new_active); + } else { + slave_err(bond->dev, new_active->dev, "Could not set as active slave; either %s is down or the link is down\n", + new_active->dev->name); + ret = -EINVAL; + } + } + } + unblock_netpoll_tx(); + + return ret; +} + +/* There are two tricky bits here. First, if MII monitoring is activated, then + * we must disable ARP monitoring. Second, if the timer isn't running, we must + * start it. + */ +static int bond_option_miimon_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting MII monitoring interval to %llu\n", + newval->value); + WRITE_ONCE(bond->params.miimon, newval->value); + if (bond->params.updelay) + netdev_dbg(bond->dev, "Note: Updating updelay (to %d) since it is a multiple of the miimon value\n", + bond->params.updelay * bond->params.miimon); + if (bond->params.downdelay) + netdev_dbg(bond->dev, "Note: Updating downdelay (to %d) since it is a multiple of the miimon value\n", + bond->params.downdelay * bond->params.miimon); + if (bond->params.peer_notif_delay) + netdev_dbg(bond->dev, "Note: Updating peer_notif_delay (to %d) since it is a multiple of the miimon value\n", + bond->params.peer_notif_delay * bond->params.miimon); + if (newval->value && bond->params.arp_interval) { + netdev_dbg(bond->dev, "MII monitoring cannot be used with ARP monitoring - disabling ARP monitoring...\n"); + WRITE_ONCE(bond->params.arp_interval, 0); + if (bond->params.arp_validate) + WRITE_ONCE(bond->params.arp_validate, + BOND_ARP_VALIDATE_NONE); + } + if (bond->dev->flags & IFF_UP) { + /* If the interface is up, we may need to fire off + * the MII timer. If the interface is down, the + * timer will get fired off when the open function + * is called. + */ + if (!newval->value) { + cancel_delayed_work_sync(&bond->mii_work); + } else { + cancel_delayed_work_sync(&bond->arp_work); + queue_delayed_work(bond->wq, &bond->mii_work, 0); + } + } + + return 0; +} + +/* Set up, down and peer notification delays. These must be multiples + * of the MII monitoring value, and are stored internally as the + * multiplier. Thus, we must translate to MS for the real world. + */ +static int _bond_option_delay_set(struct bonding *bond, + const struct bond_opt_value *newval, + const char *name, + int *target) +{ + int value = newval->value; + + if (!bond->params.miimon) { + netdev_err(bond->dev, "Unable to set %s as MII monitoring is disabled\n", + name); + return -EPERM; + } + if ((value % bond->params.miimon) != 0) { + netdev_warn(bond->dev, + "%s (%d) is not a multiple of miimon (%d), value rounded to %d ms\n", + name, + value, bond->params.miimon, + (value / bond->params.miimon) * + bond->params.miimon); + } + WRITE_ONCE(*target, value / bond->params.miimon); + netdev_dbg(bond->dev, "Setting %s to %d\n", + name, + *target * bond->params.miimon); + + return 0; +} + +static int bond_option_updelay_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + return _bond_option_delay_set(bond, newval, "up delay", + &bond->params.updelay); +} + +static int bond_option_downdelay_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + return _bond_option_delay_set(bond, newval, "down delay", + &bond->params.downdelay); +} + +static int bond_option_peer_notif_delay_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + int ret = _bond_option_delay_set(bond, newval, + "peer notification delay", + &bond->params.peer_notif_delay); + return ret; +} + +static int bond_option_use_carrier_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + return 0; +} + +/* There are two tricky bits here. First, if ARP monitoring is activated, then + * we must disable MII monitoring. Second, if the ARP timer isn't running, + * we must start it. + */ +static int bond_option_arp_interval_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting ARP monitoring interval to %llu\n", + newval->value); + WRITE_ONCE(bond->params.arp_interval, newval->value); + if (newval->value) { + if (bond->params.miimon) { + netdev_dbg(bond->dev, "ARP monitoring cannot be used with MII monitoring. Disabling MII monitoring\n"); + WRITE_ONCE(bond->params.miimon, 0); + } + if (!bond->params.arp_targets[0]) + netdev_dbg(bond->dev, "ARP monitoring has been set up, but no ARP targets have been specified\n"); + } + if (bond->dev->flags & IFF_UP) { + /* If the interface is up, we may need to fire off + * the ARP timer. If the interface is down, the + * timer will get fired off when the open function + * is called. + */ + if (!newval->value) { + if (bond->params.arp_validate) + bond->recv_probe = NULL; + cancel_delayed_work_sync(&bond->arp_work); + } else { + /* arp_validate can be set only in active-backup mode */ + bond->recv_probe = bond_rcv_validate; + cancel_delayed_work_sync(&bond->mii_work); + queue_delayed_work(bond->wq, &bond->arp_work, 0); + } + } + + return 0; +} + +static void _bond_options_arp_ip_target_set(struct bonding *bond, int slot, + __be32 target, + unsigned long last_rx) +{ + __be32 *targets = bond->params.arp_targets; + struct list_head *iter; + struct slave *slave; + + if (slot >= 0 && slot < BOND_MAX_ARP_TARGETS) { + bond_for_each_slave(bond, slave, iter) + WRITE_ONCE(slave->target_last_arp_rx[slot], last_rx); + WRITE_ONCE(targets[slot], target); + } +} + +static int _bond_option_arp_ip_target_add(struct bonding *bond, __be32 target) +{ + __be32 *targets = bond->params.arp_targets; + int ind; + + if (!bond_is_ip_target_ok(target)) { + netdev_err(bond->dev, "invalid ARP target %pI4 specified for addition\n", + &target); + return -EINVAL; + } + + if (bond_get_targets_ip(targets, target) != -1) { /* dup */ + netdev_err(bond->dev, "ARP target %pI4 is already present\n", + &target); + return -EINVAL; + } + + ind = bond_get_targets_ip(targets, 0); /* first free slot */ + if (ind == -1) { + netdev_err(bond->dev, "ARP target table is full!\n"); + return -EINVAL; + } + + netdev_dbg(bond->dev, "Adding ARP target %pI4\n", &target); + + _bond_options_arp_ip_target_set(bond, ind, target, jiffies); + + return 0; +} + +static int bond_option_arp_ip_target_add(struct bonding *bond, __be32 target) +{ + return _bond_option_arp_ip_target_add(bond, target); +} + +static int bond_option_arp_ip_target_rem(struct bonding *bond, __be32 target) +{ + __be32 *targets = bond->params.arp_targets; + struct list_head *iter; + struct slave *slave; + unsigned long *targets_rx; + int ind, i; + + if (!bond_is_ip_target_ok(target)) { + netdev_err(bond->dev, "invalid ARP target %pI4 specified for removal\n", + &target); + return -EINVAL; + } + + ind = bond_get_targets_ip(targets, target); + if (ind == -1) { + netdev_err(bond->dev, "unable to remove nonexistent ARP target %pI4\n", + &target); + return -EINVAL; + } + + if (ind == 0 && !targets[1] && bond->params.arp_interval) + netdev_warn(bond->dev, "Removing last arp target with arp_interval on\n"); + + netdev_dbg(bond->dev, "Removing ARP target %pI4\n", &target); + + bond_for_each_slave(bond, slave, iter) { + targets_rx = slave->target_last_arp_rx; + for (i = ind; (i < BOND_MAX_ARP_TARGETS-1) && targets[i+1]; i++) + WRITE_ONCE(targets_rx[i], READ_ONCE(targets_rx[i+1])); + WRITE_ONCE(targets_rx[i], 0); + } + for (i = ind; (i < BOND_MAX_ARP_TARGETS-1) && targets[i+1]; i++) + WRITE_ONCE(targets[i], targets[i+1]); + WRITE_ONCE(targets[i], 0); + + return 0; +} + +void bond_option_arp_ip_targets_clear(struct bonding *bond) +{ + int i; + + for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) + _bond_options_arp_ip_target_set(bond, i, 0, 0); +} + +static int bond_option_arp_ip_targets_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + int ret = -EPERM; + __be32 target; + + if (newval->string) { + if (strlen(newval->string) < 1 || + !in4_pton(newval->string + 1, -1, (u8 *)&target, -1, NULL)) { + netdev_err(bond->dev, "invalid ARP target specified\n"); + return ret; + } + if (newval->string[0] == '+') + ret = bond_option_arp_ip_target_add(bond, target); + else if (newval->string[0] == '-') + ret = bond_option_arp_ip_target_rem(bond, target); + else + netdev_err(bond->dev, "no command found in arp_ip_targets file - use + or -\n"); + } else { + target = newval->value; + ret = bond_option_arp_ip_target_add(bond, target); + } + + return ret; +} + +#if IS_ENABLED(CONFIG_IPV6) +static bool slave_can_set_ns_maddr(const struct bonding *bond, struct slave *slave) +{ + return BOND_MODE(bond) == BOND_MODE_ACTIVEBACKUP && + !bond_is_active_slave(slave) && + slave->dev->flags & IFF_MULTICAST; +} + +/** + * slave_set_ns_maddrs - add/del all NS mac addresses for slave + * @bond: bond device + * @slave: slave device + * @add: add or remove all the NS mac addresses + * + * This function tries to add or delete all the NS mac addresses on the slave + * + * Note, the IPv6 NS target address is the unicast address in Neighbor + * Solicitation (NS) message. The dest address of NS message should be + * solicited-node multicast address of the target. The dest mac of NS message + * is converted from the solicited-node multicast address. + * + * This function is called when + * * arp_validate changes + * * enslaving, releasing new slaves + */ +static void slave_set_ns_maddrs(struct bonding *bond, struct slave *slave, bool add) +{ + struct in6_addr *targets = bond->params.ns_targets; + char slot_maddr[MAX_ADDR_LEN]; + struct in6_addr mcaddr; + int i; + + if (!slave_can_set_ns_maddr(bond, slave)) + return; + + for (i = 0; i < BOND_MAX_NS_TARGETS; i++) { + if (ipv6_addr_any(&targets[i])) + break; + + addrconf_addr_solict_mult(&targets[i], &mcaddr); + if (!ndisc_mc_map(&mcaddr, slot_maddr, slave->dev, 0)) { + if (add) + dev_mc_add(slave->dev, slot_maddr); + else + dev_mc_del(slave->dev, slot_maddr); + } + } +} + +void bond_slave_ns_maddrs_add(struct bonding *bond, struct slave *slave) +{ + if (!bond->params.arp_validate) + return; + slave_set_ns_maddrs(bond, slave, true); +} + +void bond_slave_ns_maddrs_del(struct bonding *bond, struct slave *slave) +{ + if (!bond->params.arp_validate) + return; + slave_set_ns_maddrs(bond, slave, false); +} + +/** + * slave_set_ns_maddr - set new NS mac address for slave + * @bond: bond device + * @slave: slave device + * @target: the new IPv6 target + * @slot: the old IPv6 target in the slot + * + * This function tries to replace the old mac address to new one on the slave. + * + * Note, the target/slot IPv6 address is the unicast address in Neighbor + * Solicitation (NS) message. The dest address of NS message should be + * solicited-node multicast address of the target. The dest mac of NS message + * is converted from the solicited-node multicast address. + * + * This function is called when + * * An IPv6 NS target is added or removed. + */ +static void slave_set_ns_maddr(struct bonding *bond, struct slave *slave, + struct in6_addr *target, struct in6_addr *slot) +{ + char mac_addr[MAX_ADDR_LEN]; + struct in6_addr mcast_addr; + + if (!bond->params.arp_validate || !slave_can_set_ns_maddr(bond, slave)) + return; + + /* remove the previous mac addr from slave */ + addrconf_addr_solict_mult(slot, &mcast_addr); + if (!ipv6_addr_any(slot) && + !ndisc_mc_map(&mcast_addr, mac_addr, slave->dev, 0)) + dev_mc_del(slave->dev, mac_addr); + + /* add new mac addr on slave if target is set */ + addrconf_addr_solict_mult(target, &mcast_addr); + if (!ipv6_addr_any(target) && + !ndisc_mc_map(&mcast_addr, mac_addr, slave->dev, 0)) + dev_mc_add(slave->dev, mac_addr); +} + +static void _bond_options_ns_ip6_target_set(struct bonding *bond, int slot, + struct in6_addr *target, + unsigned long last_rx) +{ + struct in6_addr *targets = bond->params.ns_targets; + struct list_head *iter; + struct slave *slave; + + if (slot >= 0 && slot < BOND_MAX_NS_TARGETS) { + bond_for_each_slave(bond, slave, iter) { + WRITE_ONCE(slave->target_last_arp_rx[slot], last_rx); + slave_set_ns_maddr(bond, slave, target, &targets[slot]); + } + targets[slot] = *target; + } +} + +void bond_option_ns_ip6_targets_clear(struct bonding *bond) +{ + struct in6_addr addr_any = in6addr_any; + int i; + + for (i = 0; i < BOND_MAX_NS_TARGETS; i++) + _bond_options_ns_ip6_target_set(bond, i, &addr_any, 0); +} + +static int bond_option_ns_ip6_targets_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + struct in6_addr *target = (struct in6_addr *)newval->extra; + struct in6_addr *targets = bond->params.ns_targets; + struct in6_addr addr_any = in6addr_any; + int index; + + if (!bond_is_ip6_target_ok(target)) { + netdev_err(bond->dev, "invalid NS target %pI6c specified for addition\n", + target); + return -EINVAL; + } + + if (bond_get_targets_ip6(targets, target) != -1) { /* dup */ + netdev_err(bond->dev, "NS target %pI6c is already present\n", + target); + return -EINVAL; + } + + index = bond_get_targets_ip6(targets, &addr_any); /* first free slot */ + if (index == -1) { + netdev_err(bond->dev, "NS target table is full!\n"); + return -EINVAL; + } + + netdev_dbg(bond->dev, "Adding NS target %pI6c\n", target); + + _bond_options_ns_ip6_target_set(bond, index, target, jiffies); + + return 0; +} +#else +static int bond_option_ns_ip6_targets_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + return -EPERM; +} + +static void slave_set_ns_maddrs(struct bonding *bond, struct slave *slave, bool add) {} + +void bond_slave_ns_maddrs_add(struct bonding *bond, struct slave *slave) {} + +void bond_slave_ns_maddrs_del(struct bonding *bond, struct slave *slave) {} +#endif + +static int bond_option_arp_validate_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + bool changed = !!bond->params.arp_validate != !!newval->value; + struct list_head *iter; + struct slave *slave; + + netdev_dbg(bond->dev, "Setting arp_validate to %s (%llu)\n", + newval->string, newval->value); + WRITE_ONCE(bond->params.arp_validate, newval->value); + + if (changed) { + bond_for_each_slave(bond, slave, iter) + slave_set_ns_maddrs(bond, slave, !!bond->params.arp_validate); + } + + return 0; +} + +static int bond_option_arp_all_targets_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting arp_all_targets to %s (%llu)\n", + newval->string, newval->value); + WRITE_ONCE(bond->params.arp_all_targets, newval->value); + + return 0; +} + +static int bond_option_missed_max_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting missed max to %s (%llu)\n", + newval->string, newval->value); + WRITE_ONCE(bond->params.missed_max, newval->value); + + return 0; +} + +static int bond_option_prio_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + struct slave *slave; + + slave = bond_slave_get_rtnl(newval->slave_dev); + if (!slave) { + netdev_dbg(newval->slave_dev, "%s called on NULL slave\n", __func__); + return -ENODEV; + } + slave->prio = newval->value; + + if (rtnl_dereference(bond->primary_slave)) + slave_warn(bond->dev, slave->dev, + "prio updated, but will not affect failover re-selection as primary slave have been set\n"); + else + bond_select_active_slave(bond); + + return 0; +} + +static int bond_option_primary_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + char *p, *primary = newval->string; + struct list_head *iter; + struct slave *slave; + + block_netpoll_tx(); + + p = strchr(primary, '\n'); + if (p) + *p = '\0'; + /* check to see if we are clearing primary */ + if (!strlen(primary)) { + netdev_dbg(bond->dev, "Setting primary slave to None\n"); + RCU_INIT_POINTER(bond->primary_slave, NULL); + memset(bond->params.primary, 0, sizeof(bond->params.primary)); + bond_select_active_slave(bond); + goto out; + } + + bond_for_each_slave(bond, slave, iter) { + if (strncmp(slave->dev->name, primary, IFNAMSIZ) == 0) { + slave_dbg(bond->dev, slave->dev, "Setting as primary slave\n"); + rcu_assign_pointer(bond->primary_slave, slave); + strcpy(bond->params.primary, slave->dev->name); + bond->force_primary = true; + bond_select_active_slave(bond); + goto out; + } + } + + if (rtnl_dereference(bond->primary_slave)) { + netdev_dbg(bond->dev, "Setting primary slave to None\n"); + RCU_INIT_POINTER(bond->primary_slave, NULL); + bond_select_active_slave(bond); + } + strscpy_pad(bond->params.primary, primary, IFNAMSIZ); + + netdev_dbg(bond->dev, "Recording %s as primary, but it has not been enslaved yet\n", + primary); + +out: + unblock_netpoll_tx(); + + return 0; +} + +static int bond_option_primary_reselect_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting primary_reselect to %s (%llu)\n", + newval->string, newval->value); + WRITE_ONCE(bond->params.primary_reselect, newval->value); + + block_netpoll_tx(); + bond_select_active_slave(bond); + unblock_netpoll_tx(); + + return 0; +} + +static int bond_option_fail_over_mac_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting fail_over_mac to %s (%llu)\n", + newval->string, newval->value); + WRITE_ONCE(bond->params.fail_over_mac, newval->value); + + return 0; +} + +static int bond_option_xmit_hash_policy_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + if (bond->xdp_prog && !__bond_xdp_check(BOND_MODE(bond), newval->value)) + return -EOPNOTSUPP; + netdev_dbg(bond->dev, "Setting xmit hash policy to %s (%llu)\n", + newval->string, newval->value); + WRITE_ONCE(bond->params.xmit_policy, newval->value); + + return 0; +} + +static int bond_option_resend_igmp_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting resend_igmp to %llu\n", + newval->value); + WRITE_ONCE(bond->params.resend_igmp, newval->value); + + return 0; +} + +static int bond_option_num_peer_notif_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + WRITE_ONCE(bond->params.num_peer_notif, newval->value); + + return 0; +} + +static int bond_option_all_slaves_active_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + struct list_head *iter; + struct slave *slave; + + if (newval->value == bond->params.all_slaves_active) + return 0; + WRITE_ONCE(bond->params.all_slaves_active, newval->value); + bond_for_each_slave(bond, slave, iter) { + if (!bond_is_active_slave(slave)) { + if (newval->value) + slave->inactive = 0; + else + slave->inactive = 1; + } + } + + return 0; +} + +static int bond_option_min_links_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting min links value to %llu\n", + newval->value); + WRITE_ONCE(bond->params.min_links, newval->value); + bond_set_carrier(bond); + + return 0; +} + +static int bond_option_lp_interval_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + WRITE_ONCE(bond->params.lp_interval, newval->value); + + return 0; +} + +static int bond_option_pps_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting packets per slave to %llu\n", + newval->value); + WRITE_ONCE(bond->params.packets_per_slave, newval->value); + if (newval->value > 0) { + bond->params.reciprocal_packets_per_slave = + reciprocal_value(newval->value); + } else { + /* reciprocal_packets_per_slave is unused if + * packets_per_slave is 0 or 1, just initialize it + */ + bond->params.reciprocal_packets_per_slave = + (struct reciprocal_value) { 0 }; + } + + return 0; +} + +static int bond_option_lacp_active_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting LACP active to %s (%llu)\n", + newval->string, newval->value); + WRITE_ONCE(bond->params.lacp_active, newval->value); + bond_3ad_update_lacp_active(bond); + + return 0; +} + +static int bond_option_lacp_rate_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting LACP rate to %s (%llu)\n", + newval->string, newval->value); + WRITE_ONCE(bond->params.lacp_fast, newval->value); + bond_3ad_update_lacp_rate(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); + WRITE_ONCE(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) +{ + netdev_dbg(bond->dev, "Setting ad_select to %s (%llu)\n", + newval->string, newval->value); + WRITE_ONCE(bond->params.ad_select, newval->value); + + return 0; +} + +static int bond_option_queue_id_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + struct slave *slave, *update_slave; + struct net_device *sdev; + struct list_head *iter; + char *delim; + int ret = 0; + u16 qid; + + /* delim will point to queue id if successful */ + delim = strchr(newval->string, ':'); + if (!delim) + goto err_no_cmd; + + /* Terminate string that points to device name and bump it + * up one, so we can read the queue id there. + */ + *delim = '\0'; + if (sscanf(++delim, "%hd\n", &qid) != 1) + goto err_no_cmd; + + /* Check buffer length, valid ifname and queue id */ + if (!dev_valid_name(newval->string) || + qid > bond->dev->real_num_tx_queues) + goto err_no_cmd; + + /* Get the pointer to that interface if it exists */ + sdev = __dev_get_by_name(dev_net(bond->dev), newval->string); + if (!sdev) + goto err_no_cmd; + + /* Search for thes slave and check for duplicate qids */ + update_slave = NULL; + bond_for_each_slave(bond, slave, iter) { + if (sdev == slave->dev) + /* We don't need to check the matching + * slave for dups, since we're overwriting it + */ + update_slave = slave; + else if (qid && qid == slave->queue_id) { + goto err_no_cmd; + } + } + + if (!update_slave) + goto err_no_cmd; + + /* Actually set the qids for the slave */ + WRITE_ONCE(update_slave->queue_id, qid); + +out: + return ret; + +err_no_cmd: + netdev_dbg(bond->dev, "invalid input for queue_id set\n"); + ret = -EPERM; + goto out; + +} + +static int bond_option_slaves_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + char command[IFNAMSIZ + 1] = { 0, }; + struct net_device *dev; + char *ifname; + int ret; + + sscanf(newval->string, "%16s", command); /* IFNAMSIZ*/ + ifname = command + 1; + if ((strlen(command) <= 1) || + (command[0] != '+' && command[0] != '-') || + !dev_valid_name(ifname)) + goto err_no_cmd; + + dev = __dev_get_by_name(dev_net(bond->dev), ifname); + if (!dev) { + netdev_dbg(bond->dev, "interface %s does not exist!\n", + ifname); + ret = -ENODEV; + goto out; + } + + switch (command[0]) { + case '+': + slave_dbg(bond->dev, dev, "Enslaving interface\n"); + ret = bond_enslave(bond->dev, dev, NULL); + break; + + case '-': + slave_dbg(bond->dev, dev, "Releasing interface\n"); + ret = bond_release(bond->dev, dev); + break; + + default: + /* should not run here. */ + goto err_no_cmd; + } + +out: + return ret; + +err_no_cmd: + netdev_err(bond->dev, "no command found in slaves file - use +ifname or -ifname\n"); + ret = -EPERM; + goto out; +} + +static int bond_option_tlb_dynamic_lb_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting dynamic-lb to %s (%llu)\n", + newval->string, newval->value); + WRITE_ONCE(bond->params.tlb_dynamic_lb, newval->value); + + return 0; +} + +static int bond_option_ad_actor_sys_prio_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting ad_actor_sys_prio to %llu\n", + newval->value); + + WRITE_ONCE(bond->params.ad_actor_sys_prio, newval->value); + bond_3ad_update_ad_actor_settings(bond); + + return 0; +} + +static int bond_option_actor_port_prio_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + struct slave *slave; + + slave = bond_slave_get_rtnl(newval->slave_dev); + if (!slave) { + netdev_dbg(bond->dev, "%s called on NULL slave\n", __func__); + return -ENODEV; + } + + netdev_dbg(newval->slave_dev, "Setting actor_port_prio to %llu\n", + newval->value); + + SLAVE_AD_INFO(slave)->port_priority = newval->value; + bond_3ad_update_ad_actor_settings(bond); + + return 0; +} + +static int bond_option_ad_actor_system_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + u8 macaddr[ETH_ALEN]; + u8 *mac; + + if (newval->string) { + if (!mac_pton(newval->string, macaddr)) + goto err; + mac = macaddr; + } else { + mac = (u8 *)&newval->value; + } + + if (is_multicast_ether_addr(mac)) + goto err; + + netdev_dbg(bond->dev, "Setting ad_actor_system to %pM\n", mac); + ether_addr_copy(bond->params.ad_actor_system, mac); + bond_3ad_update_ad_actor_settings(bond); + + return 0; + +err: + netdev_err(bond->dev, "Invalid ad_actor_system MAC address.\n"); + return -EINVAL; +} + +static int bond_option_ad_user_port_key_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting ad_user_port_key to %llu\n", + newval->value); + + WRITE_ONCE(bond->params.ad_user_port_key, newval->value); + return 0; +} + +static int bond_option_coupled_control_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_info(bond->dev, "Setting coupled_control to %s (%llu)\n", + newval->string, newval->value); + + WRITE_ONCE(bond->params.coupled_control, newval->value); + return 0; +} + +static int bond_option_broadcast_neigh_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + if (bond->params.broadcast_neighbor == newval->value) + return 0; + + WRITE_ONCE(bond->params.broadcast_neighbor, newval->value); + if (bond->dev->flags & IFF_UP) { + if (bond->params.broadcast_neighbor) + static_branch_inc(&bond_bcast_neigh_enabled); + else + static_branch_dec(&bond_bcast_neigh_enabled); + } + + netdev_dbg(bond->dev, "Setting broadcast_neighbor to %s (%llu)\n", + newval->string, newval->value); + return 0; +} diff --git a/drivers/net/bonding/bond_procfs.c b/drivers/net/bonding/bond_procfs.c new file mode 100644 index 000000000..9d71053cd --- /dev/null +++ b/drivers/net/bonding/bond_procfs.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define bond_version "Ethernet Channel Bonding Driver: v" UTS_RELEASE "\n" + +static void *bond_info_seq_start(struct seq_file *seq, loff_t *pos) + __acquires(RCU) +{ + struct bonding *bond = pde_data(file_inode(seq->file)); + struct list_head *iter; + struct slave *slave; + loff_t off = 0; + + rcu_read_lock(); + + if (*pos == 0) + return SEQ_START_TOKEN; + + bond_for_each_slave_rcu(bond, slave, iter) + if (++off == *pos) + return slave; + + return NULL; +} + +static void *bond_info_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + struct bonding *bond = pde_data(file_inode(seq->file)); + struct list_head *iter; + struct slave *slave; + bool found = false; + + ++*pos; + if (v == SEQ_START_TOKEN) + return bond_first_slave_rcu(bond); + + bond_for_each_slave_rcu(bond, slave, iter) { + if (found) + return slave; + if (slave == v) + found = true; + } + + return NULL; +} + +static void bond_info_seq_stop(struct seq_file *seq, void *v) + __releases(RCU) +{ + rcu_read_unlock(); +} + +static void bond_info_show_master(struct seq_file *seq) +{ + struct bonding *bond = pde_data(file_inode(seq->file)); + const struct bond_opt_value *optval; + struct slave *curr, *primary; + int arp_interval, fail_over_mac, miimon, i; + + curr = rcu_dereference(bond->curr_active_slave); + + seq_printf(seq, "Bonding Mode: %s", + bond_mode_name(BOND_MODE(bond))); + + fail_over_mac = READ_ONCE(bond->params.fail_over_mac); + if (BOND_MODE(bond) == BOND_MODE_ACTIVEBACKUP && fail_over_mac) { + optval = bond_opt_get_val(BOND_OPT_FAIL_OVER_MAC, + fail_over_mac); + seq_printf(seq, " (fail_over_mac %s)", optval->string); + } + + seq_printf(seq, "\n"); + + if (bond_mode_uses_xmit_hash(bond)) { + int xmit_policy = READ_ONCE(bond->params.xmit_policy); + + optval = bond_opt_get_val(BOND_OPT_XMIT_HASH, xmit_policy); + seq_printf(seq, "Transmit Hash Policy: %s (%d)\n", + optval->string, xmit_policy); + } + + if (bond_uses_primary(bond)) { + primary = rcu_dereference(bond->primary_slave); + seq_printf(seq, "Primary Slave: %s", + primary ? primary->dev->name : "None"); + if (primary) { + optval = bond_opt_get_val(BOND_OPT_PRIMARY_RESELECT, + READ_ONCE(bond->params.primary_reselect)); + seq_printf(seq, " (primary_reselect %s)", + optval->string); + } + + seq_printf(seq, "\nCurrently Active Slave: %s\n", + (curr) ? curr->dev->name : "None"); + } + + seq_printf(seq, "MII Status: %s\n", netif_carrier_ok(bond->dev) ? + "up" : "down"); + miimon = READ_ONCE(bond->params.miimon); + seq_printf(seq, "MII Polling Interval (ms): %d\n", miimon); + seq_printf(seq, "Up Delay (ms): %d\n", + READ_ONCE(bond->params.updelay) * miimon); + seq_printf(seq, "Down Delay (ms): %d\n", + READ_ONCE(bond->params.downdelay) * miimon); + seq_printf(seq, "Peer Notification Delay (ms): %d\n", + READ_ONCE(bond->params.peer_notif_delay) * miimon); + + + /* ARP information */ + arp_interval = READ_ONCE(bond->params.arp_interval); + if (arp_interval > 0) { + int printed = 0; + + seq_printf(seq, "ARP Polling Interval (ms): %d\n", + arp_interval); + seq_printf(seq, "ARP Missed Max: %u\n", + READ_ONCE(bond->params.missed_max)); + + seq_printf(seq, "ARP IP target/s (n.n.n.n form):"); + + for (i = 0; (i < BOND_MAX_ARP_TARGETS); i++) { + __be32 t = READ_ONCE(bond->params.arp_targets[i]); + + if (!t) + break; + if (printed) + seq_printf(seq, ","); + seq_printf(seq, " %pI4", &t); + printed = 1; + } + seq_printf(seq, "\n"); + +#if IS_ENABLED(CONFIG_IPV6) + printed = 0; + seq_printf(seq, "NS IPv6 target/s (xx::xx form):"); + + for (i = 0; (i < BOND_MAX_NS_TARGETS); i++) { + if (ipv6_addr_any(&bond->params.ns_targets[i])) + break; + if (printed) + seq_printf(seq, ","); + seq_printf(seq, " %pI6c", &bond->params.ns_targets[i]); + printed = 1; + } + seq_printf(seq, "\n"); +#endif + } + + if (BOND_MODE(bond) == BOND_MODE_8023AD) { + struct ad_info ad_info; + + seq_puts(seq, "\n802.3ad info\n"); + seq_printf(seq, "LACP active: %s\n", + READ_ONCE(bond->params.lacp_active) ? "on" : "off"); + seq_printf(seq, "LACP rate: %s\n", + READ_ONCE(bond->params.lacp_fast) ? "fast" : "slow"); + seq_printf(seq, "Min links: %d\n", + READ_ONCE(bond->params.min_links)); + optval = bond_opt_get_val(BOND_OPT_AD_SELECT, + READ_ONCE(bond->params.ad_select)); + seq_printf(seq, "Aggregator selection policy (ad_select): %s\n", + optval->string); + if (capable(CAP_NET_ADMIN)) { + seq_printf(seq, "System priority: %d\n", + BOND_AD_INFO(bond).system.sys_priority); + seq_printf(seq, "System MAC address: %pM\n", + &BOND_AD_INFO(bond).system.sys_mac_addr); + + if (__bond_3ad_get_active_agg_info(bond, &ad_info)) { + seq_printf(seq, + "bond %s has no active aggregator\n", + bond->dev->name); + } else { + seq_printf(seq, "Active Aggregator Info:\n"); + + seq_printf(seq, "\tAggregator ID: %d\n", + ad_info.aggregator_id); + seq_printf(seq, "\tNumber of ports: %d\n", + ad_info.ports); + seq_printf(seq, "\tActor Key: %d\n", + ad_info.actor_key); + seq_printf(seq, "\tPartner Key: %d\n", + ad_info.partner_key); + seq_printf(seq, "\tPartner Mac Address: %pM\n", + ad_info.partner_system); + } + } + } +} + +/* Note: runs under rcu_read_lock() */ +static void bond_info_show_slave(struct seq_file *seq, + const struct slave *slave) +{ + struct bonding *bond = pde_data(file_inode(seq->file)); + + seq_printf(seq, "\nSlave Interface: %s\n", slave->dev->name); + seq_printf(seq, "MII Status: %s\n", bond_slave_link_status(slave->link)); + if (slave->speed == SPEED_UNKNOWN) + seq_printf(seq, "Speed: %s\n", "Unknown"); + else + seq_printf(seq, "Speed: %d Mbps\n", slave->speed); + + if (slave->duplex == DUPLEX_UNKNOWN) + seq_printf(seq, "Duplex: %s\n", "Unknown"); + else + seq_printf(seq, "Duplex: %s\n", slave->duplex ? "full" : "half"); + + seq_printf(seq, "Link Failure Count: %u\n", + slave->link_failure_count); + + seq_printf(seq, "Permanent HW addr: %*phC\n", + slave->dev->addr_len, slave->perm_hwaddr); + seq_printf(seq, "Slave queue ID: %d\n", READ_ONCE(slave->queue_id)); + + if (BOND_MODE(bond) == BOND_MODE_8023AD) { + const struct port *port = &SLAVE_AD_INFO(slave)->port; + const struct aggregator *agg = rcu_dereference(port->aggregator); + + if (agg) { + seq_printf(seq, "Aggregator ID: %d\n", + agg->aggregator_identifier); + seq_printf(seq, "Actor Churn State: %s\n", + bond_3ad_churn_desc(READ_ONCE(port->sm_churn_actor_state))); + seq_printf(seq, "Partner Churn State: %s\n", + bond_3ad_churn_desc(READ_ONCE(port->sm_churn_partner_state))); + seq_printf(seq, "Actor Churned Count: %d\n", + READ_ONCE(port->churn_actor_count)); + seq_printf(seq, "Partner Churned Count: %d\n", + READ_ONCE(port->churn_partner_count)); + + if (capable(CAP_NET_ADMIN)) { + seq_puts(seq, "details actor lacp pdu:\n"); + seq_printf(seq, " system priority: %d\n", + port->actor_system_priority); + seq_printf(seq, " system mac address: %pM\n", + &port->actor_system); + seq_printf(seq, " port key: %d\n", + port->actor_oper_port_key); + seq_printf(seq, " port priority: %d\n", + port->actor_port_priority); + seq_printf(seq, " port number: %d\n", + port->actor_port_number); + seq_printf(seq, " port state: %d\n", + port->actor_oper_port_state); + + seq_puts(seq, "details partner lacp pdu:\n"); + seq_printf(seq, " system priority: %d\n", + port->partner_oper.system_priority); + seq_printf(seq, " system mac address: %pM\n", + &port->partner_oper.system); + seq_printf(seq, " oper key: %d\n", + port->partner_oper.key); + seq_printf(seq, " port priority: %d\n", + port->partner_oper.port_priority); + seq_printf(seq, " port number: %d\n", + port->partner_oper.port_number); + seq_printf(seq, " port state: %d\n", + port->partner_oper.port_state); + } + } else { + seq_puts(seq, "Aggregator ID: N/A\n"); + } + } +} + +static int bond_info_seq_show(struct seq_file *seq, void *v) +{ + if (v == SEQ_START_TOKEN) { + seq_printf(seq, "%s\n", bond_version); + bond_info_show_master(seq); + } else + bond_info_show_slave(seq, v); + + return 0; +} + +static const struct seq_operations bond_info_seq_ops = { + .start = bond_info_seq_start, + .next = bond_info_seq_next, + .stop = bond_info_seq_stop, + .show = bond_info_seq_show, +}; + +void bond_create_proc_entry(struct bonding *bond) +{ + struct net_device *bond_dev = bond->dev; + struct bond_net *bn = net_generic(dev_net(bond_dev), bond_net_id); + + if (bn->proc_dir) { + bond->proc_entry = proc_create_seq_data(bond_dev->name, 0444, + bn->proc_dir, &bond_info_seq_ops, bond); + if (bond->proc_entry == NULL) + netdev_warn(bond_dev, "Cannot create /proc/net/%s/%s\n", + KBUILD_MODNAME, bond_dev->name); + else + memcpy(bond->proc_file_name, bond_dev->name, IFNAMSIZ); + } +} + +void bond_remove_proc_entry(struct bonding *bond) +{ + struct net_device *bond_dev = bond->dev; + struct bond_net *bn = net_generic(dev_net(bond_dev), bond_net_id); + + if (bn->proc_dir && bond->proc_entry) { + remove_proc_entry(bond->proc_file_name, bn->proc_dir); + memset(bond->proc_file_name, 0, IFNAMSIZ); + bond->proc_entry = NULL; + } +} + +/* Create the bonding directory under /proc/net, if doesn't exist yet. + * Caller must hold rtnl_lock. + */ +void __net_init bond_create_proc_dir(struct bond_net *bn) +{ + if (!bn->proc_dir) { + bn->proc_dir = proc_mkdir(KBUILD_MODNAME, bn->net->proc_net); + if (!bn->proc_dir) + pr_warn("Warning: Cannot create /proc/net/%s\n", + KBUILD_MODNAME); + } +} + +/* Destroy the bonding directory under /proc/net, if empty. + */ +void __net_exit bond_destroy_proc_dir(struct bond_net *bn) +{ + if (bn->proc_dir) { + remove_proc_entry(KBUILD_MODNAME, bn->net->proc_net); + bn->proc_dir = NULL; + } +} diff --git a/drivers/net/bonding/bond_sysfs.c b/drivers/net/bonding/bond_sysfs.c new file mode 100644 index 000000000..3232406cb --- /dev/null +++ b/drivers/net/bonding/bond_sysfs.c @@ -0,0 +1,864 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright(c) 2004-2005 Intel Corporation. All rights reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define to_bond(cd) ((struct bonding *)(netdev_priv(to_net_dev(cd)))) + +/* "show" function for the bond_masters attribute. + * The class parameter is ignored. + */ +static ssize_t bonding_show_bonds(const struct class *cls, + const struct class_attribute *attr, + char *buf) +{ + const struct bond_net *bn = + container_of_const(attr, struct bond_net, class_attr_bonding_masters); + struct bonding *bond; + int res = 0; + + rcu_read_lock(); + + list_for_each_entry_rcu(bond, &bn->dev_list, bond_list) { + if (res > (PAGE_SIZE - IFNAMSIZ)) { + /* not enough space for another interface name */ + if ((PAGE_SIZE - res) > 10) + res = PAGE_SIZE - 10; + res += sysfs_emit_at(buf, res, "++more++ "); + break; + } + res += sysfs_emit_at(buf, res, "%s ", bond->dev->name); + } + if (res) + buf[res-1] = '\n'; /* eat the leftover space */ + + rcu_read_unlock(); + return res; +} + +static struct net_device *bond_get_by_name(const struct bond_net *bn, const char *ifname) +{ + struct bonding *bond; + + list_for_each_entry(bond, &bn->dev_list, bond_list) { + if (strncmp(bond->dev->name, ifname, IFNAMSIZ) == 0) + return bond->dev; + } + return NULL; +} + +/* "store" function for the bond_masters attribute. This is what + * creates and deletes entire bonds. + * + * The class parameter is ignored. + */ +static ssize_t bonding_store_bonds(const struct class *cls, + const struct class_attribute *attr, + const char *buffer, size_t count) +{ + const struct bond_net *bn = + container_of_const(attr, struct bond_net, class_attr_bonding_masters); + char command[IFNAMSIZ + 1] = {0, }; + char *ifname; + int rv, res = count; + + sscanf(buffer, "%16s", command); /* IFNAMSIZ*/ + ifname = command + 1; + if ((strlen(command) <= 1) || + !dev_valid_name(ifname)) + goto err_no_cmd; + + if (command[0] == '+') { + pr_info("%s is being created...\n", ifname); + rv = bond_create(bn->net, ifname); + if (rv) { + if (rv == -EEXIST) + pr_info("%s already exists\n", ifname); + else + pr_info("%s creation failed\n", ifname); + res = rv; + } + } else if (command[0] == '-') { + struct net_device *bond_dev; + + rtnl_lock(); + bond_dev = bond_get_by_name(bn, ifname); + if (bond_dev) { + pr_info("%s is being deleted...\n", ifname); + unregister_netdevice(bond_dev); + } else { + pr_err("unable to delete non-existent %s\n", ifname); + res = -ENODEV; + } + rtnl_unlock(); + } else + goto err_no_cmd; + + /* Always return either count or an error. If you return 0, you'll + * get called forever, which is bad. + */ + return res; + +err_no_cmd: + pr_err("no command found in bonding_masters - use +ifname or -ifname\n"); + return -EPERM; +} + +/* class attribute for bond_masters file. This ends up in /sys/class/net */ +static const struct class_attribute class_attr_bonding_masters = { + .attr = { + .name = "bonding_masters", + .mode = 0644, + }, + .show = bonding_show_bonds, + .store = bonding_store_bonds, +}; + +/* Generic "store" method for bonding sysfs option setting */ +static ssize_t bonding_sysfs_store_option(struct device *d, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + struct bonding *bond = to_bond(d); + const struct bond_option *opt; + char *buffer_clone; + int ret; + + opt = bond_opt_get_by_name(attr->attr.name); + if (WARN_ON(!opt)) + return -ENOENT; + buffer_clone = kstrndup(buffer, count, GFP_KERNEL); + if (!buffer_clone) + return -ENOMEM; + ret = bond_opt_tryset_rtnl(bond, opt->id, buffer_clone); + if (!ret) + ret = count; + kfree(buffer_clone); + + return ret; +} + +/* Show the slaves in the current bond. */ +static ssize_t bonding_show_slaves(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct bonding *bond = to_bond(d); + struct list_head *iter; + struct slave *slave; + int res = 0; + + rcu_read_lock(); + + bond_for_each_slave_rcu(bond, slave, iter) { + if (res > (PAGE_SIZE - IFNAMSIZ)) { + /* not enough space for another interface name */ + if ((PAGE_SIZE - res) > 10) + res = PAGE_SIZE - 10; + res += sysfs_emit_at(buf, res, "++more++ "); + break; + } + res += sysfs_emit_at(buf, res, "%s ", slave->dev->name); + } + + rcu_read_unlock(); + + if (res) + buf[res-1] = '\n'; /* eat the leftover space */ + + return res; +} +static DEVICE_ATTR(slaves, 0644, bonding_show_slaves, + bonding_sysfs_store_option); + +/* Show the bonding mode. */ +static ssize_t bonding_show_mode(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct bonding *bond = to_bond(d); + const struct bond_opt_value *val; + + val = bond_opt_get_val(BOND_OPT_MODE, BOND_MODE(bond)); + + return sysfs_emit(buf, "%s %d\n", val->string, BOND_MODE(bond)); +} +static DEVICE_ATTR(mode, 0644, bonding_show_mode, bonding_sysfs_store_option); + +/* Show the bonding transmit hash method. */ +static ssize_t bonding_show_xmit_hash(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + const struct bond_opt_value *val; + int xmit_policy; + + xmit_policy = READ_ONCE(bond->params.xmit_policy); + val = bond_opt_get_val(BOND_OPT_XMIT_HASH, xmit_policy); + + return sysfs_emit(buf, "%s %d\n", val->string, xmit_policy); +} +static DEVICE_ATTR(xmit_hash_policy, 0644, + bonding_show_xmit_hash, bonding_sysfs_store_option); + +/* Show arp_validate. */ +static ssize_t bonding_show_arp_validate(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + const struct bond_opt_value *val; + int arp_validate; + + arp_validate = READ_ONCE(bond->params.arp_validate); + val = bond_opt_get_val(BOND_OPT_ARP_VALIDATE, arp_validate); + + return sysfs_emit(buf, "%s %d\n", val->string, arp_validate); +} +static DEVICE_ATTR(arp_validate, 0644, bonding_show_arp_validate, + bonding_sysfs_store_option); + +/* Show arp_all_targets. */ +static ssize_t bonding_show_arp_all_targets(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + const struct bond_opt_value *val; + int arp_all_targets; + + arp_all_targets = READ_ONCE(bond->params.arp_all_targets); + val = bond_opt_get_val(BOND_OPT_ARP_ALL_TARGETS, arp_all_targets); + return sysfs_emit(buf, "%s %d\n", val->string, arp_all_targets); +} +static DEVICE_ATTR(arp_all_targets, 0644, + bonding_show_arp_all_targets, bonding_sysfs_store_option); + +/* Show fail_over_mac. */ +static ssize_t bonding_show_fail_over_mac(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + const struct bond_opt_value *val; + int fail_over_mac; + + fail_over_mac = READ_ONCE(bond->params.fail_over_mac); + val = bond_opt_get_val(BOND_OPT_FAIL_OVER_MAC, fail_over_mac); + + return sysfs_emit(buf, "%s %d\n", val->string, fail_over_mac); +} +static DEVICE_ATTR(fail_over_mac, 0644, + bonding_show_fail_over_mac, bonding_sysfs_store_option); + +/* Show the arp timer interval. */ +static ssize_t bonding_show_arp_interval(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + return sysfs_emit(buf, "%d\n", READ_ONCE(bond->params.arp_interval)); +} +static DEVICE_ATTR(arp_interval, 0644, + bonding_show_arp_interval, bonding_sysfs_store_option); + +/* Show the arp targets. */ +static ssize_t bonding_show_arp_targets(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + int i, res = 0; + + for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) { + __be32 t = READ_ONCE(bond->params.arp_targets[i]); + + if (t) + res += sysfs_emit_at(buf, res, "%pI4 ", &t); + } + if (res) + buf[res-1] = '\n'; /* eat the leftover space */ + + return res; +} +static DEVICE_ATTR(arp_ip_target, 0644, + bonding_show_arp_targets, bonding_sysfs_store_option); + +/* Show the arp missed max. */ +static ssize_t bonding_show_missed_max(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + return sysfs_emit(buf, "%u\n", READ_ONCE(bond->params.missed_max)); +} +static DEVICE_ATTR(arp_missed_max, 0644, + bonding_show_missed_max, bonding_sysfs_store_option); + +/* Show the up and down delays. */ +static ssize_t bonding_show_downdelay(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + return sysfs_emit(buf, "%d\n", READ_ONCE(bond->params.downdelay) * + READ_ONCE(bond->params.miimon)); +} +static DEVICE_ATTR(downdelay, 0644, + bonding_show_downdelay, bonding_sysfs_store_option); + +static ssize_t bonding_show_updelay(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + return sysfs_emit(buf, "%d\n", READ_ONCE(bond->params.updelay) * + READ_ONCE(bond->params.miimon)); + +} +static DEVICE_ATTR(updelay, 0644, + bonding_show_updelay, bonding_sysfs_store_option); + +static ssize_t bonding_show_peer_notif_delay(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + return sysfs_emit(buf, "%d\n", + READ_ONCE(bond->params.peer_notif_delay) * + READ_ONCE(bond->params.miimon)); +} +static DEVICE_ATTR(peer_notif_delay, 0644, + bonding_show_peer_notif_delay, bonding_sysfs_store_option); + +/* Show the LACP activity and interval. */ +static ssize_t bonding_show_lacp_active(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + const struct bond_opt_value *val; + int lacp_active; + + lacp_active = READ_ONCE(bond->params.lacp_active); + val = bond_opt_get_val(BOND_OPT_LACP_ACTIVE, lacp_active); + + return sysfs_emit(buf, "%s %d\n", val->string, lacp_active); +} +static DEVICE_ATTR(lacp_active, 0644, + bonding_show_lacp_active, bonding_sysfs_store_option); + +static ssize_t bonding_show_lacp_rate(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + const struct bond_opt_value *val; + int lacp_fast; + + lacp_fast = READ_ONCE(bond->params.lacp_fast); + val = bond_opt_get_val(BOND_OPT_LACP_RATE, lacp_fast); + + return sysfs_emit(buf, "%s %d\n", val->string, lacp_fast); +} +static DEVICE_ATTR(lacp_rate, 0644, + bonding_show_lacp_rate, bonding_sysfs_store_option); + +static ssize_t bonding_show_min_links(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + return sysfs_emit(buf, "%u\n", READ_ONCE(bond->params.min_links)); +} +static DEVICE_ATTR(min_links, 0644, + bonding_show_min_links, bonding_sysfs_store_option); + +static ssize_t bonding_show_ad_select(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + const struct bond_opt_value *val; + int ad_select; + + ad_select = READ_ONCE(bond->params.ad_select); + val = bond_opt_get_val(BOND_OPT_AD_SELECT, ad_select); + + return sysfs_emit(buf, "%s %d\n", val->string, ad_select); +} +static DEVICE_ATTR(ad_select, 0644, + bonding_show_ad_select, bonding_sysfs_store_option); + +/* Show the number of peer notifications to send after a failover event. */ +static ssize_t bonding_show_num_peer_notif(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + return sysfs_emit(buf, "%d\n", READ_ONCE(bond->params.num_peer_notif)); +} +static DEVICE_ATTR(num_grat_arp, 0644, + bonding_show_num_peer_notif, bonding_sysfs_store_option); +static DEVICE_ATTR(num_unsol_na, 0644, + bonding_show_num_peer_notif, bonding_sysfs_store_option); + +/* Show the MII monitor interval. */ +static ssize_t bonding_show_miimon(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + return sysfs_emit(buf, "%d\n", READ_ONCE(bond->params.miimon)); +} +static DEVICE_ATTR(miimon, 0644, + bonding_show_miimon, bonding_sysfs_store_option); + +/* Show the primary slave. */ +static ssize_t bonding_show_primary(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + struct slave *primary; + int count = 0; + + rcu_read_lock(); + primary = rcu_dereference(bond->primary_slave); + if (primary) + count = sysfs_emit(buf, "%s\n", primary->dev->name); + rcu_read_unlock(); + + return count; +} +static DEVICE_ATTR(primary, 0644, + bonding_show_primary, bonding_sysfs_store_option); + +/* Show the primary_reselect flag. */ +static ssize_t bonding_show_primary_reselect(struct device *d, + struct device_attribute *attr, + char *buf) +{ + const struct bonding *bond = to_bond(d); + const struct bond_opt_value *val; + int primary_reselect; + + primary_reselect = READ_ONCE(bond->params.primary_reselect); + + val = bond_opt_get_val(BOND_OPT_PRIMARY_RESELECT, primary_reselect); + + return sysfs_emit(buf, "%s %d\n", val->string, primary_reselect); +} +static DEVICE_ATTR(primary_reselect, 0644, + bonding_show_primary_reselect, bonding_sysfs_store_option); + +/* use_carrier is obsolete, but print value for compatibility */ +static ssize_t bonding_show_carrier(struct device *d, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "1\n"); +} +static DEVICE_ATTR(use_carrier, 0644, + bonding_show_carrier, bonding_sysfs_store_option); + + +/* Show currently active_slave. */ +static ssize_t bonding_show_active_slave(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + struct net_device *slave_dev; + int count = 0; + + rcu_read_lock(); + slave_dev = bond_option_active_slave_get_rcu(bond); + if (slave_dev) + count = sysfs_emit(buf, "%s\n", slave_dev->name); + rcu_read_unlock(); + + return count; +} +static DEVICE_ATTR(active_slave, 0644, + bonding_show_active_slave, bonding_sysfs_store_option); + +/* Show link status of the bond interface. */ +static ssize_t bonding_show_mii_status(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + bool active = netif_carrier_ok(bond->dev); + + return sysfs_emit(buf, "%s\n", active ? "up" : "down"); +} +static DEVICE_ATTR(mii_status, 0444, bonding_show_mii_status, NULL); + +/* Show current 802.3ad aggregator ID. */ +static ssize_t bonding_show_ad_aggregator(struct device *d, + struct device_attribute *attr, + char *buf) +{ + int count = 0; + struct bonding *bond = to_bond(d); + + if (BOND_MODE(bond) == BOND_MODE_8023AD) { + struct ad_info ad_info; + + count = sysfs_emit(buf, "%d\n", + bond_3ad_get_active_agg_info(bond, &ad_info) + ? 0 : ad_info.aggregator_id); + } + + return count; +} +static DEVICE_ATTR(ad_aggregator, 0444, bonding_show_ad_aggregator, NULL); + + +/* Show number of active 802.3ad ports. */ +static ssize_t bonding_show_ad_num_ports(struct device *d, + struct device_attribute *attr, + char *buf) +{ + int count = 0; + struct bonding *bond = to_bond(d); + + if (BOND_MODE(bond) == BOND_MODE_8023AD) { + struct ad_info ad_info; + + count = sysfs_emit(buf, "%d\n", + bond_3ad_get_active_agg_info(bond, &ad_info) + ? 0 : ad_info.ports); + } + + return count; +} +static DEVICE_ATTR(ad_num_ports, 0444, bonding_show_ad_num_ports, NULL); + + +/* Show current 802.3ad actor key. */ +static ssize_t bonding_show_ad_actor_key(struct device *d, + struct device_attribute *attr, + char *buf) +{ + int count = 0; + struct bonding *bond = to_bond(d); + + if (BOND_MODE(bond) == BOND_MODE_8023AD && capable(CAP_NET_ADMIN)) { + struct ad_info ad_info; + + count = sysfs_emit(buf, "%d\n", + bond_3ad_get_active_agg_info(bond, &ad_info) + ? 0 : ad_info.actor_key); + } + + return count; +} +static DEVICE_ATTR(ad_actor_key, 0444, bonding_show_ad_actor_key, NULL); + + +/* Show current 802.3ad partner key. */ +static ssize_t bonding_show_ad_partner_key(struct device *d, + struct device_attribute *attr, + char *buf) +{ + int count = 0; + struct bonding *bond = to_bond(d); + + if (BOND_MODE(bond) == BOND_MODE_8023AD && capable(CAP_NET_ADMIN)) { + struct ad_info ad_info; + + count = sysfs_emit(buf, "%d\n", + bond_3ad_get_active_agg_info(bond, &ad_info) + ? 0 : ad_info.partner_key); + } + + return count; +} +static DEVICE_ATTR(ad_partner_key, 0444, bonding_show_ad_partner_key, NULL); + + +/* Show current 802.3ad partner mac. */ +static ssize_t bonding_show_ad_partner_mac(struct device *d, + struct device_attribute *attr, + char *buf) +{ + int count = 0; + struct bonding *bond = to_bond(d); + + if (BOND_MODE(bond) == BOND_MODE_8023AD && capable(CAP_NET_ADMIN)) { + struct ad_info ad_info; + + if (!bond_3ad_get_active_agg_info(bond, &ad_info)) + count = sysfs_emit(buf, "%pM\n", ad_info.partner_system); + } + + return count; +} +static DEVICE_ATTR(ad_partner_mac, 0444, bonding_show_ad_partner_mac, NULL); + +/* Show the queue_ids of the slaves in the current bond. */ +static ssize_t bonding_show_queue_id(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + struct list_head *iter; + struct slave *slave; + int res = 0; + + rcu_read_lock(); + + bond_for_each_slave_rcu(bond, slave, iter) { + if (res > (PAGE_SIZE - IFNAMSIZ - 6)) { + /* not enough space for another interface_name:queue_id pair */ + if ((PAGE_SIZE - res) > 10) + res = PAGE_SIZE - 10; + res += sysfs_emit_at(buf, res, "++more++ "); + break; + } + res += sysfs_emit_at(buf, res, "%s:%d ", + slave->dev->name, + READ_ONCE(slave->queue_id)); + } + if (res) + buf[res-1] = '\n'; /* eat the leftover space */ + + rcu_read_unlock(); + + return res; +} +static DEVICE_ATTR(queue_id, 0644, bonding_show_queue_id, + bonding_sysfs_store_option); + + +/* Show the all_slaves_active flag. */ +static ssize_t bonding_show_slaves_active(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + return sysfs_emit(buf, "%d\n", READ_ONCE(bond->params.all_slaves_active)); +} +static DEVICE_ATTR(all_slaves_active, 0644, + bonding_show_slaves_active, bonding_sysfs_store_option); + +/* Show the number of IGMP membership reports to send on link failure */ +static ssize_t bonding_show_resend_igmp(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + return sysfs_emit(buf, "%d\n", READ_ONCE(bond->params.resend_igmp)); +} +static DEVICE_ATTR(resend_igmp, 0644, + bonding_show_resend_igmp, bonding_sysfs_store_option); + + +static ssize_t bonding_show_lp_interval(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + return sysfs_emit(buf, "%d\n", READ_ONCE(bond->params.lp_interval)); +} +static DEVICE_ATTR(lp_interval, 0644, + bonding_show_lp_interval, bonding_sysfs_store_option); + +static ssize_t bonding_show_tlb_dynamic_lb(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + return sysfs_emit(buf, "%d\n", READ_ONCE(bond->params.tlb_dynamic_lb)); +} +static DEVICE_ATTR(tlb_dynamic_lb, 0644, + bonding_show_tlb_dynamic_lb, bonding_sysfs_store_option); + +static ssize_t bonding_show_packets_per_slave(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + unsigned int packets_per_slave = READ_ONCE(bond->params.packets_per_slave); + + return sysfs_emit(buf, "%u\n", packets_per_slave); +} +static DEVICE_ATTR(packets_per_slave, 0644, + bonding_show_packets_per_slave, bonding_sysfs_store_option); + +static ssize_t bonding_show_ad_actor_sys_prio(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + if (BOND_MODE(bond) == BOND_MODE_8023AD && capable(CAP_NET_ADMIN)) + return sysfs_emit(buf, "%hu\n", READ_ONCE(bond->params.ad_actor_sys_prio)); + + return 0; +} +static DEVICE_ATTR(ad_actor_sys_prio, 0644, + bonding_show_ad_actor_sys_prio, bonding_sysfs_store_option); + +static ssize_t bonding_show_ad_actor_system(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + if (BOND_MODE(bond) == BOND_MODE_8023AD && capable(CAP_NET_ADMIN)) + return sysfs_emit(buf, "%pM\n", bond->params.ad_actor_system); + + return 0; +} + +static DEVICE_ATTR(ad_actor_system, 0644, + bonding_show_ad_actor_system, bonding_sysfs_store_option); + +static ssize_t bonding_show_ad_user_port_key(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct bonding *bond = to_bond(d); + + if (BOND_MODE(bond) == BOND_MODE_8023AD && capable(CAP_NET_ADMIN)) + return sysfs_emit(buf, "%hu\n", + READ_ONCE(bond->params.ad_user_port_key)); + + return 0; +} +static DEVICE_ATTR(ad_user_port_key, 0644, + bonding_show_ad_user_port_key, bonding_sysfs_store_option); + +static struct attribute *per_bond_attrs[] = { + &dev_attr_slaves.attr, + &dev_attr_mode.attr, + &dev_attr_fail_over_mac.attr, + &dev_attr_arp_validate.attr, + &dev_attr_arp_all_targets.attr, + &dev_attr_arp_interval.attr, + &dev_attr_arp_ip_target.attr, + &dev_attr_downdelay.attr, + &dev_attr_updelay.attr, + &dev_attr_peer_notif_delay.attr, + &dev_attr_lacp_active.attr, + &dev_attr_lacp_rate.attr, + &dev_attr_ad_select.attr, + &dev_attr_xmit_hash_policy.attr, + &dev_attr_num_grat_arp.attr, + &dev_attr_num_unsol_na.attr, + &dev_attr_miimon.attr, + &dev_attr_primary.attr, + &dev_attr_primary_reselect.attr, + &dev_attr_use_carrier.attr, + &dev_attr_active_slave.attr, + &dev_attr_mii_status.attr, + &dev_attr_ad_aggregator.attr, + &dev_attr_ad_num_ports.attr, + &dev_attr_ad_actor_key.attr, + &dev_attr_ad_partner_key.attr, + &dev_attr_ad_partner_mac.attr, + &dev_attr_queue_id.attr, + &dev_attr_all_slaves_active.attr, + &dev_attr_resend_igmp.attr, + &dev_attr_min_links.attr, + &dev_attr_lp_interval.attr, + &dev_attr_packets_per_slave.attr, + &dev_attr_tlb_dynamic_lb.attr, + &dev_attr_ad_actor_sys_prio.attr, + &dev_attr_ad_actor_system.attr, + &dev_attr_ad_user_port_key.attr, + &dev_attr_arp_missed_max.attr, + NULL, +}; + +static const struct attribute_group bonding_group = { + .name = "bonding", + .attrs = per_bond_attrs, +}; + +/* Initialize sysfs. This sets up the bonding_masters file in + * /sys/class/net. + */ +int __net_init bond_create_sysfs(struct bond_net *bn) +{ + int ret; + + bn->class_attr_bonding_masters = class_attr_bonding_masters; + sysfs_attr_init(&bn->class_attr_bonding_masters.attr); + + ret = netdev_class_create_file_ns(&bn->class_attr_bonding_masters, + to_ns_common(bn->net)); + /* Permit multiple loads of the module by ignoring failures to + * create the bonding_masters sysfs file. Bonding devices + * created by second or subsequent loads of the module will + * not be listed in, or controllable by, bonding_masters, but + * will have the usual "bonding" sysfs directory. + * + * This is done to preserve backwards compatibility for + * initscripts/sysconfig, which load bonding multiple times to + * configure multiple bonding devices. + */ + if (ret == -EEXIST) { + /* Is someone being kinky and naming a device bonding_master? */ + if (netdev_name_in_use(bn->net, + class_attr_bonding_masters.attr.name)) + pr_err("network device named %s already exists in sysfs\n", + class_attr_bonding_masters.attr.name); + ret = 0; + } + + return ret; + +} + +/* Remove /sys/class/net/bonding_masters. */ +void __net_exit bond_destroy_sysfs(struct bond_net *bn) +{ + netdev_class_remove_file_ns(&bn->class_attr_bonding_masters, to_ns_common(bn->net)); +} + +/* Initialize sysfs for each bond. This sets up and registers + * the 'bondctl' directory for each individual bond under /sys/class/net. + */ +void bond_prepare_sysfs_group(struct bonding *bond) +{ + bond->dev->sysfs_groups[0] = &bonding_group; +} + diff --git a/drivers/net/bonding/bond_sysfs_slave.c b/drivers/net/bonding/bond_sysfs_slave.c new file mode 100644 index 000000000..fc6fe7181 --- /dev/null +++ b/drivers/net/bonding/bond_sysfs_slave.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Sysfs attributes of bond slaves + * + * Copyright (c) 2014 Scott Feldman + */ + +#include +#include +#include + +#include + +struct slave_attribute { + struct attribute attr; + ssize_t (*show)(struct slave *, char *); +}; + +#define SLAVE_ATTR_RO(_name) \ +const struct slave_attribute slave_attr_##_name = __ATTR_RO(_name) + +static ssize_t state_show(struct slave *slave, char *buf) +{ + switch (bond_slave_state(slave)) { + case BOND_STATE_ACTIVE: + return sysfs_emit(buf, "active\n"); + case BOND_STATE_BACKUP: + return sysfs_emit(buf, "backup\n"); + default: + return sysfs_emit(buf, "UNKNOWN\n"); + } +} +static SLAVE_ATTR_RO(state); + +static ssize_t mii_status_show(struct slave *slave, char *buf) +{ + return sysfs_emit(buf, "%s\n", bond_slave_link_status(slave->link)); +} +static SLAVE_ATTR_RO(mii_status); + +static ssize_t link_failure_count_show(struct slave *slave, char *buf) +{ + return sysfs_emit(buf, "%d\n", slave->link_failure_count); +} +static SLAVE_ATTR_RO(link_failure_count); + +static ssize_t perm_hwaddr_show(struct slave *slave, char *buf) +{ + return sysfs_emit(buf, "%*phC\n", + slave->dev->addr_len, + slave->perm_hwaddr); +} +static SLAVE_ATTR_RO(perm_hwaddr); + +static ssize_t queue_id_show(struct slave *slave, char *buf) +{ + return sysfs_emit(buf, "%d\n", READ_ONCE(slave->queue_id)); +} +static SLAVE_ATTR_RO(queue_id); + +static ssize_t ad_aggregator_id_show(struct slave *slave, char *buf) +{ + const struct aggregator *agg; + + if (BOND_MODE(slave->bond) == BOND_MODE_8023AD) { + rcu_read_lock(); + agg = rcu_dereference(SLAVE_AD_INFO(slave)->port.aggregator); + if (agg) { + ssize_t res = sysfs_emit(buf, "%d\n", + agg->aggregator_identifier); + rcu_read_unlock(); + return res; + } + rcu_read_unlock(); + } + + return sysfs_emit(buf, "N/A\n"); +} +static SLAVE_ATTR_RO(ad_aggregator_id); + +static ssize_t ad_actor_oper_port_state_show(struct slave *slave, char *buf) +{ + const struct port *ad_port; + + if (BOND_MODE(slave->bond) == BOND_MODE_8023AD) { + ad_port = &SLAVE_AD_INFO(slave)->port; + if (rcu_access_pointer(ad_port->aggregator)) + return sysfs_emit(buf, "%u\n", + ad_port->actor_oper_port_state); + } + + return sysfs_emit(buf, "N/A\n"); +} +static SLAVE_ATTR_RO(ad_actor_oper_port_state); + +static ssize_t ad_partner_oper_port_state_show(struct slave *slave, char *buf) +{ + const struct port *ad_port; + + if (BOND_MODE(slave->bond) == BOND_MODE_8023AD) { + ad_port = &SLAVE_AD_INFO(slave)->port; + if (rcu_access_pointer(ad_port->aggregator)) + return sysfs_emit(buf, "%u\n", + ad_port->partner_oper.port_state); + } + + return sysfs_emit(buf, "N/A\n"); +} +static SLAVE_ATTR_RO(ad_partner_oper_port_state); + +static const struct attribute *slave_attrs[] = { + &slave_attr_state.attr, + &slave_attr_mii_status.attr, + &slave_attr_link_failure_count.attr, + &slave_attr_perm_hwaddr.attr, + &slave_attr_queue_id.attr, + &slave_attr_ad_aggregator_id.attr, + &slave_attr_ad_actor_oper_port_state.attr, + &slave_attr_ad_partner_oper_port_state.attr, + NULL +}; + +#define to_slave_attr(_at) container_of(_at, struct slave_attribute, attr) + +static ssize_t slave_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct slave_attribute *slave_attr = to_slave_attr(attr); + struct slave *slave = to_slave(kobj); + + return slave_attr->show(slave, buf); +} + +const struct sysfs_ops slave_sysfs_ops = { + .show = slave_show, +}; + +int bond_sysfs_slave_add(struct slave *slave) +{ + return sysfs_create_files(&slave->kobj, slave_attrs); +} + +void bond_sysfs_slave_del(struct slave *slave) +{ + sysfs_remove_files(&slave->kobj, slave_attrs); +} -- cgit v1.3.1