Skip to content

Commit

Permalink
bridge: Add vlan to unicast fdb entries
Browse files Browse the repository at this point in the history
This patch adds vlan to unicast fdb entries that are created for
learned addresses (not the manually configured ones).  It adds
vlan id into the hash mix and uses vlan as an addditional parameter
for an entry match.

Signed-off-by: Vlad Yasevich <vyasevic@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Vlad Yasevich authored and David S. Miller committed Feb 14, 2013
1 parent 552406c commit 2ba071e
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 35 deletions.
2 changes: 1 addition & 1 deletion net/bridge/br_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
br_multicast_deliver(mdst, skb);
else
br_flood_deliver(br, skb);
} else if ((dst = __br_fdb_get(br, dest)) != NULL)
} else if ((dst = __br_fdb_get(br, dest, vid)) != NULL)
br_deliver(dst->dst, skb);
else
br_flood_deliver(br, skb);
Expand Down
69 changes: 40 additions & 29 deletions net/bridge/br_fdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <linux/slab.h>
#include <linux/atomic.h>
#include <asm/unaligned.h>
#include <linux/if_vlan.h>
#include "br_private.h"

static struct kmem_cache *br_fdb_cache __read_mostly;
Expand Down Expand Up @@ -67,11 +68,11 @@ static inline int has_expired(const struct net_bridge *br,
time_before_eq(fdb->updated + hold_time(br), jiffies);
}

static inline int br_mac_hash(const unsigned char *mac)
static inline int br_mac_hash(const unsigned char *mac, __u16 vid)
{
/* use 1 byte of OUI cnd 3 bytes of NIC */
/* use 1 byte of OUI and 3 bytes of NIC */
u32 key = get_unaligned((u32 *)(mac + 2));
return jhash_1word(key, fdb_salt) & (BR_HASH_SIZE - 1);
return jhash_2words(key, vid, fdb_salt) & (BR_HASH_SIZE - 1);
}

static void fdb_rcu_free(struct rcu_head *head)
Expand Down Expand Up @@ -132,7 +133,7 @@ void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr)
struct net_bridge_fdb_entry *f;

/* If old entry was unassociated with any port, then delete it. */
f = __br_fdb_get(br, br->dev->dev_addr);
f = __br_fdb_get(br, br->dev->dev_addr, 0);
if (f && f->is_local && !f->dst)
fdb_delete(br, f);

Expand Down Expand Up @@ -231,13 +232,16 @@ void br_fdb_delete_by_port(struct net_bridge *br,

/* No locking or refcounting, assumes caller has rcu_read_lock */
struct net_bridge_fdb_entry *__br_fdb_get(struct net_bridge *br,
const unsigned char *addr)
const unsigned char *addr,
__u16 vid)
{
struct hlist_node *h;
struct net_bridge_fdb_entry *fdb;

hlist_for_each_entry_rcu(fdb, h, &br->hash[br_mac_hash(addr)], hlist) {
if (ether_addr_equal(fdb->addr.addr, addr)) {
hlist_for_each_entry_rcu(fdb, h,
&br->hash[br_mac_hash(addr, vid)], hlist) {
if (ether_addr_equal(fdb->addr.addr, addr) &&
fdb->vlan_id == vid) {
if (unlikely(has_expired(br, fdb)))
break;
return fdb;
Expand All @@ -261,7 +265,7 @@ int br_fdb_test_addr(struct net_device *dev, unsigned char *addr)
if (!port)
ret = 0;
else {
fdb = __br_fdb_get(port->br, addr);
fdb = __br_fdb_get(port->br, addr, 0);
ret = fdb && fdb->dst && fdb->dst->dev != dev &&
fdb->dst->state == BR_STATE_FORWARDING;
}
Expand Down Expand Up @@ -325,41 +329,47 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf,
}

static struct net_bridge_fdb_entry *fdb_find(struct hlist_head *head,
const unsigned char *addr)
const unsigned char *addr,
__u16 vid)
{
struct hlist_node *h;
struct net_bridge_fdb_entry *fdb;

hlist_for_each_entry(fdb, h, head, hlist) {
if (ether_addr_equal(fdb->addr.addr, addr))
if (ether_addr_equal(fdb->addr.addr, addr) &&
fdb->vlan_id == vid)
return fdb;
}
return NULL;
}

static struct net_bridge_fdb_entry *fdb_find_rcu(struct hlist_head *head,
const unsigned char *addr)
const unsigned char *addr,
__u16 vid)
{
struct hlist_node *h;
struct net_bridge_fdb_entry *fdb;

hlist_for_each_entry_rcu(fdb, h, head, hlist) {
if (ether_addr_equal(fdb->addr.addr, addr))
if (ether_addr_equal(fdb->addr.addr, addr) &&
fdb->vlan_id == vid)
return fdb;
}
return NULL;
}

static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head,
struct net_bridge_port *source,
const unsigned char *addr)
const unsigned char *addr,
__u16 vid)
{
struct net_bridge_fdb_entry *fdb;

fdb = kmem_cache_alloc(br_fdb_cache, GFP_ATOMIC);
if (fdb) {
memcpy(fdb->addr.addr, addr, ETH_ALEN);
fdb->dst = source;
fdb->vlan_id = vid;
fdb->is_local = 0;
fdb->is_static = 0;
fdb->updated = fdb->used = jiffies;
Expand All @@ -371,13 +381,13 @@ static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head,
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 hlist_head *head = &br->hash[br_mac_hash(addr, 0)];
struct net_bridge_fdb_entry *fdb;

if (!is_valid_ether_addr(addr))
return -EINVAL;

fdb = fdb_find(head, addr);
fdb = fdb_find(head, addr, 0);
if (fdb) {
/* it is okay to have multiple ports with same
* address, just use the first one.
Expand All @@ -390,7 +400,7 @@ static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
fdb_delete(br, fdb);
}

fdb = fdb_create(head, source, addr);
fdb = fdb_create(head, source, addr, 0);
if (!fdb)
return -ENOMEM;

Expand All @@ -412,9 +422,9 @@ int br_fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
}

void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
const unsigned char *addr)
const unsigned char *addr, u16 vid)
{
struct hlist_head *head = &br->hash[br_mac_hash(addr)];
struct hlist_head *head = &br->hash[br_mac_hash(addr, vid)];
struct net_bridge_fdb_entry *fdb;

/* some users want to always flood. */
Expand All @@ -426,7 +436,7 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
source->state == BR_STATE_FORWARDING))
return;

fdb = fdb_find_rcu(head, addr);
fdb = fdb_find_rcu(head, addr, vid);
if (likely(fdb)) {
/* attempt to update an entry for a local interface */
if (unlikely(fdb->is_local)) {
Expand All @@ -441,8 +451,8 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
}
} else {
spin_lock(&br->hash_lock);
if (likely(!fdb_find(head, addr))) {
fdb = fdb_create(head, source, addr);
if (likely(!fdb_find(head, addr, vid))) {
fdb = fdb_create(head, source, addr, vid);
if (fdb)
fdb_notify(br, fdb, RTM_NEWNEIGH);
}
Expand Down Expand Up @@ -571,18 +581,18 @@ int br_fdb_dump(struct sk_buff *skb,

/* Update (create or replace) forwarding database entry */
static int fdb_add_entry(struct net_bridge_port *source, const __u8 *addr,
__u16 state, __u16 flags)
__u16 state, __u16 flags, __u16 vid)
{
struct net_bridge *br = source->br;
struct hlist_head *head = &br->hash[br_mac_hash(addr)];
struct hlist_head *head = &br->hash[br_mac_hash(addr, vid)];
struct net_bridge_fdb_entry *fdb;

fdb = fdb_find(head, addr);
fdb = fdb_find(head, addr, vid);
if (fdb == NULL) {
if (!(flags & NLM_F_CREATE))
return -ENOENT;

fdb = fdb_create(head, source, addr);
fdb = fdb_create(head, source, addr, vid);
if (!fdb)
return -ENOMEM;
fdb_notify(br, fdb, RTM_NEWNEIGH);
Expand Down Expand Up @@ -629,11 +639,12 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],

if (ndm->ndm_flags & NTF_USE) {
rcu_read_lock();
br_fdb_update(p->br, p, addr);
br_fdb_update(p->br, p, addr, 0);
rcu_read_unlock();
} else {
spin_lock_bh(&p->br->hash_lock);
err = fdb_add_entry(p, addr, ndm->ndm_state, nlh_flags);
err = fdb_add_entry(p, addr, ndm->ndm_state, nlh_flags,
0);
spin_unlock_bh(&p->br->hash_lock);
}

Expand All @@ -643,10 +654,10 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
static int fdb_delete_by_addr(struct net_bridge_port *p, const u8 *addr)
{
struct net_bridge *br = p->br;
struct hlist_head *head = &br->hash[br_mac_hash(addr)];
struct hlist_head *head = &br->hash[br_mac_hash(addr, 0)];
struct net_bridge_fdb_entry *fdb;

fdb = fdb_find(head, addr);
fdb = fdb_find(head, addr, 0);
if (!fdb)
return -ENOENT;

Expand Down
9 changes: 6 additions & 3 deletions net/bridge/br_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ int br_handle_frame_finish(struct sk_buff *skb)

/* insert into forwarding database after filtering to avoid spoofing */
br = p->br;
br_fdb_update(br, p, eth_hdr(skb)->h_source);
br_fdb_update(br, p, eth_hdr(skb)->h_source, vid);

if (!is_broadcast_ether_addr(dest) && is_multicast_ether_addr(dest) &&
br_multicast_rcv(br, p, skb))
Expand Down Expand Up @@ -110,7 +110,8 @@ int br_handle_frame_finish(struct sk_buff *skb)
skb2 = skb;

br->dev->stats.multicast++;
} else if ((dst = __br_fdb_get(br, dest)) && dst->is_local) {
} else if ((dst = __br_fdb_get(br, dest, vid)) &&
dst->is_local) {
skb2 = skb;
/* Do not forward the packet since it's local. */
skb = NULL;
Expand Down Expand Up @@ -138,8 +139,10 @@ int br_handle_frame_finish(struct sk_buff *skb)
static int br_handle_local_finish(struct sk_buff *skb)
{
struct net_bridge_port *p = br_port_get_rcu(skb->dev);
u16 vid = 0;

br_fdb_update(p->br, p, eth_hdr(skb)->h_source);
br_vlan_get_tag(skb, &vid);
br_fdb_update(p->br, p, eth_hdr(skb)->h_source, vid);
return 0; /* process further */
}

Expand Down
7 changes: 5 additions & 2 deletions net/bridge/br_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ struct net_bridge_fdb_entry
mac_addr addr;
unsigned char is_local;
unsigned char is_static;
__u16 vlan_id;
};

struct net_bridge_port_group {
Expand Down Expand Up @@ -373,7 +374,8 @@ extern void br_fdb_cleanup(unsigned long arg);
extern void br_fdb_delete_by_port(struct net_bridge *br,
const struct net_bridge_port *p, int do_all);
extern struct net_bridge_fdb_entry *__br_fdb_get(struct net_bridge *br,
const unsigned char *addr);
const unsigned char *addr,
__u16 vid);
extern int br_fdb_test_addr(struct net_device *dev, unsigned char *addr);
extern int br_fdb_fillbuf(struct net_bridge *br, void *buf,
unsigned long count, unsigned long off);
Expand All @@ -382,7 +384,8 @@ extern int br_fdb_insert(struct net_bridge *br,
const unsigned char *addr);
extern void br_fdb_update(struct net_bridge *br,
struct net_bridge_port *source,
const unsigned char *addr);
const unsigned char *addr,
u16 vid);

extern int br_fdb_delete(struct ndmsg *ndm,
struct net_device *dev,
Expand Down

0 comments on commit 2ba071e

Please sign in to comment.