mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-11 12:28:41 +08:00
[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>
This commit is contained in:
parent
6fcf9412de
commit
b3f1be4b54
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
/* insert into forwarding database after filtering to avoid spoofing */
|
||||
br_fdb_update(p->br, p, eth_hdr(skb)->h_source);
|
||||
if (!p || p->state == BR_STATE_DISABLED)
|
||||
goto drop;
|
||||
|
||||
if (p->state == BR_STATE_LEARNING) {
|
||||
kfree_skb(skb);
|
||||
goto out;
|
||||
}
|
||||
/* insert into forwarding database after filtering to avoid spoofing */
|
||||
br = p->br;
|
||||
br_fdb_update(br, p, eth_hdr(skb)->h_source);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user