bridge br_multicast: Fix skb leakage in error path.
[safe/jmp/linux-2.6] / net / bridge / br_stp_if.c
index 23dea14..d527119 100644 (file)
@@ -5,8 +5,6 @@
  *     Authors:
  *     Lennert Buytenhek               <buytenh@gnu.org>
  *
- *     $Id: br_stp_if.c,v 1.4 2001/04/14 21:14:39 davem Exp $
- *
  *     This program is free software; you can redistribute it and/or
  *     modify it under the terms of the GNU General Public License
  *     as published by the Free Software Foundation; either version
@@ -14,8 +12,8 @@
  */
 
 #include <linux/kernel.h>
-#include <linux/smp_lock.h>
 #include <linux/etherdevice.h>
+#include <linux/rtnetlink.h>
 
 #include "br_private.h"
 #include "br_private_stp.h"
@@ -27,7 +25,7 @@
  */
 static inline port_id br_make_port_id(__u8 priority, __u16 port_no)
 {
-       return ((u16)priority << BR_PORT_BITS) 
+       return ((u16)priority << BR_PORT_BITS)
                | (port_no & ((1<<BR_PORT_BITS)-1));
 }
 
@@ -49,7 +47,7 @@ void br_stp_enable_bridge(struct net_bridge *br)
        spin_lock_bh(&br->lock);
        mod_timer(&br->hello_timer, jiffies + br->hello_time);
        mod_timer(&br->gc_timer, jiffies + HZ/10);
-       
+
        br_config_bpdu_generation(br);
 
        list_for_each_entry(p, &br->port_list, list) {
@@ -109,6 +107,9 @@ void br_stp_disable_port(struct net_bridge_port *p)
        del_timer(&p->forward_delay_timer);
        del_timer(&p->hold_timer);
 
+       br_fdb_delete_by_port(br, p, 0);
+       br_multicast_disable_port(p);
+
        br_configuration_update(br);
 
        br_port_state_selection(br);
@@ -117,10 +118,68 @@ void br_stp_disable_port(struct net_bridge_port *p)
                br_become_root_bridge(br);
 }
 
+static void br_stp_start(struct net_bridge *br)
+{
+       int r;
+       char *argv[] = { BR_STP_PROG, br->dev->name, "start", NULL };
+       char *envp[] = { NULL };
+
+       r = call_usermodehelper(BR_STP_PROG, argv, envp, UMH_WAIT_PROC);
+       if (r == 0) {
+               br->stp_enabled = BR_USER_STP;
+               printk(KERN_INFO "%s: userspace STP started\n", br->dev->name);
+       } else {
+               br->stp_enabled = BR_KERNEL_STP;
+               printk(KERN_INFO "%s: starting userspace STP failed, "
+                               "starting kernel STP\n", br->dev->name);
+
+               /* To start timers on any ports left in blocking */
+               spin_lock_bh(&br->lock);
+               br_port_state_selection(br);
+               spin_unlock_bh(&br->lock);
+       }
+}
+
+static void br_stp_stop(struct net_bridge *br)
+{
+       int r;
+       char *argv[] = { BR_STP_PROG, br->dev->name, "stop", NULL };
+       char *envp[] = { NULL };
+
+       if (br->stp_enabled == BR_USER_STP) {
+               r = call_usermodehelper(BR_STP_PROG, argv, envp, 1);
+               printk(KERN_INFO "%s: userspace STP stopped, return code %d\n",
+                       br->dev->name, r);
+
+
+               /* To start timers on any ports left in blocking */
+               spin_lock_bh(&br->lock);
+               br_port_state_selection(br);
+               spin_unlock_bh(&br->lock);
+       }
+
+       br->stp_enabled = BR_NO_STP;
+}
+
+void br_stp_set_enabled(struct net_bridge *br, unsigned long val)
+{
+       ASSERT_RTNL();
+
+       if (val) {
+               if (br->stp_enabled == BR_NO_STP)
+                       br_stp_start(br);
+       } else {
+               if (br->stp_enabled != BR_NO_STP)
+                       br_stp_stop(br);
+       }
+}
+
 /* called under bridge lock */
 void br_stp_change_bridge_id(struct net_bridge *br, const unsigned char *addr)
 {
-       unsigned char oldaddr[6];
+       /* should be aligned on 2 bytes for compare_ether_addr() */
+       unsigned short oldaddr_aligned[ETH_ALEN >> 1];
+       unsigned char *oldaddr = (unsigned char *)oldaddr_aligned;
        struct net_bridge_port *p;
        int wasroot;
 
@@ -145,14 +204,21 @@ void br_stp_change_bridge_id(struct net_bridge *br, const unsigned char *addr)
                br_become_root_bridge(br);
 }
 
-static const unsigned char br_mac_zero[6];
+/* should be aligned on 2 bytes for compare_ether_addr() */
+static const unsigned short br_mac_zero_aligned[ETH_ALEN >> 1];
 
 /* called under bridge lock */
 void br_stp_recalculate_bridge_id(struct net_bridge *br)
 {
+       const unsigned char *br_mac_zero =
+                       (const unsigned char *)br_mac_zero_aligned;
        const unsigned char *addr = br_mac_zero;
        struct net_bridge_port *p;
 
+       /* user has chosen a value so keep it */
+       if (br->flags & BR_SET_MAC_ADDR)
+               return;
+
        list_for_each_entry(p, &br->port_list, list) {
                if (addr == br_mac_zero ||
                    memcmp(p->dev->dev_addr, addr, ETH_ALEN) < 0)