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

[BRIDGE]: Fix several startup/shutdown timer races.

parent a0cae8f4
...@@ -110,23 +110,38 @@ static int br_dev_accept_fastpath(struct net_device *dev, struct dst_entry *dst) ...@@ -110,23 +110,38 @@ static int br_dev_accept_fastpath(struct net_device *dev, struct dst_entry *dst)
return -1; return -1;
} }
/* convert later to direct kfree */
static void br_dev_free(struct net_device *dev)
{
struct net_bridge *br = dev->priv;
WARN_ON(!list_empty(&br->port_list));
WARN_ON(!list_empty(&br->age_list));
BUG_ON(timer_pending(&br->hello_timer));
BUG_ON(timer_pending(&br->tcn_timer));
BUG_ON(timer_pending(&br->topology_change_timer));
BUG_ON(timer_pending(&br->gc_timer));
kfree(dev);
}
void br_dev_setup(struct net_device *dev) void br_dev_setup(struct net_device *dev)
{ {
memset(dev->dev_addr, 0, ETH_ALEN); memset(dev->dev_addr, 0, ETH_ALEN);
ether_setup(dev);
dev->do_ioctl = br_dev_do_ioctl; dev->do_ioctl = br_dev_do_ioctl;
dev->get_stats = br_dev_get_stats; dev->get_stats = br_dev_get_stats;
dev->hard_start_xmit = br_dev_xmit; dev->hard_start_xmit = br_dev_xmit;
dev->open = br_dev_open; dev->open = br_dev_open;
dev->set_multicast_list = br_dev_set_multicast_list; dev->set_multicast_list = br_dev_set_multicast_list;
dev->destructor = (void (*)(struct net_device *))kfree; dev->destructor = br_dev_free;
SET_MODULE_OWNER(dev); SET_MODULE_OWNER(dev);
dev->stop = br_dev_stop; dev->stop = br_dev_stop;
dev->accept_fastpath = br_dev_accept_fastpath; dev->accept_fastpath = br_dev_accept_fastpath;
dev->tx_queue_len = 0; dev->tx_queue_len = 0;
dev->set_mac_address = NULL; dev->set_mac_address = NULL;
dev->priv_flags = IFF_EBRIDGE; dev->priv_flags = IFF_EBRIDGE;
ether_setup(dev);
} }
...@@ -41,6 +41,13 @@ static int br_initial_port_cost(struct net_device *dev) ...@@ -41,6 +41,13 @@ static int br_initial_port_cost(struct net_device *dev)
static void destroy_nbp(void *arg) static void destroy_nbp(void *arg)
{ {
struct net_bridge_port *p = arg; struct net_bridge_port *p = arg;
p->dev->br_port = NULL;
BUG_ON(timer_pending(&p->message_age_timer));
BUG_ON(timer_pending(&p->forward_delay_timer));
BUG_ON(timer_pending(&p->hold_timer));
dev_put(p->dev); dev_put(p->dev);
kfree(p); kfree(p);
} }
...@@ -53,16 +60,19 @@ static void del_nbp(struct net_bridge_port *p) ...@@ -53,16 +60,19 @@ static void del_nbp(struct net_bridge_port *p)
br_stp_disable_port(p); br_stp_disable_port(p);
dev_set_promiscuity(dev, -1); dev_set_promiscuity(dev, -1);
dev->br_port = NULL;
list_del_rcu(&p->list); list_del_rcu(&p->list);
br_fdb_delete_by_port(p->br, p); br_fdb_delete_by_port(p->br, p);
del_timer(&p->message_age_timer);
del_timer(&p->forward_delay_timer);
del_timer(&p->hold_timer);
call_rcu(&p->rcu, destroy_nbp, p); call_rcu(&p->rcu, destroy_nbp, p);
} }
static void del_ifs(struct net_bridge *br) static void del_br(struct net_bridge *br)
{ {
struct list_head *p, *n; struct list_head *p, *n;
...@@ -71,6 +81,10 @@ static void del_ifs(struct net_bridge *br) ...@@ -71,6 +81,10 @@ static void del_ifs(struct net_bridge *br)
del_nbp(list_entry(p, struct net_bridge_port, list)); del_nbp(list_entry(p, struct net_bridge_port, list));
} }
spin_unlock_bh(&br->lock); spin_unlock_bh(&br->lock);
del_timer_sync(&br->gc_timer);
unregister_netdevice(br->dev);
} }
static struct net_bridge *new_nb(const char *name) static struct net_bridge *new_nb(const char *name)
...@@ -182,15 +196,14 @@ int br_del_bridge(const char *name) ...@@ -182,15 +196,14 @@ int br_del_bridge(const char *name)
ret = -EBUSY; ret = -EBUSY;
} }
else { else
del_ifs((struct net_bridge *) dev->priv); del_br(dev->priv);
unregister_netdevice(dev);
}
rtnl_unlock(); rtnl_unlock();
return ret; return ret;
} }
/* called under bridge lock */
int br_add_if(struct net_bridge *br, struct net_device *dev) int br_add_if(struct net_bridge *br, struct net_device *dev)
{ {
struct net_bridge_port *p; struct net_bridge_port *p;
...@@ -205,7 +218,6 @@ int br_add_if(struct net_bridge *br, struct net_device *dev) ...@@ -205,7 +218,6 @@ int br_add_if(struct net_bridge *br, struct net_device *dev)
return -ELOOP; return -ELOOP;
dev_hold(dev); dev_hold(dev);
spin_lock_bh(&br->lock);
if ((p = new_nbp(br, dev)) == NULL) { if ((p = new_nbp(br, dev)) == NULL) {
spin_unlock_bh(&br->lock); spin_unlock_bh(&br->lock);
dev_put(dev); dev_put(dev);
...@@ -218,26 +230,21 @@ int br_add_if(struct net_bridge *br, struct net_device *dev) ...@@ -218,26 +230,21 @@ int br_add_if(struct net_bridge *br, struct net_device *dev)
br_fdb_insert(br, p, dev->dev_addr, 1); br_fdb_insert(br, p, dev->dev_addr, 1);
if ((br->dev->flags & IFF_UP) && (dev->flags & IFF_UP)) if ((br->dev->flags & IFF_UP) && (dev->flags & IFF_UP))
br_stp_enable_port(p); br_stp_enable_port(p);
spin_unlock_bh(&br->lock);
return 0; return 0;
} }
/* called under bridge lock */
int br_del_if(struct net_bridge *br, struct net_device *dev) int br_del_if(struct net_bridge *br, struct net_device *dev)
{ {
struct net_bridge_port *p; struct net_bridge_port *p;
int retval = 0;
spin_lock_bh(&br->lock);
if ((p = dev->br_port) == NULL || p->br != br) if ((p = dev->br_port) == NULL || p->br != br)
retval = -EINVAL; return -EINVAL;
else {
del_nbp(p);
br_stp_recalculate_bridge_id(br);
}
spin_unlock_bh(&br->lock);
return retval; del_nbp(p);
br_stp_recalculate_bridge_id(br);
return 0;
} }
int br_get_bridge_ifindices(int *indices, int num) int br_get_bridge_ifindices(int *indices, int num)
...@@ -274,13 +281,8 @@ void __exit br_cleanup_bridges(void) ...@@ -274,13 +281,8 @@ void __exit br_cleanup_bridges(void)
rtnl_lock(); rtnl_lock();
for (dev = dev_base; dev; dev = nxt) { for (dev = dev_base; dev; dev = nxt) {
nxt = dev->next; nxt = dev->next;
if (dev->priv_flags & IFF_EBRIDGE) { if (dev->priv_flags & IFF_EBRIDGE)
pr_debug("cleanup %s\n", dev->name); del_br(dev->priv);
del_ifs((struct net_bridge *) dev->priv);
unregister_netdevice(dev);
}
} }
rtnl_unlock(); rtnl_unlock();
......
...@@ -59,10 +59,12 @@ static int br_ioctl_device(struct net_bridge *br, ...@@ -59,10 +59,12 @@ static int br_ioctl_device(struct net_bridge *br,
if (dev == NULL) if (dev == NULL)
return -EINVAL; return -EINVAL;
spin_lock_bh(&br->lock);
if (cmd == BRCTL_ADD_IF) if (cmd == BRCTL_ADD_IF)
ret = br_add_if(br, dev); ret = br_add_if(br, dev);
else else
ret = br_del_if(br, dev); ret = br_del_if(br, dev);
spin_unlock_bh(&br->lock);
dev_put(dev); dev_put(dev);
return ret; return ret;
......
...@@ -38,39 +38,27 @@ static int br_device_event(struct notifier_block *unused, unsigned long event, v ...@@ -38,39 +38,27 @@ static int br_device_event(struct notifier_block *unused, unsigned long event, v
br = p->br; br = p->br;
spin_lock_bh(&br->lock);
switch (event) switch (event)
{ {
case NETDEV_CHANGEADDR: case NETDEV_CHANGEADDR:
spin_lock_bh(&br->lock);
br_fdb_changeaddr(p, dev->dev_addr); br_fdb_changeaddr(p, dev->dev_addr);
br_stp_recalculate_bridge_id(br); br_stp_recalculate_bridge_id(br);
spin_unlock_bh(&br->lock);
break;
case NETDEV_GOING_DOWN:
/* extend the protocol to send some kind of notification? */
break; break;
case NETDEV_DOWN: case NETDEV_DOWN:
if (br->dev->flags & IFF_UP) { br_stp_disable_port(p);
spin_lock_bh(&br->lock);
br_stp_disable_port(p);
spin_unlock_bh(&br->lock);
}
break; break;
case NETDEV_UP: case NETDEV_UP:
if (!(br->dev->flags & IFF_UP)) { br_stp_enable_port(p);
spin_lock_bh(&br->lock);
br_stp_enable_port(p);
spin_unlock_bh(&br->lock);
}
break; break;
case NETDEV_UNREGISTER: case NETDEV_UNREGISTER:
br_del_if(br, dev); br_del_if(br, dev);
break; break;
} }
spin_unlock_bh(&br->lock);
return NOTIFY_DONE; return NOTIFY_DONE;
} }
...@@ -43,8 +43,7 @@ void br_stp_enable_bridge(struct net_bridge *br) ...@@ -43,8 +43,7 @@ void br_stp_enable_bridge(struct net_bridge *br)
struct net_bridge_port *p; struct net_bridge_port *p;
spin_lock_bh(&br->lock); spin_lock_bh(&br->lock);
br->hello_timer.expires = jiffies + br->hello_time; mod_timer(&br->hello_timer, jiffies + br->hello_time);
add_timer(&br->hello_timer);
br_config_bpdu_generation(br); br_config_bpdu_generation(br);
list_for_each_entry(p, &br->port_list, list) { list_for_each_entry(p, &br->port_list, list) {
...@@ -74,8 +73,6 @@ void br_stp_disable_bridge(struct net_bridge *br) ...@@ -74,8 +73,6 @@ void br_stp_disable_bridge(struct net_bridge *br)
del_timer_sync(&br->hello_timer); del_timer_sync(&br->hello_timer);
del_timer_sync(&br->topology_change_timer); del_timer_sync(&br->topology_change_timer);
del_timer_sync(&br->tcn_timer); del_timer_sync(&br->tcn_timer);
del_timer_sync(&br->gc_timer);
} }
/* called under bridge lock */ /* called under bridge lock */
......
...@@ -43,8 +43,7 @@ static void br_hello_timer_expired(unsigned long arg) ...@@ -43,8 +43,7 @@ static void br_hello_timer_expired(unsigned long arg)
if (br->dev->flags & IFF_UP) { if (br->dev->flags & IFF_UP) {
br_config_bpdu_generation(br); br_config_bpdu_generation(br);
br->hello_timer.expires = jiffies + br->hello_time; mod_timer(&br->hello_timer, jiffies + br->hello_time);
add_timer(&br->hello_timer);
} }
spin_unlock_bh(&br->lock); spin_unlock_bh(&br->lock);
} }
...@@ -73,6 +72,8 @@ static void br_message_age_timer_expired(unsigned long arg) ...@@ -73,6 +72,8 @@ static void br_message_age_timer_expired(unsigned long arg)
* check is redundant. I'm leaving it in for now, though. * check is redundant. I'm leaving it in for now, though.
*/ */
spin_lock_bh(&br->lock); spin_lock_bh(&br->lock);
if (p->state == BR_STATE_DISABLED)
goto unlock;
was_root = br_is_root_bridge(br); was_root = br_is_root_bridge(br);
br_become_designated_port(p); br_become_designated_port(p);
...@@ -80,6 +81,7 @@ static void br_message_age_timer_expired(unsigned long arg) ...@@ -80,6 +81,7 @@ static void br_message_age_timer_expired(unsigned long arg)
br_port_state_selection(br); br_port_state_selection(br);
if (br_is_root_bridge(br) && !was_root) if (br_is_root_bridge(br) && !was_root)
br_become_root_bridge(br); br_become_root_bridge(br);
unlock:
spin_unlock_bh(&br->lock); spin_unlock_bh(&br->lock);
} }
...@@ -93,8 +95,8 @@ static void br_forward_delay_timer_expired(unsigned long arg) ...@@ -93,8 +95,8 @@ static void br_forward_delay_timer_expired(unsigned long arg)
spin_lock_bh(&br->lock); spin_lock_bh(&br->lock);
if (p->state == BR_STATE_LISTENING) { if (p->state == BR_STATE_LISTENING) {
p->state = BR_STATE_LEARNING; p->state = BR_STATE_LEARNING;
p->forward_delay_timer.expires = jiffies + br->forward_delay; mod_timer(&p->forward_delay_timer,
add_timer(&p->forward_delay_timer); jiffies + br->forward_delay);
} else if (p->state == BR_STATE_LEARNING) { } else if (p->state == BR_STATE_LEARNING) {
p->state = BR_STATE_FORWARDING; p->state = BR_STATE_FORWARDING;
if (br_is_designated_for_some_port(br)) if (br_is_designated_for_some_port(br))
...@@ -113,8 +115,7 @@ static void br_tcn_timer_expired(unsigned long arg) ...@@ -113,8 +115,7 @@ static void br_tcn_timer_expired(unsigned long arg)
if (br->dev->flags & IFF_UP) { if (br->dev->flags & IFF_UP) {
br_transmit_tcn(br); br_transmit_tcn(br);
br->tcn_timer.expires = jiffies + br->bridge_hello_time; mod_timer(&br->tcn_timer,jiffies + br->bridge_hello_time);
add_timer(&br->tcn_timer);
} }
spin_unlock_bh(&br->lock); spin_unlock_bh(&br->lock);
} }
......
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