[BRIDGE]: fix for RCU and deadlock on device removal
Stephen Hemminger [Fri, 10 Feb 2006 01:08:52 +0000 (17:08 -0800)]
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>

net/bridge/br_if.c
net/bridge/br_input.c
net/bridge/br_private.h
net/bridge/br_stp_bpdu.c

index da687c8..70b7ef9 100644 (file)
@@ -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);
index e3a73ce..4eef837 100644 (file)
@@ -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;
 }
 
 /*
index e330b17..c5bd631 100644 (file)
@@ -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;
index d071f1c..296f6a4 100644 (file)
@@ -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;