Instead of holding netns refcnt in tc actions, we can minimize the holding time by saving it in struct tcf_exts instead. This means we can just hold netns refcnt right before call_rcu() and release it after tcf_exts_destroy() is done. However, because on netns cleanup path we call tcf_proto_destroy() too, obviously we can not hold netns for a zero refcnt, in this case we have to do cleanup synchronously. It is fine for RCU too, the caller cleanup_net() already waits for a grace period. For other cases, refcnt is non-zero and we can safely grab it as normal and release it after we are done. This patch provides two new API for each filter to use: tcf_exts_get_net() and tcf_exts_put_net(). And all filters now can use the following pattern: void __destroy_filter() { tcf_exts_destroy(); tcf_exts_put_net(); // <== release netns refcnt kfree(); } void some_work() { rtnl_lock(); __destroy_filter(); rtnl_unlock(); } void some_rcu_callback() { tcf_queue_work(some_work); } if (tcf_exts_get_net()) // <== hold netns refcnt call_rcu(some_rcu_callback); else __destroy_filter();
Showing the changes to include (1 of 2 files). Show all
Unified · Split
| @@ -94,6 +94,7 @@ struct tcf_exts { · 93 unmodified lines | |||
| 94 | __u32 type; /* for backward compat(TCA_OLD_COMPAT) */ | 94 | __u32 type; /* for backward compat(TCA_OLD_COMPAT) */ |
| 95 | int nr_actions; | 95 | int nr_actions; |
| 96 | struct tc_action **actions; | 96 | struct tc_action **actions; |
| 97 | struct net *net; | ||
| 97 | #endif | 98 | #endif |
| 98 | /* Map to export classifier specific extension TLV types to the | 99 | /* Map to export classifier specific extension TLV types to the |
| 99 | * generic extensions API. Unsupported extensions must be set to 0. | 100 | * generic extensions API. Unsupported extensions must be set to 0. |
| @@ -107,6 +108,7 @@ static inline int tcf_exts_init(struct tcf_exts *exts, int action, int police) · 7 unmodified lines | |||
| 107 | #ifdef CONFIG_NET_CLS_ACT | 108 | #ifdef CONFIG_NET_CLS_ACT |
| 108 | exts->type = 0; | 109 | exts->type = 0; |
| 109 | exts->nr_actions = 0; | 110 | exts->nr_actions = 0; |
| 111 | exts->net = NULL; | ||
| 110 | exts->actions = kcalloc(TCA_ACT_MAX_PRIO, sizeof(struct tc_action *), | 112 | exts->actions = kcalloc(TCA_ACT_MAX_PRIO, sizeof(struct tc_action *), |
| 111 | GFP_KERNEL); | 113 | GFP_KERNEL); |
| 112 | if (!exts->actions) | 114 | if (!exts->actions) |
| @@ -117,6 +119,28 @@ static inline int tcf_exts_init(struct tcf_exts *exts, int action, int police) · 4 unmodified lines | |||
| 117 | return 0; | 119 | return 0; |
| 118 | } | 120 | } |
| 119 | 121 | ||
| 122 | /* Return false if the netns is being destroyed in cleanup_net(). Callers | ||
| 123 | * need to do cleanup synchronously in this case, otherwise may race with | ||
| 124 | * tc_action_net_exit(). Return true for other cases. | ||
| 125 | */ | ||
| 126 | static inline bool tcf_exts_get_net(struct tcf_exts *exts) | ||
| 127 | { | ||
| 128 | #ifdef CONFIG_NET_CLS_ACT | ||
| 129 | exts->net = maybe_get_net(exts->net); | ||
| 130 | return exts->net != NULL; | ||
| 131 | #else | ||
| 132 | return true; | ||
| 133 | #endif | ||
| 134 | } | ||
| 135 | |||
| 136 | static inline void tcf_exts_put_net(struct tcf_exts *exts) | ||
| 137 | { | ||
| 138 | #ifdef CONFIG_NET_CLS_ACT | ||
| 139 | if (exts->net) | ||
| 140 | put_net(exts->net); | ||
| 141 | #endif | ||
| 142 | } | ||
| 143 | |||
| 120 | static inline void tcf_exts_to_list(const struct tcf_exts *exts, | 144 | static inline void tcf_exts_to_list(const struct tcf_exts *exts, |
| 121 | struct list_head *actions) | 145 | struct list_head *actions) |
| 122 | { | 146 | { |