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.
Unified · Split
| @@ -9,6 +9,7 @@ struct tcf_mirred { · 8 unmodified lines | ||
| 9 | 9 | int tcfm_ifindex; |
| 10 | 10 | int tcfm_ok_push; |
| 11 | 11 | struct net_device *tcfm_dev; |
| 12 | + struct list_head tcfm_list; | |
| 12 | 13 | }; |
| 13 | 14 | #define to_mirred(pc) \ |
| 14 | 15 | container_of(pc, struct tcf_mirred, common) |
| @@ -33,6 +33,7 @@ · 32 unmodified lines | ||
| 33 | 33 | static struct tcf_common *tcf_mirred_ht[MIRRED_TAB_MASK + 1]; |
| 34 | 34 | static u32 mirred_idx_gen; |
| 35 | 35 | static DEFINE_RWLOCK(mirred_lock); |
| 36 | +static LIST_HEAD(mirred_list); | |
| 36 | 37 | |
| 37 | 38 | static struct tcf_hashinfo mirred_hash_info = { |
| 38 | 39 | .htab = tcf_mirred_ht, |
| @@ -47,7 +48,9 @@ static inline int tcf_mirred_release(struct tcf_mirred *m, int bind) · 8 unmodified lines | ||
| 47 | 48 | m->tcf_bindcnt--; |
| 48 | 49 | m->tcf_refcnt--; |
| 49 | 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 | 54 | tcf_hash_destroy(&m->common, &mirred_hash_info); |
| 52 | 55 | return 1; |
| 53 | 56 | } |
| @@ -134,8 +137,10 @@ static int tcf_mirred_init(struct nlattr *nla, struct nlattr *est, · 80 unmodified lines | ||
| 134 | 137 | m->tcfm_ok_push = ok_push; |
| 135 | 138 | } |
| 136 | 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 | 142 | tcf_hash_insert(pc, &mirred_hash_info); |
| 143 | + } | |
| 139 | 144 | |
| 140 | 145 | return ret; |
| 141 | 146 | } |
| @@ -162,9 +167,14 @@ static int tcf_mirred(struct sk_buff *skb, struct tc_action *a, · 20 unmodified lines | ||
| 162 | 167 | m->tcf_tm.lastuse = jiffies; |
| 163 | 168 | |
| 164 | 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 | 175 | if (!(dev->flags & IFF_UP)) { |
| 166 | 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 | 178 | dev->name); |
| 169 | 179 | goto out; |
| 170 | 180 | } |
| @@ -232,6 +242,28 @@ nla_put_failure: · 61 unmodified lines | ||
| 232 | 242 | return -1; |
| 233 | 243 | } |
| 234 | 244 | |
| 245 | +static 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 | + | |
| 262 | +static struct notifier_block mirred_device_notifier = { | |
| 263 | + .notifier_call = mirred_device_event, | |
| 264 | +}; | |
| 265 | + | |
| 266 | + | |
| 235 | 267 | static struct tc_action_ops act_mirred_ops = { |
| 236 | 268 | .kind = "mirred", |
| 237 | 269 | .hinfo = &mirred_hash_info, |
| @@ -252,12 +284,17 @@ MODULE_LICENSE("GPL"); · 14 unmodified lines | ||
| 252 | 284 | |
| 253 | 285 | static 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 | 291 | pr_info("Mirror/redirect action on\n"); |
| 256 | 292 | return tcf_register_action(&act_mirred_ops); |
| 257 | 293 | } |
| 258 | 294 | |
| 259 | 295 | static void __exit mirred_cleanup_module(void) |
| 260 | 296 | { |
| 297 | + unregister_netdevice_notifier(&mirred_device_notifier); | |
| 261 | 298 | tcf_unregister_action(&act_mirred_ops); |
| 262 | 299 | } |
| 263 | 300 | |