Skip to content

Commit

Permalink
[BRIDGE]: fix for RCU and deadlock on device removal
Browse files Browse the repository at this point in the history
Change Bridge receive path to correctly handle RCU removal of device
from bridge.  Also fixes deadlock between carrier_check and del_nbp.
This replaces the previous deleted flag fix.

Signed-off-by: Stephen Hemminger <shemminger@osdl.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Stephen Hemminger authored and David S. Miller committed Feb 10, 2006
1 parent 6fcf941 commit b3f1be4
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 30 deletions.
21 changes: 11 additions & 10 deletions net/bridge/br_if.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,14 @@ static int port_cost(struct net_device *dev)
*/
static void port_carrier_check(void *arg)
{
struct net_bridge_port *p = arg;
struct net_device *dev = arg;
struct net_bridge_port *p;

rtnl_lock();
p = dev->br_port;
if (!p)
goto done;

if (netif_carrier_ok(p->dev)) {
u32 cost = port_cost(p->dev);

Expand All @@ -97,14 +102,14 @@ static void port_carrier_check(void *arg)
br_stp_disable_port(p);
spin_unlock_bh(&p->br->lock);
}
done:
rtnl_unlock();
}

static void destroy_nbp(struct net_bridge_port *p)
{
struct net_device *dev = p->dev;

dev->br_port = NULL;
p->br = NULL;
p->dev = NULL;
dev_put(dev);
Expand Down Expand Up @@ -133,24 +138,20 @@ static void del_nbp(struct net_bridge_port *p)
struct net_bridge *br = p->br;
struct net_device *dev = p->dev;

/* Race between RTNL notify and RCU callback */
if (p->deleted)
return;

dev_set_promiscuity(dev, -1);

cancel_delayed_work(&p->carrier_check);
flush_scheduled_work();

spin_lock_bh(&br->lock);
br_stp_disable_port(p);
p->deleted = 1;
spin_unlock_bh(&br->lock);

br_fdb_delete_by_port(br, p);

list_del_rcu(&p->list);

rcu_assign_pointer(dev->br_port, NULL);

call_rcu(&p->rcu, destroy_nbp_rcu);
}

Expand Down Expand Up @@ -254,11 +255,10 @@ static struct net_bridge_port *new_nbp(struct net_bridge *br,
p->dev = dev;
p->path_cost = port_cost(dev);
p->priority = 0x8000 >> BR_PORT_BITS;
dev->br_port = p;
p->port_no = index;
br_init_port(p);
p->state = BR_STATE_DISABLED;
INIT_WORK(&p->carrier_check, port_carrier_check, p);
INIT_WORK(&p->carrier_check, port_carrier_check, dev);
kobject_init(&p->kobj);

return p;
Expand Down Expand Up @@ -397,6 +397,7 @@ int br_add_if(struct net_bridge *br, struct net_device *dev)
else if ((err = br_sysfs_addif(p)))
del_nbp(p);
else {
rcu_assign_pointer(dev->br_port, p);
dev_set_promiscuity(dev, 1);

list_add_rcu(&p->list, &br->port_list);
Expand Down
19 changes: 12 additions & 7 deletions net/bridge/br_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,20 @@ static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb)
int br_handle_frame_finish(struct sk_buff *skb)
{
const unsigned char *dest = eth_hdr(skb)->h_dest;
struct net_bridge_port *p = skb->dev->br_port;
struct net_bridge *br = p->br;
struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);
struct net_bridge *br;
struct net_bridge_fdb_entry *dst;
int passedup = 0;

if (!p || p->state == BR_STATE_DISABLED)
goto drop;

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

if (p->state == BR_STATE_LEARNING) {
kfree_skb(skb);
goto out;
}
if (p->state == BR_STATE_LEARNING)
goto drop;

if (br->dev->flags & IFF_PROMISC) {
struct sk_buff *skb2;
Expand Down Expand Up @@ -93,6 +95,9 @@ int br_handle_frame_finish(struct sk_buff *skb)

out:
return 0;
drop:
kfree_skb(skb);
goto out;
}

/*
Expand Down
1 change: 0 additions & 1 deletion net/bridge/br_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ struct net_bridge_port
/* STP */
u8 priority;
u8 state;
u8 deleted;
u16 port_no;
unsigned char topology_change_ack;
unsigned char config_pending;
Expand Down
30 changes: 18 additions & 12 deletions net/bridge/br_stp_bpdu.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,29 +133,35 @@ void br_send_tcn_bpdu(struct net_bridge_port *p)

static const unsigned char header[6] = {0x42, 0x42, 0x03, 0x00, 0x00, 0x00};

/* NO locks */
/* NO locks, but rcu_read_lock (preempt_disabled) */
int br_stp_handle_bpdu(struct sk_buff *skb)
{
struct net_bridge_port *p = skb->dev->br_port;
struct net_bridge *br = p->br;
struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);
struct net_bridge *br;
unsigned char *buf;

if (!p)
goto err;

br = p->br;
spin_lock(&br->lock);

if (p->state == BR_STATE_DISABLED || !(br->dev->flags & IFF_UP))
goto out;

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

if (!br->stp_enabled)
goto out;

/* need at least the 802 and STP headers */
if (!pskb_may_pull(skb, sizeof(header)+1) ||
memcmp(skb->data, header, sizeof(header)))
goto err;
goto out;

buf = skb_pull(skb, sizeof(header));

spin_lock_bh(&br->lock);
if (p->state == BR_STATE_DISABLED
|| !(br->dev->flags & IFF_UP)
|| !br->stp_enabled)
goto out;

if (buf[0] == BPDU_TYPE_CONFIG) {
struct br_config_bpdu bpdu;

Expand Down Expand Up @@ -201,7 +207,7 @@ int br_stp_handle_bpdu(struct sk_buff *skb)
br_received_tcn_bpdu(p);
}
out:
spin_unlock_bh(&br->lock);
spin_unlock(&br->lock);
err:
kfree_skb(skb);
return 0;
Expand Down

0 comments on commit b3f1be4

Please sign in to comment.