1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * net/sched/act_gact.c Generic actions |
4 | * |
5 | * copyright Jamal Hadi Salim (2002-4) |
6 | */ |
7 | |
8 | #include <linux/types.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/string.h> |
11 | #include <linux/errno.h> |
12 | #include <linux/skbuff.h> |
13 | #include <linux/rtnetlink.h> |
14 | #include <linux/module.h> |
15 | #include <linux/init.h> |
16 | #include <net/netlink.h> |
17 | #include <net/pkt_sched.h> |
18 | #include <net/pkt_cls.h> |
19 | #include <linux/tc_act/tc_gact.h> |
20 | #include <net/tc_act/tc_gact.h> |
21 | #include <net/tc_wrapper.h> |
22 | |
23 | static struct tc_action_ops act_gact_ops; |
24 | |
25 | #ifdef CONFIG_GACT_PROB |
26 | static int gact_net_rand(struct tcf_gact *gact) |
27 | { |
28 | smp_rmb(); /* coupled with smp_wmb() in tcf_gact_init() */ |
29 | if (get_random_u32_below(ceil: gact->tcfg_pval)) |
30 | return gact->tcf_action; |
31 | return gact->tcfg_paction; |
32 | } |
33 | |
34 | static int gact_determ(struct tcf_gact *gact) |
35 | { |
36 | u32 pack = atomic_inc_return(v: &gact->packets); |
37 | |
38 | smp_rmb(); /* coupled with smp_wmb() in tcf_gact_init() */ |
39 | if (pack % gact->tcfg_pval) |
40 | return gact->tcf_action; |
41 | return gact->tcfg_paction; |
42 | } |
43 | |
44 | typedef int (*g_rand)(struct tcf_gact *gact); |
45 | static g_rand gact_rand[MAX_RAND] = { NULL, gact_net_rand, gact_determ }; |
46 | #endif /* CONFIG_GACT_PROB */ |
47 | |
48 | static const struct nla_policy gact_policy[TCA_GACT_MAX + 1] = { |
49 | [TCA_GACT_PARMS] = { .len = sizeof(struct tc_gact) }, |
50 | [TCA_GACT_PROB] = { .len = sizeof(struct tc_gact_p) }, |
51 | }; |
52 | |
53 | static int tcf_gact_init(struct net *net, struct nlattr *nla, |
54 | struct nlattr *est, struct tc_action **a, |
55 | struct tcf_proto *tp, u32 flags, |
56 | struct netlink_ext_ack *extack) |
57 | { |
58 | struct tc_action_net *tn = net_generic(net, id: act_gact_ops.net_id); |
59 | bool bind = flags & TCA_ACT_FLAGS_BIND; |
60 | struct nlattr *tb[TCA_GACT_MAX + 1]; |
61 | struct tcf_chain *goto_ch = NULL; |
62 | struct tc_gact *parm; |
63 | struct tcf_gact *gact; |
64 | int ret = 0; |
65 | u32 index; |
66 | int err; |
67 | #ifdef CONFIG_GACT_PROB |
68 | struct tc_gact_p *p_parm = NULL; |
69 | #endif |
70 | |
71 | if (nla == NULL) |
72 | return -EINVAL; |
73 | |
74 | err = nla_parse_nested_deprecated(tb, TCA_GACT_MAX, nla, policy: gact_policy, |
75 | NULL); |
76 | if (err < 0) |
77 | return err; |
78 | |
79 | if (tb[TCA_GACT_PARMS] == NULL) |
80 | return -EINVAL; |
81 | parm = nla_data(nla: tb[TCA_GACT_PARMS]); |
82 | index = parm->index; |
83 | |
84 | #ifndef CONFIG_GACT_PROB |
85 | if (tb[TCA_GACT_PROB] != NULL) |
86 | return -EOPNOTSUPP; |
87 | #else |
88 | if (tb[TCA_GACT_PROB]) { |
89 | p_parm = nla_data(nla: tb[TCA_GACT_PROB]); |
90 | if (p_parm->ptype >= MAX_RAND) |
91 | return -EINVAL; |
92 | if (TC_ACT_EXT_CMP(p_parm->paction, TC_ACT_GOTO_CHAIN)) { |
93 | NL_SET_ERR_MSG(extack, |
94 | "goto chain not allowed on fallback" ); |
95 | return -EINVAL; |
96 | } |
97 | } |
98 | #endif |
99 | |
100 | err = tcf_idr_check_alloc(tn, index: &index, a, bind); |
101 | if (!err) { |
102 | ret = tcf_idr_create_from_flags(tn, index, est, a, |
103 | ops: &act_gact_ops, bind, flags); |
104 | if (ret) { |
105 | tcf_idr_cleanup(tn, index); |
106 | return ret; |
107 | } |
108 | ret = ACT_P_CREATED; |
109 | } else if (err > 0) { |
110 | if (bind)/* dont override defaults */ |
111 | return 0; |
112 | if (!(flags & TCA_ACT_FLAGS_REPLACE)) { |
113 | tcf_idr_release(a: *a, bind); |
114 | return -EEXIST; |
115 | } |
116 | } else { |
117 | return err; |
118 | } |
119 | |
120 | err = tcf_action_check_ctrlact(action: parm->action, tp, handle: &goto_ch, newchain: extack); |
121 | if (err < 0) |
122 | goto release_idr; |
123 | gact = to_gact(*a); |
124 | |
125 | spin_lock_bh(lock: &gact->tcf_lock); |
126 | goto_ch = tcf_action_set_ctrlact(a: *a, action: parm->action, newchain: goto_ch); |
127 | #ifdef CONFIG_GACT_PROB |
128 | if (p_parm) { |
129 | gact->tcfg_paction = p_parm->paction; |
130 | gact->tcfg_pval = max_t(u16, 1, p_parm->pval); |
131 | /* Make sure tcfg_pval is written before tcfg_ptype |
132 | * coupled with smp_rmb() in gact_net_rand() & gact_determ() |
133 | */ |
134 | smp_wmb(); |
135 | gact->tcfg_ptype = p_parm->ptype; |
136 | } |
137 | #endif |
138 | spin_unlock_bh(lock: &gact->tcf_lock); |
139 | |
140 | if (goto_ch) |
141 | tcf_chain_put_by_act(chain: goto_ch); |
142 | |
143 | return ret; |
144 | release_idr: |
145 | tcf_idr_release(a: *a, bind); |
146 | return err; |
147 | } |
148 | |
149 | TC_INDIRECT_SCOPE int tcf_gact_act(struct sk_buff *skb, |
150 | const struct tc_action *a, |
151 | struct tcf_result *res) |
152 | { |
153 | struct tcf_gact *gact = to_gact(a); |
154 | int action = READ_ONCE(gact->tcf_action); |
155 | |
156 | #ifdef CONFIG_GACT_PROB |
157 | { |
158 | u32 ptype = READ_ONCE(gact->tcfg_ptype); |
159 | |
160 | if (ptype) |
161 | action = gact_rand[ptype](gact); |
162 | } |
163 | #endif |
164 | tcf_action_update_bstats(a: &gact->common, skb); |
165 | if (action == TC_ACT_SHOT) |
166 | tcf_action_inc_drop_qstats(a: &gact->common); |
167 | |
168 | tcf_lastuse_update(tm: &gact->tcf_tm); |
169 | |
170 | return action; |
171 | } |
172 | |
173 | static void tcf_gact_stats_update(struct tc_action *a, u64 bytes, u64 packets, |
174 | u64 drops, u64 lastuse, bool hw) |
175 | { |
176 | struct tcf_gact *gact = to_gact(a); |
177 | int action = READ_ONCE(gact->tcf_action); |
178 | struct tcf_t *tm = &gact->tcf_tm; |
179 | |
180 | tcf_action_update_stats(a, bytes, packets, |
181 | drops: action == TC_ACT_SHOT ? packets : drops, hw); |
182 | tm->lastuse = max_t(u64, tm->lastuse, lastuse); |
183 | } |
184 | |
185 | static int tcf_gact_dump(struct sk_buff *skb, struct tc_action *a, |
186 | int bind, int ref) |
187 | { |
188 | unsigned char *b = skb_tail_pointer(skb); |
189 | struct tcf_gact *gact = to_gact(a); |
190 | struct tc_gact opt = { |
191 | .index = gact->tcf_index, |
192 | .refcnt = refcount_read(r: &gact->tcf_refcnt) - ref, |
193 | .bindcnt = atomic_read(v: &gact->tcf_bindcnt) - bind, |
194 | }; |
195 | struct tcf_t t; |
196 | |
197 | spin_lock_bh(lock: &gact->tcf_lock); |
198 | opt.action = gact->tcf_action; |
199 | if (nla_put(skb, attrtype: TCA_GACT_PARMS, attrlen: sizeof(opt), data: &opt)) |
200 | goto nla_put_failure; |
201 | #ifdef CONFIG_GACT_PROB |
202 | if (gact->tcfg_ptype) { |
203 | struct tc_gact_p p_opt = { |
204 | .paction = gact->tcfg_paction, |
205 | .pval = gact->tcfg_pval, |
206 | .ptype = gact->tcfg_ptype, |
207 | }; |
208 | |
209 | if (nla_put(skb, attrtype: TCA_GACT_PROB, attrlen: sizeof(p_opt), data: &p_opt)) |
210 | goto nla_put_failure; |
211 | } |
212 | #endif |
213 | tcf_tm_dump(dtm: &t, stm: &gact->tcf_tm); |
214 | if (nla_put_64bit(skb, attrtype: TCA_GACT_TM, attrlen: sizeof(t), data: &t, padattr: TCA_GACT_PAD)) |
215 | goto nla_put_failure; |
216 | spin_unlock_bh(lock: &gact->tcf_lock); |
217 | |
218 | return skb->len; |
219 | |
220 | nla_put_failure: |
221 | spin_unlock_bh(lock: &gact->tcf_lock); |
222 | nlmsg_trim(skb, mark: b); |
223 | return -1; |
224 | } |
225 | |
226 | static size_t tcf_gact_get_fill_size(const struct tc_action *act) |
227 | { |
228 | size_t sz = nla_total_size(payload: sizeof(struct tc_gact)); /* TCA_GACT_PARMS */ |
229 | |
230 | #ifdef CONFIG_GACT_PROB |
231 | if (to_gact(act)->tcfg_ptype) |
232 | /* TCA_GACT_PROB */ |
233 | sz += nla_total_size(payload: sizeof(struct tc_gact_p)); |
234 | #endif |
235 | |
236 | return sz; |
237 | } |
238 | |
239 | static int tcf_gact_offload_act_setup(struct tc_action *act, void *entry_data, |
240 | u32 *index_inc, bool bind, |
241 | struct netlink_ext_ack *extack) |
242 | { |
243 | if (bind) { |
244 | struct flow_action_entry *entry = entry_data; |
245 | |
246 | if (is_tcf_gact_ok(a: act)) { |
247 | entry->id = FLOW_ACTION_ACCEPT; |
248 | } else if (is_tcf_gact_shot(a: act)) { |
249 | entry->id = FLOW_ACTION_DROP; |
250 | } else if (is_tcf_gact_trap(a: act)) { |
251 | entry->id = FLOW_ACTION_TRAP; |
252 | } else if (is_tcf_gact_goto_chain(a: act)) { |
253 | entry->id = FLOW_ACTION_GOTO; |
254 | entry->chain_index = tcf_gact_goto_chain_index(a: act); |
255 | } else if (is_tcf_gact_continue(a: act)) { |
256 | NL_SET_ERR_MSG_MOD(extack, "Offload of \"continue\" action is not supported" ); |
257 | return -EOPNOTSUPP; |
258 | } else if (is_tcf_gact_reclassify(a: act)) { |
259 | NL_SET_ERR_MSG_MOD(extack, "Offload of \"reclassify\" action is not supported" ); |
260 | return -EOPNOTSUPP; |
261 | } else if (is_tcf_gact_pipe(a: act)) { |
262 | NL_SET_ERR_MSG_MOD(extack, "Offload of \"pipe\" action is not supported" ); |
263 | return -EOPNOTSUPP; |
264 | } else { |
265 | NL_SET_ERR_MSG_MOD(extack, "Unsupported generic action offload" ); |
266 | return -EOPNOTSUPP; |
267 | } |
268 | *index_inc = 1; |
269 | } else { |
270 | struct flow_offload_action *fl_action = entry_data; |
271 | |
272 | if (is_tcf_gact_ok(a: act)) |
273 | fl_action->id = FLOW_ACTION_ACCEPT; |
274 | else if (is_tcf_gact_shot(a: act)) |
275 | fl_action->id = FLOW_ACTION_DROP; |
276 | else if (is_tcf_gact_trap(a: act)) |
277 | fl_action->id = FLOW_ACTION_TRAP; |
278 | else if (is_tcf_gact_goto_chain(a: act)) |
279 | fl_action->id = FLOW_ACTION_GOTO; |
280 | else |
281 | return -EOPNOTSUPP; |
282 | } |
283 | |
284 | return 0; |
285 | } |
286 | |
287 | static struct tc_action_ops act_gact_ops = { |
288 | .kind = "gact" , |
289 | .id = TCA_ID_GACT, |
290 | .owner = THIS_MODULE, |
291 | .act = tcf_gact_act, |
292 | .stats_update = tcf_gact_stats_update, |
293 | .dump = tcf_gact_dump, |
294 | .init = tcf_gact_init, |
295 | .get_fill_size = tcf_gact_get_fill_size, |
296 | .offload_act_setup = tcf_gact_offload_act_setup, |
297 | .size = sizeof(struct tcf_gact), |
298 | }; |
299 | |
300 | static __net_init int gact_init_net(struct net *net) |
301 | { |
302 | struct tc_action_net *tn = net_generic(net, id: act_gact_ops.net_id); |
303 | |
304 | return tc_action_net_init(net, tn, ops: &act_gact_ops); |
305 | } |
306 | |
307 | static void __net_exit gact_exit_net(struct list_head *net_list) |
308 | { |
309 | tc_action_net_exit(net_list, id: act_gact_ops.net_id); |
310 | } |
311 | |
312 | static struct pernet_operations gact_net_ops = { |
313 | .init = gact_init_net, |
314 | .exit_batch = gact_exit_net, |
315 | .id = &act_gact_ops.net_id, |
316 | .size = sizeof(struct tc_action_net), |
317 | }; |
318 | |
319 | MODULE_AUTHOR("Jamal Hadi Salim(2002-4)" ); |
320 | MODULE_DESCRIPTION("Generic Classifier actions" ); |
321 | MODULE_LICENSE("GPL" ); |
322 | |
323 | static int __init gact_init_module(void) |
324 | { |
325 | #ifdef CONFIG_GACT_PROB |
326 | pr_info("GACT probability on\n" ); |
327 | #else |
328 | pr_info("GACT probability NOT on\n" ); |
329 | #endif |
330 | |
331 | return tcf_register_action(a: &act_gact_ops, ops: &gact_net_ops); |
332 | } |
333 | |
334 | static void __exit gact_cleanup_module(void) |
335 | { |
336 | tcf_unregister_action(a: &act_gact_ops, ops: &gact_net_ops); |
337 | } |
338 | |
339 | module_init(gact_init_module); |
340 | module_exit(gact_cleanup_module); |
341 | |