Commit 318a924d authored by Stephen Hemminger's avatar Stephen Hemminger Committed by David S. Miller

[BRIDGE]: Forwarding table lockless update

Optimize bridge forwarding table for the fastpath of updating
an existing entry. Use RCU to find the entry and change it's time.
Fallback to normal locking for insert. This gives about a 1/3
improvement in packets forwarding per second on SMP.
Signed-off-by: default avatarStephen Hemminger <shemminger@osdl.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 46d5439d
......@@ -25,7 +25,7 @@
static kmem_cache_t *br_fdb_cache;
static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
const unsigned char *addr, int is_local);
const unsigned char *addr);
void __init br_fdb_init(void)
{
......@@ -101,8 +101,7 @@ void br_fdb_changeaddr(struct net_bridge_port *p, const unsigned char *newaddr)
}
insert:
/* insert new address, may fail if invalid address or dup. */
fdb_insert(br, p, newaddr, 1);
fdb_insert(br, p, newaddr);
spin_unlock_bh(&br->hash_lock);
}
......@@ -258,71 +257,108 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf,
return num;
}
static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
const unsigned char *addr, int is_local)
static inline struct net_bridge_fdb_entry *fdb_find(struct hlist_head *head,
const unsigned char *addr)
{
struct hlist_node *h;
struct net_bridge_fdb_entry *fdb;
int hash = br_mac_hash(addr);
if (!is_valid_ether_addr(addr))
return -EADDRNOTAVAIL;
hlist_for_each_entry(fdb, h, &br->hash[hash], hlist) {
if (!memcmp(fdb->addr.addr, addr, ETH_ALEN)) {
/* attempt to update an entry for a local interface */
if (fdb->is_local) {
/* it is okay to have multiple ports with same
* address, just don't allow to be spoofed.
*/
if (is_local)
return 0;
if (net_ratelimit())
printk(KERN_WARNING "%s: received packet with "
" own address as source address\n",
source->dev->name);
return -EEXIST;
}
if (is_local) {
printk(KERN_WARNING "%s adding interface with same address "
"as a received packet\n",
source->dev->name);
goto update;
hlist_for_each_entry_rcu(fdb, h, head, hlist) {
if (!memcmp(fdb->addr.addr, addr, ETH_ALEN))
return fdb;
}
return NULL;
}
if (fdb->is_static)
return 0;
goto update;
}
}
static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head,
struct net_bridge_port *source,
const unsigned char *addr,
int is_local)
{
struct net_bridge_fdb_entry *fdb;
fdb = kmem_cache_alloc(br_fdb_cache, GFP_ATOMIC);
if (!fdb)
return ENOMEM;
if (fdb) {
memcpy(fdb->addr.addr, addr, ETH_ALEN);
atomic_set(&fdb->use_count, 1);
hlist_add_head_rcu(&fdb->hlist, &br->hash[hash]);
hlist_add_head_rcu(&fdb->hlist, head);
update:
fdb->dst = source;
fdb->is_local = is_local;
fdb->is_static = is_local;
fdb->ageing_timer = jiffies;
}
return fdb;
}
static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
const unsigned char *addr)
{
struct hlist_head *head = &br->hash[br_mac_hash(addr)];
struct net_bridge_fdb_entry *fdb;
if (!is_valid_ether_addr(addr))
return -EINVAL;
fdb = fdb_find(head, addr);
if (fdb) {
/* it is okay to have multiple ports with same
* address, just use the first one.
*/
if (fdb->is_local)
return 0;
printk(KERN_WARNING "%s adding interface with same address "
"as a received packet\n",
source->dev->name);
fdb_delete(fdb);
}
if (!fdb_create(head, source, addr, 1))
return -ENOMEM;
return 0;
}
int br_fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
const unsigned char *addr, int is_local)
const unsigned char *addr)
{
int ret;
spin_lock_bh(&br->hash_lock);
ret = fdb_insert(br, source, addr, is_local);
ret = fdb_insert(br, source, addr);
spin_unlock_bh(&br->hash_lock);
return ret;
}
void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
const unsigned char *addr)
{
struct hlist_head *head = &br->hash[br_mac_hash(addr)];
struct net_bridge_fdb_entry *fdb;
rcu_read_lock();
fdb = fdb_find(head, addr);
if (likely(fdb)) {
/* attempt to update an entry for a local interface */
if (unlikely(fdb->is_local)) {
if (net_ratelimit())
printk(KERN_WARNING "%s: received packet with "
" own address as source address\n",
source->dev->name);
} else {
/* fastpath: update of existing entry */
fdb->dst = source;
fdb->ageing_timer = jiffies;
}
} else {
spin_lock_bh(&br->hash_lock);
if (!fdb_find(head, addr))
fdb_create(head, source, addr, 0);
/* else we lose race and someone else inserts
* it first, don't bother updating
*/
spin_unlock_bh(&br->hash_lock);
}
rcu_read_unlock();
}
......@@ -332,7 +332,7 @@ int br_add_if(struct net_bridge *br, struct net_device *dev)
if (IS_ERR(p = new_nbp(br, dev, br_initial_port_cost(dev))))
return PTR_ERR(p);
if ((err = br_fdb_insert(br, p, dev->dev_addr, 1)))
if ((err = br_fdb_insert(br, p, dev->dev_addr)))
destroy_nbp(p);
else if ((err = br_sysfs_addif(p)))
......
......@@ -105,12 +105,12 @@ int br_handle_frame(struct net_bridge_port *p, struct sk_buff **pskb)
if (p->state == BR_STATE_DISABLED)
goto err;
if (eth_hdr(skb)->h_source[0] & 1)
if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
goto err;
if (p->state == BR_STATE_LEARNING ||
p->state == BR_STATE_FORWARDING)
br_fdb_insert(p->br, p, eth_hdr(skb)->h_source, 0);
br_fdb_update(p->br, p, eth_hdr(skb)->h_source);
if (p->br->stp_enabled &&
!memcmp(dest, bridge_ula, 5) &&
......
......@@ -146,8 +146,10 @@ extern int br_fdb_fillbuf(struct net_bridge *br, void *buf,
unsigned long count, unsigned long off);
extern int br_fdb_insert(struct net_bridge *br,
struct net_bridge_port *source,
const unsigned char *addr,
int is_local);
const unsigned char *addr);
extern void br_fdb_update(struct net_bridge *br,
struct net_bridge_port *source,
const unsigned char *addr);
/* br_forward.c */
extern void br_deliver(const struct net_bridge_port *to,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment