diff options
Diffstat (limited to 'net/sched/cls_fw.c')
-rw-r--r-- | net/sched/cls_fw.c | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/net/sched/cls_fw.c b/net/sched/cls_fw.c new file mode 100644 index 000000000000..fdfc83af3d1f --- /dev/null +++ b/net/sched/cls_fw.c | |||
@@ -0,0 +1,378 @@ | |||
1 | /* | ||
2 | * net/sched/cls_fw.c Classifier mapping ipchains' fwmark to traffic class. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License | ||
6 | * as published by the Free Software Foundation; either version | ||
7 | * 2 of the License, or (at your option) any later version. | ||
8 | * | ||
9 | * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> | ||
10 | * | ||
11 | * Changes: | ||
12 | * Karlis Peisenieks <karlis@mt.lv> : 990415 : fw_walk off by one | ||
13 | * Karlis Peisenieks <karlis@mt.lv> : 990415 : fw_delete killed all the filter (and kernel). | ||
14 | * Alex <alex@pilotsoft.com> : 2004xxyy: Added Action extension | ||
15 | * | ||
16 | * JHS: We should remove the CONFIG_NET_CLS_IND from here | ||
17 | * eventually when the meta match extension is made available | ||
18 | * | ||
19 | */ | ||
20 | |||
21 | #include <linux/config.h> | ||
22 | #include <linux/module.h> | ||
23 | #include <asm/uaccess.h> | ||
24 | #include <asm/system.h> | ||
25 | #include <linux/bitops.h> | ||
26 | #include <linux/types.h> | ||
27 | #include <linux/kernel.h> | ||
28 | #include <linux/sched.h> | ||
29 | #include <linux/string.h> | ||
30 | #include <linux/mm.h> | ||
31 | #include <linux/socket.h> | ||
32 | #include <linux/sockios.h> | ||
33 | #include <linux/in.h> | ||
34 | #include <linux/errno.h> | ||
35 | #include <linux/interrupt.h> | ||
36 | #include <linux/if_ether.h> | ||
37 | #include <linux/inet.h> | ||
38 | #include <linux/netdevice.h> | ||
39 | #include <linux/etherdevice.h> | ||
40 | #include <linux/notifier.h> | ||
41 | #include <linux/netfilter.h> | ||
42 | #include <net/ip.h> | ||
43 | #include <net/route.h> | ||
44 | #include <linux/skbuff.h> | ||
45 | #include <net/sock.h> | ||
46 | #include <net/act_api.h> | ||
47 | #include <net/pkt_cls.h> | ||
48 | |||
49 | struct fw_head | ||
50 | { | ||
51 | struct fw_filter *ht[256]; | ||
52 | }; | ||
53 | |||
54 | struct fw_filter | ||
55 | { | ||
56 | struct fw_filter *next; | ||
57 | u32 id; | ||
58 | struct tcf_result res; | ||
59 | #ifdef CONFIG_NET_CLS_IND | ||
60 | char indev[IFNAMSIZ]; | ||
61 | #endif /* CONFIG_NET_CLS_IND */ | ||
62 | struct tcf_exts exts; | ||
63 | }; | ||
64 | |||
65 | static struct tcf_ext_map fw_ext_map = { | ||
66 | .action = TCA_FW_ACT, | ||
67 | .police = TCA_FW_POLICE | ||
68 | }; | ||
69 | |||
70 | static __inline__ int fw_hash(u32 handle) | ||
71 | { | ||
72 | return handle&0xFF; | ||
73 | } | ||
74 | |||
75 | static int fw_classify(struct sk_buff *skb, struct tcf_proto *tp, | ||
76 | struct tcf_result *res) | ||
77 | { | ||
78 | struct fw_head *head = (struct fw_head*)tp->root; | ||
79 | struct fw_filter *f; | ||
80 | int r; | ||
81 | #ifdef CONFIG_NETFILTER | ||
82 | u32 id = skb->nfmark; | ||
83 | #else | ||
84 | u32 id = 0; | ||
85 | #endif | ||
86 | |||
87 | if (head != NULL) { | ||
88 | for (f=head->ht[fw_hash(id)]; f; f=f->next) { | ||
89 | if (f->id == id) { | ||
90 | *res = f->res; | ||
91 | #ifdef CONFIG_NET_CLS_IND | ||
92 | if (!tcf_match_indev(skb, f->indev)) | ||
93 | continue; | ||
94 | #endif /* CONFIG_NET_CLS_IND */ | ||
95 | r = tcf_exts_exec(skb, &f->exts, res); | ||
96 | if (r < 0) | ||
97 | continue; | ||
98 | |||
99 | return r; | ||
100 | } | ||
101 | } | ||
102 | } else { | ||
103 | /* old method */ | ||
104 | if (id && (TC_H_MAJ(id) == 0 || !(TC_H_MAJ(id^tp->q->handle)))) { | ||
105 | res->classid = id; | ||
106 | res->class = 0; | ||
107 | return 0; | ||
108 | } | ||
109 | } | ||
110 | |||
111 | return -1; | ||
112 | } | ||
113 | |||
114 | static unsigned long fw_get(struct tcf_proto *tp, u32 handle) | ||
115 | { | ||
116 | struct fw_head *head = (struct fw_head*)tp->root; | ||
117 | struct fw_filter *f; | ||
118 | |||
119 | if (head == NULL) | ||
120 | return 0; | ||
121 | |||
122 | for (f=head->ht[fw_hash(handle)]; f; f=f->next) { | ||
123 | if (f->id == handle) | ||
124 | return (unsigned long)f; | ||
125 | } | ||
126 | return 0; | ||
127 | } | ||
128 | |||
129 | static void fw_put(struct tcf_proto *tp, unsigned long f) | ||
130 | { | ||
131 | } | ||
132 | |||
133 | static int fw_init(struct tcf_proto *tp) | ||
134 | { | ||
135 | return 0; | ||
136 | } | ||
137 | |||
138 | static inline void | ||
139 | fw_delete_filter(struct tcf_proto *tp, struct fw_filter *f) | ||
140 | { | ||
141 | tcf_unbind_filter(tp, &f->res); | ||
142 | tcf_exts_destroy(tp, &f->exts); | ||
143 | kfree(f); | ||
144 | } | ||
145 | |||
146 | static void fw_destroy(struct tcf_proto *tp) | ||
147 | { | ||
148 | struct fw_head *head = (struct fw_head*)xchg(&tp->root, NULL); | ||
149 | struct fw_filter *f; | ||
150 | int h; | ||
151 | |||
152 | if (head == NULL) | ||
153 | return; | ||
154 | |||
155 | for (h=0; h<256; h++) { | ||
156 | while ((f=head->ht[h]) != NULL) { | ||
157 | head->ht[h] = f->next; | ||
158 | fw_delete_filter(tp, f); | ||
159 | } | ||
160 | } | ||
161 | kfree(head); | ||
162 | } | ||
163 | |||
164 | static int fw_delete(struct tcf_proto *tp, unsigned long arg) | ||
165 | { | ||
166 | struct fw_head *head = (struct fw_head*)tp->root; | ||
167 | struct fw_filter *f = (struct fw_filter*)arg; | ||
168 | struct fw_filter **fp; | ||
169 | |||
170 | if (head == NULL || f == NULL) | ||
171 | goto out; | ||
172 | |||
173 | for (fp=&head->ht[fw_hash(f->id)]; *fp; fp = &(*fp)->next) { | ||
174 | if (*fp == f) { | ||
175 | tcf_tree_lock(tp); | ||
176 | *fp = f->next; | ||
177 | tcf_tree_unlock(tp); | ||
178 | fw_delete_filter(tp, f); | ||
179 | return 0; | ||
180 | } | ||
181 | } | ||
182 | out: | ||
183 | return -EINVAL; | ||
184 | } | ||
185 | |||
186 | static int | ||
187 | fw_change_attrs(struct tcf_proto *tp, struct fw_filter *f, | ||
188 | struct rtattr **tb, struct rtattr **tca, unsigned long base) | ||
189 | { | ||
190 | struct tcf_exts e; | ||
191 | int err; | ||
192 | |||
193 | err = tcf_exts_validate(tp, tb, tca[TCA_RATE-1], &e, &fw_ext_map); | ||
194 | if (err < 0) | ||
195 | return err; | ||
196 | |||
197 | err = -EINVAL; | ||
198 | if (tb[TCA_FW_CLASSID-1]) { | ||
199 | if (RTA_PAYLOAD(tb[TCA_FW_CLASSID-1]) != sizeof(u32)) | ||
200 | goto errout; | ||
201 | f->res.classid = *(u32*)RTA_DATA(tb[TCA_FW_CLASSID-1]); | ||
202 | tcf_bind_filter(tp, &f->res, base); | ||
203 | } | ||
204 | |||
205 | #ifdef CONFIG_NET_CLS_IND | ||
206 | if (tb[TCA_FW_INDEV-1]) { | ||
207 | err = tcf_change_indev(tp, f->indev, tb[TCA_FW_INDEV-1]); | ||
208 | if (err < 0) | ||
209 | goto errout; | ||
210 | } | ||
211 | #endif /* CONFIG_NET_CLS_IND */ | ||
212 | |||
213 | tcf_exts_change(tp, &f->exts, &e); | ||
214 | |||
215 | return 0; | ||
216 | errout: | ||
217 | tcf_exts_destroy(tp, &e); | ||
218 | return err; | ||
219 | } | ||
220 | |||
221 | static int fw_change(struct tcf_proto *tp, unsigned long base, | ||
222 | u32 handle, | ||
223 | struct rtattr **tca, | ||
224 | unsigned long *arg) | ||
225 | { | ||
226 | struct fw_head *head = (struct fw_head*)tp->root; | ||
227 | struct fw_filter *f = (struct fw_filter *) *arg; | ||
228 | struct rtattr *opt = tca[TCA_OPTIONS-1]; | ||
229 | struct rtattr *tb[TCA_FW_MAX]; | ||
230 | int err; | ||
231 | |||
232 | if (!opt) | ||
233 | return handle ? -EINVAL : 0; | ||
234 | |||
235 | if (rtattr_parse_nested(tb, TCA_FW_MAX, opt) < 0) | ||
236 | return -EINVAL; | ||
237 | |||
238 | if (f != NULL) { | ||
239 | if (f->id != handle && handle) | ||
240 | return -EINVAL; | ||
241 | return fw_change_attrs(tp, f, tb, tca, base); | ||
242 | } | ||
243 | |||
244 | if (!handle) | ||
245 | return -EINVAL; | ||
246 | |||
247 | if (head == NULL) { | ||
248 | head = kmalloc(sizeof(struct fw_head), GFP_KERNEL); | ||
249 | if (head == NULL) | ||
250 | return -ENOBUFS; | ||
251 | memset(head, 0, sizeof(*head)); | ||
252 | |||
253 | tcf_tree_lock(tp); | ||
254 | tp->root = head; | ||
255 | tcf_tree_unlock(tp); | ||
256 | } | ||
257 | |||
258 | f = kmalloc(sizeof(struct fw_filter), GFP_KERNEL); | ||
259 | if (f == NULL) | ||
260 | return -ENOBUFS; | ||
261 | memset(f, 0, sizeof(*f)); | ||
262 | |||
263 | f->id = handle; | ||
264 | |||
265 | err = fw_change_attrs(tp, f, tb, tca, base); | ||
266 | if (err < 0) | ||
267 | goto errout; | ||
268 | |||
269 | f->next = head->ht[fw_hash(handle)]; | ||
270 | tcf_tree_lock(tp); | ||
271 | head->ht[fw_hash(handle)] = f; | ||
272 | tcf_tree_unlock(tp); | ||
273 | |||
274 | *arg = (unsigned long)f; | ||
275 | return 0; | ||
276 | |||
277 | errout: | ||
278 | if (f) | ||
279 | kfree(f); | ||
280 | return err; | ||
281 | } | ||
282 | |||
283 | static void fw_walk(struct tcf_proto *tp, struct tcf_walker *arg) | ||
284 | { | ||
285 | struct fw_head *head = (struct fw_head*)tp->root; | ||
286 | int h; | ||
287 | |||
288 | if (head == NULL) | ||
289 | arg->stop = 1; | ||
290 | |||
291 | if (arg->stop) | ||
292 | return; | ||
293 | |||
294 | for (h = 0; h < 256; h++) { | ||
295 | struct fw_filter *f; | ||
296 | |||
297 | for (f = head->ht[h]; f; f = f->next) { | ||
298 | if (arg->count < arg->skip) { | ||
299 | arg->count++; | ||
300 | continue; | ||
301 | } | ||
302 | if (arg->fn(tp, (unsigned long)f, arg) < 0) { | ||
303 | arg->stop = 1; | ||
304 | return; | ||
305 | } | ||
306 | arg->count++; | ||
307 | } | ||
308 | } | ||
309 | } | ||
310 | |||
311 | static int fw_dump(struct tcf_proto *tp, unsigned long fh, | ||
312 | struct sk_buff *skb, struct tcmsg *t) | ||
313 | { | ||
314 | struct fw_filter *f = (struct fw_filter*)fh; | ||
315 | unsigned char *b = skb->tail; | ||
316 | struct rtattr *rta; | ||
317 | |||
318 | if (f == NULL) | ||
319 | return skb->len; | ||
320 | |||
321 | t->tcm_handle = f->id; | ||
322 | |||
323 | if (!f->res.classid && !tcf_exts_is_available(&f->exts)) | ||
324 | return skb->len; | ||
325 | |||
326 | rta = (struct rtattr*)b; | ||
327 | RTA_PUT(skb, TCA_OPTIONS, 0, NULL); | ||
328 | |||
329 | if (f->res.classid) | ||
330 | RTA_PUT(skb, TCA_FW_CLASSID, 4, &f->res.classid); | ||
331 | #ifdef CONFIG_NET_CLS_IND | ||
332 | if (strlen(f->indev)) | ||
333 | RTA_PUT(skb, TCA_FW_INDEV, IFNAMSIZ, f->indev); | ||
334 | #endif /* CONFIG_NET_CLS_IND */ | ||
335 | |||
336 | if (tcf_exts_dump(skb, &f->exts, &fw_ext_map) < 0) | ||
337 | goto rtattr_failure; | ||
338 | |||
339 | rta->rta_len = skb->tail - b; | ||
340 | |||
341 | if (tcf_exts_dump_stats(skb, &f->exts, &fw_ext_map) < 0) | ||
342 | goto rtattr_failure; | ||
343 | |||
344 | return skb->len; | ||
345 | |||
346 | rtattr_failure: | ||
347 | skb_trim(skb, b - skb->data); | ||
348 | return -1; | ||
349 | } | ||
350 | |||
351 | static struct tcf_proto_ops cls_fw_ops = { | ||
352 | .next = NULL, | ||
353 | .kind = "fw", | ||
354 | .classify = fw_classify, | ||
355 | .init = fw_init, | ||
356 | .destroy = fw_destroy, | ||
357 | .get = fw_get, | ||
358 | .put = fw_put, | ||
359 | .change = fw_change, | ||
360 | .delete = fw_delete, | ||
361 | .walk = fw_walk, | ||
362 | .dump = fw_dump, | ||
363 | .owner = THIS_MODULE, | ||
364 | }; | ||
365 | |||
366 | static int __init init_fw(void) | ||
367 | { | ||
368 | return register_tcf_proto_ops(&cls_fw_ops); | ||
369 | } | ||
370 | |||
371 | static void __exit exit_fw(void) | ||
372 | { | ||
373 | unregister_tcf_proto_ops(&cls_fw_ops); | ||
374 | } | ||
375 | |||
376 | module_init(init_fw) | ||
377 | module_exit(exit_fw) | ||
378 | MODULE_LICENSE("GPL"); | ||