aboutsummaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorStephen Hemminger2006-02-09 17:08:52 -0800
committerDavid S. Miller2006-02-09 17:08:52 -0800
commitb3f1be4b5412e34647764457bec901e06b03e624 (patch)
treeb3f1529ff0882ebb5aed190d8a61b52f703503ce /net
parent6fcf9412de64056238a6295f21db7aa9c37a532e (diff)
[BRIDGE]: fix for RCU and deadlock on device removal
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>
Diffstat (limited to 'net')
-rw-r--r--net/bridge/br_if.c21
-rw-r--r--net/bridge/br_input.c19
-rw-r--r--net/bridge/br_private.h1
-rw-r--r--net/bridge/br_stp_bpdu.c30
4 files changed, 41 insertions, 30 deletions
diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c
index da687c8dc6ff..70b7ef917234 100644
--- a/net/bridge/br_if.c
+++ b/net/bridge/br_if.c
@@ -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);
@@ -97,6 +102,7 @@ static void port_carrier_check(void *arg)
br_stp_disable_port(p);
spin_unlock_bh(&p->br->lock);
}
+done:
rtnl_unlock();
}
@@ -104,7 +110,6 @@ 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);
@@ -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);
}
@@ -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;
@@ -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);
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index e3a73cead6b6..4eef83755315 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -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;
@@ -93,6 +95,9 @@ int br_handle_frame_finish(struct sk_buff *skb)
out:
return 0;
+drop:
+ kfree_skb(skb);
+ goto out;
}
/*
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index e330b17b6d81..c5bd631ffcd5 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -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;
diff --git a/net/bridge/br_stp_bpdu.c b/net/bridge/br_stp_bpdu.c
index d071f1c9ad0b..296f6a487c52 100644
--- a/net/bridge/br_stp_bpdu.c
+++ b/net/bridge/br_stp_bpdu.c
@@ -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;
@@ -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;