aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorstephen hemminger <shemminger@vyatta.com>2010-07-22 14:45:04 -0400
committerDavid S. Miller <davem@davemloft.net>2010-07-25 00:04:20 -0400
commit3b87956ea645fb4de7e59c7d0aa94de04be72615 (patch)
tree0dde04e6dfad7cd76afbb31eff72a8f08a574a9c
parent76ac21f5ef30e46397e405695eb1240ff0955209 (diff)
net sched: fix race in mirred device removal
This fixes hang when target device of mirred packet classifier action is removed. If a mirror or redirection action is configured to cause packets to go to another device, the classifier holds a ref count, but was assuming the adminstrator cleaned up all redirections before removing. The fix is to add a notifier and cleanup during unregister. The new list is implicitly protected by RTNL mutex because it is held during filter add/delete as well as notifier. Signed-off-by: Stephen Hemminger <shemminger@vyatta.com> Acked-by: Jamal Hadi Salim <hadi@cyberus.ca> Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--include/net/tc_act/tc_mirred.h1
-rw-r--r--net/sched/act_mirred.c43
2 files changed, 41 insertions, 3 deletions
diff --git a/include/net/tc_act/tc_mirred.h b/include/net/tc_act/tc_mirred.h
index ceac661cdfd..cfe2943690f 100644
--- a/include/net/tc_act/tc_mirred.h
+++ b/include/net/tc_act/tc_mirred.h
@@ -9,6 +9,7 @@ struct tcf_mirred {
9 int tcfm_ifindex; 9 int tcfm_ifindex;
10 int tcfm_ok_push; 10 int tcfm_ok_push;
11 struct net_device *tcfm_dev; 11 struct net_device *tcfm_dev;
12 struct list_head tcfm_list;
12}; 13};
13#define to_mirred(pc) \ 14#define to_mirred(pc) \
14 container_of(pc, struct tcf_mirred, common) 15 container_of(pc, struct tcf_mirred, common)
diff --git a/net/sched/act_mirred.c b/net/sched/act_mirred.c
index c0b6863e3b8..1980b71c283 100644
--- a/net/sched/act_mirred.c
+++ b/net/sched/act_mirred.c
@@ -33,6 +33,7 @@
33static struct tcf_common *tcf_mirred_ht[MIRRED_TAB_MASK + 1]; 33static struct tcf_common *tcf_mirred_ht[MIRRED_TAB_MASK + 1];
34static u32 mirred_idx_gen; 34static u32 mirred_idx_gen;
35static DEFINE_RWLOCK(mirred_lock); 35static DEFINE_RWLOCK(mirred_lock);
36static LIST_HEAD(mirred_list);
36 37
37static struct tcf_hashinfo mirred_hash_info = { 38static struct tcf_hashinfo mirred_hash_info = {
38 .htab = tcf_mirred_ht, 39 .htab = tcf_mirred_ht,
@@ -47,7 +48,9 @@ static inline int tcf_mirred_release(struct tcf_mirred *m, int bind)
47 m->tcf_bindcnt--; 48 m->tcf_bindcnt--;
48 m->tcf_refcnt--; 49 m->tcf_refcnt--;
49 if(!m->tcf_bindcnt && m->tcf_refcnt <= 0) { 50 if(!m->tcf_bindcnt && m->tcf_refcnt <= 0) {
50 dev_put(m->tcfm_dev); 51 list_del(&m->tcfm_list);
52 if (m->tcfm_dev)
53 dev_put(m->tcfm_dev);
51 tcf_hash_destroy(&m->common, &mirred_hash_info); 54 tcf_hash_destroy(&m->common, &mirred_hash_info);
52 return 1; 55 return 1;
53 } 56 }
@@ -134,8 +137,10 @@ static int tcf_mirred_init(struct nlattr *nla, struct nlattr *est,
134 m->tcfm_ok_push = ok_push; 137 m->tcfm_ok_push = ok_push;
135 } 138 }
136 spin_unlock_bh(&m->tcf_lock); 139 spin_unlock_bh(&m->tcf_lock);
137 if (ret == ACT_P_CREATED) 140 if (ret == ACT_P_CREATED) {
141 list_add(&m->tcfm_list, &mirred_list);
138 tcf_hash_insert(pc, &mirred_hash_info); 142 tcf_hash_insert(pc, &mirred_hash_info);
143 }
139 144
140 return ret; 145 return ret;
141} 146}
@@ -162,9 +167,14 @@ static int tcf_mirred(struct sk_buff *skb, struct tc_action *a,
162 m->tcf_tm.lastuse = jiffies; 167 m->tcf_tm.lastuse = jiffies;
163 168
164 dev = m->tcfm_dev; 169 dev = m->tcfm_dev;
170 if (!dev) {
171 printk_once(KERN_NOTICE "tc mirred: target device is gone\n");
172 goto out;
173 }
174
165 if (!(dev->flags & IFF_UP)) { 175 if (!(dev->flags & IFF_UP)) {
166 if (net_ratelimit()) 176 if (net_ratelimit())
167 pr_notice("tc mirred to Houston: device %s is gone!\n", 177 pr_notice("tc mirred to Houston: device %s is down\n",
168 dev->name); 178 dev->name);
169 goto out; 179 goto out;
170 } 180 }
@@ -232,6 +242,28 @@ nla_put_failure:
232 return -1; 242 return -1;
233} 243}
234 244
245static int mirred_device_event(struct notifier_block *unused,
246 unsigned long event, void *ptr)
247{
248 struct net_device *dev = ptr;
249 struct tcf_mirred *m;
250
251 if (event == NETDEV_UNREGISTER)
252 list_for_each_entry(m, &mirred_list, tcfm_list) {
253 if (m->tcfm_dev == dev) {
254 dev_put(dev);
255 m->tcfm_dev = NULL;
256 }
257 }
258
259 return NOTIFY_DONE;
260}
261
262static struct notifier_block mirred_device_notifier = {
263 .notifier_call = mirred_device_event,
264};
265
266
235static struct tc_action_ops act_mirred_ops = { 267static struct tc_action_ops act_mirred_ops = {
236 .kind = "mirred", 268 .kind = "mirred",
237 .hinfo = &mirred_hash_info, 269 .hinfo = &mirred_hash_info,
@@ -252,12 +284,17 @@ MODULE_LICENSE("GPL");
252 284
253static int __init mirred_init_module(void) 285static int __init mirred_init_module(void)
254{ 286{
287 int err = register_netdevice_notifier(&mirred_device_notifier);
288 if (err)
289 return err;
290
255 pr_info("Mirror/redirect action on\n"); 291 pr_info("Mirror/redirect action on\n");
256 return tcf_register_action(&act_mirred_ops); 292 return tcf_register_action(&act_mirred_ops);
257} 293}
258 294
259static void __exit mirred_cleanup_module(void) 295static void __exit mirred_cleanup_module(void)
260{ 296{
297 unregister_netdevice_notifier(&mirred_device_notifier);
261 tcf_unregister_action(&act_mirred_ops); 298 tcf_unregister_action(&act_mirred_ops);
262} 299}
263 300