Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1da177e4 LT |
2 | /* |
3 | * net/sched/cls_basic.c Basic Packet Classifier. | |
4 | * | |
1da177e4 LT |
5 | * Authors: Thomas Graf <tgraf@suug.ch> |
6 | */ | |
7 | ||
1da177e4 | 8 | #include <linux/module.h> |
5a0e3ad6 | 9 | #include <linux/slab.h> |
1da177e4 LT |
10 | #include <linux/types.h> |
11 | #include <linux/kernel.h> | |
1da177e4 | 12 | #include <linux/string.h> |
1da177e4 LT |
13 | #include <linux/errno.h> |
14 | #include <linux/rtnetlink.h> | |
15 | #include <linux/skbuff.h> | |
1d8134fe | 16 | #include <linux/idr.h> |
5954894b | 17 | #include <linux/percpu.h> |
dc5fc579 | 18 | #include <net/netlink.h> |
1da177e4 LT |
19 | #include <net/act_api.h> |
20 | #include <net/pkt_cls.h> | |
9f3101dc | 21 | #include <net/tc_wrapper.h> |
1da177e4 | 22 | |
cc7ec456 | 23 | struct basic_head { |
1da177e4 | 24 | struct list_head flist; |
1d8134fe | 25 | struct idr handle_idr; |
9888faef | 26 | struct rcu_head rcu; |
1da177e4 LT |
27 | }; |
28 | ||
cc7ec456 | 29 | struct basic_filter { |
1da177e4 LT |
30 | u32 handle; |
31 | struct tcf_exts exts; | |
32 | struct tcf_ematch_tree ematches; | |
33 | struct tcf_result res; | |
9888faef | 34 | struct tcf_proto *tp; |
1da177e4 | 35 | struct list_head link; |
5954894b | 36 | struct tc_basic_pcnt __percpu *pf; |
aaa908ff | 37 | struct rcu_work rwork; |
1da177e4 LT |
38 | }; |
39 | ||
9f3101dc PT |
40 | TC_INDIRECT_SCOPE int basic_classify(struct sk_buff *skb, |
41 | const struct tcf_proto *tp, | |
42 | struct tcf_result *res) | |
1da177e4 LT |
43 | { |
44 | int r; | |
9888faef | 45 | struct basic_head *head = rcu_dereference_bh(tp->root); |
1da177e4 LT |
46 | struct basic_filter *f; |
47 | ||
9888faef | 48 | list_for_each_entry_rcu(f, &head->flist, link) { |
5954894b | 49 | __this_cpu_inc(f->pf->rcnt); |
1da177e4 LT |
50 | if (!tcf_em_tree_match(skb, &f->ematches, NULL)) |
51 | continue; | |
5954894b | 52 | __this_cpu_inc(f->pf->rhit); |
1da177e4 LT |
53 | *res = f->res; |
54 | r = tcf_exts_exec(skb, &f->exts, res); | |
55 | if (r < 0) | |
56 | continue; | |
57 | return r; | |
58 | } | |
59 | return -1; | |
60 | } | |
61 | ||
8113c095 | 62 | static void *basic_get(struct tcf_proto *tp, u32 handle) |
1da177e4 | 63 | { |
9888faef | 64 | struct basic_head *head = rtnl_dereference(tp->root); |
1da177e4 LT |
65 | struct basic_filter *f; |
66 | ||
1c1bc6bd DB |
67 | list_for_each_entry(f, &head->flist, link) { |
68 | if (f->handle == handle) { | |
8113c095 | 69 | return f; |
1c1bc6bd DB |
70 | } |
71 | } | |
1da177e4 | 72 | |
8113c095 | 73 | return NULL; |
1da177e4 LT |
74 | } |
75 | ||
1da177e4 LT |
76 | static int basic_init(struct tcf_proto *tp) |
77 | { | |
d3fa76ee PM |
78 | struct basic_head *head; |
79 | ||
80 | head = kzalloc(sizeof(*head), GFP_KERNEL); | |
81 | if (head == NULL) | |
82 | return -ENOBUFS; | |
83 | INIT_LIST_HEAD(&head->flist); | |
1d8134fe | 84 | idr_init(&head->handle_idr); |
9888faef | 85 | rcu_assign_pointer(tp->root, head); |
1da177e4 LT |
86 | return 0; |
87 | } | |
88 | ||
0b2a5989 CW |
89 | static void __basic_delete_filter(struct basic_filter *f) |
90 | { | |
91 | tcf_exts_destroy(&f->exts); | |
92 | tcf_em_tree_destroy(&f->ematches); | |
93 | tcf_exts_put_net(&f->exts); | |
5954894b | 94 | free_percpu(f->pf); |
0b2a5989 CW |
95 | kfree(f); |
96 | } | |
97 | ||
c96a4838 | 98 | static void basic_delete_filter_work(struct work_struct *work) |
1da177e4 | 99 | { |
aaa908ff CW |
100 | struct basic_filter *f = container_of(to_rcu_work(work), |
101 | struct basic_filter, | |
102 | rwork); | |
c96a4838 | 103 | rtnl_lock(); |
0b2a5989 | 104 | __basic_delete_filter(f); |
c96a4838 | 105 | rtnl_unlock(); |
1da177e4 LT |
106 | } |
107 | ||
12db03b6 VB |
108 | static void basic_destroy(struct tcf_proto *tp, bool rtnl_held, |
109 | struct netlink_ext_ack *extack) | |
1da177e4 | 110 | { |
9888faef | 111 | struct basic_head *head = rtnl_dereference(tp->root); |
1da177e4 | 112 | struct basic_filter *f, *n; |
10297b99 | 113 | |
1da177e4 | 114 | list_for_each_entry_safe(f, n, &head->flist, link) { |
9888faef | 115 | list_del_rcu(&f->link); |
18cdb37e | 116 | tcf_unbind_filter(tp, &f->res); |
9c160941 | 117 | idr_remove(&head->handle_idr, f->handle); |
0b2a5989 | 118 | if (tcf_exts_get_net(&f->exts)) |
aaa908ff | 119 | tcf_queue_work(&f->rwork, basic_delete_filter_work); |
0b2a5989 CW |
120 | else |
121 | __basic_delete_filter(f); | |
1da177e4 | 122 | } |
1d8134fe | 123 | idr_destroy(&head->handle_idr); |
9888faef | 124 | kfree_rcu(head, rcu); |
1da177e4 LT |
125 | } |
126 | ||
571acf21 | 127 | static int basic_delete(struct tcf_proto *tp, void *arg, bool *last, |
12db03b6 | 128 | bool rtnl_held, struct netlink_ext_ack *extack) |
1da177e4 | 129 | { |
763dbf63 | 130 | struct basic_head *head = rtnl_dereference(tp->root); |
8113c095 | 131 | struct basic_filter *f = arg; |
1da177e4 | 132 | |
e4386456 JP |
133 | list_del_rcu(&f->link); |
134 | tcf_unbind_filter(tp, &f->res); | |
9c160941 | 135 | idr_remove(&head->handle_idr, f->handle); |
0b2a5989 | 136 | tcf_exts_get_net(&f->exts); |
aaa908ff | 137 | tcf_queue_work(&f->rwork, basic_delete_filter_work); |
763dbf63 | 138 | *last = list_empty(&head->flist); |
e4386456 | 139 | return 0; |
1da177e4 LT |
140 | } |
141 | ||
6fa8c014 PM |
142 | static const struct nla_policy basic_policy[TCA_BASIC_MAX + 1] = { |
143 | [TCA_BASIC_CLASSID] = { .type = NLA_U32 }, | |
144 | [TCA_BASIC_EMATCHES] = { .type = NLA_NESTED }, | |
145 | }; | |
146 | ||
c1b52739 BL |
147 | static int basic_set_parms(struct net *net, struct tcf_proto *tp, |
148 | struct basic_filter *f, unsigned long base, | |
149 | struct nlattr **tb, | |
695176bf | 150 | struct nlattr *est, u32 flags, |
50a56190 | 151 | struct netlink_ext_ack *extack) |
1da177e4 | 152 | { |
6459082a | 153 | int err; |
1da177e4 | 154 | |
695176bf | 155 | err = tcf_exts_validate(net, tp, tb, est, &f->exts, flags, extack); |
1da177e4 LT |
156 | if (err < 0) |
157 | return err; | |
158 | ||
4ebc1e3c | 159 | err = tcf_em_tree_validate(tp, tb[TCA_BASIC_EMATCHES], &f->ematches); |
1da177e4 | 160 | if (err < 0) |
ff1f8ca0 | 161 | return err; |
1da177e4 | 162 | |
add93b61 | 163 | if (tb[TCA_BASIC_CLASSID]) { |
1587bac4 | 164 | f->res.classid = nla_get_u32(tb[TCA_BASIC_CLASSID]); |
1da177e4 LT |
165 | tcf_bind_filter(tp, &f->res, base); |
166 | } | |
167 | ||
9888faef | 168 | f->tp = tp; |
1da177e4 | 169 | return 0; |
1da177e4 LT |
170 | } |
171 | ||
c1b52739 | 172 | static int basic_change(struct net *net, struct sk_buff *in_skb, |
af4c6641 | 173 | struct tcf_proto *tp, unsigned long base, u32 handle, |
695176bf CW |
174 | struct nlattr **tca, void **arg, |
175 | u32 flags, struct netlink_ext_ack *extack) | |
1da177e4 | 176 | { |
cee63723 | 177 | int err; |
9888faef | 178 | struct basic_head *head = rtnl_dereference(tp->root); |
add93b61 | 179 | struct nlattr *tb[TCA_BASIC_MAX + 1]; |
9888faef JF |
180 | struct basic_filter *fold = (struct basic_filter *) *arg; |
181 | struct basic_filter *fnew; | |
1da177e4 | 182 | |
add93b61 | 183 | if (tca[TCA_OPTIONS] == NULL) |
1da177e4 LT |
184 | return -EINVAL; |
185 | ||
8cb08174 JB |
186 | err = nla_parse_nested_deprecated(tb, TCA_BASIC_MAX, tca[TCA_OPTIONS], |
187 | basic_policy, NULL); | |
cee63723 PM |
188 | if (err < 0) |
189 | return err; | |
1da177e4 | 190 | |
9888faef JF |
191 | if (fold != NULL) { |
192 | if (handle && fold->handle != handle) | |
1da177e4 | 193 | return -EINVAL; |
1da177e4 LT |
194 | } |
195 | ||
9888faef | 196 | fnew = kzalloc(sizeof(*fnew), GFP_KERNEL); |
bd42b788 JP |
197 | if (!fnew) |
198 | return -ENOBUFS; | |
1da177e4 | 199 | |
14215108 | 200 | err = tcf_exts_init(&fnew->exts, net, TCA_BASIC_ACT, TCA_BASIC_POLICE); |
b9a24bb7 WC |
201 | if (err < 0) |
202 | goto errout; | |
203 | ||
05af0ebb MW |
204 | if (!handle) { |
205 | handle = 1; | |
206 | err = idr_alloc_u32(&head->handle_idr, fnew, &handle, | |
207 | INT_MAX, GFP_KERNEL); | |
208 | } else if (!fold) { | |
209 | err = idr_alloc_u32(&head->handle_idr, fnew, &handle, | |
210 | handle, GFP_KERNEL); | |
1da177e4 | 211 | } |
05af0ebb MW |
212 | if (err) |
213 | goto errout; | |
214 | fnew->handle = handle; | |
5954894b CW |
215 | fnew->pf = alloc_percpu(struct tc_basic_pcnt); |
216 | if (!fnew->pf) { | |
217 | err = -ENOMEM; | |
218 | goto errout; | |
219 | } | |
1da177e4 | 220 | |
695176bf | 221 | err = basic_set_parms(net, tp, fnew, base, tb, tca[TCA_RATE], flags, |
50a56190 | 222 | extack); |
1d8134fe CW |
223 | if (err < 0) { |
224 | if (!fold) | |
9c160941 | 225 | idr_remove(&head->handle_idr, fnew->handle); |
1da177e4 | 226 | goto errout; |
1d8134fe | 227 | } |
1da177e4 | 228 | |
8113c095 | 229 | *arg = fnew; |
9888faef JF |
230 | |
231 | if (fold) { | |
234a4624 | 232 | idr_replace(&head->handle_idr, fnew, fnew->handle); |
9888faef | 233 | list_replace_rcu(&fold->link, &fnew->link); |
18cdb37e | 234 | tcf_unbind_filter(tp, &fold->res); |
0b2a5989 | 235 | tcf_exts_get_net(&fold->exts); |
aaa908ff | 236 | tcf_queue_work(&fold->rwork, basic_delete_filter_work); |
9888faef JF |
237 | } else { |
238 | list_add_rcu(&fnew->link, &head->flist); | |
239 | } | |
1da177e4 LT |
240 | |
241 | return 0; | |
242 | errout: | |
5954894b | 243 | free_percpu(fnew->pf); |
b9a24bb7 | 244 | tcf_exts_destroy(&fnew->exts); |
9888faef | 245 | kfree(fnew); |
1da177e4 LT |
246 | return err; |
247 | } | |
248 | ||
12db03b6 VB |
249 | static void basic_walk(struct tcf_proto *tp, struct tcf_walker *arg, |
250 | bool rtnl_held) | |
1da177e4 | 251 | { |
9888faef | 252 | struct basic_head *head = rtnl_dereference(tp->root); |
1da177e4 LT |
253 | struct basic_filter *f; |
254 | ||
255 | list_for_each_entry(f, &head->flist, link) { | |
5508ff7c | 256 | if (!tc_cls_stats_dump(tp, arg, f)) |
1da177e4 | 257 | break; |
1da177e4 LT |
258 | } |
259 | } | |
260 | ||
2e24cd75 CW |
261 | static void basic_bind_class(void *fh, u32 classid, unsigned long cl, void *q, |
262 | unsigned long base) | |
07d79fc7 CW |
263 | { |
264 | struct basic_filter *f = fh; | |
265 | ||
cc9039a1 | 266 | tc_cls_bind_class(classid, cl, q, &f->res, base); |
07d79fc7 CW |
267 | } |
268 | ||
8113c095 | 269 | static int basic_dump(struct net *net, struct tcf_proto *tp, void *fh, |
12db03b6 | 270 | struct sk_buff *skb, struct tcmsg *t, bool rtnl_held) |
1da177e4 | 271 | { |
5954894b | 272 | struct tc_basic_pcnt gpf = {}; |
8113c095 | 273 | struct basic_filter *f = fh; |
4b3550ef | 274 | struct nlattr *nest; |
5954894b | 275 | int cpu; |
1da177e4 LT |
276 | |
277 | if (f == NULL) | |
278 | return skb->len; | |
279 | ||
280 | t->tcm_handle = f->handle; | |
281 | ||
ae0be8de | 282 | nest = nla_nest_start_noflag(skb, TCA_OPTIONS); |
4b3550ef PM |
283 | if (nest == NULL) |
284 | goto nla_put_failure; | |
1da177e4 | 285 | |
1b34ec43 DM |
286 | if (f->res.classid && |
287 | nla_put_u32(skb, TCA_BASIC_CLASSID, f->res.classid)) | |
288 | goto nla_put_failure; | |
e1e284a4 | 289 | |
5954894b CW |
290 | for_each_possible_cpu(cpu) { |
291 | struct tc_basic_pcnt *pf = per_cpu_ptr(f->pf, cpu); | |
292 | ||
293 | gpf.rcnt += pf->rcnt; | |
294 | gpf.rhit += pf->rhit; | |
295 | } | |
296 | ||
297 | if (nla_put_64bit(skb, TCA_BASIC_PCNT, | |
298 | sizeof(struct tc_basic_pcnt), | |
299 | &gpf, TCA_BASIC_PAD)) | |
300 | goto nla_put_failure; | |
301 | ||
5da57f42 | 302 | if (tcf_exts_dump(skb, &f->exts) < 0 || |
1da177e4 | 303 | tcf_em_tree_dump(skb, &f->ematches, TCA_BASIC_EMATCHES) < 0) |
add93b61 | 304 | goto nla_put_failure; |
1da177e4 | 305 | |
4b3550ef | 306 | nla_nest_end(skb, nest); |
4c46ee52 | 307 | |
5da57f42 | 308 | if (tcf_exts_dump_stats(skb, &f->exts) < 0) |
4c46ee52 | 309 | goto nla_put_failure; |
310 | ||
1da177e4 LT |
311 | return skb->len; |
312 | ||
add93b61 | 313 | nla_put_failure: |
4b3550ef | 314 | nla_nest_cancel(skb, nest); |
1da177e4 LT |
315 | return -1; |
316 | } | |
317 | ||
2eb9d75c | 318 | static struct tcf_proto_ops cls_basic_ops __read_mostly = { |
1da177e4 LT |
319 | .kind = "basic", |
320 | .classify = basic_classify, | |
321 | .init = basic_init, | |
322 | .destroy = basic_destroy, | |
323 | .get = basic_get, | |
1da177e4 LT |
324 | .change = basic_change, |
325 | .delete = basic_delete, | |
326 | .walk = basic_walk, | |
327 | .dump = basic_dump, | |
07d79fc7 | 328 | .bind_class = basic_bind_class, |
1da177e4 LT |
329 | .owner = THIS_MODULE, |
330 | }; | |
331 | ||
332 | static int __init init_basic(void) | |
333 | { | |
334 | return register_tcf_proto_ops(&cls_basic_ops); | |
335 | } | |
336 | ||
10297b99 | 337 | static void __exit exit_basic(void) |
1da177e4 LT |
338 | { |
339 | unregister_tcf_proto_ops(&cls_basic_ops); | |
340 | } | |
341 | ||
342 | module_init(init_basic) | |
343 | module_exit(exit_basic) | |
344 | MODULE_LICENSE("GPL"); |