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

[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: default avatarStephen Hemminger <shemminger@osdl.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 6fcf9412
...@@ -79,9 +79,14 @@ static int port_cost(struct net_device *dev) ...@@ -79,9 +79,14 @@ static int port_cost(struct net_device *dev)
*/ */
static void port_carrier_check(void *arg) 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(); rtnl_lock();
p = dev->br_port;
if (!p)
goto done;
if (netif_carrier_ok(p->dev)) { if (netif_carrier_ok(p->dev)) {
u32 cost = port_cost(p->dev); u32 cost = port_cost(p->dev);
...@@ -97,6 +102,7 @@ static void port_carrier_check(void *arg) ...@@ -97,6 +102,7 @@ static void port_carrier_check(void *arg)
br_stp_disable_port(p); br_stp_disable_port(p);
spin_unlock_bh(&p->br->lock); spin_unlock_bh(&p->br->lock);
} }
done:
rtnl_unlock(); rtnl_unlock();
} }
...@@ -104,7 +110,6 @@ static void destroy_nbp(struct net_bridge_port *p) ...@@ -104,7 +110,6 @@ static void destroy_nbp(struct net_bridge_port *p)
{ {
struct net_device *dev = p->dev; struct net_device *dev = p->dev;
dev->br_port = NULL;
p->br = NULL; p->br = NULL;
p->dev = NULL; p->dev = NULL;
dev_put(dev); dev_put(dev);
...@@ -133,24 +138,20 @@ static void del_nbp(struct net_bridge_port *p) ...@@ -133,24 +138,20 @@ static void del_nbp(struct net_bridge_port *p)
struct net_bridge *br = p->br; struct net_bridge *br = p->br;
struct net_device *dev = p->dev; struct net_device *dev = p->dev;
/* Race between RTNL notify and RCU callback */
if (p->deleted)
return;
dev_set_promiscuity(dev, -1); dev_set_promiscuity(dev, -1);
cancel_delayed_work(&p->carrier_check); cancel_delayed_work(&p->carrier_check);
flush_scheduled_work();
spin_lock_bh(&br->lock); spin_lock_bh(&br->lock);
br_stp_disable_port(p); br_stp_disable_port(p);
p->deleted = 1;
spin_unlock_bh(&br->lock); spin_unlock_bh(&br->lock);
br_fdb_delete_by_port(br, p); br_fdb_delete_by_port(br, p);
list_del_rcu(&p->list); list_del_rcu(&p->list);
rcu_assign_pointer(dev->br_port, NULL);
call_rcu(&p->rcu, destroy_nbp_rcu); call_rcu(&p->rcu, destroy_nbp_rcu);
} }
...@@ -254,11 +255,10 @@ static struct net_bridge_port *new_nbp(struct net_bridge *br, ...@@ -254,11 +255,10 @@ static struct net_bridge_port *new_nbp(struct net_bridge *br,
p->dev = dev; p->dev = dev;
p->path_cost = port_cost(dev); p->path_cost = port_cost(dev);
p->priority = 0x8000 >> BR_PORT_BITS; p->priority = 0x8000 >> BR_PORT_BITS;
dev->br_port = p;
p->port_no = index; p->port_no = index;
br_init_port(p); br_init_port(p);
p->state = BR_STATE_DISABLED; 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); kobject_init(&p->kobj);
return p; return p;
...@@ -397,6 +397,7 @@ int br_add_if(struct net_bridge *br, struct net_device *dev) ...@@ -397,6 +397,7 @@ int br_add_if(struct net_bridge *br, struct net_device *dev)
else if ((err = br_sysfs_addif(p))) else if ((err = br_sysfs_addif(p)))
del_nbp(p); del_nbp(p);
else { else {
rcu_assign_pointer(dev->br_port, p);
dev_set_promiscuity(dev, 1); dev_set_promiscuity(dev, 1);
list_add_rcu(&p->list, &br->port_list); list_add_rcu(&p->list, &br->port_list);
......
...@@ -45,18 +45,20 @@ static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb) ...@@ -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) int br_handle_frame_finish(struct sk_buff *skb)
{ {
const unsigned char *dest = eth_hdr(skb)->h_dest; const unsigned char *dest = eth_hdr(skb)->h_dest;
struct net_bridge_port *p = skb->dev->br_port; struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);
struct net_bridge *br = p->br; struct net_bridge *br;
struct net_bridge_fdb_entry *dst; struct net_bridge_fdb_entry *dst;
int passedup = 0; int passedup = 0;
if (!p || p->state == BR_STATE_DISABLED)
goto drop;
/* insert into forwarding database after filtering to avoid spoofing */ /* 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) { if (p->state == BR_STATE_LEARNING)
kfree_skb(skb); goto drop;
goto out;
}
if (br->dev->flags & IFF_PROMISC) { if (br->dev->flags & IFF_PROMISC) {
struct sk_buff *skb2; struct sk_buff *skb2;
...@@ -93,6 +95,9 @@ int br_handle_frame_finish(struct sk_buff *skb) ...@@ -93,6 +95,9 @@ int br_handle_frame_finish(struct sk_buff *skb)
out: out:
return 0; return 0;
drop:
kfree_skb(skb);
goto out;
} }
/* /*
......
...@@ -68,7 +68,6 @@ struct net_bridge_port ...@@ -68,7 +68,6 @@ struct net_bridge_port
/* STP */ /* STP */
u8 priority; u8 priority;
u8 state; u8 state;
u8 deleted;
u16 port_no; u16 port_no;
unsigned char topology_change_ack; unsigned char topology_change_ack;
unsigned char config_pending; unsigned char config_pending;
......
...@@ -133,29 +133,35 @@ void br_send_tcn_bpdu(struct net_bridge_port *p) ...@@ -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}; 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) int br_stp_handle_bpdu(struct sk_buff *skb)
{ {
struct net_bridge_port *p = skb->dev->br_port; struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);
struct net_bridge *br = p->br; struct net_bridge *br;
unsigned char *buf; 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 */ /* 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 */ /* need at least the 802 and STP headers */
if (!pskb_may_pull(skb, sizeof(header)+1) || if (!pskb_may_pull(skb, sizeof(header)+1) ||
memcmp(skb->data, header, sizeof(header))) memcmp(skb->data, header, sizeof(header)))
goto err; goto out;
buf = skb_pull(skb, sizeof(header)); 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) { if (buf[0] == BPDU_TYPE_CONFIG) {
struct br_config_bpdu bpdu; struct br_config_bpdu bpdu;
...@@ -201,7 +207,7 @@ int br_stp_handle_bpdu(struct sk_buff *skb) ...@@ -201,7 +207,7 @@ int br_stp_handle_bpdu(struct sk_buff *skb)
br_received_tcn_bpdu(p); br_received_tcn_bpdu(p);
} }
out: out:
spin_unlock_bh(&br->lock); spin_unlock(&br->lock);
err: err:
kfree_skb(skb); kfree_skb(skb);
return 0; return 0;
......
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