1b76b31693
Convert bonding timers to workqueues. This converts the various monitor functions to run in periodic work queues instead of timers. This patch introduces the framework and convers the calls, but does not resolve various locking issues, and does not stand alone. Signed-off-by: Andy Gospodarek <andy@greyhouse.net> Signed-off-by: Jay Vosburgh <fubar@us.ibm.com> Signed-off-by: Jeff Garzik <jeff@garzik.org>
1689 lines
43 KiB
C
1689 lines
43 KiB
C
/*
|
|
* Copyright(c) 1999 - 2004 Intel Corporation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* The full GNU General Public License is included in this distribution in the
|
|
* file called LICENSE.
|
|
*
|
|
*/
|
|
|
|
//#define BONDING_DEBUG 1
|
|
|
|
#include <linux/skbuff.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/pkt_sched.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/ipv6.h>
|
|
#include <linux/if_arp.h>
|
|
#include <linux/if_ether.h>
|
|
#include <linux/if_bonding.h>
|
|
#include <linux/if_vlan.h>
|
|
#include <linux/in.h>
|
|
#include <net/ipx.h>
|
|
#include <net/arp.h>
|
|
#include <asm/byteorder.h>
|
|
#include "bonding.h"
|
|
#include "bond_alb.h"
|
|
|
|
|
|
#define ALB_TIMER_TICKS_PER_SEC 10 /* should be a divisor of HZ */
|
|
#define BOND_TLB_REBALANCE_INTERVAL 10 /* In seconds, periodic re-balancing.
|
|
* Used for division - never set
|
|
* to zero !!!
|
|
*/
|
|
#define BOND_ALB_LP_INTERVAL 1 /* In seconds, periodic send of
|
|
* learning packets to the switch
|
|
*/
|
|
|
|
#define BOND_TLB_REBALANCE_TICKS (BOND_TLB_REBALANCE_INTERVAL \
|
|
* ALB_TIMER_TICKS_PER_SEC)
|
|
|
|
#define BOND_ALB_LP_TICKS (BOND_ALB_LP_INTERVAL \
|
|
* ALB_TIMER_TICKS_PER_SEC)
|
|
|
|
#define TLB_HASH_TABLE_SIZE 256 /* The size of the clients hash table.
|
|
* Note that this value MUST NOT be smaller
|
|
* because the key hash table is BYTE wide !
|
|
*/
|
|
|
|
|
|
#define TLB_NULL_INDEX 0xffffffff
|
|
#define MAX_LP_BURST 3
|
|
|
|
/* rlb defs */
|
|
#define RLB_HASH_TABLE_SIZE 256
|
|
#define RLB_NULL_INDEX 0xffffffff
|
|
#define RLB_UPDATE_DELAY 2*ALB_TIMER_TICKS_PER_SEC /* 2 seconds */
|
|
#define RLB_ARP_BURST_SIZE 2
|
|
#define RLB_UPDATE_RETRY 3 /* 3-ticks - must be smaller than the rlb
|
|
* rebalance interval (5 min).
|
|
*/
|
|
/* RLB_PROMISC_TIMEOUT = 10 sec equals the time that the current slave is
|
|
* promiscuous after failover
|
|
*/
|
|
#define RLB_PROMISC_TIMEOUT 10*ALB_TIMER_TICKS_PER_SEC
|
|
|
|
static const u8 mac_bcast[ETH_ALEN] = {0xff,0xff,0xff,0xff,0xff,0xff};
|
|
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()
|
|
|
|
static inline struct arp_pkt *arp_pkt(const struct sk_buff *skb)
|
|
{
|
|
return (struct arp_pkt *)skb_network_header(skb);
|
|
}
|
|
|
|
/* Forward declaration */
|
|
static void alb_send_learning_packets(struct slave *slave, u8 mac_addr[]);
|
|
|
|
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 _lock_tx_hashtbl(struct bonding *bond)
|
|
{
|
|
spin_lock(&(BOND_ALB_INFO(bond).tx_hashtbl_lock));
|
|
}
|
|
|
|
static inline void _unlock_tx_hashtbl(struct bonding *bond)
|
|
{
|
|
spin_unlock(&(BOND_ALB_INFO(bond).tx_hashtbl_lock));
|
|
}
|
|
|
|
/* Caller must hold tx_hashtbl lock */
|
|
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;
|
|
}
|
|
|
|
/* Caller must hold bond lock for read */
|
|
static void tlb_clear_slave(struct bonding *bond, struct slave *slave, int save_load)
|
|
{
|
|
struct tlb_client_info *tx_hash_table;
|
|
u32 index;
|
|
|
|
_lock_tx_hashtbl(bond);
|
|
|
|
/* clear slave from tx_hashtbl */
|
|
tx_hash_table = BOND_ALB_INFO(bond).tx_hashtbl;
|
|
|
|
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);
|
|
|
|
_unlock_tx_hashtbl(bond);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
spin_lock_init(&(bond_info->tx_hashtbl_lock));
|
|
|
|
new_hashtbl = kzalloc(size, GFP_KERNEL);
|
|
if (!new_hashtbl) {
|
|
printk(KERN_ERR DRV_NAME
|
|
": %s: Error: Failed to allocate TLB hash table\n",
|
|
bond->dev->name);
|
|
return -1;
|
|
}
|
|
_lock_tx_hashtbl(bond);
|
|
|
|
bond_info->tx_hashtbl = new_hashtbl;
|
|
|
|
for (i = 0; i < TLB_HASH_TABLE_SIZE; i++) {
|
|
tlb_init_table_entry(&bond_info->tx_hashtbl[i], 1);
|
|
}
|
|
|
|
_unlock_tx_hashtbl(bond);
|
|
|
|
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));
|
|
|
|
_lock_tx_hashtbl(bond);
|
|
|
|
kfree(bond_info->tx_hashtbl);
|
|
bond_info->tx_hashtbl = NULL;
|
|
|
|
_unlock_tx_hashtbl(bond);
|
|
}
|
|
|
|
/* Caller must hold bond lock for read */
|
|
static struct slave *tlb_get_least_loaded_slave(struct bonding *bond)
|
|
{
|
|
struct slave *slave, *least_loaded;
|
|
s64 max_gap;
|
|
int i, found = 0;
|
|
|
|
/* Find the first enabled slave */
|
|
bond_for_each_slave(bond, slave, i) {
|
|
if (SLAVE_IS_OK(slave)) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
return NULL;
|
|
}
|
|
|
|
least_loaded = slave;
|
|
max_gap = (s64)(slave->speed << 20) - /* Convert to Megabit per sec */
|
|
(s64)(SLAVE_TLB_INFO(slave).load << 3); /* Bytes to bits */
|
|
|
|
/* Find the slave with the largest gap */
|
|
bond_for_each_slave_from(bond, slave, i, least_loaded) {
|
|
if (SLAVE_IS_OK(slave)) {
|
|
s64 gap = (s64)(slave->speed << 20) -
|
|
(s64)(SLAVE_TLB_INFO(slave).load << 3);
|
|
if (max_gap < gap) {
|
|
least_loaded = slave;
|
|
max_gap = gap;
|
|
}
|
|
}
|
|
}
|
|
|
|
return least_loaded;
|
|
}
|
|
|
|
/* Caller must hold bond lock for read */
|
|
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;
|
|
|
|
_lock_tx_hashtbl(bond);
|
|
|
|
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;
|
|
}
|
|
|
|
_unlock_tx_hashtbl(bond);
|
|
|
|
return assigned_slave;
|
|
}
|
|
|
|
/*********************** rlb specific functions ***************************/
|
|
static inline void _lock_rx_hashtbl(struct bonding *bond)
|
|
{
|
|
spin_lock(&(BOND_ALB_INFO(bond).rx_hashtbl_lock));
|
|
}
|
|
|
|
static inline void _unlock_rx_hashtbl(struct bonding *bond)
|
|
{
|
|
spin_unlock(&(BOND_ALB_INFO(bond).rx_hashtbl_lock));
|
|
}
|
|
|
|
/* 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;
|
|
|
|
_lock_rx_hashtbl(bond);
|
|
|
|
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)) {
|
|
/* update the clients MAC address */
|
|
memcpy(client_info->mac_dst, arp->mac_src, ETH_ALEN);
|
|
client_info->ntt = 1;
|
|
bond_info->rx_ntt = 1;
|
|
}
|
|
|
|
_unlock_rx_hashtbl(bond);
|
|
}
|
|
|
|
static int rlb_arp_recv(struct sk_buff *skb, struct net_device *bond_dev, struct packet_type *ptype, struct net_device *orig_dev)
|
|
{
|
|
struct bonding *bond = bond_dev->priv;
|
|
struct arp_pkt *arp = (struct arp_pkt *)skb->data;
|
|
int res = NET_RX_DROP;
|
|
|
|
if (bond_dev->nd_net != &init_net)
|
|
goto out;
|
|
|
|
if (!(bond_dev->flags & IFF_MASTER))
|
|
goto out;
|
|
|
|
if (!arp) {
|
|
dprintk("Packet has no ARP data\n");
|
|
goto out;
|
|
}
|
|
|
|
if (skb->len < sizeof(struct arp_pkt)) {
|
|
dprintk("Packet is too small to be an ARP\n");
|
|
goto out;
|
|
}
|
|
|
|
if (arp->op_code == htons(ARPOP_REPLY)) {
|
|
/* update rx hash table for this ARP */
|
|
rlb_update_entry_from_arp(bond, arp);
|
|
dprintk("Server received an ARP Reply from client\n");
|
|
}
|
|
|
|
res = NET_RX_SUCCESS;
|
|
|
|
out:
|
|
dev_kfree_skb(skb);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Caller must hold bond lock for read */
|
|
static struct slave *rlb_next_rx_slave(struct bonding *bond)
|
|
{
|
|
struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond));
|
|
struct slave *rx_slave, *slave, *start_at;
|
|
int i = 0;
|
|
|
|
if (bond_info->next_rx_slave) {
|
|
start_at = bond_info->next_rx_slave;
|
|
} else {
|
|
start_at = bond->first_slave;
|
|
}
|
|
|
|
rx_slave = NULL;
|
|
|
|
bond_for_each_slave_from(bond, slave, i, start_at) {
|
|
if (SLAVE_IS_OK(slave)) {
|
|
if (!rx_slave) {
|
|
rx_slave = slave;
|
|
} else if (slave->speed > rx_slave->speed) {
|
|
rx_slave = slave;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rx_slave) {
|
|
bond_info->next_rx_slave = rx_slave->next;
|
|
}
|
|
|
|
return rx_slave;
|
|
}
|
|
|
|
/* teach the switch the mac of a disabled slave
|
|
* on the primary for fault tolerance
|
|
*
|
|
* Caller must hold bond->curr_slave_lock for write or bond lock for write
|
|
*/
|
|
static void rlb_teach_disabled_mac_on_primary(struct bonding *bond, u8 addr[])
|
|
{
|
|
if (!bond->curr_active_slave) {
|
|
return;
|
|
}
|
|
|
|
if (!bond->alb_info.primary_is_promisc) {
|
|
bond->alb_info.primary_is_promisc = 1;
|
|
dev_set_promiscuity(bond->curr_active_slave->dev, 1);
|
|
}
|
|
|
|
bond->alb_info.rlb_promisc_timeout_counter = 0;
|
|
|
|
alb_send_learning_packets(bond->curr_active_slave, addr);
|
|
}
|
|
|
|
/* slave being removed should not be active at this point
|
|
*
|
|
* Caller must hold bond lock for read
|
|
*/
|
|
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 */
|
|
_lock_rx_hashtbl(bond);
|
|
|
|
rx_hash_table = bond_info->rx_hashtbl;
|
|
index = bond_info->rx_hashtbl_head;
|
|
for (; index != RLB_NULL_INDEX; index = next_index) {
|
|
next_index = rx_hash_table[index].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 (memcmp(rx_hash_table[index].mac_dst,
|
|
mac_bcast, ETH_ALEN)) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
_unlock_rx_hashtbl(bond);
|
|
|
|
write_lock(&bond->curr_slave_lock);
|
|
|
|
if (slave != bond->curr_active_slave) {
|
|
rlb_teach_disabled_mac_on_primary(bond, slave->dev->dev_addr);
|
|
}
|
|
|
|
write_unlock(&bond->curr_slave_lock);
|
|
}
|
|
|
|
static void rlb_update_client(struct rlb_client_info *client_info)
|
|
{
|
|
int i;
|
|
|
|
if (!client_info->slave) {
|
|
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) {
|
|
printk(KERN_ERR DRV_NAME
|
|
": %s: Error: failed to create an ARP packet\n",
|
|
client_info->slave->dev->master->name);
|
|
continue;
|
|
}
|
|
|
|
skb->dev = client_info->slave->dev;
|
|
|
|
if (client_info->tag) {
|
|
skb = vlan_put_tag(skb, client_info->vlan_id);
|
|
if (!skb) {
|
|
printk(KERN_ERR DRV_NAME
|
|
": %s: Error: failed to insert VLAN tag\n",
|
|
client_info->slave->dev->master->name);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
_lock_rx_hashtbl(bond);
|
|
|
|
hash_index = bond_info->rx_hashtbl_head;
|
|
for (; hash_index != RLB_NULL_INDEX; hash_index = client_info->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 untill this counter is zero so that
|
|
* not to confuse the clients.
|
|
*/
|
|
bond_info->rlb_update_delay_counter = RLB_UPDATE_DELAY;
|
|
|
|
_unlock_rx_hashtbl(bond);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
_lock_rx_hashtbl(bond);
|
|
|
|
hash_index = bond_info->rx_hashtbl_head;
|
|
for (; hash_index != RLB_NULL_INDEX; hash_index = client_info->next) {
|
|
client_info = &(bond_info->rx_hashtbl[hash_index]);
|
|
|
|
if ((client_info->slave == slave) &&
|
|
memcmp(client_info->mac_dst, mac_bcast, ETH_ALEN)) {
|
|
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;
|
|
}
|
|
|
|
_unlock_rx_hashtbl(bond);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
_lock_rx_hashtbl(bond);
|
|
|
|
hash_index = bond_info->rx_hashtbl_head;
|
|
for (; hash_index != RLB_NULL_INDEX; hash_index = client_info->next) {
|
|
client_info = &(bond_info->rx_hashtbl[hash_index]);
|
|
|
|
if (!client_info->slave) {
|
|
printk(KERN_ERR DRV_NAME
|
|
": %s: Error: found a client with no channel in "
|
|
"the client's hash table\n",
|
|
bond->dev->name);
|
|
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) &&
|
|
memcmp(client_info->slave->dev->dev_addr,
|
|
bond->dev->dev_addr, ETH_ALEN) &&
|
|
memcmp(client_info->mac_dst, mac_bcast, ETH_ALEN)) {
|
|
client_info->ntt = 1;
|
|
bond_info->rx_ntt = 1;
|
|
}
|
|
}
|
|
|
|
_unlock_rx_hashtbl(bond);
|
|
}
|
|
|
|
/* Caller must hold both bond and ptr locks for read */
|
|
static struct slave *rlb_choose_channel(struct sk_buff *skb, struct bonding *bond)
|
|
{
|
|
struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond));
|
|
struct arp_pkt *arp = arp_pkt(skb);
|
|
struct slave *assigned_slave;
|
|
struct rlb_client_info *client_info;
|
|
u32 hash_index = 0;
|
|
|
|
_lock_rx_hashtbl(bond);
|
|
|
|
hash_index = _simple_hash((u8 *)&arp->ip_dst, sizeof(arp->ip_src));
|
|
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 (memcmp(arp->mac_dst, mac_bcast, ETH_ALEN)) {
|
|
/* update mac address from arp */
|
|
memcpy(client_info->mac_dst, arp->mac_dst, ETH_ALEN);
|
|
}
|
|
|
|
assigned_slave = client_info->slave;
|
|
if (assigned_slave) {
|
|
_unlock_rx_hashtbl(bond);
|
|
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 (bond->curr_active_slave &&
|
|
client_info->slave != bond->curr_active_slave) {
|
|
client_info->slave = bond->curr_active_slave;
|
|
rlb_update_client(client_info);
|
|
}
|
|
}
|
|
}
|
|
/* assign a new slave */
|
|
assigned_slave = rlb_next_rx_slave(bond);
|
|
|
|
if (assigned_slave) {
|
|
client_info->ip_src = arp->ip_src;
|
|
client_info->ip_dst = arp->ip_dst;
|
|
/* arp->mac_dst is broadcast for arp reqeusts.
|
|
* will be updated with clients actual unicast mac address
|
|
* upon receiving an arp reply.
|
|
*/
|
|
memcpy(client_info->mac_dst, arp->mac_dst, ETH_ALEN);
|
|
client_info->slave = assigned_slave;
|
|
|
|
if (memcmp(client_info->mac_dst, mac_bcast, ETH_ALEN)) {
|
|
client_info->ntt = 1;
|
|
bond->alb_info.rx_ntt = 1;
|
|
} else {
|
|
client_info->ntt = 0;
|
|
}
|
|
|
|
if (!list_empty(&bond->vlan_list)) {
|
|
unsigned short vlan_id;
|
|
int res = vlan_get_tag(skb, &vlan_id);
|
|
if (!res) {
|
|
client_info->tag = 1;
|
|
client_info->vlan_id = vlan_id;
|
|
}
|
|
}
|
|
|
|
if (!client_info->assigned) {
|
|
u32 prev_tbl_head = bond_info->rx_hashtbl_head;
|
|
bond_info->rx_hashtbl_head = hash_index;
|
|
client_info->next = prev_tbl_head;
|
|
if (prev_tbl_head != RLB_NULL_INDEX) {
|
|
bond_info->rx_hashtbl[prev_tbl_head].prev =
|
|
hash_index;
|
|
}
|
|
client_info->assigned = 1;
|
|
}
|
|
}
|
|
|
|
_unlock_rx_hashtbl(bond);
|
|
|
|
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 arp_pkt *arp = arp_pkt(skb);
|
|
struct slave *tx_slave = NULL;
|
|
|
|
if (arp->op_code == __constant_htons(ARPOP_REPLY)) {
|
|
/* the arp must be sent on the selected
|
|
* rx channel
|
|
*/
|
|
tx_slave = rlb_choose_channel(skb, bond);
|
|
if (tx_slave) {
|
|
memcpy(arp->mac_src,tx_slave->dev->dev_addr, ETH_ALEN);
|
|
}
|
|
dprintk("Server sent ARP Reply packet\n");
|
|
} else if (arp->op_code == __constant_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.
|
|
*/
|
|
rlb_choose_channel(skb, bond);
|
|
|
|
/* The ARP relpy 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);
|
|
dprintk("Server sent ARP Request packet\n");
|
|
}
|
|
|
|
return tx_slave;
|
|
}
|
|
|
|
/* Caller must hold bond lock for read */
|
|
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;
|
|
|
|
_lock_rx_hashtbl(bond);
|
|
|
|
ntt = 0;
|
|
hash_index = bond_info->rx_hashtbl_head;
|
|
for (; hash_index != RLB_NULL_INDEX; hash_index = client_info->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;
|
|
client_info->ntt = 1;
|
|
ntt = 1;
|
|
}
|
|
}
|
|
|
|
/* update the team's flag only after the whole iteration */
|
|
if (ntt) {
|
|
bond_info->rx_ntt = 1;
|
|
}
|
|
_unlock_rx_hashtbl(bond);
|
|
}
|
|
|
|
/* Caller must hold rx_hashtbl lock */
|
|
static void rlb_init_table_entry(struct rlb_client_info *entry)
|
|
{
|
|
memset(entry, 0, sizeof(struct rlb_client_info));
|
|
entry->next = RLB_NULL_INDEX;
|
|
entry->prev = RLB_NULL_INDEX;
|
|
}
|
|
|
|
static int rlb_initialize(struct bonding *bond)
|
|
{
|
|
struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond));
|
|
struct packet_type *pk_type = &(BOND_ALB_INFO(bond).rlb_pkt_type);
|
|
struct rlb_client_info *new_hashtbl;
|
|
int size = RLB_HASH_TABLE_SIZE * sizeof(struct rlb_client_info);
|
|
int i;
|
|
|
|
spin_lock_init(&(bond_info->rx_hashtbl_lock));
|
|
|
|
new_hashtbl = kmalloc(size, GFP_KERNEL);
|
|
if (!new_hashtbl) {
|
|
printk(KERN_ERR DRV_NAME
|
|
": %s: Error: Failed to allocate RLB hash table\n",
|
|
bond->dev->name);
|
|
return -1;
|
|
}
|
|
_lock_rx_hashtbl(bond);
|
|
|
|
bond_info->rx_hashtbl = new_hashtbl;
|
|
|
|
bond_info->rx_hashtbl_head = RLB_NULL_INDEX;
|
|
|
|
for (i = 0; i < RLB_HASH_TABLE_SIZE; i++) {
|
|
rlb_init_table_entry(bond_info->rx_hashtbl + i);
|
|
}
|
|
|
|
_unlock_rx_hashtbl(bond);
|
|
|
|
/*initialize packet type*/
|
|
pk_type->type = __constant_htons(ETH_P_ARP);
|
|
pk_type->dev = bond->dev;
|
|
pk_type->func = rlb_arp_recv;
|
|
|
|
/* register to receive ARPs */
|
|
dev_add_pack(pk_type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rlb_deinitialize(struct bonding *bond)
|
|
{
|
|
struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond));
|
|
|
|
dev_remove_pack(&(bond_info->rlb_pkt_type));
|
|
|
|
_lock_rx_hashtbl(bond);
|
|
|
|
kfree(bond_info->rx_hashtbl);
|
|
bond_info->rx_hashtbl = NULL;
|
|
bond_info->rx_hashtbl_head = RLB_NULL_INDEX;
|
|
|
|
_unlock_rx_hashtbl(bond);
|
|
}
|
|
|
|
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;
|
|
|
|
_lock_rx_hashtbl(bond);
|
|
|
|
curr_index = bond_info->rx_hashtbl_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].next;
|
|
u32 prev_index = bond_info->rx_hashtbl[curr_index].prev;
|
|
|
|
if (curr->tag && (curr->vlan_id == vlan_id)) {
|
|
if (curr_index == bond_info->rx_hashtbl_head) {
|
|
bond_info->rx_hashtbl_head = next_index;
|
|
}
|
|
if (prev_index != RLB_NULL_INDEX) {
|
|
bond_info->rx_hashtbl[prev_index].next = next_index;
|
|
}
|
|
if (next_index != RLB_NULL_INDEX) {
|
|
bond_info->rx_hashtbl[next_index].prev = prev_index;
|
|
}
|
|
|
|
rlb_init_table_entry(curr);
|
|
}
|
|
|
|
curr_index = next_index;
|
|
}
|
|
|
|
_unlock_rx_hashtbl(bond);
|
|
}
|
|
|
|
/*********************** tlb/rlb shared functions *********************/
|
|
|
|
static void alb_send_learning_packets(struct slave *slave, u8 mac_addr[])
|
|
{
|
|
struct bonding *bond = bond_get_bond_by_slave(slave);
|
|
struct learning_pkt pkt;
|
|
int size = sizeof(struct learning_pkt);
|
|
int i;
|
|
|
|
memset(&pkt, 0, size);
|
|
memcpy(pkt.mac_dst, mac_addr, ETH_ALEN);
|
|
memcpy(pkt.mac_src, mac_addr, ETH_ALEN);
|
|
pkt.type = __constant_htons(ETH_P_LOOP);
|
|
|
|
for (i = 0; i < MAX_LP_BURST; i++) {
|
|
struct sk_buff *skb;
|
|
char *data;
|
|
|
|
skb = dev_alloc_skb(size);
|
|
if (!skb) {
|
|
return;
|
|
}
|
|
|
|
data = skb_put(skb, size);
|
|
memcpy(data, &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;
|
|
|
|
if (!list_empty(&bond->vlan_list)) {
|
|
struct vlan_entry *vlan;
|
|
|
|
vlan = bond_next_vlan(bond,
|
|
bond->alb_info.current_alb_vlan);
|
|
|
|
bond->alb_info.current_alb_vlan = vlan;
|
|
if (!vlan) {
|
|
kfree_skb(skb);
|
|
continue;
|
|
}
|
|
|
|
skb = vlan_put_tag(skb, vlan->vlan_id);
|
|
if (!skb) {
|
|
printk(KERN_ERR DRV_NAME
|
|
": %s: Error: failed to insert VLAN tag\n",
|
|
bond->dev->name);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
dev_queue_xmit(skb);
|
|
}
|
|
}
|
|
|
|
/* hw is a boolean parameter that determines whether we should try and
|
|
* set the hw address of the device as well as the hw address of the
|
|
* net_device
|
|
*/
|
|
static int alb_set_slave_mac_addr(struct slave *slave, u8 addr[], int hw)
|
|
{
|
|
struct net_device *dev = slave->dev;
|
|
struct sockaddr s_addr;
|
|
|
|
if (!hw) {
|
|
memcpy(dev->dev_addr, addr, 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(s_addr.sa_data, addr, dev->addr_len);
|
|
s_addr.sa_family = dev->type;
|
|
if (dev_set_mac_address(dev, &s_addr)) {
|
|
printk(KERN_ERR DRV_NAME
|
|
": %s: Error: dev_set_mac_address of dev %s failed! ALB "
|
|
"mode requires that the base driver support setting "
|
|
"the hw address also when the network device's "
|
|
"interface is open\n",
|
|
dev->master->name, dev->name);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Caller must hold bond lock for write or curr_slave_lock for write*/
|
|
static void alb_swap_mac_addr(struct bonding *bond, struct slave *slave1, struct slave *slave2)
|
|
{
|
|
struct slave *disabled_slave = NULL;
|
|
u8 tmp_mac_addr[ETH_ALEN];
|
|
int slaves_state_differ;
|
|
|
|
slaves_state_differ = (SLAVE_IS_OK(slave1) != SLAVE_IS_OK(slave2));
|
|
|
|
memcpy(tmp_mac_addr, slave1->dev->dev_addr, ETH_ALEN);
|
|
alb_set_slave_mac_addr(slave1, slave2->dev->dev_addr, bond->alb_info.rlb_enabled);
|
|
alb_set_slave_mac_addr(slave2, tmp_mac_addr, bond->alb_info.rlb_enabled);
|
|
|
|
/* fasten the change in the switch */
|
|
if (SLAVE_IS_OK(slave1)) {
|
|
alb_send_learning_packets(slave1, slave1->dev->dev_addr);
|
|
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 (SLAVE_IS_OK(slave2)) {
|
|
alb_send_learning_packets(slave2, slave2->dev->dev_addr);
|
|
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 that slave no longer uses @slave's permanent address.
|
|
*
|
|
* Caller must hold bond lock
|
|
*/
|
|
static void alb_change_hw_addr_on_detach(struct bonding *bond, struct slave *slave)
|
|
{
|
|
int perm_curr_diff;
|
|
int perm_bond_diff;
|
|
|
|
perm_curr_diff = memcmp(slave->perm_hwaddr,
|
|
slave->dev->dev_addr,
|
|
ETH_ALEN);
|
|
perm_bond_diff = memcmp(slave->perm_hwaddr,
|
|
bond->dev->dev_addr,
|
|
ETH_ALEN);
|
|
|
|
if (perm_curr_diff && perm_bond_diff) {
|
|
struct slave *tmp_slave;
|
|
int i, found = 0;
|
|
|
|
bond_for_each_slave(bond, tmp_slave, i) {
|
|
if (!memcmp(slave->perm_hwaddr,
|
|
tmp_slave->dev->dev_addr,
|
|
ETH_ALEN)) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
alb_swap_mac_addr(bond, slave, tmp_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 premanent
|
|
* 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.
|
|
*
|
|
* caller must hold the bond lock for write since the mac addresses are compared
|
|
* and may be swapped.
|
|
*/
|
|
static int alb_handle_addr_collision_on_attach(struct bonding *bond, struct slave *slave)
|
|
{
|
|
struct slave *tmp_slave1, *tmp_slave2, *free_mac_slave;
|
|
struct slave *has_bond_addr = bond->curr_active_slave;
|
|
int i, j, found = 0;
|
|
|
|
if (bond->slave_cnt == 0) {
|
|
/* 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 (memcmp(slave->perm_hwaddr, bond->dev->dev_addr, ETH_ALEN)) {
|
|
bond_for_each_slave(bond, tmp_slave1, i) {
|
|
if (!memcmp(tmp_slave1->dev->dev_addr, slave->dev->dev_addr,
|
|
ETH_ALEN)) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
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->alb_info.rlb_enabled);
|
|
}
|
|
|
|
/* The slave's address is equal to the address of the bond.
|
|
* Search for a spare address in the bond for this slave.
|
|
*/
|
|
free_mac_slave = NULL;
|
|
|
|
bond_for_each_slave(bond, tmp_slave1, i) {
|
|
found = 0;
|
|
bond_for_each_slave(bond, tmp_slave2, j) {
|
|
if (!memcmp(tmp_slave1->perm_hwaddr,
|
|
tmp_slave2->dev->dev_addr,
|
|
ETH_ALEN)) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
/* no slave has tmp_slave1's perm addr
|
|
* as its curr addr
|
|
*/
|
|
free_mac_slave = tmp_slave1;
|
|
break;
|
|
}
|
|
|
|
if (!has_bond_addr) {
|
|
if (!memcmp(tmp_slave1->dev->dev_addr,
|
|
bond->dev->dev_addr,
|
|
ETH_ALEN)) {
|
|
|
|
has_bond_addr = tmp_slave1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (free_mac_slave) {
|
|
alb_set_slave_mac_addr(slave, free_mac_slave->perm_hwaddr,
|
|
bond->alb_info.rlb_enabled);
|
|
|
|
printk(KERN_WARNING DRV_NAME
|
|
": %s: Warning: the hw address of slave %s is in use by "
|
|
"the bond; giving it the hw address of %s\n",
|
|
bond->dev->name, slave->dev->name, free_mac_slave->dev->name);
|
|
|
|
} else if (has_bond_addr) {
|
|
printk(KERN_ERR DRV_NAME
|
|
": %s: Error: the hw address of slave %s 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",
|
|
bond->dev->name, slave->dev->name);
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* alb_set_mac_address
|
|
* @bond:
|
|
* @addr:
|
|
*
|
|
* 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 sockaddr sa;
|
|
struct slave *slave, *stop_at;
|
|
char tmp_addr[ETH_ALEN];
|
|
int res;
|
|
int i;
|
|
|
|
if (bond->alb_info.rlb_enabled) {
|
|
return 0;
|
|
}
|
|
|
|
bond_for_each_slave(bond, slave, i) {
|
|
if (slave->dev->set_mac_address == NULL) {
|
|
res = -EOPNOTSUPP;
|
|
goto unwind;
|
|
}
|
|
|
|
/* save net_device's current hw address */
|
|
memcpy(tmp_addr, slave->dev->dev_addr, ETH_ALEN);
|
|
|
|
res = dev_set_mac_address(slave->dev, addr);
|
|
|
|
/* restore net_device's hw address */
|
|
memcpy(slave->dev->dev_addr, tmp_addr, ETH_ALEN);
|
|
|
|
if (res) {
|
|
goto unwind;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
unwind:
|
|
memcpy(sa.sa_data, bond->dev->dev_addr, bond->dev->addr_len);
|
|
sa.sa_family = bond->dev->type;
|
|
|
|
/* unwind from head to the slave that failed */
|
|
stop_at = slave;
|
|
bond_for_each_slave_from_to(bond, slave, i, bond->first_slave, stop_at) {
|
|
memcpy(tmp_addr, slave->dev->dev_addr, ETH_ALEN);
|
|
dev_set_mac_address(slave->dev, &sa);
|
|
memcpy(slave->dev->dev_addr, tmp_addr, ETH_ALEN);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/************************ exported alb funcions ************************/
|
|
|
|
int bond_alb_initialize(struct bonding *bond, int rlb_enabled)
|
|
{
|
|
int res;
|
|
|
|
res = tlb_initialize(bond);
|
|
if (res) {
|
|
return res;
|
|
}
|
|
|
|
if (rlb_enabled) {
|
|
bond->alb_info.rlb_enabled = 1;
|
|
/* initialize rlb */
|
|
res = rlb_initialize(bond);
|
|
if (res) {
|
|
tlb_deinitialize(bond);
|
|
return res;
|
|
}
|
|
} 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);
|
|
}
|
|
}
|
|
|
|
int bond_alb_xmit(struct sk_buff *skb, struct net_device *bond_dev)
|
|
{
|
|
struct bonding *bond = bond_dev->priv;
|
|
struct ethhdr *eth_data;
|
|
struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond));
|
|
struct slave *tx_slave = NULL;
|
|
static const __be32 ip_bcast = htonl(0xffffffff);
|
|
int hash_size = 0;
|
|
int do_tx_balance = 1;
|
|
u32 hash_index = 0;
|
|
const u8 *hash_start = NULL;
|
|
int res = 1;
|
|
|
|
skb_reset_mac_header(skb);
|
|
eth_data = eth_hdr(skb);
|
|
|
|
/* make sure that the curr_active_slave and the slaves list do
|
|
* not change during tx
|
|
*/
|
|
read_lock(&bond->lock);
|
|
read_lock(&bond->curr_slave_lock);
|
|
|
|
if (!BOND_IS_OK(bond)) {
|
|
goto out;
|
|
}
|
|
|
|
switch (ntohs(skb->protocol)) {
|
|
case ETH_P_IP: {
|
|
const struct iphdr *iph = ip_hdr(skb);
|
|
|
|
if ((memcmp(eth_data->h_dest, mac_bcast, ETH_ALEN) == 0) ||
|
|
(iph->daddr == ip_bcast) ||
|
|
(iph->protocol == IPPROTO_IGMP)) {
|
|
do_tx_balance = 0;
|
|
break;
|
|
}
|
|
hash_start = (char *)&(iph->daddr);
|
|
hash_size = sizeof(iph->daddr);
|
|
}
|
|
break;
|
|
case ETH_P_IPV6:
|
|
if (memcmp(eth_data->h_dest, mac_bcast, ETH_ALEN) == 0) {
|
|
do_tx_balance = 0;
|
|
break;
|
|
}
|
|
|
|
hash_start = (char *)&(ipv6_hdr(skb)->daddr);
|
|
hash_size = sizeof(ipv6_hdr(skb)->daddr);
|
|
break;
|
|
case ETH_P_IPX:
|
|
if (ipx_hdr(skb)->ipx_checksum != IPX_NO_CHECKSUM) {
|
|
/* something is wrong with this packet */
|
|
do_tx_balance = 0;
|
|
break;
|
|
}
|
|
|
|
if (ipx_hdr(skb)->ipx_type != IPX_TYPE_NCP) {
|
|
/* The only protocol worth balancing in
|
|
* this family since it has an "ARP" like
|
|
* mechanism
|
|
*/
|
|
do_tx_balance = 0;
|
|
break;
|
|
}
|
|
|
|
hash_start = (char*)eth_data->h_dest;
|
|
hash_size = ETH_ALEN;
|
|
break;
|
|
case ETH_P_ARP:
|
|
do_tx_balance = 0;
|
|
if (bond_info->rlb_enabled) {
|
|
tx_slave = rlb_arp_xmit(skb, bond);
|
|
}
|
|
break;
|
|
default:
|
|
do_tx_balance = 0;
|
|
break;
|
|
}
|
|
|
|
if (do_tx_balance) {
|
|
hash_index = _simple_hash(hash_start, hash_size);
|
|
tx_slave = tlb_choose_channel(bond, hash_index, skb->len);
|
|
}
|
|
|
|
if (!tx_slave) {
|
|
/* unbalanced or unassigned, send through primary */
|
|
tx_slave = bond->curr_active_slave;
|
|
bond_info->unbalanced_load += skb->len;
|
|
}
|
|
|
|
if (tx_slave && SLAVE_IS_OK(tx_slave)) {
|
|
if (tx_slave != bond->curr_active_slave) {
|
|
memcpy(eth_data->h_source,
|
|
tx_slave->dev->dev_addr,
|
|
ETH_ALEN);
|
|
}
|
|
|
|
res = bond_dev_queue_xmit(bond, skb, tx_slave->dev);
|
|
} else {
|
|
if (tx_slave) {
|
|
tlb_clear_slave(bond, tx_slave, 0);
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (res) {
|
|
/* no suitable interface, frame not sent */
|
|
dev_kfree_skb(skb);
|
|
}
|
|
read_unlock(&bond->curr_slave_lock);
|
|
read_unlock(&bond->lock);
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
int i;
|
|
|
|
read_lock(&bond->lock);
|
|
|
|
if (bond->kill_timers) {
|
|
goto out;
|
|
}
|
|
|
|
if (bond->slave_cnt == 0) {
|
|
bond_info->tx_rebalance_counter = 0;
|
|
bond_info->lp_counter = 0;
|
|
goto re_arm;
|
|
}
|
|
|
|
bond_info->tx_rebalance_counter++;
|
|
bond_info->lp_counter++;
|
|
|
|
/* send learning packets */
|
|
if (bond_info->lp_counter >= BOND_ALB_LP_TICKS) {
|
|
/* change of curr_active_slave involves swapping of mac addresses.
|
|
* in order to avoid this swapping from happening while
|
|
* sending the learning packets, the curr_slave_lock must be held for
|
|
* read.
|
|
*/
|
|
read_lock(&bond->curr_slave_lock);
|
|
|
|
bond_for_each_slave(bond, slave, i) {
|
|
alb_send_learning_packets(slave, slave->dev->dev_addr);
|
|
}
|
|
|
|
read_unlock(&bond->curr_slave_lock);
|
|
|
|
bond_info->lp_counter = 0;
|
|
}
|
|
|
|
/* rebalance tx traffic */
|
|
if (bond_info->tx_rebalance_counter >= BOND_TLB_REBALANCE_TICKS) {
|
|
|
|
read_lock(&bond->curr_slave_lock);
|
|
|
|
bond_for_each_slave(bond, slave, i) {
|
|
tlb_clear_slave(bond, slave, 1);
|
|
if (slave == bond->curr_active_slave) {
|
|
SLAVE_TLB_INFO(slave).load =
|
|
bond_info->unbalanced_load /
|
|
BOND_TLB_REBALANCE_INTERVAL;
|
|
bond_info->unbalanced_load = 0;
|
|
}
|
|
}
|
|
|
|
read_unlock(&bond->curr_slave_lock);
|
|
|
|
bond_info->tx_rebalance_counter = 0;
|
|
}
|
|
|
|
/* handle rlb stuff */
|
|
if (bond_info->rlb_enabled) {
|
|
/* the following code changes the promiscuity of the
|
|
* the curr_active_slave. It needs to be locked with a
|
|
* write lock to protect from other code that also
|
|
* sets the promiscuity.
|
|
*/
|
|
write_lock_bh(&bond->curr_slave_lock);
|
|
|
|
if (bond_info->primary_is_promisc &&
|
|
(++bond_info->rlb_promisc_timeout_counter >= RLB_PROMISC_TIMEOUT)) {
|
|
|
|
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.
|
|
*/
|
|
dev_set_promiscuity(bond->curr_active_slave->dev, -1);
|
|
bond_info->primary_is_promisc = 0;
|
|
}
|
|
|
|
write_unlock_bh(&bond->curr_slave_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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
re_arm:
|
|
queue_delayed_work(bond->wq, &bond->alb_work, alb_delta_in_ticks);
|
|
out:
|
|
read_unlock(&bond->lock);
|
|
}
|
|
|
|
/* 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,
|
|
bond->alb_info.rlb_enabled);
|
|
if (res) {
|
|
return res;
|
|
}
|
|
|
|
/* caller must hold the bond lock for write since the mac addresses
|
|
* are compared and may be swapped.
|
|
*/
|
|
write_lock_bh(&bond->lock);
|
|
|
|
res = alb_handle_addr_collision_on_attach(bond, slave);
|
|
|
|
write_unlock_bh(&bond->lock);
|
|
|
|
if (res) {
|
|
return res;
|
|
}
|
|
|
|
tlb_init_slave(slave);
|
|
|
|
/* order a rebalance ASAP */
|
|
bond->alb_info.tx_rebalance_counter = BOND_TLB_REBALANCE_TICKS;
|
|
|
|
if (bond->alb_info.rlb_enabled) {
|
|
bond->alb_info.rlb_rebalance = 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Caller must hold bond lock for write */
|
|
void bond_alb_deinit_slave(struct bonding *bond, struct slave *slave)
|
|
{
|
|
if (bond->slave_cnt > 1) {
|
|
alb_change_hw_addr_on_detach(bond, slave);
|
|
}
|
|
|
|
tlb_clear_slave(bond, slave, 0);
|
|
|
|
if (bond->alb_info.rlb_enabled) {
|
|
bond->alb_info.next_rx_slave = NULL;
|
|
rlb_clear_slave(bond, slave);
|
|
}
|
|
}
|
|
|
|
/* Caller must hold bond lock for read */
|
|
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 */
|
|
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..
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 bond curr_slave_lock for write (or bond lock for write)
|
|
*/
|
|
void bond_alb_handle_active_change(struct bonding *bond, struct slave *new_slave)
|
|
{
|
|
struct slave *swap_slave;
|
|
int i;
|
|
|
|
if (bond->curr_active_slave == new_slave) {
|
|
return;
|
|
}
|
|
|
|
if (bond->curr_active_slave && bond->alb_info.primary_is_promisc) {
|
|
dev_set_promiscuity(bond->curr_active_slave->dev, -1);
|
|
bond->alb_info.primary_is_promisc = 0;
|
|
bond->alb_info.rlb_promisc_timeout_counter = 0;
|
|
}
|
|
|
|
swap_slave = bond->curr_active_slave;
|
|
bond->curr_active_slave = new_slave;
|
|
|
|
if (!new_slave || (bond->slave_cnt == 0)) {
|
|
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) {
|
|
struct slave *tmp_slave;
|
|
/* find slave that is holding the bond's mac address */
|
|
bond_for_each_slave(bond, tmp_slave, i) {
|
|
if (!memcmp(tmp_slave->dev->dev_addr,
|
|
bond->dev->dev_addr, ETH_ALEN)) {
|
|
swap_slave = tmp_slave;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* curr_active_slave must be set before calling alb_swap_mac_addr */
|
|
if (swap_slave) {
|
|
/* swap mac address */
|
|
alb_swap_mac_addr(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->alb_info.rlb_enabled);
|
|
/* fasten bond mac on new current slave */
|
|
alb_send_learning_packets(new_slave, bond->dev->dev_addr);
|
|
}
|
|
}
|
|
|
|
int bond_alb_set_mac_address(struct net_device *bond_dev, void *addr)
|
|
{
|
|
struct bonding *bond = bond_dev->priv;
|
|
struct sockaddr *sa = addr;
|
|
struct slave *slave, *swap_slave;
|
|
int res;
|
|
int i;
|
|
|
|
if (!is_valid_ether_addr(sa->sa_data)) {
|
|
return -EADDRNOTAVAIL;
|
|
}
|
|
|
|
res = alb_set_mac_address(bond, addr);
|
|
if (res) {
|
|
return res;
|
|
}
|
|
|
|
memcpy(bond_dev->dev_addr, sa->sa_data, bond_dev->addr_len);
|
|
|
|
/* 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.
|
|
*/
|
|
if (!bond->curr_active_slave) {
|
|
return 0;
|
|
}
|
|
|
|
swap_slave = NULL;
|
|
|
|
bond_for_each_slave(bond, slave, i) {
|
|
if (!memcmp(slave->dev->dev_addr, bond_dev->dev_addr, ETH_ALEN)) {
|
|
swap_slave = slave;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (swap_slave) {
|
|
alb_swap_mac_addr(bond, swap_slave, bond->curr_active_slave);
|
|
} else {
|
|
alb_set_slave_mac_addr(bond->curr_active_slave, bond_dev->dev_addr,
|
|
bond->alb_info.rlb_enabled);
|
|
|
|
alb_send_learning_packets(bond->curr_active_slave, bond_dev->dev_addr);
|
|
if (bond->alb_info.rlb_enabled) {
|
|
/* inform clients mac address has changed */
|
|
rlb_req_update_slave_clients(bond, bond->curr_active_slave);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void bond_alb_clear_vlan(struct bonding *bond, unsigned short vlan_id)
|
|
{
|
|
if (bond->alb_info.current_alb_vlan &&
|
|
(bond->alb_info.current_alb_vlan->vlan_id == vlan_id)) {
|
|
bond->alb_info.current_alb_vlan = NULL;
|
|
}
|
|
|
|
if (bond->alb_info.rlb_enabled) {
|
|
rlb_clear_vlan(bond, vlan_id);
|
|
}
|
|
}
|
|
|